diff --git a/VimFiles/autoload/omni/common/debug.vim b/VimFiles/autoload/omni/common/debug.vim new file mode 100755 index 0000000..eded649 --- /dev/null +++ b/VimFiles/autoload/omni/common/debug.vim @@ -0,0 +1,32 @@ +" Description: Omni completion debug functions +" Maintainer: Vissale NEANG +" Last Change: 26 sept. 2007 + +let s:CACHE_DEBUG_TRACE = [] + +" Start debug, clear the debug file +function! omni#common#debug#Start() + let s:CACHE_DEBUG_TRACE = [] + call extend(s:CACHE_DEBUG_TRACE, ['============ Debug Start ============']) + call writefile(s:CACHE_DEBUG_TRACE, "Omni.dbg") +endfunc + +" End debug, write to debug file +function! omni#common#debug#End() + call extend(s:CACHE_DEBUG_TRACE, ["============= Debug End ============="]) + call extend(s:CACHE_DEBUG_TRACE, [""]) + call writefile(s:CACHE_DEBUG_TRACE, "Omni.dbg") +endfunc + +" Debug trace function +function! omni#common#debug#Trace(szFuncName, ...) + let szTrace = a:szFuncName + let paramNum = a:0 + if paramNum>0 + let szTrace .= ':' + endif + for i in range(paramNum) + let szTrace = szTrace .' ('. string(eval('a:'.string(i+1))).')' + endfor + call extend(s:CACHE_DEBUG_TRACE, [szTrace]) +endfunc diff --git a/VimFiles/autoload/omni/common/debug.vim b/VimFiles/autoload/omni/common/debug.vim new file mode 100755 index 0000000..eded649 --- /dev/null +++ b/VimFiles/autoload/omni/common/debug.vim @@ -0,0 +1,32 @@ +" Description: Omni completion debug functions +" Maintainer: Vissale NEANG +" Last Change: 26 sept. 2007 + +let s:CACHE_DEBUG_TRACE = [] + +" Start debug, clear the debug file +function! omni#common#debug#Start() + let s:CACHE_DEBUG_TRACE = [] + call extend(s:CACHE_DEBUG_TRACE, ['============ Debug Start ============']) + call writefile(s:CACHE_DEBUG_TRACE, "Omni.dbg") +endfunc + +" End debug, write to debug file +function! omni#common#debug#End() + call extend(s:CACHE_DEBUG_TRACE, ["============= Debug End ============="]) + call extend(s:CACHE_DEBUG_TRACE, [""]) + call writefile(s:CACHE_DEBUG_TRACE, "Omni.dbg") +endfunc + +" Debug trace function +function! omni#common#debug#Trace(szFuncName, ...) + let szTrace = a:szFuncName + let paramNum = a:0 + if paramNum>0 + let szTrace .= ':' + endif + for i in range(paramNum) + let szTrace = szTrace .' ('. string(eval('a:'.string(i+1))).')' + endfor + call extend(s:CACHE_DEBUG_TRACE, [szTrace]) +endfunc diff --git a/VimFiles/autoload/omni/common/utils.vim b/VimFiles/autoload/omni/common/utils.vim new file mode 100755 index 0000000..c880ad2 --- /dev/null +++ b/VimFiles/autoload/omni/common/utils.vim @@ -0,0 +1,67 @@ +" Description: Omni completion utils +" Maintainer: Vissale NEANG +" Last Change: 26 sept. 2007 + +" For sort numbers in list +function! omni#common#utils#CompareNumber(i1, i2) + let num1 = eval(a:i1) + let num2 = eval(a:i2) + return num1 == num2 ? 0 : num1 > num2 ? 1 : -1 +endfunc + +" TagList function calling the vim taglist() with try catch +" The only throwed exception is 'TagList:UserInterrupt' +" We also force the noignorecase option to avoid linear search when calling +" taglist() +function! omni#common#utils#TagList(szTagQuery) + let result = [] + let bUserIgnoreCase = &ignorecase + " Forcing noignorecase search => binary search can be used in taglist() + " if tags in the tag file are sorted + if bUserIgnoreCase + set noignorecase + endif + try + let result = taglist(a:szTagQuery) + catch /^Vim:Interrupt$/ + " Restoring user's setting + if bUserIgnoreCase + set ignorecase + endif + throw 'TagList:UserInterrupt' + catch + "Note: it seems that ctags can generate corrupted files, in this case + "taglist() will fail to read the tagfile and an exception from + "has_add() is thrown + endtry + + " Restoring user's setting + if bUserIgnoreCase + set ignorecase + endif + return result +endfunc + +" Same as TagList but don't throw exception +function! omni#common#utils#TagListNoThrow(szTagQuery) + let result = [] + try + let result = omni#common#utils#TagList(a:szTagQuery) + catch + endtry + return result +endfunc + +" Get the word under the cursor +function! omni#common#utils#GetWordUnderCursor() + let szLine = getline('.') + let startPos = getpos('.')[2]-1 + let startPos = (startPos < 0)? 0 : startPos + if szLine[startPos] =~ '\w' + let startPos = searchpos('\<\w\+', 'cbn', line('.'))[1] - 1 + endif + + let startPos = (startPos < 0)? 0 : startPos + let szResult = matchstr(szLine, '\w\+', startPos) + return szResult +endfunc diff --git a/VimFiles/autoload/omni/common/debug.vim b/VimFiles/autoload/omni/common/debug.vim new file mode 100755 index 0000000..eded649 --- /dev/null +++ b/VimFiles/autoload/omni/common/debug.vim @@ -0,0 +1,32 @@ +" Description: Omni completion debug functions +" Maintainer: Vissale NEANG +" Last Change: 26 sept. 2007 + +let s:CACHE_DEBUG_TRACE = [] + +" Start debug, clear the debug file +function! omni#common#debug#Start() + let s:CACHE_DEBUG_TRACE = [] + call extend(s:CACHE_DEBUG_TRACE, ['============ Debug Start ============']) + call writefile(s:CACHE_DEBUG_TRACE, "Omni.dbg") +endfunc + +" End debug, write to debug file +function! omni#common#debug#End() + call extend(s:CACHE_DEBUG_TRACE, ["============= Debug End ============="]) + call extend(s:CACHE_DEBUG_TRACE, [""]) + call writefile(s:CACHE_DEBUG_TRACE, "Omni.dbg") +endfunc + +" Debug trace function +function! omni#common#debug#Trace(szFuncName, ...) + let szTrace = a:szFuncName + let paramNum = a:0 + if paramNum>0 + let szTrace .= ':' + endif + for i in range(paramNum) + let szTrace = szTrace .' ('. string(eval('a:'.string(i+1))).')' + endfor + call extend(s:CACHE_DEBUG_TRACE, [szTrace]) +endfunc diff --git a/VimFiles/autoload/omni/common/utils.vim b/VimFiles/autoload/omni/common/utils.vim new file mode 100755 index 0000000..c880ad2 --- /dev/null +++ b/VimFiles/autoload/omni/common/utils.vim @@ -0,0 +1,67 @@ +" Description: Omni completion utils +" Maintainer: Vissale NEANG +" Last Change: 26 sept. 2007 + +" For sort numbers in list +function! omni#common#utils#CompareNumber(i1, i2) + let num1 = eval(a:i1) + let num2 = eval(a:i2) + return num1 == num2 ? 0 : num1 > num2 ? 1 : -1 +endfunc + +" TagList function calling the vim taglist() with try catch +" The only throwed exception is 'TagList:UserInterrupt' +" We also force the noignorecase option to avoid linear search when calling +" taglist() +function! omni#common#utils#TagList(szTagQuery) + let result = [] + let bUserIgnoreCase = &ignorecase + " Forcing noignorecase search => binary search can be used in taglist() + " if tags in the tag file are sorted + if bUserIgnoreCase + set noignorecase + endif + try + let result = taglist(a:szTagQuery) + catch /^Vim:Interrupt$/ + " Restoring user's setting + if bUserIgnoreCase + set ignorecase + endif + throw 'TagList:UserInterrupt' + catch + "Note: it seems that ctags can generate corrupted files, in this case + "taglist() will fail to read the tagfile and an exception from + "has_add() is thrown + endtry + + " Restoring user's setting + if bUserIgnoreCase + set ignorecase + endif + return result +endfunc + +" Same as TagList but don't throw exception +function! omni#common#utils#TagListNoThrow(szTagQuery) + let result = [] + try + let result = omni#common#utils#TagList(a:szTagQuery) + catch + endtry + return result +endfunc + +" Get the word under the cursor +function! omni#common#utils#GetWordUnderCursor() + let szLine = getline('.') + let startPos = getpos('.')[2]-1 + let startPos = (startPos < 0)? 0 : startPos + if szLine[startPos] =~ '\w' + let startPos = searchpos('\<\w\+', 'cbn', line('.'))[1] - 1 + endif + + let startPos = (startPos < 0)? 0 : startPos + let szResult = matchstr(szLine, '\w\+', startPos) + return szResult +endfunc diff --git a/VimFiles/autoload/omni/cpp/complete.vim b/VimFiles/autoload/omni/cpp/complete.vim new file mode 100755 index 0000000..a7e4edc --- /dev/null +++ b/VimFiles/autoload/omni/cpp/complete.vim @@ -0,0 +1,569 @@ +" 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 diff --git a/VimFiles/autoload/omni/common/debug.vim b/VimFiles/autoload/omni/common/debug.vim new file mode 100755 index 0000000..eded649 --- /dev/null +++ b/VimFiles/autoload/omni/common/debug.vim @@ -0,0 +1,32 @@ +" Description: Omni completion debug functions +" Maintainer: Vissale NEANG +" Last Change: 26 sept. 2007 + +let s:CACHE_DEBUG_TRACE = [] + +" Start debug, clear the debug file +function! omni#common#debug#Start() + let s:CACHE_DEBUG_TRACE = [] + call extend(s:CACHE_DEBUG_TRACE, ['============ Debug Start ============']) + call writefile(s:CACHE_DEBUG_TRACE, "Omni.dbg") +endfunc + +" End debug, write to debug file +function! omni#common#debug#End() + call extend(s:CACHE_DEBUG_TRACE, ["============= Debug End ============="]) + call extend(s:CACHE_DEBUG_TRACE, [""]) + call writefile(s:CACHE_DEBUG_TRACE, "Omni.dbg") +endfunc + +" Debug trace function +function! omni#common#debug#Trace(szFuncName, ...) + let szTrace = a:szFuncName + let paramNum = a:0 + if paramNum>0 + let szTrace .= ':' + endif + for i in range(paramNum) + let szTrace = szTrace .' ('. string(eval('a:'.string(i+1))).')' + endfor + call extend(s:CACHE_DEBUG_TRACE, [szTrace]) +endfunc diff --git a/VimFiles/autoload/omni/common/utils.vim b/VimFiles/autoload/omni/common/utils.vim new file mode 100755 index 0000000..c880ad2 --- /dev/null +++ b/VimFiles/autoload/omni/common/utils.vim @@ -0,0 +1,67 @@ +" Description: Omni completion utils +" Maintainer: Vissale NEANG +" Last Change: 26 sept. 2007 + +" For sort numbers in list +function! omni#common#utils#CompareNumber(i1, i2) + let num1 = eval(a:i1) + let num2 = eval(a:i2) + return num1 == num2 ? 0 : num1 > num2 ? 1 : -1 +endfunc + +" TagList function calling the vim taglist() with try catch +" The only throwed exception is 'TagList:UserInterrupt' +" We also force the noignorecase option to avoid linear search when calling +" taglist() +function! omni#common#utils#TagList(szTagQuery) + let result = [] + let bUserIgnoreCase = &ignorecase + " Forcing noignorecase search => binary search can be used in taglist() + " if tags in the tag file are sorted + if bUserIgnoreCase + set noignorecase + endif + try + let result = taglist(a:szTagQuery) + catch /^Vim:Interrupt$/ + " Restoring user's setting + if bUserIgnoreCase + set ignorecase + endif + throw 'TagList:UserInterrupt' + catch + "Note: it seems that ctags can generate corrupted files, in this case + "taglist() will fail to read the tagfile and an exception from + "has_add() is thrown + endtry + + " Restoring user's setting + if bUserIgnoreCase + set ignorecase + endif + return result +endfunc + +" Same as TagList but don't throw exception +function! omni#common#utils#TagListNoThrow(szTagQuery) + let result = [] + try + let result = omni#common#utils#TagList(a:szTagQuery) + catch + endtry + return result +endfunc + +" Get the word under the cursor +function! omni#common#utils#GetWordUnderCursor() + let szLine = getline('.') + let startPos = getpos('.')[2]-1 + let startPos = (startPos < 0)? 0 : startPos + if szLine[startPos] =~ '\w' + let startPos = searchpos('\<\w\+', 'cbn', line('.'))[1] - 1 + endif + + let startPos = (startPos < 0)? 0 : startPos + let szResult = matchstr(szLine, '\w\+', startPos) + return szResult +endfunc diff --git a/VimFiles/autoload/omni/cpp/complete.vim b/VimFiles/autoload/omni/cpp/complete.vim new file mode 100755 index 0000000..a7e4edc --- /dev/null +++ b/VimFiles/autoload/omni/cpp/complete.vim @@ -0,0 +1,569 @@ +" 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 diff --git a/VimFiles/autoload/omni/cpp/includes.vim b/VimFiles/autoload/omni/cpp/includes.vim new file mode 100755 index 0000000..10a89bc --- /dev/null +++ b/VimFiles/autoload/omni/cpp/includes.vim @@ -0,0 +1,126 @@ +" Description: Omni completion script for cpp files +" Maintainer: Vissale NEANG +" Last Change: 26 sept. 2007 + +let g:omni#cpp#includes#CACHE_INCLUDES = {} +let g:omni#cpp#includes#CACHE_FILE_TIME = {} + +let s:rePreprocIncludePart = '\C#\s*include\s*' +let s:reIncludeFilePart = '\(<\|"\)\(\f\|\s\)\+\(>\|"\)' +let s:rePreprocIncludeFile = s:rePreprocIncludePart . s:reIncludeFilePart + +" Get the include list of a file +function! omni#cpp#includes#GetList(...) + if a:0 > 0 + return s:GetIncludeListFromFile(a:1, (a:0 > 1)? a:2 : 0 ) + else + return s:GetIncludeListFromCurrentBuffer() + endif +endfunc + +" Get the include list from the current buffer +function! s:GetIncludeListFromCurrentBuffer() + let listIncludes = [] + let originalPos = getpos('.') + + call setpos('.', [0, 1, 1, 0]) + let curPos = [1,1] + let alreadyInclude = {} + while curPos != [0,0] + let curPos = searchpos('\C\(^'.s:rePreprocIncludeFile.'\)', 'W') + if curPos != [0,0] + let szLine = getline('.') + let startPos = curPos[1] + let endPos = matchend(szLine, s:reIncludeFilePart, startPos-1) + if endPos!=-1 + let szInclusion = szLine[startPos-1:endPos-1] + let szIncludeFile = substitute(szInclusion, '\('.s:rePreprocIncludePart.'\)\|[<>""]', '', 'g') + let szResolvedInclude = omni#cpp#utils#ResolveFilePath(szIncludeFile) + + " Protection over self inclusion + if szResolvedInclude != '' && szResolvedInclude != omni#cpp#utils#ResolveFilePath(getreg('%')) + let includePos = curPos + if !has_key(alreadyInclude, szResolvedInclude) + call extend(listIncludes, [{'pos' : includePos, 'include' : szResolvedInclude}]) + let alreadyInclude[szResolvedInclude] = 1 + endif + endif + endif + endif + endwhile + + call setpos('.', originalPos) + return listIncludes +endfunc + +" Get the include list from a file +function! s:GetIncludeListFromFile(szFilePath, bUpdate) + let listIncludes = [] + if a:szFilePath == '' + return listIncludes + endif + + if !a:bUpdate && has_key(g:omni#cpp#includes#CACHE_INCLUDES, a:szFilePath) + return copy(g:omni#cpp#includes#CACHE_INCLUDES[a:szFilePath]) + endif + + let g:omni#cpp#includes#CACHE_FILE_TIME[a:szFilePath] = getftime(a:szFilePath) + + let szFixedPath = escape(a:szFilePath, g:omni#cpp#utils#szEscapedCharacters) + execute 'silent! lvimgrep /\C\(^'.s:rePreprocIncludeFile.'\)/gj '.szFixedPath + + let listQuickFix = getloclist(0) + let alreadyInclude = {} + for qf in listQuickFix + let szLine = qf.text + let startPos = qf.col + let endPos = matchend(szLine, s:reIncludeFilePart, startPos-1) + if endPos!=-1 + let szInclusion = szLine[startPos-1:endPos-1] + let szIncludeFile = substitute(szInclusion, '\('.s:rePreprocIncludePart.'\)\|[<>""]', '', 'g') + let szResolvedInclude = omni#cpp#utils#ResolveFilePath(szIncludeFile) + + " Protection over self inclusion + if szResolvedInclude != '' && szResolvedInclude != a:szFilePath + let includePos = [qf.lnum, qf.col] + if !has_key(alreadyInclude, szResolvedInclude) + call extend(listIncludes, [{'pos' : includePos, 'include' : szResolvedInclude}]) + let alreadyInclude[szResolvedInclude] = 1 + endif + endif + endif + endfor + + let g:omni#cpp#includes#CACHE_INCLUDES[a:szFilePath] = listIncludes + + return copy(listIncludes) +endfunc + +" For debug purpose +function! omni#cpp#includes#Display() + let szPathBuffer = omni#cpp#utils#ResolveFilePath(getreg('%')) + call s:DisplayIncludeTree(szPathBuffer, 0) +endfunc + +" For debug purpose +function! s:DisplayIncludeTree(szFilePath, indent, ...) + let includeGuard = {} + if a:0 >0 + let includeGuard = a:1 + endif + let szFilePath = omni#cpp#utils#ResolveFilePath(a:szFilePath) + if has_key(includeGuard, szFilePath) + return + else + let includeGuard[szFilePath] = 1 + endif + + let szIndent = repeat(' ', a:indent) + echo szIndent . a:szFilePath + let incList = omni#cpp#includes#GetList(a:szFilePath) + for inc in incList + call s:DisplayIncludeTree(inc.include, a:indent+1, includeGuard) + endfor +endfunc + + diff --git a/VimFiles/autoload/omni/common/debug.vim b/VimFiles/autoload/omni/common/debug.vim new file mode 100755 index 0000000..eded649 --- /dev/null +++ b/VimFiles/autoload/omni/common/debug.vim @@ -0,0 +1,32 @@ +" Description: Omni completion debug functions +" Maintainer: Vissale NEANG +" Last Change: 26 sept. 2007 + +let s:CACHE_DEBUG_TRACE = [] + +" Start debug, clear the debug file +function! omni#common#debug#Start() + let s:CACHE_DEBUG_TRACE = [] + call extend(s:CACHE_DEBUG_TRACE, ['============ Debug Start ============']) + call writefile(s:CACHE_DEBUG_TRACE, "Omni.dbg") +endfunc + +" End debug, write to debug file +function! omni#common#debug#End() + call extend(s:CACHE_DEBUG_TRACE, ["============= Debug End ============="]) + call extend(s:CACHE_DEBUG_TRACE, [""]) + call writefile(s:CACHE_DEBUG_TRACE, "Omni.dbg") +endfunc + +" Debug trace function +function! omni#common#debug#Trace(szFuncName, ...) + let szTrace = a:szFuncName + let paramNum = a:0 + if paramNum>0 + let szTrace .= ':' + endif + for i in range(paramNum) + let szTrace = szTrace .' ('. string(eval('a:'.string(i+1))).')' + endfor + call extend(s:CACHE_DEBUG_TRACE, [szTrace]) +endfunc diff --git a/VimFiles/autoload/omni/common/utils.vim b/VimFiles/autoload/omni/common/utils.vim new file mode 100755 index 0000000..c880ad2 --- /dev/null +++ b/VimFiles/autoload/omni/common/utils.vim @@ -0,0 +1,67 @@ +" Description: Omni completion utils +" Maintainer: Vissale NEANG +" Last Change: 26 sept. 2007 + +" For sort numbers in list +function! omni#common#utils#CompareNumber(i1, i2) + let num1 = eval(a:i1) + let num2 = eval(a:i2) + return num1 == num2 ? 0 : num1 > num2 ? 1 : -1 +endfunc + +" TagList function calling the vim taglist() with try catch +" The only throwed exception is 'TagList:UserInterrupt' +" We also force the noignorecase option to avoid linear search when calling +" taglist() +function! omni#common#utils#TagList(szTagQuery) + let result = [] + let bUserIgnoreCase = &ignorecase + " Forcing noignorecase search => binary search can be used in taglist() + " if tags in the tag file are sorted + if bUserIgnoreCase + set noignorecase + endif + try + let result = taglist(a:szTagQuery) + catch /^Vim:Interrupt$/ + " Restoring user's setting + if bUserIgnoreCase + set ignorecase + endif + throw 'TagList:UserInterrupt' + catch + "Note: it seems that ctags can generate corrupted files, in this case + "taglist() will fail to read the tagfile and an exception from + "has_add() is thrown + endtry + + " Restoring user's setting + if bUserIgnoreCase + set ignorecase + endif + return result +endfunc + +" Same as TagList but don't throw exception +function! omni#common#utils#TagListNoThrow(szTagQuery) + let result = [] + try + let result = omni#common#utils#TagList(a:szTagQuery) + catch + endtry + return result +endfunc + +" Get the word under the cursor +function! omni#common#utils#GetWordUnderCursor() + let szLine = getline('.') + let startPos = getpos('.')[2]-1 + let startPos = (startPos < 0)? 0 : startPos + if szLine[startPos] =~ '\w' + let startPos = searchpos('\<\w\+', 'cbn', line('.'))[1] - 1 + endif + + let startPos = (startPos < 0)? 0 : startPos + let szResult = matchstr(szLine, '\w\+', startPos) + return szResult +endfunc diff --git a/VimFiles/autoload/omni/cpp/complete.vim b/VimFiles/autoload/omni/cpp/complete.vim new file mode 100755 index 0000000..a7e4edc --- /dev/null +++ b/VimFiles/autoload/omni/cpp/complete.vim @@ -0,0 +1,569 @@ +" 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 diff --git a/VimFiles/autoload/omni/cpp/includes.vim b/VimFiles/autoload/omni/cpp/includes.vim new file mode 100755 index 0000000..10a89bc --- /dev/null +++ b/VimFiles/autoload/omni/cpp/includes.vim @@ -0,0 +1,126 @@ +" Description: Omni completion script for cpp files +" Maintainer: Vissale NEANG +" Last Change: 26 sept. 2007 + +let g:omni#cpp#includes#CACHE_INCLUDES = {} +let g:omni#cpp#includes#CACHE_FILE_TIME = {} + +let s:rePreprocIncludePart = '\C#\s*include\s*' +let s:reIncludeFilePart = '\(<\|"\)\(\f\|\s\)\+\(>\|"\)' +let s:rePreprocIncludeFile = s:rePreprocIncludePart . s:reIncludeFilePart + +" Get the include list of a file +function! omni#cpp#includes#GetList(...) + if a:0 > 0 + return s:GetIncludeListFromFile(a:1, (a:0 > 1)? a:2 : 0 ) + else + return s:GetIncludeListFromCurrentBuffer() + endif +endfunc + +" Get the include list from the current buffer +function! s:GetIncludeListFromCurrentBuffer() + let listIncludes = [] + let originalPos = getpos('.') + + call setpos('.', [0, 1, 1, 0]) + let curPos = [1,1] + let alreadyInclude = {} + while curPos != [0,0] + let curPos = searchpos('\C\(^'.s:rePreprocIncludeFile.'\)', 'W') + if curPos != [0,0] + let szLine = getline('.') + let startPos = curPos[1] + let endPos = matchend(szLine, s:reIncludeFilePart, startPos-1) + if endPos!=-1 + let szInclusion = szLine[startPos-1:endPos-1] + let szIncludeFile = substitute(szInclusion, '\('.s:rePreprocIncludePart.'\)\|[<>""]', '', 'g') + let szResolvedInclude = omni#cpp#utils#ResolveFilePath(szIncludeFile) + + " Protection over self inclusion + if szResolvedInclude != '' && szResolvedInclude != omni#cpp#utils#ResolveFilePath(getreg('%')) + let includePos = curPos + if !has_key(alreadyInclude, szResolvedInclude) + call extend(listIncludes, [{'pos' : includePos, 'include' : szResolvedInclude}]) + let alreadyInclude[szResolvedInclude] = 1 + endif + endif + endif + endif + endwhile + + call setpos('.', originalPos) + return listIncludes +endfunc + +" Get the include list from a file +function! s:GetIncludeListFromFile(szFilePath, bUpdate) + let listIncludes = [] + if a:szFilePath == '' + return listIncludes + endif + + if !a:bUpdate && has_key(g:omni#cpp#includes#CACHE_INCLUDES, a:szFilePath) + return copy(g:omni#cpp#includes#CACHE_INCLUDES[a:szFilePath]) + endif + + let g:omni#cpp#includes#CACHE_FILE_TIME[a:szFilePath] = getftime(a:szFilePath) + + let szFixedPath = escape(a:szFilePath, g:omni#cpp#utils#szEscapedCharacters) + execute 'silent! lvimgrep /\C\(^'.s:rePreprocIncludeFile.'\)/gj '.szFixedPath + + let listQuickFix = getloclist(0) + let alreadyInclude = {} + for qf in listQuickFix + let szLine = qf.text + let startPos = qf.col + let endPos = matchend(szLine, s:reIncludeFilePart, startPos-1) + if endPos!=-1 + let szInclusion = szLine[startPos-1:endPos-1] + let szIncludeFile = substitute(szInclusion, '\('.s:rePreprocIncludePart.'\)\|[<>""]', '', 'g') + let szResolvedInclude = omni#cpp#utils#ResolveFilePath(szIncludeFile) + + " Protection over self inclusion + if szResolvedInclude != '' && szResolvedInclude != a:szFilePath + let includePos = [qf.lnum, qf.col] + if !has_key(alreadyInclude, szResolvedInclude) + call extend(listIncludes, [{'pos' : includePos, 'include' : szResolvedInclude}]) + let alreadyInclude[szResolvedInclude] = 1 + endif + endif + endif + endfor + + let g:omni#cpp#includes#CACHE_INCLUDES[a:szFilePath] = listIncludes + + return copy(listIncludes) +endfunc + +" For debug purpose +function! omni#cpp#includes#Display() + let szPathBuffer = omni#cpp#utils#ResolveFilePath(getreg('%')) + call s:DisplayIncludeTree(szPathBuffer, 0) +endfunc + +" For debug purpose +function! s:DisplayIncludeTree(szFilePath, indent, ...) + let includeGuard = {} + if a:0 >0 + let includeGuard = a:1 + endif + let szFilePath = omni#cpp#utils#ResolveFilePath(a:szFilePath) + if has_key(includeGuard, szFilePath) + return + else + let includeGuard[szFilePath] = 1 + endif + + let szIndent = repeat(' ', a:indent) + echo szIndent . a:szFilePath + let incList = omni#cpp#includes#GetList(a:szFilePath) + for inc in incList + call s:DisplayIncludeTree(inc.include, a:indent+1, includeGuard) + endfor +endfunc + + diff --git a/VimFiles/autoload/omni/cpp/items.vim b/VimFiles/autoload/omni/cpp/items.vim new file mode 100755 index 0000000..b943ad4 --- /dev/null +++ b/VimFiles/autoload/omni/cpp/items.vim @@ -0,0 +1,660 @@ +" Description: Omni completion script for cpp files +" Maintainer: Vissale NEANG +" Last Change: 26 sept. 2007 + +" Build the item list of an instruction +" An item is an instruction between a -> or . or ->* or .* +" We can sort an item in different kinds: +" eg: ((MyClass1*)(pObject))->_memberOfClass1.get() ->show() +" | cast | | member | | method | | method | +" @return a list of item +" an item is a dictionnary where keys are: +" tokens = list of token +" kind = itemVariable|itemCast|itemCppCast|itemTemplate|itemFunction|itemUnknown|itemThis|itemScope +function! omni#cpp#items#Get(tokens, ...) + let bGetWordUnderCursor = (a:0>0)? a:1 : 0 + + let result = [] + let itemsDelimiters = ['->', '.', '->*', '.*'] + + let tokens = reverse(omni#cpp#utils#BuildParenthesisGroups(a:tokens)) + + " fsm states: + " 0 = initial state + " TODO: add description of fsm states + let state=(bGetWordUnderCursor)? 1 : 0 + let item = {'tokens' : [], 'kind' : 'itemUnknown'} + let parenGroup=-1 + for token in tokens + if state==0 + if index(itemsDelimiters, token.value)>=0 + let item = {'tokens' : [], 'kind' : 'itemUnknown'} + let state = 1 + elseif token.value=='::' + let state = 9 + let item.kind = 'itemScope' + " Maybe end of tokens + elseif token.kind =='cppOperatorPunctuator' + " If it's a cppOperatorPunctuator and the current token is not + " a itemsDelimiters or '::' we can exit + let state=-1 + break + endif + elseif state==1 + call insert(item.tokens, token) + if token.kind=='cppWord' + " It's an attribute member or a variable + let item.kind = 'itemVariable' + let state = 2 + " Maybe end of tokens + elseif token.value=='this' + let item.kind = 'itemThis' + let state = 2 + " Maybe end of tokens + elseif token.value==')' + let parenGroup = token.group + let state = 3 + elseif token.value==']' + let parenGroup = token.group + let state = 4 + elseif token.kind == 'cppDigit' + let state = -1 + break + endif + elseif state==2 + if index(itemsDelimiters, token.value)>=0 + call insert(result, item) + let item = {'tokens' : [], 'kind' : 'itemUnknown'} + let state = 1 + elseif token.value == '::' + call insert(item.tokens, token) + " We have to get namespace or classscope + let state = 8 + " Maybe end of tokens + else + call insert(result, item) + let state=-1 + break + endif + elseif state==3 + call insert(item.tokens, token) + if token.value=='(' && token.group == parenGroup + let state = 5 + " Maybe end of tokens + endif + elseif state==4 + call insert(item.tokens, token) + if token.value=='[' && token.group == parenGroup + let state = 1 + endif + elseif state==5 + if token.kind=='cppWord' + " It's a function or method + let item.kind = 'itemFunction' + call insert(item.tokens, token) + let state = 2 + " Maybe end of tokens + elseif token.value == '>' + " Maybe a cpp cast or template + let item.kind = 'itemTemplate' + call insert(item.tokens, token) + let parenGroup = token.group + let state = 6 + else + " Perhaps it's a C cast eg: ((void*)(pData)) or a variable eg:(*pData) + let item.kind = omni#cpp#utils#GetCastType(item.tokens) + let state=-1 + call insert(result, item) + break + endif + elseif state==6 + call insert(item.tokens, token) + if token.value == '<' && token.group == parenGroup + " Maybe a cpp cast or template + let state = 7 + endif + elseif state==7 + call insert(item.tokens, token) + if token.kind=='cppKeyword' + " It's a cpp cast + let item.kind = omni#cpp#utils#GetCastType(item.tokens) + let state=-1 + call insert(result, item) + break + else + " Template ? + let state=-1 + call insert(result, item) + break + endif + elseif state==8 + if token.kind=='cppWord' + call insert(item.tokens, token) + let state = 2 + " Maybe end of tokens + else + let state=-1 + call insert(result, item) + break + endif + elseif state==9 + if token.kind == 'cppWord' + call insert(item.tokens, token) + let state = 10 + " Maybe end of tokens + else + let state=-1 + call insert(result, item) + break + endif + elseif state==10 + if token.value == '::' + call insert(item.tokens, token) + let state = 9 + " Maybe end of tokens + else + let state=-1 + call insert(result, item) + break + endif + endif + endfor + + if index([2, 5, 8, 9, 10], state)>=0 + if state==5 + let item.kind = omni#cpp#utils#GetCastType(item.tokens) + endif + call insert(result, item) + endif + + return result +endfunc + +" Resolve type information of items +" @param namespaces: list of namespaces used in the file +" @param szCurrentClassScope: the current class scope, only used for the first +" item to detect if this item is a class member (attribute, method) +" @param items: list of item, can be an empty list @see GetItemsToComplete +function! omni#cpp#items#ResolveItemsTypeInfo(contextStack, items) + " Note: kind = itemVariable|cCast|cppCast|template|function|itemUnknown|this + " For the first item, if it's a variable we try to detect the type of the + " variable with the function searchdecl. If it fails, thanks to the + " current class scope, we try to detect if the variable is an attribute + " member. + " If the kind of the item is a function, we have to first check if the + " function is a method of the class, if it fails we try to get a match in + " the global namespace. After that we get the returned type of the + " function. + " It the kind is a C cast or C++ cast, there is no problem, it's the + " easiest case. We just extract the type of the cast. + + let szCurrentContext = '' + let typeInfo = {} + " Note: We search the decl only for the first item + let bSearchDecl = 1 + for item in a:items + let curItem = item + if index(['itemVariable', 'itemFunction'], curItem.kind)>=0 + " Note: a variable can be : MyNs::MyClass::_var or _var or (*pVar) + " or _var[0][0] + let szSymbol = s:GetSymbol(curItem.tokens) + + " If we have MyNamespace::myVar + " We add MyNamespace in the context stack set szSymbol to myVar + if match(szSymbol, '::\w\+$') >= 0 + let szCurrentContext = substitute(szSymbol, '::\w\+$', '', 'g') + let szSymbol = matchstr(szSymbol, '\w\+$') + endif + let tmpContextStack = a:contextStack + if szCurrentContext != '' + let tmpContextStack = [szCurrentContext] + a:contextStack + endif + + if curItem.kind == 'itemVariable' + let typeInfo = s:GetTypeInfoOfVariable(tmpContextStack, szSymbol, bSearchDecl) + else + let typeInfo = s:GetTypeInfoOfReturnedType(tmpContextStack, szSymbol) + endif + + elseif curItem.kind == 'itemThis' + if len(a:contextStack) + let typeInfo = omni#cpp#utils#CreateTypeInfo(substitute(a:contextStack[0], '^::', '', 'g')) + endif + elseif curItem.kind == 'itemCast' + let typeInfo = omni#cpp#utils#CreateTypeInfo(s:ResolveCCast(curItem.tokens)) + elseif curItem.kind == 'itemCppCast' + let typeInfo = omni#cpp#utils#CreateTypeInfo(s:ResolveCppCast(curItem.tokens)) + elseif curItem.kind == 'itemScope' + let typeInfo = omni#cpp#utils#CreateTypeInfo(substitute(s:TokensToString(curItem.tokens), '\s', '', 'g')) + endif + + if omni#cpp#utils#IsTypeInfoValid(typeInfo) + let szCurrentContext = omni#cpp#utils#GetTypeInfoString(typeInfo) + endif + let bSearchDecl = 0 + endfor + + return typeInfo +endfunc + +" Get symbol name +function! s:GetSymbol(tokens) + let szSymbol = '' + let state = 0 + for token in a:tokens + if state == 0 + if token.value == '::' + let szSymbol .= token.value + let state = 1 + elseif token.kind == 'cppWord' + let szSymbol .= token.value + let state = 2 + " Maybe end of token + endif + elseif state == 1 + if token.kind == 'cppWord' + let szSymbol .= token.value + let state = 2 + " Maybe end of token + else + " Error + break + endif + elseif state == 2 + if token.value == '::' + let szSymbol .= token.value + let state = 1 + else + break + endif + endif + endfor + return szSymbol +endfunc + +" Search a declaration. +" eg: std::map +" can be empty +" Note: The returned type info can be a typedef +" The typedef resolution is done later +" @return +" - a dictionnary where keys are +" - type: the type of value same as type() +" - value: the value +function! s:GetTypeInfoOfVariable(contextStack, szVariable, bSearchDecl) + let result = {} + + if a:bSearchDecl + " Search type of declaration + "let result = s:SearchTypeInfoOfDecl(a:szVariable) + let result = s:SearchDecl(a:szVariable) + endif + + if result=={} + let szFilter = "index(['m', 'v'], v:val.kind[0])>=0" + let tagItem = s:ResolveSymbol(a:contextStack, a:szVariable, szFilter) + if tagItem=={} + return result + endif + + let szCmdWithoutVariable = substitute(omni#cpp#utils#ExtractCmdFromTagItem(tagItem), '\C\<'.a:szVariable.'\>.*', '', 'g') + let tokens = omni#cpp#tokenizer#Tokenize(omni#cpp#utils#GetCodeFromLine(szCmdWithoutVariable)) + let result = omni#cpp#utils#CreateTypeInfo(omni#cpp#utils#ExtractTypeInfoFromTokens(tokens)) + " TODO: Namespace resolution for result + + if result != {} && result.value=='' + " result.value=='' + " eg: + " struct + " { + " }gVariable; + if has_key(tagItem, 'typeref') + " Maybe the variable is a global var of an + " unnamed class, struct or union. + " eg: + " 1) + " struct + " { + " }gVariable; + " In this case we need the tags (the patched version) + " Note: We can have a named type like this: + " 2) + " class A + " { + " }gVariable; + if s:IsUnnamedType(tagItem) + " It's an unnamed type we are in the case 1) + let result = omni#cpp#utils#CreateTypeInfo(tagItem) + else + " It's not an unnamed type we are in the case 2) + + " eg: tagItem.typeref = 'struct:MY_STRUCT::MY_SUBSTRUCT' + let szTypeRef = substitute(tagItem.typeref, '^\w\+:', '', '') + + " eg: szTypeRef = 'MY_STRUCT::MY_SUBSTRUCT' + let result = omni#cpp#utils#CreateTypeInfo(szTypeRef) + endif + endif + endif + endif + return result +endfunc + +" Get the type info string from the returned type of function +function! s:GetTypeInfoOfReturnedType(contextStack, szFunctionName) + let result = {} + + let szFilter = "index(['f', 'p'], v:val.kind[0])>=0" + let tagItem = s:ResolveSymbol(a:contextStack, a:szFunctionName, szFilter) + + if tagItem != {} + let szCmdWithoutVariable = substitute(omni#cpp#utils#ExtractCmdFromTagItem(tagItem), '\C\<'.a:szFunctionName.'\>.*', '', 'g') + let tokens = omni#cpp#tokenizer#Tokenize(omni#cpp#utils#GetCodeFromLine(szCmdWithoutVariable)) + let result = omni#cpp#utils#CreateTypeInfo(omni#cpp#utils#ExtractTypeInfoFromTokens(tokens)) + " TODO: Namespace resolution for result + return result + endif + return result +endfunc + +" Resolve a symbol, return a tagItem +" Gets the first symbol found in the context stack +function! s:ResolveSymbol(contextStack, szSymbol, szTagFilter) + let tagItem = {} + for szCurrentContext in a:contextStack + if szCurrentContext != '::' + let szTagQuery = substitute(szCurrentContext, '^::', '', 'g').'::'.a:szSymbol + else + let szTagQuery = a:szSymbol + endif + + let tagList = omni#common#utils#TagListNoThrow('^'.szTagQuery.'$') + call filter(tagList, a:szTagFilter) + if len(tagList) + let tagItem = tagList[0] + break + endif + endfor + return tagItem +endfunc + +" Return if the tag item represent an unnamed type +function! s:IsUnnamedType(tagItem) + let bResult = 0 + if has_key(a:tagItem, 'typeref') + " Note: Thanks for __anon ! + let bResult = match(a:tagItem.typeref, '\C\<__anon') >= 0 + endif + return bResult +endfunc + +" Search the declaration of a variable and return the type info +function! s:SearchTypeInfoOfDecl(szVariable) + let szReVariable = '\C\<'.a:szVariable.'\>' + + let originalPos = getpos('.') + let origPos = originalPos[1:2] + let curPos = origPos + let stopPos = origPos + + while curPos !=[0,0] + " We go to the start of the current scope + let curPos = searchpairpos('{', '', '}', 'bW', g:omni#cpp#utils#expIgnoreComments) + if curPos != [0,0] + let matchPos = curPos + " Now want to search our variable but we don't want to go in child + " scope + while matchPos != [0,0] + let matchPos = searchpos('{\|'.szReVariable, 'W', stopPos[0]) + if matchPos != [0,0] + " We ignore matches under comment + if omni#cpp#utils#IsCursorInCommentOrString() + continue + endif + + " Getting the current line + let szLine = getline('.') + if match(szLine, szReVariable)>=0 + " We found our variable + " Check if the current instruction is a decl instruction + let tokens = omni#cpp#utils#TokenizeCurrentInstruction() + let szTypeInfo = s:ExtractTypeInfoFromDecl(tokens) + if szTypeInfo != '' + call setpos('.', originalPos) + return omni#cpp#utils#CreateTypeInfo(szTypeInfo) + endif + else + " We found a child scope, we don't want to go in, thus + " we search for the end } of this child scope + let bracketEnd = searchpairpos('{', '', '}', 'nW', g:omni#cpp#utils#expIgnoreComments) + if bracketEnd == [0,0] + break + endif + + if bracketEnd[0] >= stopPos[0] + " The end of the scope is after our cursor we stop + " the search + break + else + " We move the cursor and continue to search our + " variable + call setpos('.', [0, bracketEnd[0], bracketEnd[1], 0]) + endif + endif + endif + endwhile + + " Backing to the start of the scope + call setpos('.', [0,curPos[0], curPos[1], 0]) + let stopPos = curPos + endif + endwhile + + let result = {} + if s:LocalSearchDecl(a:szVariable)==0 && !omni#cpp#utils#IsCursorInCommentOrString() + let tokens = omni#cpp#utils#TokenizeCurrentInstruction() + let szTypeInfo = s:ExtractTypeInfoFromDecl(tokens) + if szTypeInfo != '' + let result = omni#cpp#utils#CreateTypeInfo(szTypeInfo) + endif + endif + + call setpos('.', originalPos) + + return result +endfunc + +" Search a declaration +" @return +" - tokens of the current instruction if success +" - empty list if failure +function! s:SearchDecl(szVariable) + let result = {} + let originalPos = getpos('.') + let searchResult = s:LocalSearchDecl(a:szVariable) + if searchResult==0 + " searchdecl() may detect a decl if the variable is in a conditional + " instruction (if, elseif, while etc...) + " We have to check if the detected decl is really a decl instruction + let tokens = omni#cpp#utils#TokenizeCurrentInstruction() + + for token in tokens + " Simple test + if index(['if', 'elseif', 'while', 'for', 'switch'], token.value)>=0 + " Invalid declaration instruction + call setpos('.', originalPos) + return result + endif + endfor + + let szTypeInfo = s:ExtractTypeInfoFromDecl(tokens) + if szTypeInfo != '' + let result = omni#cpp#utils#CreateTypeInfo(szTypeInfo) + endif + endif + call setpos('.', originalPos) + return result +endfunc + +" Extract the type info string from an instruction. +" We use a small parser to extract the type +" We parse the code according to a C++ BNF from: http://www.nongnu.org/hcb/#basic.link +" @param tokens: token list of the current instruction +function! s:ExtractTypeInfoFromDecl(tokens) + return omni#cpp#utils#ExtractTypeInfoFromTokens(a:tokens) +endfunc + +" Convert tokens to string +function! s:TokensToString(tokens) + let result = '' + for token in a:tokens + let result = result . token.value . ' ' + endfor + return result[:-2] +endfunc + +" Resolve a cast. +" Resolve a C++ cast +" @param list of token. tokens must be a list that represents +" a cast expression (C++ cast) the function does not control +" if it's a cast or not +" eg: static_cast(something) +" @return type info string +function! s:ResolveCppCast(tokens) + return omni#cpp#utils#ExtractTypeInfoFromTokens(s:ResolveCast(a:tokens, '<', '>')) +endfunc + +" Resolve a cast. +" Resolve a C cast +" @param list of token. tokens must be a list that represents +" a cast expression (C cast) the function does not control +" if it's a cast or not +" eg: (MyClass*)something +" @return type info string +function! s:ResolveCCast(tokens) + return omni#cpp#utils#ExtractTypeInfoFromTokens(s:ResolveCast(a:tokens, '(', ')')) +endfunc + +" Resolve a cast. +" Resolve a C cast +" @param list of token. tokens must be a list that represents +" a cast expression (C cast) the function does not control +" if it's a cast or not +" eg: (MyClass*)something +" @return type tokens +function! s:ResolveCast(tokens, startChar, endChar) + let tokens = omni#cpp#utils#BuildParenthesisGroups(a:tokens) + + " We remove useless parenthesis eg: (((MyClass))) + let tokens = omni#cpp#utils#SimplifyParenthesis(tokens) + + let countItem=0 + let startIndex = -1 + let endIndex = -1 + let i = 0 + for token in tokens + if startIndex==-1 + if token.value==a:startChar + let countItem += 1 + let startIndex = i + endif + else + if token.value==a:startChar + let countItem += 1 + elseif token.value==a:endChar + let countItem -= 1 + endif + + if countItem==0 + let endIndex = i + break + endif + endif + let i+=1 + endfor + + return tokens[startIndex+1 : endIndex-1] +endfunc + +" Replacement for build-in function 'searchdecl' +" It does not require that the upper-level bracket is in the first column. +" Otherwise it should be equal to 'searchdecl(name, 0, 1)' +" @param name: name of variable to find declaration for +function! s:LocalSearchDecl(name) + + if g:OmniCpp_LocalSearchDecl == 0 + let bUserIgnoreCase = &ignorecase + + " Forcing the noignorecase option + " avoid bug when, for example, if we have a declaration like this : "A a;" + set noignorecase + + let result = searchdecl(a:name, 0, 1) + + " Restoring user's setting + let &ignorecase = bUserIgnoreCase + + return result + endif + + let lastpos = getpos('.') + let winview = winsaveview() + let lastfoldenable = &foldenable + let &foldenable = 0 + + " We add \C (noignorecase) to + " avoid bug when, for example, if we have a declaration like this : "A a;" + let varname = "\\C\\<" . a:name . "\\>" + + " Go to first blank line before begin of highest scope + normal 99[{ + let scopepos = getpos('.') + while (line('.') > 1) && (len(split(getline('.'))) > 0) + call cursor(line('.')-1, 0) + endwhile + + let declpos = [ 0, 0, 0, 0 ] + while search(varname, '', scopepos[1]) > 0 + " Check if we are a string or a comment + if omni#cpp#utils#IsCursorInCommentOrString() + continue + endif + + " Remember match + let declpos = getpos('.') + endwhile + if declpos[1] != 0 + " We found a match + call winrestview(winview) + call setpos('.', declpos) + let &foldenable = lastfoldenable + return 0 + endif + + while search(varname, '', lastpos[1]) > 0 + " Check if current scope is ending before variable + let old_cur = getpos('.') + normal ]} + let new_cur = getpos('.') + call setpos('.', old_cur) + if (new_cur[1] < lastpos[1]) || ((new_cur[1] == lastpos[1]) && (new_cur[2] < lastpos[2])) + continue + endif + + " Check if we are a string or a comment + if omni#cpp#utils#IsCursorInCommentOrString() + continue + endif + + " We found match + call winrestview(winview) + call setpos('.', old_cur) + let &foldenable = lastfoldenable + return 0 + endwhile + + " No match found. + call winrestview(winview) + let &foldenable = lastfoldenable + return 1 +endfunc diff --git a/VimFiles/autoload/omni/common/debug.vim b/VimFiles/autoload/omni/common/debug.vim new file mode 100755 index 0000000..eded649 --- /dev/null +++ b/VimFiles/autoload/omni/common/debug.vim @@ -0,0 +1,32 @@ +" Description: Omni completion debug functions +" Maintainer: Vissale NEANG +" Last Change: 26 sept. 2007 + +let s:CACHE_DEBUG_TRACE = [] + +" Start debug, clear the debug file +function! omni#common#debug#Start() + let s:CACHE_DEBUG_TRACE = [] + call extend(s:CACHE_DEBUG_TRACE, ['============ Debug Start ============']) + call writefile(s:CACHE_DEBUG_TRACE, "Omni.dbg") +endfunc + +" End debug, write to debug file +function! omni#common#debug#End() + call extend(s:CACHE_DEBUG_TRACE, ["============= Debug End ============="]) + call extend(s:CACHE_DEBUG_TRACE, [""]) + call writefile(s:CACHE_DEBUG_TRACE, "Omni.dbg") +endfunc + +" Debug trace function +function! omni#common#debug#Trace(szFuncName, ...) + let szTrace = a:szFuncName + let paramNum = a:0 + if paramNum>0 + let szTrace .= ':' + endif + for i in range(paramNum) + let szTrace = szTrace .' ('. string(eval('a:'.string(i+1))).')' + endfor + call extend(s:CACHE_DEBUG_TRACE, [szTrace]) +endfunc diff --git a/VimFiles/autoload/omni/common/utils.vim b/VimFiles/autoload/omni/common/utils.vim new file mode 100755 index 0000000..c880ad2 --- /dev/null +++ b/VimFiles/autoload/omni/common/utils.vim @@ -0,0 +1,67 @@ +" Description: Omni completion utils +" Maintainer: Vissale NEANG +" Last Change: 26 sept. 2007 + +" For sort numbers in list +function! omni#common#utils#CompareNumber(i1, i2) + let num1 = eval(a:i1) + let num2 = eval(a:i2) + return num1 == num2 ? 0 : num1 > num2 ? 1 : -1 +endfunc + +" TagList function calling the vim taglist() with try catch +" The only throwed exception is 'TagList:UserInterrupt' +" We also force the noignorecase option to avoid linear search when calling +" taglist() +function! omni#common#utils#TagList(szTagQuery) + let result = [] + let bUserIgnoreCase = &ignorecase + " Forcing noignorecase search => binary search can be used in taglist() + " if tags in the tag file are sorted + if bUserIgnoreCase + set noignorecase + endif + try + let result = taglist(a:szTagQuery) + catch /^Vim:Interrupt$/ + " Restoring user's setting + if bUserIgnoreCase + set ignorecase + endif + throw 'TagList:UserInterrupt' + catch + "Note: it seems that ctags can generate corrupted files, in this case + "taglist() will fail to read the tagfile and an exception from + "has_add() is thrown + endtry + + " Restoring user's setting + if bUserIgnoreCase + set ignorecase + endif + return result +endfunc + +" Same as TagList but don't throw exception +function! omni#common#utils#TagListNoThrow(szTagQuery) + let result = [] + try + let result = omni#common#utils#TagList(a:szTagQuery) + catch + endtry + return result +endfunc + +" Get the word under the cursor +function! omni#common#utils#GetWordUnderCursor() + let szLine = getline('.') + let startPos = getpos('.')[2]-1 + let startPos = (startPos < 0)? 0 : startPos + if szLine[startPos] =~ '\w' + let startPos = searchpos('\<\w\+', 'cbn', line('.'))[1] - 1 + endif + + let startPos = (startPos < 0)? 0 : startPos + let szResult = matchstr(szLine, '\w\+', startPos) + return szResult +endfunc diff --git a/VimFiles/autoload/omni/cpp/complete.vim b/VimFiles/autoload/omni/cpp/complete.vim new file mode 100755 index 0000000..a7e4edc --- /dev/null +++ b/VimFiles/autoload/omni/cpp/complete.vim @@ -0,0 +1,569 @@ +" 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 diff --git a/VimFiles/autoload/omni/cpp/includes.vim b/VimFiles/autoload/omni/cpp/includes.vim new file mode 100755 index 0000000..10a89bc --- /dev/null +++ b/VimFiles/autoload/omni/cpp/includes.vim @@ -0,0 +1,126 @@ +" Description: Omni completion script for cpp files +" Maintainer: Vissale NEANG +" Last Change: 26 sept. 2007 + +let g:omni#cpp#includes#CACHE_INCLUDES = {} +let g:omni#cpp#includes#CACHE_FILE_TIME = {} + +let s:rePreprocIncludePart = '\C#\s*include\s*' +let s:reIncludeFilePart = '\(<\|"\)\(\f\|\s\)\+\(>\|"\)' +let s:rePreprocIncludeFile = s:rePreprocIncludePart . s:reIncludeFilePart + +" Get the include list of a file +function! omni#cpp#includes#GetList(...) + if a:0 > 0 + return s:GetIncludeListFromFile(a:1, (a:0 > 1)? a:2 : 0 ) + else + return s:GetIncludeListFromCurrentBuffer() + endif +endfunc + +" Get the include list from the current buffer +function! s:GetIncludeListFromCurrentBuffer() + let listIncludes = [] + let originalPos = getpos('.') + + call setpos('.', [0, 1, 1, 0]) + let curPos = [1,1] + let alreadyInclude = {} + while curPos != [0,0] + let curPos = searchpos('\C\(^'.s:rePreprocIncludeFile.'\)', 'W') + if curPos != [0,0] + let szLine = getline('.') + let startPos = curPos[1] + let endPos = matchend(szLine, s:reIncludeFilePart, startPos-1) + if endPos!=-1 + let szInclusion = szLine[startPos-1:endPos-1] + let szIncludeFile = substitute(szInclusion, '\('.s:rePreprocIncludePart.'\)\|[<>""]', '', 'g') + let szResolvedInclude = omni#cpp#utils#ResolveFilePath(szIncludeFile) + + " Protection over self inclusion + if szResolvedInclude != '' && szResolvedInclude != omni#cpp#utils#ResolveFilePath(getreg('%')) + let includePos = curPos + if !has_key(alreadyInclude, szResolvedInclude) + call extend(listIncludes, [{'pos' : includePos, 'include' : szResolvedInclude}]) + let alreadyInclude[szResolvedInclude] = 1 + endif + endif + endif + endif + endwhile + + call setpos('.', originalPos) + return listIncludes +endfunc + +" Get the include list from a file +function! s:GetIncludeListFromFile(szFilePath, bUpdate) + let listIncludes = [] + if a:szFilePath == '' + return listIncludes + endif + + if !a:bUpdate && has_key(g:omni#cpp#includes#CACHE_INCLUDES, a:szFilePath) + return copy(g:omni#cpp#includes#CACHE_INCLUDES[a:szFilePath]) + endif + + let g:omni#cpp#includes#CACHE_FILE_TIME[a:szFilePath] = getftime(a:szFilePath) + + let szFixedPath = escape(a:szFilePath, g:omni#cpp#utils#szEscapedCharacters) + execute 'silent! lvimgrep /\C\(^'.s:rePreprocIncludeFile.'\)/gj '.szFixedPath + + let listQuickFix = getloclist(0) + let alreadyInclude = {} + for qf in listQuickFix + let szLine = qf.text + let startPos = qf.col + let endPos = matchend(szLine, s:reIncludeFilePart, startPos-1) + if endPos!=-1 + let szInclusion = szLine[startPos-1:endPos-1] + let szIncludeFile = substitute(szInclusion, '\('.s:rePreprocIncludePart.'\)\|[<>""]', '', 'g') + let szResolvedInclude = omni#cpp#utils#ResolveFilePath(szIncludeFile) + + " Protection over self inclusion + if szResolvedInclude != '' && szResolvedInclude != a:szFilePath + let includePos = [qf.lnum, qf.col] + if !has_key(alreadyInclude, szResolvedInclude) + call extend(listIncludes, [{'pos' : includePos, 'include' : szResolvedInclude}]) + let alreadyInclude[szResolvedInclude] = 1 + endif + endif + endif + endfor + + let g:omni#cpp#includes#CACHE_INCLUDES[a:szFilePath] = listIncludes + + return copy(listIncludes) +endfunc + +" For debug purpose +function! omni#cpp#includes#Display() + let szPathBuffer = omni#cpp#utils#ResolveFilePath(getreg('%')) + call s:DisplayIncludeTree(szPathBuffer, 0) +endfunc + +" For debug purpose +function! s:DisplayIncludeTree(szFilePath, indent, ...) + let includeGuard = {} + if a:0 >0 + let includeGuard = a:1 + endif + let szFilePath = omni#cpp#utils#ResolveFilePath(a:szFilePath) + if has_key(includeGuard, szFilePath) + return + else + let includeGuard[szFilePath] = 1 + endif + + let szIndent = repeat(' ', a:indent) + echo szIndent . a:szFilePath + let incList = omni#cpp#includes#GetList(a:szFilePath) + for inc in incList + call s:DisplayIncludeTree(inc.include, a:indent+1, includeGuard) + endfor +endfunc + + diff --git a/VimFiles/autoload/omni/cpp/items.vim b/VimFiles/autoload/omni/cpp/items.vim new file mode 100755 index 0000000..b943ad4 --- /dev/null +++ b/VimFiles/autoload/omni/cpp/items.vim @@ -0,0 +1,660 @@ +" Description: Omni completion script for cpp files +" Maintainer: Vissale NEANG +" Last Change: 26 sept. 2007 + +" Build the item list of an instruction +" An item is an instruction between a -> or . or ->* or .* +" We can sort an item in different kinds: +" eg: ((MyClass1*)(pObject))->_memberOfClass1.get() ->show() +" | cast | | member | | method | | method | +" @return a list of item +" an item is a dictionnary where keys are: +" tokens = list of token +" kind = itemVariable|itemCast|itemCppCast|itemTemplate|itemFunction|itemUnknown|itemThis|itemScope +function! omni#cpp#items#Get(tokens, ...) + let bGetWordUnderCursor = (a:0>0)? a:1 : 0 + + let result = [] + let itemsDelimiters = ['->', '.', '->*', '.*'] + + let tokens = reverse(omni#cpp#utils#BuildParenthesisGroups(a:tokens)) + + " fsm states: + " 0 = initial state + " TODO: add description of fsm states + let state=(bGetWordUnderCursor)? 1 : 0 + let item = {'tokens' : [], 'kind' : 'itemUnknown'} + let parenGroup=-1 + for token in tokens + if state==0 + if index(itemsDelimiters, token.value)>=0 + let item = {'tokens' : [], 'kind' : 'itemUnknown'} + let state = 1 + elseif token.value=='::' + let state = 9 + let item.kind = 'itemScope' + " Maybe end of tokens + elseif token.kind =='cppOperatorPunctuator' + " If it's a cppOperatorPunctuator and the current token is not + " a itemsDelimiters or '::' we can exit + let state=-1 + break + endif + elseif state==1 + call insert(item.tokens, token) + if token.kind=='cppWord' + " It's an attribute member or a variable + let item.kind = 'itemVariable' + let state = 2 + " Maybe end of tokens + elseif token.value=='this' + let item.kind = 'itemThis' + let state = 2 + " Maybe end of tokens + elseif token.value==')' + let parenGroup = token.group + let state = 3 + elseif token.value==']' + let parenGroup = token.group + let state = 4 + elseif token.kind == 'cppDigit' + let state = -1 + break + endif + elseif state==2 + if index(itemsDelimiters, token.value)>=0 + call insert(result, item) + let item = {'tokens' : [], 'kind' : 'itemUnknown'} + let state = 1 + elseif token.value == '::' + call insert(item.tokens, token) + " We have to get namespace or classscope + let state = 8 + " Maybe end of tokens + else + call insert(result, item) + let state=-1 + break + endif + elseif state==3 + call insert(item.tokens, token) + if token.value=='(' && token.group == parenGroup + let state = 5 + " Maybe end of tokens + endif + elseif state==4 + call insert(item.tokens, token) + if token.value=='[' && token.group == parenGroup + let state = 1 + endif + elseif state==5 + if token.kind=='cppWord' + " It's a function or method + let item.kind = 'itemFunction' + call insert(item.tokens, token) + let state = 2 + " Maybe end of tokens + elseif token.value == '>' + " Maybe a cpp cast or template + let item.kind = 'itemTemplate' + call insert(item.tokens, token) + let parenGroup = token.group + let state = 6 + else + " Perhaps it's a C cast eg: ((void*)(pData)) or a variable eg:(*pData) + let item.kind = omni#cpp#utils#GetCastType(item.tokens) + let state=-1 + call insert(result, item) + break + endif + elseif state==6 + call insert(item.tokens, token) + if token.value == '<' && token.group == parenGroup + " Maybe a cpp cast or template + let state = 7 + endif + elseif state==7 + call insert(item.tokens, token) + if token.kind=='cppKeyword' + " It's a cpp cast + let item.kind = omni#cpp#utils#GetCastType(item.tokens) + let state=-1 + call insert(result, item) + break + else + " Template ? + let state=-1 + call insert(result, item) + break + endif + elseif state==8 + if token.kind=='cppWord' + call insert(item.tokens, token) + let state = 2 + " Maybe end of tokens + else + let state=-1 + call insert(result, item) + break + endif + elseif state==9 + if token.kind == 'cppWord' + call insert(item.tokens, token) + let state = 10 + " Maybe end of tokens + else + let state=-1 + call insert(result, item) + break + endif + elseif state==10 + if token.value == '::' + call insert(item.tokens, token) + let state = 9 + " Maybe end of tokens + else + let state=-1 + call insert(result, item) + break + endif + endif + endfor + + if index([2, 5, 8, 9, 10], state)>=0 + if state==5 + let item.kind = omni#cpp#utils#GetCastType(item.tokens) + endif + call insert(result, item) + endif + + return result +endfunc + +" Resolve type information of items +" @param namespaces: list of namespaces used in the file +" @param szCurrentClassScope: the current class scope, only used for the first +" item to detect if this item is a class member (attribute, method) +" @param items: list of item, can be an empty list @see GetItemsToComplete +function! omni#cpp#items#ResolveItemsTypeInfo(contextStack, items) + " Note: kind = itemVariable|cCast|cppCast|template|function|itemUnknown|this + " For the first item, if it's a variable we try to detect the type of the + " variable with the function searchdecl. If it fails, thanks to the + " current class scope, we try to detect if the variable is an attribute + " member. + " If the kind of the item is a function, we have to first check if the + " function is a method of the class, if it fails we try to get a match in + " the global namespace. After that we get the returned type of the + " function. + " It the kind is a C cast or C++ cast, there is no problem, it's the + " easiest case. We just extract the type of the cast. + + let szCurrentContext = '' + let typeInfo = {} + " Note: We search the decl only for the first item + let bSearchDecl = 1 + for item in a:items + let curItem = item + if index(['itemVariable', 'itemFunction'], curItem.kind)>=0 + " Note: a variable can be : MyNs::MyClass::_var or _var or (*pVar) + " or _var[0][0] + let szSymbol = s:GetSymbol(curItem.tokens) + + " If we have MyNamespace::myVar + " We add MyNamespace in the context stack set szSymbol to myVar + if match(szSymbol, '::\w\+$') >= 0 + let szCurrentContext = substitute(szSymbol, '::\w\+$', '', 'g') + let szSymbol = matchstr(szSymbol, '\w\+$') + endif + let tmpContextStack = a:contextStack + if szCurrentContext != '' + let tmpContextStack = [szCurrentContext] + a:contextStack + endif + + if curItem.kind == 'itemVariable' + let typeInfo = s:GetTypeInfoOfVariable(tmpContextStack, szSymbol, bSearchDecl) + else + let typeInfo = s:GetTypeInfoOfReturnedType(tmpContextStack, szSymbol) + endif + + elseif curItem.kind == 'itemThis' + if len(a:contextStack) + let typeInfo = omni#cpp#utils#CreateTypeInfo(substitute(a:contextStack[0], '^::', '', 'g')) + endif + elseif curItem.kind == 'itemCast' + let typeInfo = omni#cpp#utils#CreateTypeInfo(s:ResolveCCast(curItem.tokens)) + elseif curItem.kind == 'itemCppCast' + let typeInfo = omni#cpp#utils#CreateTypeInfo(s:ResolveCppCast(curItem.tokens)) + elseif curItem.kind == 'itemScope' + let typeInfo = omni#cpp#utils#CreateTypeInfo(substitute(s:TokensToString(curItem.tokens), '\s', '', 'g')) + endif + + if omni#cpp#utils#IsTypeInfoValid(typeInfo) + let szCurrentContext = omni#cpp#utils#GetTypeInfoString(typeInfo) + endif + let bSearchDecl = 0 + endfor + + return typeInfo +endfunc + +" Get symbol name +function! s:GetSymbol(tokens) + let szSymbol = '' + let state = 0 + for token in a:tokens + if state == 0 + if token.value == '::' + let szSymbol .= token.value + let state = 1 + elseif token.kind == 'cppWord' + let szSymbol .= token.value + let state = 2 + " Maybe end of token + endif + elseif state == 1 + if token.kind == 'cppWord' + let szSymbol .= token.value + let state = 2 + " Maybe end of token + else + " Error + break + endif + elseif state == 2 + if token.value == '::' + let szSymbol .= token.value + let state = 1 + else + break + endif + endif + endfor + return szSymbol +endfunc + +" Search a declaration. +" eg: std::map +" can be empty +" Note: The returned type info can be a typedef +" The typedef resolution is done later +" @return +" - a dictionnary where keys are +" - type: the type of value same as type() +" - value: the value +function! s:GetTypeInfoOfVariable(contextStack, szVariable, bSearchDecl) + let result = {} + + if a:bSearchDecl + " Search type of declaration + "let result = s:SearchTypeInfoOfDecl(a:szVariable) + let result = s:SearchDecl(a:szVariable) + endif + + if result=={} + let szFilter = "index(['m', 'v'], v:val.kind[0])>=0" + let tagItem = s:ResolveSymbol(a:contextStack, a:szVariable, szFilter) + if tagItem=={} + return result + endif + + let szCmdWithoutVariable = substitute(omni#cpp#utils#ExtractCmdFromTagItem(tagItem), '\C\<'.a:szVariable.'\>.*', '', 'g') + let tokens = omni#cpp#tokenizer#Tokenize(omni#cpp#utils#GetCodeFromLine(szCmdWithoutVariable)) + let result = omni#cpp#utils#CreateTypeInfo(omni#cpp#utils#ExtractTypeInfoFromTokens(tokens)) + " TODO: Namespace resolution for result + + if result != {} && result.value=='' + " result.value=='' + " eg: + " struct + " { + " }gVariable; + if has_key(tagItem, 'typeref') + " Maybe the variable is a global var of an + " unnamed class, struct or union. + " eg: + " 1) + " struct + " { + " }gVariable; + " In this case we need the tags (the patched version) + " Note: We can have a named type like this: + " 2) + " class A + " { + " }gVariable; + if s:IsUnnamedType(tagItem) + " It's an unnamed type we are in the case 1) + let result = omni#cpp#utils#CreateTypeInfo(tagItem) + else + " It's not an unnamed type we are in the case 2) + + " eg: tagItem.typeref = 'struct:MY_STRUCT::MY_SUBSTRUCT' + let szTypeRef = substitute(tagItem.typeref, '^\w\+:', '', '') + + " eg: szTypeRef = 'MY_STRUCT::MY_SUBSTRUCT' + let result = omni#cpp#utils#CreateTypeInfo(szTypeRef) + endif + endif + endif + endif + return result +endfunc + +" Get the type info string from the returned type of function +function! s:GetTypeInfoOfReturnedType(contextStack, szFunctionName) + let result = {} + + let szFilter = "index(['f', 'p'], v:val.kind[0])>=0" + let tagItem = s:ResolveSymbol(a:contextStack, a:szFunctionName, szFilter) + + if tagItem != {} + let szCmdWithoutVariable = substitute(omni#cpp#utils#ExtractCmdFromTagItem(tagItem), '\C\<'.a:szFunctionName.'\>.*', '', 'g') + let tokens = omni#cpp#tokenizer#Tokenize(omni#cpp#utils#GetCodeFromLine(szCmdWithoutVariable)) + let result = omni#cpp#utils#CreateTypeInfo(omni#cpp#utils#ExtractTypeInfoFromTokens(tokens)) + " TODO: Namespace resolution for result + return result + endif + return result +endfunc + +" Resolve a symbol, return a tagItem +" Gets the first symbol found in the context stack +function! s:ResolveSymbol(contextStack, szSymbol, szTagFilter) + let tagItem = {} + for szCurrentContext in a:contextStack + if szCurrentContext != '::' + let szTagQuery = substitute(szCurrentContext, '^::', '', 'g').'::'.a:szSymbol + else + let szTagQuery = a:szSymbol + endif + + let tagList = omni#common#utils#TagListNoThrow('^'.szTagQuery.'$') + call filter(tagList, a:szTagFilter) + if len(tagList) + let tagItem = tagList[0] + break + endif + endfor + return tagItem +endfunc + +" Return if the tag item represent an unnamed type +function! s:IsUnnamedType(tagItem) + let bResult = 0 + if has_key(a:tagItem, 'typeref') + " Note: Thanks for __anon ! + let bResult = match(a:tagItem.typeref, '\C\<__anon') >= 0 + endif + return bResult +endfunc + +" Search the declaration of a variable and return the type info +function! s:SearchTypeInfoOfDecl(szVariable) + let szReVariable = '\C\<'.a:szVariable.'\>' + + let originalPos = getpos('.') + let origPos = originalPos[1:2] + let curPos = origPos + let stopPos = origPos + + while curPos !=[0,0] + " We go to the start of the current scope + let curPos = searchpairpos('{', '', '}', 'bW', g:omni#cpp#utils#expIgnoreComments) + if curPos != [0,0] + let matchPos = curPos + " Now want to search our variable but we don't want to go in child + " scope + while matchPos != [0,0] + let matchPos = searchpos('{\|'.szReVariable, 'W', stopPos[0]) + if matchPos != [0,0] + " We ignore matches under comment + if omni#cpp#utils#IsCursorInCommentOrString() + continue + endif + + " Getting the current line + let szLine = getline('.') + if match(szLine, szReVariable)>=0 + " We found our variable + " Check if the current instruction is a decl instruction + let tokens = omni#cpp#utils#TokenizeCurrentInstruction() + let szTypeInfo = s:ExtractTypeInfoFromDecl(tokens) + if szTypeInfo != '' + call setpos('.', originalPos) + return omni#cpp#utils#CreateTypeInfo(szTypeInfo) + endif + else + " We found a child scope, we don't want to go in, thus + " we search for the end } of this child scope + let bracketEnd = searchpairpos('{', '', '}', 'nW', g:omni#cpp#utils#expIgnoreComments) + if bracketEnd == [0,0] + break + endif + + if bracketEnd[0] >= stopPos[0] + " The end of the scope is after our cursor we stop + " the search + break + else + " We move the cursor and continue to search our + " variable + call setpos('.', [0, bracketEnd[0], bracketEnd[1], 0]) + endif + endif + endif + endwhile + + " Backing to the start of the scope + call setpos('.', [0,curPos[0], curPos[1], 0]) + let stopPos = curPos + endif + endwhile + + let result = {} + if s:LocalSearchDecl(a:szVariable)==0 && !omni#cpp#utils#IsCursorInCommentOrString() + let tokens = omni#cpp#utils#TokenizeCurrentInstruction() + let szTypeInfo = s:ExtractTypeInfoFromDecl(tokens) + if szTypeInfo != '' + let result = omni#cpp#utils#CreateTypeInfo(szTypeInfo) + endif + endif + + call setpos('.', originalPos) + + return result +endfunc + +" Search a declaration +" @return +" - tokens of the current instruction if success +" - empty list if failure +function! s:SearchDecl(szVariable) + let result = {} + let originalPos = getpos('.') + let searchResult = s:LocalSearchDecl(a:szVariable) + if searchResult==0 + " searchdecl() may detect a decl if the variable is in a conditional + " instruction (if, elseif, while etc...) + " We have to check if the detected decl is really a decl instruction + let tokens = omni#cpp#utils#TokenizeCurrentInstruction() + + for token in tokens + " Simple test + if index(['if', 'elseif', 'while', 'for', 'switch'], token.value)>=0 + " Invalid declaration instruction + call setpos('.', originalPos) + return result + endif + endfor + + let szTypeInfo = s:ExtractTypeInfoFromDecl(tokens) + if szTypeInfo != '' + let result = omni#cpp#utils#CreateTypeInfo(szTypeInfo) + endif + endif + call setpos('.', originalPos) + return result +endfunc + +" Extract the type info string from an instruction. +" We use a small parser to extract the type +" We parse the code according to a C++ BNF from: http://www.nongnu.org/hcb/#basic.link +" @param tokens: token list of the current instruction +function! s:ExtractTypeInfoFromDecl(tokens) + return omni#cpp#utils#ExtractTypeInfoFromTokens(a:tokens) +endfunc + +" Convert tokens to string +function! s:TokensToString(tokens) + let result = '' + for token in a:tokens + let result = result . token.value . ' ' + endfor + return result[:-2] +endfunc + +" Resolve a cast. +" Resolve a C++ cast +" @param list of token. tokens must be a list that represents +" a cast expression (C++ cast) the function does not control +" if it's a cast or not +" eg: static_cast(something) +" @return type info string +function! s:ResolveCppCast(tokens) + return omni#cpp#utils#ExtractTypeInfoFromTokens(s:ResolveCast(a:tokens, '<', '>')) +endfunc + +" Resolve a cast. +" Resolve a C cast +" @param list of token. tokens must be a list that represents +" a cast expression (C cast) the function does not control +" if it's a cast or not +" eg: (MyClass*)something +" @return type info string +function! s:ResolveCCast(tokens) + return omni#cpp#utils#ExtractTypeInfoFromTokens(s:ResolveCast(a:tokens, '(', ')')) +endfunc + +" Resolve a cast. +" Resolve a C cast +" @param list of token. tokens must be a list that represents +" a cast expression (C cast) the function does not control +" if it's a cast or not +" eg: (MyClass*)something +" @return type tokens +function! s:ResolveCast(tokens, startChar, endChar) + let tokens = omni#cpp#utils#BuildParenthesisGroups(a:tokens) + + " We remove useless parenthesis eg: (((MyClass))) + let tokens = omni#cpp#utils#SimplifyParenthesis(tokens) + + let countItem=0 + let startIndex = -1 + let endIndex = -1 + let i = 0 + for token in tokens + if startIndex==-1 + if token.value==a:startChar + let countItem += 1 + let startIndex = i + endif + else + if token.value==a:startChar + let countItem += 1 + elseif token.value==a:endChar + let countItem -= 1 + endif + + if countItem==0 + let endIndex = i + break + endif + endif + let i+=1 + endfor + + return tokens[startIndex+1 : endIndex-1] +endfunc + +" Replacement for build-in function 'searchdecl' +" It does not require that the upper-level bracket is in the first column. +" Otherwise it should be equal to 'searchdecl(name, 0, 1)' +" @param name: name of variable to find declaration for +function! s:LocalSearchDecl(name) + + if g:OmniCpp_LocalSearchDecl == 0 + let bUserIgnoreCase = &ignorecase + + " Forcing the noignorecase option + " avoid bug when, for example, if we have a declaration like this : "A a;" + set noignorecase + + let result = searchdecl(a:name, 0, 1) + + " Restoring user's setting + let &ignorecase = bUserIgnoreCase + + return result + endif + + let lastpos = getpos('.') + let winview = winsaveview() + let lastfoldenable = &foldenable + let &foldenable = 0 + + " We add \C (noignorecase) to + " avoid bug when, for example, if we have a declaration like this : "A a;" + let varname = "\\C\\<" . a:name . "\\>" + + " Go to first blank line before begin of highest scope + normal 99[{ + let scopepos = getpos('.') + while (line('.') > 1) && (len(split(getline('.'))) > 0) + call cursor(line('.')-1, 0) + endwhile + + let declpos = [ 0, 0, 0, 0 ] + while search(varname, '', scopepos[1]) > 0 + " Check if we are a string or a comment + if omni#cpp#utils#IsCursorInCommentOrString() + continue + endif + + " Remember match + let declpos = getpos('.') + endwhile + if declpos[1] != 0 + " We found a match + call winrestview(winview) + call setpos('.', declpos) + let &foldenable = lastfoldenable + return 0 + endif + + while search(varname, '', lastpos[1]) > 0 + " Check if current scope is ending before variable + let old_cur = getpos('.') + normal ]} + let new_cur = getpos('.') + call setpos('.', old_cur) + if (new_cur[1] < lastpos[1]) || ((new_cur[1] == lastpos[1]) && (new_cur[2] < lastpos[2])) + continue + endif + + " Check if we are a string or a comment + if omni#cpp#utils#IsCursorInCommentOrString() + continue + endif + + " We found match + call winrestview(winview) + call setpos('.', old_cur) + let &foldenable = lastfoldenable + return 0 + endwhile + + " No match found. + call winrestview(winview) + let &foldenable = lastfoldenable + return 1 +endfunc diff --git a/VimFiles/autoload/omni/cpp/maycomplete.vim b/VimFiles/autoload/omni/cpp/maycomplete.vim new file mode 100755 index 0000000..610526b --- /dev/null +++ b/VimFiles/autoload/omni/cpp/maycomplete.vim @@ -0,0 +1,82 @@ +" Description: Omni completion script for cpp files +" Maintainer: Vissale NEANG +" Last Change: 26 sept. 2007 + +" Check if we can use omni completion in the current buffer +function! s:CanUseOmnicompletion() + " For C and C++ files and only if the omnifunc is omni#cpp#complete#Main + return (index(['c', 'cpp'], &filetype)>=0 && &omnifunc == 'omni#cpp#complete#Main' && !omni#cpp#utils#IsCursorInCommentOrString()) +endfunc + +" Return the mapping of omni completion +function! omni#cpp#maycomplete#Complete() + let szOmniMapping = "\\" + + " 0 = don't select first item + " 1 = select first item (inserting it to the text, default vim behaviour) + " 2 = select first item (without inserting it to the text) + if g:OmniCpp_SelectFirstItem == 0 + " We have to force the menuone option to avoid confusion when there is + " only one popup item + set completeopt-=menu + set completeopt+=menuone + let szOmniMapping .= "\" + elseif g:OmniCpp_SelectFirstItem == 2 + " We have to force the menuone option to avoid confusion when there is + " only one popup item + set completeopt-=menu + set completeopt+=menuone + let szOmniMapping .= "\" + let szOmniMapping .= "\=pumvisible() ? \"\\\" : \"\"\" + endif + return szOmniMapping +endfunc + +" May complete function for dot +function! omni#cpp#maycomplete#Dot() + if s:CanUseOmnicompletion() && g:OmniCpp_MayCompleteDot + let g:omni#cpp#items#data = omni#cpp#items#Get(omni#cpp#utils#TokenizeCurrentInstruction('.')) + if len(g:omni#cpp#items#data) + let s:bMayComplete = 1 + return '.' . omni#cpp#maycomplete#Complete() + endif + endif + return '.' +endfunc +" May complete function for arrow +function! omni#cpp#maycomplete#Arrow() + if s:CanUseOmnicompletion() && g:OmniCpp_MayCompleteArrow + let index = col('.') - 2 + if index >= 0 + let char = getline('.')[index] + if char == '-' + let g:omni#cpp#items#data = omni#cpp#items#Get(omni#cpp#utils#TokenizeCurrentInstruction('>')) + if len(g:omni#cpp#items#data) + let s:bMayComplete = 1 + return '>' . omni#cpp#maycomplete#Complete() + endif + endif + endif + endif + return '>' +endfunc + +" May complete function for double points +function! omni#cpp#maycomplete#Scope() + if s:CanUseOmnicompletion() && g:OmniCpp_MayCompleteScope + let index = col('.') - 2 + if index >= 0 + let char = getline('.')[index] + if char == ':' + let g:omni#cpp#items#data = omni#cpp#items#Get(omni#cpp#utils#TokenizeCurrentInstruction(':')) + if len(g:omni#cpp#items#data) + if len(g:omni#cpp#items#data[-1].tokens) && g:omni#cpp#items#data[-1].tokens[-1].value != '::' + let s:bMayComplete = 1 + return ':' . omni#cpp#maycomplete#Complete() + endif + endif + endif + endif + endif + return ':' +endfunc diff --git a/VimFiles/autoload/omni/common/debug.vim b/VimFiles/autoload/omni/common/debug.vim new file mode 100755 index 0000000..eded649 --- /dev/null +++ b/VimFiles/autoload/omni/common/debug.vim @@ -0,0 +1,32 @@ +" Description: Omni completion debug functions +" Maintainer: Vissale NEANG +" Last Change: 26 sept. 2007 + +let s:CACHE_DEBUG_TRACE = [] + +" Start debug, clear the debug file +function! omni#common#debug#Start() + let s:CACHE_DEBUG_TRACE = [] + call extend(s:CACHE_DEBUG_TRACE, ['============ Debug Start ============']) + call writefile(s:CACHE_DEBUG_TRACE, "Omni.dbg") +endfunc + +" End debug, write to debug file +function! omni#common#debug#End() + call extend(s:CACHE_DEBUG_TRACE, ["============= Debug End ============="]) + call extend(s:CACHE_DEBUG_TRACE, [""]) + call writefile(s:CACHE_DEBUG_TRACE, "Omni.dbg") +endfunc + +" Debug trace function +function! omni#common#debug#Trace(szFuncName, ...) + let szTrace = a:szFuncName + let paramNum = a:0 + if paramNum>0 + let szTrace .= ':' + endif + for i in range(paramNum) + let szTrace = szTrace .' ('. string(eval('a:'.string(i+1))).')' + endfor + call extend(s:CACHE_DEBUG_TRACE, [szTrace]) +endfunc diff --git a/VimFiles/autoload/omni/common/utils.vim b/VimFiles/autoload/omni/common/utils.vim new file mode 100755 index 0000000..c880ad2 --- /dev/null +++ b/VimFiles/autoload/omni/common/utils.vim @@ -0,0 +1,67 @@ +" Description: Omni completion utils +" Maintainer: Vissale NEANG +" Last Change: 26 sept. 2007 + +" For sort numbers in list +function! omni#common#utils#CompareNumber(i1, i2) + let num1 = eval(a:i1) + let num2 = eval(a:i2) + return num1 == num2 ? 0 : num1 > num2 ? 1 : -1 +endfunc + +" TagList function calling the vim taglist() with try catch +" The only throwed exception is 'TagList:UserInterrupt' +" We also force the noignorecase option to avoid linear search when calling +" taglist() +function! omni#common#utils#TagList(szTagQuery) + let result = [] + let bUserIgnoreCase = &ignorecase + " Forcing noignorecase search => binary search can be used in taglist() + " if tags in the tag file are sorted + if bUserIgnoreCase + set noignorecase + endif + try + let result = taglist(a:szTagQuery) + catch /^Vim:Interrupt$/ + " Restoring user's setting + if bUserIgnoreCase + set ignorecase + endif + throw 'TagList:UserInterrupt' + catch + "Note: it seems that ctags can generate corrupted files, in this case + "taglist() will fail to read the tagfile and an exception from + "has_add() is thrown + endtry + + " Restoring user's setting + if bUserIgnoreCase + set ignorecase + endif + return result +endfunc + +" Same as TagList but don't throw exception +function! omni#common#utils#TagListNoThrow(szTagQuery) + let result = [] + try + let result = omni#common#utils#TagList(a:szTagQuery) + catch + endtry + return result +endfunc + +" Get the word under the cursor +function! omni#common#utils#GetWordUnderCursor() + let szLine = getline('.') + let startPos = getpos('.')[2]-1 + let startPos = (startPos < 0)? 0 : startPos + if szLine[startPos] =~ '\w' + let startPos = searchpos('\<\w\+', 'cbn', line('.'))[1] - 1 + endif + + let startPos = (startPos < 0)? 0 : startPos + let szResult = matchstr(szLine, '\w\+', startPos) + return szResult +endfunc diff --git a/VimFiles/autoload/omni/cpp/complete.vim b/VimFiles/autoload/omni/cpp/complete.vim new file mode 100755 index 0000000..a7e4edc --- /dev/null +++ b/VimFiles/autoload/omni/cpp/complete.vim @@ -0,0 +1,569 @@ +" 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 diff --git a/VimFiles/autoload/omni/cpp/includes.vim b/VimFiles/autoload/omni/cpp/includes.vim new file mode 100755 index 0000000..10a89bc --- /dev/null +++ b/VimFiles/autoload/omni/cpp/includes.vim @@ -0,0 +1,126 @@ +" Description: Omni completion script for cpp files +" Maintainer: Vissale NEANG +" Last Change: 26 sept. 2007 + +let g:omni#cpp#includes#CACHE_INCLUDES = {} +let g:omni#cpp#includes#CACHE_FILE_TIME = {} + +let s:rePreprocIncludePart = '\C#\s*include\s*' +let s:reIncludeFilePart = '\(<\|"\)\(\f\|\s\)\+\(>\|"\)' +let s:rePreprocIncludeFile = s:rePreprocIncludePart . s:reIncludeFilePart + +" Get the include list of a file +function! omni#cpp#includes#GetList(...) + if a:0 > 0 + return s:GetIncludeListFromFile(a:1, (a:0 > 1)? a:2 : 0 ) + else + return s:GetIncludeListFromCurrentBuffer() + endif +endfunc + +" Get the include list from the current buffer +function! s:GetIncludeListFromCurrentBuffer() + let listIncludes = [] + let originalPos = getpos('.') + + call setpos('.', [0, 1, 1, 0]) + let curPos = [1,1] + let alreadyInclude = {} + while curPos != [0,0] + let curPos = searchpos('\C\(^'.s:rePreprocIncludeFile.'\)', 'W') + if curPos != [0,0] + let szLine = getline('.') + let startPos = curPos[1] + let endPos = matchend(szLine, s:reIncludeFilePart, startPos-1) + if endPos!=-1 + let szInclusion = szLine[startPos-1:endPos-1] + let szIncludeFile = substitute(szInclusion, '\('.s:rePreprocIncludePart.'\)\|[<>""]', '', 'g') + let szResolvedInclude = omni#cpp#utils#ResolveFilePath(szIncludeFile) + + " Protection over self inclusion + if szResolvedInclude != '' && szResolvedInclude != omni#cpp#utils#ResolveFilePath(getreg('%')) + let includePos = curPos + if !has_key(alreadyInclude, szResolvedInclude) + call extend(listIncludes, [{'pos' : includePos, 'include' : szResolvedInclude}]) + let alreadyInclude[szResolvedInclude] = 1 + endif + endif + endif + endif + endwhile + + call setpos('.', originalPos) + return listIncludes +endfunc + +" Get the include list from a file +function! s:GetIncludeListFromFile(szFilePath, bUpdate) + let listIncludes = [] + if a:szFilePath == '' + return listIncludes + endif + + if !a:bUpdate && has_key(g:omni#cpp#includes#CACHE_INCLUDES, a:szFilePath) + return copy(g:omni#cpp#includes#CACHE_INCLUDES[a:szFilePath]) + endif + + let g:omni#cpp#includes#CACHE_FILE_TIME[a:szFilePath] = getftime(a:szFilePath) + + let szFixedPath = escape(a:szFilePath, g:omni#cpp#utils#szEscapedCharacters) + execute 'silent! lvimgrep /\C\(^'.s:rePreprocIncludeFile.'\)/gj '.szFixedPath + + let listQuickFix = getloclist(0) + let alreadyInclude = {} + for qf in listQuickFix + let szLine = qf.text + let startPos = qf.col + let endPos = matchend(szLine, s:reIncludeFilePart, startPos-1) + if endPos!=-1 + let szInclusion = szLine[startPos-1:endPos-1] + let szIncludeFile = substitute(szInclusion, '\('.s:rePreprocIncludePart.'\)\|[<>""]', '', 'g') + let szResolvedInclude = omni#cpp#utils#ResolveFilePath(szIncludeFile) + + " Protection over self inclusion + if szResolvedInclude != '' && szResolvedInclude != a:szFilePath + let includePos = [qf.lnum, qf.col] + if !has_key(alreadyInclude, szResolvedInclude) + call extend(listIncludes, [{'pos' : includePos, 'include' : szResolvedInclude}]) + let alreadyInclude[szResolvedInclude] = 1 + endif + endif + endif + endfor + + let g:omni#cpp#includes#CACHE_INCLUDES[a:szFilePath] = listIncludes + + return copy(listIncludes) +endfunc + +" For debug purpose +function! omni#cpp#includes#Display() + let szPathBuffer = omni#cpp#utils#ResolveFilePath(getreg('%')) + call s:DisplayIncludeTree(szPathBuffer, 0) +endfunc + +" For debug purpose +function! s:DisplayIncludeTree(szFilePath, indent, ...) + let includeGuard = {} + if a:0 >0 + let includeGuard = a:1 + endif + let szFilePath = omni#cpp#utils#ResolveFilePath(a:szFilePath) + if has_key(includeGuard, szFilePath) + return + else + let includeGuard[szFilePath] = 1 + endif + + let szIndent = repeat(' ', a:indent) + echo szIndent . a:szFilePath + let incList = omni#cpp#includes#GetList(a:szFilePath) + for inc in incList + call s:DisplayIncludeTree(inc.include, a:indent+1, includeGuard) + endfor +endfunc + + diff --git a/VimFiles/autoload/omni/cpp/items.vim b/VimFiles/autoload/omni/cpp/items.vim new file mode 100755 index 0000000..b943ad4 --- /dev/null +++ b/VimFiles/autoload/omni/cpp/items.vim @@ -0,0 +1,660 @@ +" Description: Omni completion script for cpp files +" Maintainer: Vissale NEANG +" Last Change: 26 sept. 2007 + +" Build the item list of an instruction +" An item is an instruction between a -> or . or ->* or .* +" We can sort an item in different kinds: +" eg: ((MyClass1*)(pObject))->_memberOfClass1.get() ->show() +" | cast | | member | | method | | method | +" @return a list of item +" an item is a dictionnary where keys are: +" tokens = list of token +" kind = itemVariable|itemCast|itemCppCast|itemTemplate|itemFunction|itemUnknown|itemThis|itemScope +function! omni#cpp#items#Get(tokens, ...) + let bGetWordUnderCursor = (a:0>0)? a:1 : 0 + + let result = [] + let itemsDelimiters = ['->', '.', '->*', '.*'] + + let tokens = reverse(omni#cpp#utils#BuildParenthesisGroups(a:tokens)) + + " fsm states: + " 0 = initial state + " TODO: add description of fsm states + let state=(bGetWordUnderCursor)? 1 : 0 + let item = {'tokens' : [], 'kind' : 'itemUnknown'} + let parenGroup=-1 + for token in tokens + if state==0 + if index(itemsDelimiters, token.value)>=0 + let item = {'tokens' : [], 'kind' : 'itemUnknown'} + let state = 1 + elseif token.value=='::' + let state = 9 + let item.kind = 'itemScope' + " Maybe end of tokens + elseif token.kind =='cppOperatorPunctuator' + " If it's a cppOperatorPunctuator and the current token is not + " a itemsDelimiters or '::' we can exit + let state=-1 + break + endif + elseif state==1 + call insert(item.tokens, token) + if token.kind=='cppWord' + " It's an attribute member or a variable + let item.kind = 'itemVariable' + let state = 2 + " Maybe end of tokens + elseif token.value=='this' + let item.kind = 'itemThis' + let state = 2 + " Maybe end of tokens + elseif token.value==')' + let parenGroup = token.group + let state = 3 + elseif token.value==']' + let parenGroup = token.group + let state = 4 + elseif token.kind == 'cppDigit' + let state = -1 + break + endif + elseif state==2 + if index(itemsDelimiters, token.value)>=0 + call insert(result, item) + let item = {'tokens' : [], 'kind' : 'itemUnknown'} + let state = 1 + elseif token.value == '::' + call insert(item.tokens, token) + " We have to get namespace or classscope + let state = 8 + " Maybe end of tokens + else + call insert(result, item) + let state=-1 + break + endif + elseif state==3 + call insert(item.tokens, token) + if token.value=='(' && token.group == parenGroup + let state = 5 + " Maybe end of tokens + endif + elseif state==4 + call insert(item.tokens, token) + if token.value=='[' && token.group == parenGroup + let state = 1 + endif + elseif state==5 + if token.kind=='cppWord' + " It's a function or method + let item.kind = 'itemFunction' + call insert(item.tokens, token) + let state = 2 + " Maybe end of tokens + elseif token.value == '>' + " Maybe a cpp cast or template + let item.kind = 'itemTemplate' + call insert(item.tokens, token) + let parenGroup = token.group + let state = 6 + else + " Perhaps it's a C cast eg: ((void*)(pData)) or a variable eg:(*pData) + let item.kind = omni#cpp#utils#GetCastType(item.tokens) + let state=-1 + call insert(result, item) + break + endif + elseif state==6 + call insert(item.tokens, token) + if token.value == '<' && token.group == parenGroup + " Maybe a cpp cast or template + let state = 7 + endif + elseif state==7 + call insert(item.tokens, token) + if token.kind=='cppKeyword' + " It's a cpp cast + let item.kind = omni#cpp#utils#GetCastType(item.tokens) + let state=-1 + call insert(result, item) + break + else + " Template ? + let state=-1 + call insert(result, item) + break + endif + elseif state==8 + if token.kind=='cppWord' + call insert(item.tokens, token) + let state = 2 + " Maybe end of tokens + else + let state=-1 + call insert(result, item) + break + endif + elseif state==9 + if token.kind == 'cppWord' + call insert(item.tokens, token) + let state = 10 + " Maybe end of tokens + else + let state=-1 + call insert(result, item) + break + endif + elseif state==10 + if token.value == '::' + call insert(item.tokens, token) + let state = 9 + " Maybe end of tokens + else + let state=-1 + call insert(result, item) + break + endif + endif + endfor + + if index([2, 5, 8, 9, 10], state)>=0 + if state==5 + let item.kind = omni#cpp#utils#GetCastType(item.tokens) + endif + call insert(result, item) + endif + + return result +endfunc + +" Resolve type information of items +" @param namespaces: list of namespaces used in the file +" @param szCurrentClassScope: the current class scope, only used for the first +" item to detect if this item is a class member (attribute, method) +" @param items: list of item, can be an empty list @see GetItemsToComplete +function! omni#cpp#items#ResolveItemsTypeInfo(contextStack, items) + " Note: kind = itemVariable|cCast|cppCast|template|function|itemUnknown|this + " For the first item, if it's a variable we try to detect the type of the + " variable with the function searchdecl. If it fails, thanks to the + " current class scope, we try to detect if the variable is an attribute + " member. + " If the kind of the item is a function, we have to first check if the + " function is a method of the class, if it fails we try to get a match in + " the global namespace. After that we get the returned type of the + " function. + " It the kind is a C cast or C++ cast, there is no problem, it's the + " easiest case. We just extract the type of the cast. + + let szCurrentContext = '' + let typeInfo = {} + " Note: We search the decl only for the first item + let bSearchDecl = 1 + for item in a:items + let curItem = item + if index(['itemVariable', 'itemFunction'], curItem.kind)>=0 + " Note: a variable can be : MyNs::MyClass::_var or _var or (*pVar) + " or _var[0][0] + let szSymbol = s:GetSymbol(curItem.tokens) + + " If we have MyNamespace::myVar + " We add MyNamespace in the context stack set szSymbol to myVar + if match(szSymbol, '::\w\+$') >= 0 + let szCurrentContext = substitute(szSymbol, '::\w\+$', '', 'g') + let szSymbol = matchstr(szSymbol, '\w\+$') + endif + let tmpContextStack = a:contextStack + if szCurrentContext != '' + let tmpContextStack = [szCurrentContext] + a:contextStack + endif + + if curItem.kind == 'itemVariable' + let typeInfo = s:GetTypeInfoOfVariable(tmpContextStack, szSymbol, bSearchDecl) + else + let typeInfo = s:GetTypeInfoOfReturnedType(tmpContextStack, szSymbol) + endif + + elseif curItem.kind == 'itemThis' + if len(a:contextStack) + let typeInfo = omni#cpp#utils#CreateTypeInfo(substitute(a:contextStack[0], '^::', '', 'g')) + endif + elseif curItem.kind == 'itemCast' + let typeInfo = omni#cpp#utils#CreateTypeInfo(s:ResolveCCast(curItem.tokens)) + elseif curItem.kind == 'itemCppCast' + let typeInfo = omni#cpp#utils#CreateTypeInfo(s:ResolveCppCast(curItem.tokens)) + elseif curItem.kind == 'itemScope' + let typeInfo = omni#cpp#utils#CreateTypeInfo(substitute(s:TokensToString(curItem.tokens), '\s', '', 'g')) + endif + + if omni#cpp#utils#IsTypeInfoValid(typeInfo) + let szCurrentContext = omni#cpp#utils#GetTypeInfoString(typeInfo) + endif + let bSearchDecl = 0 + endfor + + return typeInfo +endfunc + +" Get symbol name +function! s:GetSymbol(tokens) + let szSymbol = '' + let state = 0 + for token in a:tokens + if state == 0 + if token.value == '::' + let szSymbol .= token.value + let state = 1 + elseif token.kind == 'cppWord' + let szSymbol .= token.value + let state = 2 + " Maybe end of token + endif + elseif state == 1 + if token.kind == 'cppWord' + let szSymbol .= token.value + let state = 2 + " Maybe end of token + else + " Error + break + endif + elseif state == 2 + if token.value == '::' + let szSymbol .= token.value + let state = 1 + else + break + endif + endif + endfor + return szSymbol +endfunc + +" Search a declaration. +" eg: std::map +" can be empty +" Note: The returned type info can be a typedef +" The typedef resolution is done later +" @return +" - a dictionnary where keys are +" - type: the type of value same as type() +" - value: the value +function! s:GetTypeInfoOfVariable(contextStack, szVariable, bSearchDecl) + let result = {} + + if a:bSearchDecl + " Search type of declaration + "let result = s:SearchTypeInfoOfDecl(a:szVariable) + let result = s:SearchDecl(a:szVariable) + endif + + if result=={} + let szFilter = "index(['m', 'v'], v:val.kind[0])>=0" + let tagItem = s:ResolveSymbol(a:contextStack, a:szVariable, szFilter) + if tagItem=={} + return result + endif + + let szCmdWithoutVariable = substitute(omni#cpp#utils#ExtractCmdFromTagItem(tagItem), '\C\<'.a:szVariable.'\>.*', '', 'g') + let tokens = omni#cpp#tokenizer#Tokenize(omni#cpp#utils#GetCodeFromLine(szCmdWithoutVariable)) + let result = omni#cpp#utils#CreateTypeInfo(omni#cpp#utils#ExtractTypeInfoFromTokens(tokens)) + " TODO: Namespace resolution for result + + if result != {} && result.value=='' + " result.value=='' + " eg: + " struct + " { + " }gVariable; + if has_key(tagItem, 'typeref') + " Maybe the variable is a global var of an + " unnamed class, struct or union. + " eg: + " 1) + " struct + " { + " }gVariable; + " In this case we need the tags (the patched version) + " Note: We can have a named type like this: + " 2) + " class A + " { + " }gVariable; + if s:IsUnnamedType(tagItem) + " It's an unnamed type we are in the case 1) + let result = omni#cpp#utils#CreateTypeInfo(tagItem) + else + " It's not an unnamed type we are in the case 2) + + " eg: tagItem.typeref = 'struct:MY_STRUCT::MY_SUBSTRUCT' + let szTypeRef = substitute(tagItem.typeref, '^\w\+:', '', '') + + " eg: szTypeRef = 'MY_STRUCT::MY_SUBSTRUCT' + let result = omni#cpp#utils#CreateTypeInfo(szTypeRef) + endif + endif + endif + endif + return result +endfunc + +" Get the type info string from the returned type of function +function! s:GetTypeInfoOfReturnedType(contextStack, szFunctionName) + let result = {} + + let szFilter = "index(['f', 'p'], v:val.kind[0])>=0" + let tagItem = s:ResolveSymbol(a:contextStack, a:szFunctionName, szFilter) + + if tagItem != {} + let szCmdWithoutVariable = substitute(omni#cpp#utils#ExtractCmdFromTagItem(tagItem), '\C\<'.a:szFunctionName.'\>.*', '', 'g') + let tokens = omni#cpp#tokenizer#Tokenize(omni#cpp#utils#GetCodeFromLine(szCmdWithoutVariable)) + let result = omni#cpp#utils#CreateTypeInfo(omni#cpp#utils#ExtractTypeInfoFromTokens(tokens)) + " TODO: Namespace resolution for result + return result + endif + return result +endfunc + +" Resolve a symbol, return a tagItem +" Gets the first symbol found in the context stack +function! s:ResolveSymbol(contextStack, szSymbol, szTagFilter) + let tagItem = {} + for szCurrentContext in a:contextStack + if szCurrentContext != '::' + let szTagQuery = substitute(szCurrentContext, '^::', '', 'g').'::'.a:szSymbol + else + let szTagQuery = a:szSymbol + endif + + let tagList = omni#common#utils#TagListNoThrow('^'.szTagQuery.'$') + call filter(tagList, a:szTagFilter) + if len(tagList) + let tagItem = tagList[0] + break + endif + endfor + return tagItem +endfunc + +" Return if the tag item represent an unnamed type +function! s:IsUnnamedType(tagItem) + let bResult = 0 + if has_key(a:tagItem, 'typeref') + " Note: Thanks for __anon ! + let bResult = match(a:tagItem.typeref, '\C\<__anon') >= 0 + endif + return bResult +endfunc + +" Search the declaration of a variable and return the type info +function! s:SearchTypeInfoOfDecl(szVariable) + let szReVariable = '\C\<'.a:szVariable.'\>' + + let originalPos = getpos('.') + let origPos = originalPos[1:2] + let curPos = origPos + let stopPos = origPos + + while curPos !=[0,0] + " We go to the start of the current scope + let curPos = searchpairpos('{', '', '}', 'bW', g:omni#cpp#utils#expIgnoreComments) + if curPos != [0,0] + let matchPos = curPos + " Now want to search our variable but we don't want to go in child + " scope + while matchPos != [0,0] + let matchPos = searchpos('{\|'.szReVariable, 'W', stopPos[0]) + if matchPos != [0,0] + " We ignore matches under comment + if omni#cpp#utils#IsCursorInCommentOrString() + continue + endif + + " Getting the current line + let szLine = getline('.') + if match(szLine, szReVariable)>=0 + " We found our variable + " Check if the current instruction is a decl instruction + let tokens = omni#cpp#utils#TokenizeCurrentInstruction() + let szTypeInfo = s:ExtractTypeInfoFromDecl(tokens) + if szTypeInfo != '' + call setpos('.', originalPos) + return omni#cpp#utils#CreateTypeInfo(szTypeInfo) + endif + else + " We found a child scope, we don't want to go in, thus + " we search for the end } of this child scope + let bracketEnd = searchpairpos('{', '', '}', 'nW', g:omni#cpp#utils#expIgnoreComments) + if bracketEnd == [0,0] + break + endif + + if bracketEnd[0] >= stopPos[0] + " The end of the scope is after our cursor we stop + " the search + break + else + " We move the cursor and continue to search our + " variable + call setpos('.', [0, bracketEnd[0], bracketEnd[1], 0]) + endif + endif + endif + endwhile + + " Backing to the start of the scope + call setpos('.', [0,curPos[0], curPos[1], 0]) + let stopPos = curPos + endif + endwhile + + let result = {} + if s:LocalSearchDecl(a:szVariable)==0 && !omni#cpp#utils#IsCursorInCommentOrString() + let tokens = omni#cpp#utils#TokenizeCurrentInstruction() + let szTypeInfo = s:ExtractTypeInfoFromDecl(tokens) + if szTypeInfo != '' + let result = omni#cpp#utils#CreateTypeInfo(szTypeInfo) + endif + endif + + call setpos('.', originalPos) + + return result +endfunc + +" Search a declaration +" @return +" - tokens of the current instruction if success +" - empty list if failure +function! s:SearchDecl(szVariable) + let result = {} + let originalPos = getpos('.') + let searchResult = s:LocalSearchDecl(a:szVariable) + if searchResult==0 + " searchdecl() may detect a decl if the variable is in a conditional + " instruction (if, elseif, while etc...) + " We have to check if the detected decl is really a decl instruction + let tokens = omni#cpp#utils#TokenizeCurrentInstruction() + + for token in tokens + " Simple test + if index(['if', 'elseif', 'while', 'for', 'switch'], token.value)>=0 + " Invalid declaration instruction + call setpos('.', originalPos) + return result + endif + endfor + + let szTypeInfo = s:ExtractTypeInfoFromDecl(tokens) + if szTypeInfo != '' + let result = omni#cpp#utils#CreateTypeInfo(szTypeInfo) + endif + endif + call setpos('.', originalPos) + return result +endfunc + +" Extract the type info string from an instruction. +" We use a small parser to extract the type +" We parse the code according to a C++ BNF from: http://www.nongnu.org/hcb/#basic.link +" @param tokens: token list of the current instruction +function! s:ExtractTypeInfoFromDecl(tokens) + return omni#cpp#utils#ExtractTypeInfoFromTokens(a:tokens) +endfunc + +" Convert tokens to string +function! s:TokensToString(tokens) + let result = '' + for token in a:tokens + let result = result . token.value . ' ' + endfor + return result[:-2] +endfunc + +" Resolve a cast. +" Resolve a C++ cast +" @param list of token. tokens must be a list that represents +" a cast expression (C++ cast) the function does not control +" if it's a cast or not +" eg: static_cast(something) +" @return type info string +function! s:ResolveCppCast(tokens) + return omni#cpp#utils#ExtractTypeInfoFromTokens(s:ResolveCast(a:tokens, '<', '>')) +endfunc + +" Resolve a cast. +" Resolve a C cast +" @param list of token. tokens must be a list that represents +" a cast expression (C cast) the function does not control +" if it's a cast or not +" eg: (MyClass*)something +" @return type info string +function! s:ResolveCCast(tokens) + return omni#cpp#utils#ExtractTypeInfoFromTokens(s:ResolveCast(a:tokens, '(', ')')) +endfunc + +" Resolve a cast. +" Resolve a C cast +" @param list of token. tokens must be a list that represents +" a cast expression (C cast) the function does not control +" if it's a cast or not +" eg: (MyClass*)something +" @return type tokens +function! s:ResolveCast(tokens, startChar, endChar) + let tokens = omni#cpp#utils#BuildParenthesisGroups(a:tokens) + + " We remove useless parenthesis eg: (((MyClass))) + let tokens = omni#cpp#utils#SimplifyParenthesis(tokens) + + let countItem=0 + let startIndex = -1 + let endIndex = -1 + let i = 0 + for token in tokens + if startIndex==-1 + if token.value==a:startChar + let countItem += 1 + let startIndex = i + endif + else + if token.value==a:startChar + let countItem += 1 + elseif token.value==a:endChar + let countItem -= 1 + endif + + if countItem==0 + let endIndex = i + break + endif + endif + let i+=1 + endfor + + return tokens[startIndex+1 : endIndex-1] +endfunc + +" Replacement for build-in function 'searchdecl' +" It does not require that the upper-level bracket is in the first column. +" Otherwise it should be equal to 'searchdecl(name, 0, 1)' +" @param name: name of variable to find declaration for +function! s:LocalSearchDecl(name) + + if g:OmniCpp_LocalSearchDecl == 0 + let bUserIgnoreCase = &ignorecase + + " Forcing the noignorecase option + " avoid bug when, for example, if we have a declaration like this : "A a;" + set noignorecase + + let result = searchdecl(a:name, 0, 1) + + " Restoring user's setting + let &ignorecase = bUserIgnoreCase + + return result + endif + + let lastpos = getpos('.') + let winview = winsaveview() + let lastfoldenable = &foldenable + let &foldenable = 0 + + " We add \C (noignorecase) to + " avoid bug when, for example, if we have a declaration like this : "A a;" + let varname = "\\C\\<" . a:name . "\\>" + + " Go to first blank line before begin of highest scope + normal 99[{ + let scopepos = getpos('.') + while (line('.') > 1) && (len(split(getline('.'))) > 0) + call cursor(line('.')-1, 0) + endwhile + + let declpos = [ 0, 0, 0, 0 ] + while search(varname, '', scopepos[1]) > 0 + " Check if we are a string or a comment + if omni#cpp#utils#IsCursorInCommentOrString() + continue + endif + + " Remember match + let declpos = getpos('.') + endwhile + if declpos[1] != 0 + " We found a match + call winrestview(winview) + call setpos('.', declpos) + let &foldenable = lastfoldenable + return 0 + endif + + while search(varname, '', lastpos[1]) > 0 + " Check if current scope is ending before variable + let old_cur = getpos('.') + normal ]} + let new_cur = getpos('.') + call setpos('.', old_cur) + if (new_cur[1] < lastpos[1]) || ((new_cur[1] == lastpos[1]) && (new_cur[2] < lastpos[2])) + continue + endif + + " Check if we are a string or a comment + if omni#cpp#utils#IsCursorInCommentOrString() + continue + endif + + " We found match + call winrestview(winview) + call setpos('.', old_cur) + let &foldenable = lastfoldenable + return 0 + endwhile + + " No match found. + call winrestview(winview) + let &foldenable = lastfoldenable + return 1 +endfunc diff --git a/VimFiles/autoload/omni/cpp/maycomplete.vim b/VimFiles/autoload/omni/cpp/maycomplete.vim new file mode 100755 index 0000000..610526b --- /dev/null +++ b/VimFiles/autoload/omni/cpp/maycomplete.vim @@ -0,0 +1,82 @@ +" Description: Omni completion script for cpp files +" Maintainer: Vissale NEANG +" Last Change: 26 sept. 2007 + +" Check if we can use omni completion in the current buffer +function! s:CanUseOmnicompletion() + " For C and C++ files and only if the omnifunc is omni#cpp#complete#Main + return (index(['c', 'cpp'], &filetype)>=0 && &omnifunc == 'omni#cpp#complete#Main' && !omni#cpp#utils#IsCursorInCommentOrString()) +endfunc + +" Return the mapping of omni completion +function! omni#cpp#maycomplete#Complete() + let szOmniMapping = "\\" + + " 0 = don't select first item + " 1 = select first item (inserting it to the text, default vim behaviour) + " 2 = select first item (without inserting it to the text) + if g:OmniCpp_SelectFirstItem == 0 + " We have to force the menuone option to avoid confusion when there is + " only one popup item + set completeopt-=menu + set completeopt+=menuone + let szOmniMapping .= "\" + elseif g:OmniCpp_SelectFirstItem == 2 + " We have to force the menuone option to avoid confusion when there is + " only one popup item + set completeopt-=menu + set completeopt+=menuone + let szOmniMapping .= "\" + let szOmniMapping .= "\=pumvisible() ? \"\\\" : \"\"\" + endif + return szOmniMapping +endfunc + +" May complete function for dot +function! omni#cpp#maycomplete#Dot() + if s:CanUseOmnicompletion() && g:OmniCpp_MayCompleteDot + let g:omni#cpp#items#data = omni#cpp#items#Get(omni#cpp#utils#TokenizeCurrentInstruction('.')) + if len(g:omni#cpp#items#data) + let s:bMayComplete = 1 + return '.' . omni#cpp#maycomplete#Complete() + endif + endif + return '.' +endfunc +" May complete function for arrow +function! omni#cpp#maycomplete#Arrow() + if s:CanUseOmnicompletion() && g:OmniCpp_MayCompleteArrow + let index = col('.') - 2 + if index >= 0 + let char = getline('.')[index] + if char == '-' + let g:omni#cpp#items#data = omni#cpp#items#Get(omni#cpp#utils#TokenizeCurrentInstruction('>')) + if len(g:omni#cpp#items#data) + let s:bMayComplete = 1 + return '>' . omni#cpp#maycomplete#Complete() + endif + endif + endif + endif + return '>' +endfunc + +" May complete function for double points +function! omni#cpp#maycomplete#Scope() + if s:CanUseOmnicompletion() && g:OmniCpp_MayCompleteScope + let index = col('.') - 2 + if index >= 0 + let char = getline('.')[index] + if char == ':' + let g:omni#cpp#items#data = omni#cpp#items#Get(omni#cpp#utils#TokenizeCurrentInstruction(':')) + if len(g:omni#cpp#items#data) + if len(g:omni#cpp#items#data[-1].tokens) && g:omni#cpp#items#data[-1].tokens[-1].value != '::' + let s:bMayComplete = 1 + return ':' . omni#cpp#maycomplete#Complete() + endif + endif + endif + endif + endif + return ':' +endfunc diff --git a/VimFiles/autoload/omni/cpp/namespaces.vim b/VimFiles/autoload/omni/cpp/namespaces.vim new file mode 100755 index 0000000..386b3f9 --- /dev/null +++ b/VimFiles/autoload/omni/cpp/namespaces.vim @@ -0,0 +1,838 @@ +" Description: Omni completion script for cpp files +" Maintainer: Vissale NEANG +" Last Change: 26 sept. 2007 + +let g:omni#cpp#namespaces#CacheResolve = {} +let g:omni#cpp#namespaces#CacheUsing = {} +" TODO: For the next release +"let g:omni#cpp#namespaces#CacheAlias = {} + +" Get the using namespace list from a line +function! s:GetNamespaceAliasListFromLine(szLine) + let result = {} + let tokens = omni#cpp#tokenizer#Tokenize(a:szLine) + let szAlias = '' + let szNamespace = '' + let state = 0 + for token in tokens + if state==0 + let szAlias = '' + let szNamespace = '' + if token.value == '/*' + let state = 1 + elseif token.value == '//' + " It's a comment + let state = -1 + break + elseif token.value == 'namespace' + let state = 2 + endif + elseif state==1 + if token.value == '*/' + let state=0 + endif + elseif state==2 + if token.kind == 'cppWord' + let szAlias .= token.value + let state = 3 + else + let state = -1 + break + endif + elseif state == 3 + if token.value == '=' + let state = 4 + else + let state = -1 + break + endif + elseif state == 4 + if token.value == '::' + let szNamespace .= token.value + let state = 5 + elseif token.kind == 'cppWord' + let szNamespace .= token.value + let state = 6 + " Maybe end of tokens + endif + elseif state==5 + if token.kind == 'cppWord' + let szNamespace .= token.value + let state = 6 + " Maybe end of tokens + else + " Error, we can't have 'namespace ALIAS = Something::' + let state = -1 + break + endif + elseif state==6 + if token.value == '::' + let szNamespace .= token.value + let state = 5 + else + call extend(result, {szAlias : szNamespace}) + let state = 0 + endif + endif + endfor + + if state == 6 + call extend(result, {szAlias : szNamespace}) + endif + + return result +endfunc + +" Get the using namespace list from a line +function! s:GetNamespaceListFromLine(szLine) + let result = [] + let tokens = omni#cpp#tokenizer#Tokenize(a:szLine) + let szNamespace = '' + let state = 0 + for token in tokens + if state==0 + let szNamespace = '' + if token.value == '/*' + let state = 1 + elseif token.value == '//' + " It's a comment + let state = -1 + break + elseif token.value == 'using' + let state = 2 + endif + elseif state==1 + if token.value == '*/' + let state=0 + endif + elseif state==2 + if token.value == 'namespace' + let state = 3 + else + " Error, 'using' must be followed by 'namespace' + let state = -1 + break + endif + elseif state==3 + if token.value == '::' + let szNamespace .= token.value + let state = 4 + elseif token.kind == 'cppWord' + let szNamespace .= token.value + let state = 5 + " Maybe end of tokens + endif + elseif state==4 + if token.kind == 'cppWord' + let szNamespace .= token.value + let state = 5 + " Maybe end of tokens + else + " Error, we can't have 'using namespace Something::' + let state = -1 + break + endif + elseif state==5 + if token.value == '::' + let szNamespace .= token.value + let state = 4 + else + call extend(result, [szNamespace]) + let state = 0 + endif + endif + endfor + + if state == 5 + call extend(result, [szNamespace]) + endif + + return result +endfunc + +" Get the namespace list from a namespace map +function! s:GetUsingNamespaceListFromMap(namespaceMap, ...) + let stopLine = 0 + if a:0>0 + let stopLine = a:1 + endif + + let result = [] + let keys = sort(keys(a:namespaceMap), 'omni#common#utils#CompareNumber') + for i in keys + if stopLine != 0 && i > stopLine + break + endif + call extend(result, a:namespaceMap[i]) + endfor + return result +endfunc + +" Get global using namespace list from the current buffer +function! omni#cpp#namespaces#GetListFromCurrentBuffer(...) + let namespaceMap = s:GetAllUsingNamespaceMapFromCurrentBuffer() + let result = [] + if namespaceMap != {} + let result = s:GetUsingNamespaceListFromMap(namespaceMap, (a:0 > 0)? a:1 : line('.')) + endif + return result +endfunc + +" Get global using namespace map from the current buffer and include files recursively +function! s:GetAllUsingNamespaceMapFromCurrentBuffer(...) + let includeGuard = (a:0>0)? a:1 : {} + + let szBufferName = getreg("%") + let szFilePath = omni#cpp#utils#ResolveFilePath(szBufferName) + let szFilePath = (szFilePath=='')? szBufferName : szFilePath + + let namespaceMap = {} + if has_key(includeGuard, szFilePath) + return namespaceMap + else + let includeGuard[szFilePath] = 1 + endif + + let namespaceMap = omni#cpp#namespaces#GetMapFromCurrentBuffer() + + if g:OmniCpp_NamespaceSearch != 2 + " We don't search included files if OmniCpp_NamespaceSearch != 2 + return namespaceMap + endif + + for inc in omni#cpp#includes#GetList() + let lnum = inc.pos[0] + let tmpMap = s:GetAllUsingNamespaceMapFromFile(inc.include, includeGuard) + if tmpMap != {} + if has_key(namespaceMap, lnum) + call extend(namespaceMap[lnum], s:GetUsingNamespaceListFromMap(tmpMap)) + else + let namespaceMap[lnum] = s:GetUsingNamespaceListFromMap(tmpMap) + endif + endif + endfor + + return namespaceMap +endfunc + +" Get global using namespace map from a file and include files recursively +function! s:GetAllUsingNamespaceMapFromFile(szFilePath, ...) + let includeGuard = {} + if a:0 >0 + let includeGuard = a:1 + endif + + let szFilePath = omni#cpp#utils#ResolveFilePath(a:szFilePath) + let szFilePath = (szFilePath=='')? a:szFilePath : szFilePath + + let namespaceMap = {} + if has_key(includeGuard, szFilePath) + return namespaceMap + else + let includeGuard[szFilePath] = 1 + endif + + " If g:OmniCpp_NamespaceSearch == 1 (search namespaces only in the current + " buffer) we don't use cache for the current buffer + let namespaceMap = omni#cpp#namespaces#GetMapFromBuffer(szFilePath, g:OmniCpp_NamespaceSearch==1) + + if g:OmniCpp_NamespaceSearch != 2 + " We don't search included files if OmniCpp_NamespaceSearch != 2 + return namespaceMap + endif + + for inc in omni#cpp#includes#GetList(szFilePath) + let lnum = inc.pos[0] + let tmpMap = s:GetAllUsingNamespaceMapFromFile(inc.include, includeGuard) + if tmpMap != {} + if has_key(namespaceMap, lnum) + call extend(namespaceMap[lnum], s:GetUsingNamespaceListFromMap(tmpMap)) + else + let namespaceMap[lnum] = s:GetUsingNamespaceListFromMap(tmpMap) + endif + endif + endfor + + return namespaceMap +endfunc + +" Get global using namespace map from a the current buffer +function! omni#cpp#namespaces#GetMapFromCurrentBuffer() + let namespaceMap = {} + let originalPos = getpos('.') + + call setpos('.', [0, 1, 1, 0]) + let curPos = [1,1] + while curPos != [0,0] + let curPos = searchpos('\C^using\s\+namespace', 'W') + if curPos != [0,0] + let szLine = getline('.') + let startPos = curPos[1] + let endPos = match(szLine, ';', startPos-1) + if endPos!=-1 + " We get the namespace list from the line + let namespaceMap[curPos[0]] = s:GetNamespaceListFromLine(szLine) + endif + endif + endwhile + + call setpos('.', originalPos) + return namespaceMap +endfunc + +" Get global using namespace map from a file +function! omni#cpp#namespaces#GetMapFromBuffer(szFilePath, ...) + let bUpdate = 0 + if a:0 > 0 + let bUpdate = a:1 + endif + + let szFilePath = omni#cpp#utils#ResolveFilePath(a:szFilePath) + let szFilePath = (szFilePath=='')? a:szFilePath : szFilePath + + if !bUpdate && has_key(g:omni#cpp#namespaces#CacheUsing, szFilePath) + return copy(g:omni#cpp#namespaces#CacheUsing[szFilePath]) + endif + + let namespaceMap = {} + " The file exists, we get the global namespaces in this file + let szFixedPath = escape(szFilePath, g:omni#cpp#utils#szEscapedCharacters) + execute 'silent! lvimgrep /\C^using\s\+namespace/gj '.szFixedPath + + " key = line number + " value = list of namespaces + let listQuickFix = getloclist(0) + for qf in listQuickFix + let szLine = qf.text + let startPos = qf.col + let endPos = match(szLine, ';', startPos-1) + if endPos!=-1 + " We get the namespace list from the line + let namespaceMap[qf.lnum] = s:GetNamespaceListFromLine(szLine) + endif + endfor + + if szFixedPath != '' + let g:omni#cpp#namespaces#CacheUsing[szFixedPath] = namespaceMap + endif + + return copy(namespaceMap) +endfunc + +" Get the stop position when searching for local variables +function! s:GetStopPositionForLocalSearch() + " Stop position when searching a local variable + let originalPos = getpos('.') + let origPos = originalPos[1:2] + let stopPosition = origPos + let curPos = origPos + while curPos !=[0,0] + let stopPosition = curPos + let curPos = searchpairpos('{', '', '}', 'bW', g:omni#cpp#utils#expIgnoreComments) + endwhile + call setpos('.', originalPos) + + return stopPosition +endfunc + +" Get namespaces alias used at the cursor postion in a vim buffer +" Note: The result depends on the current cursor position +" @return +" - Map of namespace alias +function! s:GetNamespaceAliasMap() + " We store the cursor position because searchpairpos() moves the cursor + let result = {} + let originalPos = getpos('.') + let origPos = originalPos[1:2] + + let stopPos = s:GetStopPositionForLocalSearch() + let stopLine = stopPos[0] + let curPos = origPos + let lastLine = 0 + let nextStopLine = origPos[0] + let szReAlias = '\Cnamespace\s\+\w\+\s\+=' + while curPos !=[0,0] + let curPos = searchpos('}\|\('. szReAlias .'\)', 'bW',stopLine) + if curPos!=[0,0] && curPos[0]!=lastLine + let lastLine = curPos[0] + + let szLine = getline('.') + if origPos[0] == curPos[0] + " We get the line until cursor position + let szLine = szLine[:origPos[1]] + endif + + let szLine = omni#cpp#utils#GetCodeFromLine(szLine) + if match(szLine, szReAlias)<0 + " We found a '}' + let curPos = searchpairpos('{', '', '}', 'bW', g:omni#cpp#utils#expIgnoreComments) + else + " We get the namespace alias from the line + call extend(result, s:GetNamespaceAliasListFromLine(szLine)) + let nextStopLine = curPos[0] + endif + endif + endwhile + + " Setting the cursor to the original position + call setpos('.', originalPos) + + call s:ResolveAliasKeys(result) + return result +endfunc + +" Resolve an alias +" eg: namespace IAmAnAlias1 = Ns1 +" eg: namespace IAmAnAlias2 = IAmAnAlias1::Ns2 +" => IAmAnAlias2 = Ns1::Ns2 +function! s:ResolveAliasKey(mapNamespaceAlias, szAlias) + let szResult = a:mapNamespaceAlias[a:szAlias] + " ::Ns1::Ns2::Ns3 => ['Ns1', 'Ns2', 'Ns3'] + let listNamespace = split(szResult, '::') + if len(listNamespace) + " szBeginPart = 'Ns1' + let szBeginPart = remove(listNamespace, 0) + + " Is 'Ns1' an alias ? + if has_key(a:mapNamespaceAlias, szBeginPart) && szBeginPart != a:szAlias + " Resolving alias 'Ns1' + " eg: Ns1 = NsResolved + let szResult = s:ResolveAliasKey(a:mapNamespaceAlias, szBeginPart) + " szEndPart = 'Ns2::Ns3' + let szEndPart = join(listNamespace, '::') + if szEndPart != '' + " Concatenation => szResult = 'NsResolved::Ns2::Ns3' + let szResult .= '::' . szEndPart + endif + endif + endif + return szResult +endfunc + +" Resolve all keys in the namespace alias map +function! s:ResolveAliasKeys(mapNamespaceAlias) + let mapNamespaceAlias = a:mapNamespaceAlias + call map(mapNamespaceAlias, 's:ResolveAliasKey(mapNamespaceAlias, v:key)') +endfunc + +" Resolve namespace alias +function! omni#cpp#namespaces#ResolveAlias(mapNamespaceAlias, szNamespace) + let szResult = a:szNamespace + " ::Ns1::Ns2::Ns3 => ['Ns1', 'Ns2', 'Ns3'] + let listNamespace = split(a:szNamespace, '::') + if len(listNamespace) + " szBeginPart = 'Ns1' + let szBeginPart = remove(listNamespace, 0) + + " Is 'Ns1' an alias ? + if has_key(a:mapNamespaceAlias, szBeginPart) + " Resolving alias 'Ns1' + " eg: Ns1 = NsResolved + let szResult = a:mapNamespaceAlias[szBeginPart] + " szEndPart = 'Ns2::Ns3' + let szEndPart = join(listNamespace, '::') + if szEndPart != '' + " Concatenation => szResult = 'NsResolved::Ns2::Ns3' + let szResult .= '::' . szEndPart + endif + + " If a:szNamespace starts with '::' we add '::' to the beginning + " of the result + if match(a:szNamespace, '^::')>=0 + let szResult = omni#cpp#utils#SimplifyScope('::' . szResult) + endif + endif + endif + return szResult +endfunc + +" Resolve namespace alias +function! s:ResolveAliasInNamespaceList(mapNamespaceAlias, listNamespaces) + call map(a:listNamespaces, 'omni#cpp#namespaces#ResolveAlias(a:mapNamespaceAlias, v:val)') +endfunc + +" Get namespaces used at the cursor postion in a vim buffer +" Note: The result depends on the current cursor position +" @return +" - List of namespace used in the reverse order +function! omni#cpp#namespaces#GetUsingNamespaces() + " We have to get local using namespace declarations + " We need the current cursor position and the position of the start of the + " current scope + + " We store the cursor position because searchpairpos() moves the cursor + let result = [] + let originalPos = getpos('.') + let origPos = originalPos[1:2] + + let stopPos = s:GetStopPositionForLocalSearch() + + let stopLine = stopPos[0] + let curPos = origPos + let lastLine = 0 + let nextStopLine = origPos[0] + while curPos !=[0,0] + let curPos = searchpos('\C}\|\(using\s\+namespace\)', 'bW',stopLine) + if curPos!=[0,0] && curPos[0]!=lastLine + let lastLine = curPos[0] + + let szLine = getline('.') + if origPos[0] == curPos[0] + " We get the line until cursor position + let szLine = szLine[:origPos[1]] + endif + + let szLine = omni#cpp#utils#GetCodeFromLine(szLine) + if match(szLine, '\Cusing\s\+namespace')<0 + " We found a '}' + let curPos = searchpairpos('{', '', '}', 'bW', g:omni#cpp#utils#expIgnoreComments) + else + " We get the namespace list from the line + let result = s:GetNamespaceListFromLine(szLine) + result + let nextStopLine = curPos[0] + endif + endif + endwhile + + " Setting the cursor to the original position + call setpos('.', originalPos) + + " 2) Now we can get all global using namespace declaration from the + " beginning of the file to nextStopLine + let result = omni#cpp#namespaces#GetListFromCurrentBuffer(nextStopLine) + result + + " Resolving alias in the namespace list + " TODO: For the next release + "let g:omni#cpp#namespaces#CacheAlias= s:GetNamespaceAliasMap() + "call s:ResolveAliasInNamespaceList(g:omni#cpp#namespaces#CacheAlias, result) + + return ['::'] + result +endfunc + +" Resolve a using namespace regarding the current context +" For each namespace used: +" - We get all possible contexts where the namespace +" can be define +" - We do a comparison test of each parent contexts with the current +" context list +" - If one and only one parent context is present in the +" current context list we add the namespace in the current +" context +" - If there is more than one of parent contexts in the +" current context the namespace is ambiguous +" @return +" - result item +" - kind = 0|1 +" - 0 = unresolved or error +" - 1 = resolved +" - value = resolved namespace +function! s:ResolveNamespace(namespace, mapCurrentContexts) + let result = {'kind':0, 'value': ''} + + " If the namespace is already resolved we add it in the list of + " current contexts + if match(a:namespace, '^::')>=0 + let result.kind = 1 + let result.value = a:namespace + return result + elseif match(a:namespace, '\w\+::\w\+')>=0 + let mapCurrentContextsTmp = copy(a:mapCurrentContexts) + let resolvedItem = {} + for nsTmp in split(a:namespace, '::') + let resolvedItem = s:ResolveNamespace(nsTmp, mapCurrentContextsTmp) + if resolvedItem.kind + " Note: We don't extend the map + let mapCurrentContextsTmp = {resolvedItem.value : 1} + else + break + endif + endfor + if resolvedItem!={} && resolvedItem.kind + let result.kind = 1 + let result.value = resolvedItem.value + endif + return result + endif + + " We get all possible parent contexts of this namespace + let listTagsOfNamespace = [] + if has_key(g:omni#cpp#namespaces#CacheResolve, a:namespace) + let listTagsOfNamespace = g:omni#cpp#namespaces#CacheResolve[a:namespace] + else + let listTagsOfNamespace = omni#common#utils#TagList('^'.a:namespace.'$') + let g:omni#cpp#namespaces#CacheResolve[a:namespace] = listTagsOfNamespace + endif + + if len(listTagsOfNamespace)==0 + return result + endif + call filter(listTagsOfNamespace, 'v:val.kind[0]=="n"') + + " We extract parent context from tags + " We use a map to avoid multiple entries + let mapContext = {} + for tagItem in listTagsOfNamespace + let szParentContext = omni#cpp#utils#ExtractScope(tagItem) + let mapContext[szParentContext] = 1 + endfor + let listParentContext = keys(mapContext) + + " Now for each parent context we test if the context is in the current + " contexts list + let listResolvedNamespace = [] + for szParentContext in listParentContext + if has_key(a:mapCurrentContexts, szParentContext) + call extend(listResolvedNamespace, [omni#cpp#utils#SimplifyScope(szParentContext.'::'.a:namespace)]) + endif + endfor + + " Now we know if the namespace is ambiguous or not + let len = len(listResolvedNamespace) + if len==1 + " Namespace resolved + let result.kind = 1 + let result.value = listResolvedNamespace[0] + elseif len > 1 + " Ambiguous namespace, possible matches are in listResolvedNamespace + else + " Other cases + endif + return result +endfunc + +" Resolve namespaces +"@return +" - List of resolved namespaces +function! omni#cpp#namespaces#ResolveAll(namespacesUsed) + + " We add the default context '::' + let contextOrder = 0 + let mapCurrentContexts = {} + + " For each namespace used: + " - We get all possible contexts where the namespace + " can be define + " - We do a comparison test of each parent contexts with the current + " context list + " - If one and only one parent context is present in the + " current context list we add the namespace in the current + " context + " - If there is more than one of parent contexts in the + " current context the namespace is ambiguous + for ns in a:namespacesUsed + let resolvedItem = s:ResolveNamespace(ns, mapCurrentContexts) + if resolvedItem.kind + let contextOrder+=1 + let mapCurrentContexts[resolvedItem.value] = contextOrder + endif + endfor + + " Build the list of current contexts from the map, we have to keep the + " order + let mapReorder = {} + for key in keys(mapCurrentContexts) + let mapReorder[ mapCurrentContexts[key] ] = key + endfor + let result = [] + for key in sort(keys(mapReorder)) + call extend(result, [mapReorder[key]]) + endfor + return result +endfunc + +" Build the context stack +function! s:BuildContextStack(namespaces, szCurrentScope) + let result = copy(a:namespaces) + if a:szCurrentScope != '::' + let tagItem = omni#cpp#utils#GetResolvedTagItem(a:namespaces, omni#cpp#utils#CreateTypeInfo(a:szCurrentScope)) + if has_key(tagItem, 'inherits') + let listBaseClass = omni#cpp#utils#GetClassInheritanceList(a:namespaces, omni#cpp#utils#CreateTypeInfo(a:szCurrentScope)) + let result = listBaseClass + result + elseif has_key(tagItem, 'kind') && index(['c', 's', 'u', 'n'], tagItem.kind[0])>=0 + call insert(result, omni#cpp#utils#ExtractTypeInfoFromTag(tagItem)) + endif + endif + return result +endfunc + +" Returns the class scope at the current position of the cursor +" @return a string that represents the class scope +" eg: ::NameSpace1::Class1 +" The returned string always starts with '::' +" Note: In term of performance it's the weak point of the script +function! s:GetClassScopeAtCursor() + " We store the cursor position because searchpairpos() moves the cursor + let originalPos = getpos('.') + let endPos = originalPos[1:2] + let listCode = [] + let result = {'namespaces': [], 'scope': ''} + + while endPos!=[0,0] + let endPos = searchpairpos('{', '', '}', 'bW', g:omni#cpp#utils#expIgnoreComments) + let szReStartPos = '[;{}]\|\%^' + let startPos = searchpairpos(szReStartPos, '', '{', 'bWn', g:omni#cpp#utils#expIgnoreComments) + + " If the file starts with a comment so the startPos can be [0,0] + " we change it to [1,1] + if startPos==[0,0] + let startPos = [1,1] + endif + + " Get lines backward from cursor position to last ; or { or } + " or when we are at the beginning of the file. + " We store lines in listCode + if endPos!=[0,0] + " We remove the last character which is a '{' + " We also remove starting { or } or ; if exits + let szCodeWithoutComments = substitute(omni#cpp#utils#GetCode(startPos, endPos)[:-2], '^[;{}]', '', 'g') + call insert(listCode, {'startLine' : startPos[0], 'code' : szCodeWithoutComments}) + endif + endwhile + " Setting the cursor to the original position + call setpos('.', originalPos) + + let listClassScope = [] + let bResolved = 0 + let startLine = 0 + " Now we can check in the list of code if there is a function + for code in listCode + " We get the name of the namespace, class, struct or union + " and we store it in listClassScope + let tokens = omni#cpp#tokenizer#Tokenize(code.code) + let bContinue=0 + let bAddNamespace = 0 + let state=0 + for token in tokens + if state==0 + if index(['namespace', 'class', 'struct', 'union'], token.value)>=0 + if token.value == 'namespace' + let bAddNamespace = 1 + endif + let state= 1 + " Maybe end of tokens + endif + elseif state==1 + if token.kind == 'cppWord' + " eg: namespace MyNs { class MyCl {}; } + " => listClassScope = [MyNs, MyCl] + call extend( listClassScope , [token.value] ) + + " Add the namespace in result + if bAddNamespace + call extend(result.namespaces, [token.value]) + let bAddNamespace = 0 + endif + + let bContinue=1 + break + endif + endif + endfor + if bContinue==1 + continue + endif + + " Simple test to check if we have a chance to find a + " class method + let aPos = matchend(code.code, '::\s*\~*\s*\w\+\s*(') + if aPos ==-1 + continue + endif + + let startLine = code.startLine + let listTmp = [] + " eg: 'void MyNamespace::MyClass::foo(' + " => tokens = ['MyClass', '::', 'MyNamespace', 'void'] + let tokens = reverse(omni#cpp#tokenizer#Tokenize(code.code[:aPos-1])[:-4]) + let state = 0 + " Reading tokens backward + for token in tokens + if state==0 + if token.kind=='cppWord' + call insert(listTmp, token.value) + let state=1 + endif + elseif state==1 + if token.value=='::' + let state=2 + else + break + endif + elseif state==2 + if token.kind=='cppWord' + call insert(listTmp, token.value) + let state=1 + else + break + endif + endif + endfor + + if len(listTmp) + if len(listClassScope) + let bResolved = 1 + " Merging class scopes + " eg: current class scope = 'MyNs::MyCl1' + " method class scope = 'MyCl1::MyCl2' + " If we add the method class scope to current class scope + " we'll have MyNs::MyCl1::MyCl1::MyCl2 => it's wrong + " we want MyNs::MyCl1::MyCl2 + let index = 0 + for methodClassScope in listTmp + if methodClassScope==listClassScope[-1] + let listTmp = listTmp[index+1:] + break + else + let index+=1 + endif + endfor + endif + call extend(listClassScope, listTmp) + break + endif + endfor + + let szClassScope = '::' + if len(listClassScope) + if bResolved + let szClassScope .= join(listClassScope, '::') + else + let szClassScope = join(listClassScope, '::') + + " The class scope is not resolved, we have to check using + " namespace declarations and search the class scope in each + " namespace + if startLine != 0 + let namespaces = ['::'] + omni#cpp#namespaces#GetListFromCurrentBuffer(startLine) + let namespaces = omni#cpp#namespaces#ResolveAll(namespaces) + let tagItem = omni#cpp#utils#GetResolvedTagItem(namespaces, omni#cpp#utils#CreateTypeInfo(szClassScope)) + if tagItem != {} + let szClassScope = omni#cpp#utils#ExtractTypeInfoFromTag(tagItem) + endif + endif + endif + endif + + let result.scope = szClassScope + return result +endfunc + +" Get all contexts at the cursor position +function! omni#cpp#namespaces#GetContexts() + " Get the current class scope at the cursor, the result depends on the current cursor position + let scopeItem = s:GetClassScopeAtCursor() + let listUsingNamespace = copy(g:OmniCpp_DefaultNamespaces) + call extend(listUsingNamespace, scopeItem.namespaces) + if g:OmniCpp_NamespaceSearch && &filetype != 'c' + " Get namespaces used in the file until the cursor position + let listUsingNamespace = omni#cpp#namespaces#GetUsingNamespaces() + listUsingNamespace + " Resolving namespaces, removing ambiguous namespaces + let namespaces = omni#cpp#namespaces#ResolveAll(listUsingNamespace) + else + let namespaces = ['::'] + listUsingNamespace + endif + call reverse(namespaces) + + " Building context stack from namespaces and the current class scope + return s:BuildContextStack(namespaces, scopeItem.scope) +endfunc diff --git a/VimFiles/autoload/omni/common/debug.vim b/VimFiles/autoload/omni/common/debug.vim new file mode 100755 index 0000000..eded649 --- /dev/null +++ b/VimFiles/autoload/omni/common/debug.vim @@ -0,0 +1,32 @@ +" Description: Omni completion debug functions +" Maintainer: Vissale NEANG +" Last Change: 26 sept. 2007 + +let s:CACHE_DEBUG_TRACE = [] + +" Start debug, clear the debug file +function! omni#common#debug#Start() + let s:CACHE_DEBUG_TRACE = [] + call extend(s:CACHE_DEBUG_TRACE, ['============ Debug Start ============']) + call writefile(s:CACHE_DEBUG_TRACE, "Omni.dbg") +endfunc + +" End debug, write to debug file +function! omni#common#debug#End() + call extend(s:CACHE_DEBUG_TRACE, ["============= Debug End ============="]) + call extend(s:CACHE_DEBUG_TRACE, [""]) + call writefile(s:CACHE_DEBUG_TRACE, "Omni.dbg") +endfunc + +" Debug trace function +function! omni#common#debug#Trace(szFuncName, ...) + let szTrace = a:szFuncName + let paramNum = a:0 + if paramNum>0 + let szTrace .= ':' + endif + for i in range(paramNum) + let szTrace = szTrace .' ('. string(eval('a:'.string(i+1))).')' + endfor + call extend(s:CACHE_DEBUG_TRACE, [szTrace]) +endfunc diff --git a/VimFiles/autoload/omni/common/utils.vim b/VimFiles/autoload/omni/common/utils.vim new file mode 100755 index 0000000..c880ad2 --- /dev/null +++ b/VimFiles/autoload/omni/common/utils.vim @@ -0,0 +1,67 @@ +" Description: Omni completion utils +" Maintainer: Vissale NEANG +" Last Change: 26 sept. 2007 + +" For sort numbers in list +function! omni#common#utils#CompareNumber(i1, i2) + let num1 = eval(a:i1) + let num2 = eval(a:i2) + return num1 == num2 ? 0 : num1 > num2 ? 1 : -1 +endfunc + +" TagList function calling the vim taglist() with try catch +" The only throwed exception is 'TagList:UserInterrupt' +" We also force the noignorecase option to avoid linear search when calling +" taglist() +function! omni#common#utils#TagList(szTagQuery) + let result = [] + let bUserIgnoreCase = &ignorecase + " Forcing noignorecase search => binary search can be used in taglist() + " if tags in the tag file are sorted + if bUserIgnoreCase + set noignorecase + endif + try + let result = taglist(a:szTagQuery) + catch /^Vim:Interrupt$/ + " Restoring user's setting + if bUserIgnoreCase + set ignorecase + endif + throw 'TagList:UserInterrupt' + catch + "Note: it seems that ctags can generate corrupted files, in this case + "taglist() will fail to read the tagfile and an exception from + "has_add() is thrown + endtry + + " Restoring user's setting + if bUserIgnoreCase + set ignorecase + endif + return result +endfunc + +" Same as TagList but don't throw exception +function! omni#common#utils#TagListNoThrow(szTagQuery) + let result = [] + try + let result = omni#common#utils#TagList(a:szTagQuery) + catch + endtry + return result +endfunc + +" Get the word under the cursor +function! omni#common#utils#GetWordUnderCursor() + let szLine = getline('.') + let startPos = getpos('.')[2]-1 + let startPos = (startPos < 0)? 0 : startPos + if szLine[startPos] =~ '\w' + let startPos = searchpos('\<\w\+', 'cbn', line('.'))[1] - 1 + endif + + let startPos = (startPos < 0)? 0 : startPos + let szResult = matchstr(szLine, '\w\+', startPos) + return szResult +endfunc diff --git a/VimFiles/autoload/omni/cpp/complete.vim b/VimFiles/autoload/omni/cpp/complete.vim new file mode 100755 index 0000000..a7e4edc --- /dev/null +++ b/VimFiles/autoload/omni/cpp/complete.vim @@ -0,0 +1,569 @@ +" 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 diff --git a/VimFiles/autoload/omni/cpp/includes.vim b/VimFiles/autoload/omni/cpp/includes.vim new file mode 100755 index 0000000..10a89bc --- /dev/null +++ b/VimFiles/autoload/omni/cpp/includes.vim @@ -0,0 +1,126 @@ +" Description: Omni completion script for cpp files +" Maintainer: Vissale NEANG +" Last Change: 26 sept. 2007 + +let g:omni#cpp#includes#CACHE_INCLUDES = {} +let g:omni#cpp#includes#CACHE_FILE_TIME = {} + +let s:rePreprocIncludePart = '\C#\s*include\s*' +let s:reIncludeFilePart = '\(<\|"\)\(\f\|\s\)\+\(>\|"\)' +let s:rePreprocIncludeFile = s:rePreprocIncludePart . s:reIncludeFilePart + +" Get the include list of a file +function! omni#cpp#includes#GetList(...) + if a:0 > 0 + return s:GetIncludeListFromFile(a:1, (a:0 > 1)? a:2 : 0 ) + else + return s:GetIncludeListFromCurrentBuffer() + endif +endfunc + +" Get the include list from the current buffer +function! s:GetIncludeListFromCurrentBuffer() + let listIncludes = [] + let originalPos = getpos('.') + + call setpos('.', [0, 1, 1, 0]) + let curPos = [1,1] + let alreadyInclude = {} + while curPos != [0,0] + let curPos = searchpos('\C\(^'.s:rePreprocIncludeFile.'\)', 'W') + if curPos != [0,0] + let szLine = getline('.') + let startPos = curPos[1] + let endPos = matchend(szLine, s:reIncludeFilePart, startPos-1) + if endPos!=-1 + let szInclusion = szLine[startPos-1:endPos-1] + let szIncludeFile = substitute(szInclusion, '\('.s:rePreprocIncludePart.'\)\|[<>""]', '', 'g') + let szResolvedInclude = omni#cpp#utils#ResolveFilePath(szIncludeFile) + + " Protection over self inclusion + if szResolvedInclude != '' && szResolvedInclude != omni#cpp#utils#ResolveFilePath(getreg('%')) + let includePos = curPos + if !has_key(alreadyInclude, szResolvedInclude) + call extend(listIncludes, [{'pos' : includePos, 'include' : szResolvedInclude}]) + let alreadyInclude[szResolvedInclude] = 1 + endif + endif + endif + endif + endwhile + + call setpos('.', originalPos) + return listIncludes +endfunc + +" Get the include list from a file +function! s:GetIncludeListFromFile(szFilePath, bUpdate) + let listIncludes = [] + if a:szFilePath == '' + return listIncludes + endif + + if !a:bUpdate && has_key(g:omni#cpp#includes#CACHE_INCLUDES, a:szFilePath) + return copy(g:omni#cpp#includes#CACHE_INCLUDES[a:szFilePath]) + endif + + let g:omni#cpp#includes#CACHE_FILE_TIME[a:szFilePath] = getftime(a:szFilePath) + + let szFixedPath = escape(a:szFilePath, g:omni#cpp#utils#szEscapedCharacters) + execute 'silent! lvimgrep /\C\(^'.s:rePreprocIncludeFile.'\)/gj '.szFixedPath + + let listQuickFix = getloclist(0) + let alreadyInclude = {} + for qf in listQuickFix + let szLine = qf.text + let startPos = qf.col + let endPos = matchend(szLine, s:reIncludeFilePart, startPos-1) + if endPos!=-1 + let szInclusion = szLine[startPos-1:endPos-1] + let szIncludeFile = substitute(szInclusion, '\('.s:rePreprocIncludePart.'\)\|[<>""]', '', 'g') + let szResolvedInclude = omni#cpp#utils#ResolveFilePath(szIncludeFile) + + " Protection over self inclusion + if szResolvedInclude != '' && szResolvedInclude != a:szFilePath + let includePos = [qf.lnum, qf.col] + if !has_key(alreadyInclude, szResolvedInclude) + call extend(listIncludes, [{'pos' : includePos, 'include' : szResolvedInclude}]) + let alreadyInclude[szResolvedInclude] = 1 + endif + endif + endif + endfor + + let g:omni#cpp#includes#CACHE_INCLUDES[a:szFilePath] = listIncludes + + return copy(listIncludes) +endfunc + +" For debug purpose +function! omni#cpp#includes#Display() + let szPathBuffer = omni#cpp#utils#ResolveFilePath(getreg('%')) + call s:DisplayIncludeTree(szPathBuffer, 0) +endfunc + +" For debug purpose +function! s:DisplayIncludeTree(szFilePath, indent, ...) + let includeGuard = {} + if a:0 >0 + let includeGuard = a:1 + endif + let szFilePath = omni#cpp#utils#ResolveFilePath(a:szFilePath) + if has_key(includeGuard, szFilePath) + return + else + let includeGuard[szFilePath] = 1 + endif + + let szIndent = repeat(' ', a:indent) + echo szIndent . a:szFilePath + let incList = omni#cpp#includes#GetList(a:szFilePath) + for inc in incList + call s:DisplayIncludeTree(inc.include, a:indent+1, includeGuard) + endfor +endfunc + + diff --git a/VimFiles/autoload/omni/cpp/items.vim b/VimFiles/autoload/omni/cpp/items.vim new file mode 100755 index 0000000..b943ad4 --- /dev/null +++ b/VimFiles/autoload/omni/cpp/items.vim @@ -0,0 +1,660 @@ +" Description: Omni completion script for cpp files +" Maintainer: Vissale NEANG +" Last Change: 26 sept. 2007 + +" Build the item list of an instruction +" An item is an instruction between a -> or . or ->* or .* +" We can sort an item in different kinds: +" eg: ((MyClass1*)(pObject))->_memberOfClass1.get() ->show() +" | cast | | member | | method | | method | +" @return a list of item +" an item is a dictionnary where keys are: +" tokens = list of token +" kind = itemVariable|itemCast|itemCppCast|itemTemplate|itemFunction|itemUnknown|itemThis|itemScope +function! omni#cpp#items#Get(tokens, ...) + let bGetWordUnderCursor = (a:0>0)? a:1 : 0 + + let result = [] + let itemsDelimiters = ['->', '.', '->*', '.*'] + + let tokens = reverse(omni#cpp#utils#BuildParenthesisGroups(a:tokens)) + + " fsm states: + " 0 = initial state + " TODO: add description of fsm states + let state=(bGetWordUnderCursor)? 1 : 0 + let item = {'tokens' : [], 'kind' : 'itemUnknown'} + let parenGroup=-1 + for token in tokens + if state==0 + if index(itemsDelimiters, token.value)>=0 + let item = {'tokens' : [], 'kind' : 'itemUnknown'} + let state = 1 + elseif token.value=='::' + let state = 9 + let item.kind = 'itemScope' + " Maybe end of tokens + elseif token.kind =='cppOperatorPunctuator' + " If it's a cppOperatorPunctuator and the current token is not + " a itemsDelimiters or '::' we can exit + let state=-1 + break + endif + elseif state==1 + call insert(item.tokens, token) + if token.kind=='cppWord' + " It's an attribute member or a variable + let item.kind = 'itemVariable' + let state = 2 + " Maybe end of tokens + elseif token.value=='this' + let item.kind = 'itemThis' + let state = 2 + " Maybe end of tokens + elseif token.value==')' + let parenGroup = token.group + let state = 3 + elseif token.value==']' + let parenGroup = token.group + let state = 4 + elseif token.kind == 'cppDigit' + let state = -1 + break + endif + elseif state==2 + if index(itemsDelimiters, token.value)>=0 + call insert(result, item) + let item = {'tokens' : [], 'kind' : 'itemUnknown'} + let state = 1 + elseif token.value == '::' + call insert(item.tokens, token) + " We have to get namespace or classscope + let state = 8 + " Maybe end of tokens + else + call insert(result, item) + let state=-1 + break + endif + elseif state==3 + call insert(item.tokens, token) + if token.value=='(' && token.group == parenGroup + let state = 5 + " Maybe end of tokens + endif + elseif state==4 + call insert(item.tokens, token) + if token.value=='[' && token.group == parenGroup + let state = 1 + endif + elseif state==5 + if token.kind=='cppWord' + " It's a function or method + let item.kind = 'itemFunction' + call insert(item.tokens, token) + let state = 2 + " Maybe end of tokens + elseif token.value == '>' + " Maybe a cpp cast or template + let item.kind = 'itemTemplate' + call insert(item.tokens, token) + let parenGroup = token.group + let state = 6 + else + " Perhaps it's a C cast eg: ((void*)(pData)) or a variable eg:(*pData) + let item.kind = omni#cpp#utils#GetCastType(item.tokens) + let state=-1 + call insert(result, item) + break + endif + elseif state==6 + call insert(item.tokens, token) + if token.value == '<' && token.group == parenGroup + " Maybe a cpp cast or template + let state = 7 + endif + elseif state==7 + call insert(item.tokens, token) + if token.kind=='cppKeyword' + " It's a cpp cast + let item.kind = omni#cpp#utils#GetCastType(item.tokens) + let state=-1 + call insert(result, item) + break + else + " Template ? + let state=-1 + call insert(result, item) + break + endif + elseif state==8 + if token.kind=='cppWord' + call insert(item.tokens, token) + let state = 2 + " Maybe end of tokens + else + let state=-1 + call insert(result, item) + break + endif + elseif state==9 + if token.kind == 'cppWord' + call insert(item.tokens, token) + let state = 10 + " Maybe end of tokens + else + let state=-1 + call insert(result, item) + break + endif + elseif state==10 + if token.value == '::' + call insert(item.tokens, token) + let state = 9 + " Maybe end of tokens + else + let state=-1 + call insert(result, item) + break + endif + endif + endfor + + if index([2, 5, 8, 9, 10], state)>=0 + if state==5 + let item.kind = omni#cpp#utils#GetCastType(item.tokens) + endif + call insert(result, item) + endif + + return result +endfunc + +" Resolve type information of items +" @param namespaces: list of namespaces used in the file +" @param szCurrentClassScope: the current class scope, only used for the first +" item to detect if this item is a class member (attribute, method) +" @param items: list of item, can be an empty list @see GetItemsToComplete +function! omni#cpp#items#ResolveItemsTypeInfo(contextStack, items) + " Note: kind = itemVariable|cCast|cppCast|template|function|itemUnknown|this + " For the first item, if it's a variable we try to detect the type of the + " variable with the function searchdecl. If it fails, thanks to the + " current class scope, we try to detect if the variable is an attribute + " member. + " If the kind of the item is a function, we have to first check if the + " function is a method of the class, if it fails we try to get a match in + " the global namespace. After that we get the returned type of the + " function. + " It the kind is a C cast or C++ cast, there is no problem, it's the + " easiest case. We just extract the type of the cast. + + let szCurrentContext = '' + let typeInfo = {} + " Note: We search the decl only for the first item + let bSearchDecl = 1 + for item in a:items + let curItem = item + if index(['itemVariable', 'itemFunction'], curItem.kind)>=0 + " Note: a variable can be : MyNs::MyClass::_var or _var or (*pVar) + " or _var[0][0] + let szSymbol = s:GetSymbol(curItem.tokens) + + " If we have MyNamespace::myVar + " We add MyNamespace in the context stack set szSymbol to myVar + if match(szSymbol, '::\w\+$') >= 0 + let szCurrentContext = substitute(szSymbol, '::\w\+$', '', 'g') + let szSymbol = matchstr(szSymbol, '\w\+$') + endif + let tmpContextStack = a:contextStack + if szCurrentContext != '' + let tmpContextStack = [szCurrentContext] + a:contextStack + endif + + if curItem.kind == 'itemVariable' + let typeInfo = s:GetTypeInfoOfVariable(tmpContextStack, szSymbol, bSearchDecl) + else + let typeInfo = s:GetTypeInfoOfReturnedType(tmpContextStack, szSymbol) + endif + + elseif curItem.kind == 'itemThis' + if len(a:contextStack) + let typeInfo = omni#cpp#utils#CreateTypeInfo(substitute(a:contextStack[0], '^::', '', 'g')) + endif + elseif curItem.kind == 'itemCast' + let typeInfo = omni#cpp#utils#CreateTypeInfo(s:ResolveCCast(curItem.tokens)) + elseif curItem.kind == 'itemCppCast' + let typeInfo = omni#cpp#utils#CreateTypeInfo(s:ResolveCppCast(curItem.tokens)) + elseif curItem.kind == 'itemScope' + let typeInfo = omni#cpp#utils#CreateTypeInfo(substitute(s:TokensToString(curItem.tokens), '\s', '', 'g')) + endif + + if omni#cpp#utils#IsTypeInfoValid(typeInfo) + let szCurrentContext = omni#cpp#utils#GetTypeInfoString(typeInfo) + endif + let bSearchDecl = 0 + endfor + + return typeInfo +endfunc + +" Get symbol name +function! s:GetSymbol(tokens) + let szSymbol = '' + let state = 0 + for token in a:tokens + if state == 0 + if token.value == '::' + let szSymbol .= token.value + let state = 1 + elseif token.kind == 'cppWord' + let szSymbol .= token.value + let state = 2 + " Maybe end of token + endif + elseif state == 1 + if token.kind == 'cppWord' + let szSymbol .= token.value + let state = 2 + " Maybe end of token + else + " Error + break + endif + elseif state == 2 + if token.value == '::' + let szSymbol .= token.value + let state = 1 + else + break + endif + endif + endfor + return szSymbol +endfunc + +" Search a declaration. +" eg: std::map +" can be empty +" Note: The returned type info can be a typedef +" The typedef resolution is done later +" @return +" - a dictionnary where keys are +" - type: the type of value same as type() +" - value: the value +function! s:GetTypeInfoOfVariable(contextStack, szVariable, bSearchDecl) + let result = {} + + if a:bSearchDecl + " Search type of declaration + "let result = s:SearchTypeInfoOfDecl(a:szVariable) + let result = s:SearchDecl(a:szVariable) + endif + + if result=={} + let szFilter = "index(['m', 'v'], v:val.kind[0])>=0" + let tagItem = s:ResolveSymbol(a:contextStack, a:szVariable, szFilter) + if tagItem=={} + return result + endif + + let szCmdWithoutVariable = substitute(omni#cpp#utils#ExtractCmdFromTagItem(tagItem), '\C\<'.a:szVariable.'\>.*', '', 'g') + let tokens = omni#cpp#tokenizer#Tokenize(omni#cpp#utils#GetCodeFromLine(szCmdWithoutVariable)) + let result = omni#cpp#utils#CreateTypeInfo(omni#cpp#utils#ExtractTypeInfoFromTokens(tokens)) + " TODO: Namespace resolution for result + + if result != {} && result.value=='' + " result.value=='' + " eg: + " struct + " { + " }gVariable; + if has_key(tagItem, 'typeref') + " Maybe the variable is a global var of an + " unnamed class, struct or union. + " eg: + " 1) + " struct + " { + " }gVariable; + " In this case we need the tags (the patched version) + " Note: We can have a named type like this: + " 2) + " class A + " { + " }gVariable; + if s:IsUnnamedType(tagItem) + " It's an unnamed type we are in the case 1) + let result = omni#cpp#utils#CreateTypeInfo(tagItem) + else + " It's not an unnamed type we are in the case 2) + + " eg: tagItem.typeref = 'struct:MY_STRUCT::MY_SUBSTRUCT' + let szTypeRef = substitute(tagItem.typeref, '^\w\+:', '', '') + + " eg: szTypeRef = 'MY_STRUCT::MY_SUBSTRUCT' + let result = omni#cpp#utils#CreateTypeInfo(szTypeRef) + endif + endif + endif + endif + return result +endfunc + +" Get the type info string from the returned type of function +function! s:GetTypeInfoOfReturnedType(contextStack, szFunctionName) + let result = {} + + let szFilter = "index(['f', 'p'], v:val.kind[0])>=0" + let tagItem = s:ResolveSymbol(a:contextStack, a:szFunctionName, szFilter) + + if tagItem != {} + let szCmdWithoutVariable = substitute(omni#cpp#utils#ExtractCmdFromTagItem(tagItem), '\C\<'.a:szFunctionName.'\>.*', '', 'g') + let tokens = omni#cpp#tokenizer#Tokenize(omni#cpp#utils#GetCodeFromLine(szCmdWithoutVariable)) + let result = omni#cpp#utils#CreateTypeInfo(omni#cpp#utils#ExtractTypeInfoFromTokens(tokens)) + " TODO: Namespace resolution for result + return result + endif + return result +endfunc + +" Resolve a symbol, return a tagItem +" Gets the first symbol found in the context stack +function! s:ResolveSymbol(contextStack, szSymbol, szTagFilter) + let tagItem = {} + for szCurrentContext in a:contextStack + if szCurrentContext != '::' + let szTagQuery = substitute(szCurrentContext, '^::', '', 'g').'::'.a:szSymbol + else + let szTagQuery = a:szSymbol + endif + + let tagList = omni#common#utils#TagListNoThrow('^'.szTagQuery.'$') + call filter(tagList, a:szTagFilter) + if len(tagList) + let tagItem = tagList[0] + break + endif + endfor + return tagItem +endfunc + +" Return if the tag item represent an unnamed type +function! s:IsUnnamedType(tagItem) + let bResult = 0 + if has_key(a:tagItem, 'typeref') + " Note: Thanks for __anon ! + let bResult = match(a:tagItem.typeref, '\C\<__anon') >= 0 + endif + return bResult +endfunc + +" Search the declaration of a variable and return the type info +function! s:SearchTypeInfoOfDecl(szVariable) + let szReVariable = '\C\<'.a:szVariable.'\>' + + let originalPos = getpos('.') + let origPos = originalPos[1:2] + let curPos = origPos + let stopPos = origPos + + while curPos !=[0,0] + " We go to the start of the current scope + let curPos = searchpairpos('{', '', '}', 'bW', g:omni#cpp#utils#expIgnoreComments) + if curPos != [0,0] + let matchPos = curPos + " Now want to search our variable but we don't want to go in child + " scope + while matchPos != [0,0] + let matchPos = searchpos('{\|'.szReVariable, 'W', stopPos[0]) + if matchPos != [0,0] + " We ignore matches under comment + if omni#cpp#utils#IsCursorInCommentOrString() + continue + endif + + " Getting the current line + let szLine = getline('.') + if match(szLine, szReVariable)>=0 + " We found our variable + " Check if the current instruction is a decl instruction + let tokens = omni#cpp#utils#TokenizeCurrentInstruction() + let szTypeInfo = s:ExtractTypeInfoFromDecl(tokens) + if szTypeInfo != '' + call setpos('.', originalPos) + return omni#cpp#utils#CreateTypeInfo(szTypeInfo) + endif + else + " We found a child scope, we don't want to go in, thus + " we search for the end } of this child scope + let bracketEnd = searchpairpos('{', '', '}', 'nW', g:omni#cpp#utils#expIgnoreComments) + if bracketEnd == [0,0] + break + endif + + if bracketEnd[0] >= stopPos[0] + " The end of the scope is after our cursor we stop + " the search + break + else + " We move the cursor and continue to search our + " variable + call setpos('.', [0, bracketEnd[0], bracketEnd[1], 0]) + endif + endif + endif + endwhile + + " Backing to the start of the scope + call setpos('.', [0,curPos[0], curPos[1], 0]) + let stopPos = curPos + endif + endwhile + + let result = {} + if s:LocalSearchDecl(a:szVariable)==0 && !omni#cpp#utils#IsCursorInCommentOrString() + let tokens = omni#cpp#utils#TokenizeCurrentInstruction() + let szTypeInfo = s:ExtractTypeInfoFromDecl(tokens) + if szTypeInfo != '' + let result = omni#cpp#utils#CreateTypeInfo(szTypeInfo) + endif + endif + + call setpos('.', originalPos) + + return result +endfunc + +" Search a declaration +" @return +" - tokens of the current instruction if success +" - empty list if failure +function! s:SearchDecl(szVariable) + let result = {} + let originalPos = getpos('.') + let searchResult = s:LocalSearchDecl(a:szVariable) + if searchResult==0 + " searchdecl() may detect a decl if the variable is in a conditional + " instruction (if, elseif, while etc...) + " We have to check if the detected decl is really a decl instruction + let tokens = omni#cpp#utils#TokenizeCurrentInstruction() + + for token in tokens + " Simple test + if index(['if', 'elseif', 'while', 'for', 'switch'], token.value)>=0 + " Invalid declaration instruction + call setpos('.', originalPos) + return result + endif + endfor + + let szTypeInfo = s:ExtractTypeInfoFromDecl(tokens) + if szTypeInfo != '' + let result = omni#cpp#utils#CreateTypeInfo(szTypeInfo) + endif + endif + call setpos('.', originalPos) + return result +endfunc + +" Extract the type info string from an instruction. +" We use a small parser to extract the type +" We parse the code according to a C++ BNF from: http://www.nongnu.org/hcb/#basic.link +" @param tokens: token list of the current instruction +function! s:ExtractTypeInfoFromDecl(tokens) + return omni#cpp#utils#ExtractTypeInfoFromTokens(a:tokens) +endfunc + +" Convert tokens to string +function! s:TokensToString(tokens) + let result = '' + for token in a:tokens + let result = result . token.value . ' ' + endfor + return result[:-2] +endfunc + +" Resolve a cast. +" Resolve a C++ cast +" @param list of token. tokens must be a list that represents +" a cast expression (C++ cast) the function does not control +" if it's a cast or not +" eg: static_cast(something) +" @return type info string +function! s:ResolveCppCast(tokens) + return omni#cpp#utils#ExtractTypeInfoFromTokens(s:ResolveCast(a:tokens, '<', '>')) +endfunc + +" Resolve a cast. +" Resolve a C cast +" @param list of token. tokens must be a list that represents +" a cast expression (C cast) the function does not control +" if it's a cast or not +" eg: (MyClass*)something +" @return type info string +function! s:ResolveCCast(tokens) + return omni#cpp#utils#ExtractTypeInfoFromTokens(s:ResolveCast(a:tokens, '(', ')')) +endfunc + +" Resolve a cast. +" Resolve a C cast +" @param list of token. tokens must be a list that represents +" a cast expression (C cast) the function does not control +" if it's a cast or not +" eg: (MyClass*)something +" @return type tokens +function! s:ResolveCast(tokens, startChar, endChar) + let tokens = omni#cpp#utils#BuildParenthesisGroups(a:tokens) + + " We remove useless parenthesis eg: (((MyClass))) + let tokens = omni#cpp#utils#SimplifyParenthesis(tokens) + + let countItem=0 + let startIndex = -1 + let endIndex = -1 + let i = 0 + for token in tokens + if startIndex==-1 + if token.value==a:startChar + let countItem += 1 + let startIndex = i + endif + else + if token.value==a:startChar + let countItem += 1 + elseif token.value==a:endChar + let countItem -= 1 + endif + + if countItem==0 + let endIndex = i + break + endif + endif + let i+=1 + endfor + + return tokens[startIndex+1 : endIndex-1] +endfunc + +" Replacement for build-in function 'searchdecl' +" It does not require that the upper-level bracket is in the first column. +" Otherwise it should be equal to 'searchdecl(name, 0, 1)' +" @param name: name of variable to find declaration for +function! s:LocalSearchDecl(name) + + if g:OmniCpp_LocalSearchDecl == 0 + let bUserIgnoreCase = &ignorecase + + " Forcing the noignorecase option + " avoid bug when, for example, if we have a declaration like this : "A a;" + set noignorecase + + let result = searchdecl(a:name, 0, 1) + + " Restoring user's setting + let &ignorecase = bUserIgnoreCase + + return result + endif + + let lastpos = getpos('.') + let winview = winsaveview() + let lastfoldenable = &foldenable + let &foldenable = 0 + + " We add \C (noignorecase) to + " avoid bug when, for example, if we have a declaration like this : "A a;" + let varname = "\\C\\<" . a:name . "\\>" + + " Go to first blank line before begin of highest scope + normal 99[{ + let scopepos = getpos('.') + while (line('.') > 1) && (len(split(getline('.'))) > 0) + call cursor(line('.')-1, 0) + endwhile + + let declpos = [ 0, 0, 0, 0 ] + while search(varname, '', scopepos[1]) > 0 + " Check if we are a string or a comment + if omni#cpp#utils#IsCursorInCommentOrString() + continue + endif + + " Remember match + let declpos = getpos('.') + endwhile + if declpos[1] != 0 + " We found a match + call winrestview(winview) + call setpos('.', declpos) + let &foldenable = lastfoldenable + return 0 + endif + + while search(varname, '', lastpos[1]) > 0 + " Check if current scope is ending before variable + let old_cur = getpos('.') + normal ]} + let new_cur = getpos('.') + call setpos('.', old_cur) + if (new_cur[1] < lastpos[1]) || ((new_cur[1] == lastpos[1]) && (new_cur[2] < lastpos[2])) + continue + endif + + " Check if we are a string or a comment + if omni#cpp#utils#IsCursorInCommentOrString() + continue + endif + + " We found match + call winrestview(winview) + call setpos('.', old_cur) + let &foldenable = lastfoldenable + return 0 + endwhile + + " No match found. + call winrestview(winview) + let &foldenable = lastfoldenable + return 1 +endfunc diff --git a/VimFiles/autoload/omni/cpp/maycomplete.vim b/VimFiles/autoload/omni/cpp/maycomplete.vim new file mode 100755 index 0000000..610526b --- /dev/null +++ b/VimFiles/autoload/omni/cpp/maycomplete.vim @@ -0,0 +1,82 @@ +" Description: Omni completion script for cpp files +" Maintainer: Vissale NEANG +" Last Change: 26 sept. 2007 + +" Check if we can use omni completion in the current buffer +function! s:CanUseOmnicompletion() + " For C and C++ files and only if the omnifunc is omni#cpp#complete#Main + return (index(['c', 'cpp'], &filetype)>=0 && &omnifunc == 'omni#cpp#complete#Main' && !omni#cpp#utils#IsCursorInCommentOrString()) +endfunc + +" Return the mapping of omni completion +function! omni#cpp#maycomplete#Complete() + let szOmniMapping = "\\" + + " 0 = don't select first item + " 1 = select first item (inserting it to the text, default vim behaviour) + " 2 = select first item (without inserting it to the text) + if g:OmniCpp_SelectFirstItem == 0 + " We have to force the menuone option to avoid confusion when there is + " only one popup item + set completeopt-=menu + set completeopt+=menuone + let szOmniMapping .= "\" + elseif g:OmniCpp_SelectFirstItem == 2 + " We have to force the menuone option to avoid confusion when there is + " only one popup item + set completeopt-=menu + set completeopt+=menuone + let szOmniMapping .= "\" + let szOmniMapping .= "\=pumvisible() ? \"\\\" : \"\"\" + endif + return szOmniMapping +endfunc + +" May complete function for dot +function! omni#cpp#maycomplete#Dot() + if s:CanUseOmnicompletion() && g:OmniCpp_MayCompleteDot + let g:omni#cpp#items#data = omni#cpp#items#Get(omni#cpp#utils#TokenizeCurrentInstruction('.')) + if len(g:omni#cpp#items#data) + let s:bMayComplete = 1 + return '.' . omni#cpp#maycomplete#Complete() + endif + endif + return '.' +endfunc +" May complete function for arrow +function! omni#cpp#maycomplete#Arrow() + if s:CanUseOmnicompletion() && g:OmniCpp_MayCompleteArrow + let index = col('.') - 2 + if index >= 0 + let char = getline('.')[index] + if char == '-' + let g:omni#cpp#items#data = omni#cpp#items#Get(omni#cpp#utils#TokenizeCurrentInstruction('>')) + if len(g:omni#cpp#items#data) + let s:bMayComplete = 1 + return '>' . omni#cpp#maycomplete#Complete() + endif + endif + endif + endif + return '>' +endfunc + +" May complete function for double points +function! omni#cpp#maycomplete#Scope() + if s:CanUseOmnicompletion() && g:OmniCpp_MayCompleteScope + let index = col('.') - 2 + if index >= 0 + let char = getline('.')[index] + if char == ':' + let g:omni#cpp#items#data = omni#cpp#items#Get(omni#cpp#utils#TokenizeCurrentInstruction(':')) + if len(g:omni#cpp#items#data) + if len(g:omni#cpp#items#data[-1].tokens) && g:omni#cpp#items#data[-1].tokens[-1].value != '::' + let s:bMayComplete = 1 + return ':' . omni#cpp#maycomplete#Complete() + endif + endif + endif + endif + endif + return ':' +endfunc diff --git a/VimFiles/autoload/omni/cpp/namespaces.vim b/VimFiles/autoload/omni/cpp/namespaces.vim new file mode 100755 index 0000000..386b3f9 --- /dev/null +++ b/VimFiles/autoload/omni/cpp/namespaces.vim @@ -0,0 +1,838 @@ +" Description: Omni completion script for cpp files +" Maintainer: Vissale NEANG +" Last Change: 26 sept. 2007 + +let g:omni#cpp#namespaces#CacheResolve = {} +let g:omni#cpp#namespaces#CacheUsing = {} +" TODO: For the next release +"let g:omni#cpp#namespaces#CacheAlias = {} + +" Get the using namespace list from a line +function! s:GetNamespaceAliasListFromLine(szLine) + let result = {} + let tokens = omni#cpp#tokenizer#Tokenize(a:szLine) + let szAlias = '' + let szNamespace = '' + let state = 0 + for token in tokens + if state==0 + let szAlias = '' + let szNamespace = '' + if token.value == '/*' + let state = 1 + elseif token.value == '//' + " It's a comment + let state = -1 + break + elseif token.value == 'namespace' + let state = 2 + endif + elseif state==1 + if token.value == '*/' + let state=0 + endif + elseif state==2 + if token.kind == 'cppWord' + let szAlias .= token.value + let state = 3 + else + let state = -1 + break + endif + elseif state == 3 + if token.value == '=' + let state = 4 + else + let state = -1 + break + endif + elseif state == 4 + if token.value == '::' + let szNamespace .= token.value + let state = 5 + elseif token.kind == 'cppWord' + let szNamespace .= token.value + let state = 6 + " Maybe end of tokens + endif + elseif state==5 + if token.kind == 'cppWord' + let szNamespace .= token.value + let state = 6 + " Maybe end of tokens + else + " Error, we can't have 'namespace ALIAS = Something::' + let state = -1 + break + endif + elseif state==6 + if token.value == '::' + let szNamespace .= token.value + let state = 5 + else + call extend(result, {szAlias : szNamespace}) + let state = 0 + endif + endif + endfor + + if state == 6 + call extend(result, {szAlias : szNamespace}) + endif + + return result +endfunc + +" Get the using namespace list from a line +function! s:GetNamespaceListFromLine(szLine) + let result = [] + let tokens = omni#cpp#tokenizer#Tokenize(a:szLine) + let szNamespace = '' + let state = 0 + for token in tokens + if state==0 + let szNamespace = '' + if token.value == '/*' + let state = 1 + elseif token.value == '//' + " It's a comment + let state = -1 + break + elseif token.value == 'using' + let state = 2 + endif + elseif state==1 + if token.value == '*/' + let state=0 + endif + elseif state==2 + if token.value == 'namespace' + let state = 3 + else + " Error, 'using' must be followed by 'namespace' + let state = -1 + break + endif + elseif state==3 + if token.value == '::' + let szNamespace .= token.value + let state = 4 + elseif token.kind == 'cppWord' + let szNamespace .= token.value + let state = 5 + " Maybe end of tokens + endif + elseif state==4 + if token.kind == 'cppWord' + let szNamespace .= token.value + let state = 5 + " Maybe end of tokens + else + " Error, we can't have 'using namespace Something::' + let state = -1 + break + endif + elseif state==5 + if token.value == '::' + let szNamespace .= token.value + let state = 4 + else + call extend(result, [szNamespace]) + let state = 0 + endif + endif + endfor + + if state == 5 + call extend(result, [szNamespace]) + endif + + return result +endfunc + +" Get the namespace list from a namespace map +function! s:GetUsingNamespaceListFromMap(namespaceMap, ...) + let stopLine = 0 + if a:0>0 + let stopLine = a:1 + endif + + let result = [] + let keys = sort(keys(a:namespaceMap), 'omni#common#utils#CompareNumber') + for i in keys + if stopLine != 0 && i > stopLine + break + endif + call extend(result, a:namespaceMap[i]) + endfor + return result +endfunc + +" Get global using namespace list from the current buffer +function! omni#cpp#namespaces#GetListFromCurrentBuffer(...) + let namespaceMap = s:GetAllUsingNamespaceMapFromCurrentBuffer() + let result = [] + if namespaceMap != {} + let result = s:GetUsingNamespaceListFromMap(namespaceMap, (a:0 > 0)? a:1 : line('.')) + endif + return result +endfunc + +" Get global using namespace map from the current buffer and include files recursively +function! s:GetAllUsingNamespaceMapFromCurrentBuffer(...) + let includeGuard = (a:0>0)? a:1 : {} + + let szBufferName = getreg("%") + let szFilePath = omni#cpp#utils#ResolveFilePath(szBufferName) + let szFilePath = (szFilePath=='')? szBufferName : szFilePath + + let namespaceMap = {} + if has_key(includeGuard, szFilePath) + return namespaceMap + else + let includeGuard[szFilePath] = 1 + endif + + let namespaceMap = omni#cpp#namespaces#GetMapFromCurrentBuffer() + + if g:OmniCpp_NamespaceSearch != 2 + " We don't search included files if OmniCpp_NamespaceSearch != 2 + return namespaceMap + endif + + for inc in omni#cpp#includes#GetList() + let lnum = inc.pos[0] + let tmpMap = s:GetAllUsingNamespaceMapFromFile(inc.include, includeGuard) + if tmpMap != {} + if has_key(namespaceMap, lnum) + call extend(namespaceMap[lnum], s:GetUsingNamespaceListFromMap(tmpMap)) + else + let namespaceMap[lnum] = s:GetUsingNamespaceListFromMap(tmpMap) + endif + endif + endfor + + return namespaceMap +endfunc + +" Get global using namespace map from a file and include files recursively +function! s:GetAllUsingNamespaceMapFromFile(szFilePath, ...) + let includeGuard = {} + if a:0 >0 + let includeGuard = a:1 + endif + + let szFilePath = omni#cpp#utils#ResolveFilePath(a:szFilePath) + let szFilePath = (szFilePath=='')? a:szFilePath : szFilePath + + let namespaceMap = {} + if has_key(includeGuard, szFilePath) + return namespaceMap + else + let includeGuard[szFilePath] = 1 + endif + + " If g:OmniCpp_NamespaceSearch == 1 (search namespaces only in the current + " buffer) we don't use cache for the current buffer + let namespaceMap = omni#cpp#namespaces#GetMapFromBuffer(szFilePath, g:OmniCpp_NamespaceSearch==1) + + if g:OmniCpp_NamespaceSearch != 2 + " We don't search included files if OmniCpp_NamespaceSearch != 2 + return namespaceMap + endif + + for inc in omni#cpp#includes#GetList(szFilePath) + let lnum = inc.pos[0] + let tmpMap = s:GetAllUsingNamespaceMapFromFile(inc.include, includeGuard) + if tmpMap != {} + if has_key(namespaceMap, lnum) + call extend(namespaceMap[lnum], s:GetUsingNamespaceListFromMap(tmpMap)) + else + let namespaceMap[lnum] = s:GetUsingNamespaceListFromMap(tmpMap) + endif + endif + endfor + + return namespaceMap +endfunc + +" Get global using namespace map from a the current buffer +function! omni#cpp#namespaces#GetMapFromCurrentBuffer() + let namespaceMap = {} + let originalPos = getpos('.') + + call setpos('.', [0, 1, 1, 0]) + let curPos = [1,1] + while curPos != [0,0] + let curPos = searchpos('\C^using\s\+namespace', 'W') + if curPos != [0,0] + let szLine = getline('.') + let startPos = curPos[1] + let endPos = match(szLine, ';', startPos-1) + if endPos!=-1 + " We get the namespace list from the line + let namespaceMap[curPos[0]] = s:GetNamespaceListFromLine(szLine) + endif + endif + endwhile + + call setpos('.', originalPos) + return namespaceMap +endfunc + +" Get global using namespace map from a file +function! omni#cpp#namespaces#GetMapFromBuffer(szFilePath, ...) + let bUpdate = 0 + if a:0 > 0 + let bUpdate = a:1 + endif + + let szFilePath = omni#cpp#utils#ResolveFilePath(a:szFilePath) + let szFilePath = (szFilePath=='')? a:szFilePath : szFilePath + + if !bUpdate && has_key(g:omni#cpp#namespaces#CacheUsing, szFilePath) + return copy(g:omni#cpp#namespaces#CacheUsing[szFilePath]) + endif + + let namespaceMap = {} + " The file exists, we get the global namespaces in this file + let szFixedPath = escape(szFilePath, g:omni#cpp#utils#szEscapedCharacters) + execute 'silent! lvimgrep /\C^using\s\+namespace/gj '.szFixedPath + + " key = line number + " value = list of namespaces + let listQuickFix = getloclist(0) + for qf in listQuickFix + let szLine = qf.text + let startPos = qf.col + let endPos = match(szLine, ';', startPos-1) + if endPos!=-1 + " We get the namespace list from the line + let namespaceMap[qf.lnum] = s:GetNamespaceListFromLine(szLine) + endif + endfor + + if szFixedPath != '' + let g:omni#cpp#namespaces#CacheUsing[szFixedPath] = namespaceMap + endif + + return copy(namespaceMap) +endfunc + +" Get the stop position when searching for local variables +function! s:GetStopPositionForLocalSearch() + " Stop position when searching a local variable + let originalPos = getpos('.') + let origPos = originalPos[1:2] + let stopPosition = origPos + let curPos = origPos + while curPos !=[0,0] + let stopPosition = curPos + let curPos = searchpairpos('{', '', '}', 'bW', g:omni#cpp#utils#expIgnoreComments) + endwhile + call setpos('.', originalPos) + + return stopPosition +endfunc + +" Get namespaces alias used at the cursor postion in a vim buffer +" Note: The result depends on the current cursor position +" @return +" - Map of namespace alias +function! s:GetNamespaceAliasMap() + " We store the cursor position because searchpairpos() moves the cursor + let result = {} + let originalPos = getpos('.') + let origPos = originalPos[1:2] + + let stopPos = s:GetStopPositionForLocalSearch() + let stopLine = stopPos[0] + let curPos = origPos + let lastLine = 0 + let nextStopLine = origPos[0] + let szReAlias = '\Cnamespace\s\+\w\+\s\+=' + while curPos !=[0,0] + let curPos = searchpos('}\|\('. szReAlias .'\)', 'bW',stopLine) + if curPos!=[0,0] && curPos[0]!=lastLine + let lastLine = curPos[0] + + let szLine = getline('.') + if origPos[0] == curPos[0] + " We get the line until cursor position + let szLine = szLine[:origPos[1]] + endif + + let szLine = omni#cpp#utils#GetCodeFromLine(szLine) + if match(szLine, szReAlias)<0 + " We found a '}' + let curPos = searchpairpos('{', '', '}', 'bW', g:omni#cpp#utils#expIgnoreComments) + else + " We get the namespace alias from the line + call extend(result, s:GetNamespaceAliasListFromLine(szLine)) + let nextStopLine = curPos[0] + endif + endif + endwhile + + " Setting the cursor to the original position + call setpos('.', originalPos) + + call s:ResolveAliasKeys(result) + return result +endfunc + +" Resolve an alias +" eg: namespace IAmAnAlias1 = Ns1 +" eg: namespace IAmAnAlias2 = IAmAnAlias1::Ns2 +" => IAmAnAlias2 = Ns1::Ns2 +function! s:ResolveAliasKey(mapNamespaceAlias, szAlias) + let szResult = a:mapNamespaceAlias[a:szAlias] + " ::Ns1::Ns2::Ns3 => ['Ns1', 'Ns2', 'Ns3'] + let listNamespace = split(szResult, '::') + if len(listNamespace) + " szBeginPart = 'Ns1' + let szBeginPart = remove(listNamespace, 0) + + " Is 'Ns1' an alias ? + if has_key(a:mapNamespaceAlias, szBeginPart) && szBeginPart != a:szAlias + " Resolving alias 'Ns1' + " eg: Ns1 = NsResolved + let szResult = s:ResolveAliasKey(a:mapNamespaceAlias, szBeginPart) + " szEndPart = 'Ns2::Ns3' + let szEndPart = join(listNamespace, '::') + if szEndPart != '' + " Concatenation => szResult = 'NsResolved::Ns2::Ns3' + let szResult .= '::' . szEndPart + endif + endif + endif + return szResult +endfunc + +" Resolve all keys in the namespace alias map +function! s:ResolveAliasKeys(mapNamespaceAlias) + let mapNamespaceAlias = a:mapNamespaceAlias + call map(mapNamespaceAlias, 's:ResolveAliasKey(mapNamespaceAlias, v:key)') +endfunc + +" Resolve namespace alias +function! omni#cpp#namespaces#ResolveAlias(mapNamespaceAlias, szNamespace) + let szResult = a:szNamespace + " ::Ns1::Ns2::Ns3 => ['Ns1', 'Ns2', 'Ns3'] + let listNamespace = split(a:szNamespace, '::') + if len(listNamespace) + " szBeginPart = 'Ns1' + let szBeginPart = remove(listNamespace, 0) + + " Is 'Ns1' an alias ? + if has_key(a:mapNamespaceAlias, szBeginPart) + " Resolving alias 'Ns1' + " eg: Ns1 = NsResolved + let szResult = a:mapNamespaceAlias[szBeginPart] + " szEndPart = 'Ns2::Ns3' + let szEndPart = join(listNamespace, '::') + if szEndPart != '' + " Concatenation => szResult = 'NsResolved::Ns2::Ns3' + let szResult .= '::' . szEndPart + endif + + " If a:szNamespace starts with '::' we add '::' to the beginning + " of the result + if match(a:szNamespace, '^::')>=0 + let szResult = omni#cpp#utils#SimplifyScope('::' . szResult) + endif + endif + endif + return szResult +endfunc + +" Resolve namespace alias +function! s:ResolveAliasInNamespaceList(mapNamespaceAlias, listNamespaces) + call map(a:listNamespaces, 'omni#cpp#namespaces#ResolveAlias(a:mapNamespaceAlias, v:val)') +endfunc + +" Get namespaces used at the cursor postion in a vim buffer +" Note: The result depends on the current cursor position +" @return +" - List of namespace used in the reverse order +function! omni#cpp#namespaces#GetUsingNamespaces() + " We have to get local using namespace declarations + " We need the current cursor position and the position of the start of the + " current scope + + " We store the cursor position because searchpairpos() moves the cursor + let result = [] + let originalPos = getpos('.') + let origPos = originalPos[1:2] + + let stopPos = s:GetStopPositionForLocalSearch() + + let stopLine = stopPos[0] + let curPos = origPos + let lastLine = 0 + let nextStopLine = origPos[0] + while curPos !=[0,0] + let curPos = searchpos('\C}\|\(using\s\+namespace\)', 'bW',stopLine) + if curPos!=[0,0] && curPos[0]!=lastLine + let lastLine = curPos[0] + + let szLine = getline('.') + if origPos[0] == curPos[0] + " We get the line until cursor position + let szLine = szLine[:origPos[1]] + endif + + let szLine = omni#cpp#utils#GetCodeFromLine(szLine) + if match(szLine, '\Cusing\s\+namespace')<0 + " We found a '}' + let curPos = searchpairpos('{', '', '}', 'bW', g:omni#cpp#utils#expIgnoreComments) + else + " We get the namespace list from the line + let result = s:GetNamespaceListFromLine(szLine) + result + let nextStopLine = curPos[0] + endif + endif + endwhile + + " Setting the cursor to the original position + call setpos('.', originalPos) + + " 2) Now we can get all global using namespace declaration from the + " beginning of the file to nextStopLine + let result = omni#cpp#namespaces#GetListFromCurrentBuffer(nextStopLine) + result + + " Resolving alias in the namespace list + " TODO: For the next release + "let g:omni#cpp#namespaces#CacheAlias= s:GetNamespaceAliasMap() + "call s:ResolveAliasInNamespaceList(g:omni#cpp#namespaces#CacheAlias, result) + + return ['::'] + result +endfunc + +" Resolve a using namespace regarding the current context +" For each namespace used: +" - We get all possible contexts where the namespace +" can be define +" - We do a comparison test of each parent contexts with the current +" context list +" - If one and only one parent context is present in the +" current context list we add the namespace in the current +" context +" - If there is more than one of parent contexts in the +" current context the namespace is ambiguous +" @return +" - result item +" - kind = 0|1 +" - 0 = unresolved or error +" - 1 = resolved +" - value = resolved namespace +function! s:ResolveNamespace(namespace, mapCurrentContexts) + let result = {'kind':0, 'value': ''} + + " If the namespace is already resolved we add it in the list of + " current contexts + if match(a:namespace, '^::')>=0 + let result.kind = 1 + let result.value = a:namespace + return result + elseif match(a:namespace, '\w\+::\w\+')>=0 + let mapCurrentContextsTmp = copy(a:mapCurrentContexts) + let resolvedItem = {} + for nsTmp in split(a:namespace, '::') + let resolvedItem = s:ResolveNamespace(nsTmp, mapCurrentContextsTmp) + if resolvedItem.kind + " Note: We don't extend the map + let mapCurrentContextsTmp = {resolvedItem.value : 1} + else + break + endif + endfor + if resolvedItem!={} && resolvedItem.kind + let result.kind = 1 + let result.value = resolvedItem.value + endif + return result + endif + + " We get all possible parent contexts of this namespace + let listTagsOfNamespace = [] + if has_key(g:omni#cpp#namespaces#CacheResolve, a:namespace) + let listTagsOfNamespace = g:omni#cpp#namespaces#CacheResolve[a:namespace] + else + let listTagsOfNamespace = omni#common#utils#TagList('^'.a:namespace.'$') + let g:omni#cpp#namespaces#CacheResolve[a:namespace] = listTagsOfNamespace + endif + + if len(listTagsOfNamespace)==0 + return result + endif + call filter(listTagsOfNamespace, 'v:val.kind[0]=="n"') + + " We extract parent context from tags + " We use a map to avoid multiple entries + let mapContext = {} + for tagItem in listTagsOfNamespace + let szParentContext = omni#cpp#utils#ExtractScope(tagItem) + let mapContext[szParentContext] = 1 + endfor + let listParentContext = keys(mapContext) + + " Now for each parent context we test if the context is in the current + " contexts list + let listResolvedNamespace = [] + for szParentContext in listParentContext + if has_key(a:mapCurrentContexts, szParentContext) + call extend(listResolvedNamespace, [omni#cpp#utils#SimplifyScope(szParentContext.'::'.a:namespace)]) + endif + endfor + + " Now we know if the namespace is ambiguous or not + let len = len(listResolvedNamespace) + if len==1 + " Namespace resolved + let result.kind = 1 + let result.value = listResolvedNamespace[0] + elseif len > 1 + " Ambiguous namespace, possible matches are in listResolvedNamespace + else + " Other cases + endif + return result +endfunc + +" Resolve namespaces +"@return +" - List of resolved namespaces +function! omni#cpp#namespaces#ResolveAll(namespacesUsed) + + " We add the default context '::' + let contextOrder = 0 + let mapCurrentContexts = {} + + " For each namespace used: + " - We get all possible contexts where the namespace + " can be define + " - We do a comparison test of each parent contexts with the current + " context list + " - If one and only one parent context is present in the + " current context list we add the namespace in the current + " context + " - If there is more than one of parent contexts in the + " current context the namespace is ambiguous + for ns in a:namespacesUsed + let resolvedItem = s:ResolveNamespace(ns, mapCurrentContexts) + if resolvedItem.kind + let contextOrder+=1 + let mapCurrentContexts[resolvedItem.value] = contextOrder + endif + endfor + + " Build the list of current contexts from the map, we have to keep the + " order + let mapReorder = {} + for key in keys(mapCurrentContexts) + let mapReorder[ mapCurrentContexts[key] ] = key + endfor + let result = [] + for key in sort(keys(mapReorder)) + call extend(result, [mapReorder[key]]) + endfor + return result +endfunc + +" Build the context stack +function! s:BuildContextStack(namespaces, szCurrentScope) + let result = copy(a:namespaces) + if a:szCurrentScope != '::' + let tagItem = omni#cpp#utils#GetResolvedTagItem(a:namespaces, omni#cpp#utils#CreateTypeInfo(a:szCurrentScope)) + if has_key(tagItem, 'inherits') + let listBaseClass = omni#cpp#utils#GetClassInheritanceList(a:namespaces, omni#cpp#utils#CreateTypeInfo(a:szCurrentScope)) + let result = listBaseClass + result + elseif has_key(tagItem, 'kind') && index(['c', 's', 'u', 'n'], tagItem.kind[0])>=0 + call insert(result, omni#cpp#utils#ExtractTypeInfoFromTag(tagItem)) + endif + endif + return result +endfunc + +" Returns the class scope at the current position of the cursor +" @return a string that represents the class scope +" eg: ::NameSpace1::Class1 +" The returned string always starts with '::' +" Note: In term of performance it's the weak point of the script +function! s:GetClassScopeAtCursor() + " We store the cursor position because searchpairpos() moves the cursor + let originalPos = getpos('.') + let endPos = originalPos[1:2] + let listCode = [] + let result = {'namespaces': [], 'scope': ''} + + while endPos!=[0,0] + let endPos = searchpairpos('{', '', '}', 'bW', g:omni#cpp#utils#expIgnoreComments) + let szReStartPos = '[;{}]\|\%^' + let startPos = searchpairpos(szReStartPos, '', '{', 'bWn', g:omni#cpp#utils#expIgnoreComments) + + " If the file starts with a comment so the startPos can be [0,0] + " we change it to [1,1] + if startPos==[0,0] + let startPos = [1,1] + endif + + " Get lines backward from cursor position to last ; or { or } + " or when we are at the beginning of the file. + " We store lines in listCode + if endPos!=[0,0] + " We remove the last character which is a '{' + " We also remove starting { or } or ; if exits + let szCodeWithoutComments = substitute(omni#cpp#utils#GetCode(startPos, endPos)[:-2], '^[;{}]', '', 'g') + call insert(listCode, {'startLine' : startPos[0], 'code' : szCodeWithoutComments}) + endif + endwhile + " Setting the cursor to the original position + call setpos('.', originalPos) + + let listClassScope = [] + let bResolved = 0 + let startLine = 0 + " Now we can check in the list of code if there is a function + for code in listCode + " We get the name of the namespace, class, struct or union + " and we store it in listClassScope + let tokens = omni#cpp#tokenizer#Tokenize(code.code) + let bContinue=0 + let bAddNamespace = 0 + let state=0 + for token in tokens + if state==0 + if index(['namespace', 'class', 'struct', 'union'], token.value)>=0 + if token.value == 'namespace' + let bAddNamespace = 1 + endif + let state= 1 + " Maybe end of tokens + endif + elseif state==1 + if token.kind == 'cppWord' + " eg: namespace MyNs { class MyCl {}; } + " => listClassScope = [MyNs, MyCl] + call extend( listClassScope , [token.value] ) + + " Add the namespace in result + if bAddNamespace + call extend(result.namespaces, [token.value]) + let bAddNamespace = 0 + endif + + let bContinue=1 + break + endif + endif + endfor + if bContinue==1 + continue + endif + + " Simple test to check if we have a chance to find a + " class method + let aPos = matchend(code.code, '::\s*\~*\s*\w\+\s*(') + if aPos ==-1 + continue + endif + + let startLine = code.startLine + let listTmp = [] + " eg: 'void MyNamespace::MyClass::foo(' + " => tokens = ['MyClass', '::', 'MyNamespace', 'void'] + let tokens = reverse(omni#cpp#tokenizer#Tokenize(code.code[:aPos-1])[:-4]) + let state = 0 + " Reading tokens backward + for token in tokens + if state==0 + if token.kind=='cppWord' + call insert(listTmp, token.value) + let state=1 + endif + elseif state==1 + if token.value=='::' + let state=2 + else + break + endif + elseif state==2 + if token.kind=='cppWord' + call insert(listTmp, token.value) + let state=1 + else + break + endif + endif + endfor + + if len(listTmp) + if len(listClassScope) + let bResolved = 1 + " Merging class scopes + " eg: current class scope = 'MyNs::MyCl1' + " method class scope = 'MyCl1::MyCl2' + " If we add the method class scope to current class scope + " we'll have MyNs::MyCl1::MyCl1::MyCl2 => it's wrong + " we want MyNs::MyCl1::MyCl2 + let index = 0 + for methodClassScope in listTmp + if methodClassScope==listClassScope[-1] + let listTmp = listTmp[index+1:] + break + else + let index+=1 + endif + endfor + endif + call extend(listClassScope, listTmp) + break + endif + endfor + + let szClassScope = '::' + if len(listClassScope) + if bResolved + let szClassScope .= join(listClassScope, '::') + else + let szClassScope = join(listClassScope, '::') + + " The class scope is not resolved, we have to check using + " namespace declarations and search the class scope in each + " namespace + if startLine != 0 + let namespaces = ['::'] + omni#cpp#namespaces#GetListFromCurrentBuffer(startLine) + let namespaces = omni#cpp#namespaces#ResolveAll(namespaces) + let tagItem = omni#cpp#utils#GetResolvedTagItem(namespaces, omni#cpp#utils#CreateTypeInfo(szClassScope)) + if tagItem != {} + let szClassScope = omni#cpp#utils#ExtractTypeInfoFromTag(tagItem) + endif + endif + endif + endif + + let result.scope = szClassScope + return result +endfunc + +" Get all contexts at the cursor position +function! omni#cpp#namespaces#GetContexts() + " Get the current class scope at the cursor, the result depends on the current cursor position + let scopeItem = s:GetClassScopeAtCursor() + let listUsingNamespace = copy(g:OmniCpp_DefaultNamespaces) + call extend(listUsingNamespace, scopeItem.namespaces) + if g:OmniCpp_NamespaceSearch && &filetype != 'c' + " Get namespaces used in the file until the cursor position + let listUsingNamespace = omni#cpp#namespaces#GetUsingNamespaces() + listUsingNamespace + " Resolving namespaces, removing ambiguous namespaces + let namespaces = omni#cpp#namespaces#ResolveAll(listUsingNamespace) + else + let namespaces = ['::'] + listUsingNamespace + endif + call reverse(namespaces) + + " Building context stack from namespaces and the current class scope + return s:BuildContextStack(namespaces, scopeItem.scope) +endfunc diff --git a/VimFiles/autoload/omni/cpp/settings.vim b/VimFiles/autoload/omni/cpp/settings.vim new file mode 100755 index 0000000..6683d3a --- /dev/null +++ b/VimFiles/autoload/omni/cpp/settings.vim @@ -0,0 +1,96 @@ +" Description: Omni completion script for cpp files +" Maintainer: Vissale NEANG +" Last Change: 26 sept. 2007 + +function! omni#cpp#settings#Init() + " Global scope search on/off + " 0 = disabled + " 1 = enabled + if !exists('g:OmniCpp_GlobalScopeSearch') + let g:OmniCpp_GlobalScopeSearch = 1 + endif + + " Sets the namespace search method + " 0 = disabled + " 1 = search namespaces in the current file + " 2 = search namespaces in the current file and included files + if !exists('g:OmniCpp_NamespaceSearch') + let g:OmniCpp_NamespaceSearch = 1 + endif + + " Set the class scope completion mode + " 0 = auto + " 1 = show all members (static, public, protected and private) + if !exists('g:OmniCpp_DisplayMode') + let g:OmniCpp_DisplayMode = 0 + endif + + " Set if the scope is displayed in the abbr column of the popup + " 0 = no + " 1 = yes + if !exists('g:OmniCpp_ShowScopeInAbbr') + let g:OmniCpp_ShowScopeInAbbr = 0 + endif + + " Set if the function prototype is displayed in the abbr column of the popup + " 0 = no + " 1 = yes + if !exists('g:OmniCpp_ShowPrototypeInAbbr') + let g:OmniCpp_ShowPrototypeInAbbr = 0 + endif + + " Set if the access (+,#,-) is displayed + " 0 = no + " 1 = yes + if !exists('g:OmniCpp_ShowAccess') + let g:OmniCpp_ShowAccess = 1 + endif + + " Set the list of default namespaces + " eg: ['std'] + if !exists('g:OmniCpp_DefaultNamespaces') + let g:OmniCpp_DefaultNamespaces = [] + endif + + " Set MayComplete to '.' + " 0 = disabled + " 1 = enabled + " default = 1 + if !exists('g:OmniCpp_MayCompleteDot') + let g:OmniCpp_MayCompleteDot = 1 + endif + + " Set MayComplete to '->' + " 0 = disabled + " 1 = enabled + " default = 1 + if !exists('g:OmniCpp_MayCompleteArrow') + let g:OmniCpp_MayCompleteArrow = 1 + endif + + " Set MayComplete to dot + " 0 = disabled + " 1 = enabled + " default = 0 + if !exists('g:OmniCpp_MayCompleteScope') + let g:OmniCpp_MayCompleteScope = 0 + endif + + " When completeopt does not contain longest option, this setting + " controls the behaviour of the popup menu selection when starting the completion + " 0 = don't select first item + " 1 = select first item (inserting it to the text) + " 2 = select first item (without inserting it to the text) + " default = 0 + if !exists('g:OmniCpp_SelectFirstItem') + let g:OmniCpp_SelectFirstItem= 0 + endif + + " Use local search function for variable definitions + " 0 = use standard vim search function + " 1 = use local search function + " default = 0 + if !exists('g:OmniCpp_LocalSearchDecl') + let g:OmniCpp_LocalSearchDecl= 0 + endif +endfunc diff --git a/VimFiles/autoload/omni/common/debug.vim b/VimFiles/autoload/omni/common/debug.vim new file mode 100755 index 0000000..eded649 --- /dev/null +++ b/VimFiles/autoload/omni/common/debug.vim @@ -0,0 +1,32 @@ +" Description: Omni completion debug functions +" Maintainer: Vissale NEANG +" Last Change: 26 sept. 2007 + +let s:CACHE_DEBUG_TRACE = [] + +" Start debug, clear the debug file +function! omni#common#debug#Start() + let s:CACHE_DEBUG_TRACE = [] + call extend(s:CACHE_DEBUG_TRACE, ['============ Debug Start ============']) + call writefile(s:CACHE_DEBUG_TRACE, "Omni.dbg") +endfunc + +" End debug, write to debug file +function! omni#common#debug#End() + call extend(s:CACHE_DEBUG_TRACE, ["============= Debug End ============="]) + call extend(s:CACHE_DEBUG_TRACE, [""]) + call writefile(s:CACHE_DEBUG_TRACE, "Omni.dbg") +endfunc + +" Debug trace function +function! omni#common#debug#Trace(szFuncName, ...) + let szTrace = a:szFuncName + let paramNum = a:0 + if paramNum>0 + let szTrace .= ':' + endif + for i in range(paramNum) + let szTrace = szTrace .' ('. string(eval('a:'.string(i+1))).')' + endfor + call extend(s:CACHE_DEBUG_TRACE, [szTrace]) +endfunc diff --git a/VimFiles/autoload/omni/common/utils.vim b/VimFiles/autoload/omni/common/utils.vim new file mode 100755 index 0000000..c880ad2 --- /dev/null +++ b/VimFiles/autoload/omni/common/utils.vim @@ -0,0 +1,67 @@ +" Description: Omni completion utils +" Maintainer: Vissale NEANG +" Last Change: 26 sept. 2007 + +" For sort numbers in list +function! omni#common#utils#CompareNumber(i1, i2) + let num1 = eval(a:i1) + let num2 = eval(a:i2) + return num1 == num2 ? 0 : num1 > num2 ? 1 : -1 +endfunc + +" TagList function calling the vim taglist() with try catch +" The only throwed exception is 'TagList:UserInterrupt' +" We also force the noignorecase option to avoid linear search when calling +" taglist() +function! omni#common#utils#TagList(szTagQuery) + let result = [] + let bUserIgnoreCase = &ignorecase + " Forcing noignorecase search => binary search can be used in taglist() + " if tags in the tag file are sorted + if bUserIgnoreCase + set noignorecase + endif + try + let result = taglist(a:szTagQuery) + catch /^Vim:Interrupt$/ + " Restoring user's setting + if bUserIgnoreCase + set ignorecase + endif + throw 'TagList:UserInterrupt' + catch + "Note: it seems that ctags can generate corrupted files, in this case + "taglist() will fail to read the tagfile and an exception from + "has_add() is thrown + endtry + + " Restoring user's setting + if bUserIgnoreCase + set ignorecase + endif + return result +endfunc + +" Same as TagList but don't throw exception +function! omni#common#utils#TagListNoThrow(szTagQuery) + let result = [] + try + let result = omni#common#utils#TagList(a:szTagQuery) + catch + endtry + return result +endfunc + +" Get the word under the cursor +function! omni#common#utils#GetWordUnderCursor() + let szLine = getline('.') + let startPos = getpos('.')[2]-1 + let startPos = (startPos < 0)? 0 : startPos + if szLine[startPos] =~ '\w' + let startPos = searchpos('\<\w\+', 'cbn', line('.'))[1] - 1 + endif + + let startPos = (startPos < 0)? 0 : startPos + let szResult = matchstr(szLine, '\w\+', startPos) + return szResult +endfunc diff --git a/VimFiles/autoload/omni/cpp/complete.vim b/VimFiles/autoload/omni/cpp/complete.vim new file mode 100755 index 0000000..a7e4edc --- /dev/null +++ b/VimFiles/autoload/omni/cpp/complete.vim @@ -0,0 +1,569 @@ +" 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 diff --git a/VimFiles/autoload/omni/cpp/includes.vim b/VimFiles/autoload/omni/cpp/includes.vim new file mode 100755 index 0000000..10a89bc --- /dev/null +++ b/VimFiles/autoload/omni/cpp/includes.vim @@ -0,0 +1,126 @@ +" Description: Omni completion script for cpp files +" Maintainer: Vissale NEANG +" Last Change: 26 sept. 2007 + +let g:omni#cpp#includes#CACHE_INCLUDES = {} +let g:omni#cpp#includes#CACHE_FILE_TIME = {} + +let s:rePreprocIncludePart = '\C#\s*include\s*' +let s:reIncludeFilePart = '\(<\|"\)\(\f\|\s\)\+\(>\|"\)' +let s:rePreprocIncludeFile = s:rePreprocIncludePart . s:reIncludeFilePart + +" Get the include list of a file +function! omni#cpp#includes#GetList(...) + if a:0 > 0 + return s:GetIncludeListFromFile(a:1, (a:0 > 1)? a:2 : 0 ) + else + return s:GetIncludeListFromCurrentBuffer() + endif +endfunc + +" Get the include list from the current buffer +function! s:GetIncludeListFromCurrentBuffer() + let listIncludes = [] + let originalPos = getpos('.') + + call setpos('.', [0, 1, 1, 0]) + let curPos = [1,1] + let alreadyInclude = {} + while curPos != [0,0] + let curPos = searchpos('\C\(^'.s:rePreprocIncludeFile.'\)', 'W') + if curPos != [0,0] + let szLine = getline('.') + let startPos = curPos[1] + let endPos = matchend(szLine, s:reIncludeFilePart, startPos-1) + if endPos!=-1 + let szInclusion = szLine[startPos-1:endPos-1] + let szIncludeFile = substitute(szInclusion, '\('.s:rePreprocIncludePart.'\)\|[<>""]', '', 'g') + let szResolvedInclude = omni#cpp#utils#ResolveFilePath(szIncludeFile) + + " Protection over self inclusion + if szResolvedInclude != '' && szResolvedInclude != omni#cpp#utils#ResolveFilePath(getreg('%')) + let includePos = curPos + if !has_key(alreadyInclude, szResolvedInclude) + call extend(listIncludes, [{'pos' : includePos, 'include' : szResolvedInclude}]) + let alreadyInclude[szResolvedInclude] = 1 + endif + endif + endif + endif + endwhile + + call setpos('.', originalPos) + return listIncludes +endfunc + +" Get the include list from a file +function! s:GetIncludeListFromFile(szFilePath, bUpdate) + let listIncludes = [] + if a:szFilePath == '' + return listIncludes + endif + + if !a:bUpdate && has_key(g:omni#cpp#includes#CACHE_INCLUDES, a:szFilePath) + return copy(g:omni#cpp#includes#CACHE_INCLUDES[a:szFilePath]) + endif + + let g:omni#cpp#includes#CACHE_FILE_TIME[a:szFilePath] = getftime(a:szFilePath) + + let szFixedPath = escape(a:szFilePath, g:omni#cpp#utils#szEscapedCharacters) + execute 'silent! lvimgrep /\C\(^'.s:rePreprocIncludeFile.'\)/gj '.szFixedPath + + let listQuickFix = getloclist(0) + let alreadyInclude = {} + for qf in listQuickFix + let szLine = qf.text + let startPos = qf.col + let endPos = matchend(szLine, s:reIncludeFilePart, startPos-1) + if endPos!=-1 + let szInclusion = szLine[startPos-1:endPos-1] + let szIncludeFile = substitute(szInclusion, '\('.s:rePreprocIncludePart.'\)\|[<>""]', '', 'g') + let szResolvedInclude = omni#cpp#utils#ResolveFilePath(szIncludeFile) + + " Protection over self inclusion + if szResolvedInclude != '' && szResolvedInclude != a:szFilePath + let includePos = [qf.lnum, qf.col] + if !has_key(alreadyInclude, szResolvedInclude) + call extend(listIncludes, [{'pos' : includePos, 'include' : szResolvedInclude}]) + let alreadyInclude[szResolvedInclude] = 1 + endif + endif + endif + endfor + + let g:omni#cpp#includes#CACHE_INCLUDES[a:szFilePath] = listIncludes + + return copy(listIncludes) +endfunc + +" For debug purpose +function! omni#cpp#includes#Display() + let szPathBuffer = omni#cpp#utils#ResolveFilePath(getreg('%')) + call s:DisplayIncludeTree(szPathBuffer, 0) +endfunc + +" For debug purpose +function! s:DisplayIncludeTree(szFilePath, indent, ...) + let includeGuard = {} + if a:0 >0 + let includeGuard = a:1 + endif + let szFilePath = omni#cpp#utils#ResolveFilePath(a:szFilePath) + if has_key(includeGuard, szFilePath) + return + else + let includeGuard[szFilePath] = 1 + endif + + let szIndent = repeat(' ', a:indent) + echo szIndent . a:szFilePath + let incList = omni#cpp#includes#GetList(a:szFilePath) + for inc in incList + call s:DisplayIncludeTree(inc.include, a:indent+1, includeGuard) + endfor +endfunc + + diff --git a/VimFiles/autoload/omni/cpp/items.vim b/VimFiles/autoload/omni/cpp/items.vim new file mode 100755 index 0000000..b943ad4 --- /dev/null +++ b/VimFiles/autoload/omni/cpp/items.vim @@ -0,0 +1,660 @@ +" Description: Omni completion script for cpp files +" Maintainer: Vissale NEANG +" Last Change: 26 sept. 2007 + +" Build the item list of an instruction +" An item is an instruction between a -> or . or ->* or .* +" We can sort an item in different kinds: +" eg: ((MyClass1*)(pObject))->_memberOfClass1.get() ->show() +" | cast | | member | | method | | method | +" @return a list of item +" an item is a dictionnary where keys are: +" tokens = list of token +" kind = itemVariable|itemCast|itemCppCast|itemTemplate|itemFunction|itemUnknown|itemThis|itemScope +function! omni#cpp#items#Get(tokens, ...) + let bGetWordUnderCursor = (a:0>0)? a:1 : 0 + + let result = [] + let itemsDelimiters = ['->', '.', '->*', '.*'] + + let tokens = reverse(omni#cpp#utils#BuildParenthesisGroups(a:tokens)) + + " fsm states: + " 0 = initial state + " TODO: add description of fsm states + let state=(bGetWordUnderCursor)? 1 : 0 + let item = {'tokens' : [], 'kind' : 'itemUnknown'} + let parenGroup=-1 + for token in tokens + if state==0 + if index(itemsDelimiters, token.value)>=0 + let item = {'tokens' : [], 'kind' : 'itemUnknown'} + let state = 1 + elseif token.value=='::' + let state = 9 + let item.kind = 'itemScope' + " Maybe end of tokens + elseif token.kind =='cppOperatorPunctuator' + " If it's a cppOperatorPunctuator and the current token is not + " a itemsDelimiters or '::' we can exit + let state=-1 + break + endif + elseif state==1 + call insert(item.tokens, token) + if token.kind=='cppWord' + " It's an attribute member or a variable + let item.kind = 'itemVariable' + let state = 2 + " Maybe end of tokens + elseif token.value=='this' + let item.kind = 'itemThis' + let state = 2 + " Maybe end of tokens + elseif token.value==')' + let parenGroup = token.group + let state = 3 + elseif token.value==']' + let parenGroup = token.group + let state = 4 + elseif token.kind == 'cppDigit' + let state = -1 + break + endif + elseif state==2 + if index(itemsDelimiters, token.value)>=0 + call insert(result, item) + let item = {'tokens' : [], 'kind' : 'itemUnknown'} + let state = 1 + elseif token.value == '::' + call insert(item.tokens, token) + " We have to get namespace or classscope + let state = 8 + " Maybe end of tokens + else + call insert(result, item) + let state=-1 + break + endif + elseif state==3 + call insert(item.tokens, token) + if token.value=='(' && token.group == parenGroup + let state = 5 + " Maybe end of tokens + endif + elseif state==4 + call insert(item.tokens, token) + if token.value=='[' && token.group == parenGroup + let state = 1 + endif + elseif state==5 + if token.kind=='cppWord' + " It's a function or method + let item.kind = 'itemFunction' + call insert(item.tokens, token) + let state = 2 + " Maybe end of tokens + elseif token.value == '>' + " Maybe a cpp cast or template + let item.kind = 'itemTemplate' + call insert(item.tokens, token) + let parenGroup = token.group + let state = 6 + else + " Perhaps it's a C cast eg: ((void*)(pData)) or a variable eg:(*pData) + let item.kind = omni#cpp#utils#GetCastType(item.tokens) + let state=-1 + call insert(result, item) + break + endif + elseif state==6 + call insert(item.tokens, token) + if token.value == '<' && token.group == parenGroup + " Maybe a cpp cast or template + let state = 7 + endif + elseif state==7 + call insert(item.tokens, token) + if token.kind=='cppKeyword' + " It's a cpp cast + let item.kind = omni#cpp#utils#GetCastType(item.tokens) + let state=-1 + call insert(result, item) + break + else + " Template ? + let state=-1 + call insert(result, item) + break + endif + elseif state==8 + if token.kind=='cppWord' + call insert(item.tokens, token) + let state = 2 + " Maybe end of tokens + else + let state=-1 + call insert(result, item) + break + endif + elseif state==9 + if token.kind == 'cppWord' + call insert(item.tokens, token) + let state = 10 + " Maybe end of tokens + else + let state=-1 + call insert(result, item) + break + endif + elseif state==10 + if token.value == '::' + call insert(item.tokens, token) + let state = 9 + " Maybe end of tokens + else + let state=-1 + call insert(result, item) + break + endif + endif + endfor + + if index([2, 5, 8, 9, 10], state)>=0 + if state==5 + let item.kind = omni#cpp#utils#GetCastType(item.tokens) + endif + call insert(result, item) + endif + + return result +endfunc + +" Resolve type information of items +" @param namespaces: list of namespaces used in the file +" @param szCurrentClassScope: the current class scope, only used for the first +" item to detect if this item is a class member (attribute, method) +" @param items: list of item, can be an empty list @see GetItemsToComplete +function! omni#cpp#items#ResolveItemsTypeInfo(contextStack, items) + " Note: kind = itemVariable|cCast|cppCast|template|function|itemUnknown|this + " For the first item, if it's a variable we try to detect the type of the + " variable with the function searchdecl. If it fails, thanks to the + " current class scope, we try to detect if the variable is an attribute + " member. + " If the kind of the item is a function, we have to first check if the + " function is a method of the class, if it fails we try to get a match in + " the global namespace. After that we get the returned type of the + " function. + " It the kind is a C cast or C++ cast, there is no problem, it's the + " easiest case. We just extract the type of the cast. + + let szCurrentContext = '' + let typeInfo = {} + " Note: We search the decl only for the first item + let bSearchDecl = 1 + for item in a:items + let curItem = item + if index(['itemVariable', 'itemFunction'], curItem.kind)>=0 + " Note: a variable can be : MyNs::MyClass::_var or _var or (*pVar) + " or _var[0][0] + let szSymbol = s:GetSymbol(curItem.tokens) + + " If we have MyNamespace::myVar + " We add MyNamespace in the context stack set szSymbol to myVar + if match(szSymbol, '::\w\+$') >= 0 + let szCurrentContext = substitute(szSymbol, '::\w\+$', '', 'g') + let szSymbol = matchstr(szSymbol, '\w\+$') + endif + let tmpContextStack = a:contextStack + if szCurrentContext != '' + let tmpContextStack = [szCurrentContext] + a:contextStack + endif + + if curItem.kind == 'itemVariable' + let typeInfo = s:GetTypeInfoOfVariable(tmpContextStack, szSymbol, bSearchDecl) + else + let typeInfo = s:GetTypeInfoOfReturnedType(tmpContextStack, szSymbol) + endif + + elseif curItem.kind == 'itemThis' + if len(a:contextStack) + let typeInfo = omni#cpp#utils#CreateTypeInfo(substitute(a:contextStack[0], '^::', '', 'g')) + endif + elseif curItem.kind == 'itemCast' + let typeInfo = omni#cpp#utils#CreateTypeInfo(s:ResolveCCast(curItem.tokens)) + elseif curItem.kind == 'itemCppCast' + let typeInfo = omni#cpp#utils#CreateTypeInfo(s:ResolveCppCast(curItem.tokens)) + elseif curItem.kind == 'itemScope' + let typeInfo = omni#cpp#utils#CreateTypeInfo(substitute(s:TokensToString(curItem.tokens), '\s', '', 'g')) + endif + + if omni#cpp#utils#IsTypeInfoValid(typeInfo) + let szCurrentContext = omni#cpp#utils#GetTypeInfoString(typeInfo) + endif + let bSearchDecl = 0 + endfor + + return typeInfo +endfunc + +" Get symbol name +function! s:GetSymbol(tokens) + let szSymbol = '' + let state = 0 + for token in a:tokens + if state == 0 + if token.value == '::' + let szSymbol .= token.value + let state = 1 + elseif token.kind == 'cppWord' + let szSymbol .= token.value + let state = 2 + " Maybe end of token + endif + elseif state == 1 + if token.kind == 'cppWord' + let szSymbol .= token.value + let state = 2 + " Maybe end of token + else + " Error + break + endif + elseif state == 2 + if token.value == '::' + let szSymbol .= token.value + let state = 1 + else + break + endif + endif + endfor + return szSymbol +endfunc + +" Search a declaration. +" eg: std::map +" can be empty +" Note: The returned type info can be a typedef +" The typedef resolution is done later +" @return +" - a dictionnary where keys are +" - type: the type of value same as type() +" - value: the value +function! s:GetTypeInfoOfVariable(contextStack, szVariable, bSearchDecl) + let result = {} + + if a:bSearchDecl + " Search type of declaration + "let result = s:SearchTypeInfoOfDecl(a:szVariable) + let result = s:SearchDecl(a:szVariable) + endif + + if result=={} + let szFilter = "index(['m', 'v'], v:val.kind[0])>=0" + let tagItem = s:ResolveSymbol(a:contextStack, a:szVariable, szFilter) + if tagItem=={} + return result + endif + + let szCmdWithoutVariable = substitute(omni#cpp#utils#ExtractCmdFromTagItem(tagItem), '\C\<'.a:szVariable.'\>.*', '', 'g') + let tokens = omni#cpp#tokenizer#Tokenize(omni#cpp#utils#GetCodeFromLine(szCmdWithoutVariable)) + let result = omni#cpp#utils#CreateTypeInfo(omni#cpp#utils#ExtractTypeInfoFromTokens(tokens)) + " TODO: Namespace resolution for result + + if result != {} && result.value=='' + " result.value=='' + " eg: + " struct + " { + " }gVariable; + if has_key(tagItem, 'typeref') + " Maybe the variable is a global var of an + " unnamed class, struct or union. + " eg: + " 1) + " struct + " { + " }gVariable; + " In this case we need the tags (the patched version) + " Note: We can have a named type like this: + " 2) + " class A + " { + " }gVariable; + if s:IsUnnamedType(tagItem) + " It's an unnamed type we are in the case 1) + let result = omni#cpp#utils#CreateTypeInfo(tagItem) + else + " It's not an unnamed type we are in the case 2) + + " eg: tagItem.typeref = 'struct:MY_STRUCT::MY_SUBSTRUCT' + let szTypeRef = substitute(tagItem.typeref, '^\w\+:', '', '') + + " eg: szTypeRef = 'MY_STRUCT::MY_SUBSTRUCT' + let result = omni#cpp#utils#CreateTypeInfo(szTypeRef) + endif + endif + endif + endif + return result +endfunc + +" Get the type info string from the returned type of function +function! s:GetTypeInfoOfReturnedType(contextStack, szFunctionName) + let result = {} + + let szFilter = "index(['f', 'p'], v:val.kind[0])>=0" + let tagItem = s:ResolveSymbol(a:contextStack, a:szFunctionName, szFilter) + + if tagItem != {} + let szCmdWithoutVariable = substitute(omni#cpp#utils#ExtractCmdFromTagItem(tagItem), '\C\<'.a:szFunctionName.'\>.*', '', 'g') + let tokens = omni#cpp#tokenizer#Tokenize(omni#cpp#utils#GetCodeFromLine(szCmdWithoutVariable)) + let result = omni#cpp#utils#CreateTypeInfo(omni#cpp#utils#ExtractTypeInfoFromTokens(tokens)) + " TODO: Namespace resolution for result + return result + endif + return result +endfunc + +" Resolve a symbol, return a tagItem +" Gets the first symbol found in the context stack +function! s:ResolveSymbol(contextStack, szSymbol, szTagFilter) + let tagItem = {} + for szCurrentContext in a:contextStack + if szCurrentContext != '::' + let szTagQuery = substitute(szCurrentContext, '^::', '', 'g').'::'.a:szSymbol + else + let szTagQuery = a:szSymbol + endif + + let tagList = omni#common#utils#TagListNoThrow('^'.szTagQuery.'$') + call filter(tagList, a:szTagFilter) + if len(tagList) + let tagItem = tagList[0] + break + endif + endfor + return tagItem +endfunc + +" Return if the tag item represent an unnamed type +function! s:IsUnnamedType(tagItem) + let bResult = 0 + if has_key(a:tagItem, 'typeref') + " Note: Thanks for __anon ! + let bResult = match(a:tagItem.typeref, '\C\<__anon') >= 0 + endif + return bResult +endfunc + +" Search the declaration of a variable and return the type info +function! s:SearchTypeInfoOfDecl(szVariable) + let szReVariable = '\C\<'.a:szVariable.'\>' + + let originalPos = getpos('.') + let origPos = originalPos[1:2] + let curPos = origPos + let stopPos = origPos + + while curPos !=[0,0] + " We go to the start of the current scope + let curPos = searchpairpos('{', '', '}', 'bW', g:omni#cpp#utils#expIgnoreComments) + if curPos != [0,0] + let matchPos = curPos + " Now want to search our variable but we don't want to go in child + " scope + while matchPos != [0,0] + let matchPos = searchpos('{\|'.szReVariable, 'W', stopPos[0]) + if matchPos != [0,0] + " We ignore matches under comment + if omni#cpp#utils#IsCursorInCommentOrString() + continue + endif + + " Getting the current line + let szLine = getline('.') + if match(szLine, szReVariable)>=0 + " We found our variable + " Check if the current instruction is a decl instruction + let tokens = omni#cpp#utils#TokenizeCurrentInstruction() + let szTypeInfo = s:ExtractTypeInfoFromDecl(tokens) + if szTypeInfo != '' + call setpos('.', originalPos) + return omni#cpp#utils#CreateTypeInfo(szTypeInfo) + endif + else + " We found a child scope, we don't want to go in, thus + " we search for the end } of this child scope + let bracketEnd = searchpairpos('{', '', '}', 'nW', g:omni#cpp#utils#expIgnoreComments) + if bracketEnd == [0,0] + break + endif + + if bracketEnd[0] >= stopPos[0] + " The end of the scope is after our cursor we stop + " the search + break + else + " We move the cursor and continue to search our + " variable + call setpos('.', [0, bracketEnd[0], bracketEnd[1], 0]) + endif + endif + endif + endwhile + + " Backing to the start of the scope + call setpos('.', [0,curPos[0], curPos[1], 0]) + let stopPos = curPos + endif + endwhile + + let result = {} + if s:LocalSearchDecl(a:szVariable)==0 && !omni#cpp#utils#IsCursorInCommentOrString() + let tokens = omni#cpp#utils#TokenizeCurrentInstruction() + let szTypeInfo = s:ExtractTypeInfoFromDecl(tokens) + if szTypeInfo != '' + let result = omni#cpp#utils#CreateTypeInfo(szTypeInfo) + endif + endif + + call setpos('.', originalPos) + + return result +endfunc + +" Search a declaration +" @return +" - tokens of the current instruction if success +" - empty list if failure +function! s:SearchDecl(szVariable) + let result = {} + let originalPos = getpos('.') + let searchResult = s:LocalSearchDecl(a:szVariable) + if searchResult==0 + " searchdecl() may detect a decl if the variable is in a conditional + " instruction (if, elseif, while etc...) + " We have to check if the detected decl is really a decl instruction + let tokens = omni#cpp#utils#TokenizeCurrentInstruction() + + for token in tokens + " Simple test + if index(['if', 'elseif', 'while', 'for', 'switch'], token.value)>=0 + " Invalid declaration instruction + call setpos('.', originalPos) + return result + endif + endfor + + let szTypeInfo = s:ExtractTypeInfoFromDecl(tokens) + if szTypeInfo != '' + let result = omni#cpp#utils#CreateTypeInfo(szTypeInfo) + endif + endif + call setpos('.', originalPos) + return result +endfunc + +" Extract the type info string from an instruction. +" We use a small parser to extract the type +" We parse the code according to a C++ BNF from: http://www.nongnu.org/hcb/#basic.link +" @param tokens: token list of the current instruction +function! s:ExtractTypeInfoFromDecl(tokens) + return omni#cpp#utils#ExtractTypeInfoFromTokens(a:tokens) +endfunc + +" Convert tokens to string +function! s:TokensToString(tokens) + let result = '' + for token in a:tokens + let result = result . token.value . ' ' + endfor + return result[:-2] +endfunc + +" Resolve a cast. +" Resolve a C++ cast +" @param list of token. tokens must be a list that represents +" a cast expression (C++ cast) the function does not control +" if it's a cast or not +" eg: static_cast(something) +" @return type info string +function! s:ResolveCppCast(tokens) + return omni#cpp#utils#ExtractTypeInfoFromTokens(s:ResolveCast(a:tokens, '<', '>')) +endfunc + +" Resolve a cast. +" Resolve a C cast +" @param list of token. tokens must be a list that represents +" a cast expression (C cast) the function does not control +" if it's a cast or not +" eg: (MyClass*)something +" @return type info string +function! s:ResolveCCast(tokens) + return omni#cpp#utils#ExtractTypeInfoFromTokens(s:ResolveCast(a:tokens, '(', ')')) +endfunc + +" Resolve a cast. +" Resolve a C cast +" @param list of token. tokens must be a list that represents +" a cast expression (C cast) the function does not control +" if it's a cast or not +" eg: (MyClass*)something +" @return type tokens +function! s:ResolveCast(tokens, startChar, endChar) + let tokens = omni#cpp#utils#BuildParenthesisGroups(a:tokens) + + " We remove useless parenthesis eg: (((MyClass))) + let tokens = omni#cpp#utils#SimplifyParenthesis(tokens) + + let countItem=0 + let startIndex = -1 + let endIndex = -1 + let i = 0 + for token in tokens + if startIndex==-1 + if token.value==a:startChar + let countItem += 1 + let startIndex = i + endif + else + if token.value==a:startChar + let countItem += 1 + elseif token.value==a:endChar + let countItem -= 1 + endif + + if countItem==0 + let endIndex = i + break + endif + endif + let i+=1 + endfor + + return tokens[startIndex+1 : endIndex-1] +endfunc + +" Replacement for build-in function 'searchdecl' +" It does not require that the upper-level bracket is in the first column. +" Otherwise it should be equal to 'searchdecl(name, 0, 1)' +" @param name: name of variable to find declaration for +function! s:LocalSearchDecl(name) + + if g:OmniCpp_LocalSearchDecl == 0 + let bUserIgnoreCase = &ignorecase + + " Forcing the noignorecase option + " avoid bug when, for example, if we have a declaration like this : "A a;" + set noignorecase + + let result = searchdecl(a:name, 0, 1) + + " Restoring user's setting + let &ignorecase = bUserIgnoreCase + + return result + endif + + let lastpos = getpos('.') + let winview = winsaveview() + let lastfoldenable = &foldenable + let &foldenable = 0 + + " We add \C (noignorecase) to + " avoid bug when, for example, if we have a declaration like this : "A a;" + let varname = "\\C\\<" . a:name . "\\>" + + " Go to first blank line before begin of highest scope + normal 99[{ + let scopepos = getpos('.') + while (line('.') > 1) && (len(split(getline('.'))) > 0) + call cursor(line('.')-1, 0) + endwhile + + let declpos = [ 0, 0, 0, 0 ] + while search(varname, '', scopepos[1]) > 0 + " Check if we are a string or a comment + if omni#cpp#utils#IsCursorInCommentOrString() + continue + endif + + " Remember match + let declpos = getpos('.') + endwhile + if declpos[1] != 0 + " We found a match + call winrestview(winview) + call setpos('.', declpos) + let &foldenable = lastfoldenable + return 0 + endif + + while search(varname, '', lastpos[1]) > 0 + " Check if current scope is ending before variable + let old_cur = getpos('.') + normal ]} + let new_cur = getpos('.') + call setpos('.', old_cur) + if (new_cur[1] < lastpos[1]) || ((new_cur[1] == lastpos[1]) && (new_cur[2] < lastpos[2])) + continue + endif + + " Check if we are a string or a comment + if omni#cpp#utils#IsCursorInCommentOrString() + continue + endif + + " We found match + call winrestview(winview) + call setpos('.', old_cur) + let &foldenable = lastfoldenable + return 0 + endwhile + + " No match found. + call winrestview(winview) + let &foldenable = lastfoldenable + return 1 +endfunc diff --git a/VimFiles/autoload/omni/cpp/maycomplete.vim b/VimFiles/autoload/omni/cpp/maycomplete.vim new file mode 100755 index 0000000..610526b --- /dev/null +++ b/VimFiles/autoload/omni/cpp/maycomplete.vim @@ -0,0 +1,82 @@ +" Description: Omni completion script for cpp files +" Maintainer: Vissale NEANG +" Last Change: 26 sept. 2007 + +" Check if we can use omni completion in the current buffer +function! s:CanUseOmnicompletion() + " For C and C++ files and only if the omnifunc is omni#cpp#complete#Main + return (index(['c', 'cpp'], &filetype)>=0 && &omnifunc == 'omni#cpp#complete#Main' && !omni#cpp#utils#IsCursorInCommentOrString()) +endfunc + +" Return the mapping of omni completion +function! omni#cpp#maycomplete#Complete() + let szOmniMapping = "\\" + + " 0 = don't select first item + " 1 = select first item (inserting it to the text, default vim behaviour) + " 2 = select first item (without inserting it to the text) + if g:OmniCpp_SelectFirstItem == 0 + " We have to force the menuone option to avoid confusion when there is + " only one popup item + set completeopt-=menu + set completeopt+=menuone + let szOmniMapping .= "\" + elseif g:OmniCpp_SelectFirstItem == 2 + " We have to force the menuone option to avoid confusion when there is + " only one popup item + set completeopt-=menu + set completeopt+=menuone + let szOmniMapping .= "\" + let szOmniMapping .= "\=pumvisible() ? \"\\\" : \"\"\" + endif + return szOmniMapping +endfunc + +" May complete function for dot +function! omni#cpp#maycomplete#Dot() + if s:CanUseOmnicompletion() && g:OmniCpp_MayCompleteDot + let g:omni#cpp#items#data = omni#cpp#items#Get(omni#cpp#utils#TokenizeCurrentInstruction('.')) + if len(g:omni#cpp#items#data) + let s:bMayComplete = 1 + return '.' . omni#cpp#maycomplete#Complete() + endif + endif + return '.' +endfunc +" May complete function for arrow +function! omni#cpp#maycomplete#Arrow() + if s:CanUseOmnicompletion() && g:OmniCpp_MayCompleteArrow + let index = col('.') - 2 + if index >= 0 + let char = getline('.')[index] + if char == '-' + let g:omni#cpp#items#data = omni#cpp#items#Get(omni#cpp#utils#TokenizeCurrentInstruction('>')) + if len(g:omni#cpp#items#data) + let s:bMayComplete = 1 + return '>' . omni#cpp#maycomplete#Complete() + endif + endif + endif + endif + return '>' +endfunc + +" May complete function for double points +function! omni#cpp#maycomplete#Scope() + if s:CanUseOmnicompletion() && g:OmniCpp_MayCompleteScope + let index = col('.') - 2 + if index >= 0 + let char = getline('.')[index] + if char == ':' + let g:omni#cpp#items#data = omni#cpp#items#Get(omni#cpp#utils#TokenizeCurrentInstruction(':')) + if len(g:omni#cpp#items#data) + if len(g:omni#cpp#items#data[-1].tokens) && g:omni#cpp#items#data[-1].tokens[-1].value != '::' + let s:bMayComplete = 1 + return ':' . omni#cpp#maycomplete#Complete() + endif + endif + endif + endif + endif + return ':' +endfunc diff --git a/VimFiles/autoload/omni/cpp/namespaces.vim b/VimFiles/autoload/omni/cpp/namespaces.vim new file mode 100755 index 0000000..386b3f9 --- /dev/null +++ b/VimFiles/autoload/omni/cpp/namespaces.vim @@ -0,0 +1,838 @@ +" Description: Omni completion script for cpp files +" Maintainer: Vissale NEANG +" Last Change: 26 sept. 2007 + +let g:omni#cpp#namespaces#CacheResolve = {} +let g:omni#cpp#namespaces#CacheUsing = {} +" TODO: For the next release +"let g:omni#cpp#namespaces#CacheAlias = {} + +" Get the using namespace list from a line +function! s:GetNamespaceAliasListFromLine(szLine) + let result = {} + let tokens = omni#cpp#tokenizer#Tokenize(a:szLine) + let szAlias = '' + let szNamespace = '' + let state = 0 + for token in tokens + if state==0 + let szAlias = '' + let szNamespace = '' + if token.value == '/*' + let state = 1 + elseif token.value == '//' + " It's a comment + let state = -1 + break + elseif token.value == 'namespace' + let state = 2 + endif + elseif state==1 + if token.value == '*/' + let state=0 + endif + elseif state==2 + if token.kind == 'cppWord' + let szAlias .= token.value + let state = 3 + else + let state = -1 + break + endif + elseif state == 3 + if token.value == '=' + let state = 4 + else + let state = -1 + break + endif + elseif state == 4 + if token.value == '::' + let szNamespace .= token.value + let state = 5 + elseif token.kind == 'cppWord' + let szNamespace .= token.value + let state = 6 + " Maybe end of tokens + endif + elseif state==5 + if token.kind == 'cppWord' + let szNamespace .= token.value + let state = 6 + " Maybe end of tokens + else + " Error, we can't have 'namespace ALIAS = Something::' + let state = -1 + break + endif + elseif state==6 + if token.value == '::' + let szNamespace .= token.value + let state = 5 + else + call extend(result, {szAlias : szNamespace}) + let state = 0 + endif + endif + endfor + + if state == 6 + call extend(result, {szAlias : szNamespace}) + endif + + return result +endfunc + +" Get the using namespace list from a line +function! s:GetNamespaceListFromLine(szLine) + let result = [] + let tokens = omni#cpp#tokenizer#Tokenize(a:szLine) + let szNamespace = '' + let state = 0 + for token in tokens + if state==0 + let szNamespace = '' + if token.value == '/*' + let state = 1 + elseif token.value == '//' + " It's a comment + let state = -1 + break + elseif token.value == 'using' + let state = 2 + endif + elseif state==1 + if token.value == '*/' + let state=0 + endif + elseif state==2 + if token.value == 'namespace' + let state = 3 + else + " Error, 'using' must be followed by 'namespace' + let state = -1 + break + endif + elseif state==3 + if token.value == '::' + let szNamespace .= token.value + let state = 4 + elseif token.kind == 'cppWord' + let szNamespace .= token.value + let state = 5 + " Maybe end of tokens + endif + elseif state==4 + if token.kind == 'cppWord' + let szNamespace .= token.value + let state = 5 + " Maybe end of tokens + else + " Error, we can't have 'using namespace Something::' + let state = -1 + break + endif + elseif state==5 + if token.value == '::' + let szNamespace .= token.value + let state = 4 + else + call extend(result, [szNamespace]) + let state = 0 + endif + endif + endfor + + if state == 5 + call extend(result, [szNamespace]) + endif + + return result +endfunc + +" Get the namespace list from a namespace map +function! s:GetUsingNamespaceListFromMap(namespaceMap, ...) + let stopLine = 0 + if a:0>0 + let stopLine = a:1 + endif + + let result = [] + let keys = sort(keys(a:namespaceMap), 'omni#common#utils#CompareNumber') + for i in keys + if stopLine != 0 && i > stopLine + break + endif + call extend(result, a:namespaceMap[i]) + endfor + return result +endfunc + +" Get global using namespace list from the current buffer +function! omni#cpp#namespaces#GetListFromCurrentBuffer(...) + let namespaceMap = s:GetAllUsingNamespaceMapFromCurrentBuffer() + let result = [] + if namespaceMap != {} + let result = s:GetUsingNamespaceListFromMap(namespaceMap, (a:0 > 0)? a:1 : line('.')) + endif + return result +endfunc + +" Get global using namespace map from the current buffer and include files recursively +function! s:GetAllUsingNamespaceMapFromCurrentBuffer(...) + let includeGuard = (a:0>0)? a:1 : {} + + let szBufferName = getreg("%") + let szFilePath = omni#cpp#utils#ResolveFilePath(szBufferName) + let szFilePath = (szFilePath=='')? szBufferName : szFilePath + + let namespaceMap = {} + if has_key(includeGuard, szFilePath) + return namespaceMap + else + let includeGuard[szFilePath] = 1 + endif + + let namespaceMap = omni#cpp#namespaces#GetMapFromCurrentBuffer() + + if g:OmniCpp_NamespaceSearch != 2 + " We don't search included files if OmniCpp_NamespaceSearch != 2 + return namespaceMap + endif + + for inc in omni#cpp#includes#GetList() + let lnum = inc.pos[0] + let tmpMap = s:GetAllUsingNamespaceMapFromFile(inc.include, includeGuard) + if tmpMap != {} + if has_key(namespaceMap, lnum) + call extend(namespaceMap[lnum], s:GetUsingNamespaceListFromMap(tmpMap)) + else + let namespaceMap[lnum] = s:GetUsingNamespaceListFromMap(tmpMap) + endif + endif + endfor + + return namespaceMap +endfunc + +" Get global using namespace map from a file and include files recursively +function! s:GetAllUsingNamespaceMapFromFile(szFilePath, ...) + let includeGuard = {} + if a:0 >0 + let includeGuard = a:1 + endif + + let szFilePath = omni#cpp#utils#ResolveFilePath(a:szFilePath) + let szFilePath = (szFilePath=='')? a:szFilePath : szFilePath + + let namespaceMap = {} + if has_key(includeGuard, szFilePath) + return namespaceMap + else + let includeGuard[szFilePath] = 1 + endif + + " If g:OmniCpp_NamespaceSearch == 1 (search namespaces only in the current + " buffer) we don't use cache for the current buffer + let namespaceMap = omni#cpp#namespaces#GetMapFromBuffer(szFilePath, g:OmniCpp_NamespaceSearch==1) + + if g:OmniCpp_NamespaceSearch != 2 + " We don't search included files if OmniCpp_NamespaceSearch != 2 + return namespaceMap + endif + + for inc in omni#cpp#includes#GetList(szFilePath) + let lnum = inc.pos[0] + let tmpMap = s:GetAllUsingNamespaceMapFromFile(inc.include, includeGuard) + if tmpMap != {} + if has_key(namespaceMap, lnum) + call extend(namespaceMap[lnum], s:GetUsingNamespaceListFromMap(tmpMap)) + else + let namespaceMap[lnum] = s:GetUsingNamespaceListFromMap(tmpMap) + endif + endif + endfor + + return namespaceMap +endfunc + +" Get global using namespace map from a the current buffer +function! omni#cpp#namespaces#GetMapFromCurrentBuffer() + let namespaceMap = {} + let originalPos = getpos('.') + + call setpos('.', [0, 1, 1, 0]) + let curPos = [1,1] + while curPos != [0,0] + let curPos = searchpos('\C^using\s\+namespace', 'W') + if curPos != [0,0] + let szLine = getline('.') + let startPos = curPos[1] + let endPos = match(szLine, ';', startPos-1) + if endPos!=-1 + " We get the namespace list from the line + let namespaceMap[curPos[0]] = s:GetNamespaceListFromLine(szLine) + endif + endif + endwhile + + call setpos('.', originalPos) + return namespaceMap +endfunc + +" Get global using namespace map from a file +function! omni#cpp#namespaces#GetMapFromBuffer(szFilePath, ...) + let bUpdate = 0 + if a:0 > 0 + let bUpdate = a:1 + endif + + let szFilePath = omni#cpp#utils#ResolveFilePath(a:szFilePath) + let szFilePath = (szFilePath=='')? a:szFilePath : szFilePath + + if !bUpdate && has_key(g:omni#cpp#namespaces#CacheUsing, szFilePath) + return copy(g:omni#cpp#namespaces#CacheUsing[szFilePath]) + endif + + let namespaceMap = {} + " The file exists, we get the global namespaces in this file + let szFixedPath = escape(szFilePath, g:omni#cpp#utils#szEscapedCharacters) + execute 'silent! lvimgrep /\C^using\s\+namespace/gj '.szFixedPath + + " key = line number + " value = list of namespaces + let listQuickFix = getloclist(0) + for qf in listQuickFix + let szLine = qf.text + let startPos = qf.col + let endPos = match(szLine, ';', startPos-1) + if endPos!=-1 + " We get the namespace list from the line + let namespaceMap[qf.lnum] = s:GetNamespaceListFromLine(szLine) + endif + endfor + + if szFixedPath != '' + let g:omni#cpp#namespaces#CacheUsing[szFixedPath] = namespaceMap + endif + + return copy(namespaceMap) +endfunc + +" Get the stop position when searching for local variables +function! s:GetStopPositionForLocalSearch() + " Stop position when searching a local variable + let originalPos = getpos('.') + let origPos = originalPos[1:2] + let stopPosition = origPos + let curPos = origPos + while curPos !=[0,0] + let stopPosition = curPos + let curPos = searchpairpos('{', '', '}', 'bW', g:omni#cpp#utils#expIgnoreComments) + endwhile + call setpos('.', originalPos) + + return stopPosition +endfunc + +" Get namespaces alias used at the cursor postion in a vim buffer +" Note: The result depends on the current cursor position +" @return +" - Map of namespace alias +function! s:GetNamespaceAliasMap() + " We store the cursor position because searchpairpos() moves the cursor + let result = {} + let originalPos = getpos('.') + let origPos = originalPos[1:2] + + let stopPos = s:GetStopPositionForLocalSearch() + let stopLine = stopPos[0] + let curPos = origPos + let lastLine = 0 + let nextStopLine = origPos[0] + let szReAlias = '\Cnamespace\s\+\w\+\s\+=' + while curPos !=[0,0] + let curPos = searchpos('}\|\('. szReAlias .'\)', 'bW',stopLine) + if curPos!=[0,0] && curPos[0]!=lastLine + let lastLine = curPos[0] + + let szLine = getline('.') + if origPos[0] == curPos[0] + " We get the line until cursor position + let szLine = szLine[:origPos[1]] + endif + + let szLine = omni#cpp#utils#GetCodeFromLine(szLine) + if match(szLine, szReAlias)<0 + " We found a '}' + let curPos = searchpairpos('{', '', '}', 'bW', g:omni#cpp#utils#expIgnoreComments) + else + " We get the namespace alias from the line + call extend(result, s:GetNamespaceAliasListFromLine(szLine)) + let nextStopLine = curPos[0] + endif + endif + endwhile + + " Setting the cursor to the original position + call setpos('.', originalPos) + + call s:ResolveAliasKeys(result) + return result +endfunc + +" Resolve an alias +" eg: namespace IAmAnAlias1 = Ns1 +" eg: namespace IAmAnAlias2 = IAmAnAlias1::Ns2 +" => IAmAnAlias2 = Ns1::Ns2 +function! s:ResolveAliasKey(mapNamespaceAlias, szAlias) + let szResult = a:mapNamespaceAlias[a:szAlias] + " ::Ns1::Ns2::Ns3 => ['Ns1', 'Ns2', 'Ns3'] + let listNamespace = split(szResult, '::') + if len(listNamespace) + " szBeginPart = 'Ns1' + let szBeginPart = remove(listNamespace, 0) + + " Is 'Ns1' an alias ? + if has_key(a:mapNamespaceAlias, szBeginPart) && szBeginPart != a:szAlias + " Resolving alias 'Ns1' + " eg: Ns1 = NsResolved + let szResult = s:ResolveAliasKey(a:mapNamespaceAlias, szBeginPart) + " szEndPart = 'Ns2::Ns3' + let szEndPart = join(listNamespace, '::') + if szEndPart != '' + " Concatenation => szResult = 'NsResolved::Ns2::Ns3' + let szResult .= '::' . szEndPart + endif + endif + endif + return szResult +endfunc + +" Resolve all keys in the namespace alias map +function! s:ResolveAliasKeys(mapNamespaceAlias) + let mapNamespaceAlias = a:mapNamespaceAlias + call map(mapNamespaceAlias, 's:ResolveAliasKey(mapNamespaceAlias, v:key)') +endfunc + +" Resolve namespace alias +function! omni#cpp#namespaces#ResolveAlias(mapNamespaceAlias, szNamespace) + let szResult = a:szNamespace + " ::Ns1::Ns2::Ns3 => ['Ns1', 'Ns2', 'Ns3'] + let listNamespace = split(a:szNamespace, '::') + if len(listNamespace) + " szBeginPart = 'Ns1' + let szBeginPart = remove(listNamespace, 0) + + " Is 'Ns1' an alias ? + if has_key(a:mapNamespaceAlias, szBeginPart) + " Resolving alias 'Ns1' + " eg: Ns1 = NsResolved + let szResult = a:mapNamespaceAlias[szBeginPart] + " szEndPart = 'Ns2::Ns3' + let szEndPart = join(listNamespace, '::') + if szEndPart != '' + " Concatenation => szResult = 'NsResolved::Ns2::Ns3' + let szResult .= '::' . szEndPart + endif + + " If a:szNamespace starts with '::' we add '::' to the beginning + " of the result + if match(a:szNamespace, '^::')>=0 + let szResult = omni#cpp#utils#SimplifyScope('::' . szResult) + endif + endif + endif + return szResult +endfunc + +" Resolve namespace alias +function! s:ResolveAliasInNamespaceList(mapNamespaceAlias, listNamespaces) + call map(a:listNamespaces, 'omni#cpp#namespaces#ResolveAlias(a:mapNamespaceAlias, v:val)') +endfunc + +" Get namespaces used at the cursor postion in a vim buffer +" Note: The result depends on the current cursor position +" @return +" - List of namespace used in the reverse order +function! omni#cpp#namespaces#GetUsingNamespaces() + " We have to get local using namespace declarations + " We need the current cursor position and the position of the start of the + " current scope + + " We store the cursor position because searchpairpos() moves the cursor + let result = [] + let originalPos = getpos('.') + let origPos = originalPos[1:2] + + let stopPos = s:GetStopPositionForLocalSearch() + + let stopLine = stopPos[0] + let curPos = origPos + let lastLine = 0 + let nextStopLine = origPos[0] + while curPos !=[0,0] + let curPos = searchpos('\C}\|\(using\s\+namespace\)', 'bW',stopLine) + if curPos!=[0,0] && curPos[0]!=lastLine + let lastLine = curPos[0] + + let szLine = getline('.') + if origPos[0] == curPos[0] + " We get the line until cursor position + let szLine = szLine[:origPos[1]] + endif + + let szLine = omni#cpp#utils#GetCodeFromLine(szLine) + if match(szLine, '\Cusing\s\+namespace')<0 + " We found a '}' + let curPos = searchpairpos('{', '', '}', 'bW', g:omni#cpp#utils#expIgnoreComments) + else + " We get the namespace list from the line + let result = s:GetNamespaceListFromLine(szLine) + result + let nextStopLine = curPos[0] + endif + endif + endwhile + + " Setting the cursor to the original position + call setpos('.', originalPos) + + " 2) Now we can get all global using namespace declaration from the + " beginning of the file to nextStopLine + let result = omni#cpp#namespaces#GetListFromCurrentBuffer(nextStopLine) + result + + " Resolving alias in the namespace list + " TODO: For the next release + "let g:omni#cpp#namespaces#CacheAlias= s:GetNamespaceAliasMap() + "call s:ResolveAliasInNamespaceList(g:omni#cpp#namespaces#CacheAlias, result) + + return ['::'] + result +endfunc + +" Resolve a using namespace regarding the current context +" For each namespace used: +" - We get all possible contexts where the namespace +" can be define +" - We do a comparison test of each parent contexts with the current +" context list +" - If one and only one parent context is present in the +" current context list we add the namespace in the current +" context +" - If there is more than one of parent contexts in the +" current context the namespace is ambiguous +" @return +" - result item +" - kind = 0|1 +" - 0 = unresolved or error +" - 1 = resolved +" - value = resolved namespace +function! s:ResolveNamespace(namespace, mapCurrentContexts) + let result = {'kind':0, 'value': ''} + + " If the namespace is already resolved we add it in the list of + " current contexts + if match(a:namespace, '^::')>=0 + let result.kind = 1 + let result.value = a:namespace + return result + elseif match(a:namespace, '\w\+::\w\+')>=0 + let mapCurrentContextsTmp = copy(a:mapCurrentContexts) + let resolvedItem = {} + for nsTmp in split(a:namespace, '::') + let resolvedItem = s:ResolveNamespace(nsTmp, mapCurrentContextsTmp) + if resolvedItem.kind + " Note: We don't extend the map + let mapCurrentContextsTmp = {resolvedItem.value : 1} + else + break + endif + endfor + if resolvedItem!={} && resolvedItem.kind + let result.kind = 1 + let result.value = resolvedItem.value + endif + return result + endif + + " We get all possible parent contexts of this namespace + let listTagsOfNamespace = [] + if has_key(g:omni#cpp#namespaces#CacheResolve, a:namespace) + let listTagsOfNamespace = g:omni#cpp#namespaces#CacheResolve[a:namespace] + else + let listTagsOfNamespace = omni#common#utils#TagList('^'.a:namespace.'$') + let g:omni#cpp#namespaces#CacheResolve[a:namespace] = listTagsOfNamespace + endif + + if len(listTagsOfNamespace)==0 + return result + endif + call filter(listTagsOfNamespace, 'v:val.kind[0]=="n"') + + " We extract parent context from tags + " We use a map to avoid multiple entries + let mapContext = {} + for tagItem in listTagsOfNamespace + let szParentContext = omni#cpp#utils#ExtractScope(tagItem) + let mapContext[szParentContext] = 1 + endfor + let listParentContext = keys(mapContext) + + " Now for each parent context we test if the context is in the current + " contexts list + let listResolvedNamespace = [] + for szParentContext in listParentContext + if has_key(a:mapCurrentContexts, szParentContext) + call extend(listResolvedNamespace, [omni#cpp#utils#SimplifyScope(szParentContext.'::'.a:namespace)]) + endif + endfor + + " Now we know if the namespace is ambiguous or not + let len = len(listResolvedNamespace) + if len==1 + " Namespace resolved + let result.kind = 1 + let result.value = listResolvedNamespace[0] + elseif len > 1 + " Ambiguous namespace, possible matches are in listResolvedNamespace + else + " Other cases + endif + return result +endfunc + +" Resolve namespaces +"@return +" - List of resolved namespaces +function! omni#cpp#namespaces#ResolveAll(namespacesUsed) + + " We add the default context '::' + let contextOrder = 0 + let mapCurrentContexts = {} + + " For each namespace used: + " - We get all possible contexts where the namespace + " can be define + " - We do a comparison test of each parent contexts with the current + " context list + " - If one and only one parent context is present in the + " current context list we add the namespace in the current + " context + " - If there is more than one of parent contexts in the + " current context the namespace is ambiguous + for ns in a:namespacesUsed + let resolvedItem = s:ResolveNamespace(ns, mapCurrentContexts) + if resolvedItem.kind + let contextOrder+=1 + let mapCurrentContexts[resolvedItem.value] = contextOrder + endif + endfor + + " Build the list of current contexts from the map, we have to keep the + " order + let mapReorder = {} + for key in keys(mapCurrentContexts) + let mapReorder[ mapCurrentContexts[key] ] = key + endfor + let result = [] + for key in sort(keys(mapReorder)) + call extend(result, [mapReorder[key]]) + endfor + return result +endfunc + +" Build the context stack +function! s:BuildContextStack(namespaces, szCurrentScope) + let result = copy(a:namespaces) + if a:szCurrentScope != '::' + let tagItem = omni#cpp#utils#GetResolvedTagItem(a:namespaces, omni#cpp#utils#CreateTypeInfo(a:szCurrentScope)) + if has_key(tagItem, 'inherits') + let listBaseClass = omni#cpp#utils#GetClassInheritanceList(a:namespaces, omni#cpp#utils#CreateTypeInfo(a:szCurrentScope)) + let result = listBaseClass + result + elseif has_key(tagItem, 'kind') && index(['c', 's', 'u', 'n'], tagItem.kind[0])>=0 + call insert(result, omni#cpp#utils#ExtractTypeInfoFromTag(tagItem)) + endif + endif + return result +endfunc + +" Returns the class scope at the current position of the cursor +" @return a string that represents the class scope +" eg: ::NameSpace1::Class1 +" The returned string always starts with '::' +" Note: In term of performance it's the weak point of the script +function! s:GetClassScopeAtCursor() + " We store the cursor position because searchpairpos() moves the cursor + let originalPos = getpos('.') + let endPos = originalPos[1:2] + let listCode = [] + let result = {'namespaces': [], 'scope': ''} + + while endPos!=[0,0] + let endPos = searchpairpos('{', '', '}', 'bW', g:omni#cpp#utils#expIgnoreComments) + let szReStartPos = '[;{}]\|\%^' + let startPos = searchpairpos(szReStartPos, '', '{', 'bWn', g:omni#cpp#utils#expIgnoreComments) + + " If the file starts with a comment so the startPos can be [0,0] + " we change it to [1,1] + if startPos==[0,0] + let startPos = [1,1] + endif + + " Get lines backward from cursor position to last ; or { or } + " or when we are at the beginning of the file. + " We store lines in listCode + if endPos!=[0,0] + " We remove the last character which is a '{' + " We also remove starting { or } or ; if exits + let szCodeWithoutComments = substitute(omni#cpp#utils#GetCode(startPos, endPos)[:-2], '^[;{}]', '', 'g') + call insert(listCode, {'startLine' : startPos[0], 'code' : szCodeWithoutComments}) + endif + endwhile + " Setting the cursor to the original position + call setpos('.', originalPos) + + let listClassScope = [] + let bResolved = 0 + let startLine = 0 + " Now we can check in the list of code if there is a function + for code in listCode + " We get the name of the namespace, class, struct or union + " and we store it in listClassScope + let tokens = omni#cpp#tokenizer#Tokenize(code.code) + let bContinue=0 + let bAddNamespace = 0 + let state=0 + for token in tokens + if state==0 + if index(['namespace', 'class', 'struct', 'union'], token.value)>=0 + if token.value == 'namespace' + let bAddNamespace = 1 + endif + let state= 1 + " Maybe end of tokens + endif + elseif state==1 + if token.kind == 'cppWord' + " eg: namespace MyNs { class MyCl {}; } + " => listClassScope = [MyNs, MyCl] + call extend( listClassScope , [token.value] ) + + " Add the namespace in result + if bAddNamespace + call extend(result.namespaces, [token.value]) + let bAddNamespace = 0 + endif + + let bContinue=1 + break + endif + endif + endfor + if bContinue==1 + continue + endif + + " Simple test to check if we have a chance to find a + " class method + let aPos = matchend(code.code, '::\s*\~*\s*\w\+\s*(') + if aPos ==-1 + continue + endif + + let startLine = code.startLine + let listTmp = [] + " eg: 'void MyNamespace::MyClass::foo(' + " => tokens = ['MyClass', '::', 'MyNamespace', 'void'] + let tokens = reverse(omni#cpp#tokenizer#Tokenize(code.code[:aPos-1])[:-4]) + let state = 0 + " Reading tokens backward + for token in tokens + if state==0 + if token.kind=='cppWord' + call insert(listTmp, token.value) + let state=1 + endif + elseif state==1 + if token.value=='::' + let state=2 + else + break + endif + elseif state==2 + if token.kind=='cppWord' + call insert(listTmp, token.value) + let state=1 + else + break + endif + endif + endfor + + if len(listTmp) + if len(listClassScope) + let bResolved = 1 + " Merging class scopes + " eg: current class scope = 'MyNs::MyCl1' + " method class scope = 'MyCl1::MyCl2' + " If we add the method class scope to current class scope + " we'll have MyNs::MyCl1::MyCl1::MyCl2 => it's wrong + " we want MyNs::MyCl1::MyCl2 + let index = 0 + for methodClassScope in listTmp + if methodClassScope==listClassScope[-1] + let listTmp = listTmp[index+1:] + break + else + let index+=1 + endif + endfor + endif + call extend(listClassScope, listTmp) + break + endif + endfor + + let szClassScope = '::' + if len(listClassScope) + if bResolved + let szClassScope .= join(listClassScope, '::') + else + let szClassScope = join(listClassScope, '::') + + " The class scope is not resolved, we have to check using + " namespace declarations and search the class scope in each + " namespace + if startLine != 0 + let namespaces = ['::'] + omni#cpp#namespaces#GetListFromCurrentBuffer(startLine) + let namespaces = omni#cpp#namespaces#ResolveAll(namespaces) + let tagItem = omni#cpp#utils#GetResolvedTagItem(namespaces, omni#cpp#utils#CreateTypeInfo(szClassScope)) + if tagItem != {} + let szClassScope = omni#cpp#utils#ExtractTypeInfoFromTag(tagItem) + endif + endif + endif + endif + + let result.scope = szClassScope + return result +endfunc + +" Get all contexts at the cursor position +function! omni#cpp#namespaces#GetContexts() + " Get the current class scope at the cursor, the result depends on the current cursor position + let scopeItem = s:GetClassScopeAtCursor() + let listUsingNamespace = copy(g:OmniCpp_DefaultNamespaces) + call extend(listUsingNamespace, scopeItem.namespaces) + if g:OmniCpp_NamespaceSearch && &filetype != 'c' + " Get namespaces used in the file until the cursor position + let listUsingNamespace = omni#cpp#namespaces#GetUsingNamespaces() + listUsingNamespace + " Resolving namespaces, removing ambiguous namespaces + let namespaces = omni#cpp#namespaces#ResolveAll(listUsingNamespace) + else + let namespaces = ['::'] + listUsingNamespace + endif + call reverse(namespaces) + + " Building context stack from namespaces and the current class scope + return s:BuildContextStack(namespaces, scopeItem.scope) +endfunc diff --git a/VimFiles/autoload/omni/cpp/settings.vim b/VimFiles/autoload/omni/cpp/settings.vim new file mode 100755 index 0000000..6683d3a --- /dev/null +++ b/VimFiles/autoload/omni/cpp/settings.vim @@ -0,0 +1,96 @@ +" Description: Omni completion script for cpp files +" Maintainer: Vissale NEANG +" Last Change: 26 sept. 2007 + +function! omni#cpp#settings#Init() + " Global scope search on/off + " 0 = disabled + " 1 = enabled + if !exists('g:OmniCpp_GlobalScopeSearch') + let g:OmniCpp_GlobalScopeSearch = 1 + endif + + " Sets the namespace search method + " 0 = disabled + " 1 = search namespaces in the current file + " 2 = search namespaces in the current file and included files + if !exists('g:OmniCpp_NamespaceSearch') + let g:OmniCpp_NamespaceSearch = 1 + endif + + " Set the class scope completion mode + " 0 = auto + " 1 = show all members (static, public, protected and private) + if !exists('g:OmniCpp_DisplayMode') + let g:OmniCpp_DisplayMode = 0 + endif + + " Set if the scope is displayed in the abbr column of the popup + " 0 = no + " 1 = yes + if !exists('g:OmniCpp_ShowScopeInAbbr') + let g:OmniCpp_ShowScopeInAbbr = 0 + endif + + " Set if the function prototype is displayed in the abbr column of the popup + " 0 = no + " 1 = yes + if !exists('g:OmniCpp_ShowPrototypeInAbbr') + let g:OmniCpp_ShowPrototypeInAbbr = 0 + endif + + " Set if the access (+,#,-) is displayed + " 0 = no + " 1 = yes + if !exists('g:OmniCpp_ShowAccess') + let g:OmniCpp_ShowAccess = 1 + endif + + " Set the list of default namespaces + " eg: ['std'] + if !exists('g:OmniCpp_DefaultNamespaces') + let g:OmniCpp_DefaultNamespaces = [] + endif + + " Set MayComplete to '.' + " 0 = disabled + " 1 = enabled + " default = 1 + if !exists('g:OmniCpp_MayCompleteDot') + let g:OmniCpp_MayCompleteDot = 1 + endif + + " Set MayComplete to '->' + " 0 = disabled + " 1 = enabled + " default = 1 + if !exists('g:OmniCpp_MayCompleteArrow') + let g:OmniCpp_MayCompleteArrow = 1 + endif + + " Set MayComplete to dot + " 0 = disabled + " 1 = enabled + " default = 0 + if !exists('g:OmniCpp_MayCompleteScope') + let g:OmniCpp_MayCompleteScope = 0 + endif + + " When completeopt does not contain longest option, this setting + " controls the behaviour of the popup menu selection when starting the completion + " 0 = don't select first item + " 1 = select first item (inserting it to the text) + " 2 = select first item (without inserting it to the text) + " default = 0 + if !exists('g:OmniCpp_SelectFirstItem') + let g:OmniCpp_SelectFirstItem= 0 + endif + + " Use local search function for variable definitions + " 0 = use standard vim search function + " 1 = use local search function + " default = 0 + if !exists('g:OmniCpp_LocalSearchDecl') + let g:OmniCpp_LocalSearchDecl= 0 + endif +endfunc diff --git a/VimFiles/autoload/omni/cpp/tokenizer.vim b/VimFiles/autoload/omni/cpp/tokenizer.vim new file mode 100755 index 0000000..16e0be2 --- /dev/null +++ b/VimFiles/autoload/omni/cpp/tokenizer.vim @@ -0,0 +1,93 @@ +" Description: Omni completion tokenizer +" Maintainer: Vissale NEANG +" Last Change: 26 sept. 2007 +" TODO: Generic behaviour for Tokenize() + +" From the C++ BNF +let s:cppKeyword = ['asm', 'auto', 'bool', 'break', 'case', 'catch', 'char', 'class', 'const', 'const_cast', 'continue', 'default', 'delete', 'do', 'double', 'dynamic_cast', 'else', 'enum', 'explicit', 'export', 'extern', 'false', 'float', 'for', 'friend', 'goto', 'if', 'inline', 'int', 'long', 'mutable', 'namespace', 'new', 'operator', 'private', 'protected', 'public', 'register', 'reinterpret_cast', 'return', 'short', 'signed', 'sizeof', 'static', 'static_cast', 'struct', 'switch', 'template', 'this', 'throw', 'true', 'try', 'typedef', 'typeid', 'typename', 'union', 'unsigned', 'using', 'virtual', 'void', 'volatile', 'wchar_t', 'while', 'and', 'and_eq', 'bitand', 'bitor', 'compl', 'not', 'not_eq', 'or', 'or_eq', 'xor', 'xor_eq'] + +let s:reCppKeyword = '\C\<'.join(s:cppKeyword, '\>\|\<').'\>' + +" The order of items in this list is very important because we use this list to build a regular +" expression (see below) for tokenization +let s:cppOperatorPunctuator = ['->*', '->', '--', '-=', '-', '!=', '!', '##', '#', '%:%:', '%=', '%>', '%:', '%', '&&', '&=', '&', '(', ')', '*=', '*', ',', '...', '.*', '.', '/=', '/', '::', ':>', ':', ';', '?', '[', ']', '^=', '^', '{', '||', '|=', '|', '}', '~', '++', '+=', '+', '<<=', '<%', '<:', '<<', '<=', '<', '==', '=', '>>=', '>>', '>=', '>'] + +" We build the regexp for the tokenizer +let s:reCComment = '\/\*\|\*\/' +let s:reCppComment = '\/\/' +let s:reComment = s:reCComment.'\|'.s:reCppComment +let s:reCppOperatorOrPunctuator = escape(join(s:cppOperatorPunctuator, '\|'), '*./^~[]') + + +" Tokenize a c++ code +" a token is dictionary where keys are: +" - kind = cppKeyword|cppWord|cppOperatorPunctuator|unknown|cComment|cppComment|cppDigit +" - value = 'something' +" Note: a cppWord is any word that is not a cpp keyword +function! omni#cpp#tokenizer#Tokenize(szCode) + let result = [] + + " The regexp to find a token, a token is a keyword, word or + " c++ operator or punctuator. To work properly we have to put + " spaces and tabs to our regexp. + let reTokenSearch = '\(\w\+\)\|\s\+\|'.s:reComment.'\|'.s:reCppOperatorOrPunctuator + " eg: 'using namespace std;' + " ^ ^ + " start=0 end=5 + let startPos = 0 + let endPos = matchend(a:szCode, reTokenSearch) + let len = endPos-startPos + while endPos!=-1 + " eg: 'using namespace std;' + " ^ ^ + " start=0 end=5 + " token = 'using' + " We also remove space and tabs + let token = substitute(strpart(a:szCode, startPos, len), '\s', '', 'g') + + " eg: 'using namespace std;' + " ^ ^ + " start=5 end=15 + let startPos = endPos + let endPos = matchend(a:szCode, reTokenSearch, startPos) + let len = endPos-startPos + + " It the token is empty we continue + if token=='' + continue + endif + + " Building the token + let resultToken = {'kind' : 'unknown', 'value' : token} + + " Classify the token + if token =~ '^\d\+' + " It's a digit + let resultToken.kind = 'cppDigit' + elseif token=~'^\w\+$' + " It's a word + let resultToken.kind = 'cppWord' + + " But maybe it's a c++ keyword + if match(token, s:reCppKeyword)>=0 + let resultToken.kind = 'cppKeyword' + endif + else + if match(token, s:reComment)>=0 + if index(['/*','*/'],token)>=0 + let resultToken.kind = 'cComment' + else + let resultToken.kind = 'cppComment' + endif + else + " It's an operator + let resultToken.kind = 'cppOperatorPunctuator' + endif + endif + + " We have our token, let's add it to the result list + call extend(result, [resultToken]) + endwhile + + return result +endfunc diff --git a/VimFiles/autoload/omni/common/debug.vim b/VimFiles/autoload/omni/common/debug.vim new file mode 100755 index 0000000..eded649 --- /dev/null +++ b/VimFiles/autoload/omni/common/debug.vim @@ -0,0 +1,32 @@ +" Description: Omni completion debug functions +" Maintainer: Vissale NEANG +" Last Change: 26 sept. 2007 + +let s:CACHE_DEBUG_TRACE = [] + +" Start debug, clear the debug file +function! omni#common#debug#Start() + let s:CACHE_DEBUG_TRACE = [] + call extend(s:CACHE_DEBUG_TRACE, ['============ Debug Start ============']) + call writefile(s:CACHE_DEBUG_TRACE, "Omni.dbg") +endfunc + +" End debug, write to debug file +function! omni#common#debug#End() + call extend(s:CACHE_DEBUG_TRACE, ["============= Debug End ============="]) + call extend(s:CACHE_DEBUG_TRACE, [""]) + call writefile(s:CACHE_DEBUG_TRACE, "Omni.dbg") +endfunc + +" Debug trace function +function! omni#common#debug#Trace(szFuncName, ...) + let szTrace = a:szFuncName + let paramNum = a:0 + if paramNum>0 + let szTrace .= ':' + endif + for i in range(paramNum) + let szTrace = szTrace .' ('. string(eval('a:'.string(i+1))).')' + endfor + call extend(s:CACHE_DEBUG_TRACE, [szTrace]) +endfunc diff --git a/VimFiles/autoload/omni/common/utils.vim b/VimFiles/autoload/omni/common/utils.vim new file mode 100755 index 0000000..c880ad2 --- /dev/null +++ b/VimFiles/autoload/omni/common/utils.vim @@ -0,0 +1,67 @@ +" Description: Omni completion utils +" Maintainer: Vissale NEANG +" Last Change: 26 sept. 2007 + +" For sort numbers in list +function! omni#common#utils#CompareNumber(i1, i2) + let num1 = eval(a:i1) + let num2 = eval(a:i2) + return num1 == num2 ? 0 : num1 > num2 ? 1 : -1 +endfunc + +" TagList function calling the vim taglist() with try catch +" The only throwed exception is 'TagList:UserInterrupt' +" We also force the noignorecase option to avoid linear search when calling +" taglist() +function! omni#common#utils#TagList(szTagQuery) + let result = [] + let bUserIgnoreCase = &ignorecase + " Forcing noignorecase search => binary search can be used in taglist() + " if tags in the tag file are sorted + if bUserIgnoreCase + set noignorecase + endif + try + let result = taglist(a:szTagQuery) + catch /^Vim:Interrupt$/ + " Restoring user's setting + if bUserIgnoreCase + set ignorecase + endif + throw 'TagList:UserInterrupt' + catch + "Note: it seems that ctags can generate corrupted files, in this case + "taglist() will fail to read the tagfile and an exception from + "has_add() is thrown + endtry + + " Restoring user's setting + if bUserIgnoreCase + set ignorecase + endif + return result +endfunc + +" Same as TagList but don't throw exception +function! omni#common#utils#TagListNoThrow(szTagQuery) + let result = [] + try + let result = omni#common#utils#TagList(a:szTagQuery) + catch + endtry + return result +endfunc + +" Get the word under the cursor +function! omni#common#utils#GetWordUnderCursor() + let szLine = getline('.') + let startPos = getpos('.')[2]-1 + let startPos = (startPos < 0)? 0 : startPos + if szLine[startPos] =~ '\w' + let startPos = searchpos('\<\w\+', 'cbn', line('.'))[1] - 1 + endif + + let startPos = (startPos < 0)? 0 : startPos + let szResult = matchstr(szLine, '\w\+', startPos) + return szResult +endfunc diff --git a/VimFiles/autoload/omni/cpp/complete.vim b/VimFiles/autoload/omni/cpp/complete.vim new file mode 100755 index 0000000..a7e4edc --- /dev/null +++ b/VimFiles/autoload/omni/cpp/complete.vim @@ -0,0 +1,569 @@ +" 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 diff --git a/VimFiles/autoload/omni/cpp/includes.vim b/VimFiles/autoload/omni/cpp/includes.vim new file mode 100755 index 0000000..10a89bc --- /dev/null +++ b/VimFiles/autoload/omni/cpp/includes.vim @@ -0,0 +1,126 @@ +" Description: Omni completion script for cpp files +" Maintainer: Vissale NEANG +" Last Change: 26 sept. 2007 + +let g:omni#cpp#includes#CACHE_INCLUDES = {} +let g:omni#cpp#includes#CACHE_FILE_TIME = {} + +let s:rePreprocIncludePart = '\C#\s*include\s*' +let s:reIncludeFilePart = '\(<\|"\)\(\f\|\s\)\+\(>\|"\)' +let s:rePreprocIncludeFile = s:rePreprocIncludePart . s:reIncludeFilePart + +" Get the include list of a file +function! omni#cpp#includes#GetList(...) + if a:0 > 0 + return s:GetIncludeListFromFile(a:1, (a:0 > 1)? a:2 : 0 ) + else + return s:GetIncludeListFromCurrentBuffer() + endif +endfunc + +" Get the include list from the current buffer +function! s:GetIncludeListFromCurrentBuffer() + let listIncludes = [] + let originalPos = getpos('.') + + call setpos('.', [0, 1, 1, 0]) + let curPos = [1,1] + let alreadyInclude = {} + while curPos != [0,0] + let curPos = searchpos('\C\(^'.s:rePreprocIncludeFile.'\)', 'W') + if curPos != [0,0] + let szLine = getline('.') + let startPos = curPos[1] + let endPos = matchend(szLine, s:reIncludeFilePart, startPos-1) + if endPos!=-1 + let szInclusion = szLine[startPos-1:endPos-1] + let szIncludeFile = substitute(szInclusion, '\('.s:rePreprocIncludePart.'\)\|[<>""]', '', 'g') + let szResolvedInclude = omni#cpp#utils#ResolveFilePath(szIncludeFile) + + " Protection over self inclusion + if szResolvedInclude != '' && szResolvedInclude != omni#cpp#utils#ResolveFilePath(getreg('%')) + let includePos = curPos + if !has_key(alreadyInclude, szResolvedInclude) + call extend(listIncludes, [{'pos' : includePos, 'include' : szResolvedInclude}]) + let alreadyInclude[szResolvedInclude] = 1 + endif + endif + endif + endif + endwhile + + call setpos('.', originalPos) + return listIncludes +endfunc + +" Get the include list from a file +function! s:GetIncludeListFromFile(szFilePath, bUpdate) + let listIncludes = [] + if a:szFilePath == '' + return listIncludes + endif + + if !a:bUpdate && has_key(g:omni#cpp#includes#CACHE_INCLUDES, a:szFilePath) + return copy(g:omni#cpp#includes#CACHE_INCLUDES[a:szFilePath]) + endif + + let g:omni#cpp#includes#CACHE_FILE_TIME[a:szFilePath] = getftime(a:szFilePath) + + let szFixedPath = escape(a:szFilePath, g:omni#cpp#utils#szEscapedCharacters) + execute 'silent! lvimgrep /\C\(^'.s:rePreprocIncludeFile.'\)/gj '.szFixedPath + + let listQuickFix = getloclist(0) + let alreadyInclude = {} + for qf in listQuickFix + let szLine = qf.text + let startPos = qf.col + let endPos = matchend(szLine, s:reIncludeFilePart, startPos-1) + if endPos!=-1 + let szInclusion = szLine[startPos-1:endPos-1] + let szIncludeFile = substitute(szInclusion, '\('.s:rePreprocIncludePart.'\)\|[<>""]', '', 'g') + let szResolvedInclude = omni#cpp#utils#ResolveFilePath(szIncludeFile) + + " Protection over self inclusion + if szResolvedInclude != '' && szResolvedInclude != a:szFilePath + let includePos = [qf.lnum, qf.col] + if !has_key(alreadyInclude, szResolvedInclude) + call extend(listIncludes, [{'pos' : includePos, 'include' : szResolvedInclude}]) + let alreadyInclude[szResolvedInclude] = 1 + endif + endif + endif + endfor + + let g:omni#cpp#includes#CACHE_INCLUDES[a:szFilePath] = listIncludes + + return copy(listIncludes) +endfunc + +" For debug purpose +function! omni#cpp#includes#Display() + let szPathBuffer = omni#cpp#utils#ResolveFilePath(getreg('%')) + call s:DisplayIncludeTree(szPathBuffer, 0) +endfunc + +" For debug purpose +function! s:DisplayIncludeTree(szFilePath, indent, ...) + let includeGuard = {} + if a:0 >0 + let includeGuard = a:1 + endif + let szFilePath = omni#cpp#utils#ResolveFilePath(a:szFilePath) + if has_key(includeGuard, szFilePath) + return + else + let includeGuard[szFilePath] = 1 + endif + + let szIndent = repeat(' ', a:indent) + echo szIndent . a:szFilePath + let incList = omni#cpp#includes#GetList(a:szFilePath) + for inc in incList + call s:DisplayIncludeTree(inc.include, a:indent+1, includeGuard) + endfor +endfunc + + diff --git a/VimFiles/autoload/omni/cpp/items.vim b/VimFiles/autoload/omni/cpp/items.vim new file mode 100755 index 0000000..b943ad4 --- /dev/null +++ b/VimFiles/autoload/omni/cpp/items.vim @@ -0,0 +1,660 @@ +" Description: Omni completion script for cpp files +" Maintainer: Vissale NEANG +" Last Change: 26 sept. 2007 + +" Build the item list of an instruction +" An item is an instruction between a -> or . or ->* or .* +" We can sort an item in different kinds: +" eg: ((MyClass1*)(pObject))->_memberOfClass1.get() ->show() +" | cast | | member | | method | | method | +" @return a list of item +" an item is a dictionnary where keys are: +" tokens = list of token +" kind = itemVariable|itemCast|itemCppCast|itemTemplate|itemFunction|itemUnknown|itemThis|itemScope +function! omni#cpp#items#Get(tokens, ...) + let bGetWordUnderCursor = (a:0>0)? a:1 : 0 + + let result = [] + let itemsDelimiters = ['->', '.', '->*', '.*'] + + let tokens = reverse(omni#cpp#utils#BuildParenthesisGroups(a:tokens)) + + " fsm states: + " 0 = initial state + " TODO: add description of fsm states + let state=(bGetWordUnderCursor)? 1 : 0 + let item = {'tokens' : [], 'kind' : 'itemUnknown'} + let parenGroup=-1 + for token in tokens + if state==0 + if index(itemsDelimiters, token.value)>=0 + let item = {'tokens' : [], 'kind' : 'itemUnknown'} + let state = 1 + elseif token.value=='::' + let state = 9 + let item.kind = 'itemScope' + " Maybe end of tokens + elseif token.kind =='cppOperatorPunctuator' + " If it's a cppOperatorPunctuator and the current token is not + " a itemsDelimiters or '::' we can exit + let state=-1 + break + endif + elseif state==1 + call insert(item.tokens, token) + if token.kind=='cppWord' + " It's an attribute member or a variable + let item.kind = 'itemVariable' + let state = 2 + " Maybe end of tokens + elseif token.value=='this' + let item.kind = 'itemThis' + let state = 2 + " Maybe end of tokens + elseif token.value==')' + let parenGroup = token.group + let state = 3 + elseif token.value==']' + let parenGroup = token.group + let state = 4 + elseif token.kind == 'cppDigit' + let state = -1 + break + endif + elseif state==2 + if index(itemsDelimiters, token.value)>=0 + call insert(result, item) + let item = {'tokens' : [], 'kind' : 'itemUnknown'} + let state = 1 + elseif token.value == '::' + call insert(item.tokens, token) + " We have to get namespace or classscope + let state = 8 + " Maybe end of tokens + else + call insert(result, item) + let state=-1 + break + endif + elseif state==3 + call insert(item.tokens, token) + if token.value=='(' && token.group == parenGroup + let state = 5 + " Maybe end of tokens + endif + elseif state==4 + call insert(item.tokens, token) + if token.value=='[' && token.group == parenGroup + let state = 1 + endif + elseif state==5 + if token.kind=='cppWord' + " It's a function or method + let item.kind = 'itemFunction' + call insert(item.tokens, token) + let state = 2 + " Maybe end of tokens + elseif token.value == '>' + " Maybe a cpp cast or template + let item.kind = 'itemTemplate' + call insert(item.tokens, token) + let parenGroup = token.group + let state = 6 + else + " Perhaps it's a C cast eg: ((void*)(pData)) or a variable eg:(*pData) + let item.kind = omni#cpp#utils#GetCastType(item.tokens) + let state=-1 + call insert(result, item) + break + endif + elseif state==6 + call insert(item.tokens, token) + if token.value == '<' && token.group == parenGroup + " Maybe a cpp cast or template + let state = 7 + endif + elseif state==7 + call insert(item.tokens, token) + if token.kind=='cppKeyword' + " It's a cpp cast + let item.kind = omni#cpp#utils#GetCastType(item.tokens) + let state=-1 + call insert(result, item) + break + else + " Template ? + let state=-1 + call insert(result, item) + break + endif + elseif state==8 + if token.kind=='cppWord' + call insert(item.tokens, token) + let state = 2 + " Maybe end of tokens + else + let state=-1 + call insert(result, item) + break + endif + elseif state==9 + if token.kind == 'cppWord' + call insert(item.tokens, token) + let state = 10 + " Maybe end of tokens + else + let state=-1 + call insert(result, item) + break + endif + elseif state==10 + if token.value == '::' + call insert(item.tokens, token) + let state = 9 + " Maybe end of tokens + else + let state=-1 + call insert(result, item) + break + endif + endif + endfor + + if index([2, 5, 8, 9, 10], state)>=0 + if state==5 + let item.kind = omni#cpp#utils#GetCastType(item.tokens) + endif + call insert(result, item) + endif + + return result +endfunc + +" Resolve type information of items +" @param namespaces: list of namespaces used in the file +" @param szCurrentClassScope: the current class scope, only used for the first +" item to detect if this item is a class member (attribute, method) +" @param items: list of item, can be an empty list @see GetItemsToComplete +function! omni#cpp#items#ResolveItemsTypeInfo(contextStack, items) + " Note: kind = itemVariable|cCast|cppCast|template|function|itemUnknown|this + " For the first item, if it's a variable we try to detect the type of the + " variable with the function searchdecl. If it fails, thanks to the + " current class scope, we try to detect if the variable is an attribute + " member. + " If the kind of the item is a function, we have to first check if the + " function is a method of the class, if it fails we try to get a match in + " the global namespace. After that we get the returned type of the + " function. + " It the kind is a C cast or C++ cast, there is no problem, it's the + " easiest case. We just extract the type of the cast. + + let szCurrentContext = '' + let typeInfo = {} + " Note: We search the decl only for the first item + let bSearchDecl = 1 + for item in a:items + let curItem = item + if index(['itemVariable', 'itemFunction'], curItem.kind)>=0 + " Note: a variable can be : MyNs::MyClass::_var or _var or (*pVar) + " or _var[0][0] + let szSymbol = s:GetSymbol(curItem.tokens) + + " If we have MyNamespace::myVar + " We add MyNamespace in the context stack set szSymbol to myVar + if match(szSymbol, '::\w\+$') >= 0 + let szCurrentContext = substitute(szSymbol, '::\w\+$', '', 'g') + let szSymbol = matchstr(szSymbol, '\w\+$') + endif + let tmpContextStack = a:contextStack + if szCurrentContext != '' + let tmpContextStack = [szCurrentContext] + a:contextStack + endif + + if curItem.kind == 'itemVariable' + let typeInfo = s:GetTypeInfoOfVariable(tmpContextStack, szSymbol, bSearchDecl) + else + let typeInfo = s:GetTypeInfoOfReturnedType(tmpContextStack, szSymbol) + endif + + elseif curItem.kind == 'itemThis' + if len(a:contextStack) + let typeInfo = omni#cpp#utils#CreateTypeInfo(substitute(a:contextStack[0], '^::', '', 'g')) + endif + elseif curItem.kind == 'itemCast' + let typeInfo = omni#cpp#utils#CreateTypeInfo(s:ResolveCCast(curItem.tokens)) + elseif curItem.kind == 'itemCppCast' + let typeInfo = omni#cpp#utils#CreateTypeInfo(s:ResolveCppCast(curItem.tokens)) + elseif curItem.kind == 'itemScope' + let typeInfo = omni#cpp#utils#CreateTypeInfo(substitute(s:TokensToString(curItem.tokens), '\s', '', 'g')) + endif + + if omni#cpp#utils#IsTypeInfoValid(typeInfo) + let szCurrentContext = omni#cpp#utils#GetTypeInfoString(typeInfo) + endif + let bSearchDecl = 0 + endfor + + return typeInfo +endfunc + +" Get symbol name +function! s:GetSymbol(tokens) + let szSymbol = '' + let state = 0 + for token in a:tokens + if state == 0 + if token.value == '::' + let szSymbol .= token.value + let state = 1 + elseif token.kind == 'cppWord' + let szSymbol .= token.value + let state = 2 + " Maybe end of token + endif + elseif state == 1 + if token.kind == 'cppWord' + let szSymbol .= token.value + let state = 2 + " Maybe end of token + else + " Error + break + endif + elseif state == 2 + if token.value == '::' + let szSymbol .= token.value + let state = 1 + else + break + endif + endif + endfor + return szSymbol +endfunc + +" Search a declaration. +" eg: std::map +" can be empty +" Note: The returned type info can be a typedef +" The typedef resolution is done later +" @return +" - a dictionnary where keys are +" - type: the type of value same as type() +" - value: the value +function! s:GetTypeInfoOfVariable(contextStack, szVariable, bSearchDecl) + let result = {} + + if a:bSearchDecl + " Search type of declaration + "let result = s:SearchTypeInfoOfDecl(a:szVariable) + let result = s:SearchDecl(a:szVariable) + endif + + if result=={} + let szFilter = "index(['m', 'v'], v:val.kind[0])>=0" + let tagItem = s:ResolveSymbol(a:contextStack, a:szVariable, szFilter) + if tagItem=={} + return result + endif + + let szCmdWithoutVariable = substitute(omni#cpp#utils#ExtractCmdFromTagItem(tagItem), '\C\<'.a:szVariable.'\>.*', '', 'g') + let tokens = omni#cpp#tokenizer#Tokenize(omni#cpp#utils#GetCodeFromLine(szCmdWithoutVariable)) + let result = omni#cpp#utils#CreateTypeInfo(omni#cpp#utils#ExtractTypeInfoFromTokens(tokens)) + " TODO: Namespace resolution for result + + if result != {} && result.value=='' + " result.value=='' + " eg: + " struct + " { + " }gVariable; + if has_key(tagItem, 'typeref') + " Maybe the variable is a global var of an + " unnamed class, struct or union. + " eg: + " 1) + " struct + " { + " }gVariable; + " In this case we need the tags (the patched version) + " Note: We can have a named type like this: + " 2) + " class A + " { + " }gVariable; + if s:IsUnnamedType(tagItem) + " It's an unnamed type we are in the case 1) + let result = omni#cpp#utils#CreateTypeInfo(tagItem) + else + " It's not an unnamed type we are in the case 2) + + " eg: tagItem.typeref = 'struct:MY_STRUCT::MY_SUBSTRUCT' + let szTypeRef = substitute(tagItem.typeref, '^\w\+:', '', '') + + " eg: szTypeRef = 'MY_STRUCT::MY_SUBSTRUCT' + let result = omni#cpp#utils#CreateTypeInfo(szTypeRef) + endif + endif + endif + endif + return result +endfunc + +" Get the type info string from the returned type of function +function! s:GetTypeInfoOfReturnedType(contextStack, szFunctionName) + let result = {} + + let szFilter = "index(['f', 'p'], v:val.kind[0])>=0" + let tagItem = s:ResolveSymbol(a:contextStack, a:szFunctionName, szFilter) + + if tagItem != {} + let szCmdWithoutVariable = substitute(omni#cpp#utils#ExtractCmdFromTagItem(tagItem), '\C\<'.a:szFunctionName.'\>.*', '', 'g') + let tokens = omni#cpp#tokenizer#Tokenize(omni#cpp#utils#GetCodeFromLine(szCmdWithoutVariable)) + let result = omni#cpp#utils#CreateTypeInfo(omni#cpp#utils#ExtractTypeInfoFromTokens(tokens)) + " TODO: Namespace resolution for result + return result + endif + return result +endfunc + +" Resolve a symbol, return a tagItem +" Gets the first symbol found in the context stack +function! s:ResolveSymbol(contextStack, szSymbol, szTagFilter) + let tagItem = {} + for szCurrentContext in a:contextStack + if szCurrentContext != '::' + let szTagQuery = substitute(szCurrentContext, '^::', '', 'g').'::'.a:szSymbol + else + let szTagQuery = a:szSymbol + endif + + let tagList = omni#common#utils#TagListNoThrow('^'.szTagQuery.'$') + call filter(tagList, a:szTagFilter) + if len(tagList) + let tagItem = tagList[0] + break + endif + endfor + return tagItem +endfunc + +" Return if the tag item represent an unnamed type +function! s:IsUnnamedType(tagItem) + let bResult = 0 + if has_key(a:tagItem, 'typeref') + " Note: Thanks for __anon ! + let bResult = match(a:tagItem.typeref, '\C\<__anon') >= 0 + endif + return bResult +endfunc + +" Search the declaration of a variable and return the type info +function! s:SearchTypeInfoOfDecl(szVariable) + let szReVariable = '\C\<'.a:szVariable.'\>' + + let originalPos = getpos('.') + let origPos = originalPos[1:2] + let curPos = origPos + let stopPos = origPos + + while curPos !=[0,0] + " We go to the start of the current scope + let curPos = searchpairpos('{', '', '}', 'bW', g:omni#cpp#utils#expIgnoreComments) + if curPos != [0,0] + let matchPos = curPos + " Now want to search our variable but we don't want to go in child + " scope + while matchPos != [0,0] + let matchPos = searchpos('{\|'.szReVariable, 'W', stopPos[0]) + if matchPos != [0,0] + " We ignore matches under comment + if omni#cpp#utils#IsCursorInCommentOrString() + continue + endif + + " Getting the current line + let szLine = getline('.') + if match(szLine, szReVariable)>=0 + " We found our variable + " Check if the current instruction is a decl instruction + let tokens = omni#cpp#utils#TokenizeCurrentInstruction() + let szTypeInfo = s:ExtractTypeInfoFromDecl(tokens) + if szTypeInfo != '' + call setpos('.', originalPos) + return omni#cpp#utils#CreateTypeInfo(szTypeInfo) + endif + else + " We found a child scope, we don't want to go in, thus + " we search for the end } of this child scope + let bracketEnd = searchpairpos('{', '', '}', 'nW', g:omni#cpp#utils#expIgnoreComments) + if bracketEnd == [0,0] + break + endif + + if bracketEnd[0] >= stopPos[0] + " The end of the scope is after our cursor we stop + " the search + break + else + " We move the cursor and continue to search our + " variable + call setpos('.', [0, bracketEnd[0], bracketEnd[1], 0]) + endif + endif + endif + endwhile + + " Backing to the start of the scope + call setpos('.', [0,curPos[0], curPos[1], 0]) + let stopPos = curPos + endif + endwhile + + let result = {} + if s:LocalSearchDecl(a:szVariable)==0 && !omni#cpp#utils#IsCursorInCommentOrString() + let tokens = omni#cpp#utils#TokenizeCurrentInstruction() + let szTypeInfo = s:ExtractTypeInfoFromDecl(tokens) + if szTypeInfo != '' + let result = omni#cpp#utils#CreateTypeInfo(szTypeInfo) + endif + endif + + call setpos('.', originalPos) + + return result +endfunc + +" Search a declaration +" @return +" - tokens of the current instruction if success +" - empty list if failure +function! s:SearchDecl(szVariable) + let result = {} + let originalPos = getpos('.') + let searchResult = s:LocalSearchDecl(a:szVariable) + if searchResult==0 + " searchdecl() may detect a decl if the variable is in a conditional + " instruction (if, elseif, while etc...) + " We have to check if the detected decl is really a decl instruction + let tokens = omni#cpp#utils#TokenizeCurrentInstruction() + + for token in tokens + " Simple test + if index(['if', 'elseif', 'while', 'for', 'switch'], token.value)>=0 + " Invalid declaration instruction + call setpos('.', originalPos) + return result + endif + endfor + + let szTypeInfo = s:ExtractTypeInfoFromDecl(tokens) + if szTypeInfo != '' + let result = omni#cpp#utils#CreateTypeInfo(szTypeInfo) + endif + endif + call setpos('.', originalPos) + return result +endfunc + +" Extract the type info string from an instruction. +" We use a small parser to extract the type +" We parse the code according to a C++ BNF from: http://www.nongnu.org/hcb/#basic.link +" @param tokens: token list of the current instruction +function! s:ExtractTypeInfoFromDecl(tokens) + return omni#cpp#utils#ExtractTypeInfoFromTokens(a:tokens) +endfunc + +" Convert tokens to string +function! s:TokensToString(tokens) + let result = '' + for token in a:tokens + let result = result . token.value . ' ' + endfor + return result[:-2] +endfunc + +" Resolve a cast. +" Resolve a C++ cast +" @param list of token. tokens must be a list that represents +" a cast expression (C++ cast) the function does not control +" if it's a cast or not +" eg: static_cast(something) +" @return type info string +function! s:ResolveCppCast(tokens) + return omni#cpp#utils#ExtractTypeInfoFromTokens(s:ResolveCast(a:tokens, '<', '>')) +endfunc + +" Resolve a cast. +" Resolve a C cast +" @param list of token. tokens must be a list that represents +" a cast expression (C cast) the function does not control +" if it's a cast or not +" eg: (MyClass*)something +" @return type info string +function! s:ResolveCCast(tokens) + return omni#cpp#utils#ExtractTypeInfoFromTokens(s:ResolveCast(a:tokens, '(', ')')) +endfunc + +" Resolve a cast. +" Resolve a C cast +" @param list of token. tokens must be a list that represents +" a cast expression (C cast) the function does not control +" if it's a cast or not +" eg: (MyClass*)something +" @return type tokens +function! s:ResolveCast(tokens, startChar, endChar) + let tokens = omni#cpp#utils#BuildParenthesisGroups(a:tokens) + + " We remove useless parenthesis eg: (((MyClass))) + let tokens = omni#cpp#utils#SimplifyParenthesis(tokens) + + let countItem=0 + let startIndex = -1 + let endIndex = -1 + let i = 0 + for token in tokens + if startIndex==-1 + if token.value==a:startChar + let countItem += 1 + let startIndex = i + endif + else + if token.value==a:startChar + let countItem += 1 + elseif token.value==a:endChar + let countItem -= 1 + endif + + if countItem==0 + let endIndex = i + break + endif + endif + let i+=1 + endfor + + return tokens[startIndex+1 : endIndex-1] +endfunc + +" Replacement for build-in function 'searchdecl' +" It does not require that the upper-level bracket is in the first column. +" Otherwise it should be equal to 'searchdecl(name, 0, 1)' +" @param name: name of variable to find declaration for +function! s:LocalSearchDecl(name) + + if g:OmniCpp_LocalSearchDecl == 0 + let bUserIgnoreCase = &ignorecase + + " Forcing the noignorecase option + " avoid bug when, for example, if we have a declaration like this : "A a;" + set noignorecase + + let result = searchdecl(a:name, 0, 1) + + " Restoring user's setting + let &ignorecase = bUserIgnoreCase + + return result + endif + + let lastpos = getpos('.') + let winview = winsaveview() + let lastfoldenable = &foldenable + let &foldenable = 0 + + " We add \C (noignorecase) to + " avoid bug when, for example, if we have a declaration like this : "A a;" + let varname = "\\C\\<" . a:name . "\\>" + + " Go to first blank line before begin of highest scope + normal 99[{ + let scopepos = getpos('.') + while (line('.') > 1) && (len(split(getline('.'))) > 0) + call cursor(line('.')-1, 0) + endwhile + + let declpos = [ 0, 0, 0, 0 ] + while search(varname, '', scopepos[1]) > 0 + " Check if we are a string or a comment + if omni#cpp#utils#IsCursorInCommentOrString() + continue + endif + + " Remember match + let declpos = getpos('.') + endwhile + if declpos[1] != 0 + " We found a match + call winrestview(winview) + call setpos('.', declpos) + let &foldenable = lastfoldenable + return 0 + endif + + while search(varname, '', lastpos[1]) > 0 + " Check if current scope is ending before variable + let old_cur = getpos('.') + normal ]} + let new_cur = getpos('.') + call setpos('.', old_cur) + if (new_cur[1] < lastpos[1]) || ((new_cur[1] == lastpos[1]) && (new_cur[2] < lastpos[2])) + continue + endif + + " Check if we are a string or a comment + if omni#cpp#utils#IsCursorInCommentOrString() + continue + endif + + " We found match + call winrestview(winview) + call setpos('.', old_cur) + let &foldenable = lastfoldenable + return 0 + endwhile + + " No match found. + call winrestview(winview) + let &foldenable = lastfoldenable + return 1 +endfunc diff --git a/VimFiles/autoload/omni/cpp/maycomplete.vim b/VimFiles/autoload/omni/cpp/maycomplete.vim new file mode 100755 index 0000000..610526b --- /dev/null +++ b/VimFiles/autoload/omni/cpp/maycomplete.vim @@ -0,0 +1,82 @@ +" Description: Omni completion script for cpp files +" Maintainer: Vissale NEANG +" Last Change: 26 sept. 2007 + +" Check if we can use omni completion in the current buffer +function! s:CanUseOmnicompletion() + " For C and C++ files and only if the omnifunc is omni#cpp#complete#Main + return (index(['c', 'cpp'], &filetype)>=0 && &omnifunc == 'omni#cpp#complete#Main' && !omni#cpp#utils#IsCursorInCommentOrString()) +endfunc + +" Return the mapping of omni completion +function! omni#cpp#maycomplete#Complete() + let szOmniMapping = "\\" + + " 0 = don't select first item + " 1 = select first item (inserting it to the text, default vim behaviour) + " 2 = select first item (without inserting it to the text) + if g:OmniCpp_SelectFirstItem == 0 + " We have to force the menuone option to avoid confusion when there is + " only one popup item + set completeopt-=menu + set completeopt+=menuone + let szOmniMapping .= "\" + elseif g:OmniCpp_SelectFirstItem == 2 + " We have to force the menuone option to avoid confusion when there is + " only one popup item + set completeopt-=menu + set completeopt+=menuone + let szOmniMapping .= "\" + let szOmniMapping .= "\=pumvisible() ? \"\\\" : \"\"\" + endif + return szOmniMapping +endfunc + +" May complete function for dot +function! omni#cpp#maycomplete#Dot() + if s:CanUseOmnicompletion() && g:OmniCpp_MayCompleteDot + let g:omni#cpp#items#data = omni#cpp#items#Get(omni#cpp#utils#TokenizeCurrentInstruction('.')) + if len(g:omni#cpp#items#data) + let s:bMayComplete = 1 + return '.' . omni#cpp#maycomplete#Complete() + endif + endif + return '.' +endfunc +" May complete function for arrow +function! omni#cpp#maycomplete#Arrow() + if s:CanUseOmnicompletion() && g:OmniCpp_MayCompleteArrow + let index = col('.') - 2 + if index >= 0 + let char = getline('.')[index] + if char == '-' + let g:omni#cpp#items#data = omni#cpp#items#Get(omni#cpp#utils#TokenizeCurrentInstruction('>')) + if len(g:omni#cpp#items#data) + let s:bMayComplete = 1 + return '>' . omni#cpp#maycomplete#Complete() + endif + endif + endif + endif + return '>' +endfunc + +" May complete function for double points +function! omni#cpp#maycomplete#Scope() + if s:CanUseOmnicompletion() && g:OmniCpp_MayCompleteScope + let index = col('.') - 2 + if index >= 0 + let char = getline('.')[index] + if char == ':' + let g:omni#cpp#items#data = omni#cpp#items#Get(omni#cpp#utils#TokenizeCurrentInstruction(':')) + if len(g:omni#cpp#items#data) + if len(g:omni#cpp#items#data[-1].tokens) && g:omni#cpp#items#data[-1].tokens[-1].value != '::' + let s:bMayComplete = 1 + return ':' . omni#cpp#maycomplete#Complete() + endif + endif + endif + endif + endif + return ':' +endfunc diff --git a/VimFiles/autoload/omni/cpp/namespaces.vim b/VimFiles/autoload/omni/cpp/namespaces.vim new file mode 100755 index 0000000..386b3f9 --- /dev/null +++ b/VimFiles/autoload/omni/cpp/namespaces.vim @@ -0,0 +1,838 @@ +" Description: Omni completion script for cpp files +" Maintainer: Vissale NEANG +" Last Change: 26 sept. 2007 + +let g:omni#cpp#namespaces#CacheResolve = {} +let g:omni#cpp#namespaces#CacheUsing = {} +" TODO: For the next release +"let g:omni#cpp#namespaces#CacheAlias = {} + +" Get the using namespace list from a line +function! s:GetNamespaceAliasListFromLine(szLine) + let result = {} + let tokens = omni#cpp#tokenizer#Tokenize(a:szLine) + let szAlias = '' + let szNamespace = '' + let state = 0 + for token in tokens + if state==0 + let szAlias = '' + let szNamespace = '' + if token.value == '/*' + let state = 1 + elseif token.value == '//' + " It's a comment + let state = -1 + break + elseif token.value == 'namespace' + let state = 2 + endif + elseif state==1 + if token.value == '*/' + let state=0 + endif + elseif state==2 + if token.kind == 'cppWord' + let szAlias .= token.value + let state = 3 + else + let state = -1 + break + endif + elseif state == 3 + if token.value == '=' + let state = 4 + else + let state = -1 + break + endif + elseif state == 4 + if token.value == '::' + let szNamespace .= token.value + let state = 5 + elseif token.kind == 'cppWord' + let szNamespace .= token.value + let state = 6 + " Maybe end of tokens + endif + elseif state==5 + if token.kind == 'cppWord' + let szNamespace .= token.value + let state = 6 + " Maybe end of tokens + else + " Error, we can't have 'namespace ALIAS = Something::' + let state = -1 + break + endif + elseif state==6 + if token.value == '::' + let szNamespace .= token.value + let state = 5 + else + call extend(result, {szAlias : szNamespace}) + let state = 0 + endif + endif + endfor + + if state == 6 + call extend(result, {szAlias : szNamespace}) + endif + + return result +endfunc + +" Get the using namespace list from a line +function! s:GetNamespaceListFromLine(szLine) + let result = [] + let tokens = omni#cpp#tokenizer#Tokenize(a:szLine) + let szNamespace = '' + let state = 0 + for token in tokens + if state==0 + let szNamespace = '' + if token.value == '/*' + let state = 1 + elseif token.value == '//' + " It's a comment + let state = -1 + break + elseif token.value == 'using' + let state = 2 + endif + elseif state==1 + if token.value == '*/' + let state=0 + endif + elseif state==2 + if token.value == 'namespace' + let state = 3 + else + " Error, 'using' must be followed by 'namespace' + let state = -1 + break + endif + elseif state==3 + if token.value == '::' + let szNamespace .= token.value + let state = 4 + elseif token.kind == 'cppWord' + let szNamespace .= token.value + let state = 5 + " Maybe end of tokens + endif + elseif state==4 + if token.kind == 'cppWord' + let szNamespace .= token.value + let state = 5 + " Maybe end of tokens + else + " Error, we can't have 'using namespace Something::' + let state = -1 + break + endif + elseif state==5 + if token.value == '::' + let szNamespace .= token.value + let state = 4 + else + call extend(result, [szNamespace]) + let state = 0 + endif + endif + endfor + + if state == 5 + call extend(result, [szNamespace]) + endif + + return result +endfunc + +" Get the namespace list from a namespace map +function! s:GetUsingNamespaceListFromMap(namespaceMap, ...) + let stopLine = 0 + if a:0>0 + let stopLine = a:1 + endif + + let result = [] + let keys = sort(keys(a:namespaceMap), 'omni#common#utils#CompareNumber') + for i in keys + if stopLine != 0 && i > stopLine + break + endif + call extend(result, a:namespaceMap[i]) + endfor + return result +endfunc + +" Get global using namespace list from the current buffer +function! omni#cpp#namespaces#GetListFromCurrentBuffer(...) + let namespaceMap = s:GetAllUsingNamespaceMapFromCurrentBuffer() + let result = [] + if namespaceMap != {} + let result = s:GetUsingNamespaceListFromMap(namespaceMap, (a:0 > 0)? a:1 : line('.')) + endif + return result +endfunc + +" Get global using namespace map from the current buffer and include files recursively +function! s:GetAllUsingNamespaceMapFromCurrentBuffer(...) + let includeGuard = (a:0>0)? a:1 : {} + + let szBufferName = getreg("%") + let szFilePath = omni#cpp#utils#ResolveFilePath(szBufferName) + let szFilePath = (szFilePath=='')? szBufferName : szFilePath + + let namespaceMap = {} + if has_key(includeGuard, szFilePath) + return namespaceMap + else + let includeGuard[szFilePath] = 1 + endif + + let namespaceMap = omni#cpp#namespaces#GetMapFromCurrentBuffer() + + if g:OmniCpp_NamespaceSearch != 2 + " We don't search included files if OmniCpp_NamespaceSearch != 2 + return namespaceMap + endif + + for inc in omni#cpp#includes#GetList() + let lnum = inc.pos[0] + let tmpMap = s:GetAllUsingNamespaceMapFromFile(inc.include, includeGuard) + if tmpMap != {} + if has_key(namespaceMap, lnum) + call extend(namespaceMap[lnum], s:GetUsingNamespaceListFromMap(tmpMap)) + else + let namespaceMap[lnum] = s:GetUsingNamespaceListFromMap(tmpMap) + endif + endif + endfor + + return namespaceMap +endfunc + +" Get global using namespace map from a file and include files recursively +function! s:GetAllUsingNamespaceMapFromFile(szFilePath, ...) + let includeGuard = {} + if a:0 >0 + let includeGuard = a:1 + endif + + let szFilePath = omni#cpp#utils#ResolveFilePath(a:szFilePath) + let szFilePath = (szFilePath=='')? a:szFilePath : szFilePath + + let namespaceMap = {} + if has_key(includeGuard, szFilePath) + return namespaceMap + else + let includeGuard[szFilePath] = 1 + endif + + " If g:OmniCpp_NamespaceSearch == 1 (search namespaces only in the current + " buffer) we don't use cache for the current buffer + let namespaceMap = omni#cpp#namespaces#GetMapFromBuffer(szFilePath, g:OmniCpp_NamespaceSearch==1) + + if g:OmniCpp_NamespaceSearch != 2 + " We don't search included files if OmniCpp_NamespaceSearch != 2 + return namespaceMap + endif + + for inc in omni#cpp#includes#GetList(szFilePath) + let lnum = inc.pos[0] + let tmpMap = s:GetAllUsingNamespaceMapFromFile(inc.include, includeGuard) + if tmpMap != {} + if has_key(namespaceMap, lnum) + call extend(namespaceMap[lnum], s:GetUsingNamespaceListFromMap(tmpMap)) + else + let namespaceMap[lnum] = s:GetUsingNamespaceListFromMap(tmpMap) + endif + endif + endfor + + return namespaceMap +endfunc + +" Get global using namespace map from a the current buffer +function! omni#cpp#namespaces#GetMapFromCurrentBuffer() + let namespaceMap = {} + let originalPos = getpos('.') + + call setpos('.', [0, 1, 1, 0]) + let curPos = [1,1] + while curPos != [0,0] + let curPos = searchpos('\C^using\s\+namespace', 'W') + if curPos != [0,0] + let szLine = getline('.') + let startPos = curPos[1] + let endPos = match(szLine, ';', startPos-1) + if endPos!=-1 + " We get the namespace list from the line + let namespaceMap[curPos[0]] = s:GetNamespaceListFromLine(szLine) + endif + endif + endwhile + + call setpos('.', originalPos) + return namespaceMap +endfunc + +" Get global using namespace map from a file +function! omni#cpp#namespaces#GetMapFromBuffer(szFilePath, ...) + let bUpdate = 0 + if a:0 > 0 + let bUpdate = a:1 + endif + + let szFilePath = omni#cpp#utils#ResolveFilePath(a:szFilePath) + let szFilePath = (szFilePath=='')? a:szFilePath : szFilePath + + if !bUpdate && has_key(g:omni#cpp#namespaces#CacheUsing, szFilePath) + return copy(g:omni#cpp#namespaces#CacheUsing[szFilePath]) + endif + + let namespaceMap = {} + " The file exists, we get the global namespaces in this file + let szFixedPath = escape(szFilePath, g:omni#cpp#utils#szEscapedCharacters) + execute 'silent! lvimgrep /\C^using\s\+namespace/gj '.szFixedPath + + " key = line number + " value = list of namespaces + let listQuickFix = getloclist(0) + for qf in listQuickFix + let szLine = qf.text + let startPos = qf.col + let endPos = match(szLine, ';', startPos-1) + if endPos!=-1 + " We get the namespace list from the line + let namespaceMap[qf.lnum] = s:GetNamespaceListFromLine(szLine) + endif + endfor + + if szFixedPath != '' + let g:omni#cpp#namespaces#CacheUsing[szFixedPath] = namespaceMap + endif + + return copy(namespaceMap) +endfunc + +" Get the stop position when searching for local variables +function! s:GetStopPositionForLocalSearch() + " Stop position when searching a local variable + let originalPos = getpos('.') + let origPos = originalPos[1:2] + let stopPosition = origPos + let curPos = origPos + while curPos !=[0,0] + let stopPosition = curPos + let curPos = searchpairpos('{', '', '}', 'bW', g:omni#cpp#utils#expIgnoreComments) + endwhile + call setpos('.', originalPos) + + return stopPosition +endfunc + +" Get namespaces alias used at the cursor postion in a vim buffer +" Note: The result depends on the current cursor position +" @return +" - Map of namespace alias +function! s:GetNamespaceAliasMap() + " We store the cursor position because searchpairpos() moves the cursor + let result = {} + let originalPos = getpos('.') + let origPos = originalPos[1:2] + + let stopPos = s:GetStopPositionForLocalSearch() + let stopLine = stopPos[0] + let curPos = origPos + let lastLine = 0 + let nextStopLine = origPos[0] + let szReAlias = '\Cnamespace\s\+\w\+\s\+=' + while curPos !=[0,0] + let curPos = searchpos('}\|\('. szReAlias .'\)', 'bW',stopLine) + if curPos!=[0,0] && curPos[0]!=lastLine + let lastLine = curPos[0] + + let szLine = getline('.') + if origPos[0] == curPos[0] + " We get the line until cursor position + let szLine = szLine[:origPos[1]] + endif + + let szLine = omni#cpp#utils#GetCodeFromLine(szLine) + if match(szLine, szReAlias)<0 + " We found a '}' + let curPos = searchpairpos('{', '', '}', 'bW', g:omni#cpp#utils#expIgnoreComments) + else + " We get the namespace alias from the line + call extend(result, s:GetNamespaceAliasListFromLine(szLine)) + let nextStopLine = curPos[0] + endif + endif + endwhile + + " Setting the cursor to the original position + call setpos('.', originalPos) + + call s:ResolveAliasKeys(result) + return result +endfunc + +" Resolve an alias +" eg: namespace IAmAnAlias1 = Ns1 +" eg: namespace IAmAnAlias2 = IAmAnAlias1::Ns2 +" => IAmAnAlias2 = Ns1::Ns2 +function! s:ResolveAliasKey(mapNamespaceAlias, szAlias) + let szResult = a:mapNamespaceAlias[a:szAlias] + " ::Ns1::Ns2::Ns3 => ['Ns1', 'Ns2', 'Ns3'] + let listNamespace = split(szResult, '::') + if len(listNamespace) + " szBeginPart = 'Ns1' + let szBeginPart = remove(listNamespace, 0) + + " Is 'Ns1' an alias ? + if has_key(a:mapNamespaceAlias, szBeginPart) && szBeginPart != a:szAlias + " Resolving alias 'Ns1' + " eg: Ns1 = NsResolved + let szResult = s:ResolveAliasKey(a:mapNamespaceAlias, szBeginPart) + " szEndPart = 'Ns2::Ns3' + let szEndPart = join(listNamespace, '::') + if szEndPart != '' + " Concatenation => szResult = 'NsResolved::Ns2::Ns3' + let szResult .= '::' . szEndPart + endif + endif + endif + return szResult +endfunc + +" Resolve all keys in the namespace alias map +function! s:ResolveAliasKeys(mapNamespaceAlias) + let mapNamespaceAlias = a:mapNamespaceAlias + call map(mapNamespaceAlias, 's:ResolveAliasKey(mapNamespaceAlias, v:key)') +endfunc + +" Resolve namespace alias +function! omni#cpp#namespaces#ResolveAlias(mapNamespaceAlias, szNamespace) + let szResult = a:szNamespace + " ::Ns1::Ns2::Ns3 => ['Ns1', 'Ns2', 'Ns3'] + let listNamespace = split(a:szNamespace, '::') + if len(listNamespace) + " szBeginPart = 'Ns1' + let szBeginPart = remove(listNamespace, 0) + + " Is 'Ns1' an alias ? + if has_key(a:mapNamespaceAlias, szBeginPart) + " Resolving alias 'Ns1' + " eg: Ns1 = NsResolved + let szResult = a:mapNamespaceAlias[szBeginPart] + " szEndPart = 'Ns2::Ns3' + let szEndPart = join(listNamespace, '::') + if szEndPart != '' + " Concatenation => szResult = 'NsResolved::Ns2::Ns3' + let szResult .= '::' . szEndPart + endif + + " If a:szNamespace starts with '::' we add '::' to the beginning + " of the result + if match(a:szNamespace, '^::')>=0 + let szResult = omni#cpp#utils#SimplifyScope('::' . szResult) + endif + endif + endif + return szResult +endfunc + +" Resolve namespace alias +function! s:ResolveAliasInNamespaceList(mapNamespaceAlias, listNamespaces) + call map(a:listNamespaces, 'omni#cpp#namespaces#ResolveAlias(a:mapNamespaceAlias, v:val)') +endfunc + +" Get namespaces used at the cursor postion in a vim buffer +" Note: The result depends on the current cursor position +" @return +" - List of namespace used in the reverse order +function! omni#cpp#namespaces#GetUsingNamespaces() + " We have to get local using namespace declarations + " We need the current cursor position and the position of the start of the + " current scope + + " We store the cursor position because searchpairpos() moves the cursor + let result = [] + let originalPos = getpos('.') + let origPos = originalPos[1:2] + + let stopPos = s:GetStopPositionForLocalSearch() + + let stopLine = stopPos[0] + let curPos = origPos + let lastLine = 0 + let nextStopLine = origPos[0] + while curPos !=[0,0] + let curPos = searchpos('\C}\|\(using\s\+namespace\)', 'bW',stopLine) + if curPos!=[0,0] && curPos[0]!=lastLine + let lastLine = curPos[0] + + let szLine = getline('.') + if origPos[0] == curPos[0] + " We get the line until cursor position + let szLine = szLine[:origPos[1]] + endif + + let szLine = omni#cpp#utils#GetCodeFromLine(szLine) + if match(szLine, '\Cusing\s\+namespace')<0 + " We found a '}' + let curPos = searchpairpos('{', '', '}', 'bW', g:omni#cpp#utils#expIgnoreComments) + else + " We get the namespace list from the line + let result = s:GetNamespaceListFromLine(szLine) + result + let nextStopLine = curPos[0] + endif + endif + endwhile + + " Setting the cursor to the original position + call setpos('.', originalPos) + + " 2) Now we can get all global using namespace declaration from the + " beginning of the file to nextStopLine + let result = omni#cpp#namespaces#GetListFromCurrentBuffer(nextStopLine) + result + + " Resolving alias in the namespace list + " TODO: For the next release + "let g:omni#cpp#namespaces#CacheAlias= s:GetNamespaceAliasMap() + "call s:ResolveAliasInNamespaceList(g:omni#cpp#namespaces#CacheAlias, result) + + return ['::'] + result +endfunc + +" Resolve a using namespace regarding the current context +" For each namespace used: +" - We get all possible contexts where the namespace +" can be define +" - We do a comparison test of each parent contexts with the current +" context list +" - If one and only one parent context is present in the +" current context list we add the namespace in the current +" context +" - If there is more than one of parent contexts in the +" current context the namespace is ambiguous +" @return +" - result item +" - kind = 0|1 +" - 0 = unresolved or error +" - 1 = resolved +" - value = resolved namespace +function! s:ResolveNamespace(namespace, mapCurrentContexts) + let result = {'kind':0, 'value': ''} + + " If the namespace is already resolved we add it in the list of + " current contexts + if match(a:namespace, '^::')>=0 + let result.kind = 1 + let result.value = a:namespace + return result + elseif match(a:namespace, '\w\+::\w\+')>=0 + let mapCurrentContextsTmp = copy(a:mapCurrentContexts) + let resolvedItem = {} + for nsTmp in split(a:namespace, '::') + let resolvedItem = s:ResolveNamespace(nsTmp, mapCurrentContextsTmp) + if resolvedItem.kind + " Note: We don't extend the map + let mapCurrentContextsTmp = {resolvedItem.value : 1} + else + break + endif + endfor + if resolvedItem!={} && resolvedItem.kind + let result.kind = 1 + let result.value = resolvedItem.value + endif + return result + endif + + " We get all possible parent contexts of this namespace + let listTagsOfNamespace = [] + if has_key(g:omni#cpp#namespaces#CacheResolve, a:namespace) + let listTagsOfNamespace = g:omni#cpp#namespaces#CacheResolve[a:namespace] + else + let listTagsOfNamespace = omni#common#utils#TagList('^'.a:namespace.'$') + let g:omni#cpp#namespaces#CacheResolve[a:namespace] = listTagsOfNamespace + endif + + if len(listTagsOfNamespace)==0 + return result + endif + call filter(listTagsOfNamespace, 'v:val.kind[0]=="n"') + + " We extract parent context from tags + " We use a map to avoid multiple entries + let mapContext = {} + for tagItem in listTagsOfNamespace + let szParentContext = omni#cpp#utils#ExtractScope(tagItem) + let mapContext[szParentContext] = 1 + endfor + let listParentContext = keys(mapContext) + + " Now for each parent context we test if the context is in the current + " contexts list + let listResolvedNamespace = [] + for szParentContext in listParentContext + if has_key(a:mapCurrentContexts, szParentContext) + call extend(listResolvedNamespace, [omni#cpp#utils#SimplifyScope(szParentContext.'::'.a:namespace)]) + endif + endfor + + " Now we know if the namespace is ambiguous or not + let len = len(listResolvedNamespace) + if len==1 + " Namespace resolved + let result.kind = 1 + let result.value = listResolvedNamespace[0] + elseif len > 1 + " Ambiguous namespace, possible matches are in listResolvedNamespace + else + " Other cases + endif + return result +endfunc + +" Resolve namespaces +"@return +" - List of resolved namespaces +function! omni#cpp#namespaces#ResolveAll(namespacesUsed) + + " We add the default context '::' + let contextOrder = 0 + let mapCurrentContexts = {} + + " For each namespace used: + " - We get all possible contexts where the namespace + " can be define + " - We do a comparison test of each parent contexts with the current + " context list + " - If one and only one parent context is present in the + " current context list we add the namespace in the current + " context + " - If there is more than one of parent contexts in the + " current context the namespace is ambiguous + for ns in a:namespacesUsed + let resolvedItem = s:ResolveNamespace(ns, mapCurrentContexts) + if resolvedItem.kind + let contextOrder+=1 + let mapCurrentContexts[resolvedItem.value] = contextOrder + endif + endfor + + " Build the list of current contexts from the map, we have to keep the + " order + let mapReorder = {} + for key in keys(mapCurrentContexts) + let mapReorder[ mapCurrentContexts[key] ] = key + endfor + let result = [] + for key in sort(keys(mapReorder)) + call extend(result, [mapReorder[key]]) + endfor + return result +endfunc + +" Build the context stack +function! s:BuildContextStack(namespaces, szCurrentScope) + let result = copy(a:namespaces) + if a:szCurrentScope != '::' + let tagItem = omni#cpp#utils#GetResolvedTagItem(a:namespaces, omni#cpp#utils#CreateTypeInfo(a:szCurrentScope)) + if has_key(tagItem, 'inherits') + let listBaseClass = omni#cpp#utils#GetClassInheritanceList(a:namespaces, omni#cpp#utils#CreateTypeInfo(a:szCurrentScope)) + let result = listBaseClass + result + elseif has_key(tagItem, 'kind') && index(['c', 's', 'u', 'n'], tagItem.kind[0])>=0 + call insert(result, omni#cpp#utils#ExtractTypeInfoFromTag(tagItem)) + endif + endif + return result +endfunc + +" Returns the class scope at the current position of the cursor +" @return a string that represents the class scope +" eg: ::NameSpace1::Class1 +" The returned string always starts with '::' +" Note: In term of performance it's the weak point of the script +function! s:GetClassScopeAtCursor() + " We store the cursor position because searchpairpos() moves the cursor + let originalPos = getpos('.') + let endPos = originalPos[1:2] + let listCode = [] + let result = {'namespaces': [], 'scope': ''} + + while endPos!=[0,0] + let endPos = searchpairpos('{', '', '}', 'bW', g:omni#cpp#utils#expIgnoreComments) + let szReStartPos = '[;{}]\|\%^' + let startPos = searchpairpos(szReStartPos, '', '{', 'bWn', g:omni#cpp#utils#expIgnoreComments) + + " If the file starts with a comment so the startPos can be [0,0] + " we change it to [1,1] + if startPos==[0,0] + let startPos = [1,1] + endif + + " Get lines backward from cursor position to last ; or { or } + " or when we are at the beginning of the file. + " We store lines in listCode + if endPos!=[0,0] + " We remove the last character which is a '{' + " We also remove starting { or } or ; if exits + let szCodeWithoutComments = substitute(omni#cpp#utils#GetCode(startPos, endPos)[:-2], '^[;{}]', '', 'g') + call insert(listCode, {'startLine' : startPos[0], 'code' : szCodeWithoutComments}) + endif + endwhile + " Setting the cursor to the original position + call setpos('.', originalPos) + + let listClassScope = [] + let bResolved = 0 + let startLine = 0 + " Now we can check in the list of code if there is a function + for code in listCode + " We get the name of the namespace, class, struct or union + " and we store it in listClassScope + let tokens = omni#cpp#tokenizer#Tokenize(code.code) + let bContinue=0 + let bAddNamespace = 0 + let state=0 + for token in tokens + if state==0 + if index(['namespace', 'class', 'struct', 'union'], token.value)>=0 + if token.value == 'namespace' + let bAddNamespace = 1 + endif + let state= 1 + " Maybe end of tokens + endif + elseif state==1 + if token.kind == 'cppWord' + " eg: namespace MyNs { class MyCl {}; } + " => listClassScope = [MyNs, MyCl] + call extend( listClassScope , [token.value] ) + + " Add the namespace in result + if bAddNamespace + call extend(result.namespaces, [token.value]) + let bAddNamespace = 0 + endif + + let bContinue=1 + break + endif + endif + endfor + if bContinue==1 + continue + endif + + " Simple test to check if we have a chance to find a + " class method + let aPos = matchend(code.code, '::\s*\~*\s*\w\+\s*(') + if aPos ==-1 + continue + endif + + let startLine = code.startLine + let listTmp = [] + " eg: 'void MyNamespace::MyClass::foo(' + " => tokens = ['MyClass', '::', 'MyNamespace', 'void'] + let tokens = reverse(omni#cpp#tokenizer#Tokenize(code.code[:aPos-1])[:-4]) + let state = 0 + " Reading tokens backward + for token in tokens + if state==0 + if token.kind=='cppWord' + call insert(listTmp, token.value) + let state=1 + endif + elseif state==1 + if token.value=='::' + let state=2 + else + break + endif + elseif state==2 + if token.kind=='cppWord' + call insert(listTmp, token.value) + let state=1 + else + break + endif + endif + endfor + + if len(listTmp) + if len(listClassScope) + let bResolved = 1 + " Merging class scopes + " eg: current class scope = 'MyNs::MyCl1' + " method class scope = 'MyCl1::MyCl2' + " If we add the method class scope to current class scope + " we'll have MyNs::MyCl1::MyCl1::MyCl2 => it's wrong + " we want MyNs::MyCl1::MyCl2 + let index = 0 + for methodClassScope in listTmp + if methodClassScope==listClassScope[-1] + let listTmp = listTmp[index+1:] + break + else + let index+=1 + endif + endfor + endif + call extend(listClassScope, listTmp) + break + endif + endfor + + let szClassScope = '::' + if len(listClassScope) + if bResolved + let szClassScope .= join(listClassScope, '::') + else + let szClassScope = join(listClassScope, '::') + + " The class scope is not resolved, we have to check using + " namespace declarations and search the class scope in each + " namespace + if startLine != 0 + let namespaces = ['::'] + omni#cpp#namespaces#GetListFromCurrentBuffer(startLine) + let namespaces = omni#cpp#namespaces#ResolveAll(namespaces) + let tagItem = omni#cpp#utils#GetResolvedTagItem(namespaces, omni#cpp#utils#CreateTypeInfo(szClassScope)) + if tagItem != {} + let szClassScope = omni#cpp#utils#ExtractTypeInfoFromTag(tagItem) + endif + endif + endif + endif + + let result.scope = szClassScope + return result +endfunc + +" Get all contexts at the cursor position +function! omni#cpp#namespaces#GetContexts() + " Get the current class scope at the cursor, the result depends on the current cursor position + let scopeItem = s:GetClassScopeAtCursor() + let listUsingNamespace = copy(g:OmniCpp_DefaultNamespaces) + call extend(listUsingNamespace, scopeItem.namespaces) + if g:OmniCpp_NamespaceSearch && &filetype != 'c' + " Get namespaces used in the file until the cursor position + let listUsingNamespace = omni#cpp#namespaces#GetUsingNamespaces() + listUsingNamespace + " Resolving namespaces, removing ambiguous namespaces + let namespaces = omni#cpp#namespaces#ResolveAll(listUsingNamespace) + else + let namespaces = ['::'] + listUsingNamespace + endif + call reverse(namespaces) + + " Building context stack from namespaces and the current class scope + return s:BuildContextStack(namespaces, scopeItem.scope) +endfunc diff --git a/VimFiles/autoload/omni/cpp/settings.vim b/VimFiles/autoload/omni/cpp/settings.vim new file mode 100755 index 0000000..6683d3a --- /dev/null +++ b/VimFiles/autoload/omni/cpp/settings.vim @@ -0,0 +1,96 @@ +" Description: Omni completion script for cpp files +" Maintainer: Vissale NEANG +" Last Change: 26 sept. 2007 + +function! omni#cpp#settings#Init() + " Global scope search on/off + " 0 = disabled + " 1 = enabled + if !exists('g:OmniCpp_GlobalScopeSearch') + let g:OmniCpp_GlobalScopeSearch = 1 + endif + + " Sets the namespace search method + " 0 = disabled + " 1 = search namespaces in the current file + " 2 = search namespaces in the current file and included files + if !exists('g:OmniCpp_NamespaceSearch') + let g:OmniCpp_NamespaceSearch = 1 + endif + + " Set the class scope completion mode + " 0 = auto + " 1 = show all members (static, public, protected and private) + if !exists('g:OmniCpp_DisplayMode') + let g:OmniCpp_DisplayMode = 0 + endif + + " Set if the scope is displayed in the abbr column of the popup + " 0 = no + " 1 = yes + if !exists('g:OmniCpp_ShowScopeInAbbr') + let g:OmniCpp_ShowScopeInAbbr = 0 + endif + + " Set if the function prototype is displayed in the abbr column of the popup + " 0 = no + " 1 = yes + if !exists('g:OmniCpp_ShowPrototypeInAbbr') + let g:OmniCpp_ShowPrototypeInAbbr = 0 + endif + + " Set if the access (+,#,-) is displayed + " 0 = no + " 1 = yes + if !exists('g:OmniCpp_ShowAccess') + let g:OmniCpp_ShowAccess = 1 + endif + + " Set the list of default namespaces + " eg: ['std'] + if !exists('g:OmniCpp_DefaultNamespaces') + let g:OmniCpp_DefaultNamespaces = [] + endif + + " Set MayComplete to '.' + " 0 = disabled + " 1 = enabled + " default = 1 + if !exists('g:OmniCpp_MayCompleteDot') + let g:OmniCpp_MayCompleteDot = 1 + endif + + " Set MayComplete to '->' + " 0 = disabled + " 1 = enabled + " default = 1 + if !exists('g:OmniCpp_MayCompleteArrow') + let g:OmniCpp_MayCompleteArrow = 1 + endif + + " Set MayComplete to dot + " 0 = disabled + " 1 = enabled + " default = 0 + if !exists('g:OmniCpp_MayCompleteScope') + let g:OmniCpp_MayCompleteScope = 0 + endif + + " When completeopt does not contain longest option, this setting + " controls the behaviour of the popup menu selection when starting the completion + " 0 = don't select first item + " 1 = select first item (inserting it to the text) + " 2 = select first item (without inserting it to the text) + " default = 0 + if !exists('g:OmniCpp_SelectFirstItem') + let g:OmniCpp_SelectFirstItem= 0 + endif + + " Use local search function for variable definitions + " 0 = use standard vim search function + " 1 = use local search function + " default = 0 + if !exists('g:OmniCpp_LocalSearchDecl') + let g:OmniCpp_LocalSearchDecl= 0 + endif +endfunc diff --git a/VimFiles/autoload/omni/cpp/tokenizer.vim b/VimFiles/autoload/omni/cpp/tokenizer.vim new file mode 100755 index 0000000..16e0be2 --- /dev/null +++ b/VimFiles/autoload/omni/cpp/tokenizer.vim @@ -0,0 +1,93 @@ +" Description: Omni completion tokenizer +" Maintainer: Vissale NEANG +" Last Change: 26 sept. 2007 +" TODO: Generic behaviour for Tokenize() + +" From the C++ BNF +let s:cppKeyword = ['asm', 'auto', 'bool', 'break', 'case', 'catch', 'char', 'class', 'const', 'const_cast', 'continue', 'default', 'delete', 'do', 'double', 'dynamic_cast', 'else', 'enum', 'explicit', 'export', 'extern', 'false', 'float', 'for', 'friend', 'goto', 'if', 'inline', 'int', 'long', 'mutable', 'namespace', 'new', 'operator', 'private', 'protected', 'public', 'register', 'reinterpret_cast', 'return', 'short', 'signed', 'sizeof', 'static', 'static_cast', 'struct', 'switch', 'template', 'this', 'throw', 'true', 'try', 'typedef', 'typeid', 'typename', 'union', 'unsigned', 'using', 'virtual', 'void', 'volatile', 'wchar_t', 'while', 'and', 'and_eq', 'bitand', 'bitor', 'compl', 'not', 'not_eq', 'or', 'or_eq', 'xor', 'xor_eq'] + +let s:reCppKeyword = '\C\<'.join(s:cppKeyword, '\>\|\<').'\>' + +" The order of items in this list is very important because we use this list to build a regular +" expression (see below) for tokenization +let s:cppOperatorPunctuator = ['->*', '->', '--', '-=', '-', '!=', '!', '##', '#', '%:%:', '%=', '%>', '%:', '%', '&&', '&=', '&', '(', ')', '*=', '*', ',', '...', '.*', '.', '/=', '/', '::', ':>', ':', ';', '?', '[', ']', '^=', '^', '{', '||', '|=', '|', '}', '~', '++', '+=', '+', '<<=', '<%', '<:', '<<', '<=', '<', '==', '=', '>>=', '>>', '>=', '>'] + +" We build the regexp for the tokenizer +let s:reCComment = '\/\*\|\*\/' +let s:reCppComment = '\/\/' +let s:reComment = s:reCComment.'\|'.s:reCppComment +let s:reCppOperatorOrPunctuator = escape(join(s:cppOperatorPunctuator, '\|'), '*./^~[]') + + +" Tokenize a c++ code +" a token is dictionary where keys are: +" - kind = cppKeyword|cppWord|cppOperatorPunctuator|unknown|cComment|cppComment|cppDigit +" - value = 'something' +" Note: a cppWord is any word that is not a cpp keyword +function! omni#cpp#tokenizer#Tokenize(szCode) + let result = [] + + " The regexp to find a token, a token is a keyword, word or + " c++ operator or punctuator. To work properly we have to put + " spaces and tabs to our regexp. + let reTokenSearch = '\(\w\+\)\|\s\+\|'.s:reComment.'\|'.s:reCppOperatorOrPunctuator + " eg: 'using namespace std;' + " ^ ^ + " start=0 end=5 + let startPos = 0 + let endPos = matchend(a:szCode, reTokenSearch) + let len = endPos-startPos + while endPos!=-1 + " eg: 'using namespace std;' + " ^ ^ + " start=0 end=5 + " token = 'using' + " We also remove space and tabs + let token = substitute(strpart(a:szCode, startPos, len), '\s', '', 'g') + + " eg: 'using namespace std;' + " ^ ^ + " start=5 end=15 + let startPos = endPos + let endPos = matchend(a:szCode, reTokenSearch, startPos) + let len = endPos-startPos + + " It the token is empty we continue + if token=='' + continue + endif + + " Building the token + let resultToken = {'kind' : 'unknown', 'value' : token} + + " Classify the token + if token =~ '^\d\+' + " It's a digit + let resultToken.kind = 'cppDigit' + elseif token=~'^\w\+$' + " It's a word + let resultToken.kind = 'cppWord' + + " But maybe it's a c++ keyword + if match(token, s:reCppKeyword)>=0 + let resultToken.kind = 'cppKeyword' + endif + else + if match(token, s:reComment)>=0 + if index(['/*','*/'],token)>=0 + let resultToken.kind = 'cComment' + else + let resultToken.kind = 'cppComment' + endif + else + " It's an operator + let resultToken.kind = 'cppOperatorPunctuator' + endif + endif + + " We have our token, let's add it to the result list + call extend(result, [resultToken]) + endwhile + + return result +endfunc diff --git a/VimFiles/autoload/omni/cpp/utils.vim b/VimFiles/autoload/omni/cpp/utils.vim new file mode 100755 index 0000000..5d74d34 --- /dev/null +++ b/VimFiles/autoload/omni/cpp/utils.vim @@ -0,0 +1,587 @@ +" Description: Omni completion script for cpp files +" Maintainer: Vissale NEANG +" Last Change: 26 sept. 2007 + +let g:omni#cpp#utils#CACHE_TAG_INHERITS = {} +let g:omni#cpp#utils#szFilterGlobalScope = "(!has_key(v:val, 'class') && !has_key(v:val, 'struct') && !has_key(v:val, 'union') && !has_key(v:val, 'namespace')" +let g:omni#cpp#utils#szFilterGlobalScope .= "&& (!has_key(v:val, 'enum') || (has_key(v:val, 'enum') && v:val.enum =~ '^\\w\\+$')))" + +" Expression used to ignore comments +" Note: this expression drop drastically the performance +"let omni#cpp#utils#expIgnoreComments = 'match(synIDattr(synID(line("."), col("."), 1), "name"), '\CcComment')!=-1' +" This one is faster but not really good for C comments +let omni#cpp#utils#reIgnoreComment = escape('\/\/\|\/\*\|\*\/', '*/\') +let omni#cpp#utils#expIgnoreComments = 'getline(".") =~ g:omni#cpp#utils#reIgnoreComment' + +" Characters to escape in a filename for vimgrep +"TODO: Find more characters to escape +let omni#cpp#utils#szEscapedCharacters = ' %#' + +" Resolve the path of the file +" TODO: absolute file path +function! omni#cpp#utils#ResolveFilePath(szFile) + let result = '' + let listPath = split(globpath(&path, a:szFile), "\n") + if len(listPath) + let result = listPath[0] + endif + return simplify(result) +endfunc + +" Get code without comments and with empty strings +" szSingleLine must not have carriage return +function! omni#cpp#utils#GetCodeFromLine(szSingleLine) + " We set all strings to empty strings, it's safer for + " the next of the process + let szResult = substitute(a:szSingleLine, '".*"', '""', 'g') + + " Removing c++ comments, we can use the pattern ".*" because + " we are modifying a line + let szResult = substitute(szResult, '\/\/.*', '', 'g') + + " Now we have the entire code in one line and we can remove C comments + return s:RemoveCComments(szResult) +endfunc + +" Remove C comments on a line +function! s:RemoveCComments(szLine) + let result = a:szLine + + " We have to match the first '/*' and first '*/' + let startCmt = match(result, '\/\*') + let endCmt = match(result, '\*\/') + while startCmt!=-1 && endCmt!=-1 && startCmt0 + let result = result[ : startCmt-1 ] . result[ endCmt+2 : ] + else + " Case where '/*' is at the start of the line + let result = result[ endCmt+2 : ] + endif + let startCmt = match(result, '\/\*') + let endCmt = match(result, '\*\/') + endwhile + return result +endfunc + +" Get a c++ code from current buffer from [lineStart, colStart] to +" [lineEnd, colEnd] without c++ and c comments, without end of line +" and with empty strings if any +" @return a string +function! omni#cpp#utils#GetCode(posStart, posEnd) + let posStart = a:posStart + let posEnd = a:posEnd + if a:posStart[0]>a:posEnd[0] + let posStart = a:posEnd + let posEnd = a:posStart + elseif a:posStart[0]==a:posEnd[0] && a:posStart[1]>a:posEnd[1] + let posStart = a:posEnd + let posEnd = a:posStart + endif + + " Getting the lines + let lines = getline(posStart[0], posEnd[0]) + let lenLines = len(lines) + + " Formatting the result + let result = '' + if lenLines==1 + let sStart = posStart[1]-1 + let sEnd = posEnd[1]-1 + let line = lines[0] + let lenLastLine = strlen(line) + let sEnd = (sEnd>lenLastLine)?lenLastLine : sEnd + if sStart >= 0 + let result = omni#cpp#utils#GetCodeFromLine(line[ sStart : sEnd ]) + endif + elseif lenLines>1 + let sStart = posStart[1]-1 + let sEnd = posEnd[1]-1 + let lenLastLine = strlen(lines[-1]) + let sEnd = (sEnd>lenLastLine)?lenLastLine : sEnd + if sStart >= 0 + let lines[0] = lines[0][ sStart : ] + let lines[-1] = lines[-1][ : sEnd ] + for aLine in lines + let result = result . omni#cpp#utils#GetCodeFromLine(aLine)." " + endfor + let result = result[:-2] + endif + endif + + " Now we have the entire code in one line and we can remove C comments + return s:RemoveCComments(result) +endfunc + +" Extract the scope (context) of a tag item +" eg: ::MyNamespace +" @return a string of the scope. a scope from tag always starts with '::' +function! omni#cpp#utils#ExtractScope(tagItem) + let listKindScope = ['class', 'struct', 'union', 'namespace', 'enum'] + let szResult = '::' + for scope in listKindScope + if has_key(a:tagItem, scope) + let szResult = szResult . a:tagItem[scope] + break + endif + endfor + return szResult +endfunc + +" Simplify scope string, remove consecutive '::' if any +function! omni#cpp#utils#SimplifyScope(szScope) + let szResult = substitute(a:szScope, '\(::\)\+', '::', 'g') + if szResult=='::' + return szResult + else + return substitute(szResult, '::$', '', 'g') + endif +endfunc + +" Check if the cursor is in comment +function! omni#cpp#utils#IsCursorInCommentOrString() + return match(synIDattr(synID(line("."), col(".")-1, 1), "name"), '\C\=0 +endfunc + +" Tokenize the current instruction until the cursor position. +" @return list of tokens +function! omni#cpp#utils#TokenizeCurrentInstruction(...) + let szAppendText = '' + if a:0>0 + let szAppendText = a:1 + endif + + let startPos = searchpos('[;{}]\|\%^', 'bWn') + let curPos = getpos('.')[1:2] + " We don't want the character under the cursor + let column = curPos[1]-1 + let curPos[1] = (column<1)?1:column + return omni#cpp#tokenizer#Tokenize(omni#cpp#utils#GetCode(startPos, curPos)[1:] . szAppendText) +endfunc + +" Tokenize the current instruction until the word under the cursor. +" @return list of tokens +function! omni#cpp#utils#TokenizeCurrentInstructionUntilWord() + let startPos = searchpos('[;{}]\|\%^', 'bWn') + + " Saving the current cursor pos + let originalPos = getpos('.') + + " We go at the end of the word + execute 'normal gee' + let curPos = getpos('.')[1:2] + + " Restoring the original cursor pos + call setpos('.', originalPos) + + let szCode = omni#cpp#utils#GetCode(startPos, curPos)[1:] + return omni#cpp#tokenizer#Tokenize(szCode) +endfunc + +" Build parenthesis groups +" add a new key 'group' in the token +" where value is the group number of the parenthesis +" eg: (void*)(MyClass*) +" group1 group0 +" if a parenthesis is unresolved the group id is -1 +" @return a copy of a:tokens with parenthesis group +function! omni#cpp#utils#BuildParenthesisGroups(tokens) + let tokens = copy(a:tokens) + let kinds = {'(': '()', ')' : '()', '[' : '[]', ']' : '[]', '<' : '<>', '>' : '<>', '{': '{}', '}': '{}'} + let unresolved = {'()' : [], '[]': [], '<>' : [], '{}' : []} + let groupId = 0 + + " Note: we build paren group in a backward way + " because we can often have parenthesis unbalanced + " instruction + " eg: doSomething(_member.get()-> + for token in reverse(tokens) + if index([')', ']', '>', '}'], token.value)>=0 + let token['group'] = groupId + call extend(unresolved[kinds[token.value]], [token]) + let groupId+=1 + elseif index(['(', '[', '<', '{'], token.value)>=0 + if len(unresolved[kinds[token.value]]) + let tokenResolved = remove(unresolved[kinds[token.value]], -1) + let token['group'] = tokenResolved.group + else + let token['group'] = -1 + endif + endif + endfor + + return reverse(tokens) +endfunc + +" Determine if tokens represent a C cast +" @return +" - itemCast +" - itemCppCast +" - itemVariable +" - itemThis +function! omni#cpp#utils#GetCastType(tokens) + " Note: a:tokens is not modified + let tokens = omni#cpp#utils#SimplifyParenthesis(omni#cpp#utils#BuildParenthesisGroups(a:tokens)) + + if tokens[0].value == '(' + return 'itemCast' + elseif index(['static_cast', 'dynamic_cast', 'reinterpret_cast', 'const_cast'], tokens[0].value)>=0 + return 'itemCppCast' + else + for token in tokens + if token.value=='this' + return 'itemThis' + endif + endfor + return 'itemVariable' + endif +endfunc + +" Remove useless parenthesis +function! omni#cpp#utils#SimplifyParenthesis(tokens) + "Note: a:tokens is not modified + let tokens = a:tokens + " We remove useless parenthesis eg: (((MyClass))) + if len(tokens)>2 + while tokens[0].value=='(' && tokens[-1].value==')' && tokens[0].group==tokens[-1].group + let tokens = tokens[1:-2] + endwhile + endif + return tokens +endfunc + +" Function create a type info +function! omni#cpp#utils#CreateTypeInfo(param) + let type = type(a:param) + return {'type': type, 'value':a:param} +endfunc + +" Extract type info from a tag item +" eg: ::MyNamespace::MyClass +function! omni#cpp#utils#ExtractTypeInfoFromTag(tagItem) + let szTypeInfo = omni#cpp#utils#ExtractScope(a:tagItem) . '::' . substitute(a:tagItem.name, '.*::', '', 'g') + return omni#cpp#utils#SimplifyScope(szTypeInfo) +endfunc + +" Build a class inheritance list +function! omni#cpp#utils#GetClassInheritanceList(namespaces, typeInfo) + let result = [] + for tagItem in omni#cpp#utils#GetResolvedTags(a:namespaces, a:typeInfo) + call extend(result, [omni#cpp#utils#ExtractTypeInfoFromTag(tagItem)]) + endfor + return result +endfunc + +" Get class inheritance list where items in the list are tag items. +" TODO: Verify inheritance order +function! omni#cpp#utils#GetResolvedTags(namespaces, typeInfo) + let result = [] + let tagItem = omni#cpp#utils#GetResolvedTagItem(a:namespaces, a:typeInfo) + if tagItem!={} + let szTypeInfo = omni#cpp#utils#ExtractTypeInfoFromTag(tagItem) + if has_key(g:omni#cpp#utils#CACHE_TAG_INHERITS, szTypeInfo) + let result = g:omni#cpp#utils#CACHE_TAG_INHERITS[szTypeInfo] + else + call extend(result, [tagItem]) + if has_key(tagItem, 'inherits') + for baseClassTypeInfo in split(tagItem.inherits, ',') + let namespaces = [omni#cpp#utils#ExtractScope(tagItem), '::'] + call extend(result, omni#cpp#utils#GetResolvedTags(namespaces, omni#cpp#utils#CreateTypeInfo(baseClassTypeInfo))) + endfor + endif + let g:omni#cpp#utils#CACHE_TAG_INHERITS[szTypeInfo] = result + endif + endif + return result +endfunc + +" Get a tag item after a scope resolution and typedef resolution +function! omni#cpp#utils#GetResolvedTagItem(namespaces, typeInfo) + let typeInfo = {} + if type(a:typeInfo) == 1 + let typeInfo = omni#cpp#utils#CreateTypeInfo(a:typeInfo) + else + let typeInfo = a:typeInfo + endif + + let result = {} + if !omni#cpp#utils#IsTypeInfoValid(typeInfo) + return result + endif + + " Unnamed type case eg: '1::2' + if typeInfo.type == 4 + " Here there is no typedef or namespace to resolve, the tagInfo.value is a tag item + " representing a variable ('v') a member ('m') or a typedef ('t') and the typename is + " always in global scope + return typeInfo.value + endif + + " Named type case eg: 'MyNamespace::MyClass' + let szTypeInfo = omni#cpp#utils#GetTypeInfoString(typeInfo) + + " Resolving namespace alias + " TODO: For the next release + "let szTypeInfo = omni#cpp#namespaces#ResolveAlias(g:omni#cpp#namespaces#CacheAlias, szTypeInfo) + + if szTypeInfo=='::' + return result + endif + + " We can only get members of class, struct, union and namespace + let szTagFilter = "index(['c', 's', 'u', 'n', 't'], v:val.kind[0])>=0" + let szTagQuery = szTypeInfo + + if s:IsTypeInfoResolved(szTypeInfo) + " The type info is already resolved, we remove the starting '::' + let szTagQuery = substitute(szTypeInfo, '^::', '', 'g') + if len(split(szTagQuery, '::'))==1 + " eg: ::MyClass + " Here we have to get tags that have no parent scope + " That's why we change the szTagFilter + let szTagFilter .= '&& ' . g:omni#cpp#utils#szFilterGlobalScope + let tagList = omni#common#utils#TagListNoThrow('^'.szTagQuery.'$') + call filter(tagList, szTagFilter) + if len(tagList) + let result = tagList[0] + endif + else + " eg: ::MyNamespace::MyClass + let tagList = omni#common#utils#TagListNoThrow('^'.szTagQuery.'$') + call filter(tagList, szTagFilter) + + if len(tagList) + let result = tagList[0] + endif + endif + else + " The type is not resolved + let tagList = omni#common#utils#TagListNoThrow('^'.szTagQuery.'$') + call filter(tagList, szTagFilter) + + if len(tagList) + " Resolving scope (namespace, nested class etc...) + let szScopeOfTypeInfo = s:ExtractScopeFromTypeInfo(szTypeInfo) + if s:IsTypeInfoResolved(szTypeInfo) + let result = s:GetTagOfSameScope(tagList, szScopeOfTypeInfo) + else + " For each namespace of the namespace list we try to get a tag + " that can be in the same scope + if g:OmniCpp_NamespaceSearch && &filetype != 'c' + for scope in a:namespaces + let szTmpScope = omni#cpp#utils#SimplifyScope(scope.'::'.szScopeOfTypeInfo) + let result = s:GetTagOfSameScope(tagList, szTmpScope) + if result!={} + break + endif + endfor + else + let szTmpScope = omni#cpp#utils#SimplifyScope('::'.szScopeOfTypeInfo) + let result = s:GetTagOfSameScope(tagList, szTmpScope) + endif + endif + endif + endif + + if result!={} + " We have our tagItem but maybe it's a typedef or an unnamed type + if result.kind[0]=='t' + " Here we can have a typedef to another typedef, a class, struct, union etc + " but we can also have a typedef to an unnamed type, in that + " case the result contains a 'typeref' key + let namespaces = [omni#cpp#utils#ExtractScope(result), '::'] + if has_key(result, 'typeref') + let result = omni#cpp#utils#GetResolvedTagItem(namespaces, omni#cpp#utils#CreateTypeInfo(result)) + else + let szCmd = omni#cpp#utils#ExtractCmdFromTagItem(result) + let szCode = substitute(omni#cpp#utils#GetCodeFromLine(szCmd), '\C\<'.result.name.'\>.*', '', 'g') + let szTypeInfo = omni#cpp#utils#ExtractTypeInfoFromTokens(omni#cpp#tokenizer#Tokenize(szCode)) + let result = omni#cpp#utils#GetResolvedTagItem(namespaces, omni#cpp#utils#CreateTypeInfo(szTypeInfo)) + " TODO: Namespace resolution for result + endif + endif + endif + + return result +endfunc + +" Returns if the type info is valid +" @return +" - 1 if valid +" - 0 otherwise +function! omni#cpp#utils#IsTypeInfoValid(typeInfo) + if a:typeInfo=={} + return 0 + else + if a:typeInfo.type == 1 && a:typeInfo.value=='' + " String case + return 0 + elseif a:typeInfo.type == 4 && a:typeInfo.value=={} + " Dictionary case + return 0 + endif + endif + return 1 +endfunc + +" Get the string of the type info +function! omni#cpp#utils#GetTypeInfoString(typeInfo) + if a:typeInfo.type == 1 + return a:typeInfo.value + else + return substitute(a:typeInfo.value.typeref, '^\w\+:', '', 'g') + endif +endfunc + +" A resolved type info starts with '::' +" @return +" - 1 if type info starts with '::' +" - 0 otherwise +function! s:IsTypeInfoResolved(szTypeInfo) + return match(a:szTypeInfo, '^::')!=-1 +endfunc + +" A returned type info's scope may not have the global namespace '::' +" eg: '::NameSpace1::NameSpace2::MyClass' => '::NameSpace1::NameSpace2' +" 'NameSpace1::NameSpace2::MyClass' => 'NameSpace1::NameSpace2' +function! s:ExtractScopeFromTypeInfo(szTypeInfo) + let szScope = substitute(a:szTypeInfo, '\w\+$', '', 'g') + if szScope =='::' + return szScope + else + return substitute(szScope, '::$', '', 'g') + endif +endfunc + +" @return +" - the tag with the same scope +" - {} otherwise +function! s:GetTagOfSameScope(listTags, szScopeToMatch) + for tagItem in a:listTags + let szScopeOfTag = omni#cpp#utils#ExtractScope(tagItem) + if szScopeOfTag == a:szScopeToMatch + return tagItem + endif + endfor + return {} +endfunc + +" Extract the cmd of a tag item without regexp +function! omni#cpp#utils#ExtractCmdFromTagItem(tagItem) + let line = a:tagItem.cmd + let re = '\(\/\^\)\|\(\$\/\)' + if match(line, re)!=-1 + let line = substitute(line, re, '', 'g') + return line + else + " TODO: the cmd is a line number + return '' + endif +endfunc + +" Extract type from tokens. +" eg: examples of tokens format +" 'const MyClass&' +" 'const map < int, int >&' +" 'MyNs::MyClass' +" '::MyClass**' +" 'MyClass a, *b = NULL, c[1] = {}; +" 'hello(MyClass a, MyClass* b' +" @return the type info string eg: ::std::map +" can be empty +function! omni#cpp#utils#ExtractTypeInfoFromTokens(tokens) + let szResult = '' + let state = 0 + + let tokens = omni#cpp#utils#BuildParenthesisGroups(a:tokens) + + " If there is an unbalanced parenthesis we are in a parameter list + let bParameterList = 0 + for token in tokens + if token.value == '(' && token.group==-1 + let bParameterList = 1 + break + endif + endfor + + if bParameterList + let tokens = reverse(tokens) + let state = 0 + let parenGroup = -1 + for token in tokens + if state==0 + if token.value=='>' + let parenGroup = token.group + let state=1 + elseif token.kind == 'cppWord' + let szResult = token.value.szResult + let state=2 + elseif index(['*', '&'], token.value)<0 + break + endif + elseif state==1 + if token.value=='<' && token.group==parenGroup + let state=0 + endif + elseif state==2 + if token.value=='::' + let szResult = token.value.szResult + let state=3 + else + break + endif + elseif state==3 + if token.kind == 'cppWord' + let szResult = token.value.szResult + let state=2 + else + break + endif + endif + endfor + return szResult + endif + + for token in tokens + if state==0 + if token.value == '::' + let szResult .= token.value + let state = 1 + elseif token.kind == 'cppWord' + let szResult .= token.value + let state = 2 + " Maybe end of token + endif + elseif state==1 + if token.kind == 'cppWord' + let szResult .= token.value + let state = 2 + " Maybe end of token + else + break + endif + elseif state==2 + if token.value == '::' + let szResult .= token.value + let state = 1 + else + break + endif + endif + endfor + return szResult +endfunc + +" Get the preview window string +function! omni#cpp#utils#GetPreviewWindowStringFromTagItem(tagItem) + let szResult = '' + + let szResult .= 'name: '.a:tagItem.name."\n" + for tagKey in keys(a:tagItem) + if index(['name', 'static'], tagKey)>=0 + continue + endif + let szResult .= tagKey.': '.a:tagItem[tagKey]."\n" + endfor + + return substitute(szResult, "\n$", '', 'g') +endfunc diff --git a/VimFiles/autoload/omni/common/debug.vim b/VimFiles/autoload/omni/common/debug.vim new file mode 100755 index 0000000..eded649 --- /dev/null +++ b/VimFiles/autoload/omni/common/debug.vim @@ -0,0 +1,32 @@ +" Description: Omni completion debug functions +" Maintainer: Vissale NEANG +" Last Change: 26 sept. 2007 + +let s:CACHE_DEBUG_TRACE = [] + +" Start debug, clear the debug file +function! omni#common#debug#Start() + let s:CACHE_DEBUG_TRACE = [] + call extend(s:CACHE_DEBUG_TRACE, ['============ Debug Start ============']) + call writefile(s:CACHE_DEBUG_TRACE, "Omni.dbg") +endfunc + +" End debug, write to debug file +function! omni#common#debug#End() + call extend(s:CACHE_DEBUG_TRACE, ["============= Debug End ============="]) + call extend(s:CACHE_DEBUG_TRACE, [""]) + call writefile(s:CACHE_DEBUG_TRACE, "Omni.dbg") +endfunc + +" Debug trace function +function! omni#common#debug#Trace(szFuncName, ...) + let szTrace = a:szFuncName + let paramNum = a:0 + if paramNum>0 + let szTrace .= ':' + endif + for i in range(paramNum) + let szTrace = szTrace .' ('. string(eval('a:'.string(i+1))).')' + endfor + call extend(s:CACHE_DEBUG_TRACE, [szTrace]) +endfunc diff --git a/VimFiles/autoload/omni/common/utils.vim b/VimFiles/autoload/omni/common/utils.vim new file mode 100755 index 0000000..c880ad2 --- /dev/null +++ b/VimFiles/autoload/omni/common/utils.vim @@ -0,0 +1,67 @@ +" Description: Omni completion utils +" Maintainer: Vissale NEANG +" Last Change: 26 sept. 2007 + +" For sort numbers in list +function! omni#common#utils#CompareNumber(i1, i2) + let num1 = eval(a:i1) + let num2 = eval(a:i2) + return num1 == num2 ? 0 : num1 > num2 ? 1 : -1 +endfunc + +" TagList function calling the vim taglist() with try catch +" The only throwed exception is 'TagList:UserInterrupt' +" We also force the noignorecase option to avoid linear search when calling +" taglist() +function! omni#common#utils#TagList(szTagQuery) + let result = [] + let bUserIgnoreCase = &ignorecase + " Forcing noignorecase search => binary search can be used in taglist() + " if tags in the tag file are sorted + if bUserIgnoreCase + set noignorecase + endif + try + let result = taglist(a:szTagQuery) + catch /^Vim:Interrupt$/ + " Restoring user's setting + if bUserIgnoreCase + set ignorecase + endif + throw 'TagList:UserInterrupt' + catch + "Note: it seems that ctags can generate corrupted files, in this case + "taglist() will fail to read the tagfile and an exception from + "has_add() is thrown + endtry + + " Restoring user's setting + if bUserIgnoreCase + set ignorecase + endif + return result +endfunc + +" Same as TagList but don't throw exception +function! omni#common#utils#TagListNoThrow(szTagQuery) + let result = [] + try + let result = omni#common#utils#TagList(a:szTagQuery) + catch + endtry + return result +endfunc + +" Get the word under the cursor +function! omni#common#utils#GetWordUnderCursor() + let szLine = getline('.') + let startPos = getpos('.')[2]-1 + let startPos = (startPos < 0)? 0 : startPos + if szLine[startPos] =~ '\w' + let startPos = searchpos('\<\w\+', 'cbn', line('.'))[1] - 1 + endif + + let startPos = (startPos < 0)? 0 : startPos + let szResult = matchstr(szLine, '\w\+', startPos) + return szResult +endfunc diff --git a/VimFiles/autoload/omni/cpp/complete.vim b/VimFiles/autoload/omni/cpp/complete.vim new file mode 100755 index 0000000..a7e4edc --- /dev/null +++ b/VimFiles/autoload/omni/cpp/complete.vim @@ -0,0 +1,569 @@ +" 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 diff --git a/VimFiles/autoload/omni/cpp/includes.vim b/VimFiles/autoload/omni/cpp/includes.vim new file mode 100755 index 0000000..10a89bc --- /dev/null +++ b/VimFiles/autoload/omni/cpp/includes.vim @@ -0,0 +1,126 @@ +" Description: Omni completion script for cpp files +" Maintainer: Vissale NEANG +" Last Change: 26 sept. 2007 + +let g:omni#cpp#includes#CACHE_INCLUDES = {} +let g:omni#cpp#includes#CACHE_FILE_TIME = {} + +let s:rePreprocIncludePart = '\C#\s*include\s*' +let s:reIncludeFilePart = '\(<\|"\)\(\f\|\s\)\+\(>\|"\)' +let s:rePreprocIncludeFile = s:rePreprocIncludePart . s:reIncludeFilePart + +" Get the include list of a file +function! omni#cpp#includes#GetList(...) + if a:0 > 0 + return s:GetIncludeListFromFile(a:1, (a:0 > 1)? a:2 : 0 ) + else + return s:GetIncludeListFromCurrentBuffer() + endif +endfunc + +" Get the include list from the current buffer +function! s:GetIncludeListFromCurrentBuffer() + let listIncludes = [] + let originalPos = getpos('.') + + call setpos('.', [0, 1, 1, 0]) + let curPos = [1,1] + let alreadyInclude = {} + while curPos != [0,0] + let curPos = searchpos('\C\(^'.s:rePreprocIncludeFile.'\)', 'W') + if curPos != [0,0] + let szLine = getline('.') + let startPos = curPos[1] + let endPos = matchend(szLine, s:reIncludeFilePart, startPos-1) + if endPos!=-1 + let szInclusion = szLine[startPos-1:endPos-1] + let szIncludeFile = substitute(szInclusion, '\('.s:rePreprocIncludePart.'\)\|[<>""]', '', 'g') + let szResolvedInclude = omni#cpp#utils#ResolveFilePath(szIncludeFile) + + " Protection over self inclusion + if szResolvedInclude != '' && szResolvedInclude != omni#cpp#utils#ResolveFilePath(getreg('%')) + let includePos = curPos + if !has_key(alreadyInclude, szResolvedInclude) + call extend(listIncludes, [{'pos' : includePos, 'include' : szResolvedInclude}]) + let alreadyInclude[szResolvedInclude] = 1 + endif + endif + endif + endif + endwhile + + call setpos('.', originalPos) + return listIncludes +endfunc + +" Get the include list from a file +function! s:GetIncludeListFromFile(szFilePath, bUpdate) + let listIncludes = [] + if a:szFilePath == '' + return listIncludes + endif + + if !a:bUpdate && has_key(g:omni#cpp#includes#CACHE_INCLUDES, a:szFilePath) + return copy(g:omni#cpp#includes#CACHE_INCLUDES[a:szFilePath]) + endif + + let g:omni#cpp#includes#CACHE_FILE_TIME[a:szFilePath] = getftime(a:szFilePath) + + let szFixedPath = escape(a:szFilePath, g:omni#cpp#utils#szEscapedCharacters) + execute 'silent! lvimgrep /\C\(^'.s:rePreprocIncludeFile.'\)/gj '.szFixedPath + + let listQuickFix = getloclist(0) + let alreadyInclude = {} + for qf in listQuickFix + let szLine = qf.text + let startPos = qf.col + let endPos = matchend(szLine, s:reIncludeFilePart, startPos-1) + if endPos!=-1 + let szInclusion = szLine[startPos-1:endPos-1] + let szIncludeFile = substitute(szInclusion, '\('.s:rePreprocIncludePart.'\)\|[<>""]', '', 'g') + let szResolvedInclude = omni#cpp#utils#ResolveFilePath(szIncludeFile) + + " Protection over self inclusion + if szResolvedInclude != '' && szResolvedInclude != a:szFilePath + let includePos = [qf.lnum, qf.col] + if !has_key(alreadyInclude, szResolvedInclude) + call extend(listIncludes, [{'pos' : includePos, 'include' : szResolvedInclude}]) + let alreadyInclude[szResolvedInclude] = 1 + endif + endif + endif + endfor + + let g:omni#cpp#includes#CACHE_INCLUDES[a:szFilePath] = listIncludes + + return copy(listIncludes) +endfunc + +" For debug purpose +function! omni#cpp#includes#Display() + let szPathBuffer = omni#cpp#utils#ResolveFilePath(getreg('%')) + call s:DisplayIncludeTree(szPathBuffer, 0) +endfunc + +" For debug purpose +function! s:DisplayIncludeTree(szFilePath, indent, ...) + let includeGuard = {} + if a:0 >0 + let includeGuard = a:1 + endif + let szFilePath = omni#cpp#utils#ResolveFilePath(a:szFilePath) + if has_key(includeGuard, szFilePath) + return + else + let includeGuard[szFilePath] = 1 + endif + + let szIndent = repeat(' ', a:indent) + echo szIndent . a:szFilePath + let incList = omni#cpp#includes#GetList(a:szFilePath) + for inc in incList + call s:DisplayIncludeTree(inc.include, a:indent+1, includeGuard) + endfor +endfunc + + diff --git a/VimFiles/autoload/omni/cpp/items.vim b/VimFiles/autoload/omni/cpp/items.vim new file mode 100755 index 0000000..b943ad4 --- /dev/null +++ b/VimFiles/autoload/omni/cpp/items.vim @@ -0,0 +1,660 @@ +" Description: Omni completion script for cpp files +" Maintainer: Vissale NEANG +" Last Change: 26 sept. 2007 + +" Build the item list of an instruction +" An item is an instruction between a -> or . or ->* or .* +" We can sort an item in different kinds: +" eg: ((MyClass1*)(pObject))->_memberOfClass1.get() ->show() +" | cast | | member | | method | | method | +" @return a list of item +" an item is a dictionnary where keys are: +" tokens = list of token +" kind = itemVariable|itemCast|itemCppCast|itemTemplate|itemFunction|itemUnknown|itemThis|itemScope +function! omni#cpp#items#Get(tokens, ...) + let bGetWordUnderCursor = (a:0>0)? a:1 : 0 + + let result = [] + let itemsDelimiters = ['->', '.', '->*', '.*'] + + let tokens = reverse(omni#cpp#utils#BuildParenthesisGroups(a:tokens)) + + " fsm states: + " 0 = initial state + " TODO: add description of fsm states + let state=(bGetWordUnderCursor)? 1 : 0 + let item = {'tokens' : [], 'kind' : 'itemUnknown'} + let parenGroup=-1 + for token in tokens + if state==0 + if index(itemsDelimiters, token.value)>=0 + let item = {'tokens' : [], 'kind' : 'itemUnknown'} + let state = 1 + elseif token.value=='::' + let state = 9 + let item.kind = 'itemScope' + " Maybe end of tokens + elseif token.kind =='cppOperatorPunctuator' + " If it's a cppOperatorPunctuator and the current token is not + " a itemsDelimiters or '::' we can exit + let state=-1 + break + endif + elseif state==1 + call insert(item.tokens, token) + if token.kind=='cppWord' + " It's an attribute member or a variable + let item.kind = 'itemVariable' + let state = 2 + " Maybe end of tokens + elseif token.value=='this' + let item.kind = 'itemThis' + let state = 2 + " Maybe end of tokens + elseif token.value==')' + let parenGroup = token.group + let state = 3 + elseif token.value==']' + let parenGroup = token.group + let state = 4 + elseif token.kind == 'cppDigit' + let state = -1 + break + endif + elseif state==2 + if index(itemsDelimiters, token.value)>=0 + call insert(result, item) + let item = {'tokens' : [], 'kind' : 'itemUnknown'} + let state = 1 + elseif token.value == '::' + call insert(item.tokens, token) + " We have to get namespace or classscope + let state = 8 + " Maybe end of tokens + else + call insert(result, item) + let state=-1 + break + endif + elseif state==3 + call insert(item.tokens, token) + if token.value=='(' && token.group == parenGroup + let state = 5 + " Maybe end of tokens + endif + elseif state==4 + call insert(item.tokens, token) + if token.value=='[' && token.group == parenGroup + let state = 1 + endif + elseif state==5 + if token.kind=='cppWord' + " It's a function or method + let item.kind = 'itemFunction' + call insert(item.tokens, token) + let state = 2 + " Maybe end of tokens + elseif token.value == '>' + " Maybe a cpp cast or template + let item.kind = 'itemTemplate' + call insert(item.tokens, token) + let parenGroup = token.group + let state = 6 + else + " Perhaps it's a C cast eg: ((void*)(pData)) or a variable eg:(*pData) + let item.kind = omni#cpp#utils#GetCastType(item.tokens) + let state=-1 + call insert(result, item) + break + endif + elseif state==6 + call insert(item.tokens, token) + if token.value == '<' && token.group == parenGroup + " Maybe a cpp cast or template + let state = 7 + endif + elseif state==7 + call insert(item.tokens, token) + if token.kind=='cppKeyword' + " It's a cpp cast + let item.kind = omni#cpp#utils#GetCastType(item.tokens) + let state=-1 + call insert(result, item) + break + else + " Template ? + let state=-1 + call insert(result, item) + break + endif + elseif state==8 + if token.kind=='cppWord' + call insert(item.tokens, token) + let state = 2 + " Maybe end of tokens + else + let state=-1 + call insert(result, item) + break + endif + elseif state==9 + if token.kind == 'cppWord' + call insert(item.tokens, token) + let state = 10 + " Maybe end of tokens + else + let state=-1 + call insert(result, item) + break + endif + elseif state==10 + if token.value == '::' + call insert(item.tokens, token) + let state = 9 + " Maybe end of tokens + else + let state=-1 + call insert(result, item) + break + endif + endif + endfor + + if index([2, 5, 8, 9, 10], state)>=0 + if state==5 + let item.kind = omni#cpp#utils#GetCastType(item.tokens) + endif + call insert(result, item) + endif + + return result +endfunc + +" Resolve type information of items +" @param namespaces: list of namespaces used in the file +" @param szCurrentClassScope: the current class scope, only used for the first +" item to detect if this item is a class member (attribute, method) +" @param items: list of item, can be an empty list @see GetItemsToComplete +function! omni#cpp#items#ResolveItemsTypeInfo(contextStack, items) + " Note: kind = itemVariable|cCast|cppCast|template|function|itemUnknown|this + " For the first item, if it's a variable we try to detect the type of the + " variable with the function searchdecl. If it fails, thanks to the + " current class scope, we try to detect if the variable is an attribute + " member. + " If the kind of the item is a function, we have to first check if the + " function is a method of the class, if it fails we try to get a match in + " the global namespace. After that we get the returned type of the + " function. + " It the kind is a C cast or C++ cast, there is no problem, it's the + " easiest case. We just extract the type of the cast. + + let szCurrentContext = '' + let typeInfo = {} + " Note: We search the decl only for the first item + let bSearchDecl = 1 + for item in a:items + let curItem = item + if index(['itemVariable', 'itemFunction'], curItem.kind)>=0 + " Note: a variable can be : MyNs::MyClass::_var or _var or (*pVar) + " or _var[0][0] + let szSymbol = s:GetSymbol(curItem.tokens) + + " If we have MyNamespace::myVar + " We add MyNamespace in the context stack set szSymbol to myVar + if match(szSymbol, '::\w\+$') >= 0 + let szCurrentContext = substitute(szSymbol, '::\w\+$', '', 'g') + let szSymbol = matchstr(szSymbol, '\w\+$') + endif + let tmpContextStack = a:contextStack + if szCurrentContext != '' + let tmpContextStack = [szCurrentContext] + a:contextStack + endif + + if curItem.kind == 'itemVariable' + let typeInfo = s:GetTypeInfoOfVariable(tmpContextStack, szSymbol, bSearchDecl) + else + let typeInfo = s:GetTypeInfoOfReturnedType(tmpContextStack, szSymbol) + endif + + elseif curItem.kind == 'itemThis' + if len(a:contextStack) + let typeInfo = omni#cpp#utils#CreateTypeInfo(substitute(a:contextStack[0], '^::', '', 'g')) + endif + elseif curItem.kind == 'itemCast' + let typeInfo = omni#cpp#utils#CreateTypeInfo(s:ResolveCCast(curItem.tokens)) + elseif curItem.kind == 'itemCppCast' + let typeInfo = omni#cpp#utils#CreateTypeInfo(s:ResolveCppCast(curItem.tokens)) + elseif curItem.kind == 'itemScope' + let typeInfo = omni#cpp#utils#CreateTypeInfo(substitute(s:TokensToString(curItem.tokens), '\s', '', 'g')) + endif + + if omni#cpp#utils#IsTypeInfoValid(typeInfo) + let szCurrentContext = omni#cpp#utils#GetTypeInfoString(typeInfo) + endif + let bSearchDecl = 0 + endfor + + return typeInfo +endfunc + +" Get symbol name +function! s:GetSymbol(tokens) + let szSymbol = '' + let state = 0 + for token in a:tokens + if state == 0 + if token.value == '::' + let szSymbol .= token.value + let state = 1 + elseif token.kind == 'cppWord' + let szSymbol .= token.value + let state = 2 + " Maybe end of token + endif + elseif state == 1 + if token.kind == 'cppWord' + let szSymbol .= token.value + let state = 2 + " Maybe end of token + else + " Error + break + endif + elseif state == 2 + if token.value == '::' + let szSymbol .= token.value + let state = 1 + else + break + endif + endif + endfor + return szSymbol +endfunc + +" Search a declaration. +" eg: std::map +" can be empty +" Note: The returned type info can be a typedef +" The typedef resolution is done later +" @return +" - a dictionnary where keys are +" - type: the type of value same as type() +" - value: the value +function! s:GetTypeInfoOfVariable(contextStack, szVariable, bSearchDecl) + let result = {} + + if a:bSearchDecl + " Search type of declaration + "let result = s:SearchTypeInfoOfDecl(a:szVariable) + let result = s:SearchDecl(a:szVariable) + endif + + if result=={} + let szFilter = "index(['m', 'v'], v:val.kind[0])>=0" + let tagItem = s:ResolveSymbol(a:contextStack, a:szVariable, szFilter) + if tagItem=={} + return result + endif + + let szCmdWithoutVariable = substitute(omni#cpp#utils#ExtractCmdFromTagItem(tagItem), '\C\<'.a:szVariable.'\>.*', '', 'g') + let tokens = omni#cpp#tokenizer#Tokenize(omni#cpp#utils#GetCodeFromLine(szCmdWithoutVariable)) + let result = omni#cpp#utils#CreateTypeInfo(omni#cpp#utils#ExtractTypeInfoFromTokens(tokens)) + " TODO: Namespace resolution for result + + if result != {} && result.value=='' + " result.value=='' + " eg: + " struct + " { + " }gVariable; + if has_key(tagItem, 'typeref') + " Maybe the variable is a global var of an + " unnamed class, struct or union. + " eg: + " 1) + " struct + " { + " }gVariable; + " In this case we need the tags (the patched version) + " Note: We can have a named type like this: + " 2) + " class A + " { + " }gVariable; + if s:IsUnnamedType(tagItem) + " It's an unnamed type we are in the case 1) + let result = omni#cpp#utils#CreateTypeInfo(tagItem) + else + " It's not an unnamed type we are in the case 2) + + " eg: tagItem.typeref = 'struct:MY_STRUCT::MY_SUBSTRUCT' + let szTypeRef = substitute(tagItem.typeref, '^\w\+:', '', '') + + " eg: szTypeRef = 'MY_STRUCT::MY_SUBSTRUCT' + let result = omni#cpp#utils#CreateTypeInfo(szTypeRef) + endif + endif + endif + endif + return result +endfunc + +" Get the type info string from the returned type of function +function! s:GetTypeInfoOfReturnedType(contextStack, szFunctionName) + let result = {} + + let szFilter = "index(['f', 'p'], v:val.kind[0])>=0" + let tagItem = s:ResolveSymbol(a:contextStack, a:szFunctionName, szFilter) + + if tagItem != {} + let szCmdWithoutVariable = substitute(omni#cpp#utils#ExtractCmdFromTagItem(tagItem), '\C\<'.a:szFunctionName.'\>.*', '', 'g') + let tokens = omni#cpp#tokenizer#Tokenize(omni#cpp#utils#GetCodeFromLine(szCmdWithoutVariable)) + let result = omni#cpp#utils#CreateTypeInfo(omni#cpp#utils#ExtractTypeInfoFromTokens(tokens)) + " TODO: Namespace resolution for result + return result + endif + return result +endfunc + +" Resolve a symbol, return a tagItem +" Gets the first symbol found in the context stack +function! s:ResolveSymbol(contextStack, szSymbol, szTagFilter) + let tagItem = {} + for szCurrentContext in a:contextStack + if szCurrentContext != '::' + let szTagQuery = substitute(szCurrentContext, '^::', '', 'g').'::'.a:szSymbol + else + let szTagQuery = a:szSymbol + endif + + let tagList = omni#common#utils#TagListNoThrow('^'.szTagQuery.'$') + call filter(tagList, a:szTagFilter) + if len(tagList) + let tagItem = tagList[0] + break + endif + endfor + return tagItem +endfunc + +" Return if the tag item represent an unnamed type +function! s:IsUnnamedType(tagItem) + let bResult = 0 + if has_key(a:tagItem, 'typeref') + " Note: Thanks for __anon ! + let bResult = match(a:tagItem.typeref, '\C\<__anon') >= 0 + endif + return bResult +endfunc + +" Search the declaration of a variable and return the type info +function! s:SearchTypeInfoOfDecl(szVariable) + let szReVariable = '\C\<'.a:szVariable.'\>' + + let originalPos = getpos('.') + let origPos = originalPos[1:2] + let curPos = origPos + let stopPos = origPos + + while curPos !=[0,0] + " We go to the start of the current scope + let curPos = searchpairpos('{', '', '}', 'bW', g:omni#cpp#utils#expIgnoreComments) + if curPos != [0,0] + let matchPos = curPos + " Now want to search our variable but we don't want to go in child + " scope + while matchPos != [0,0] + let matchPos = searchpos('{\|'.szReVariable, 'W', stopPos[0]) + if matchPos != [0,0] + " We ignore matches under comment + if omni#cpp#utils#IsCursorInCommentOrString() + continue + endif + + " Getting the current line + let szLine = getline('.') + if match(szLine, szReVariable)>=0 + " We found our variable + " Check if the current instruction is a decl instruction + let tokens = omni#cpp#utils#TokenizeCurrentInstruction() + let szTypeInfo = s:ExtractTypeInfoFromDecl(tokens) + if szTypeInfo != '' + call setpos('.', originalPos) + return omni#cpp#utils#CreateTypeInfo(szTypeInfo) + endif + else + " We found a child scope, we don't want to go in, thus + " we search for the end } of this child scope + let bracketEnd = searchpairpos('{', '', '}', 'nW', g:omni#cpp#utils#expIgnoreComments) + if bracketEnd == [0,0] + break + endif + + if bracketEnd[0] >= stopPos[0] + " The end of the scope is after our cursor we stop + " the search + break + else + " We move the cursor and continue to search our + " variable + call setpos('.', [0, bracketEnd[0], bracketEnd[1], 0]) + endif + endif + endif + endwhile + + " Backing to the start of the scope + call setpos('.', [0,curPos[0], curPos[1], 0]) + let stopPos = curPos + endif + endwhile + + let result = {} + if s:LocalSearchDecl(a:szVariable)==0 && !omni#cpp#utils#IsCursorInCommentOrString() + let tokens = omni#cpp#utils#TokenizeCurrentInstruction() + let szTypeInfo = s:ExtractTypeInfoFromDecl(tokens) + if szTypeInfo != '' + let result = omni#cpp#utils#CreateTypeInfo(szTypeInfo) + endif + endif + + call setpos('.', originalPos) + + return result +endfunc + +" Search a declaration +" @return +" - tokens of the current instruction if success +" - empty list if failure +function! s:SearchDecl(szVariable) + let result = {} + let originalPos = getpos('.') + let searchResult = s:LocalSearchDecl(a:szVariable) + if searchResult==0 + " searchdecl() may detect a decl if the variable is in a conditional + " instruction (if, elseif, while etc...) + " We have to check if the detected decl is really a decl instruction + let tokens = omni#cpp#utils#TokenizeCurrentInstruction() + + for token in tokens + " Simple test + if index(['if', 'elseif', 'while', 'for', 'switch'], token.value)>=0 + " Invalid declaration instruction + call setpos('.', originalPos) + return result + endif + endfor + + let szTypeInfo = s:ExtractTypeInfoFromDecl(tokens) + if szTypeInfo != '' + let result = omni#cpp#utils#CreateTypeInfo(szTypeInfo) + endif + endif + call setpos('.', originalPos) + return result +endfunc + +" Extract the type info string from an instruction. +" We use a small parser to extract the type +" We parse the code according to a C++ BNF from: http://www.nongnu.org/hcb/#basic.link +" @param tokens: token list of the current instruction +function! s:ExtractTypeInfoFromDecl(tokens) + return omni#cpp#utils#ExtractTypeInfoFromTokens(a:tokens) +endfunc + +" Convert tokens to string +function! s:TokensToString(tokens) + let result = '' + for token in a:tokens + let result = result . token.value . ' ' + endfor + return result[:-2] +endfunc + +" Resolve a cast. +" Resolve a C++ cast +" @param list of token. tokens must be a list that represents +" a cast expression (C++ cast) the function does not control +" if it's a cast or not +" eg: static_cast(something) +" @return type info string +function! s:ResolveCppCast(tokens) + return omni#cpp#utils#ExtractTypeInfoFromTokens(s:ResolveCast(a:tokens, '<', '>')) +endfunc + +" Resolve a cast. +" Resolve a C cast +" @param list of token. tokens must be a list that represents +" a cast expression (C cast) the function does not control +" if it's a cast or not +" eg: (MyClass*)something +" @return type info string +function! s:ResolveCCast(tokens) + return omni#cpp#utils#ExtractTypeInfoFromTokens(s:ResolveCast(a:tokens, '(', ')')) +endfunc + +" Resolve a cast. +" Resolve a C cast +" @param list of token. tokens must be a list that represents +" a cast expression (C cast) the function does not control +" if it's a cast or not +" eg: (MyClass*)something +" @return type tokens +function! s:ResolveCast(tokens, startChar, endChar) + let tokens = omni#cpp#utils#BuildParenthesisGroups(a:tokens) + + " We remove useless parenthesis eg: (((MyClass))) + let tokens = omni#cpp#utils#SimplifyParenthesis(tokens) + + let countItem=0 + let startIndex = -1 + let endIndex = -1 + let i = 0 + for token in tokens + if startIndex==-1 + if token.value==a:startChar + let countItem += 1 + let startIndex = i + endif + else + if token.value==a:startChar + let countItem += 1 + elseif token.value==a:endChar + let countItem -= 1 + endif + + if countItem==0 + let endIndex = i + break + endif + endif + let i+=1 + endfor + + return tokens[startIndex+1 : endIndex-1] +endfunc + +" Replacement for build-in function 'searchdecl' +" It does not require that the upper-level bracket is in the first column. +" Otherwise it should be equal to 'searchdecl(name, 0, 1)' +" @param name: name of variable to find declaration for +function! s:LocalSearchDecl(name) + + if g:OmniCpp_LocalSearchDecl == 0 + let bUserIgnoreCase = &ignorecase + + " Forcing the noignorecase option + " avoid bug when, for example, if we have a declaration like this : "A a;" + set noignorecase + + let result = searchdecl(a:name, 0, 1) + + " Restoring user's setting + let &ignorecase = bUserIgnoreCase + + return result + endif + + let lastpos = getpos('.') + let winview = winsaveview() + let lastfoldenable = &foldenable + let &foldenable = 0 + + " We add \C (noignorecase) to + " avoid bug when, for example, if we have a declaration like this : "A a;" + let varname = "\\C\\<" . a:name . "\\>" + + " Go to first blank line before begin of highest scope + normal 99[{ + let scopepos = getpos('.') + while (line('.') > 1) && (len(split(getline('.'))) > 0) + call cursor(line('.')-1, 0) + endwhile + + let declpos = [ 0, 0, 0, 0 ] + while search(varname, '', scopepos[1]) > 0 + " Check if we are a string or a comment + if omni#cpp#utils#IsCursorInCommentOrString() + continue + endif + + " Remember match + let declpos = getpos('.') + endwhile + if declpos[1] != 0 + " We found a match + call winrestview(winview) + call setpos('.', declpos) + let &foldenable = lastfoldenable + return 0 + endif + + while search(varname, '', lastpos[1]) > 0 + " Check if current scope is ending before variable + let old_cur = getpos('.') + normal ]} + let new_cur = getpos('.') + call setpos('.', old_cur) + if (new_cur[1] < lastpos[1]) || ((new_cur[1] == lastpos[1]) && (new_cur[2] < lastpos[2])) + continue + endif + + " Check if we are a string or a comment + if omni#cpp#utils#IsCursorInCommentOrString() + continue + endif + + " We found match + call winrestview(winview) + call setpos('.', old_cur) + let &foldenable = lastfoldenable + return 0 + endwhile + + " No match found. + call winrestview(winview) + let &foldenable = lastfoldenable + return 1 +endfunc diff --git a/VimFiles/autoload/omni/cpp/maycomplete.vim b/VimFiles/autoload/omni/cpp/maycomplete.vim new file mode 100755 index 0000000..610526b --- /dev/null +++ b/VimFiles/autoload/omni/cpp/maycomplete.vim @@ -0,0 +1,82 @@ +" Description: Omni completion script for cpp files +" Maintainer: Vissale NEANG +" Last Change: 26 sept. 2007 + +" Check if we can use omni completion in the current buffer +function! s:CanUseOmnicompletion() + " For C and C++ files and only if the omnifunc is omni#cpp#complete#Main + return (index(['c', 'cpp'], &filetype)>=0 && &omnifunc == 'omni#cpp#complete#Main' && !omni#cpp#utils#IsCursorInCommentOrString()) +endfunc + +" Return the mapping of omni completion +function! omni#cpp#maycomplete#Complete() + let szOmniMapping = "\\" + + " 0 = don't select first item + " 1 = select first item (inserting it to the text, default vim behaviour) + " 2 = select first item (without inserting it to the text) + if g:OmniCpp_SelectFirstItem == 0 + " We have to force the menuone option to avoid confusion when there is + " only one popup item + set completeopt-=menu + set completeopt+=menuone + let szOmniMapping .= "\" + elseif g:OmniCpp_SelectFirstItem == 2 + " We have to force the menuone option to avoid confusion when there is + " only one popup item + set completeopt-=menu + set completeopt+=menuone + let szOmniMapping .= "\" + let szOmniMapping .= "\=pumvisible() ? \"\\\" : \"\"\" + endif + return szOmniMapping +endfunc + +" May complete function for dot +function! omni#cpp#maycomplete#Dot() + if s:CanUseOmnicompletion() && g:OmniCpp_MayCompleteDot + let g:omni#cpp#items#data = omni#cpp#items#Get(omni#cpp#utils#TokenizeCurrentInstruction('.')) + if len(g:omni#cpp#items#data) + let s:bMayComplete = 1 + return '.' . omni#cpp#maycomplete#Complete() + endif + endif + return '.' +endfunc +" May complete function for arrow +function! omni#cpp#maycomplete#Arrow() + if s:CanUseOmnicompletion() && g:OmniCpp_MayCompleteArrow + let index = col('.') - 2 + if index >= 0 + let char = getline('.')[index] + if char == '-' + let g:omni#cpp#items#data = omni#cpp#items#Get(omni#cpp#utils#TokenizeCurrentInstruction('>')) + if len(g:omni#cpp#items#data) + let s:bMayComplete = 1 + return '>' . omni#cpp#maycomplete#Complete() + endif + endif + endif + endif + return '>' +endfunc + +" May complete function for double points +function! omni#cpp#maycomplete#Scope() + if s:CanUseOmnicompletion() && g:OmniCpp_MayCompleteScope + let index = col('.') - 2 + if index >= 0 + let char = getline('.')[index] + if char == ':' + let g:omni#cpp#items#data = omni#cpp#items#Get(omni#cpp#utils#TokenizeCurrentInstruction(':')) + if len(g:omni#cpp#items#data) + if len(g:omni#cpp#items#data[-1].tokens) && g:omni#cpp#items#data[-1].tokens[-1].value != '::' + let s:bMayComplete = 1 + return ':' . omni#cpp#maycomplete#Complete() + endif + endif + endif + endif + endif + return ':' +endfunc diff --git a/VimFiles/autoload/omni/cpp/namespaces.vim b/VimFiles/autoload/omni/cpp/namespaces.vim new file mode 100755 index 0000000..386b3f9 --- /dev/null +++ b/VimFiles/autoload/omni/cpp/namespaces.vim @@ -0,0 +1,838 @@ +" Description: Omni completion script for cpp files +" Maintainer: Vissale NEANG +" Last Change: 26 sept. 2007 + +let g:omni#cpp#namespaces#CacheResolve = {} +let g:omni#cpp#namespaces#CacheUsing = {} +" TODO: For the next release +"let g:omni#cpp#namespaces#CacheAlias = {} + +" Get the using namespace list from a line +function! s:GetNamespaceAliasListFromLine(szLine) + let result = {} + let tokens = omni#cpp#tokenizer#Tokenize(a:szLine) + let szAlias = '' + let szNamespace = '' + let state = 0 + for token in tokens + if state==0 + let szAlias = '' + let szNamespace = '' + if token.value == '/*' + let state = 1 + elseif token.value == '//' + " It's a comment + let state = -1 + break + elseif token.value == 'namespace' + let state = 2 + endif + elseif state==1 + if token.value == '*/' + let state=0 + endif + elseif state==2 + if token.kind == 'cppWord' + let szAlias .= token.value + let state = 3 + else + let state = -1 + break + endif + elseif state == 3 + if token.value == '=' + let state = 4 + else + let state = -1 + break + endif + elseif state == 4 + if token.value == '::' + let szNamespace .= token.value + let state = 5 + elseif token.kind == 'cppWord' + let szNamespace .= token.value + let state = 6 + " Maybe end of tokens + endif + elseif state==5 + if token.kind == 'cppWord' + let szNamespace .= token.value + let state = 6 + " Maybe end of tokens + else + " Error, we can't have 'namespace ALIAS = Something::' + let state = -1 + break + endif + elseif state==6 + if token.value == '::' + let szNamespace .= token.value + let state = 5 + else + call extend(result, {szAlias : szNamespace}) + let state = 0 + endif + endif + endfor + + if state == 6 + call extend(result, {szAlias : szNamespace}) + endif + + return result +endfunc + +" Get the using namespace list from a line +function! s:GetNamespaceListFromLine(szLine) + let result = [] + let tokens = omni#cpp#tokenizer#Tokenize(a:szLine) + let szNamespace = '' + let state = 0 + for token in tokens + if state==0 + let szNamespace = '' + if token.value == '/*' + let state = 1 + elseif token.value == '//' + " It's a comment + let state = -1 + break + elseif token.value == 'using' + let state = 2 + endif + elseif state==1 + if token.value == '*/' + let state=0 + endif + elseif state==2 + if token.value == 'namespace' + let state = 3 + else + " Error, 'using' must be followed by 'namespace' + let state = -1 + break + endif + elseif state==3 + if token.value == '::' + let szNamespace .= token.value + let state = 4 + elseif token.kind == 'cppWord' + let szNamespace .= token.value + let state = 5 + " Maybe end of tokens + endif + elseif state==4 + if token.kind == 'cppWord' + let szNamespace .= token.value + let state = 5 + " Maybe end of tokens + else + " Error, we can't have 'using namespace Something::' + let state = -1 + break + endif + elseif state==5 + if token.value == '::' + let szNamespace .= token.value + let state = 4 + else + call extend(result, [szNamespace]) + let state = 0 + endif + endif + endfor + + if state == 5 + call extend(result, [szNamespace]) + endif + + return result +endfunc + +" Get the namespace list from a namespace map +function! s:GetUsingNamespaceListFromMap(namespaceMap, ...) + let stopLine = 0 + if a:0>0 + let stopLine = a:1 + endif + + let result = [] + let keys = sort(keys(a:namespaceMap), 'omni#common#utils#CompareNumber') + for i in keys + if stopLine != 0 && i > stopLine + break + endif + call extend(result, a:namespaceMap[i]) + endfor + return result +endfunc + +" Get global using namespace list from the current buffer +function! omni#cpp#namespaces#GetListFromCurrentBuffer(...) + let namespaceMap = s:GetAllUsingNamespaceMapFromCurrentBuffer() + let result = [] + if namespaceMap != {} + let result = s:GetUsingNamespaceListFromMap(namespaceMap, (a:0 > 0)? a:1 : line('.')) + endif + return result +endfunc + +" Get global using namespace map from the current buffer and include files recursively +function! s:GetAllUsingNamespaceMapFromCurrentBuffer(...) + let includeGuard = (a:0>0)? a:1 : {} + + let szBufferName = getreg("%") + let szFilePath = omni#cpp#utils#ResolveFilePath(szBufferName) + let szFilePath = (szFilePath=='')? szBufferName : szFilePath + + let namespaceMap = {} + if has_key(includeGuard, szFilePath) + return namespaceMap + else + let includeGuard[szFilePath] = 1 + endif + + let namespaceMap = omni#cpp#namespaces#GetMapFromCurrentBuffer() + + if g:OmniCpp_NamespaceSearch != 2 + " We don't search included files if OmniCpp_NamespaceSearch != 2 + return namespaceMap + endif + + for inc in omni#cpp#includes#GetList() + let lnum = inc.pos[0] + let tmpMap = s:GetAllUsingNamespaceMapFromFile(inc.include, includeGuard) + if tmpMap != {} + if has_key(namespaceMap, lnum) + call extend(namespaceMap[lnum], s:GetUsingNamespaceListFromMap(tmpMap)) + else + let namespaceMap[lnum] = s:GetUsingNamespaceListFromMap(tmpMap) + endif + endif + endfor + + return namespaceMap +endfunc + +" Get global using namespace map from a file and include files recursively +function! s:GetAllUsingNamespaceMapFromFile(szFilePath, ...) + let includeGuard = {} + if a:0 >0 + let includeGuard = a:1 + endif + + let szFilePath = omni#cpp#utils#ResolveFilePath(a:szFilePath) + let szFilePath = (szFilePath=='')? a:szFilePath : szFilePath + + let namespaceMap = {} + if has_key(includeGuard, szFilePath) + return namespaceMap + else + let includeGuard[szFilePath] = 1 + endif + + " If g:OmniCpp_NamespaceSearch == 1 (search namespaces only in the current + " buffer) we don't use cache for the current buffer + let namespaceMap = omni#cpp#namespaces#GetMapFromBuffer(szFilePath, g:OmniCpp_NamespaceSearch==1) + + if g:OmniCpp_NamespaceSearch != 2 + " We don't search included files if OmniCpp_NamespaceSearch != 2 + return namespaceMap + endif + + for inc in omni#cpp#includes#GetList(szFilePath) + let lnum = inc.pos[0] + let tmpMap = s:GetAllUsingNamespaceMapFromFile(inc.include, includeGuard) + if tmpMap != {} + if has_key(namespaceMap, lnum) + call extend(namespaceMap[lnum], s:GetUsingNamespaceListFromMap(tmpMap)) + else + let namespaceMap[lnum] = s:GetUsingNamespaceListFromMap(tmpMap) + endif + endif + endfor + + return namespaceMap +endfunc + +" Get global using namespace map from a the current buffer +function! omni#cpp#namespaces#GetMapFromCurrentBuffer() + let namespaceMap = {} + let originalPos = getpos('.') + + call setpos('.', [0, 1, 1, 0]) + let curPos = [1,1] + while curPos != [0,0] + let curPos = searchpos('\C^using\s\+namespace', 'W') + if curPos != [0,0] + let szLine = getline('.') + let startPos = curPos[1] + let endPos = match(szLine, ';', startPos-1) + if endPos!=-1 + " We get the namespace list from the line + let namespaceMap[curPos[0]] = s:GetNamespaceListFromLine(szLine) + endif + endif + endwhile + + call setpos('.', originalPos) + return namespaceMap +endfunc + +" Get global using namespace map from a file +function! omni#cpp#namespaces#GetMapFromBuffer(szFilePath, ...) + let bUpdate = 0 + if a:0 > 0 + let bUpdate = a:1 + endif + + let szFilePath = omni#cpp#utils#ResolveFilePath(a:szFilePath) + let szFilePath = (szFilePath=='')? a:szFilePath : szFilePath + + if !bUpdate && has_key(g:omni#cpp#namespaces#CacheUsing, szFilePath) + return copy(g:omni#cpp#namespaces#CacheUsing[szFilePath]) + endif + + let namespaceMap = {} + " The file exists, we get the global namespaces in this file + let szFixedPath = escape(szFilePath, g:omni#cpp#utils#szEscapedCharacters) + execute 'silent! lvimgrep /\C^using\s\+namespace/gj '.szFixedPath + + " key = line number + " value = list of namespaces + let listQuickFix = getloclist(0) + for qf in listQuickFix + let szLine = qf.text + let startPos = qf.col + let endPos = match(szLine, ';', startPos-1) + if endPos!=-1 + " We get the namespace list from the line + let namespaceMap[qf.lnum] = s:GetNamespaceListFromLine(szLine) + endif + endfor + + if szFixedPath != '' + let g:omni#cpp#namespaces#CacheUsing[szFixedPath] = namespaceMap + endif + + return copy(namespaceMap) +endfunc + +" Get the stop position when searching for local variables +function! s:GetStopPositionForLocalSearch() + " Stop position when searching a local variable + let originalPos = getpos('.') + let origPos = originalPos[1:2] + let stopPosition = origPos + let curPos = origPos + while curPos !=[0,0] + let stopPosition = curPos + let curPos = searchpairpos('{', '', '}', 'bW', g:omni#cpp#utils#expIgnoreComments) + endwhile + call setpos('.', originalPos) + + return stopPosition +endfunc + +" Get namespaces alias used at the cursor postion in a vim buffer +" Note: The result depends on the current cursor position +" @return +" - Map of namespace alias +function! s:GetNamespaceAliasMap() + " We store the cursor position because searchpairpos() moves the cursor + let result = {} + let originalPos = getpos('.') + let origPos = originalPos[1:2] + + let stopPos = s:GetStopPositionForLocalSearch() + let stopLine = stopPos[0] + let curPos = origPos + let lastLine = 0 + let nextStopLine = origPos[0] + let szReAlias = '\Cnamespace\s\+\w\+\s\+=' + while curPos !=[0,0] + let curPos = searchpos('}\|\('. szReAlias .'\)', 'bW',stopLine) + if curPos!=[0,0] && curPos[0]!=lastLine + let lastLine = curPos[0] + + let szLine = getline('.') + if origPos[0] == curPos[0] + " We get the line until cursor position + let szLine = szLine[:origPos[1]] + endif + + let szLine = omni#cpp#utils#GetCodeFromLine(szLine) + if match(szLine, szReAlias)<0 + " We found a '}' + let curPos = searchpairpos('{', '', '}', 'bW', g:omni#cpp#utils#expIgnoreComments) + else + " We get the namespace alias from the line + call extend(result, s:GetNamespaceAliasListFromLine(szLine)) + let nextStopLine = curPos[0] + endif + endif + endwhile + + " Setting the cursor to the original position + call setpos('.', originalPos) + + call s:ResolveAliasKeys(result) + return result +endfunc + +" Resolve an alias +" eg: namespace IAmAnAlias1 = Ns1 +" eg: namespace IAmAnAlias2 = IAmAnAlias1::Ns2 +" => IAmAnAlias2 = Ns1::Ns2 +function! s:ResolveAliasKey(mapNamespaceAlias, szAlias) + let szResult = a:mapNamespaceAlias[a:szAlias] + " ::Ns1::Ns2::Ns3 => ['Ns1', 'Ns2', 'Ns3'] + let listNamespace = split(szResult, '::') + if len(listNamespace) + " szBeginPart = 'Ns1' + let szBeginPart = remove(listNamespace, 0) + + " Is 'Ns1' an alias ? + if has_key(a:mapNamespaceAlias, szBeginPart) && szBeginPart != a:szAlias + " Resolving alias 'Ns1' + " eg: Ns1 = NsResolved + let szResult = s:ResolveAliasKey(a:mapNamespaceAlias, szBeginPart) + " szEndPart = 'Ns2::Ns3' + let szEndPart = join(listNamespace, '::') + if szEndPart != '' + " Concatenation => szResult = 'NsResolved::Ns2::Ns3' + let szResult .= '::' . szEndPart + endif + endif + endif + return szResult +endfunc + +" Resolve all keys in the namespace alias map +function! s:ResolveAliasKeys(mapNamespaceAlias) + let mapNamespaceAlias = a:mapNamespaceAlias + call map(mapNamespaceAlias, 's:ResolveAliasKey(mapNamespaceAlias, v:key)') +endfunc + +" Resolve namespace alias +function! omni#cpp#namespaces#ResolveAlias(mapNamespaceAlias, szNamespace) + let szResult = a:szNamespace + " ::Ns1::Ns2::Ns3 => ['Ns1', 'Ns2', 'Ns3'] + let listNamespace = split(a:szNamespace, '::') + if len(listNamespace) + " szBeginPart = 'Ns1' + let szBeginPart = remove(listNamespace, 0) + + " Is 'Ns1' an alias ? + if has_key(a:mapNamespaceAlias, szBeginPart) + " Resolving alias 'Ns1' + " eg: Ns1 = NsResolved + let szResult = a:mapNamespaceAlias[szBeginPart] + " szEndPart = 'Ns2::Ns3' + let szEndPart = join(listNamespace, '::') + if szEndPart != '' + " Concatenation => szResult = 'NsResolved::Ns2::Ns3' + let szResult .= '::' . szEndPart + endif + + " If a:szNamespace starts with '::' we add '::' to the beginning + " of the result + if match(a:szNamespace, '^::')>=0 + let szResult = omni#cpp#utils#SimplifyScope('::' . szResult) + endif + endif + endif + return szResult +endfunc + +" Resolve namespace alias +function! s:ResolveAliasInNamespaceList(mapNamespaceAlias, listNamespaces) + call map(a:listNamespaces, 'omni#cpp#namespaces#ResolveAlias(a:mapNamespaceAlias, v:val)') +endfunc + +" Get namespaces used at the cursor postion in a vim buffer +" Note: The result depends on the current cursor position +" @return +" - List of namespace used in the reverse order +function! omni#cpp#namespaces#GetUsingNamespaces() + " We have to get local using namespace declarations + " We need the current cursor position and the position of the start of the + " current scope + + " We store the cursor position because searchpairpos() moves the cursor + let result = [] + let originalPos = getpos('.') + let origPos = originalPos[1:2] + + let stopPos = s:GetStopPositionForLocalSearch() + + let stopLine = stopPos[0] + let curPos = origPos + let lastLine = 0 + let nextStopLine = origPos[0] + while curPos !=[0,0] + let curPos = searchpos('\C}\|\(using\s\+namespace\)', 'bW',stopLine) + if curPos!=[0,0] && curPos[0]!=lastLine + let lastLine = curPos[0] + + let szLine = getline('.') + if origPos[0] == curPos[0] + " We get the line until cursor position + let szLine = szLine[:origPos[1]] + endif + + let szLine = omni#cpp#utils#GetCodeFromLine(szLine) + if match(szLine, '\Cusing\s\+namespace')<0 + " We found a '}' + let curPos = searchpairpos('{', '', '}', 'bW', g:omni#cpp#utils#expIgnoreComments) + else + " We get the namespace list from the line + let result = s:GetNamespaceListFromLine(szLine) + result + let nextStopLine = curPos[0] + endif + endif + endwhile + + " Setting the cursor to the original position + call setpos('.', originalPos) + + " 2) Now we can get all global using namespace declaration from the + " beginning of the file to nextStopLine + let result = omni#cpp#namespaces#GetListFromCurrentBuffer(nextStopLine) + result + + " Resolving alias in the namespace list + " TODO: For the next release + "let g:omni#cpp#namespaces#CacheAlias= s:GetNamespaceAliasMap() + "call s:ResolveAliasInNamespaceList(g:omni#cpp#namespaces#CacheAlias, result) + + return ['::'] + result +endfunc + +" Resolve a using namespace regarding the current context +" For each namespace used: +" - We get all possible contexts where the namespace +" can be define +" - We do a comparison test of each parent contexts with the current +" context list +" - If one and only one parent context is present in the +" current context list we add the namespace in the current +" context +" - If there is more than one of parent contexts in the +" current context the namespace is ambiguous +" @return +" - result item +" - kind = 0|1 +" - 0 = unresolved or error +" - 1 = resolved +" - value = resolved namespace +function! s:ResolveNamespace(namespace, mapCurrentContexts) + let result = {'kind':0, 'value': ''} + + " If the namespace is already resolved we add it in the list of + " current contexts + if match(a:namespace, '^::')>=0 + let result.kind = 1 + let result.value = a:namespace + return result + elseif match(a:namespace, '\w\+::\w\+')>=0 + let mapCurrentContextsTmp = copy(a:mapCurrentContexts) + let resolvedItem = {} + for nsTmp in split(a:namespace, '::') + let resolvedItem = s:ResolveNamespace(nsTmp, mapCurrentContextsTmp) + if resolvedItem.kind + " Note: We don't extend the map + let mapCurrentContextsTmp = {resolvedItem.value : 1} + else + break + endif + endfor + if resolvedItem!={} && resolvedItem.kind + let result.kind = 1 + let result.value = resolvedItem.value + endif + return result + endif + + " We get all possible parent contexts of this namespace + let listTagsOfNamespace = [] + if has_key(g:omni#cpp#namespaces#CacheResolve, a:namespace) + let listTagsOfNamespace = g:omni#cpp#namespaces#CacheResolve[a:namespace] + else + let listTagsOfNamespace = omni#common#utils#TagList('^'.a:namespace.'$') + let g:omni#cpp#namespaces#CacheResolve[a:namespace] = listTagsOfNamespace + endif + + if len(listTagsOfNamespace)==0 + return result + endif + call filter(listTagsOfNamespace, 'v:val.kind[0]=="n"') + + " We extract parent context from tags + " We use a map to avoid multiple entries + let mapContext = {} + for tagItem in listTagsOfNamespace + let szParentContext = omni#cpp#utils#ExtractScope(tagItem) + let mapContext[szParentContext] = 1 + endfor + let listParentContext = keys(mapContext) + + " Now for each parent context we test if the context is in the current + " contexts list + let listResolvedNamespace = [] + for szParentContext in listParentContext + if has_key(a:mapCurrentContexts, szParentContext) + call extend(listResolvedNamespace, [omni#cpp#utils#SimplifyScope(szParentContext.'::'.a:namespace)]) + endif + endfor + + " Now we know if the namespace is ambiguous or not + let len = len(listResolvedNamespace) + if len==1 + " Namespace resolved + let result.kind = 1 + let result.value = listResolvedNamespace[0] + elseif len > 1 + " Ambiguous namespace, possible matches are in listResolvedNamespace + else + " Other cases + endif + return result +endfunc + +" Resolve namespaces +"@return +" - List of resolved namespaces +function! omni#cpp#namespaces#ResolveAll(namespacesUsed) + + " We add the default context '::' + let contextOrder = 0 + let mapCurrentContexts = {} + + " For each namespace used: + " - We get all possible contexts where the namespace + " can be define + " - We do a comparison test of each parent contexts with the current + " context list + " - If one and only one parent context is present in the + " current context list we add the namespace in the current + " context + " - If there is more than one of parent contexts in the + " current context the namespace is ambiguous + for ns in a:namespacesUsed + let resolvedItem = s:ResolveNamespace(ns, mapCurrentContexts) + if resolvedItem.kind + let contextOrder+=1 + let mapCurrentContexts[resolvedItem.value] = contextOrder + endif + endfor + + " Build the list of current contexts from the map, we have to keep the + " order + let mapReorder = {} + for key in keys(mapCurrentContexts) + let mapReorder[ mapCurrentContexts[key] ] = key + endfor + let result = [] + for key in sort(keys(mapReorder)) + call extend(result, [mapReorder[key]]) + endfor + return result +endfunc + +" Build the context stack +function! s:BuildContextStack(namespaces, szCurrentScope) + let result = copy(a:namespaces) + if a:szCurrentScope != '::' + let tagItem = omni#cpp#utils#GetResolvedTagItem(a:namespaces, omni#cpp#utils#CreateTypeInfo(a:szCurrentScope)) + if has_key(tagItem, 'inherits') + let listBaseClass = omni#cpp#utils#GetClassInheritanceList(a:namespaces, omni#cpp#utils#CreateTypeInfo(a:szCurrentScope)) + let result = listBaseClass + result + elseif has_key(tagItem, 'kind') && index(['c', 's', 'u', 'n'], tagItem.kind[0])>=0 + call insert(result, omni#cpp#utils#ExtractTypeInfoFromTag(tagItem)) + endif + endif + return result +endfunc + +" Returns the class scope at the current position of the cursor +" @return a string that represents the class scope +" eg: ::NameSpace1::Class1 +" The returned string always starts with '::' +" Note: In term of performance it's the weak point of the script +function! s:GetClassScopeAtCursor() + " We store the cursor position because searchpairpos() moves the cursor + let originalPos = getpos('.') + let endPos = originalPos[1:2] + let listCode = [] + let result = {'namespaces': [], 'scope': ''} + + while endPos!=[0,0] + let endPos = searchpairpos('{', '', '}', 'bW', g:omni#cpp#utils#expIgnoreComments) + let szReStartPos = '[;{}]\|\%^' + let startPos = searchpairpos(szReStartPos, '', '{', 'bWn', g:omni#cpp#utils#expIgnoreComments) + + " If the file starts with a comment so the startPos can be [0,0] + " we change it to [1,1] + if startPos==[0,0] + let startPos = [1,1] + endif + + " Get lines backward from cursor position to last ; or { or } + " or when we are at the beginning of the file. + " We store lines in listCode + if endPos!=[0,0] + " We remove the last character which is a '{' + " We also remove starting { or } or ; if exits + let szCodeWithoutComments = substitute(omni#cpp#utils#GetCode(startPos, endPos)[:-2], '^[;{}]', '', 'g') + call insert(listCode, {'startLine' : startPos[0], 'code' : szCodeWithoutComments}) + endif + endwhile + " Setting the cursor to the original position + call setpos('.', originalPos) + + let listClassScope = [] + let bResolved = 0 + let startLine = 0 + " Now we can check in the list of code if there is a function + for code in listCode + " We get the name of the namespace, class, struct or union + " and we store it in listClassScope + let tokens = omni#cpp#tokenizer#Tokenize(code.code) + let bContinue=0 + let bAddNamespace = 0 + let state=0 + for token in tokens + if state==0 + if index(['namespace', 'class', 'struct', 'union'], token.value)>=0 + if token.value == 'namespace' + let bAddNamespace = 1 + endif + let state= 1 + " Maybe end of tokens + endif + elseif state==1 + if token.kind == 'cppWord' + " eg: namespace MyNs { class MyCl {}; } + " => listClassScope = [MyNs, MyCl] + call extend( listClassScope , [token.value] ) + + " Add the namespace in result + if bAddNamespace + call extend(result.namespaces, [token.value]) + let bAddNamespace = 0 + endif + + let bContinue=1 + break + endif + endif + endfor + if bContinue==1 + continue + endif + + " Simple test to check if we have a chance to find a + " class method + let aPos = matchend(code.code, '::\s*\~*\s*\w\+\s*(') + if aPos ==-1 + continue + endif + + let startLine = code.startLine + let listTmp = [] + " eg: 'void MyNamespace::MyClass::foo(' + " => tokens = ['MyClass', '::', 'MyNamespace', 'void'] + let tokens = reverse(omni#cpp#tokenizer#Tokenize(code.code[:aPos-1])[:-4]) + let state = 0 + " Reading tokens backward + for token in tokens + if state==0 + if token.kind=='cppWord' + call insert(listTmp, token.value) + let state=1 + endif + elseif state==1 + if token.value=='::' + let state=2 + else + break + endif + elseif state==2 + if token.kind=='cppWord' + call insert(listTmp, token.value) + let state=1 + else + break + endif + endif + endfor + + if len(listTmp) + if len(listClassScope) + let bResolved = 1 + " Merging class scopes + " eg: current class scope = 'MyNs::MyCl1' + " method class scope = 'MyCl1::MyCl2' + " If we add the method class scope to current class scope + " we'll have MyNs::MyCl1::MyCl1::MyCl2 => it's wrong + " we want MyNs::MyCl1::MyCl2 + let index = 0 + for methodClassScope in listTmp + if methodClassScope==listClassScope[-1] + let listTmp = listTmp[index+1:] + break + else + let index+=1 + endif + endfor + endif + call extend(listClassScope, listTmp) + break + endif + endfor + + let szClassScope = '::' + if len(listClassScope) + if bResolved + let szClassScope .= join(listClassScope, '::') + else + let szClassScope = join(listClassScope, '::') + + " The class scope is not resolved, we have to check using + " namespace declarations and search the class scope in each + " namespace + if startLine != 0 + let namespaces = ['::'] + omni#cpp#namespaces#GetListFromCurrentBuffer(startLine) + let namespaces = omni#cpp#namespaces#ResolveAll(namespaces) + let tagItem = omni#cpp#utils#GetResolvedTagItem(namespaces, omni#cpp#utils#CreateTypeInfo(szClassScope)) + if tagItem != {} + let szClassScope = omni#cpp#utils#ExtractTypeInfoFromTag(tagItem) + endif + endif + endif + endif + + let result.scope = szClassScope + return result +endfunc + +" Get all contexts at the cursor position +function! omni#cpp#namespaces#GetContexts() + " Get the current class scope at the cursor, the result depends on the current cursor position + let scopeItem = s:GetClassScopeAtCursor() + let listUsingNamespace = copy(g:OmniCpp_DefaultNamespaces) + call extend(listUsingNamespace, scopeItem.namespaces) + if g:OmniCpp_NamespaceSearch && &filetype != 'c' + " Get namespaces used in the file until the cursor position + let listUsingNamespace = omni#cpp#namespaces#GetUsingNamespaces() + listUsingNamespace + " Resolving namespaces, removing ambiguous namespaces + let namespaces = omni#cpp#namespaces#ResolveAll(listUsingNamespace) + else + let namespaces = ['::'] + listUsingNamespace + endif + call reverse(namespaces) + + " Building context stack from namespaces and the current class scope + return s:BuildContextStack(namespaces, scopeItem.scope) +endfunc diff --git a/VimFiles/autoload/omni/cpp/settings.vim b/VimFiles/autoload/omni/cpp/settings.vim new file mode 100755 index 0000000..6683d3a --- /dev/null +++ b/VimFiles/autoload/omni/cpp/settings.vim @@ -0,0 +1,96 @@ +" Description: Omni completion script for cpp files +" Maintainer: Vissale NEANG +" Last Change: 26 sept. 2007 + +function! omni#cpp#settings#Init() + " Global scope search on/off + " 0 = disabled + " 1 = enabled + if !exists('g:OmniCpp_GlobalScopeSearch') + let g:OmniCpp_GlobalScopeSearch = 1 + endif + + " Sets the namespace search method + " 0 = disabled + " 1 = search namespaces in the current file + " 2 = search namespaces in the current file and included files + if !exists('g:OmniCpp_NamespaceSearch') + let g:OmniCpp_NamespaceSearch = 1 + endif + + " Set the class scope completion mode + " 0 = auto + " 1 = show all members (static, public, protected and private) + if !exists('g:OmniCpp_DisplayMode') + let g:OmniCpp_DisplayMode = 0 + endif + + " Set if the scope is displayed in the abbr column of the popup + " 0 = no + " 1 = yes + if !exists('g:OmniCpp_ShowScopeInAbbr') + let g:OmniCpp_ShowScopeInAbbr = 0 + endif + + " Set if the function prototype is displayed in the abbr column of the popup + " 0 = no + " 1 = yes + if !exists('g:OmniCpp_ShowPrototypeInAbbr') + let g:OmniCpp_ShowPrototypeInAbbr = 0 + endif + + " Set if the access (+,#,-) is displayed + " 0 = no + " 1 = yes + if !exists('g:OmniCpp_ShowAccess') + let g:OmniCpp_ShowAccess = 1 + endif + + " Set the list of default namespaces + " eg: ['std'] + if !exists('g:OmniCpp_DefaultNamespaces') + let g:OmniCpp_DefaultNamespaces = [] + endif + + " Set MayComplete to '.' + " 0 = disabled + " 1 = enabled + " default = 1 + if !exists('g:OmniCpp_MayCompleteDot') + let g:OmniCpp_MayCompleteDot = 1 + endif + + " Set MayComplete to '->' + " 0 = disabled + " 1 = enabled + " default = 1 + if !exists('g:OmniCpp_MayCompleteArrow') + let g:OmniCpp_MayCompleteArrow = 1 + endif + + " Set MayComplete to dot + " 0 = disabled + " 1 = enabled + " default = 0 + if !exists('g:OmniCpp_MayCompleteScope') + let g:OmniCpp_MayCompleteScope = 0 + endif + + " When completeopt does not contain longest option, this setting + " controls the behaviour of the popup menu selection when starting the completion + " 0 = don't select first item + " 1 = select first item (inserting it to the text) + " 2 = select first item (without inserting it to the text) + " default = 0 + if !exists('g:OmniCpp_SelectFirstItem') + let g:OmniCpp_SelectFirstItem= 0 + endif + + " Use local search function for variable definitions + " 0 = use standard vim search function + " 1 = use local search function + " default = 0 + if !exists('g:OmniCpp_LocalSearchDecl') + let g:OmniCpp_LocalSearchDecl= 0 + endif +endfunc diff --git a/VimFiles/autoload/omni/cpp/tokenizer.vim b/VimFiles/autoload/omni/cpp/tokenizer.vim new file mode 100755 index 0000000..16e0be2 --- /dev/null +++ b/VimFiles/autoload/omni/cpp/tokenizer.vim @@ -0,0 +1,93 @@ +" Description: Omni completion tokenizer +" Maintainer: Vissale NEANG +" Last Change: 26 sept. 2007 +" TODO: Generic behaviour for Tokenize() + +" From the C++ BNF +let s:cppKeyword = ['asm', 'auto', 'bool', 'break', 'case', 'catch', 'char', 'class', 'const', 'const_cast', 'continue', 'default', 'delete', 'do', 'double', 'dynamic_cast', 'else', 'enum', 'explicit', 'export', 'extern', 'false', 'float', 'for', 'friend', 'goto', 'if', 'inline', 'int', 'long', 'mutable', 'namespace', 'new', 'operator', 'private', 'protected', 'public', 'register', 'reinterpret_cast', 'return', 'short', 'signed', 'sizeof', 'static', 'static_cast', 'struct', 'switch', 'template', 'this', 'throw', 'true', 'try', 'typedef', 'typeid', 'typename', 'union', 'unsigned', 'using', 'virtual', 'void', 'volatile', 'wchar_t', 'while', 'and', 'and_eq', 'bitand', 'bitor', 'compl', 'not', 'not_eq', 'or', 'or_eq', 'xor', 'xor_eq'] + +let s:reCppKeyword = '\C\<'.join(s:cppKeyword, '\>\|\<').'\>' + +" The order of items in this list is very important because we use this list to build a regular +" expression (see below) for tokenization +let s:cppOperatorPunctuator = ['->*', '->', '--', '-=', '-', '!=', '!', '##', '#', '%:%:', '%=', '%>', '%:', '%', '&&', '&=', '&', '(', ')', '*=', '*', ',', '...', '.*', '.', '/=', '/', '::', ':>', ':', ';', '?', '[', ']', '^=', '^', '{', '||', '|=', '|', '}', '~', '++', '+=', '+', '<<=', '<%', '<:', '<<', '<=', '<', '==', '=', '>>=', '>>', '>=', '>'] + +" We build the regexp for the tokenizer +let s:reCComment = '\/\*\|\*\/' +let s:reCppComment = '\/\/' +let s:reComment = s:reCComment.'\|'.s:reCppComment +let s:reCppOperatorOrPunctuator = escape(join(s:cppOperatorPunctuator, '\|'), '*./^~[]') + + +" Tokenize a c++ code +" a token is dictionary where keys are: +" - kind = cppKeyword|cppWord|cppOperatorPunctuator|unknown|cComment|cppComment|cppDigit +" - value = 'something' +" Note: a cppWord is any word that is not a cpp keyword +function! omni#cpp#tokenizer#Tokenize(szCode) + let result = [] + + " The regexp to find a token, a token is a keyword, word or + " c++ operator or punctuator. To work properly we have to put + " spaces and tabs to our regexp. + let reTokenSearch = '\(\w\+\)\|\s\+\|'.s:reComment.'\|'.s:reCppOperatorOrPunctuator + " eg: 'using namespace std;' + " ^ ^ + " start=0 end=5 + let startPos = 0 + let endPos = matchend(a:szCode, reTokenSearch) + let len = endPos-startPos + while endPos!=-1 + " eg: 'using namespace std;' + " ^ ^ + " start=0 end=5 + " token = 'using' + " We also remove space and tabs + let token = substitute(strpart(a:szCode, startPos, len), '\s', '', 'g') + + " eg: 'using namespace std;' + " ^ ^ + " start=5 end=15 + let startPos = endPos + let endPos = matchend(a:szCode, reTokenSearch, startPos) + let len = endPos-startPos + + " It the token is empty we continue + if token=='' + continue + endif + + " Building the token + let resultToken = {'kind' : 'unknown', 'value' : token} + + " Classify the token + if token =~ '^\d\+' + " It's a digit + let resultToken.kind = 'cppDigit' + elseif token=~'^\w\+$' + " It's a word + let resultToken.kind = 'cppWord' + + " But maybe it's a c++ keyword + if match(token, s:reCppKeyword)>=0 + let resultToken.kind = 'cppKeyword' + endif + else + if match(token, s:reComment)>=0 + if index(['/*','*/'],token)>=0 + let resultToken.kind = 'cComment' + else + let resultToken.kind = 'cppComment' + endif + else + " It's an operator + let resultToken.kind = 'cppOperatorPunctuator' + endif + endif + + " We have our token, let's add it to the result list + call extend(result, [resultToken]) + endwhile + + return result +endfunc diff --git a/VimFiles/autoload/omni/cpp/utils.vim b/VimFiles/autoload/omni/cpp/utils.vim new file mode 100755 index 0000000..5d74d34 --- /dev/null +++ b/VimFiles/autoload/omni/cpp/utils.vim @@ -0,0 +1,587 @@ +" Description: Omni completion script for cpp files +" Maintainer: Vissale NEANG +" Last Change: 26 sept. 2007 + +let g:omni#cpp#utils#CACHE_TAG_INHERITS = {} +let g:omni#cpp#utils#szFilterGlobalScope = "(!has_key(v:val, 'class') && !has_key(v:val, 'struct') && !has_key(v:val, 'union') && !has_key(v:val, 'namespace')" +let g:omni#cpp#utils#szFilterGlobalScope .= "&& (!has_key(v:val, 'enum') || (has_key(v:val, 'enum') && v:val.enum =~ '^\\w\\+$')))" + +" Expression used to ignore comments +" Note: this expression drop drastically the performance +"let omni#cpp#utils#expIgnoreComments = 'match(synIDattr(synID(line("."), col("."), 1), "name"), '\CcComment')!=-1' +" This one is faster but not really good for C comments +let omni#cpp#utils#reIgnoreComment = escape('\/\/\|\/\*\|\*\/', '*/\') +let omni#cpp#utils#expIgnoreComments = 'getline(".") =~ g:omni#cpp#utils#reIgnoreComment' + +" Characters to escape in a filename for vimgrep +"TODO: Find more characters to escape +let omni#cpp#utils#szEscapedCharacters = ' %#' + +" Resolve the path of the file +" TODO: absolute file path +function! omni#cpp#utils#ResolveFilePath(szFile) + let result = '' + let listPath = split(globpath(&path, a:szFile), "\n") + if len(listPath) + let result = listPath[0] + endif + return simplify(result) +endfunc + +" Get code without comments and with empty strings +" szSingleLine must not have carriage return +function! omni#cpp#utils#GetCodeFromLine(szSingleLine) + " We set all strings to empty strings, it's safer for + " the next of the process + let szResult = substitute(a:szSingleLine, '".*"', '""', 'g') + + " Removing c++ comments, we can use the pattern ".*" because + " we are modifying a line + let szResult = substitute(szResult, '\/\/.*', '', 'g') + + " Now we have the entire code in one line and we can remove C comments + return s:RemoveCComments(szResult) +endfunc + +" Remove C comments on a line +function! s:RemoveCComments(szLine) + let result = a:szLine + + " We have to match the first '/*' and first '*/' + let startCmt = match(result, '\/\*') + let endCmt = match(result, '\*\/') + while startCmt!=-1 && endCmt!=-1 && startCmt0 + let result = result[ : startCmt-1 ] . result[ endCmt+2 : ] + else + " Case where '/*' is at the start of the line + let result = result[ endCmt+2 : ] + endif + let startCmt = match(result, '\/\*') + let endCmt = match(result, '\*\/') + endwhile + return result +endfunc + +" Get a c++ code from current buffer from [lineStart, colStart] to +" [lineEnd, colEnd] without c++ and c comments, without end of line +" and with empty strings if any +" @return a string +function! omni#cpp#utils#GetCode(posStart, posEnd) + let posStart = a:posStart + let posEnd = a:posEnd + if a:posStart[0]>a:posEnd[0] + let posStart = a:posEnd + let posEnd = a:posStart + elseif a:posStart[0]==a:posEnd[0] && a:posStart[1]>a:posEnd[1] + let posStart = a:posEnd + let posEnd = a:posStart + endif + + " Getting the lines + let lines = getline(posStart[0], posEnd[0]) + let lenLines = len(lines) + + " Formatting the result + let result = '' + if lenLines==1 + let sStart = posStart[1]-1 + let sEnd = posEnd[1]-1 + let line = lines[0] + let lenLastLine = strlen(line) + let sEnd = (sEnd>lenLastLine)?lenLastLine : sEnd + if sStart >= 0 + let result = omni#cpp#utils#GetCodeFromLine(line[ sStart : sEnd ]) + endif + elseif lenLines>1 + let sStart = posStart[1]-1 + let sEnd = posEnd[1]-1 + let lenLastLine = strlen(lines[-1]) + let sEnd = (sEnd>lenLastLine)?lenLastLine : sEnd + if sStart >= 0 + let lines[0] = lines[0][ sStart : ] + let lines[-1] = lines[-1][ : sEnd ] + for aLine in lines + let result = result . omni#cpp#utils#GetCodeFromLine(aLine)." " + endfor + let result = result[:-2] + endif + endif + + " Now we have the entire code in one line and we can remove C comments + return s:RemoveCComments(result) +endfunc + +" Extract the scope (context) of a tag item +" eg: ::MyNamespace +" @return a string of the scope. a scope from tag always starts with '::' +function! omni#cpp#utils#ExtractScope(tagItem) + let listKindScope = ['class', 'struct', 'union', 'namespace', 'enum'] + let szResult = '::' + for scope in listKindScope + if has_key(a:tagItem, scope) + let szResult = szResult . a:tagItem[scope] + break + endif + endfor + return szResult +endfunc + +" Simplify scope string, remove consecutive '::' if any +function! omni#cpp#utils#SimplifyScope(szScope) + let szResult = substitute(a:szScope, '\(::\)\+', '::', 'g') + if szResult=='::' + return szResult + else + return substitute(szResult, '::$', '', 'g') + endif +endfunc + +" Check if the cursor is in comment +function! omni#cpp#utils#IsCursorInCommentOrString() + return match(synIDattr(synID(line("."), col(".")-1, 1), "name"), '\C\=0 +endfunc + +" Tokenize the current instruction until the cursor position. +" @return list of tokens +function! omni#cpp#utils#TokenizeCurrentInstruction(...) + let szAppendText = '' + if a:0>0 + let szAppendText = a:1 + endif + + let startPos = searchpos('[;{}]\|\%^', 'bWn') + let curPos = getpos('.')[1:2] + " We don't want the character under the cursor + let column = curPos[1]-1 + let curPos[1] = (column<1)?1:column + return omni#cpp#tokenizer#Tokenize(omni#cpp#utils#GetCode(startPos, curPos)[1:] . szAppendText) +endfunc + +" Tokenize the current instruction until the word under the cursor. +" @return list of tokens +function! omni#cpp#utils#TokenizeCurrentInstructionUntilWord() + let startPos = searchpos('[;{}]\|\%^', 'bWn') + + " Saving the current cursor pos + let originalPos = getpos('.') + + " We go at the end of the word + execute 'normal gee' + let curPos = getpos('.')[1:2] + + " Restoring the original cursor pos + call setpos('.', originalPos) + + let szCode = omni#cpp#utils#GetCode(startPos, curPos)[1:] + return omni#cpp#tokenizer#Tokenize(szCode) +endfunc + +" Build parenthesis groups +" add a new key 'group' in the token +" where value is the group number of the parenthesis +" eg: (void*)(MyClass*) +" group1 group0 +" if a parenthesis is unresolved the group id is -1 +" @return a copy of a:tokens with parenthesis group +function! omni#cpp#utils#BuildParenthesisGroups(tokens) + let tokens = copy(a:tokens) + let kinds = {'(': '()', ')' : '()', '[' : '[]', ']' : '[]', '<' : '<>', '>' : '<>', '{': '{}', '}': '{}'} + let unresolved = {'()' : [], '[]': [], '<>' : [], '{}' : []} + let groupId = 0 + + " Note: we build paren group in a backward way + " because we can often have parenthesis unbalanced + " instruction + " eg: doSomething(_member.get()-> + for token in reverse(tokens) + if index([')', ']', '>', '}'], token.value)>=0 + let token['group'] = groupId + call extend(unresolved[kinds[token.value]], [token]) + let groupId+=1 + elseif index(['(', '[', '<', '{'], token.value)>=0 + if len(unresolved[kinds[token.value]]) + let tokenResolved = remove(unresolved[kinds[token.value]], -1) + let token['group'] = tokenResolved.group + else + let token['group'] = -1 + endif + endif + endfor + + return reverse(tokens) +endfunc + +" Determine if tokens represent a C cast +" @return +" - itemCast +" - itemCppCast +" - itemVariable +" - itemThis +function! omni#cpp#utils#GetCastType(tokens) + " Note: a:tokens is not modified + let tokens = omni#cpp#utils#SimplifyParenthesis(omni#cpp#utils#BuildParenthesisGroups(a:tokens)) + + if tokens[0].value == '(' + return 'itemCast' + elseif index(['static_cast', 'dynamic_cast', 'reinterpret_cast', 'const_cast'], tokens[0].value)>=0 + return 'itemCppCast' + else + for token in tokens + if token.value=='this' + return 'itemThis' + endif + endfor + return 'itemVariable' + endif +endfunc + +" Remove useless parenthesis +function! omni#cpp#utils#SimplifyParenthesis(tokens) + "Note: a:tokens is not modified + let tokens = a:tokens + " We remove useless parenthesis eg: (((MyClass))) + if len(tokens)>2 + while tokens[0].value=='(' && tokens[-1].value==')' && tokens[0].group==tokens[-1].group + let tokens = tokens[1:-2] + endwhile + endif + return tokens +endfunc + +" Function create a type info +function! omni#cpp#utils#CreateTypeInfo(param) + let type = type(a:param) + return {'type': type, 'value':a:param} +endfunc + +" Extract type info from a tag item +" eg: ::MyNamespace::MyClass +function! omni#cpp#utils#ExtractTypeInfoFromTag(tagItem) + let szTypeInfo = omni#cpp#utils#ExtractScope(a:tagItem) . '::' . substitute(a:tagItem.name, '.*::', '', 'g') + return omni#cpp#utils#SimplifyScope(szTypeInfo) +endfunc + +" Build a class inheritance list +function! omni#cpp#utils#GetClassInheritanceList(namespaces, typeInfo) + let result = [] + for tagItem in omni#cpp#utils#GetResolvedTags(a:namespaces, a:typeInfo) + call extend(result, [omni#cpp#utils#ExtractTypeInfoFromTag(tagItem)]) + endfor + return result +endfunc + +" Get class inheritance list where items in the list are tag items. +" TODO: Verify inheritance order +function! omni#cpp#utils#GetResolvedTags(namespaces, typeInfo) + let result = [] + let tagItem = omni#cpp#utils#GetResolvedTagItem(a:namespaces, a:typeInfo) + if tagItem!={} + let szTypeInfo = omni#cpp#utils#ExtractTypeInfoFromTag(tagItem) + if has_key(g:omni#cpp#utils#CACHE_TAG_INHERITS, szTypeInfo) + let result = g:omni#cpp#utils#CACHE_TAG_INHERITS[szTypeInfo] + else + call extend(result, [tagItem]) + if has_key(tagItem, 'inherits') + for baseClassTypeInfo in split(tagItem.inherits, ',') + let namespaces = [omni#cpp#utils#ExtractScope(tagItem), '::'] + call extend(result, omni#cpp#utils#GetResolvedTags(namespaces, omni#cpp#utils#CreateTypeInfo(baseClassTypeInfo))) + endfor + endif + let g:omni#cpp#utils#CACHE_TAG_INHERITS[szTypeInfo] = result + endif + endif + return result +endfunc + +" Get a tag item after a scope resolution and typedef resolution +function! omni#cpp#utils#GetResolvedTagItem(namespaces, typeInfo) + let typeInfo = {} + if type(a:typeInfo) == 1 + let typeInfo = omni#cpp#utils#CreateTypeInfo(a:typeInfo) + else + let typeInfo = a:typeInfo + endif + + let result = {} + if !omni#cpp#utils#IsTypeInfoValid(typeInfo) + return result + endif + + " Unnamed type case eg: '1::2' + if typeInfo.type == 4 + " Here there is no typedef or namespace to resolve, the tagInfo.value is a tag item + " representing a variable ('v') a member ('m') or a typedef ('t') and the typename is + " always in global scope + return typeInfo.value + endif + + " Named type case eg: 'MyNamespace::MyClass' + let szTypeInfo = omni#cpp#utils#GetTypeInfoString(typeInfo) + + " Resolving namespace alias + " TODO: For the next release + "let szTypeInfo = omni#cpp#namespaces#ResolveAlias(g:omni#cpp#namespaces#CacheAlias, szTypeInfo) + + if szTypeInfo=='::' + return result + endif + + " We can only get members of class, struct, union and namespace + let szTagFilter = "index(['c', 's', 'u', 'n', 't'], v:val.kind[0])>=0" + let szTagQuery = szTypeInfo + + if s:IsTypeInfoResolved(szTypeInfo) + " The type info is already resolved, we remove the starting '::' + let szTagQuery = substitute(szTypeInfo, '^::', '', 'g') + if len(split(szTagQuery, '::'))==1 + " eg: ::MyClass + " Here we have to get tags that have no parent scope + " That's why we change the szTagFilter + let szTagFilter .= '&& ' . g:omni#cpp#utils#szFilterGlobalScope + let tagList = omni#common#utils#TagListNoThrow('^'.szTagQuery.'$') + call filter(tagList, szTagFilter) + if len(tagList) + let result = tagList[0] + endif + else + " eg: ::MyNamespace::MyClass + let tagList = omni#common#utils#TagListNoThrow('^'.szTagQuery.'$') + call filter(tagList, szTagFilter) + + if len(tagList) + let result = tagList[0] + endif + endif + else + " The type is not resolved + let tagList = omni#common#utils#TagListNoThrow('^'.szTagQuery.'$') + call filter(tagList, szTagFilter) + + if len(tagList) + " Resolving scope (namespace, nested class etc...) + let szScopeOfTypeInfo = s:ExtractScopeFromTypeInfo(szTypeInfo) + if s:IsTypeInfoResolved(szTypeInfo) + let result = s:GetTagOfSameScope(tagList, szScopeOfTypeInfo) + else + " For each namespace of the namespace list we try to get a tag + " that can be in the same scope + if g:OmniCpp_NamespaceSearch && &filetype != 'c' + for scope in a:namespaces + let szTmpScope = omni#cpp#utils#SimplifyScope(scope.'::'.szScopeOfTypeInfo) + let result = s:GetTagOfSameScope(tagList, szTmpScope) + if result!={} + break + endif + endfor + else + let szTmpScope = omni#cpp#utils#SimplifyScope('::'.szScopeOfTypeInfo) + let result = s:GetTagOfSameScope(tagList, szTmpScope) + endif + endif + endif + endif + + if result!={} + " We have our tagItem but maybe it's a typedef or an unnamed type + if result.kind[0]=='t' + " Here we can have a typedef to another typedef, a class, struct, union etc + " but we can also have a typedef to an unnamed type, in that + " case the result contains a 'typeref' key + let namespaces = [omni#cpp#utils#ExtractScope(result), '::'] + if has_key(result, 'typeref') + let result = omni#cpp#utils#GetResolvedTagItem(namespaces, omni#cpp#utils#CreateTypeInfo(result)) + else + let szCmd = omni#cpp#utils#ExtractCmdFromTagItem(result) + let szCode = substitute(omni#cpp#utils#GetCodeFromLine(szCmd), '\C\<'.result.name.'\>.*', '', 'g') + let szTypeInfo = omni#cpp#utils#ExtractTypeInfoFromTokens(omni#cpp#tokenizer#Tokenize(szCode)) + let result = omni#cpp#utils#GetResolvedTagItem(namespaces, omni#cpp#utils#CreateTypeInfo(szTypeInfo)) + " TODO: Namespace resolution for result + endif + endif + endif + + return result +endfunc + +" Returns if the type info is valid +" @return +" - 1 if valid +" - 0 otherwise +function! omni#cpp#utils#IsTypeInfoValid(typeInfo) + if a:typeInfo=={} + return 0 + else + if a:typeInfo.type == 1 && a:typeInfo.value=='' + " String case + return 0 + elseif a:typeInfo.type == 4 && a:typeInfo.value=={} + " Dictionary case + return 0 + endif + endif + return 1 +endfunc + +" Get the string of the type info +function! omni#cpp#utils#GetTypeInfoString(typeInfo) + if a:typeInfo.type == 1 + return a:typeInfo.value + else + return substitute(a:typeInfo.value.typeref, '^\w\+:', '', 'g') + endif +endfunc + +" A resolved type info starts with '::' +" @return +" - 1 if type info starts with '::' +" - 0 otherwise +function! s:IsTypeInfoResolved(szTypeInfo) + return match(a:szTypeInfo, '^::')!=-1 +endfunc + +" A returned type info's scope may not have the global namespace '::' +" eg: '::NameSpace1::NameSpace2::MyClass' => '::NameSpace1::NameSpace2' +" 'NameSpace1::NameSpace2::MyClass' => 'NameSpace1::NameSpace2' +function! s:ExtractScopeFromTypeInfo(szTypeInfo) + let szScope = substitute(a:szTypeInfo, '\w\+$', '', 'g') + if szScope =='::' + return szScope + else + return substitute(szScope, '::$', '', 'g') + endif +endfunc + +" @return +" - the tag with the same scope +" - {} otherwise +function! s:GetTagOfSameScope(listTags, szScopeToMatch) + for tagItem in a:listTags + let szScopeOfTag = omni#cpp#utils#ExtractScope(tagItem) + if szScopeOfTag == a:szScopeToMatch + return tagItem + endif + endfor + return {} +endfunc + +" Extract the cmd of a tag item without regexp +function! omni#cpp#utils#ExtractCmdFromTagItem(tagItem) + let line = a:tagItem.cmd + let re = '\(\/\^\)\|\(\$\/\)' + if match(line, re)!=-1 + let line = substitute(line, re, '', 'g') + return line + else + " TODO: the cmd is a line number + return '' + endif +endfunc + +" Extract type from tokens. +" eg: examples of tokens format +" 'const MyClass&' +" 'const map < int, int >&' +" 'MyNs::MyClass' +" '::MyClass**' +" 'MyClass a, *b = NULL, c[1] = {}; +" 'hello(MyClass a, MyClass* b' +" @return the type info string eg: ::std::map +" can be empty +function! omni#cpp#utils#ExtractTypeInfoFromTokens(tokens) + let szResult = '' + let state = 0 + + let tokens = omni#cpp#utils#BuildParenthesisGroups(a:tokens) + + " If there is an unbalanced parenthesis we are in a parameter list + let bParameterList = 0 + for token in tokens + if token.value == '(' && token.group==-1 + let bParameterList = 1 + break + endif + endfor + + if bParameterList + let tokens = reverse(tokens) + let state = 0 + let parenGroup = -1 + for token in tokens + if state==0 + if token.value=='>' + let parenGroup = token.group + let state=1 + elseif token.kind == 'cppWord' + let szResult = token.value.szResult + let state=2 + elseif index(['*', '&'], token.value)<0 + break + endif + elseif state==1 + if token.value=='<' && token.group==parenGroup + let state=0 + endif + elseif state==2 + if token.value=='::' + let szResult = token.value.szResult + let state=3 + else + break + endif + elseif state==3 + if token.kind == 'cppWord' + let szResult = token.value.szResult + let state=2 + else + break + endif + endif + endfor + return szResult + endif + + for token in tokens + if state==0 + if token.value == '::' + let szResult .= token.value + let state = 1 + elseif token.kind == 'cppWord' + let szResult .= token.value + let state = 2 + " Maybe end of token + endif + elseif state==1 + if token.kind == 'cppWord' + let szResult .= token.value + let state = 2 + " Maybe end of token + else + break + endif + elseif state==2 + if token.value == '::' + let szResult .= token.value + let state = 1 + else + break + endif + endif + endfor + return szResult +endfunc + +" Get the preview window string +function! omni#cpp#utils#GetPreviewWindowStringFromTagItem(tagItem) + let szResult = '' + + let szResult .= 'name: '.a:tagItem.name."\n" + for tagKey in keys(a:tagItem) + if index(['name', 'static'], tagKey)>=0 + continue + endif + let szResult .= tagKey.': '.a:tagItem[tagKey]."\n" + endfor + + return substitute(szResult, "\n$", '', 'g') +endfunc diff --git a/VimFiles/autoload/plug.vim b/VimFiles/autoload/plug.vim new file mode 100644 index 0000000..9ebcf53 --- /dev/null +++ b/VimFiles/autoload/plug.vim @@ -0,0 +1,2504 @@ +" vim-plug: Vim plugin manager +" ============================ +" +" Download plug.vim and put it in ~/.vim/autoload +" +" curl -fLo ~/.vim/autoload/plug.vim --create-dirs \ +" https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim +" +" Edit your .vimrc +" +" call plug#begin('~/.vim/plugged') +" +" " Make sure you use single quotes +" +" " Shorthand notation; fetches https://github.com/junegunn/vim-easy-align +" Plug 'junegunn/vim-easy-align' +" +" " Any valid git URL is allowed +" Plug 'https://github.com/junegunn/vim-github-dashboard.git' +" +" " Multiple Plug commands can be written in a single line using | separators +" Plug 'SirVer/ultisnips' | Plug 'honza/vim-snippets' +" +" " On-demand loading +" Plug 'scrooloose/nerdtree', { 'on': 'NERDTreeToggle' } +" Plug 'tpope/vim-fireplace', { 'for': 'clojure' } +" +" " Using a non-master branch +" Plug 'rdnetto/YCM-Generator', { 'branch': 'stable' } +" +" " Using a tagged release; wildcard allowed (requires git 1.9.2 or above) +" Plug 'fatih/vim-go', { 'tag': '*' } +" +" " Plugin options +" Plug 'nsf/gocode', { 'tag': 'v.20150303', 'rtp': 'vim' } +" +" " Plugin outside ~/.vim/plugged with post-update hook +" Plug 'junegunn/fzf', { 'dir': '~/.fzf', 'do': './install --all' } +" +" " Unmanaged plugin (manually installed and updated) +" Plug '~/my-prototype-plugin' +" +" " Initialize plugin system +" call plug#end() +" +" Then reload .vimrc and :PlugInstall to install plugins. +" +" Plug options: +" +"| Option | Description | +"| ----------------------- | ------------------------------------------------ | +"| `branch`/`tag`/`commit` | Branch/tag/commit of the repository to use | +"| `rtp` | Subdirectory that contains Vim plugin | +"| `dir` | Custom directory for the plugin | +"| `as` | Use different name for the plugin | +"| `do` | Post-update hook (string or funcref) | +"| `on` | On-demand loading: Commands or ``-mappings | +"| `for` | On-demand loading: File types | +"| `frozen` | Do not update unless explicitly specified | +" +" More information: https://github.com/junegunn/vim-plug +" +" +" Copyright (c) 2017 Junegunn Choi +" +" MIT License +" +" Permission is hereby granted, free of charge, to any person obtaining +" a copy of this software and associated documentation files (the +" "Software"), to deal in the Software without restriction, including +" without limitation the rights to use, copy, modify, merge, publish, +" distribute, sublicense, and/or sell copies of the Software, and to +" permit persons to whom the Software is furnished to do so, subject to +" the following conditions: +" +" The above copyright notice and this permission notice shall be +" included in all copies or substantial portions of the Software. +" +" THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +" EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +" MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +" NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +" LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +" OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +" WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +if exists('g:loaded_plug') + finish +endif +let g:loaded_plug = 1 + +let s:cpo_save = &cpo +set cpo&vim + +let s:plug_src = 'https://github.com/junegunn/vim-plug.git' +let s:plug_tab = get(s:, 'plug_tab', -1) +let s:plug_buf = get(s:, 'plug_buf', -1) +let s:mac_gui = has('gui_macvim') && has('gui_running') +let s:is_win = has('win32') || has('win64') +let s:nvim = has('nvim-0.2') || (has('nvim') && exists('*jobwait') && !s:is_win) +let s:vim8 = has('patch-8.0.0039') && exists('*job_start') +let s:me = resolve(expand(':p')) +let s:base_spec = { 'branch': 'master', 'frozen': 0 } +let s:TYPE = { +\ 'string': type(''), +\ 'list': type([]), +\ 'dict': type({}), +\ 'funcref': type(function('call')) +\ } +let s:loaded = get(s:, 'loaded', {}) +let s:triggers = get(s:, 'triggers', {}) + +function! plug#begin(...) + if a:0 > 0 + let s:plug_home_org = a:1 + let home = s:path(fnamemodify(expand(a:1), ':p')) + elseif exists('g:plug_home') + let home = s:path(g:plug_home) + elseif !empty(&rtp) + let home = s:path(split(&rtp, ',')[0]) . '/plugged' + else + return s:err('Unable to determine plug home. Try calling plug#begin() with a path argument.') + endif + if fnamemodify(home, ':t') ==# 'plugin' && fnamemodify(home, ':h') ==# s:first_rtp + return s:err('Invalid plug home. '.home.' is a standard Vim runtime path and is not allowed.') + endif + + let g:plug_home = home + let g:plugs = {} + let g:plugs_order = [] + let s:triggers = {} + + call s:define_commands() + return 1 +endfunction + +function! s:define_commands() + command! -nargs=+ -bar Plug call plug#() + if !executable('git') + return s:err('`git` executable not found. Most commands will not be available. To suppress this message, prepend `silent!` to `call plug#begin(...)`.') + endif + command! -nargs=* -bar -bang -complete=customlist,s:names PlugInstall call s:install(0, []) + command! -nargs=* -bar -bang -complete=customlist,s:names PlugUpdate call s:update(0, []) + command! -nargs=0 -bar -bang PlugClean call s:clean(0) + command! -nargs=0 -bar PlugUpgrade if s:upgrade() | execute 'source' s:esc(s:me) | endif + command! -nargs=0 -bar PlugStatus call s:status() + command! -nargs=0 -bar PlugDiff call s:diff() + command! -nargs=? -bar -bang -complete=file PlugSnapshot call s:snapshot(0, ) +endfunction + +function! s:to_a(v) + return type(a:v) == s:TYPE.list ? a:v : [a:v] +endfunction + +function! s:to_s(v) + return type(a:v) == s:TYPE.string ? a:v : join(a:v, "\n") . "\n" +endfunction + +function! s:glob(from, pattern) + return s:lines(globpath(a:from, a:pattern)) +endfunction + +function! s:source(from, ...) + let found = 0 + for pattern in a:000 + for vim in s:glob(a:from, pattern) + execute 'source' s:esc(vim) + let found = 1 + endfor + endfor + return found +endfunction + +function! s:assoc(dict, key, val) + let a:dict[a:key] = add(get(a:dict, a:key, []), a:val) +endfunction + +function! s:ask(message, ...) + call inputsave() + echohl WarningMsg + let answer = input(a:message.(a:0 ? ' (y/N/a) ' : ' (y/N) ')) + echohl None + call inputrestore() + echo "\r" + return (a:0 && answer =~? '^a') ? 2 : (answer =~? '^y') ? 1 : 0 +endfunction + +function! s:ask_no_interrupt(...) + try + return call('s:ask', a:000) + catch + return 0 + endtry +endfunction + +function! plug#end() + if !exists('g:plugs') + return s:err('Call plug#begin() first') + endif + + if exists('#PlugLOD') + augroup PlugLOD + autocmd! + augroup END + augroup! PlugLOD + endif + let lod = { 'ft': {}, 'map': {}, 'cmd': {} } + + if exists('g:did_load_filetypes') + filetype off + endif + for name in g:plugs_order + if !has_key(g:plugs, name) + continue + endif + let plug = g:plugs[name] + if get(s:loaded, name, 0) || !has_key(plug, 'on') && !has_key(plug, 'for') + let s:loaded[name] = 1 + continue + endif + + if has_key(plug, 'on') + let s:triggers[name] = { 'map': [], 'cmd': [] } + for cmd in s:to_a(plug.on) + if cmd =~? '^.\+' + if empty(mapcheck(cmd)) && empty(mapcheck(cmd, 'i')) + call s:assoc(lod.map, cmd, name) + endif + call add(s:triggers[name].map, cmd) + elseif cmd =~# '^[A-Z]' + let cmd = substitute(cmd, '!*$', '', '') + if exists(':'.cmd) != 2 + call s:assoc(lod.cmd, cmd, name) + endif + call add(s:triggers[name].cmd, cmd) + else + call s:err('Invalid `on` option: '.cmd. + \ '. Should start with an uppercase letter or ``.') + endif + endfor + endif + + if has_key(plug, 'for') + let types = s:to_a(plug.for) + if !empty(types) + augroup filetypedetect + call s:source(s:rtp(plug), 'ftdetect/**/*.vim', 'after/ftdetect/**/*.vim') + augroup END + endif + for type in types + call s:assoc(lod.ft, type, name) + endfor + endif + endfor + + for [cmd, names] in items(lod.cmd) + execute printf( + \ 'command! -nargs=* -range -bang -complete=file %s call s:lod_cmd(%s, "", , , , %s)', + \ cmd, string(cmd), string(names)) + endfor + + for [map, names] in items(lod.map) + for [mode, map_prefix, key_prefix] in + \ [['i', '', ''], ['n', '', ''], ['v', '', 'gv'], ['o', '', '']] + execute printf( + \ '%snoremap %s %s:call lod_map(%s, %s, %s, "%s")', + \ mode, map, map_prefix, string(map), string(names), mode != 'i', key_prefix) + endfor + endfor + + for [ft, names] in items(lod.ft) + augroup PlugLOD + execute printf('autocmd FileType %s call lod_ft(%s, %s)', + \ ft, string(ft), string(names)) + augroup END + endfor + + call s:reorg_rtp() + filetype plugin indent on + if has('vim_starting') + if has('syntax') && !exists('g:syntax_on') + syntax enable + end + else + call s:reload_plugins() + endif +endfunction + +function! s:loaded_names() + return filter(copy(g:plugs_order), 'get(s:loaded, v:val, 0)') +endfunction + +function! s:load_plugin(spec) + call s:source(s:rtp(a:spec), 'plugin/**/*.vim', 'after/plugin/**/*.vim') +endfunction + +function! s:reload_plugins() + for name in s:loaded_names() + call s:load_plugin(g:plugs[name]) + endfor +endfunction + +function! s:trim(str) + return substitute(a:str, '[\/]\+$', '', '') +endfunction + +function! s:version_requirement(val, min) + for idx in range(0, len(a:min) - 1) + let v = get(a:val, idx, 0) + if v < a:min[idx] | return 0 + elseif v > a:min[idx] | return 1 + endif + endfor + return 1 +endfunction + +function! s:git_version_requirement(...) + if !exists('s:git_version') + let s:git_version = map(split(split(s:system('git --version'))[2], '\.'), 'str2nr(v:val)') + endif + return s:version_requirement(s:git_version, a:000) +endfunction + +function! s:progress_opt(base) + return a:base && !s:is_win && + \ s:git_version_requirement(1, 7, 1) ? '--progress' : '' +endfunction + +if s:is_win + function! s:rtp(spec) + return s:path(a:spec.dir . get(a:spec, 'rtp', '')) + endfunction + + function! s:path(path) + return s:trim(substitute(a:path, '/', '\', 'g')) + endfunction + + function! s:dirpath(path) + return s:path(a:path) . '\' + endfunction + + function! s:is_local_plug(repo) + return a:repo =~? '^[a-z]:\|^[%~]' + endfunction +else + function! s:rtp(spec) + return s:dirpath(a:spec.dir . get(a:spec, 'rtp', '')) + endfunction + + function! s:path(path) + return s:trim(a:path) + endfunction + + function! s:dirpath(path) + return substitute(a:path, '[/\\]*$', '/', '') + endfunction + + function! s:is_local_plug(repo) + return a:repo[0] =~ '[/$~]' + endfunction +endif + +function! s:err(msg) + echohl ErrorMsg + echom '[vim-plug] '.a:msg + echohl None +endfunction + +function! s:warn(cmd, msg) + echohl WarningMsg + execute a:cmd 'a:msg' + echohl None +endfunction + +function! s:esc(path) + return escape(a:path, ' ') +endfunction + +function! s:escrtp(path) + return escape(a:path, ' ,') +endfunction + +function! s:remove_rtp() + for name in s:loaded_names() + let rtp = s:rtp(g:plugs[name]) + execute 'set rtp-='.s:escrtp(rtp) + let after = globpath(rtp, 'after') + if isdirectory(after) + execute 'set rtp-='.s:escrtp(after) + endif + endfor +endfunction + +function! s:reorg_rtp() + if !empty(s:first_rtp) + execute 'set rtp-='.s:first_rtp + execute 'set rtp-='.s:last_rtp + endif + + " &rtp is modified from outside + if exists('s:prtp') && s:prtp !=# &rtp + call s:remove_rtp() + unlet! s:middle + endif + + let s:middle = get(s:, 'middle', &rtp) + let rtps = map(s:loaded_names(), 's:rtp(g:plugs[v:val])') + let afters = filter(map(copy(rtps), 'globpath(v:val, "after")'), '!empty(v:val)') + let rtp = join(map(rtps, 'escape(v:val, ",")'), ',') + \ . ','.s:middle.',' + \ . join(map(afters, 'escape(v:val, ",")'), ',') + let &rtp = substitute(substitute(rtp, ',,*', ',', 'g'), '^,\|,$', '', 'g') + let s:prtp = &rtp + + if !empty(s:first_rtp) + execute 'set rtp^='.s:first_rtp + execute 'set rtp+='.s:last_rtp + endif +endfunction + +function! s:doautocmd(...) + if exists('#'.join(a:000, '#')) + execute 'doautocmd' ((v:version > 703 || has('patch442')) ? '' : '') join(a:000) + endif +endfunction + +function! s:dobufread(names) + for name in a:names + let path = s:rtp(g:plugs[name]).'/**' + for dir in ['ftdetect', 'ftplugin'] + if len(finddir(dir, path)) + if exists('#BufRead') + doautocmd BufRead + endif + return + endif + endfor + endfor +endfunction + +function! plug#load(...) + if a:0 == 0 + return s:err('Argument missing: plugin name(s) required') + endif + if !exists('g:plugs') + return s:err('plug#begin was not called') + endif + let names = a:0 == 1 && type(a:1) == s:TYPE.list ? a:1 : a:000 + let unknowns = filter(copy(names), '!has_key(g:plugs, v:val)') + if !empty(unknowns) + let s = len(unknowns) > 1 ? 's' : '' + return s:err(printf('Unknown plugin%s: %s', s, join(unknowns, ', '))) + end + let unloaded = filter(copy(names), '!get(s:loaded, v:val, 0)') + if !empty(unloaded) + for name in unloaded + call s:lod([name], ['ftdetect', 'after/ftdetect', 'plugin', 'after/plugin']) + endfor + call s:dobufread(unloaded) + return 1 + end + return 0 +endfunction + +function! s:remove_triggers(name) + if !has_key(s:triggers, a:name) + return + endif + for cmd in s:triggers[a:name].cmd + execute 'silent! delc' cmd + endfor + for map in s:triggers[a:name].map + execute 'silent! unmap' map + execute 'silent! iunmap' map + endfor + call remove(s:triggers, a:name) +endfunction + +function! s:lod(names, types, ...) + for name in a:names + call s:remove_triggers(name) + let s:loaded[name] = 1 + endfor + call s:reorg_rtp() + + for name in a:names + let rtp = s:rtp(g:plugs[name]) + for dir in a:types + call s:source(rtp, dir.'/**/*.vim') + endfor + if a:0 + if !s:source(rtp, a:1) && !empty(s:glob(rtp, a:2)) + execute 'runtime' a:1 + endif + call s:source(rtp, a:2) + endif + call s:doautocmd('User', name) + endfor +endfunction + +function! s:lod_ft(pat, names) + let syn = 'syntax/'.a:pat.'.vim' + call s:lod(a:names, ['plugin', 'after/plugin'], syn, 'after/'.syn) + execute 'autocmd! PlugLOD FileType' a:pat + call s:doautocmd('filetypeplugin', 'FileType') + call s:doautocmd('filetypeindent', 'FileType') +endfunction + +function! s:lod_cmd(cmd, bang, l1, l2, args, names) + call s:lod(a:names, ['ftdetect', 'after/ftdetect', 'plugin', 'after/plugin']) + call s:dobufread(a:names) + execute printf('%s%s%s %s', (a:l1 == a:l2 ? '' : (a:l1.','.a:l2)), a:cmd, a:bang, a:args) +endfunction + +function! s:lod_map(map, names, with_prefix, prefix) + call s:lod(a:names, ['ftdetect', 'after/ftdetect', 'plugin', 'after/plugin']) + call s:dobufread(a:names) + let extra = '' + while 1 + let c = getchar(0) + if c == 0 + break + endif + let extra .= nr2char(c) + endwhile + + if a:with_prefix + let prefix = v:count ? v:count : '' + let prefix .= '"'.v:register.a:prefix + if mode(1) == 'no' + if v:operator == 'c' + let prefix = "\" . prefix + endif + let prefix .= v:operator + endif + call feedkeys(prefix, 'n') + endif + call feedkeys(substitute(a:map, '^', "\", '') . extra) +endfunction + +function! plug#(repo, ...) + if a:0 > 1 + return s:err('Invalid number of arguments (1..2)') + endif + + try + let repo = s:trim(a:repo) + let opts = a:0 == 1 ? s:parse_options(a:1) : s:base_spec + let name = get(opts, 'as', fnamemodify(repo, ':t:s?\.git$??')) + let spec = extend(s:infer_properties(name, repo), opts) + if !has_key(g:plugs, name) + call add(g:plugs_order, name) + endif + let g:plugs[name] = spec + let s:loaded[name] = get(s:loaded, name, 0) + catch + return s:err(v:exception) + endtry +endfunction + +function! s:parse_options(arg) + let opts = copy(s:base_spec) + let type = type(a:arg) + if type == s:TYPE.string + let opts.tag = a:arg + elseif type == s:TYPE.dict + call extend(opts, a:arg) + if has_key(opts, 'dir') + let opts.dir = s:dirpath(expand(opts.dir)) + endif + else + throw 'Invalid argument type (expected: string or dictionary)' + endif + return opts +endfunction + +function! s:infer_properties(name, repo) + let repo = a:repo + if s:is_local_plug(repo) + return { 'dir': s:dirpath(expand(repo)) } + else + if repo =~ ':' + let uri = repo + else + if repo !~ '/' + throw printf('Invalid argument: %s (implicit `vim-scripts'' expansion is deprecated)', repo) + endif + let fmt = get(g:, 'plug_url_format', 'https://git::@github.com/%s.git') + let uri = printf(fmt, repo) + endif + return { 'dir': s:dirpath(g:plug_home.'/'.a:name), 'uri': uri } + endif +endfunction + +function! s:install(force, names) + call s:update_impl(0, a:force, a:names) +endfunction + +function! s:update(force, names) + call s:update_impl(1, a:force, a:names) +endfunction + +function! plug#helptags() + if !exists('g:plugs') + return s:err('plug#begin was not called') + endif + for spec in values(g:plugs) + let docd = join([s:rtp(spec), 'doc'], '/') + if isdirectory(docd) + silent! execute 'helptags' s:esc(docd) + endif + endfor + return 1 +endfunction + +function! s:syntax() + syntax clear + syntax region plug1 start=/\%1l/ end=/\%2l/ contains=plugNumber + syntax region plug2 start=/\%2l/ end=/\%3l/ contains=plugBracket,plugX + syn match plugNumber /[0-9]\+[0-9.]*/ contained + syn match plugBracket /[[\]]/ contained + syn match plugX /x/ contained + syn match plugDash /^-/ + syn match plugPlus /^+/ + syn match plugStar /^*/ + syn match plugMessage /\(^- \)\@<=.*/ + syn match plugName /\(^- \)\@<=[^ ]*:/ + syn match plugSha /\%(: \)\@<=[0-9a-f]\{4,}$/ + syn match plugTag /(tag: [^)]\+)/ + syn match plugInstall /\(^+ \)\@<=[^:]*/ + syn match plugUpdate /\(^* \)\@<=[^:]*/ + syn match plugCommit /^ \X*[0-9a-f]\{7,9} .*/ contains=plugRelDate,plugEdge,plugTag + syn match plugEdge /^ \X\+$/ + syn match plugEdge /^ \X*/ contained nextgroup=plugSha + syn match plugSha /[0-9a-f]\{7,9}/ contained + syn match plugRelDate /([^)]*)$/ contained + syn match plugNotLoaded /(not loaded)$/ + syn match plugError /^x.*/ + syn region plugDeleted start=/^\~ .*/ end=/^\ze\S/ + syn match plugH2 /^.*:\n-\+$/ + syn keyword Function PlugInstall PlugStatus PlugUpdate PlugClean + hi def link plug1 Title + hi def link plug2 Repeat + hi def link plugH2 Type + hi def link plugX Exception + hi def link plugBracket Structure + hi def link plugNumber Number + + hi def link plugDash Special + hi def link plugPlus Constant + hi def link plugStar Boolean + + hi def link plugMessage Function + hi def link plugName Label + hi def link plugInstall Function + hi def link plugUpdate Type + + hi def link plugError Error + hi def link plugDeleted Ignore + hi def link plugRelDate Comment + hi def link plugEdge PreProc + hi def link plugSha Identifier + hi def link plugTag Constant + + hi def link plugNotLoaded Comment +endfunction + +function! s:lpad(str, len) + return a:str . repeat(' ', a:len - len(a:str)) +endfunction + +function! s:lines(msg) + return split(a:msg, "[\r\n]") +endfunction + +function! s:lastline(msg) + return get(s:lines(a:msg), -1, '') +endfunction + +function! s:new_window() + execute get(g:, 'plug_window', 'vertical topleft new') +endfunction + +function! s:plug_window_exists() + let buflist = tabpagebuflist(s:plug_tab) + return !empty(buflist) && index(buflist, s:plug_buf) >= 0 +endfunction + +function! s:switch_in() + if !s:plug_window_exists() + return 0 + endif + + if winbufnr(0) != s:plug_buf + let s:pos = [tabpagenr(), winnr(), winsaveview()] + execute 'normal!' s:plug_tab.'gt' + let winnr = bufwinnr(s:plug_buf) + execute winnr.'wincmd w' + call add(s:pos, winsaveview()) + else + let s:pos = [winsaveview()] + endif + + setlocal modifiable + return 1 +endfunction + +function! s:switch_out(...) + call winrestview(s:pos[-1]) + setlocal nomodifiable + if a:0 > 0 + execute a:1 + endif + + if len(s:pos) > 1 + execute 'normal!' s:pos[0].'gt' + execute s:pos[1] 'wincmd w' + call winrestview(s:pos[2]) + endif +endfunction + +function! s:finish_bindings() + nnoremap R :call retry() + nnoremap D :PlugDiff + nnoremap S :PlugStatus + nnoremap U :call status_update() + xnoremap U :call status_update() + nnoremap ]] :silent! call section('') + nnoremap [[ :silent! call section('b') +endfunction + +function! s:prepare(...) + if empty(getcwd()) + throw 'Invalid current working directory. Cannot proceed.' + endif + + for evar in ['$GIT_DIR', '$GIT_WORK_TREE'] + if exists(evar) + throw evar.' detected. Cannot proceed.' + endif + endfor + + call s:job_abort() + if s:switch_in() + if b:plug_preview == 1 + pc + endif + enew + else + call s:new_window() + endif + + nnoremap q :if b:plug_preview==1pcendifbd + if a:0 == 0 + call s:finish_bindings() + endif + let b:plug_preview = -1 + let s:plug_tab = tabpagenr() + let s:plug_buf = winbufnr(0) + call s:assign_name() + + for k in ['', 'L', 'o', 'X', 'd', 'dd'] + execute 'silent! unmap ' k + endfor + setlocal buftype=nofile bufhidden=wipe nobuflisted nolist noswapfile nowrap cursorline modifiable nospell + setf vim-plug + if exists('g:syntax_on') + call s:syntax() + endif +endfunction + +function! s:assign_name() + " Assign buffer name + let prefix = '[Plugins]' + let name = prefix + let idx = 2 + while bufexists(name) + let name = printf('%s (%s)', prefix, idx) + let idx = idx + 1 + endwhile + silent! execute 'f' fnameescape(name) +endfunction + +function! s:chsh(swap) + let prev = [&shell, &shellcmdflag, &shellredir] + if s:is_win + set shell=cmd.exe shellcmdflag=/c shellredir=>%s\ 2>&1 + elseif a:swap + set shell=sh shellredir=>%s\ 2>&1 + endif + return prev +endfunction + +function! s:bang(cmd, ...) + try + let [sh, shellcmdflag, shrd] = s:chsh(a:0) + " FIXME: Escaping is incomplete. We could use shellescape with eval, + " but it won't work on Windows. + let cmd = a:0 ? s:with_cd(a:cmd, a:1) : a:cmd + if s:is_win + let batchfile = tempname().'.bat' + call writefile(['@echo off', cmd], batchfile) + let cmd = batchfile + endif + let g:_plug_bang = (s:is_win && has('gui_running') ? 'silent ' : '').'!'.escape(cmd, '#!%') + execute "normal! :execute g:_plug_bang\\" + finally + unlet g:_plug_bang + let [&shell, &shellcmdflag, &shellredir] = [sh, shellcmdflag, shrd] + if s:is_win + call delete(batchfile) + endif + endtry + return v:shell_error ? 'Exit status: ' . v:shell_error : '' +endfunction + +function! s:regress_bar() + let bar = substitute(getline(2)[1:-2], '.*\zs=', 'x', '') + call s:progress_bar(2, bar, len(bar)) +endfunction + +function! s:is_updated(dir) + return !empty(s:system_chomp('git log --pretty=format:"%h" "HEAD...HEAD@{1}"', a:dir)) +endfunction + +function! s:do(pull, force, todo) + for [name, spec] in items(a:todo) + if !isdirectory(spec.dir) + continue + endif + let installed = has_key(s:update.new, name) + let updated = installed ? 0 : + \ (a:pull && index(s:update.errors, name) < 0 && s:is_updated(spec.dir)) + if a:force || installed || updated + execute 'cd' s:esc(spec.dir) + call append(3, '- Post-update hook for '. name .' ... ') + let error = '' + let type = type(spec.do) + if type == s:TYPE.string + if spec.do[0] == ':' + if !get(s:loaded, name, 0) + let s:loaded[name] = 1 + call s:reorg_rtp() + endif + call s:load_plugin(spec) + try + execute spec.do[1:] + catch + let error = v:exception + endtry + if !s:plug_window_exists() + cd - + throw 'Warning: vim-plug was terminated by the post-update hook of '.name + endif + else + let error = s:bang(spec.do) + endif + elseif type == s:TYPE.funcref + try + let status = installed ? 'installed' : (updated ? 'updated' : 'unchanged') + call spec.do({ 'name': name, 'status': status, 'force': a:force }) + catch + let error = v:exception + endtry + else + let error = 'Invalid hook type' + endif + call s:switch_in() + call setline(4, empty(error) ? (getline(4) . 'OK') + \ : ('x' . getline(4)[1:] . error)) + if !empty(error) + call add(s:update.errors, name) + call s:regress_bar() + endif + cd - + endif + endfor +endfunction + +function! s:hash_match(a, b) + return stridx(a:a, a:b) == 0 || stridx(a:b, a:a) == 0 +endfunction + +function! s:checkout(spec) + let sha = a:spec.commit + let output = s:system('git rev-parse HEAD', a:spec.dir) + if !v:shell_error && !s:hash_match(sha, s:lines(output)[0]) + let output = s:system( + \ 'git fetch --depth 999999 && git checkout '.s:esc(sha).' --', a:spec.dir) + endif + return output +endfunction + +function! s:finish(pull) + let new_frozen = len(filter(keys(s:update.new), 'g:plugs[v:val].frozen')) + if new_frozen + let s = new_frozen > 1 ? 's' : '' + call append(3, printf('- Installed %d frozen plugin%s', new_frozen, s)) + endif + call append(3, '- Finishing ... ') | 4 + redraw + call plug#helptags() + call plug#end() + call setline(4, getline(4) . 'Done!') + redraw + let msgs = [] + if !empty(s:update.errors) + call add(msgs, "Press 'R' to retry.") + endif + if a:pull && len(s:update.new) < len(filter(getline(5, '$'), + \ "v:val =~ '^- ' && v:val !~# 'Already up.to.date'")) + call add(msgs, "Press 'D' to see the updated changes.") + endif + echo join(msgs, ' ') + call s:finish_bindings() +endfunction + +function! s:retry() + if empty(s:update.errors) + return + endif + echo + call s:update_impl(s:update.pull, s:update.force, + \ extend(copy(s:update.errors), [s:update.threads])) +endfunction + +function! s:is_managed(name) + return has_key(g:plugs[a:name], 'uri') +endfunction + +function! s:names(...) + return sort(filter(keys(g:plugs), 'stridx(v:val, a:1) == 0 && s:is_managed(v:val)')) +endfunction + +function! s:check_ruby() + silent! ruby require 'thread'; VIM::command("let g:plug_ruby = '#{RUBY_VERSION}'") + if !exists('g:plug_ruby') + redraw! + return s:warn('echom', 'Warning: Ruby interface is broken') + endif + let ruby_version = split(g:plug_ruby, '\.') + unlet g:plug_ruby + return s:version_requirement(ruby_version, [1, 8, 7]) +endfunction + +function! s:update_impl(pull, force, args) abort + let sync = index(a:args, '--sync') >= 0 || has('vim_starting') + let args = filter(copy(a:args), 'v:val != "--sync"') + let threads = (len(args) > 0 && args[-1] =~ '^[1-9][0-9]*$') ? + \ remove(args, -1) : get(g:, 'plug_threads', 16) + + let managed = filter(copy(g:plugs), 's:is_managed(v:key)') + let todo = empty(args) ? filter(managed, '!v:val.frozen || !isdirectory(v:val.dir)') : + \ filter(managed, 'index(args, v:key) >= 0') + + if empty(todo) + return s:warn('echo', 'No plugin to '. (a:pull ? 'update' : 'install')) + endif + + if !s:is_win && s:git_version_requirement(2, 3) + let s:git_terminal_prompt = exists('$GIT_TERMINAL_PROMPT') ? $GIT_TERMINAL_PROMPT : '' + let $GIT_TERMINAL_PROMPT = 0 + for plug in values(todo) + let plug.uri = substitute(plug.uri, + \ '^https://git::@github\.com', 'https://github.com', '') + endfor + endif + + if !isdirectory(g:plug_home) + try + call mkdir(g:plug_home, 'p') + catch + return s:err(printf('Invalid plug directory: %s. '. + \ 'Try to call plug#begin with a valid directory', g:plug_home)) + endtry + endif + + if has('nvim') && !exists('*jobwait') && threads > 1 + call s:warn('echom', '[vim-plug] Update Neovim for parallel installer') + endif + + let use_job = s:nvim || s:vim8 + let python = (has('python') || has('python3')) && !use_job + let ruby = has('ruby') && !use_job && (v:version >= 703 || v:version == 702 && has('patch374')) && !(s:is_win && has('gui_running')) && threads > 1 && s:check_ruby() + + let s:update = { + \ 'start': reltime(), + \ 'all': todo, + \ 'todo': copy(todo), + \ 'errors': [], + \ 'pull': a:pull, + \ 'force': a:force, + \ 'new': {}, + \ 'threads': (python || ruby || use_job) ? min([len(todo), threads]) : 1, + \ 'bar': '', + \ 'fin': 0 + \ } + + call s:prepare(1) + call append(0, ['', '']) + normal! 2G + silent! redraw + + let s:clone_opt = get(g:, 'plug_shallow', 1) ? + \ '--depth 1' . (s:git_version_requirement(1, 7, 10) ? ' --no-single-branch' : '') : '' + + if has('win32unix') + let s:clone_opt .= ' -c core.eol=lf -c core.autocrlf=input' + endif + + " Python version requirement (>= 2.7) + if python && !has('python3') && !ruby && !use_job && s:update.threads > 1 + redir => pyv + silent python import platform; print platform.python_version() + redir END + let python = s:version_requirement( + \ map(split(split(pyv)[0], '\.'), 'str2nr(v:val)'), [2, 6]) + endif + + if (python || ruby) && s:update.threads > 1 + try + let imd = &imd + if s:mac_gui + set noimd + endif + if ruby + call s:update_ruby() + else + call s:update_python() + endif + catch + let lines = getline(4, '$') + let printed = {} + silent! 4,$d _ + for line in lines + let name = s:extract_name(line, '.', '') + if empty(name) || !has_key(printed, name) + call append('$', line) + if !empty(name) + let printed[name] = 1 + if line[0] == 'x' && index(s:update.errors, name) < 0 + call add(s:update.errors, name) + end + endif + endif + endfor + finally + let &imd = imd + call s:update_finish() + endtry + else + call s:update_vim() + while use_job && sync + sleep 100m + if s:update.fin + break + endif + endwhile + endif +endfunction + +function! s:log4(name, msg) + call setline(4, printf('- %s (%s)', a:msg, a:name)) + redraw +endfunction + +function! s:update_finish() + if exists('s:git_terminal_prompt') + let $GIT_TERMINAL_PROMPT = s:git_terminal_prompt + endif + if s:switch_in() + call append(3, '- Updating ...') | 4 + for [name, spec] in items(filter(copy(s:update.all), 'index(s:update.errors, v:key) < 0 && (s:update.force || s:update.pull || has_key(s:update.new, v:key))')) + let [pos, _] = s:logpos(name) + if !pos + continue + endif + if has_key(spec, 'commit') + call s:log4(name, 'Checking out '.spec.commit) + let out = s:checkout(spec) + elseif has_key(spec, 'tag') + let tag = spec.tag + if tag =~ '\*' + let tags = s:lines(s:system('git tag --list '.s:shellesc(tag).' --sort -version:refname 2>&1', spec.dir)) + if !v:shell_error && !empty(tags) + let tag = tags[0] + call s:log4(name, printf('Latest tag for %s -> %s', spec.tag, tag)) + call append(3, '') + endif + endif + call s:log4(name, 'Checking out '.tag) + let out = s:system('git checkout -q '.s:esc(tag).' -- 2>&1', spec.dir) + else + let branch = s:esc(get(spec, 'branch', 'master')) + call s:log4(name, 'Merging origin/'.branch) + let out = s:system('git checkout -q '.branch.' -- 2>&1' + \. (has_key(s:update.new, name) ? '' : ('&& git merge --ff-only origin/'.branch.' 2>&1')), spec.dir) + endif + if !v:shell_error && filereadable(spec.dir.'/.gitmodules') && + \ (s:update.force || has_key(s:update.new, name) || s:is_updated(spec.dir)) + call s:log4(name, 'Updating submodules. This may take a while.') + let out .= s:bang('git submodule update --init --recursive 2>&1', spec.dir) + endif + let msg = s:format_message(v:shell_error ? 'x': '-', name, out) + if v:shell_error + call add(s:update.errors, name) + call s:regress_bar() + silent execute pos 'd _' + call append(4, msg) | 4 + elseif !empty(out) + call setline(pos, msg[0]) + endif + redraw + endfor + silent 4 d _ + try + call s:do(s:update.pull, s:update.force, filter(copy(s:update.all), 'index(s:update.errors, v:key) < 0 && has_key(v:val, "do")')) + catch + call s:warn('echom', v:exception) + call s:warn('echo', '') + return + endtry + call s:finish(s:update.pull) + call setline(1, 'Updated. Elapsed time: ' . split(reltimestr(reltime(s:update.start)))[0] . ' sec.') + call s:switch_out('normal! gg') + endif +endfunction + +function! s:job_abort() + if (!s:nvim && !s:vim8) || !exists('s:jobs') + return + endif + + for [name, j] in items(s:jobs) + if s:nvim + silent! call jobstop(j.jobid) + elseif s:vim8 + silent! call job_stop(j.jobid) + endif + if j.new + call s:system('rm -rf ' . s:shellesc(g:plugs[name].dir)) + endif + endfor + let s:jobs = {} +endfunction + +function! s:last_non_empty_line(lines) + let len = len(a:lines) + for idx in range(len) + let line = a:lines[len-idx-1] + if !empty(line) + return line + endif + endfor + return '' +endfunction + +function! s:job_out_cb(self, data) abort + let self = a:self + let data = remove(self.lines, -1) . a:data + let lines = map(split(data, "\n", 1), 'split(v:val, "\r", 1)[-1]') + call extend(self.lines, lines) + " To reduce the number of buffer updates + let self.tick = get(self, 'tick', -1) + 1 + if !self.running || self.tick % len(s:jobs) == 0 + let bullet = self.running ? (self.new ? '+' : '*') : (self.error ? 'x' : '-') + let result = self.error ? join(self.lines, "\n") : s:last_non_empty_line(self.lines) + call s:log(bullet, self.name, result) + endif +endfunction + +function! s:job_exit_cb(self, data) abort + let a:self.running = 0 + let a:self.error = a:data != 0 + call s:reap(a:self.name) + call s:tick() +endfunction + +function! s:job_cb(fn, job, ch, data) + if !s:plug_window_exists() " plug window closed + return s:job_abort() + endif + call call(a:fn, [a:job, a:data]) +endfunction + +function! s:nvim_cb(job_id, data, event) dict abort + return a:event == 'stdout' ? + \ s:job_cb('s:job_out_cb', self, 0, join(a:data, "\n")) : + \ s:job_cb('s:job_exit_cb', self, 0, a:data) +endfunction + +function! s:spawn(name, cmd, opts) + let job = { 'name': a:name, 'running': 1, 'error': 0, 'lines': [''], + \ 'batchfile': (s:is_win && (s:nvim || s:vim8)) ? tempname().'.bat' : '', + \ 'new': get(a:opts, 'new', 0) } + let s:jobs[a:name] = job + let cmd = has_key(a:opts, 'dir') ? s:with_cd(a:cmd, a:opts.dir) : a:cmd + if !empty(job.batchfile) + call writefile(['@echo off', cmd], job.batchfile) + let cmd = job.batchfile + endif + let argv = add(s:is_win ? ['cmd', '/c'] : ['sh', '-c'], cmd) + + if s:nvim + call extend(job, { + \ 'on_stdout': function('s:nvim_cb'), + \ 'on_exit': function('s:nvim_cb'), + \ }) + let jid = jobstart(argv, job) + if jid > 0 + let job.jobid = jid + else + let job.running = 0 + let job.error = 1 + let job.lines = [jid < 0 ? argv[0].' is not executable' : + \ 'Invalid arguments (or job table is full)'] + endif + elseif s:vim8 + let jid = job_start(s:is_win ? join(argv, ' ') : argv, { + \ 'out_cb': function('s:job_cb', ['s:job_out_cb', job]), + \ 'exit_cb': function('s:job_cb', ['s:job_exit_cb', job]), + \ 'out_mode': 'raw' + \}) + if job_status(jid) == 'run' + let job.jobid = jid + else + let job.running = 0 + let job.error = 1 + let job.lines = ['Failed to start job'] + endif + else + let job.lines = s:lines(call('s:system', [cmd])) + let job.error = v:shell_error != 0 + let job.running = 0 + endif +endfunction + +function! s:reap(name) + let job = s:jobs[a:name] + if job.error + call add(s:update.errors, a:name) + elseif get(job, 'new', 0) + let s:update.new[a:name] = 1 + endif + let s:update.bar .= job.error ? 'x' : '=' + + let bullet = job.error ? 'x' : '-' + let result = job.error ? join(job.lines, "\n") : s:last_non_empty_line(job.lines) + call s:log(bullet, a:name, empty(result) ? 'OK' : result) + call s:bar() + + if has_key(job, 'batchfile') && !empty(job.batchfile) + call delete(job.batchfile) + endif + call remove(s:jobs, a:name) +endfunction + +function! s:bar() + if s:switch_in() + let total = len(s:update.all) + call setline(1, (s:update.pull ? 'Updating' : 'Installing'). + \ ' plugins ('.len(s:update.bar).'/'.total.')') + call s:progress_bar(2, s:update.bar, total) + call s:switch_out() + endif +endfunction + +function! s:logpos(name) + for i in range(4, line('$')) + if getline(i) =~# '^[-+x*] '.a:name.':' + for j in range(i + 1, line('$')) + if getline(j) !~ '^ ' + return [i, j - 1] + endif + endfor + return [i, i] + endif + endfor + return [0, 0] +endfunction + +function! s:log(bullet, name, lines) + if s:switch_in() + let [b, e] = s:logpos(a:name) + if b > 0 + silent execute printf('%d,%d d _', b, e) + if b > winheight('.') + let b = 4 + endif + else + let b = 4 + endif + " FIXME For some reason, nomodifiable is set after :d in vim8 + setlocal modifiable + call append(b - 1, s:format_message(a:bullet, a:name, a:lines)) + call s:switch_out() + endif +endfunction + +function! s:update_vim() + let s:jobs = {} + + call s:bar() + call s:tick() +endfunction + +function! s:tick() + let pull = s:update.pull + let prog = s:progress_opt(s:nvim || s:vim8) +while 1 " Without TCO, Vim stack is bound to explode + if empty(s:update.todo) + if empty(s:jobs) && !s:update.fin + call s:update_finish() + let s:update.fin = 1 + endif + return + endif + + let name = keys(s:update.todo)[0] + let spec = remove(s:update.todo, name) + let new = !isdirectory(spec.dir) + + call s:log(new ? '+' : '*', name, pull ? 'Updating ...' : 'Installing ...') + redraw + + let has_tag = has_key(spec, 'tag') + if !new + let [error, _] = s:git_validate(spec, 0) + if empty(error) + if pull + let fetch_opt = (has_tag && !empty(globpath(spec.dir, '.git/shallow'))) ? '--depth 99999999' : '' + call s:spawn(name, printf('git fetch %s %s 2>&1', fetch_opt, prog), { 'dir': spec.dir }) + else + let s:jobs[name] = { 'running': 0, 'lines': ['Already installed'], 'error': 0 } + endif + else + let s:jobs[name] = { 'running': 0, 'lines': s:lines(error), 'error': 1 } + endif + else + call s:spawn(name, + \ printf('git clone %s %s %s %s 2>&1', + \ has_tag ? '' : s:clone_opt, + \ prog, + \ s:shellesc(spec.uri), + \ s:shellesc(s:trim(spec.dir))), { 'new': 1 }) + endif + + if !s:jobs[name].running + call s:reap(name) + endif + if len(s:jobs) >= s:update.threads + break + endif +endwhile +endfunction + +function! s:update_python() +let py_exe = has('python') ? 'python' : 'python3' +execute py_exe "<< EOF" +import datetime +import functools +import os +try: + import queue +except ImportError: + import Queue as queue +import random +import re +import shutil +import signal +import subprocess +import tempfile +import threading as thr +import time +import traceback +import vim + +G_NVIM = vim.eval("has('nvim')") == '1' +G_PULL = vim.eval('s:update.pull') == '1' +G_RETRIES = int(vim.eval('get(g:, "plug_retries", 2)')) + 1 +G_TIMEOUT = int(vim.eval('get(g:, "plug_timeout", 60)')) +G_CLONE_OPT = vim.eval('s:clone_opt') +G_PROGRESS = vim.eval('s:progress_opt(1)') +G_LOG_PROB = 1.0 / int(vim.eval('s:update.threads')) +G_STOP = thr.Event() +G_IS_WIN = vim.eval('s:is_win') == '1' + +class PlugError(Exception): + def __init__(self, msg): + self.msg = msg +class CmdTimedOut(PlugError): + pass +class CmdFailed(PlugError): + pass +class InvalidURI(PlugError): + pass +class Action(object): + INSTALL, UPDATE, ERROR, DONE = ['+', '*', 'x', '-'] + +class Buffer(object): + def __init__(self, lock, num_plugs, is_pull): + self.bar = '' + self.event = 'Updating' if is_pull else 'Installing' + self.lock = lock + self.maxy = int(vim.eval('winheight(".")')) + self.num_plugs = num_plugs + + def __where(self, name): + """ Find first line with name in current buffer. Return line num. """ + found, lnum = False, 0 + matcher = re.compile('^[-+x*] {0}:'.format(name)) + for line in vim.current.buffer: + if matcher.search(line) is not None: + found = True + break + lnum += 1 + + if not found: + lnum = -1 + return lnum + + def header(self): + curbuf = vim.current.buffer + curbuf[0] = self.event + ' plugins ({0}/{1})'.format(len(self.bar), self.num_plugs) + + num_spaces = self.num_plugs - len(self.bar) + curbuf[1] = '[{0}{1}]'.format(self.bar, num_spaces * ' ') + + with self.lock: + vim.command('normal! 2G') + vim.command('redraw') + + def write(self, action, name, lines): + first, rest = lines[0], lines[1:] + msg = ['{0} {1}{2}{3}'.format(action, name, ': ' if first else '', first)] + msg.extend([' ' + line for line in rest]) + + try: + if action == Action.ERROR: + self.bar += 'x' + vim.command("call add(s:update.errors, '{0}')".format(name)) + elif action == Action.DONE: + self.bar += '=' + + curbuf = vim.current.buffer + lnum = self.__where(name) + if lnum != -1: # Found matching line num + del curbuf[lnum] + if lnum > self.maxy and action in set([Action.INSTALL, Action.UPDATE]): + lnum = 3 + else: + lnum = 3 + curbuf.append(msg, lnum) + + self.header() + except vim.error: + pass + +class Command(object): + CD = 'cd /d' if G_IS_WIN else 'cd' + + def __init__(self, cmd, cmd_dir=None, timeout=60, cb=None, clean=None): + self.cmd = cmd + if cmd_dir: + self.cmd = '{0} {1} && {2}'.format(Command.CD, cmd_dir, self.cmd) + self.timeout = timeout + self.callback = cb if cb else (lambda msg: None) + self.clean = clean if clean else (lambda: None) + self.proc = None + + @property + def alive(self): + """ Returns true only if command still running. """ + return self.proc and self.proc.poll() is None + + def execute(self, ntries=3): + """ Execute the command with ntries if CmdTimedOut. + Returns the output of the command if no Exception. + """ + attempt, finished, limit = 0, False, self.timeout + + while not finished: + try: + attempt += 1 + result = self.try_command() + finished = True + return result + except CmdTimedOut: + if attempt != ntries: + self.notify_retry() + self.timeout += limit + else: + raise + + def notify_retry(self): + """ Retry required for command, notify user. """ + for count in range(3, 0, -1): + if G_STOP.is_set(): + raise KeyboardInterrupt + msg = 'Timeout. Will retry in {0} second{1} ...'.format( + count, 's' if count != 1 else '') + self.callback([msg]) + time.sleep(1) + self.callback(['Retrying ...']) + + def try_command(self): + """ Execute a cmd & poll for callback. Returns list of output. + Raises CmdFailed -> return code for Popen isn't 0 + Raises CmdTimedOut -> command exceeded timeout without new output + """ + first_line = True + + try: + tfile = tempfile.NamedTemporaryFile(mode='w+b') + preexec_fn = not G_IS_WIN and os.setsid or None + self.proc = subprocess.Popen(self.cmd, stdout=tfile, + stderr=subprocess.STDOUT, + stdin=subprocess.PIPE, shell=True, + preexec_fn=preexec_fn) + thrd = thr.Thread(target=(lambda proc: proc.wait()), args=(self.proc,)) + thrd.start() + + thread_not_started = True + while thread_not_started: + try: + thrd.join(0.1) + thread_not_started = False + except RuntimeError: + pass + + while self.alive: + if G_STOP.is_set(): + raise KeyboardInterrupt + + if first_line or random.random() < G_LOG_PROB: + first_line = False + line = '' if G_IS_WIN else nonblock_read(tfile.name) + if line: + self.callback([line]) + + time_diff = time.time() - os.path.getmtime(tfile.name) + if time_diff > self.timeout: + raise CmdTimedOut(['Timeout!']) + + thrd.join(0.5) + + tfile.seek(0) + result = [line.decode('utf-8', 'replace').rstrip() for line in tfile] + + if self.proc.returncode != 0: + raise CmdFailed([''] + result) + + return result + except: + self.terminate() + raise + + def terminate(self): + """ Terminate process and cleanup. """ + if self.alive: + if G_IS_WIN: + os.kill(self.proc.pid, signal.SIGINT) + else: + os.killpg(self.proc.pid, signal.SIGTERM) + self.clean() + +class Plugin(object): + def __init__(self, name, args, buf_q, lock): + self.name = name + self.args = args + self.buf_q = buf_q + self.lock = lock + self.tag = args.get('tag', 0) + + def manage(self): + try: + if os.path.exists(self.args['dir']): + self.update() + else: + self.install() + with self.lock: + thread_vim_command("let s:update.new['{0}'] = 1".format(self.name)) + except PlugError as exc: + self.write(Action.ERROR, self.name, exc.msg) + except KeyboardInterrupt: + G_STOP.set() + self.write(Action.ERROR, self.name, ['Interrupted!']) + except: + # Any exception except those above print stack trace + msg = 'Trace:\n{0}'.format(traceback.format_exc().rstrip()) + self.write(Action.ERROR, self.name, msg.split('\n')) + raise + + def install(self): + target = self.args['dir'] + if target[-1] == '\\': + target = target[0:-1] + + def clean(target): + def _clean(): + try: + shutil.rmtree(target) + except OSError: + pass + return _clean + + self.write(Action.INSTALL, self.name, ['Installing ...']) + callback = functools.partial(self.write, Action.INSTALL, self.name) + cmd = 'git clone {0} {1} {2} {3} 2>&1'.format( + '' if self.tag else G_CLONE_OPT, G_PROGRESS, self.args['uri'], + esc(target)) + com = Command(cmd, None, G_TIMEOUT, callback, clean(target)) + result = com.execute(G_RETRIES) + self.write(Action.DONE, self.name, result[-1:]) + + def repo_uri(self): + cmd = 'git rev-parse --abbrev-ref HEAD 2>&1 && git config -f .git/config remote.origin.url' + command = Command(cmd, self.args['dir'], G_TIMEOUT,) + result = command.execute(G_RETRIES) + return result[-1] + + def update(self): + actual_uri = self.repo_uri() + expect_uri = self.args['uri'] + regex = re.compile(r'^(?:\w+://)?(?:[^@/]*@)?([^:/]*(?::[0-9]*)?)[:/](.*?)(?:\.git)?/?$') + ma = regex.match(actual_uri) + mb = regex.match(expect_uri) + if ma is None or mb is None or ma.groups() != mb.groups(): + msg = ['', + 'Invalid URI: {0}'.format(actual_uri), + 'Expected {0}'.format(expect_uri), + 'PlugClean required.'] + raise InvalidURI(msg) + + if G_PULL: + self.write(Action.UPDATE, self.name, ['Updating ...']) + callback = functools.partial(self.write, Action.UPDATE, self.name) + fetch_opt = '--depth 99999999' if self.tag and os.path.isfile(os.path.join(self.args['dir'], '.git/shallow')) else '' + cmd = 'git fetch {0} {1} 2>&1'.format(fetch_opt, G_PROGRESS) + com = Command(cmd, self.args['dir'], G_TIMEOUT, callback) + result = com.execute(G_RETRIES) + self.write(Action.DONE, self.name, result[-1:]) + else: + self.write(Action.DONE, self.name, ['Already installed']) + + def write(self, action, name, msg): + self.buf_q.put((action, name, msg)) + +class PlugThread(thr.Thread): + def __init__(self, tname, args): + super(PlugThread, self).__init__() + self.tname = tname + self.args = args + + def run(self): + thr.current_thread().name = self.tname + buf_q, work_q, lock = self.args + + try: + while not G_STOP.is_set(): + name, args = work_q.get_nowait() + plug = Plugin(name, args, buf_q, lock) + plug.manage() + work_q.task_done() + except queue.Empty: + pass + +class RefreshThread(thr.Thread): + def __init__(self, lock): + super(RefreshThread, self).__init__() + self.lock = lock + self.running = True + + def run(self): + while self.running: + with self.lock: + thread_vim_command('noautocmd normal! a') + time.sleep(0.33) + + def stop(self): + self.running = False + +if G_NVIM: + def thread_vim_command(cmd): + vim.session.threadsafe_call(lambda: vim.command(cmd)) +else: + def thread_vim_command(cmd): + vim.command(cmd) + +def esc(name): + return '"' + name.replace('"', '\"') + '"' + +def nonblock_read(fname): + """ Read a file with nonblock flag. Return the last line. """ + fread = os.open(fname, os.O_RDONLY | os.O_NONBLOCK) + buf = os.read(fread, 100000).decode('utf-8', 'replace') + os.close(fread) + + line = buf.rstrip('\r\n') + left = max(line.rfind('\r'), line.rfind('\n')) + if left != -1: + left += 1 + line = line[left:] + + return line + +def main(): + thr.current_thread().name = 'main' + nthreads = int(vim.eval('s:update.threads')) + plugs = vim.eval('s:update.todo') + mac_gui = vim.eval('s:mac_gui') == '1' + + lock = thr.Lock() + buf = Buffer(lock, len(plugs), G_PULL) + buf_q, work_q = queue.Queue(), queue.Queue() + for work in plugs.items(): + work_q.put(work) + + start_cnt = thr.active_count() + for num in range(nthreads): + tname = 'PlugT-{0:02}'.format(num) + thread = PlugThread(tname, (buf_q, work_q, lock)) + thread.start() + if mac_gui: + rthread = RefreshThread(lock) + rthread.start() + + while not buf_q.empty() or thr.active_count() != start_cnt: + try: + action, name, msg = buf_q.get(True, 0.25) + buf.write(action, name, ['OK'] if not msg else msg) + buf_q.task_done() + except queue.Empty: + pass + except KeyboardInterrupt: + G_STOP.set() + + if mac_gui: + rthread.stop() + rthread.join() + +main() +EOF +endfunction + +function! s:update_ruby() + ruby << EOF + module PlugStream + SEP = ["\r", "\n", nil] + def get_line + buffer = '' + loop do + char = readchar rescue return + if SEP.include? char.chr + buffer << $/ + break + else + buffer << char + end + end + buffer + end + end unless defined?(PlugStream) + + def esc arg + %["#{arg.gsub('"', '\"')}"] + end + + def killall pid + pids = [pid] + if /mswin|mingw|bccwin/ =~ RUBY_PLATFORM + pids.each { |pid| Process.kill 'INT', pid.to_i rescue nil } + else + unless `which pgrep 2> /dev/null`.empty? + children = pids + until children.empty? + children = children.map { |pid| + `pgrep -P #{pid}`.lines.map { |l| l.chomp } + }.flatten + pids += children + end + end + pids.each { |pid| Process.kill 'TERM', pid.to_i rescue nil } + end + end + + def compare_git_uri a, b + regex = %r{^(?:\w+://)?(?:[^@/]*@)?([^:/]*(?::[0-9]*)?)[:/](.*?)(?:\.git)?/?$} + regex.match(a).to_a.drop(1) == regex.match(b).to_a.drop(1) + end + + require 'thread' + require 'fileutils' + require 'timeout' + running = true + iswin = VIM::evaluate('s:is_win').to_i == 1 + pull = VIM::evaluate('s:update.pull').to_i == 1 + base = VIM::evaluate('g:plug_home') + all = VIM::evaluate('s:update.todo') + limit = VIM::evaluate('get(g:, "plug_timeout", 60)') + tries = VIM::evaluate('get(g:, "plug_retries", 2)') + 1 + nthr = VIM::evaluate('s:update.threads').to_i + maxy = VIM::evaluate('winheight(".")').to_i + vim7 = VIM::evaluate('v:version').to_i <= 703 && RUBY_PLATFORM =~ /darwin/ + cd = iswin ? 'cd /d' : 'cd' + tot = VIM::evaluate('len(s:update.todo)') || 0 + bar = '' + skip = 'Already installed' + mtx = Mutex.new + take1 = proc { mtx.synchronize { running && all.shift } } + logh = proc { + cnt = bar.length + $curbuf[1] = "#{pull ? 'Updating' : 'Installing'} plugins (#{cnt}/#{tot})" + $curbuf[2] = '[' + bar.ljust(tot) + ']' + VIM::command('normal! 2G') + VIM::command('redraw') + } + where = proc { |name| (1..($curbuf.length)).find { |l| $curbuf[l] =~ /^[-+x*] #{name}:/ } } + log = proc { |name, result, type| + mtx.synchronize do + ing = ![true, false].include?(type) + bar += type ? '=' : 'x' unless ing + b = case type + when :install then '+' when :update then '*' + when true, nil then '-' else + VIM::command("call add(s:update.errors, '#{name}')") + 'x' + end + result = + if type || type.nil? + ["#{b} #{name}: #{result.lines.to_a.last || 'OK'}"] + elsif result =~ /^Interrupted|^Timeout/ + ["#{b} #{name}: #{result}"] + else + ["#{b} #{name}"] + result.lines.map { |l| " " << l } + end + if lnum = where.call(name) + $curbuf.delete lnum + lnum = 4 if ing && lnum > maxy + end + result.each_with_index do |line, offset| + $curbuf.append((lnum || 4) - 1 + offset, line.gsub(/\e\[./, '').chomp) + end + logh.call + end + } + bt = proc { |cmd, name, type, cleanup| + tried = timeout = 0 + begin + tried += 1 + timeout += limit + fd = nil + data = '' + if iswin + Timeout::timeout(timeout) do + tmp = VIM::evaluate('tempname()') + system("(#{cmd}) > #{tmp}") + data = File.read(tmp).chomp + File.unlink tmp rescue nil + end + else + fd = IO.popen(cmd).extend(PlugStream) + first_line = true + log_prob = 1.0 / nthr + while line = Timeout::timeout(timeout) { fd.get_line } + data << line + log.call name, line.chomp, type if name && (first_line || rand < log_prob) + first_line = false + end + fd.close + end + [$? == 0, data.chomp] + rescue Timeout::Error, Interrupt => e + if fd && !fd.closed? + killall fd.pid + fd.close + end + cleanup.call if cleanup + if e.is_a?(Timeout::Error) && tried < tries + 3.downto(1) do |countdown| + s = countdown > 1 ? 's' : '' + log.call name, "Timeout. Will retry in #{countdown} second#{s} ...", type + sleep 1 + end + log.call name, 'Retrying ...', type + retry + end + [false, e.is_a?(Interrupt) ? "Interrupted!" : "Timeout!"] + end + } + main = Thread.current + threads = [] + watcher = Thread.new { + if vim7 + while VIM::evaluate('getchar(1)') + sleep 0.1 + end + else + require 'io/console' # >= Ruby 1.9 + nil until IO.console.getch == 3.chr + end + mtx.synchronize do + running = false + threads.each { |t| t.raise Interrupt } unless vim7 + end + threads.each { |t| t.join rescue nil } + main.kill + } + refresh = Thread.new { + while true + mtx.synchronize do + break unless running + VIM::command('noautocmd normal! a') + end + sleep 0.2 + end + } if VIM::evaluate('s:mac_gui') == 1 + + clone_opt = VIM::evaluate('s:clone_opt') + progress = VIM::evaluate('s:progress_opt(1)') + nthr.times do + mtx.synchronize do + threads << Thread.new { + while pair = take1.call + name = pair.first + dir, uri, tag = pair.last.values_at *%w[dir uri tag] + exists = File.directory? dir + ok, result = + if exists + chdir = "#{cd} #{iswin ? dir : esc(dir)}" + ret, data = bt.call "#{chdir} && git rev-parse --abbrev-ref HEAD 2>&1 && git config -f .git/config remote.origin.url", nil, nil, nil + current_uri = data.lines.to_a.last + if !ret + if data =~ /^Interrupted|^Timeout/ + [false, data] + else + [false, [data.chomp, "PlugClean required."].join($/)] + end + elsif !compare_git_uri(current_uri, uri) + [false, ["Invalid URI: #{current_uri}", + "Expected: #{uri}", + "PlugClean required."].join($/)] + else + if pull + log.call name, 'Updating ...', :update + fetch_opt = (tag && File.exist?(File.join(dir, '.git/shallow'))) ? '--depth 99999999' : '' + bt.call "#{chdir} && git fetch #{fetch_opt} #{progress} 2>&1", name, :update, nil + else + [true, skip] + end + end + else + d = esc dir.sub(%r{[\\/]+$}, '') + log.call name, 'Installing ...', :install + bt.call "git clone #{clone_opt unless tag} #{progress} #{uri} #{d} 2>&1", name, :install, proc { + FileUtils.rm_rf dir + } + end + mtx.synchronize { VIM::command("let s:update.new['#{name}'] = 1") } if !exists && ok + log.call name, result, ok + end + } if running + end + end + threads.each { |t| t.join rescue nil } + logh.call + refresh.kill if refresh + watcher.kill +EOF +endfunction + +function! s:shellesc_cmd(arg) + let escaped = substitute(a:arg, '[&|<>()@^]', '^&', 'g') + let escaped = substitute(escaped, '%', '%%', 'g') + let escaped = substitute(escaped, '"', '\\^&', 'g') + let escaped = substitute(escaped, '\(\\\+\)\(\\^\)', '\1\1\2', 'g') + return '^"'.substitute(escaped, '\(\\\+\)$', '\1\1', '').'^"' +endfunction + +function! s:shellesc(arg) + if &shell =~# 'cmd.exe$' + return s:shellesc_cmd(a:arg) + endif + return shellescape(a:arg) +endfunction + +function! s:glob_dir(path) + return map(filter(s:glob(a:path, '**'), 'isdirectory(v:val)'), 's:dirpath(v:val)') +endfunction + +function! s:progress_bar(line, bar, total) + call setline(a:line, '[' . s:lpad(a:bar, a:total) . ']') +endfunction + +function! s:compare_git_uri(a, b) + " See `git help clone' + " https:// [user@] github.com[:port] / junegunn/vim-plug [.git] + " [git@] github.com[:port] : junegunn/vim-plug [.git] + " file:// / junegunn/vim-plug [/] + " / junegunn/vim-plug [/] + let pat = '^\%(\w\+://\)\='.'\%([^@/]*@\)\='.'\([^:/]*\%(:[0-9]*\)\=\)'.'[:/]'.'\(.\{-}\)'.'\%(\.git\)\=/\?$' + let ma = matchlist(a:a, pat) + let mb = matchlist(a:b, pat) + return ma[1:2] ==# mb[1:2] +endfunction + +function! s:format_message(bullet, name, message) + if a:bullet != 'x' + return [printf('%s %s: %s', a:bullet, a:name, s:lastline(a:message))] + else + let lines = map(s:lines(a:message), '" ".v:val') + return extend([printf('x %s:', a:name)], lines) + endif +endfunction + +function! s:with_cd(cmd, dir) + return printf('cd%s %s && %s', s:is_win ? ' /d' : '', s:shellesc(a:dir), a:cmd) +endfunction + +function! s:system(cmd, ...) + try + let [sh, shellcmdflag, shrd] = s:chsh(1) + let cmd = a:0 > 0 ? s:with_cd(a:cmd, a:1) : a:cmd + if s:is_win + let batchfile = tempname().'.bat' + call writefile(['@echo off', cmd], batchfile) + let cmd = batchfile + endif + return system(s:is_win ? '('.cmd.')' : cmd) + finally + let [&shell, &shellcmdflag, &shellredir] = [sh, shellcmdflag, shrd] + if s:is_win + call delete(batchfile) + endif + endtry +endfunction + +function! s:system_chomp(...) + let ret = call('s:system', a:000) + return v:shell_error ? '' : substitute(ret, '\n$', '', '') +endfunction + +function! s:git_validate(spec, check_branch) + let err = '' + if isdirectory(a:spec.dir) + let result = s:lines(s:system('git rev-parse --abbrev-ref HEAD 2>&1 && git config -f .git/config remote.origin.url', a:spec.dir)) + let remote = result[-1] + if v:shell_error + let err = join([remote, 'PlugClean required.'], "\n") + elseif !s:compare_git_uri(remote, a:spec.uri) + let err = join(['Invalid URI: '.remote, + \ 'Expected: '.a:spec.uri, + \ 'PlugClean required.'], "\n") + elseif a:check_branch && has_key(a:spec, 'commit') + let result = s:lines(s:system('git rev-parse HEAD 2>&1', a:spec.dir)) + let sha = result[-1] + if v:shell_error + let err = join(add(result, 'PlugClean required.'), "\n") + elseif !s:hash_match(sha, a:spec.commit) + let err = join([printf('Invalid HEAD (expected: %s, actual: %s)', + \ a:spec.commit[:6], sha[:6]), + \ 'PlugUpdate required.'], "\n") + endif + elseif a:check_branch + let branch = result[0] + " Check tag + if has_key(a:spec, 'tag') + let tag = s:system_chomp('git describe --exact-match --tags HEAD 2>&1', a:spec.dir) + if a:spec.tag !=# tag && a:spec.tag !~ '\*' + let err = printf('Invalid tag: %s (expected: %s). Try PlugUpdate.', + \ (empty(tag) ? 'N/A' : tag), a:spec.tag) + endif + " Check branch + elseif a:spec.branch !=# branch + let err = printf('Invalid branch: %s (expected: %s). Try PlugUpdate.', + \ branch, a:spec.branch) + endif + if empty(err) + let [ahead, behind] = split(s:lastline(s:system(printf( + \ 'git rev-list --count --left-right HEAD...origin/%s', + \ a:spec.branch), a:spec.dir)), '\t') + if !v:shell_error && ahead + if behind + " Only mention PlugClean if diverged, otherwise it's likely to be + " pushable (and probably not that messed up). + let err = printf( + \ "Diverged from origin/%s (%d commit(s) ahead and %d commit(s) behind!\n" + \ .'Backup local changes and run PlugClean and PlugUpdate to reinstall it.', a:spec.branch, ahead, behind) + else + let err = printf("Ahead of origin/%s by %d commit(s).\n" + \ .'Cannot update until local changes are pushed.', + \ a:spec.branch, ahead) + endif + endif + endif + endif + else + let err = 'Not found' + endif + return [err, err =~# 'PlugClean'] +endfunction + +function! s:rm_rf(dir) + if isdirectory(a:dir) + call s:system((s:is_win ? 'rmdir /S /Q ' : 'rm -rf ') . s:shellesc(a:dir)) + endif +endfunction + +function! s:clean(force) + call s:prepare() + call append(0, 'Searching for invalid plugins in '.g:plug_home) + call append(1, '') + + " List of valid directories + let dirs = [] + let errs = {} + let [cnt, total] = [0, len(g:plugs)] + for [name, spec] in items(g:plugs) + if !s:is_managed(name) + call add(dirs, spec.dir) + else + let [err, clean] = s:git_validate(spec, 1) + if clean + let errs[spec.dir] = s:lines(err)[0] + else + call add(dirs, spec.dir) + endif + endif + let cnt += 1 + call s:progress_bar(2, repeat('=', cnt), total) + normal! 2G + redraw + endfor + + let allowed = {} + for dir in dirs + let allowed[s:dirpath(fnamemodify(dir, ':h:h'))] = 1 + let allowed[dir] = 1 + for child in s:glob_dir(dir) + let allowed[child] = 1 + endfor + endfor + + let todo = [] + let found = sort(s:glob_dir(g:plug_home)) + while !empty(found) + let f = remove(found, 0) + if !has_key(allowed, f) && isdirectory(f) + call add(todo, f) + call append(line('$'), '- ' . f) + if has_key(errs, f) + call append(line('$'), ' ' . errs[f]) + endif + let found = filter(found, 'stridx(v:val, f) != 0') + end + endwhile + + 4 + redraw + if empty(todo) + call append(line('$'), 'Already clean.') + else + let s:clean_count = 0 + call append(3, ['Directories to delete:', '']) + redraw! + if a:force || s:ask_no_interrupt('Delete all directories?') + call s:delete([6, line('$')], 1) + else + call setline(4, 'Cancelled.') + nnoremap d :set opfunc=delete_opg@ + nmap dd d_ + xnoremap d :call delete_op(visualmode(), 1) + echo 'Delete the lines (d{motion}) to delete the corresponding directories' + endif + endif + 4 + setlocal nomodifiable +endfunction + +function! s:delete_op(type, ...) + call s:delete(a:0 ? [line("'<"), line("'>")] : [line("'["), line("']")], 0) +endfunction + +function! s:delete(range, force) + let [l1, l2] = a:range + let force = a:force + while l1 <= l2 + let line = getline(l1) + if line =~ '^- ' && isdirectory(line[2:]) + execute l1 + redraw! + let answer = force ? 1 : s:ask('Delete '.line[2:].'?', 1) + let force = force || answer > 1 + if answer + call s:rm_rf(line[2:]) + setlocal modifiable + call setline(l1, '~'.line[1:]) + let s:clean_count += 1 + call setline(4, printf('Removed %d directories.', s:clean_count)) + setlocal nomodifiable + endif + endif + let l1 += 1 + endwhile +endfunction + +function! s:upgrade() + echo 'Downloading the latest version of vim-plug' + redraw + let tmp = tempname() + let new = tmp . '/plug.vim' + + try + let out = s:system(printf('git clone --depth 1 %s %s', s:plug_src, tmp)) + if v:shell_error + return s:err('Error upgrading vim-plug: '. out) + endif + + if readfile(s:me) ==# readfile(new) + echo 'vim-plug is already up-to-date' + return 0 + else + call rename(s:me, s:me . '.old') + call rename(new, s:me) + unlet g:loaded_plug + echo 'vim-plug has been upgraded' + return 1 + endif + finally + silent! call s:rm_rf(tmp) + endtry +endfunction + +function! s:upgrade_specs() + for spec in values(g:plugs) + let spec.frozen = get(spec, 'frozen', 0) + endfor +endfunction + +function! s:status() + call s:prepare() + call append(0, 'Checking plugins') + call append(1, '') + + let ecnt = 0 + let unloaded = 0 + let [cnt, total] = [0, len(g:plugs)] + for [name, spec] in items(g:plugs) + let is_dir = isdirectory(spec.dir) + if has_key(spec, 'uri') + if is_dir + let [err, _] = s:git_validate(spec, 1) + let [valid, msg] = [empty(err), empty(err) ? 'OK' : err] + else + let [valid, msg] = [0, 'Not found. Try PlugInstall.'] + endif + else + if is_dir + let [valid, msg] = [1, 'OK'] + else + let [valid, msg] = [0, 'Not found.'] + endif + endif + let cnt += 1 + let ecnt += !valid + " `s:loaded` entry can be missing if PlugUpgraded + if is_dir && get(s:loaded, name, -1) == 0 + let unloaded = 1 + let msg .= ' (not loaded)' + endif + call s:progress_bar(2, repeat('=', cnt), total) + call append(3, s:format_message(valid ? '-' : 'x', name, msg)) + normal! 2G + redraw + endfor + call setline(1, 'Finished. '.ecnt.' error(s).') + normal! gg + setlocal nomodifiable + if unloaded + echo "Press 'L' on each line to load plugin, or 'U' to update" + nnoremap L :call status_load(line('.')) + xnoremap L :call status_load(line('.')) + end +endfunction + +function! s:extract_name(str, prefix, suffix) + return matchstr(a:str, '^'.a:prefix.' \zs[^:]\+\ze:.*'.a:suffix.'$') +endfunction + +function! s:status_load(lnum) + let line = getline(a:lnum) + let name = s:extract_name(line, '-', '(not loaded)') + if !empty(name) + call plug#load(name) + setlocal modifiable + call setline(a:lnum, substitute(line, ' (not loaded)$', '', '')) + setlocal nomodifiable + endif +endfunction + +function! s:status_update() range + let lines = getline(a:firstline, a:lastline) + let names = filter(map(lines, 's:extract_name(v:val, "[x-]", "")'), '!empty(v:val)') + if !empty(names) + echo + execute 'PlugUpdate' join(names) + endif +endfunction + +function! s:is_preview_window_open() + silent! wincmd P + if &previewwindow + wincmd p + return 1 + endif +endfunction + +function! s:find_name(lnum) + for lnum in reverse(range(1, a:lnum)) + let line = getline(lnum) + if empty(line) + return '' + endif + let name = s:extract_name(line, '-', '') + if !empty(name) + return name + endif + endfor + return '' +endfunction + +function! s:preview_commit() + if b:plug_preview < 0 + let b:plug_preview = !s:is_preview_window_open() + endif + + let sha = matchstr(getline('.'), '^ \X*\zs[0-9a-f]\{7,9}') + if empty(sha) + return + endif + + let name = s:find_name(line('.')) + if empty(name) || !has_key(g:plugs, name) || !isdirectory(g:plugs[name].dir) + return + endif + + if exists('g:plug_pwindow') && !s:is_preview_window_open() + execute g:plug_pwindow + execute 'e' sha + else + execute 'pedit' sha + wincmd P + endif + setlocal previewwindow filetype=git buftype=nofile nobuflisted modifiable + try + let [sh, shellcmdflag, shrd] = s:chsh(1) + let cmd = 'cd '.s:shellesc(g:plugs[name].dir).' && git show --no-color --pretty=medium '.sha + if s:is_win + let batchfile = tempname().'.bat' + call writefile(['@echo off', cmd], batchfile) + let cmd = batchfile + endif + execute 'silent %!' cmd + finally + let [&shell, &shellcmdflag, &shellredir] = [sh, shellcmdflag, shrd] + if s:is_win + call delete(batchfile) + endif + endtry + setlocal nomodifiable + nnoremap q :q + wincmd p +endfunction + +function! s:section(flags) + call search('\(^[x-] \)\@<=[^:]\+:', a:flags) +endfunction + +function! s:format_git_log(line) + let indent = ' ' + let tokens = split(a:line, nr2char(1)) + if len(tokens) != 5 + return indent.substitute(a:line, '\s*$', '', '') + endif + let [graph, sha, refs, subject, date] = tokens + let tag = matchstr(refs, 'tag: [^,)]\+') + let tag = empty(tag) ? ' ' : ' ('.tag.') ' + return printf('%s%s%s%s%s (%s)', indent, graph, sha, tag, subject, date) +endfunction + +function! s:append_ul(lnum, text) + call append(a:lnum, ['', a:text, repeat('-', len(a:text))]) +endfunction + +function! s:diff() + call s:prepare() + call append(0, ['Collecting changes ...', '']) + let cnts = [0, 0] + let bar = '' + let total = filter(copy(g:plugs), 's:is_managed(v:key) && isdirectory(v:val.dir)') + call s:progress_bar(2, bar, len(total)) + for origin in [1, 0] + let plugs = reverse(sort(items(filter(copy(total), (origin ? '' : '!').'(has_key(v:val, "commit") || has_key(v:val, "tag"))')))) + if empty(plugs) + continue + endif + call s:append_ul(2, origin ? 'Pending updates:' : 'Last update:') + for [k, v] in plugs + let range = origin ? '..origin/'.v.branch : 'HEAD@{1}..' + let diff = s:system_chomp('git log --graph --color=never '.join(map(['--pretty=format:%x01%h%x01%d%x01%s%x01%cr', range], 's:shellesc(v:val)')), v.dir) + if !empty(diff) + let ref = has_key(v, 'tag') ? (' (tag: '.v.tag.')') : has_key(v, 'commit') ? (' '.v.commit) : '' + call append(5, extend(['', '- '.k.':'.ref], map(s:lines(diff), 's:format_git_log(v:val)'))) + let cnts[origin] += 1 + endif + let bar .= '=' + call s:progress_bar(2, bar, len(total)) + normal! 2G + redraw + endfor + if !cnts[origin] + call append(5, ['', 'N/A']) + endif + endfor + call setline(1, printf('%d plugin(s) updated.', cnts[0]) + \ . (cnts[1] ? printf(' %d plugin(s) have pending updates.', cnts[1]) : '')) + + if cnts[0] || cnts[1] + nnoremap :silent! call preview_commit() + nnoremap o :silent! call preview_commit() + endif + if cnts[0] + nnoremap X :call revert() + echo "Press 'X' on each block to revert the update" + endif + normal! gg + setlocal nomodifiable +endfunction + +function! s:revert() + if search('^Pending updates', 'bnW') + return + endif + + let name = s:find_name(line('.')) + if empty(name) || !has_key(g:plugs, name) || + \ input(printf('Revert the update of %s? (y/N) ', name)) !~? '^y' + return + endif + + call s:system('git reset --hard HEAD@{1} && git checkout '.s:esc(g:plugs[name].branch).' --', g:plugs[name].dir) + setlocal modifiable + normal! "_dap + setlocal nomodifiable + echo 'Reverted' +endfunction + +function! s:snapshot(force, ...) abort + call s:prepare() + setf vim + call append(0, ['" Generated by vim-plug', + \ '" '.strftime("%c"), + \ '" :source this file in vim to restore the snapshot', + \ '" or execute: vim -S snapshot.vim', + \ '', '', 'PlugUpdate!']) + 1 + let anchor = line('$') - 3 + let names = sort(keys(filter(copy(g:plugs), + \'has_key(v:val, "uri") && !has_key(v:val, "commit") && isdirectory(v:val.dir)'))) + for name in reverse(names) + let sha = s:system_chomp('git rev-parse --short HEAD', g:plugs[name].dir) + if !empty(sha) + call append(anchor, printf("silent! let g:plugs['%s'].commit = '%s'", name, sha)) + redraw + endif + endfor + + if a:0 > 0 + let fn = expand(a:1) + if filereadable(fn) && !(a:force || s:ask(a:1.' already exists. Overwrite?')) + return + endif + call writefile(getline(1, '$'), fn) + echo 'Saved as '.a:1 + silent execute 'e' s:esc(fn) + setf vim + endif +endfunction + +function! s:split_rtp() + return split(&rtp, '\\\@0 + let szTrace .= ':' + endif + for i in range(paramNum) + let szTrace = szTrace .' ('. string(eval('a:'.string(i+1))).')' + endfor + call extend(s:CACHE_DEBUG_TRACE, [szTrace]) +endfunc diff --git a/VimFiles/autoload/omni/common/utils.vim b/VimFiles/autoload/omni/common/utils.vim new file mode 100755 index 0000000..c880ad2 --- /dev/null +++ b/VimFiles/autoload/omni/common/utils.vim @@ -0,0 +1,67 @@ +" Description: Omni completion utils +" Maintainer: Vissale NEANG +" Last Change: 26 sept. 2007 + +" For sort numbers in list +function! omni#common#utils#CompareNumber(i1, i2) + let num1 = eval(a:i1) + let num2 = eval(a:i2) + return num1 == num2 ? 0 : num1 > num2 ? 1 : -1 +endfunc + +" TagList function calling the vim taglist() with try catch +" The only throwed exception is 'TagList:UserInterrupt' +" We also force the noignorecase option to avoid linear search when calling +" taglist() +function! omni#common#utils#TagList(szTagQuery) + let result = [] + let bUserIgnoreCase = &ignorecase + " Forcing noignorecase search => binary search can be used in taglist() + " if tags in the tag file are sorted + if bUserIgnoreCase + set noignorecase + endif + try + let result = taglist(a:szTagQuery) + catch /^Vim:Interrupt$/ + " Restoring user's setting + if bUserIgnoreCase + set ignorecase + endif + throw 'TagList:UserInterrupt' + catch + "Note: it seems that ctags can generate corrupted files, in this case + "taglist() will fail to read the tagfile and an exception from + "has_add() is thrown + endtry + + " Restoring user's setting + if bUserIgnoreCase + set ignorecase + endif + return result +endfunc + +" Same as TagList but don't throw exception +function! omni#common#utils#TagListNoThrow(szTagQuery) + let result = [] + try + let result = omni#common#utils#TagList(a:szTagQuery) + catch + endtry + return result +endfunc + +" Get the word under the cursor +function! omni#common#utils#GetWordUnderCursor() + let szLine = getline('.') + let startPos = getpos('.')[2]-1 + let startPos = (startPos < 0)? 0 : startPos + if szLine[startPos] =~ '\w' + let startPos = searchpos('\<\w\+', 'cbn', line('.'))[1] - 1 + endif + + let startPos = (startPos < 0)? 0 : startPos + let szResult = matchstr(szLine, '\w\+', startPos) + return szResult +endfunc diff --git a/VimFiles/autoload/omni/cpp/complete.vim b/VimFiles/autoload/omni/cpp/complete.vim new file mode 100755 index 0000000..a7e4edc --- /dev/null +++ b/VimFiles/autoload/omni/cpp/complete.vim @@ -0,0 +1,569 @@ +" 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 diff --git a/VimFiles/autoload/omni/cpp/includes.vim b/VimFiles/autoload/omni/cpp/includes.vim new file mode 100755 index 0000000..10a89bc --- /dev/null +++ b/VimFiles/autoload/omni/cpp/includes.vim @@ -0,0 +1,126 @@ +" Description: Omni completion script for cpp files +" Maintainer: Vissale NEANG +" Last Change: 26 sept. 2007 + +let g:omni#cpp#includes#CACHE_INCLUDES = {} +let g:omni#cpp#includes#CACHE_FILE_TIME = {} + +let s:rePreprocIncludePart = '\C#\s*include\s*' +let s:reIncludeFilePart = '\(<\|"\)\(\f\|\s\)\+\(>\|"\)' +let s:rePreprocIncludeFile = s:rePreprocIncludePart . s:reIncludeFilePart + +" Get the include list of a file +function! omni#cpp#includes#GetList(...) + if a:0 > 0 + return s:GetIncludeListFromFile(a:1, (a:0 > 1)? a:2 : 0 ) + else + return s:GetIncludeListFromCurrentBuffer() + endif +endfunc + +" Get the include list from the current buffer +function! s:GetIncludeListFromCurrentBuffer() + let listIncludes = [] + let originalPos = getpos('.') + + call setpos('.', [0, 1, 1, 0]) + let curPos = [1,1] + let alreadyInclude = {} + while curPos != [0,0] + let curPos = searchpos('\C\(^'.s:rePreprocIncludeFile.'\)', 'W') + if curPos != [0,0] + let szLine = getline('.') + let startPos = curPos[1] + let endPos = matchend(szLine, s:reIncludeFilePart, startPos-1) + if endPos!=-1 + let szInclusion = szLine[startPos-1:endPos-1] + let szIncludeFile = substitute(szInclusion, '\('.s:rePreprocIncludePart.'\)\|[<>""]', '', 'g') + let szResolvedInclude = omni#cpp#utils#ResolveFilePath(szIncludeFile) + + " Protection over self inclusion + if szResolvedInclude != '' && szResolvedInclude != omni#cpp#utils#ResolveFilePath(getreg('%')) + let includePos = curPos + if !has_key(alreadyInclude, szResolvedInclude) + call extend(listIncludes, [{'pos' : includePos, 'include' : szResolvedInclude}]) + let alreadyInclude[szResolvedInclude] = 1 + endif + endif + endif + endif + endwhile + + call setpos('.', originalPos) + return listIncludes +endfunc + +" Get the include list from a file +function! s:GetIncludeListFromFile(szFilePath, bUpdate) + let listIncludes = [] + if a:szFilePath == '' + return listIncludes + endif + + if !a:bUpdate && has_key(g:omni#cpp#includes#CACHE_INCLUDES, a:szFilePath) + return copy(g:omni#cpp#includes#CACHE_INCLUDES[a:szFilePath]) + endif + + let g:omni#cpp#includes#CACHE_FILE_TIME[a:szFilePath] = getftime(a:szFilePath) + + let szFixedPath = escape(a:szFilePath, g:omni#cpp#utils#szEscapedCharacters) + execute 'silent! lvimgrep /\C\(^'.s:rePreprocIncludeFile.'\)/gj '.szFixedPath + + let listQuickFix = getloclist(0) + let alreadyInclude = {} + for qf in listQuickFix + let szLine = qf.text + let startPos = qf.col + let endPos = matchend(szLine, s:reIncludeFilePart, startPos-1) + if endPos!=-1 + let szInclusion = szLine[startPos-1:endPos-1] + let szIncludeFile = substitute(szInclusion, '\('.s:rePreprocIncludePart.'\)\|[<>""]', '', 'g') + let szResolvedInclude = omni#cpp#utils#ResolveFilePath(szIncludeFile) + + " Protection over self inclusion + if szResolvedInclude != '' && szResolvedInclude != a:szFilePath + let includePos = [qf.lnum, qf.col] + if !has_key(alreadyInclude, szResolvedInclude) + call extend(listIncludes, [{'pos' : includePos, 'include' : szResolvedInclude}]) + let alreadyInclude[szResolvedInclude] = 1 + endif + endif + endif + endfor + + let g:omni#cpp#includes#CACHE_INCLUDES[a:szFilePath] = listIncludes + + return copy(listIncludes) +endfunc + +" For debug purpose +function! omni#cpp#includes#Display() + let szPathBuffer = omni#cpp#utils#ResolveFilePath(getreg('%')) + call s:DisplayIncludeTree(szPathBuffer, 0) +endfunc + +" For debug purpose +function! s:DisplayIncludeTree(szFilePath, indent, ...) + let includeGuard = {} + if a:0 >0 + let includeGuard = a:1 + endif + let szFilePath = omni#cpp#utils#ResolveFilePath(a:szFilePath) + if has_key(includeGuard, szFilePath) + return + else + let includeGuard[szFilePath] = 1 + endif + + let szIndent = repeat(' ', a:indent) + echo szIndent . a:szFilePath + let incList = omni#cpp#includes#GetList(a:szFilePath) + for inc in incList + call s:DisplayIncludeTree(inc.include, a:indent+1, includeGuard) + endfor +endfunc + + diff --git a/VimFiles/autoload/omni/cpp/items.vim b/VimFiles/autoload/omni/cpp/items.vim new file mode 100755 index 0000000..b943ad4 --- /dev/null +++ b/VimFiles/autoload/omni/cpp/items.vim @@ -0,0 +1,660 @@ +" Description: Omni completion script for cpp files +" Maintainer: Vissale NEANG +" Last Change: 26 sept. 2007 + +" Build the item list of an instruction +" An item is an instruction between a -> or . or ->* or .* +" We can sort an item in different kinds: +" eg: ((MyClass1*)(pObject))->_memberOfClass1.get() ->show() +" | cast | | member | | method | | method | +" @return a list of item +" an item is a dictionnary where keys are: +" tokens = list of token +" kind = itemVariable|itemCast|itemCppCast|itemTemplate|itemFunction|itemUnknown|itemThis|itemScope +function! omni#cpp#items#Get(tokens, ...) + let bGetWordUnderCursor = (a:0>0)? a:1 : 0 + + let result = [] + let itemsDelimiters = ['->', '.', '->*', '.*'] + + let tokens = reverse(omni#cpp#utils#BuildParenthesisGroups(a:tokens)) + + " fsm states: + " 0 = initial state + " TODO: add description of fsm states + let state=(bGetWordUnderCursor)? 1 : 0 + let item = {'tokens' : [], 'kind' : 'itemUnknown'} + let parenGroup=-1 + for token in tokens + if state==0 + if index(itemsDelimiters, token.value)>=0 + let item = {'tokens' : [], 'kind' : 'itemUnknown'} + let state = 1 + elseif token.value=='::' + let state = 9 + let item.kind = 'itemScope' + " Maybe end of tokens + elseif token.kind =='cppOperatorPunctuator' + " If it's a cppOperatorPunctuator and the current token is not + " a itemsDelimiters or '::' we can exit + let state=-1 + break + endif + elseif state==1 + call insert(item.tokens, token) + if token.kind=='cppWord' + " It's an attribute member or a variable + let item.kind = 'itemVariable' + let state = 2 + " Maybe end of tokens + elseif token.value=='this' + let item.kind = 'itemThis' + let state = 2 + " Maybe end of tokens + elseif token.value==')' + let parenGroup = token.group + let state = 3 + elseif token.value==']' + let parenGroup = token.group + let state = 4 + elseif token.kind == 'cppDigit' + let state = -1 + break + endif + elseif state==2 + if index(itemsDelimiters, token.value)>=0 + call insert(result, item) + let item = {'tokens' : [], 'kind' : 'itemUnknown'} + let state = 1 + elseif token.value == '::' + call insert(item.tokens, token) + " We have to get namespace or classscope + let state = 8 + " Maybe end of tokens + else + call insert(result, item) + let state=-1 + break + endif + elseif state==3 + call insert(item.tokens, token) + if token.value=='(' && token.group == parenGroup + let state = 5 + " Maybe end of tokens + endif + elseif state==4 + call insert(item.tokens, token) + if token.value=='[' && token.group == parenGroup + let state = 1 + endif + elseif state==5 + if token.kind=='cppWord' + " It's a function or method + let item.kind = 'itemFunction' + call insert(item.tokens, token) + let state = 2 + " Maybe end of tokens + elseif token.value == '>' + " Maybe a cpp cast or template + let item.kind = 'itemTemplate' + call insert(item.tokens, token) + let parenGroup = token.group + let state = 6 + else + " Perhaps it's a C cast eg: ((void*)(pData)) or a variable eg:(*pData) + let item.kind = omni#cpp#utils#GetCastType(item.tokens) + let state=-1 + call insert(result, item) + break + endif + elseif state==6 + call insert(item.tokens, token) + if token.value == '<' && token.group == parenGroup + " Maybe a cpp cast or template + let state = 7 + endif + elseif state==7 + call insert(item.tokens, token) + if token.kind=='cppKeyword' + " It's a cpp cast + let item.kind = omni#cpp#utils#GetCastType(item.tokens) + let state=-1 + call insert(result, item) + break + else + " Template ? + let state=-1 + call insert(result, item) + break + endif + elseif state==8 + if token.kind=='cppWord' + call insert(item.tokens, token) + let state = 2 + " Maybe end of tokens + else + let state=-1 + call insert(result, item) + break + endif + elseif state==9 + if token.kind == 'cppWord' + call insert(item.tokens, token) + let state = 10 + " Maybe end of tokens + else + let state=-1 + call insert(result, item) + break + endif + elseif state==10 + if token.value == '::' + call insert(item.tokens, token) + let state = 9 + " Maybe end of tokens + else + let state=-1 + call insert(result, item) + break + endif + endif + endfor + + if index([2, 5, 8, 9, 10], state)>=0 + if state==5 + let item.kind = omni#cpp#utils#GetCastType(item.tokens) + endif + call insert(result, item) + endif + + return result +endfunc + +" Resolve type information of items +" @param namespaces: list of namespaces used in the file +" @param szCurrentClassScope: the current class scope, only used for the first +" item to detect if this item is a class member (attribute, method) +" @param items: list of item, can be an empty list @see GetItemsToComplete +function! omni#cpp#items#ResolveItemsTypeInfo(contextStack, items) + " Note: kind = itemVariable|cCast|cppCast|template|function|itemUnknown|this + " For the first item, if it's a variable we try to detect the type of the + " variable with the function searchdecl. If it fails, thanks to the + " current class scope, we try to detect if the variable is an attribute + " member. + " If the kind of the item is a function, we have to first check if the + " function is a method of the class, if it fails we try to get a match in + " the global namespace. After that we get the returned type of the + " function. + " It the kind is a C cast or C++ cast, there is no problem, it's the + " easiest case. We just extract the type of the cast. + + let szCurrentContext = '' + let typeInfo = {} + " Note: We search the decl only for the first item + let bSearchDecl = 1 + for item in a:items + let curItem = item + if index(['itemVariable', 'itemFunction'], curItem.kind)>=0 + " Note: a variable can be : MyNs::MyClass::_var or _var or (*pVar) + " or _var[0][0] + let szSymbol = s:GetSymbol(curItem.tokens) + + " If we have MyNamespace::myVar + " We add MyNamespace in the context stack set szSymbol to myVar + if match(szSymbol, '::\w\+$') >= 0 + let szCurrentContext = substitute(szSymbol, '::\w\+$', '', 'g') + let szSymbol = matchstr(szSymbol, '\w\+$') + endif + let tmpContextStack = a:contextStack + if szCurrentContext != '' + let tmpContextStack = [szCurrentContext] + a:contextStack + endif + + if curItem.kind == 'itemVariable' + let typeInfo = s:GetTypeInfoOfVariable(tmpContextStack, szSymbol, bSearchDecl) + else + let typeInfo = s:GetTypeInfoOfReturnedType(tmpContextStack, szSymbol) + endif + + elseif curItem.kind == 'itemThis' + if len(a:contextStack) + let typeInfo = omni#cpp#utils#CreateTypeInfo(substitute(a:contextStack[0], '^::', '', 'g')) + endif + elseif curItem.kind == 'itemCast' + let typeInfo = omni#cpp#utils#CreateTypeInfo(s:ResolveCCast(curItem.tokens)) + elseif curItem.kind == 'itemCppCast' + let typeInfo = omni#cpp#utils#CreateTypeInfo(s:ResolveCppCast(curItem.tokens)) + elseif curItem.kind == 'itemScope' + let typeInfo = omni#cpp#utils#CreateTypeInfo(substitute(s:TokensToString(curItem.tokens), '\s', '', 'g')) + endif + + if omni#cpp#utils#IsTypeInfoValid(typeInfo) + let szCurrentContext = omni#cpp#utils#GetTypeInfoString(typeInfo) + endif + let bSearchDecl = 0 + endfor + + return typeInfo +endfunc + +" Get symbol name +function! s:GetSymbol(tokens) + let szSymbol = '' + let state = 0 + for token in a:tokens + if state == 0 + if token.value == '::' + let szSymbol .= token.value + let state = 1 + elseif token.kind == 'cppWord' + let szSymbol .= token.value + let state = 2 + " Maybe end of token + endif + elseif state == 1 + if token.kind == 'cppWord' + let szSymbol .= token.value + let state = 2 + " Maybe end of token + else + " Error + break + endif + elseif state == 2 + if token.value == '::' + let szSymbol .= token.value + let state = 1 + else + break + endif + endif + endfor + return szSymbol +endfunc + +" Search a declaration. +" eg: std::map +" can be empty +" Note: The returned type info can be a typedef +" The typedef resolution is done later +" @return +" - a dictionnary where keys are +" - type: the type of value same as type() +" - value: the value +function! s:GetTypeInfoOfVariable(contextStack, szVariable, bSearchDecl) + let result = {} + + if a:bSearchDecl + " Search type of declaration + "let result = s:SearchTypeInfoOfDecl(a:szVariable) + let result = s:SearchDecl(a:szVariable) + endif + + if result=={} + let szFilter = "index(['m', 'v'], v:val.kind[0])>=0" + let tagItem = s:ResolveSymbol(a:contextStack, a:szVariable, szFilter) + if tagItem=={} + return result + endif + + let szCmdWithoutVariable = substitute(omni#cpp#utils#ExtractCmdFromTagItem(tagItem), '\C\<'.a:szVariable.'\>.*', '', 'g') + let tokens = omni#cpp#tokenizer#Tokenize(omni#cpp#utils#GetCodeFromLine(szCmdWithoutVariable)) + let result = omni#cpp#utils#CreateTypeInfo(omni#cpp#utils#ExtractTypeInfoFromTokens(tokens)) + " TODO: Namespace resolution for result + + if result != {} && result.value=='' + " result.value=='' + " eg: + " struct + " { + " }gVariable; + if has_key(tagItem, 'typeref') + " Maybe the variable is a global var of an + " unnamed class, struct or union. + " eg: + " 1) + " struct + " { + " }gVariable; + " In this case we need the tags (the patched version) + " Note: We can have a named type like this: + " 2) + " class A + " { + " }gVariable; + if s:IsUnnamedType(tagItem) + " It's an unnamed type we are in the case 1) + let result = omni#cpp#utils#CreateTypeInfo(tagItem) + else + " It's not an unnamed type we are in the case 2) + + " eg: tagItem.typeref = 'struct:MY_STRUCT::MY_SUBSTRUCT' + let szTypeRef = substitute(tagItem.typeref, '^\w\+:', '', '') + + " eg: szTypeRef = 'MY_STRUCT::MY_SUBSTRUCT' + let result = omni#cpp#utils#CreateTypeInfo(szTypeRef) + endif + endif + endif + endif + return result +endfunc + +" Get the type info string from the returned type of function +function! s:GetTypeInfoOfReturnedType(contextStack, szFunctionName) + let result = {} + + let szFilter = "index(['f', 'p'], v:val.kind[0])>=0" + let tagItem = s:ResolveSymbol(a:contextStack, a:szFunctionName, szFilter) + + if tagItem != {} + let szCmdWithoutVariable = substitute(omni#cpp#utils#ExtractCmdFromTagItem(tagItem), '\C\<'.a:szFunctionName.'\>.*', '', 'g') + let tokens = omni#cpp#tokenizer#Tokenize(omni#cpp#utils#GetCodeFromLine(szCmdWithoutVariable)) + let result = omni#cpp#utils#CreateTypeInfo(omni#cpp#utils#ExtractTypeInfoFromTokens(tokens)) + " TODO: Namespace resolution for result + return result + endif + return result +endfunc + +" Resolve a symbol, return a tagItem +" Gets the first symbol found in the context stack +function! s:ResolveSymbol(contextStack, szSymbol, szTagFilter) + let tagItem = {} + for szCurrentContext in a:contextStack + if szCurrentContext != '::' + let szTagQuery = substitute(szCurrentContext, '^::', '', 'g').'::'.a:szSymbol + else + let szTagQuery = a:szSymbol + endif + + let tagList = omni#common#utils#TagListNoThrow('^'.szTagQuery.'$') + call filter(tagList, a:szTagFilter) + if len(tagList) + let tagItem = tagList[0] + break + endif + endfor + return tagItem +endfunc + +" Return if the tag item represent an unnamed type +function! s:IsUnnamedType(tagItem) + let bResult = 0 + if has_key(a:tagItem, 'typeref') + " Note: Thanks for __anon ! + let bResult = match(a:tagItem.typeref, '\C\<__anon') >= 0 + endif + return bResult +endfunc + +" Search the declaration of a variable and return the type info +function! s:SearchTypeInfoOfDecl(szVariable) + let szReVariable = '\C\<'.a:szVariable.'\>' + + let originalPos = getpos('.') + let origPos = originalPos[1:2] + let curPos = origPos + let stopPos = origPos + + while curPos !=[0,0] + " We go to the start of the current scope + let curPos = searchpairpos('{', '', '}', 'bW', g:omni#cpp#utils#expIgnoreComments) + if curPos != [0,0] + let matchPos = curPos + " Now want to search our variable but we don't want to go in child + " scope + while matchPos != [0,0] + let matchPos = searchpos('{\|'.szReVariable, 'W', stopPos[0]) + if matchPos != [0,0] + " We ignore matches under comment + if omni#cpp#utils#IsCursorInCommentOrString() + continue + endif + + " Getting the current line + let szLine = getline('.') + if match(szLine, szReVariable)>=0 + " We found our variable + " Check if the current instruction is a decl instruction + let tokens = omni#cpp#utils#TokenizeCurrentInstruction() + let szTypeInfo = s:ExtractTypeInfoFromDecl(tokens) + if szTypeInfo != '' + call setpos('.', originalPos) + return omni#cpp#utils#CreateTypeInfo(szTypeInfo) + endif + else + " We found a child scope, we don't want to go in, thus + " we search for the end } of this child scope + let bracketEnd = searchpairpos('{', '', '}', 'nW', g:omni#cpp#utils#expIgnoreComments) + if bracketEnd == [0,0] + break + endif + + if bracketEnd[0] >= stopPos[0] + " The end of the scope is after our cursor we stop + " the search + break + else + " We move the cursor and continue to search our + " variable + call setpos('.', [0, bracketEnd[0], bracketEnd[1], 0]) + endif + endif + endif + endwhile + + " Backing to the start of the scope + call setpos('.', [0,curPos[0], curPos[1], 0]) + let stopPos = curPos + endif + endwhile + + let result = {} + if s:LocalSearchDecl(a:szVariable)==0 && !omni#cpp#utils#IsCursorInCommentOrString() + let tokens = omni#cpp#utils#TokenizeCurrentInstruction() + let szTypeInfo = s:ExtractTypeInfoFromDecl(tokens) + if szTypeInfo != '' + let result = omni#cpp#utils#CreateTypeInfo(szTypeInfo) + endif + endif + + call setpos('.', originalPos) + + return result +endfunc + +" Search a declaration +" @return +" - tokens of the current instruction if success +" - empty list if failure +function! s:SearchDecl(szVariable) + let result = {} + let originalPos = getpos('.') + let searchResult = s:LocalSearchDecl(a:szVariable) + if searchResult==0 + " searchdecl() may detect a decl if the variable is in a conditional + " instruction (if, elseif, while etc...) + " We have to check if the detected decl is really a decl instruction + let tokens = omni#cpp#utils#TokenizeCurrentInstruction() + + for token in tokens + " Simple test + if index(['if', 'elseif', 'while', 'for', 'switch'], token.value)>=0 + " Invalid declaration instruction + call setpos('.', originalPos) + return result + endif + endfor + + let szTypeInfo = s:ExtractTypeInfoFromDecl(tokens) + if szTypeInfo != '' + let result = omni#cpp#utils#CreateTypeInfo(szTypeInfo) + endif + endif + call setpos('.', originalPos) + return result +endfunc + +" Extract the type info string from an instruction. +" We use a small parser to extract the type +" We parse the code according to a C++ BNF from: http://www.nongnu.org/hcb/#basic.link +" @param tokens: token list of the current instruction +function! s:ExtractTypeInfoFromDecl(tokens) + return omni#cpp#utils#ExtractTypeInfoFromTokens(a:tokens) +endfunc + +" Convert tokens to string +function! s:TokensToString(tokens) + let result = '' + for token in a:tokens + let result = result . token.value . ' ' + endfor + return result[:-2] +endfunc + +" Resolve a cast. +" Resolve a C++ cast +" @param list of token. tokens must be a list that represents +" a cast expression (C++ cast) the function does not control +" if it's a cast or not +" eg: static_cast(something) +" @return type info string +function! s:ResolveCppCast(tokens) + return omni#cpp#utils#ExtractTypeInfoFromTokens(s:ResolveCast(a:tokens, '<', '>')) +endfunc + +" Resolve a cast. +" Resolve a C cast +" @param list of token. tokens must be a list that represents +" a cast expression (C cast) the function does not control +" if it's a cast or not +" eg: (MyClass*)something +" @return type info string +function! s:ResolveCCast(tokens) + return omni#cpp#utils#ExtractTypeInfoFromTokens(s:ResolveCast(a:tokens, '(', ')')) +endfunc + +" Resolve a cast. +" Resolve a C cast +" @param list of token. tokens must be a list that represents +" a cast expression (C cast) the function does not control +" if it's a cast or not +" eg: (MyClass*)something +" @return type tokens +function! s:ResolveCast(tokens, startChar, endChar) + let tokens = omni#cpp#utils#BuildParenthesisGroups(a:tokens) + + " We remove useless parenthesis eg: (((MyClass))) + let tokens = omni#cpp#utils#SimplifyParenthesis(tokens) + + let countItem=0 + let startIndex = -1 + let endIndex = -1 + let i = 0 + for token in tokens + if startIndex==-1 + if token.value==a:startChar + let countItem += 1 + let startIndex = i + endif + else + if token.value==a:startChar + let countItem += 1 + elseif token.value==a:endChar + let countItem -= 1 + endif + + if countItem==0 + let endIndex = i + break + endif + endif + let i+=1 + endfor + + return tokens[startIndex+1 : endIndex-1] +endfunc + +" Replacement for build-in function 'searchdecl' +" It does not require that the upper-level bracket is in the first column. +" Otherwise it should be equal to 'searchdecl(name, 0, 1)' +" @param name: name of variable to find declaration for +function! s:LocalSearchDecl(name) + + if g:OmniCpp_LocalSearchDecl == 0 + let bUserIgnoreCase = &ignorecase + + " Forcing the noignorecase option + " avoid bug when, for example, if we have a declaration like this : "A a;" + set noignorecase + + let result = searchdecl(a:name, 0, 1) + + " Restoring user's setting + let &ignorecase = bUserIgnoreCase + + return result + endif + + let lastpos = getpos('.') + let winview = winsaveview() + let lastfoldenable = &foldenable + let &foldenable = 0 + + " We add \C (noignorecase) to + " avoid bug when, for example, if we have a declaration like this : "A a;" + let varname = "\\C\\<" . a:name . "\\>" + + " Go to first blank line before begin of highest scope + normal 99[{ + let scopepos = getpos('.') + while (line('.') > 1) && (len(split(getline('.'))) > 0) + call cursor(line('.')-1, 0) + endwhile + + let declpos = [ 0, 0, 0, 0 ] + while search(varname, '', scopepos[1]) > 0 + " Check if we are a string or a comment + if omni#cpp#utils#IsCursorInCommentOrString() + continue + endif + + " Remember match + let declpos = getpos('.') + endwhile + if declpos[1] != 0 + " We found a match + call winrestview(winview) + call setpos('.', declpos) + let &foldenable = lastfoldenable + return 0 + endif + + while search(varname, '', lastpos[1]) > 0 + " Check if current scope is ending before variable + let old_cur = getpos('.') + normal ]} + let new_cur = getpos('.') + call setpos('.', old_cur) + if (new_cur[1] < lastpos[1]) || ((new_cur[1] == lastpos[1]) && (new_cur[2] < lastpos[2])) + continue + endif + + " Check if we are a string or a comment + if omni#cpp#utils#IsCursorInCommentOrString() + continue + endif + + " We found match + call winrestview(winview) + call setpos('.', old_cur) + let &foldenable = lastfoldenable + return 0 + endwhile + + " No match found. + call winrestview(winview) + let &foldenable = lastfoldenable + return 1 +endfunc diff --git a/VimFiles/autoload/omni/cpp/maycomplete.vim b/VimFiles/autoload/omni/cpp/maycomplete.vim new file mode 100755 index 0000000..610526b --- /dev/null +++ b/VimFiles/autoload/omni/cpp/maycomplete.vim @@ -0,0 +1,82 @@ +" Description: Omni completion script for cpp files +" Maintainer: Vissale NEANG +" Last Change: 26 sept. 2007 + +" Check if we can use omni completion in the current buffer +function! s:CanUseOmnicompletion() + " For C and C++ files and only if the omnifunc is omni#cpp#complete#Main + return (index(['c', 'cpp'], &filetype)>=0 && &omnifunc == 'omni#cpp#complete#Main' && !omni#cpp#utils#IsCursorInCommentOrString()) +endfunc + +" Return the mapping of omni completion +function! omni#cpp#maycomplete#Complete() + let szOmniMapping = "\\" + + " 0 = don't select first item + " 1 = select first item (inserting it to the text, default vim behaviour) + " 2 = select first item (without inserting it to the text) + if g:OmniCpp_SelectFirstItem == 0 + " We have to force the menuone option to avoid confusion when there is + " only one popup item + set completeopt-=menu + set completeopt+=menuone + let szOmniMapping .= "\" + elseif g:OmniCpp_SelectFirstItem == 2 + " We have to force the menuone option to avoid confusion when there is + " only one popup item + set completeopt-=menu + set completeopt+=menuone + let szOmniMapping .= "\" + let szOmniMapping .= "\=pumvisible() ? \"\\\" : \"\"\" + endif + return szOmniMapping +endfunc + +" May complete function for dot +function! omni#cpp#maycomplete#Dot() + if s:CanUseOmnicompletion() && g:OmniCpp_MayCompleteDot + let g:omni#cpp#items#data = omni#cpp#items#Get(omni#cpp#utils#TokenizeCurrentInstruction('.')) + if len(g:omni#cpp#items#data) + let s:bMayComplete = 1 + return '.' . omni#cpp#maycomplete#Complete() + endif + endif + return '.' +endfunc +" May complete function for arrow +function! omni#cpp#maycomplete#Arrow() + if s:CanUseOmnicompletion() && g:OmniCpp_MayCompleteArrow + let index = col('.') - 2 + if index >= 0 + let char = getline('.')[index] + if char == '-' + let g:omni#cpp#items#data = omni#cpp#items#Get(omni#cpp#utils#TokenizeCurrentInstruction('>')) + if len(g:omni#cpp#items#data) + let s:bMayComplete = 1 + return '>' . omni#cpp#maycomplete#Complete() + endif + endif + endif + endif + return '>' +endfunc + +" May complete function for double points +function! omni#cpp#maycomplete#Scope() + if s:CanUseOmnicompletion() && g:OmniCpp_MayCompleteScope + let index = col('.') - 2 + if index >= 0 + let char = getline('.')[index] + if char == ':' + let g:omni#cpp#items#data = omni#cpp#items#Get(omni#cpp#utils#TokenizeCurrentInstruction(':')) + if len(g:omni#cpp#items#data) + if len(g:omni#cpp#items#data[-1].tokens) && g:omni#cpp#items#data[-1].tokens[-1].value != '::' + let s:bMayComplete = 1 + return ':' . omni#cpp#maycomplete#Complete() + endif + endif + endif + endif + endif + return ':' +endfunc diff --git a/VimFiles/autoload/omni/cpp/namespaces.vim b/VimFiles/autoload/omni/cpp/namespaces.vim new file mode 100755 index 0000000..386b3f9 --- /dev/null +++ b/VimFiles/autoload/omni/cpp/namespaces.vim @@ -0,0 +1,838 @@ +" Description: Omni completion script for cpp files +" Maintainer: Vissale NEANG +" Last Change: 26 sept. 2007 + +let g:omni#cpp#namespaces#CacheResolve = {} +let g:omni#cpp#namespaces#CacheUsing = {} +" TODO: For the next release +"let g:omni#cpp#namespaces#CacheAlias = {} + +" Get the using namespace list from a line +function! s:GetNamespaceAliasListFromLine(szLine) + let result = {} + let tokens = omni#cpp#tokenizer#Tokenize(a:szLine) + let szAlias = '' + let szNamespace = '' + let state = 0 + for token in tokens + if state==0 + let szAlias = '' + let szNamespace = '' + if token.value == '/*' + let state = 1 + elseif token.value == '//' + " It's a comment + let state = -1 + break + elseif token.value == 'namespace' + let state = 2 + endif + elseif state==1 + if token.value == '*/' + let state=0 + endif + elseif state==2 + if token.kind == 'cppWord' + let szAlias .= token.value + let state = 3 + else + let state = -1 + break + endif + elseif state == 3 + if token.value == '=' + let state = 4 + else + let state = -1 + break + endif + elseif state == 4 + if token.value == '::' + let szNamespace .= token.value + let state = 5 + elseif token.kind == 'cppWord' + let szNamespace .= token.value + let state = 6 + " Maybe end of tokens + endif + elseif state==5 + if token.kind == 'cppWord' + let szNamespace .= token.value + let state = 6 + " Maybe end of tokens + else + " Error, we can't have 'namespace ALIAS = Something::' + let state = -1 + break + endif + elseif state==6 + if token.value == '::' + let szNamespace .= token.value + let state = 5 + else + call extend(result, {szAlias : szNamespace}) + let state = 0 + endif + endif + endfor + + if state == 6 + call extend(result, {szAlias : szNamespace}) + endif + + return result +endfunc + +" Get the using namespace list from a line +function! s:GetNamespaceListFromLine(szLine) + let result = [] + let tokens = omni#cpp#tokenizer#Tokenize(a:szLine) + let szNamespace = '' + let state = 0 + for token in tokens + if state==0 + let szNamespace = '' + if token.value == '/*' + let state = 1 + elseif token.value == '//' + " It's a comment + let state = -1 + break + elseif token.value == 'using' + let state = 2 + endif + elseif state==1 + if token.value == '*/' + let state=0 + endif + elseif state==2 + if token.value == 'namespace' + let state = 3 + else + " Error, 'using' must be followed by 'namespace' + let state = -1 + break + endif + elseif state==3 + if token.value == '::' + let szNamespace .= token.value + let state = 4 + elseif token.kind == 'cppWord' + let szNamespace .= token.value + let state = 5 + " Maybe end of tokens + endif + elseif state==4 + if token.kind == 'cppWord' + let szNamespace .= token.value + let state = 5 + " Maybe end of tokens + else + " Error, we can't have 'using namespace Something::' + let state = -1 + break + endif + elseif state==5 + if token.value == '::' + let szNamespace .= token.value + let state = 4 + else + call extend(result, [szNamespace]) + let state = 0 + endif + endif + endfor + + if state == 5 + call extend(result, [szNamespace]) + endif + + return result +endfunc + +" Get the namespace list from a namespace map +function! s:GetUsingNamespaceListFromMap(namespaceMap, ...) + let stopLine = 0 + if a:0>0 + let stopLine = a:1 + endif + + let result = [] + let keys = sort(keys(a:namespaceMap), 'omni#common#utils#CompareNumber') + for i in keys + if stopLine != 0 && i > stopLine + break + endif + call extend(result, a:namespaceMap[i]) + endfor + return result +endfunc + +" Get global using namespace list from the current buffer +function! omni#cpp#namespaces#GetListFromCurrentBuffer(...) + let namespaceMap = s:GetAllUsingNamespaceMapFromCurrentBuffer() + let result = [] + if namespaceMap != {} + let result = s:GetUsingNamespaceListFromMap(namespaceMap, (a:0 > 0)? a:1 : line('.')) + endif + return result +endfunc + +" Get global using namespace map from the current buffer and include files recursively +function! s:GetAllUsingNamespaceMapFromCurrentBuffer(...) + let includeGuard = (a:0>0)? a:1 : {} + + let szBufferName = getreg("%") + let szFilePath = omni#cpp#utils#ResolveFilePath(szBufferName) + let szFilePath = (szFilePath=='')? szBufferName : szFilePath + + let namespaceMap = {} + if has_key(includeGuard, szFilePath) + return namespaceMap + else + let includeGuard[szFilePath] = 1 + endif + + let namespaceMap = omni#cpp#namespaces#GetMapFromCurrentBuffer() + + if g:OmniCpp_NamespaceSearch != 2 + " We don't search included files if OmniCpp_NamespaceSearch != 2 + return namespaceMap + endif + + for inc in omni#cpp#includes#GetList() + let lnum = inc.pos[0] + let tmpMap = s:GetAllUsingNamespaceMapFromFile(inc.include, includeGuard) + if tmpMap != {} + if has_key(namespaceMap, lnum) + call extend(namespaceMap[lnum], s:GetUsingNamespaceListFromMap(tmpMap)) + else + let namespaceMap[lnum] = s:GetUsingNamespaceListFromMap(tmpMap) + endif + endif + endfor + + return namespaceMap +endfunc + +" Get global using namespace map from a file and include files recursively +function! s:GetAllUsingNamespaceMapFromFile(szFilePath, ...) + let includeGuard = {} + if a:0 >0 + let includeGuard = a:1 + endif + + let szFilePath = omni#cpp#utils#ResolveFilePath(a:szFilePath) + let szFilePath = (szFilePath=='')? a:szFilePath : szFilePath + + let namespaceMap = {} + if has_key(includeGuard, szFilePath) + return namespaceMap + else + let includeGuard[szFilePath] = 1 + endif + + " If g:OmniCpp_NamespaceSearch == 1 (search namespaces only in the current + " buffer) we don't use cache for the current buffer + let namespaceMap = omni#cpp#namespaces#GetMapFromBuffer(szFilePath, g:OmniCpp_NamespaceSearch==1) + + if g:OmniCpp_NamespaceSearch != 2 + " We don't search included files if OmniCpp_NamespaceSearch != 2 + return namespaceMap + endif + + for inc in omni#cpp#includes#GetList(szFilePath) + let lnum = inc.pos[0] + let tmpMap = s:GetAllUsingNamespaceMapFromFile(inc.include, includeGuard) + if tmpMap != {} + if has_key(namespaceMap, lnum) + call extend(namespaceMap[lnum], s:GetUsingNamespaceListFromMap(tmpMap)) + else + let namespaceMap[lnum] = s:GetUsingNamespaceListFromMap(tmpMap) + endif + endif + endfor + + return namespaceMap +endfunc + +" Get global using namespace map from a the current buffer +function! omni#cpp#namespaces#GetMapFromCurrentBuffer() + let namespaceMap = {} + let originalPos = getpos('.') + + call setpos('.', [0, 1, 1, 0]) + let curPos = [1,1] + while curPos != [0,0] + let curPos = searchpos('\C^using\s\+namespace', 'W') + if curPos != [0,0] + let szLine = getline('.') + let startPos = curPos[1] + let endPos = match(szLine, ';', startPos-1) + if endPos!=-1 + " We get the namespace list from the line + let namespaceMap[curPos[0]] = s:GetNamespaceListFromLine(szLine) + endif + endif + endwhile + + call setpos('.', originalPos) + return namespaceMap +endfunc + +" Get global using namespace map from a file +function! omni#cpp#namespaces#GetMapFromBuffer(szFilePath, ...) + let bUpdate = 0 + if a:0 > 0 + let bUpdate = a:1 + endif + + let szFilePath = omni#cpp#utils#ResolveFilePath(a:szFilePath) + let szFilePath = (szFilePath=='')? a:szFilePath : szFilePath + + if !bUpdate && has_key(g:omni#cpp#namespaces#CacheUsing, szFilePath) + return copy(g:omni#cpp#namespaces#CacheUsing[szFilePath]) + endif + + let namespaceMap = {} + " The file exists, we get the global namespaces in this file + let szFixedPath = escape(szFilePath, g:omni#cpp#utils#szEscapedCharacters) + execute 'silent! lvimgrep /\C^using\s\+namespace/gj '.szFixedPath + + " key = line number + " value = list of namespaces + let listQuickFix = getloclist(0) + for qf in listQuickFix + let szLine = qf.text + let startPos = qf.col + let endPos = match(szLine, ';', startPos-1) + if endPos!=-1 + " We get the namespace list from the line + let namespaceMap[qf.lnum] = s:GetNamespaceListFromLine(szLine) + endif + endfor + + if szFixedPath != '' + let g:omni#cpp#namespaces#CacheUsing[szFixedPath] = namespaceMap + endif + + return copy(namespaceMap) +endfunc + +" Get the stop position when searching for local variables +function! s:GetStopPositionForLocalSearch() + " Stop position when searching a local variable + let originalPos = getpos('.') + let origPos = originalPos[1:2] + let stopPosition = origPos + let curPos = origPos + while curPos !=[0,0] + let stopPosition = curPos + let curPos = searchpairpos('{', '', '}', 'bW', g:omni#cpp#utils#expIgnoreComments) + endwhile + call setpos('.', originalPos) + + return stopPosition +endfunc + +" Get namespaces alias used at the cursor postion in a vim buffer +" Note: The result depends on the current cursor position +" @return +" - Map of namespace alias +function! s:GetNamespaceAliasMap() + " We store the cursor position because searchpairpos() moves the cursor + let result = {} + let originalPos = getpos('.') + let origPos = originalPos[1:2] + + let stopPos = s:GetStopPositionForLocalSearch() + let stopLine = stopPos[0] + let curPos = origPos + let lastLine = 0 + let nextStopLine = origPos[0] + let szReAlias = '\Cnamespace\s\+\w\+\s\+=' + while curPos !=[0,0] + let curPos = searchpos('}\|\('. szReAlias .'\)', 'bW',stopLine) + if curPos!=[0,0] && curPos[0]!=lastLine + let lastLine = curPos[0] + + let szLine = getline('.') + if origPos[0] == curPos[0] + " We get the line until cursor position + let szLine = szLine[:origPos[1]] + endif + + let szLine = omni#cpp#utils#GetCodeFromLine(szLine) + if match(szLine, szReAlias)<0 + " We found a '}' + let curPos = searchpairpos('{', '', '}', 'bW', g:omni#cpp#utils#expIgnoreComments) + else + " We get the namespace alias from the line + call extend(result, s:GetNamespaceAliasListFromLine(szLine)) + let nextStopLine = curPos[0] + endif + endif + endwhile + + " Setting the cursor to the original position + call setpos('.', originalPos) + + call s:ResolveAliasKeys(result) + return result +endfunc + +" Resolve an alias +" eg: namespace IAmAnAlias1 = Ns1 +" eg: namespace IAmAnAlias2 = IAmAnAlias1::Ns2 +" => IAmAnAlias2 = Ns1::Ns2 +function! s:ResolveAliasKey(mapNamespaceAlias, szAlias) + let szResult = a:mapNamespaceAlias[a:szAlias] + " ::Ns1::Ns2::Ns3 => ['Ns1', 'Ns2', 'Ns3'] + let listNamespace = split(szResult, '::') + if len(listNamespace) + " szBeginPart = 'Ns1' + let szBeginPart = remove(listNamespace, 0) + + " Is 'Ns1' an alias ? + if has_key(a:mapNamespaceAlias, szBeginPart) && szBeginPart != a:szAlias + " Resolving alias 'Ns1' + " eg: Ns1 = NsResolved + let szResult = s:ResolveAliasKey(a:mapNamespaceAlias, szBeginPart) + " szEndPart = 'Ns2::Ns3' + let szEndPart = join(listNamespace, '::') + if szEndPart != '' + " Concatenation => szResult = 'NsResolved::Ns2::Ns3' + let szResult .= '::' . szEndPart + endif + endif + endif + return szResult +endfunc + +" Resolve all keys in the namespace alias map +function! s:ResolveAliasKeys(mapNamespaceAlias) + let mapNamespaceAlias = a:mapNamespaceAlias + call map(mapNamespaceAlias, 's:ResolveAliasKey(mapNamespaceAlias, v:key)') +endfunc + +" Resolve namespace alias +function! omni#cpp#namespaces#ResolveAlias(mapNamespaceAlias, szNamespace) + let szResult = a:szNamespace + " ::Ns1::Ns2::Ns3 => ['Ns1', 'Ns2', 'Ns3'] + let listNamespace = split(a:szNamespace, '::') + if len(listNamespace) + " szBeginPart = 'Ns1' + let szBeginPart = remove(listNamespace, 0) + + " Is 'Ns1' an alias ? + if has_key(a:mapNamespaceAlias, szBeginPart) + " Resolving alias 'Ns1' + " eg: Ns1 = NsResolved + let szResult = a:mapNamespaceAlias[szBeginPart] + " szEndPart = 'Ns2::Ns3' + let szEndPart = join(listNamespace, '::') + if szEndPart != '' + " Concatenation => szResult = 'NsResolved::Ns2::Ns3' + let szResult .= '::' . szEndPart + endif + + " If a:szNamespace starts with '::' we add '::' to the beginning + " of the result + if match(a:szNamespace, '^::')>=0 + let szResult = omni#cpp#utils#SimplifyScope('::' . szResult) + endif + endif + endif + return szResult +endfunc + +" Resolve namespace alias +function! s:ResolveAliasInNamespaceList(mapNamespaceAlias, listNamespaces) + call map(a:listNamespaces, 'omni#cpp#namespaces#ResolveAlias(a:mapNamespaceAlias, v:val)') +endfunc + +" Get namespaces used at the cursor postion in a vim buffer +" Note: The result depends on the current cursor position +" @return +" - List of namespace used in the reverse order +function! omni#cpp#namespaces#GetUsingNamespaces() + " We have to get local using namespace declarations + " We need the current cursor position and the position of the start of the + " current scope + + " We store the cursor position because searchpairpos() moves the cursor + let result = [] + let originalPos = getpos('.') + let origPos = originalPos[1:2] + + let stopPos = s:GetStopPositionForLocalSearch() + + let stopLine = stopPos[0] + let curPos = origPos + let lastLine = 0 + let nextStopLine = origPos[0] + while curPos !=[0,0] + let curPos = searchpos('\C}\|\(using\s\+namespace\)', 'bW',stopLine) + if curPos!=[0,0] && curPos[0]!=lastLine + let lastLine = curPos[0] + + let szLine = getline('.') + if origPos[0] == curPos[0] + " We get the line until cursor position + let szLine = szLine[:origPos[1]] + endif + + let szLine = omni#cpp#utils#GetCodeFromLine(szLine) + if match(szLine, '\Cusing\s\+namespace')<0 + " We found a '}' + let curPos = searchpairpos('{', '', '}', 'bW', g:omni#cpp#utils#expIgnoreComments) + else + " We get the namespace list from the line + let result = s:GetNamespaceListFromLine(szLine) + result + let nextStopLine = curPos[0] + endif + endif + endwhile + + " Setting the cursor to the original position + call setpos('.', originalPos) + + " 2) Now we can get all global using namespace declaration from the + " beginning of the file to nextStopLine + let result = omni#cpp#namespaces#GetListFromCurrentBuffer(nextStopLine) + result + + " Resolving alias in the namespace list + " TODO: For the next release + "let g:omni#cpp#namespaces#CacheAlias= s:GetNamespaceAliasMap() + "call s:ResolveAliasInNamespaceList(g:omni#cpp#namespaces#CacheAlias, result) + + return ['::'] + result +endfunc + +" Resolve a using namespace regarding the current context +" For each namespace used: +" - We get all possible contexts where the namespace +" can be define +" - We do a comparison test of each parent contexts with the current +" context list +" - If one and only one parent context is present in the +" current context list we add the namespace in the current +" context +" - If there is more than one of parent contexts in the +" current context the namespace is ambiguous +" @return +" - result item +" - kind = 0|1 +" - 0 = unresolved or error +" - 1 = resolved +" - value = resolved namespace +function! s:ResolveNamespace(namespace, mapCurrentContexts) + let result = {'kind':0, 'value': ''} + + " If the namespace is already resolved we add it in the list of + " current contexts + if match(a:namespace, '^::')>=0 + let result.kind = 1 + let result.value = a:namespace + return result + elseif match(a:namespace, '\w\+::\w\+')>=0 + let mapCurrentContextsTmp = copy(a:mapCurrentContexts) + let resolvedItem = {} + for nsTmp in split(a:namespace, '::') + let resolvedItem = s:ResolveNamespace(nsTmp, mapCurrentContextsTmp) + if resolvedItem.kind + " Note: We don't extend the map + let mapCurrentContextsTmp = {resolvedItem.value : 1} + else + break + endif + endfor + if resolvedItem!={} && resolvedItem.kind + let result.kind = 1 + let result.value = resolvedItem.value + endif + return result + endif + + " We get all possible parent contexts of this namespace + let listTagsOfNamespace = [] + if has_key(g:omni#cpp#namespaces#CacheResolve, a:namespace) + let listTagsOfNamespace = g:omni#cpp#namespaces#CacheResolve[a:namespace] + else + let listTagsOfNamespace = omni#common#utils#TagList('^'.a:namespace.'$') + let g:omni#cpp#namespaces#CacheResolve[a:namespace] = listTagsOfNamespace + endif + + if len(listTagsOfNamespace)==0 + return result + endif + call filter(listTagsOfNamespace, 'v:val.kind[0]=="n"') + + " We extract parent context from tags + " We use a map to avoid multiple entries + let mapContext = {} + for tagItem in listTagsOfNamespace + let szParentContext = omni#cpp#utils#ExtractScope(tagItem) + let mapContext[szParentContext] = 1 + endfor + let listParentContext = keys(mapContext) + + " Now for each parent context we test if the context is in the current + " contexts list + let listResolvedNamespace = [] + for szParentContext in listParentContext + if has_key(a:mapCurrentContexts, szParentContext) + call extend(listResolvedNamespace, [omni#cpp#utils#SimplifyScope(szParentContext.'::'.a:namespace)]) + endif + endfor + + " Now we know if the namespace is ambiguous or not + let len = len(listResolvedNamespace) + if len==1 + " Namespace resolved + let result.kind = 1 + let result.value = listResolvedNamespace[0] + elseif len > 1 + " Ambiguous namespace, possible matches are in listResolvedNamespace + else + " Other cases + endif + return result +endfunc + +" Resolve namespaces +"@return +" - List of resolved namespaces +function! omni#cpp#namespaces#ResolveAll(namespacesUsed) + + " We add the default context '::' + let contextOrder = 0 + let mapCurrentContexts = {} + + " For each namespace used: + " - We get all possible contexts where the namespace + " can be define + " - We do a comparison test of each parent contexts with the current + " context list + " - If one and only one parent context is present in the + " current context list we add the namespace in the current + " context + " - If there is more than one of parent contexts in the + " current context the namespace is ambiguous + for ns in a:namespacesUsed + let resolvedItem = s:ResolveNamespace(ns, mapCurrentContexts) + if resolvedItem.kind + let contextOrder+=1 + let mapCurrentContexts[resolvedItem.value] = contextOrder + endif + endfor + + " Build the list of current contexts from the map, we have to keep the + " order + let mapReorder = {} + for key in keys(mapCurrentContexts) + let mapReorder[ mapCurrentContexts[key] ] = key + endfor + let result = [] + for key in sort(keys(mapReorder)) + call extend(result, [mapReorder[key]]) + endfor + return result +endfunc + +" Build the context stack +function! s:BuildContextStack(namespaces, szCurrentScope) + let result = copy(a:namespaces) + if a:szCurrentScope != '::' + let tagItem = omni#cpp#utils#GetResolvedTagItem(a:namespaces, omni#cpp#utils#CreateTypeInfo(a:szCurrentScope)) + if has_key(tagItem, 'inherits') + let listBaseClass = omni#cpp#utils#GetClassInheritanceList(a:namespaces, omni#cpp#utils#CreateTypeInfo(a:szCurrentScope)) + let result = listBaseClass + result + elseif has_key(tagItem, 'kind') && index(['c', 's', 'u', 'n'], tagItem.kind[0])>=0 + call insert(result, omni#cpp#utils#ExtractTypeInfoFromTag(tagItem)) + endif + endif + return result +endfunc + +" Returns the class scope at the current position of the cursor +" @return a string that represents the class scope +" eg: ::NameSpace1::Class1 +" The returned string always starts with '::' +" Note: In term of performance it's the weak point of the script +function! s:GetClassScopeAtCursor() + " We store the cursor position because searchpairpos() moves the cursor + let originalPos = getpos('.') + let endPos = originalPos[1:2] + let listCode = [] + let result = {'namespaces': [], 'scope': ''} + + while endPos!=[0,0] + let endPos = searchpairpos('{', '', '}', 'bW', g:omni#cpp#utils#expIgnoreComments) + let szReStartPos = '[;{}]\|\%^' + let startPos = searchpairpos(szReStartPos, '', '{', 'bWn', g:omni#cpp#utils#expIgnoreComments) + + " If the file starts with a comment so the startPos can be [0,0] + " we change it to [1,1] + if startPos==[0,0] + let startPos = [1,1] + endif + + " Get lines backward from cursor position to last ; or { or } + " or when we are at the beginning of the file. + " We store lines in listCode + if endPos!=[0,0] + " We remove the last character which is a '{' + " We also remove starting { or } or ; if exits + let szCodeWithoutComments = substitute(omni#cpp#utils#GetCode(startPos, endPos)[:-2], '^[;{}]', '', 'g') + call insert(listCode, {'startLine' : startPos[0], 'code' : szCodeWithoutComments}) + endif + endwhile + " Setting the cursor to the original position + call setpos('.', originalPos) + + let listClassScope = [] + let bResolved = 0 + let startLine = 0 + " Now we can check in the list of code if there is a function + for code in listCode + " We get the name of the namespace, class, struct or union + " and we store it in listClassScope + let tokens = omni#cpp#tokenizer#Tokenize(code.code) + let bContinue=0 + let bAddNamespace = 0 + let state=0 + for token in tokens + if state==0 + if index(['namespace', 'class', 'struct', 'union'], token.value)>=0 + if token.value == 'namespace' + let bAddNamespace = 1 + endif + let state= 1 + " Maybe end of tokens + endif + elseif state==1 + if token.kind == 'cppWord' + " eg: namespace MyNs { class MyCl {}; } + " => listClassScope = [MyNs, MyCl] + call extend( listClassScope , [token.value] ) + + " Add the namespace in result + if bAddNamespace + call extend(result.namespaces, [token.value]) + let bAddNamespace = 0 + endif + + let bContinue=1 + break + endif + endif + endfor + if bContinue==1 + continue + endif + + " Simple test to check if we have a chance to find a + " class method + let aPos = matchend(code.code, '::\s*\~*\s*\w\+\s*(') + if aPos ==-1 + continue + endif + + let startLine = code.startLine + let listTmp = [] + " eg: 'void MyNamespace::MyClass::foo(' + " => tokens = ['MyClass', '::', 'MyNamespace', 'void'] + let tokens = reverse(omni#cpp#tokenizer#Tokenize(code.code[:aPos-1])[:-4]) + let state = 0 + " Reading tokens backward + for token in tokens + if state==0 + if token.kind=='cppWord' + call insert(listTmp, token.value) + let state=1 + endif + elseif state==1 + if token.value=='::' + let state=2 + else + break + endif + elseif state==2 + if token.kind=='cppWord' + call insert(listTmp, token.value) + let state=1 + else + break + endif + endif + endfor + + if len(listTmp) + if len(listClassScope) + let bResolved = 1 + " Merging class scopes + " eg: current class scope = 'MyNs::MyCl1' + " method class scope = 'MyCl1::MyCl2' + " If we add the method class scope to current class scope + " we'll have MyNs::MyCl1::MyCl1::MyCl2 => it's wrong + " we want MyNs::MyCl1::MyCl2 + let index = 0 + for methodClassScope in listTmp + if methodClassScope==listClassScope[-1] + let listTmp = listTmp[index+1:] + break + else + let index+=1 + endif + endfor + endif + call extend(listClassScope, listTmp) + break + endif + endfor + + let szClassScope = '::' + if len(listClassScope) + if bResolved + let szClassScope .= join(listClassScope, '::') + else + let szClassScope = join(listClassScope, '::') + + " The class scope is not resolved, we have to check using + " namespace declarations and search the class scope in each + " namespace + if startLine != 0 + let namespaces = ['::'] + omni#cpp#namespaces#GetListFromCurrentBuffer(startLine) + let namespaces = omni#cpp#namespaces#ResolveAll(namespaces) + let tagItem = omni#cpp#utils#GetResolvedTagItem(namespaces, omni#cpp#utils#CreateTypeInfo(szClassScope)) + if tagItem != {} + let szClassScope = omni#cpp#utils#ExtractTypeInfoFromTag(tagItem) + endif + endif + endif + endif + + let result.scope = szClassScope + return result +endfunc + +" Get all contexts at the cursor position +function! omni#cpp#namespaces#GetContexts() + " Get the current class scope at the cursor, the result depends on the current cursor position + let scopeItem = s:GetClassScopeAtCursor() + let listUsingNamespace = copy(g:OmniCpp_DefaultNamespaces) + call extend(listUsingNamespace, scopeItem.namespaces) + if g:OmniCpp_NamespaceSearch && &filetype != 'c' + " Get namespaces used in the file until the cursor position + let listUsingNamespace = omni#cpp#namespaces#GetUsingNamespaces() + listUsingNamespace + " Resolving namespaces, removing ambiguous namespaces + let namespaces = omni#cpp#namespaces#ResolveAll(listUsingNamespace) + else + let namespaces = ['::'] + listUsingNamespace + endif + call reverse(namespaces) + + " Building context stack from namespaces and the current class scope + return s:BuildContextStack(namespaces, scopeItem.scope) +endfunc diff --git a/VimFiles/autoload/omni/cpp/settings.vim b/VimFiles/autoload/omni/cpp/settings.vim new file mode 100755 index 0000000..6683d3a --- /dev/null +++ b/VimFiles/autoload/omni/cpp/settings.vim @@ -0,0 +1,96 @@ +" Description: Omni completion script for cpp files +" Maintainer: Vissale NEANG +" Last Change: 26 sept. 2007 + +function! omni#cpp#settings#Init() + " Global scope search on/off + " 0 = disabled + " 1 = enabled + if !exists('g:OmniCpp_GlobalScopeSearch') + let g:OmniCpp_GlobalScopeSearch = 1 + endif + + " Sets the namespace search method + " 0 = disabled + " 1 = search namespaces in the current file + " 2 = search namespaces in the current file and included files + if !exists('g:OmniCpp_NamespaceSearch') + let g:OmniCpp_NamespaceSearch = 1 + endif + + " Set the class scope completion mode + " 0 = auto + " 1 = show all members (static, public, protected and private) + if !exists('g:OmniCpp_DisplayMode') + let g:OmniCpp_DisplayMode = 0 + endif + + " Set if the scope is displayed in the abbr column of the popup + " 0 = no + " 1 = yes + if !exists('g:OmniCpp_ShowScopeInAbbr') + let g:OmniCpp_ShowScopeInAbbr = 0 + endif + + " Set if the function prototype is displayed in the abbr column of the popup + " 0 = no + " 1 = yes + if !exists('g:OmniCpp_ShowPrototypeInAbbr') + let g:OmniCpp_ShowPrototypeInAbbr = 0 + endif + + " Set if the access (+,#,-) is displayed + " 0 = no + " 1 = yes + if !exists('g:OmniCpp_ShowAccess') + let g:OmniCpp_ShowAccess = 1 + endif + + " Set the list of default namespaces + " eg: ['std'] + if !exists('g:OmniCpp_DefaultNamespaces') + let g:OmniCpp_DefaultNamespaces = [] + endif + + " Set MayComplete to '.' + " 0 = disabled + " 1 = enabled + " default = 1 + if !exists('g:OmniCpp_MayCompleteDot') + let g:OmniCpp_MayCompleteDot = 1 + endif + + " Set MayComplete to '->' + " 0 = disabled + " 1 = enabled + " default = 1 + if !exists('g:OmniCpp_MayCompleteArrow') + let g:OmniCpp_MayCompleteArrow = 1 + endif + + " Set MayComplete to dot + " 0 = disabled + " 1 = enabled + " default = 0 + if !exists('g:OmniCpp_MayCompleteScope') + let g:OmniCpp_MayCompleteScope = 0 + endif + + " When completeopt does not contain longest option, this setting + " controls the behaviour of the popup menu selection when starting the completion + " 0 = don't select first item + " 1 = select first item (inserting it to the text) + " 2 = select first item (without inserting it to the text) + " default = 0 + if !exists('g:OmniCpp_SelectFirstItem') + let g:OmniCpp_SelectFirstItem= 0 + endif + + " Use local search function for variable definitions + " 0 = use standard vim search function + " 1 = use local search function + " default = 0 + if !exists('g:OmniCpp_LocalSearchDecl') + let g:OmniCpp_LocalSearchDecl= 0 + endif +endfunc diff --git a/VimFiles/autoload/omni/cpp/tokenizer.vim b/VimFiles/autoload/omni/cpp/tokenizer.vim new file mode 100755 index 0000000..16e0be2 --- /dev/null +++ b/VimFiles/autoload/omni/cpp/tokenizer.vim @@ -0,0 +1,93 @@ +" Description: Omni completion tokenizer +" Maintainer: Vissale NEANG +" Last Change: 26 sept. 2007 +" TODO: Generic behaviour for Tokenize() + +" From the C++ BNF +let s:cppKeyword = ['asm', 'auto', 'bool', 'break', 'case', 'catch', 'char', 'class', 'const', 'const_cast', 'continue', 'default', 'delete', 'do', 'double', 'dynamic_cast', 'else', 'enum', 'explicit', 'export', 'extern', 'false', 'float', 'for', 'friend', 'goto', 'if', 'inline', 'int', 'long', 'mutable', 'namespace', 'new', 'operator', 'private', 'protected', 'public', 'register', 'reinterpret_cast', 'return', 'short', 'signed', 'sizeof', 'static', 'static_cast', 'struct', 'switch', 'template', 'this', 'throw', 'true', 'try', 'typedef', 'typeid', 'typename', 'union', 'unsigned', 'using', 'virtual', 'void', 'volatile', 'wchar_t', 'while', 'and', 'and_eq', 'bitand', 'bitor', 'compl', 'not', 'not_eq', 'or', 'or_eq', 'xor', 'xor_eq'] + +let s:reCppKeyword = '\C\<'.join(s:cppKeyword, '\>\|\<').'\>' + +" The order of items in this list is very important because we use this list to build a regular +" expression (see below) for tokenization +let s:cppOperatorPunctuator = ['->*', '->', '--', '-=', '-', '!=', '!', '##', '#', '%:%:', '%=', '%>', '%:', '%', '&&', '&=', '&', '(', ')', '*=', '*', ',', '...', '.*', '.', '/=', '/', '::', ':>', ':', ';', '?', '[', ']', '^=', '^', '{', '||', '|=', '|', '}', '~', '++', '+=', '+', '<<=', '<%', '<:', '<<', '<=', '<', '==', '=', '>>=', '>>', '>=', '>'] + +" We build the regexp for the tokenizer +let s:reCComment = '\/\*\|\*\/' +let s:reCppComment = '\/\/' +let s:reComment = s:reCComment.'\|'.s:reCppComment +let s:reCppOperatorOrPunctuator = escape(join(s:cppOperatorPunctuator, '\|'), '*./^~[]') + + +" Tokenize a c++ code +" a token is dictionary where keys are: +" - kind = cppKeyword|cppWord|cppOperatorPunctuator|unknown|cComment|cppComment|cppDigit +" - value = 'something' +" Note: a cppWord is any word that is not a cpp keyword +function! omni#cpp#tokenizer#Tokenize(szCode) + let result = [] + + " The regexp to find a token, a token is a keyword, word or + " c++ operator or punctuator. To work properly we have to put + " spaces and tabs to our regexp. + let reTokenSearch = '\(\w\+\)\|\s\+\|'.s:reComment.'\|'.s:reCppOperatorOrPunctuator + " eg: 'using namespace std;' + " ^ ^ + " start=0 end=5 + let startPos = 0 + let endPos = matchend(a:szCode, reTokenSearch) + let len = endPos-startPos + while endPos!=-1 + " eg: 'using namespace std;' + " ^ ^ + " start=0 end=5 + " token = 'using' + " We also remove space and tabs + let token = substitute(strpart(a:szCode, startPos, len), '\s', '', 'g') + + " eg: 'using namespace std;' + " ^ ^ + " start=5 end=15 + let startPos = endPos + let endPos = matchend(a:szCode, reTokenSearch, startPos) + let len = endPos-startPos + + " It the token is empty we continue + if token=='' + continue + endif + + " Building the token + let resultToken = {'kind' : 'unknown', 'value' : token} + + " Classify the token + if token =~ '^\d\+' + " It's a digit + let resultToken.kind = 'cppDigit' + elseif token=~'^\w\+$' + " It's a word + let resultToken.kind = 'cppWord' + + " But maybe it's a c++ keyword + if match(token, s:reCppKeyword)>=0 + let resultToken.kind = 'cppKeyword' + endif + else + if match(token, s:reComment)>=0 + if index(['/*','*/'],token)>=0 + let resultToken.kind = 'cComment' + else + let resultToken.kind = 'cppComment' + endif + else + " It's an operator + let resultToken.kind = 'cppOperatorPunctuator' + endif + endif + + " We have our token, let's add it to the result list + call extend(result, [resultToken]) + endwhile + + return result +endfunc diff --git a/VimFiles/autoload/omni/cpp/utils.vim b/VimFiles/autoload/omni/cpp/utils.vim new file mode 100755 index 0000000..5d74d34 --- /dev/null +++ b/VimFiles/autoload/omni/cpp/utils.vim @@ -0,0 +1,587 @@ +" Description: Omni completion script for cpp files +" Maintainer: Vissale NEANG +" Last Change: 26 sept. 2007 + +let g:omni#cpp#utils#CACHE_TAG_INHERITS = {} +let g:omni#cpp#utils#szFilterGlobalScope = "(!has_key(v:val, 'class') && !has_key(v:val, 'struct') && !has_key(v:val, 'union') && !has_key(v:val, 'namespace')" +let g:omni#cpp#utils#szFilterGlobalScope .= "&& (!has_key(v:val, 'enum') || (has_key(v:val, 'enum') && v:val.enum =~ '^\\w\\+$')))" + +" Expression used to ignore comments +" Note: this expression drop drastically the performance +"let omni#cpp#utils#expIgnoreComments = 'match(synIDattr(synID(line("."), col("."), 1), "name"), '\CcComment')!=-1' +" This one is faster but not really good for C comments +let omni#cpp#utils#reIgnoreComment = escape('\/\/\|\/\*\|\*\/', '*/\') +let omni#cpp#utils#expIgnoreComments = 'getline(".") =~ g:omni#cpp#utils#reIgnoreComment' + +" Characters to escape in a filename for vimgrep +"TODO: Find more characters to escape +let omni#cpp#utils#szEscapedCharacters = ' %#' + +" Resolve the path of the file +" TODO: absolute file path +function! omni#cpp#utils#ResolveFilePath(szFile) + let result = '' + let listPath = split(globpath(&path, a:szFile), "\n") + if len(listPath) + let result = listPath[0] + endif + return simplify(result) +endfunc + +" Get code without comments and with empty strings +" szSingleLine must not have carriage return +function! omni#cpp#utils#GetCodeFromLine(szSingleLine) + " We set all strings to empty strings, it's safer for + " the next of the process + let szResult = substitute(a:szSingleLine, '".*"', '""', 'g') + + " Removing c++ comments, we can use the pattern ".*" because + " we are modifying a line + let szResult = substitute(szResult, '\/\/.*', '', 'g') + + " Now we have the entire code in one line and we can remove C comments + return s:RemoveCComments(szResult) +endfunc + +" Remove C comments on a line +function! s:RemoveCComments(szLine) + let result = a:szLine + + " We have to match the first '/*' and first '*/' + let startCmt = match(result, '\/\*') + let endCmt = match(result, '\*\/') + while startCmt!=-1 && endCmt!=-1 && startCmt0 + let result = result[ : startCmt-1 ] . result[ endCmt+2 : ] + else + " Case where '/*' is at the start of the line + let result = result[ endCmt+2 : ] + endif + let startCmt = match(result, '\/\*') + let endCmt = match(result, '\*\/') + endwhile + return result +endfunc + +" Get a c++ code from current buffer from [lineStart, colStart] to +" [lineEnd, colEnd] without c++ and c comments, without end of line +" and with empty strings if any +" @return a string +function! omni#cpp#utils#GetCode(posStart, posEnd) + let posStart = a:posStart + let posEnd = a:posEnd + if a:posStart[0]>a:posEnd[0] + let posStart = a:posEnd + let posEnd = a:posStart + elseif a:posStart[0]==a:posEnd[0] && a:posStart[1]>a:posEnd[1] + let posStart = a:posEnd + let posEnd = a:posStart + endif + + " Getting the lines + let lines = getline(posStart[0], posEnd[0]) + let lenLines = len(lines) + + " Formatting the result + let result = '' + if lenLines==1 + let sStart = posStart[1]-1 + let sEnd = posEnd[1]-1 + let line = lines[0] + let lenLastLine = strlen(line) + let sEnd = (sEnd>lenLastLine)?lenLastLine : sEnd + if sStart >= 0 + let result = omni#cpp#utils#GetCodeFromLine(line[ sStart : sEnd ]) + endif + elseif lenLines>1 + let sStart = posStart[1]-1 + let sEnd = posEnd[1]-1 + let lenLastLine = strlen(lines[-1]) + let sEnd = (sEnd>lenLastLine)?lenLastLine : sEnd + if sStart >= 0 + let lines[0] = lines[0][ sStart : ] + let lines[-1] = lines[-1][ : sEnd ] + for aLine in lines + let result = result . omni#cpp#utils#GetCodeFromLine(aLine)." " + endfor + let result = result[:-2] + endif + endif + + " Now we have the entire code in one line and we can remove C comments + return s:RemoveCComments(result) +endfunc + +" Extract the scope (context) of a tag item +" eg: ::MyNamespace +" @return a string of the scope. a scope from tag always starts with '::' +function! omni#cpp#utils#ExtractScope(tagItem) + let listKindScope = ['class', 'struct', 'union', 'namespace', 'enum'] + let szResult = '::' + for scope in listKindScope + if has_key(a:tagItem, scope) + let szResult = szResult . a:tagItem[scope] + break + endif + endfor + return szResult +endfunc + +" Simplify scope string, remove consecutive '::' if any +function! omni#cpp#utils#SimplifyScope(szScope) + let szResult = substitute(a:szScope, '\(::\)\+', '::', 'g') + if szResult=='::' + return szResult + else + return substitute(szResult, '::$', '', 'g') + endif +endfunc + +" Check if the cursor is in comment +function! omni#cpp#utils#IsCursorInCommentOrString() + return match(synIDattr(synID(line("."), col(".")-1, 1), "name"), '\C\=0 +endfunc + +" Tokenize the current instruction until the cursor position. +" @return list of tokens +function! omni#cpp#utils#TokenizeCurrentInstruction(...) + let szAppendText = '' + if a:0>0 + let szAppendText = a:1 + endif + + let startPos = searchpos('[;{}]\|\%^', 'bWn') + let curPos = getpos('.')[1:2] + " We don't want the character under the cursor + let column = curPos[1]-1 + let curPos[1] = (column<1)?1:column + return omni#cpp#tokenizer#Tokenize(omni#cpp#utils#GetCode(startPos, curPos)[1:] . szAppendText) +endfunc + +" Tokenize the current instruction until the word under the cursor. +" @return list of tokens +function! omni#cpp#utils#TokenizeCurrentInstructionUntilWord() + let startPos = searchpos('[;{}]\|\%^', 'bWn') + + " Saving the current cursor pos + let originalPos = getpos('.') + + " We go at the end of the word + execute 'normal gee' + let curPos = getpos('.')[1:2] + + " Restoring the original cursor pos + call setpos('.', originalPos) + + let szCode = omni#cpp#utils#GetCode(startPos, curPos)[1:] + return omni#cpp#tokenizer#Tokenize(szCode) +endfunc + +" Build parenthesis groups +" add a new key 'group' in the token +" where value is the group number of the parenthesis +" eg: (void*)(MyClass*) +" group1 group0 +" if a parenthesis is unresolved the group id is -1 +" @return a copy of a:tokens with parenthesis group +function! omni#cpp#utils#BuildParenthesisGroups(tokens) + let tokens = copy(a:tokens) + let kinds = {'(': '()', ')' : '()', '[' : '[]', ']' : '[]', '<' : '<>', '>' : '<>', '{': '{}', '}': '{}'} + let unresolved = {'()' : [], '[]': [], '<>' : [], '{}' : []} + let groupId = 0 + + " Note: we build paren group in a backward way + " because we can often have parenthesis unbalanced + " instruction + " eg: doSomething(_member.get()-> + for token in reverse(tokens) + if index([')', ']', '>', '}'], token.value)>=0 + let token['group'] = groupId + call extend(unresolved[kinds[token.value]], [token]) + let groupId+=1 + elseif index(['(', '[', '<', '{'], token.value)>=0 + if len(unresolved[kinds[token.value]]) + let tokenResolved = remove(unresolved[kinds[token.value]], -1) + let token['group'] = tokenResolved.group + else + let token['group'] = -1 + endif + endif + endfor + + return reverse(tokens) +endfunc + +" Determine if tokens represent a C cast +" @return +" - itemCast +" - itemCppCast +" - itemVariable +" - itemThis +function! omni#cpp#utils#GetCastType(tokens) + " Note: a:tokens is not modified + let tokens = omni#cpp#utils#SimplifyParenthesis(omni#cpp#utils#BuildParenthesisGroups(a:tokens)) + + if tokens[0].value == '(' + return 'itemCast' + elseif index(['static_cast', 'dynamic_cast', 'reinterpret_cast', 'const_cast'], tokens[0].value)>=0 + return 'itemCppCast' + else + for token in tokens + if token.value=='this' + return 'itemThis' + endif + endfor + return 'itemVariable' + endif +endfunc + +" Remove useless parenthesis +function! omni#cpp#utils#SimplifyParenthesis(tokens) + "Note: a:tokens is not modified + let tokens = a:tokens + " We remove useless parenthesis eg: (((MyClass))) + if len(tokens)>2 + while tokens[0].value=='(' && tokens[-1].value==')' && tokens[0].group==tokens[-1].group + let tokens = tokens[1:-2] + endwhile + endif + return tokens +endfunc + +" Function create a type info +function! omni#cpp#utils#CreateTypeInfo(param) + let type = type(a:param) + return {'type': type, 'value':a:param} +endfunc + +" Extract type info from a tag item +" eg: ::MyNamespace::MyClass +function! omni#cpp#utils#ExtractTypeInfoFromTag(tagItem) + let szTypeInfo = omni#cpp#utils#ExtractScope(a:tagItem) . '::' . substitute(a:tagItem.name, '.*::', '', 'g') + return omni#cpp#utils#SimplifyScope(szTypeInfo) +endfunc + +" Build a class inheritance list +function! omni#cpp#utils#GetClassInheritanceList(namespaces, typeInfo) + let result = [] + for tagItem in omni#cpp#utils#GetResolvedTags(a:namespaces, a:typeInfo) + call extend(result, [omni#cpp#utils#ExtractTypeInfoFromTag(tagItem)]) + endfor + return result +endfunc + +" Get class inheritance list where items in the list are tag items. +" TODO: Verify inheritance order +function! omni#cpp#utils#GetResolvedTags(namespaces, typeInfo) + let result = [] + let tagItem = omni#cpp#utils#GetResolvedTagItem(a:namespaces, a:typeInfo) + if tagItem!={} + let szTypeInfo = omni#cpp#utils#ExtractTypeInfoFromTag(tagItem) + if has_key(g:omni#cpp#utils#CACHE_TAG_INHERITS, szTypeInfo) + let result = g:omni#cpp#utils#CACHE_TAG_INHERITS[szTypeInfo] + else + call extend(result, [tagItem]) + if has_key(tagItem, 'inherits') + for baseClassTypeInfo in split(tagItem.inherits, ',') + let namespaces = [omni#cpp#utils#ExtractScope(tagItem), '::'] + call extend(result, omni#cpp#utils#GetResolvedTags(namespaces, omni#cpp#utils#CreateTypeInfo(baseClassTypeInfo))) + endfor + endif + let g:omni#cpp#utils#CACHE_TAG_INHERITS[szTypeInfo] = result + endif + endif + return result +endfunc + +" Get a tag item after a scope resolution and typedef resolution +function! omni#cpp#utils#GetResolvedTagItem(namespaces, typeInfo) + let typeInfo = {} + if type(a:typeInfo) == 1 + let typeInfo = omni#cpp#utils#CreateTypeInfo(a:typeInfo) + else + let typeInfo = a:typeInfo + endif + + let result = {} + if !omni#cpp#utils#IsTypeInfoValid(typeInfo) + return result + endif + + " Unnamed type case eg: '1::2' + if typeInfo.type == 4 + " Here there is no typedef or namespace to resolve, the tagInfo.value is a tag item + " representing a variable ('v') a member ('m') or a typedef ('t') and the typename is + " always in global scope + return typeInfo.value + endif + + " Named type case eg: 'MyNamespace::MyClass' + let szTypeInfo = omni#cpp#utils#GetTypeInfoString(typeInfo) + + " Resolving namespace alias + " TODO: For the next release + "let szTypeInfo = omni#cpp#namespaces#ResolveAlias(g:omni#cpp#namespaces#CacheAlias, szTypeInfo) + + if szTypeInfo=='::' + return result + endif + + " We can only get members of class, struct, union and namespace + let szTagFilter = "index(['c', 's', 'u', 'n', 't'], v:val.kind[0])>=0" + let szTagQuery = szTypeInfo + + if s:IsTypeInfoResolved(szTypeInfo) + " The type info is already resolved, we remove the starting '::' + let szTagQuery = substitute(szTypeInfo, '^::', '', 'g') + if len(split(szTagQuery, '::'))==1 + " eg: ::MyClass + " Here we have to get tags that have no parent scope + " That's why we change the szTagFilter + let szTagFilter .= '&& ' . g:omni#cpp#utils#szFilterGlobalScope + let tagList = omni#common#utils#TagListNoThrow('^'.szTagQuery.'$') + call filter(tagList, szTagFilter) + if len(tagList) + let result = tagList[0] + endif + else + " eg: ::MyNamespace::MyClass + let tagList = omni#common#utils#TagListNoThrow('^'.szTagQuery.'$') + call filter(tagList, szTagFilter) + + if len(tagList) + let result = tagList[0] + endif + endif + else + " The type is not resolved + let tagList = omni#common#utils#TagListNoThrow('^'.szTagQuery.'$') + call filter(tagList, szTagFilter) + + if len(tagList) + " Resolving scope (namespace, nested class etc...) + let szScopeOfTypeInfo = s:ExtractScopeFromTypeInfo(szTypeInfo) + if s:IsTypeInfoResolved(szTypeInfo) + let result = s:GetTagOfSameScope(tagList, szScopeOfTypeInfo) + else + " For each namespace of the namespace list we try to get a tag + " that can be in the same scope + if g:OmniCpp_NamespaceSearch && &filetype != 'c' + for scope in a:namespaces + let szTmpScope = omni#cpp#utils#SimplifyScope(scope.'::'.szScopeOfTypeInfo) + let result = s:GetTagOfSameScope(tagList, szTmpScope) + if result!={} + break + endif + endfor + else + let szTmpScope = omni#cpp#utils#SimplifyScope('::'.szScopeOfTypeInfo) + let result = s:GetTagOfSameScope(tagList, szTmpScope) + endif + endif + endif + endif + + if result!={} + " We have our tagItem but maybe it's a typedef or an unnamed type + if result.kind[0]=='t' + " Here we can have a typedef to another typedef, a class, struct, union etc + " but we can also have a typedef to an unnamed type, in that + " case the result contains a 'typeref' key + let namespaces = [omni#cpp#utils#ExtractScope(result), '::'] + if has_key(result, 'typeref') + let result = omni#cpp#utils#GetResolvedTagItem(namespaces, omni#cpp#utils#CreateTypeInfo(result)) + else + let szCmd = omni#cpp#utils#ExtractCmdFromTagItem(result) + let szCode = substitute(omni#cpp#utils#GetCodeFromLine(szCmd), '\C\<'.result.name.'\>.*', '', 'g') + let szTypeInfo = omni#cpp#utils#ExtractTypeInfoFromTokens(omni#cpp#tokenizer#Tokenize(szCode)) + let result = omni#cpp#utils#GetResolvedTagItem(namespaces, omni#cpp#utils#CreateTypeInfo(szTypeInfo)) + " TODO: Namespace resolution for result + endif + endif + endif + + return result +endfunc + +" Returns if the type info is valid +" @return +" - 1 if valid +" - 0 otherwise +function! omni#cpp#utils#IsTypeInfoValid(typeInfo) + if a:typeInfo=={} + return 0 + else + if a:typeInfo.type == 1 && a:typeInfo.value=='' + " String case + return 0 + elseif a:typeInfo.type == 4 && a:typeInfo.value=={} + " Dictionary case + return 0 + endif + endif + return 1 +endfunc + +" Get the string of the type info +function! omni#cpp#utils#GetTypeInfoString(typeInfo) + if a:typeInfo.type == 1 + return a:typeInfo.value + else + return substitute(a:typeInfo.value.typeref, '^\w\+:', '', 'g') + endif +endfunc + +" A resolved type info starts with '::' +" @return +" - 1 if type info starts with '::' +" - 0 otherwise +function! s:IsTypeInfoResolved(szTypeInfo) + return match(a:szTypeInfo, '^::')!=-1 +endfunc + +" A returned type info's scope may not have the global namespace '::' +" eg: '::NameSpace1::NameSpace2::MyClass' => '::NameSpace1::NameSpace2' +" 'NameSpace1::NameSpace2::MyClass' => 'NameSpace1::NameSpace2' +function! s:ExtractScopeFromTypeInfo(szTypeInfo) + let szScope = substitute(a:szTypeInfo, '\w\+$', '', 'g') + if szScope =='::' + return szScope + else + return substitute(szScope, '::$', '', 'g') + endif +endfunc + +" @return +" - the tag with the same scope +" - {} otherwise +function! s:GetTagOfSameScope(listTags, szScopeToMatch) + for tagItem in a:listTags + let szScopeOfTag = omni#cpp#utils#ExtractScope(tagItem) + if szScopeOfTag == a:szScopeToMatch + return tagItem + endif + endfor + return {} +endfunc + +" Extract the cmd of a tag item without regexp +function! omni#cpp#utils#ExtractCmdFromTagItem(tagItem) + let line = a:tagItem.cmd + let re = '\(\/\^\)\|\(\$\/\)' + if match(line, re)!=-1 + let line = substitute(line, re, '', 'g') + return line + else + " TODO: the cmd is a line number + return '' + endif +endfunc + +" Extract type from tokens. +" eg: examples of tokens format +" 'const MyClass&' +" 'const map < int, int >&' +" 'MyNs::MyClass' +" '::MyClass**' +" 'MyClass a, *b = NULL, c[1] = {}; +" 'hello(MyClass a, MyClass* b' +" @return the type info string eg: ::std::map +" can be empty +function! omni#cpp#utils#ExtractTypeInfoFromTokens(tokens) + let szResult = '' + let state = 0 + + let tokens = omni#cpp#utils#BuildParenthesisGroups(a:tokens) + + " If there is an unbalanced parenthesis we are in a parameter list + let bParameterList = 0 + for token in tokens + if token.value == '(' && token.group==-1 + let bParameterList = 1 + break + endif + endfor + + if bParameterList + let tokens = reverse(tokens) + let state = 0 + let parenGroup = -1 + for token in tokens + if state==0 + if token.value=='>' + let parenGroup = token.group + let state=1 + elseif token.kind == 'cppWord' + let szResult = token.value.szResult + let state=2 + elseif index(['*', '&'], token.value)<0 + break + endif + elseif state==1 + if token.value=='<' && token.group==parenGroup + let state=0 + endif + elseif state==2 + if token.value=='::' + let szResult = token.value.szResult + let state=3 + else + break + endif + elseif state==3 + if token.kind == 'cppWord' + let szResult = token.value.szResult + let state=2 + else + break + endif + endif + endfor + return szResult + endif + + for token in tokens + if state==0 + if token.value == '::' + let szResult .= token.value + let state = 1 + elseif token.kind == 'cppWord' + let szResult .= token.value + let state = 2 + " Maybe end of token + endif + elseif state==1 + if token.kind == 'cppWord' + let szResult .= token.value + let state = 2 + " Maybe end of token + else + break + endif + elseif state==2 + if token.value == '::' + let szResult .= token.value + let state = 1 + else + break + endif + endif + endfor + return szResult +endfunc + +" Get the preview window string +function! omni#cpp#utils#GetPreviewWindowStringFromTagItem(tagItem) + let szResult = '' + + let szResult .= 'name: '.a:tagItem.name."\n" + for tagKey in keys(a:tagItem) + if index(['name', 'static'], tagKey)>=0 + continue + endif + let szResult .= tagKey.': '.a:tagItem[tagKey]."\n" + endfor + + return substitute(szResult, "\n$", '', 'g') +endfunc diff --git a/VimFiles/autoload/plug.vim b/VimFiles/autoload/plug.vim new file mode 100644 index 0000000..9ebcf53 --- /dev/null +++ b/VimFiles/autoload/plug.vim @@ -0,0 +1,2504 @@ +" vim-plug: Vim plugin manager +" ============================ +" +" Download plug.vim and put it in ~/.vim/autoload +" +" curl -fLo ~/.vim/autoload/plug.vim --create-dirs \ +" https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim +" +" Edit your .vimrc +" +" call plug#begin('~/.vim/plugged') +" +" " Make sure you use single quotes +" +" " Shorthand notation; fetches https://github.com/junegunn/vim-easy-align +" Plug 'junegunn/vim-easy-align' +" +" " Any valid git URL is allowed +" Plug 'https://github.com/junegunn/vim-github-dashboard.git' +" +" " Multiple Plug commands can be written in a single line using | separators +" Plug 'SirVer/ultisnips' | Plug 'honza/vim-snippets' +" +" " On-demand loading +" Plug 'scrooloose/nerdtree', { 'on': 'NERDTreeToggle' } +" Plug 'tpope/vim-fireplace', { 'for': 'clojure' } +" +" " Using a non-master branch +" Plug 'rdnetto/YCM-Generator', { 'branch': 'stable' } +" +" " Using a tagged release; wildcard allowed (requires git 1.9.2 or above) +" Plug 'fatih/vim-go', { 'tag': '*' } +" +" " Plugin options +" Plug 'nsf/gocode', { 'tag': 'v.20150303', 'rtp': 'vim' } +" +" " Plugin outside ~/.vim/plugged with post-update hook +" Plug 'junegunn/fzf', { 'dir': '~/.fzf', 'do': './install --all' } +" +" " Unmanaged plugin (manually installed and updated) +" Plug '~/my-prototype-plugin' +" +" " Initialize plugin system +" call plug#end() +" +" Then reload .vimrc and :PlugInstall to install plugins. +" +" Plug options: +" +"| Option | Description | +"| ----------------------- | ------------------------------------------------ | +"| `branch`/`tag`/`commit` | Branch/tag/commit of the repository to use | +"| `rtp` | Subdirectory that contains Vim plugin | +"| `dir` | Custom directory for the plugin | +"| `as` | Use different name for the plugin | +"| `do` | Post-update hook (string or funcref) | +"| `on` | On-demand loading: Commands or ``-mappings | +"| `for` | On-demand loading: File types | +"| `frozen` | Do not update unless explicitly specified | +" +" More information: https://github.com/junegunn/vim-plug +" +" +" Copyright (c) 2017 Junegunn Choi +" +" MIT License +" +" Permission is hereby granted, free of charge, to any person obtaining +" a copy of this software and associated documentation files (the +" "Software"), to deal in the Software without restriction, including +" without limitation the rights to use, copy, modify, merge, publish, +" distribute, sublicense, and/or sell copies of the Software, and to +" permit persons to whom the Software is furnished to do so, subject to +" the following conditions: +" +" The above copyright notice and this permission notice shall be +" included in all copies or substantial portions of the Software. +" +" THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +" EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +" MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +" NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +" LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +" OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +" WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +if exists('g:loaded_plug') + finish +endif +let g:loaded_plug = 1 + +let s:cpo_save = &cpo +set cpo&vim + +let s:plug_src = 'https://github.com/junegunn/vim-plug.git' +let s:plug_tab = get(s:, 'plug_tab', -1) +let s:plug_buf = get(s:, 'plug_buf', -1) +let s:mac_gui = has('gui_macvim') && has('gui_running') +let s:is_win = has('win32') || has('win64') +let s:nvim = has('nvim-0.2') || (has('nvim') && exists('*jobwait') && !s:is_win) +let s:vim8 = has('patch-8.0.0039') && exists('*job_start') +let s:me = resolve(expand(':p')) +let s:base_spec = { 'branch': 'master', 'frozen': 0 } +let s:TYPE = { +\ 'string': type(''), +\ 'list': type([]), +\ 'dict': type({}), +\ 'funcref': type(function('call')) +\ } +let s:loaded = get(s:, 'loaded', {}) +let s:triggers = get(s:, 'triggers', {}) + +function! plug#begin(...) + if a:0 > 0 + let s:plug_home_org = a:1 + let home = s:path(fnamemodify(expand(a:1), ':p')) + elseif exists('g:plug_home') + let home = s:path(g:plug_home) + elseif !empty(&rtp) + let home = s:path(split(&rtp, ',')[0]) . '/plugged' + else + return s:err('Unable to determine plug home. Try calling plug#begin() with a path argument.') + endif + if fnamemodify(home, ':t') ==# 'plugin' && fnamemodify(home, ':h') ==# s:first_rtp + return s:err('Invalid plug home. '.home.' is a standard Vim runtime path and is not allowed.') + endif + + let g:plug_home = home + let g:plugs = {} + let g:plugs_order = [] + let s:triggers = {} + + call s:define_commands() + return 1 +endfunction + +function! s:define_commands() + command! -nargs=+ -bar Plug call plug#() + if !executable('git') + return s:err('`git` executable not found. Most commands will not be available. To suppress this message, prepend `silent!` to `call plug#begin(...)`.') + endif + command! -nargs=* -bar -bang -complete=customlist,s:names PlugInstall call s:install(0, []) + command! -nargs=* -bar -bang -complete=customlist,s:names PlugUpdate call s:update(0, []) + command! -nargs=0 -bar -bang PlugClean call s:clean(0) + command! -nargs=0 -bar PlugUpgrade if s:upgrade() | execute 'source' s:esc(s:me) | endif + command! -nargs=0 -bar PlugStatus call s:status() + command! -nargs=0 -bar PlugDiff call s:diff() + command! -nargs=? -bar -bang -complete=file PlugSnapshot call s:snapshot(0, ) +endfunction + +function! s:to_a(v) + return type(a:v) == s:TYPE.list ? a:v : [a:v] +endfunction + +function! s:to_s(v) + return type(a:v) == s:TYPE.string ? a:v : join(a:v, "\n") . "\n" +endfunction + +function! s:glob(from, pattern) + return s:lines(globpath(a:from, a:pattern)) +endfunction + +function! s:source(from, ...) + let found = 0 + for pattern in a:000 + for vim in s:glob(a:from, pattern) + execute 'source' s:esc(vim) + let found = 1 + endfor + endfor + return found +endfunction + +function! s:assoc(dict, key, val) + let a:dict[a:key] = add(get(a:dict, a:key, []), a:val) +endfunction + +function! s:ask(message, ...) + call inputsave() + echohl WarningMsg + let answer = input(a:message.(a:0 ? ' (y/N/a) ' : ' (y/N) ')) + echohl None + call inputrestore() + echo "\r" + return (a:0 && answer =~? '^a') ? 2 : (answer =~? '^y') ? 1 : 0 +endfunction + +function! s:ask_no_interrupt(...) + try + return call('s:ask', a:000) + catch + return 0 + endtry +endfunction + +function! plug#end() + if !exists('g:plugs') + return s:err('Call plug#begin() first') + endif + + if exists('#PlugLOD') + augroup PlugLOD + autocmd! + augroup END + augroup! PlugLOD + endif + let lod = { 'ft': {}, 'map': {}, 'cmd': {} } + + if exists('g:did_load_filetypes') + filetype off + endif + for name in g:plugs_order + if !has_key(g:plugs, name) + continue + endif + let plug = g:plugs[name] + if get(s:loaded, name, 0) || !has_key(plug, 'on') && !has_key(plug, 'for') + let s:loaded[name] = 1 + continue + endif + + if has_key(plug, 'on') + let s:triggers[name] = { 'map': [], 'cmd': [] } + for cmd in s:to_a(plug.on) + if cmd =~? '^.\+' + if empty(mapcheck(cmd)) && empty(mapcheck(cmd, 'i')) + call s:assoc(lod.map, cmd, name) + endif + call add(s:triggers[name].map, cmd) + elseif cmd =~# '^[A-Z]' + let cmd = substitute(cmd, '!*$', '', '') + if exists(':'.cmd) != 2 + call s:assoc(lod.cmd, cmd, name) + endif + call add(s:triggers[name].cmd, cmd) + else + call s:err('Invalid `on` option: '.cmd. + \ '. Should start with an uppercase letter or ``.') + endif + endfor + endif + + if has_key(plug, 'for') + let types = s:to_a(plug.for) + if !empty(types) + augroup filetypedetect + call s:source(s:rtp(plug), 'ftdetect/**/*.vim', 'after/ftdetect/**/*.vim') + augroup END + endif + for type in types + call s:assoc(lod.ft, type, name) + endfor + endif + endfor + + for [cmd, names] in items(lod.cmd) + execute printf( + \ 'command! -nargs=* -range -bang -complete=file %s call s:lod_cmd(%s, "", , , , %s)', + \ cmd, string(cmd), string(names)) + endfor + + for [map, names] in items(lod.map) + for [mode, map_prefix, key_prefix] in + \ [['i', '', ''], ['n', '', ''], ['v', '', 'gv'], ['o', '', '']] + execute printf( + \ '%snoremap %s %s:call lod_map(%s, %s, %s, "%s")', + \ mode, map, map_prefix, string(map), string(names), mode != 'i', key_prefix) + endfor + endfor + + for [ft, names] in items(lod.ft) + augroup PlugLOD + execute printf('autocmd FileType %s call lod_ft(%s, %s)', + \ ft, string(ft), string(names)) + augroup END + endfor + + call s:reorg_rtp() + filetype plugin indent on + if has('vim_starting') + if has('syntax') && !exists('g:syntax_on') + syntax enable + end + else + call s:reload_plugins() + endif +endfunction + +function! s:loaded_names() + return filter(copy(g:plugs_order), 'get(s:loaded, v:val, 0)') +endfunction + +function! s:load_plugin(spec) + call s:source(s:rtp(a:spec), 'plugin/**/*.vim', 'after/plugin/**/*.vim') +endfunction + +function! s:reload_plugins() + for name in s:loaded_names() + call s:load_plugin(g:plugs[name]) + endfor +endfunction + +function! s:trim(str) + return substitute(a:str, '[\/]\+$', '', '') +endfunction + +function! s:version_requirement(val, min) + for idx in range(0, len(a:min) - 1) + let v = get(a:val, idx, 0) + if v < a:min[idx] | return 0 + elseif v > a:min[idx] | return 1 + endif + endfor + return 1 +endfunction + +function! s:git_version_requirement(...) + if !exists('s:git_version') + let s:git_version = map(split(split(s:system('git --version'))[2], '\.'), 'str2nr(v:val)') + endif + return s:version_requirement(s:git_version, a:000) +endfunction + +function! s:progress_opt(base) + return a:base && !s:is_win && + \ s:git_version_requirement(1, 7, 1) ? '--progress' : '' +endfunction + +if s:is_win + function! s:rtp(spec) + return s:path(a:spec.dir . get(a:spec, 'rtp', '')) + endfunction + + function! s:path(path) + return s:trim(substitute(a:path, '/', '\', 'g')) + endfunction + + function! s:dirpath(path) + return s:path(a:path) . '\' + endfunction + + function! s:is_local_plug(repo) + return a:repo =~? '^[a-z]:\|^[%~]' + endfunction +else + function! s:rtp(spec) + return s:dirpath(a:spec.dir . get(a:spec, 'rtp', '')) + endfunction + + function! s:path(path) + return s:trim(a:path) + endfunction + + function! s:dirpath(path) + return substitute(a:path, '[/\\]*$', '/', '') + endfunction + + function! s:is_local_plug(repo) + return a:repo[0] =~ '[/$~]' + endfunction +endif + +function! s:err(msg) + echohl ErrorMsg + echom '[vim-plug] '.a:msg + echohl None +endfunction + +function! s:warn(cmd, msg) + echohl WarningMsg + execute a:cmd 'a:msg' + echohl None +endfunction + +function! s:esc(path) + return escape(a:path, ' ') +endfunction + +function! s:escrtp(path) + return escape(a:path, ' ,') +endfunction + +function! s:remove_rtp() + for name in s:loaded_names() + let rtp = s:rtp(g:plugs[name]) + execute 'set rtp-='.s:escrtp(rtp) + let after = globpath(rtp, 'after') + if isdirectory(after) + execute 'set rtp-='.s:escrtp(after) + endif + endfor +endfunction + +function! s:reorg_rtp() + if !empty(s:first_rtp) + execute 'set rtp-='.s:first_rtp + execute 'set rtp-='.s:last_rtp + endif + + " &rtp is modified from outside + if exists('s:prtp') && s:prtp !=# &rtp + call s:remove_rtp() + unlet! s:middle + endif + + let s:middle = get(s:, 'middle', &rtp) + let rtps = map(s:loaded_names(), 's:rtp(g:plugs[v:val])') + let afters = filter(map(copy(rtps), 'globpath(v:val, "after")'), '!empty(v:val)') + let rtp = join(map(rtps, 'escape(v:val, ",")'), ',') + \ . ','.s:middle.',' + \ . join(map(afters, 'escape(v:val, ",")'), ',') + let &rtp = substitute(substitute(rtp, ',,*', ',', 'g'), '^,\|,$', '', 'g') + let s:prtp = &rtp + + if !empty(s:first_rtp) + execute 'set rtp^='.s:first_rtp + execute 'set rtp+='.s:last_rtp + endif +endfunction + +function! s:doautocmd(...) + if exists('#'.join(a:000, '#')) + execute 'doautocmd' ((v:version > 703 || has('patch442')) ? '' : '') join(a:000) + endif +endfunction + +function! s:dobufread(names) + for name in a:names + let path = s:rtp(g:plugs[name]).'/**' + for dir in ['ftdetect', 'ftplugin'] + if len(finddir(dir, path)) + if exists('#BufRead') + doautocmd BufRead + endif + return + endif + endfor + endfor +endfunction + +function! plug#load(...) + if a:0 == 0 + return s:err('Argument missing: plugin name(s) required') + endif + if !exists('g:plugs') + return s:err('plug#begin was not called') + endif + let names = a:0 == 1 && type(a:1) == s:TYPE.list ? a:1 : a:000 + let unknowns = filter(copy(names), '!has_key(g:plugs, v:val)') + if !empty(unknowns) + let s = len(unknowns) > 1 ? 's' : '' + return s:err(printf('Unknown plugin%s: %s', s, join(unknowns, ', '))) + end + let unloaded = filter(copy(names), '!get(s:loaded, v:val, 0)') + if !empty(unloaded) + for name in unloaded + call s:lod([name], ['ftdetect', 'after/ftdetect', 'plugin', 'after/plugin']) + endfor + call s:dobufread(unloaded) + return 1 + end + return 0 +endfunction + +function! s:remove_triggers(name) + if !has_key(s:triggers, a:name) + return + endif + for cmd in s:triggers[a:name].cmd + execute 'silent! delc' cmd + endfor + for map in s:triggers[a:name].map + execute 'silent! unmap' map + execute 'silent! iunmap' map + endfor + call remove(s:triggers, a:name) +endfunction + +function! s:lod(names, types, ...) + for name in a:names + call s:remove_triggers(name) + let s:loaded[name] = 1 + endfor + call s:reorg_rtp() + + for name in a:names + let rtp = s:rtp(g:plugs[name]) + for dir in a:types + call s:source(rtp, dir.'/**/*.vim') + endfor + if a:0 + if !s:source(rtp, a:1) && !empty(s:glob(rtp, a:2)) + execute 'runtime' a:1 + endif + call s:source(rtp, a:2) + endif + call s:doautocmd('User', name) + endfor +endfunction + +function! s:lod_ft(pat, names) + let syn = 'syntax/'.a:pat.'.vim' + call s:lod(a:names, ['plugin', 'after/plugin'], syn, 'after/'.syn) + execute 'autocmd! PlugLOD FileType' a:pat + call s:doautocmd('filetypeplugin', 'FileType') + call s:doautocmd('filetypeindent', 'FileType') +endfunction + +function! s:lod_cmd(cmd, bang, l1, l2, args, names) + call s:lod(a:names, ['ftdetect', 'after/ftdetect', 'plugin', 'after/plugin']) + call s:dobufread(a:names) + execute printf('%s%s%s %s', (a:l1 == a:l2 ? '' : (a:l1.','.a:l2)), a:cmd, a:bang, a:args) +endfunction + +function! s:lod_map(map, names, with_prefix, prefix) + call s:lod(a:names, ['ftdetect', 'after/ftdetect', 'plugin', 'after/plugin']) + call s:dobufread(a:names) + let extra = '' + while 1 + let c = getchar(0) + if c == 0 + break + endif + let extra .= nr2char(c) + endwhile + + if a:with_prefix + let prefix = v:count ? v:count : '' + let prefix .= '"'.v:register.a:prefix + if mode(1) == 'no' + if v:operator == 'c' + let prefix = "\" . prefix + endif + let prefix .= v:operator + endif + call feedkeys(prefix, 'n') + endif + call feedkeys(substitute(a:map, '^', "\", '') . extra) +endfunction + +function! plug#(repo, ...) + if a:0 > 1 + return s:err('Invalid number of arguments (1..2)') + endif + + try + let repo = s:trim(a:repo) + let opts = a:0 == 1 ? s:parse_options(a:1) : s:base_spec + let name = get(opts, 'as', fnamemodify(repo, ':t:s?\.git$??')) + let spec = extend(s:infer_properties(name, repo), opts) + if !has_key(g:plugs, name) + call add(g:plugs_order, name) + endif + let g:plugs[name] = spec + let s:loaded[name] = get(s:loaded, name, 0) + catch + return s:err(v:exception) + endtry +endfunction + +function! s:parse_options(arg) + let opts = copy(s:base_spec) + let type = type(a:arg) + if type == s:TYPE.string + let opts.tag = a:arg + elseif type == s:TYPE.dict + call extend(opts, a:arg) + if has_key(opts, 'dir') + let opts.dir = s:dirpath(expand(opts.dir)) + endif + else + throw 'Invalid argument type (expected: string or dictionary)' + endif + return opts +endfunction + +function! s:infer_properties(name, repo) + let repo = a:repo + if s:is_local_plug(repo) + return { 'dir': s:dirpath(expand(repo)) } + else + if repo =~ ':' + let uri = repo + else + if repo !~ '/' + throw printf('Invalid argument: %s (implicit `vim-scripts'' expansion is deprecated)', repo) + endif + let fmt = get(g:, 'plug_url_format', 'https://git::@github.com/%s.git') + let uri = printf(fmt, repo) + endif + return { 'dir': s:dirpath(g:plug_home.'/'.a:name), 'uri': uri } + endif +endfunction + +function! s:install(force, names) + call s:update_impl(0, a:force, a:names) +endfunction + +function! s:update(force, names) + call s:update_impl(1, a:force, a:names) +endfunction + +function! plug#helptags() + if !exists('g:plugs') + return s:err('plug#begin was not called') + endif + for spec in values(g:plugs) + let docd = join([s:rtp(spec), 'doc'], '/') + if isdirectory(docd) + silent! execute 'helptags' s:esc(docd) + endif + endfor + return 1 +endfunction + +function! s:syntax() + syntax clear + syntax region plug1 start=/\%1l/ end=/\%2l/ contains=plugNumber + syntax region plug2 start=/\%2l/ end=/\%3l/ contains=plugBracket,plugX + syn match plugNumber /[0-9]\+[0-9.]*/ contained + syn match plugBracket /[[\]]/ contained + syn match plugX /x/ contained + syn match plugDash /^-/ + syn match plugPlus /^+/ + syn match plugStar /^*/ + syn match plugMessage /\(^- \)\@<=.*/ + syn match plugName /\(^- \)\@<=[^ ]*:/ + syn match plugSha /\%(: \)\@<=[0-9a-f]\{4,}$/ + syn match plugTag /(tag: [^)]\+)/ + syn match plugInstall /\(^+ \)\@<=[^:]*/ + syn match plugUpdate /\(^* \)\@<=[^:]*/ + syn match plugCommit /^ \X*[0-9a-f]\{7,9} .*/ contains=plugRelDate,plugEdge,plugTag + syn match plugEdge /^ \X\+$/ + syn match plugEdge /^ \X*/ contained nextgroup=plugSha + syn match plugSha /[0-9a-f]\{7,9}/ contained + syn match plugRelDate /([^)]*)$/ contained + syn match plugNotLoaded /(not loaded)$/ + syn match plugError /^x.*/ + syn region plugDeleted start=/^\~ .*/ end=/^\ze\S/ + syn match plugH2 /^.*:\n-\+$/ + syn keyword Function PlugInstall PlugStatus PlugUpdate PlugClean + hi def link plug1 Title + hi def link plug2 Repeat + hi def link plugH2 Type + hi def link plugX Exception + hi def link plugBracket Structure + hi def link plugNumber Number + + hi def link plugDash Special + hi def link plugPlus Constant + hi def link plugStar Boolean + + hi def link plugMessage Function + hi def link plugName Label + hi def link plugInstall Function + hi def link plugUpdate Type + + hi def link plugError Error + hi def link plugDeleted Ignore + hi def link plugRelDate Comment + hi def link plugEdge PreProc + hi def link plugSha Identifier + hi def link plugTag Constant + + hi def link plugNotLoaded Comment +endfunction + +function! s:lpad(str, len) + return a:str . repeat(' ', a:len - len(a:str)) +endfunction + +function! s:lines(msg) + return split(a:msg, "[\r\n]") +endfunction + +function! s:lastline(msg) + return get(s:lines(a:msg), -1, '') +endfunction + +function! s:new_window() + execute get(g:, 'plug_window', 'vertical topleft new') +endfunction + +function! s:plug_window_exists() + let buflist = tabpagebuflist(s:plug_tab) + return !empty(buflist) && index(buflist, s:plug_buf) >= 0 +endfunction + +function! s:switch_in() + if !s:plug_window_exists() + return 0 + endif + + if winbufnr(0) != s:plug_buf + let s:pos = [tabpagenr(), winnr(), winsaveview()] + execute 'normal!' s:plug_tab.'gt' + let winnr = bufwinnr(s:plug_buf) + execute winnr.'wincmd w' + call add(s:pos, winsaveview()) + else + let s:pos = [winsaveview()] + endif + + setlocal modifiable + return 1 +endfunction + +function! s:switch_out(...) + call winrestview(s:pos[-1]) + setlocal nomodifiable + if a:0 > 0 + execute a:1 + endif + + if len(s:pos) > 1 + execute 'normal!' s:pos[0].'gt' + execute s:pos[1] 'wincmd w' + call winrestview(s:pos[2]) + endif +endfunction + +function! s:finish_bindings() + nnoremap R :call retry() + nnoremap D :PlugDiff + nnoremap S :PlugStatus + nnoremap U :call status_update() + xnoremap U :call status_update() + nnoremap ]] :silent! call section('') + nnoremap [[ :silent! call section('b') +endfunction + +function! s:prepare(...) + if empty(getcwd()) + throw 'Invalid current working directory. Cannot proceed.' + endif + + for evar in ['$GIT_DIR', '$GIT_WORK_TREE'] + if exists(evar) + throw evar.' detected. Cannot proceed.' + endif + endfor + + call s:job_abort() + if s:switch_in() + if b:plug_preview == 1 + pc + endif + enew + else + call s:new_window() + endif + + nnoremap q :if b:plug_preview==1pcendifbd + if a:0 == 0 + call s:finish_bindings() + endif + let b:plug_preview = -1 + let s:plug_tab = tabpagenr() + let s:plug_buf = winbufnr(0) + call s:assign_name() + + for k in ['', 'L', 'o', 'X', 'd', 'dd'] + execute 'silent! unmap ' k + endfor + setlocal buftype=nofile bufhidden=wipe nobuflisted nolist noswapfile nowrap cursorline modifiable nospell + setf vim-plug + if exists('g:syntax_on') + call s:syntax() + endif +endfunction + +function! s:assign_name() + " Assign buffer name + let prefix = '[Plugins]' + let name = prefix + let idx = 2 + while bufexists(name) + let name = printf('%s (%s)', prefix, idx) + let idx = idx + 1 + endwhile + silent! execute 'f' fnameescape(name) +endfunction + +function! s:chsh(swap) + let prev = [&shell, &shellcmdflag, &shellredir] + if s:is_win + set shell=cmd.exe shellcmdflag=/c shellredir=>%s\ 2>&1 + elseif a:swap + set shell=sh shellredir=>%s\ 2>&1 + endif + return prev +endfunction + +function! s:bang(cmd, ...) + try + let [sh, shellcmdflag, shrd] = s:chsh(a:0) + " FIXME: Escaping is incomplete. We could use shellescape with eval, + " but it won't work on Windows. + let cmd = a:0 ? s:with_cd(a:cmd, a:1) : a:cmd + if s:is_win + let batchfile = tempname().'.bat' + call writefile(['@echo off', cmd], batchfile) + let cmd = batchfile + endif + let g:_plug_bang = (s:is_win && has('gui_running') ? 'silent ' : '').'!'.escape(cmd, '#!%') + execute "normal! :execute g:_plug_bang\\" + finally + unlet g:_plug_bang + let [&shell, &shellcmdflag, &shellredir] = [sh, shellcmdflag, shrd] + if s:is_win + call delete(batchfile) + endif + endtry + return v:shell_error ? 'Exit status: ' . v:shell_error : '' +endfunction + +function! s:regress_bar() + let bar = substitute(getline(2)[1:-2], '.*\zs=', 'x', '') + call s:progress_bar(2, bar, len(bar)) +endfunction + +function! s:is_updated(dir) + return !empty(s:system_chomp('git log --pretty=format:"%h" "HEAD...HEAD@{1}"', a:dir)) +endfunction + +function! s:do(pull, force, todo) + for [name, spec] in items(a:todo) + if !isdirectory(spec.dir) + continue + endif + let installed = has_key(s:update.new, name) + let updated = installed ? 0 : + \ (a:pull && index(s:update.errors, name) < 0 && s:is_updated(spec.dir)) + if a:force || installed || updated + execute 'cd' s:esc(spec.dir) + call append(3, '- Post-update hook for '. name .' ... ') + let error = '' + let type = type(spec.do) + if type == s:TYPE.string + if spec.do[0] == ':' + if !get(s:loaded, name, 0) + let s:loaded[name] = 1 + call s:reorg_rtp() + endif + call s:load_plugin(spec) + try + execute spec.do[1:] + catch + let error = v:exception + endtry + if !s:plug_window_exists() + cd - + throw 'Warning: vim-plug was terminated by the post-update hook of '.name + endif + else + let error = s:bang(spec.do) + endif + elseif type == s:TYPE.funcref + try + let status = installed ? 'installed' : (updated ? 'updated' : 'unchanged') + call spec.do({ 'name': name, 'status': status, 'force': a:force }) + catch + let error = v:exception + endtry + else + let error = 'Invalid hook type' + endif + call s:switch_in() + call setline(4, empty(error) ? (getline(4) . 'OK') + \ : ('x' . getline(4)[1:] . error)) + if !empty(error) + call add(s:update.errors, name) + call s:regress_bar() + endif + cd - + endif + endfor +endfunction + +function! s:hash_match(a, b) + return stridx(a:a, a:b) == 0 || stridx(a:b, a:a) == 0 +endfunction + +function! s:checkout(spec) + let sha = a:spec.commit + let output = s:system('git rev-parse HEAD', a:spec.dir) + if !v:shell_error && !s:hash_match(sha, s:lines(output)[0]) + let output = s:system( + \ 'git fetch --depth 999999 && git checkout '.s:esc(sha).' --', a:spec.dir) + endif + return output +endfunction + +function! s:finish(pull) + let new_frozen = len(filter(keys(s:update.new), 'g:plugs[v:val].frozen')) + if new_frozen + let s = new_frozen > 1 ? 's' : '' + call append(3, printf('- Installed %d frozen plugin%s', new_frozen, s)) + endif + call append(3, '- Finishing ... ') | 4 + redraw + call plug#helptags() + call plug#end() + call setline(4, getline(4) . 'Done!') + redraw + let msgs = [] + if !empty(s:update.errors) + call add(msgs, "Press 'R' to retry.") + endif + if a:pull && len(s:update.new) < len(filter(getline(5, '$'), + \ "v:val =~ '^- ' && v:val !~# 'Already up.to.date'")) + call add(msgs, "Press 'D' to see the updated changes.") + endif + echo join(msgs, ' ') + call s:finish_bindings() +endfunction + +function! s:retry() + if empty(s:update.errors) + return + endif + echo + call s:update_impl(s:update.pull, s:update.force, + \ extend(copy(s:update.errors), [s:update.threads])) +endfunction + +function! s:is_managed(name) + return has_key(g:plugs[a:name], 'uri') +endfunction + +function! s:names(...) + return sort(filter(keys(g:plugs), 'stridx(v:val, a:1) == 0 && s:is_managed(v:val)')) +endfunction + +function! s:check_ruby() + silent! ruby require 'thread'; VIM::command("let g:plug_ruby = '#{RUBY_VERSION}'") + if !exists('g:plug_ruby') + redraw! + return s:warn('echom', 'Warning: Ruby interface is broken') + endif + let ruby_version = split(g:plug_ruby, '\.') + unlet g:plug_ruby + return s:version_requirement(ruby_version, [1, 8, 7]) +endfunction + +function! s:update_impl(pull, force, args) abort + let sync = index(a:args, '--sync') >= 0 || has('vim_starting') + let args = filter(copy(a:args), 'v:val != "--sync"') + let threads = (len(args) > 0 && args[-1] =~ '^[1-9][0-9]*$') ? + \ remove(args, -1) : get(g:, 'plug_threads', 16) + + let managed = filter(copy(g:plugs), 's:is_managed(v:key)') + let todo = empty(args) ? filter(managed, '!v:val.frozen || !isdirectory(v:val.dir)') : + \ filter(managed, 'index(args, v:key) >= 0') + + if empty(todo) + return s:warn('echo', 'No plugin to '. (a:pull ? 'update' : 'install')) + endif + + if !s:is_win && s:git_version_requirement(2, 3) + let s:git_terminal_prompt = exists('$GIT_TERMINAL_PROMPT') ? $GIT_TERMINAL_PROMPT : '' + let $GIT_TERMINAL_PROMPT = 0 + for plug in values(todo) + let plug.uri = substitute(plug.uri, + \ '^https://git::@github\.com', 'https://github.com', '') + endfor + endif + + if !isdirectory(g:plug_home) + try + call mkdir(g:plug_home, 'p') + catch + return s:err(printf('Invalid plug directory: %s. '. + \ 'Try to call plug#begin with a valid directory', g:plug_home)) + endtry + endif + + if has('nvim') && !exists('*jobwait') && threads > 1 + call s:warn('echom', '[vim-plug] Update Neovim for parallel installer') + endif + + let use_job = s:nvim || s:vim8 + let python = (has('python') || has('python3')) && !use_job + let ruby = has('ruby') && !use_job && (v:version >= 703 || v:version == 702 && has('patch374')) && !(s:is_win && has('gui_running')) && threads > 1 && s:check_ruby() + + let s:update = { + \ 'start': reltime(), + \ 'all': todo, + \ 'todo': copy(todo), + \ 'errors': [], + \ 'pull': a:pull, + \ 'force': a:force, + \ 'new': {}, + \ 'threads': (python || ruby || use_job) ? min([len(todo), threads]) : 1, + \ 'bar': '', + \ 'fin': 0 + \ } + + call s:prepare(1) + call append(0, ['', '']) + normal! 2G + silent! redraw + + let s:clone_opt = get(g:, 'plug_shallow', 1) ? + \ '--depth 1' . (s:git_version_requirement(1, 7, 10) ? ' --no-single-branch' : '') : '' + + if has('win32unix') + let s:clone_opt .= ' -c core.eol=lf -c core.autocrlf=input' + endif + + " Python version requirement (>= 2.7) + if python && !has('python3') && !ruby && !use_job && s:update.threads > 1 + redir => pyv + silent python import platform; print platform.python_version() + redir END + let python = s:version_requirement( + \ map(split(split(pyv)[0], '\.'), 'str2nr(v:val)'), [2, 6]) + endif + + if (python || ruby) && s:update.threads > 1 + try + let imd = &imd + if s:mac_gui + set noimd + endif + if ruby + call s:update_ruby() + else + call s:update_python() + endif + catch + let lines = getline(4, '$') + let printed = {} + silent! 4,$d _ + for line in lines + let name = s:extract_name(line, '.', '') + if empty(name) || !has_key(printed, name) + call append('$', line) + if !empty(name) + let printed[name] = 1 + if line[0] == 'x' && index(s:update.errors, name) < 0 + call add(s:update.errors, name) + end + endif + endif + endfor + finally + let &imd = imd + call s:update_finish() + endtry + else + call s:update_vim() + while use_job && sync + sleep 100m + if s:update.fin + break + endif + endwhile + endif +endfunction + +function! s:log4(name, msg) + call setline(4, printf('- %s (%s)', a:msg, a:name)) + redraw +endfunction + +function! s:update_finish() + if exists('s:git_terminal_prompt') + let $GIT_TERMINAL_PROMPT = s:git_terminal_prompt + endif + if s:switch_in() + call append(3, '- Updating ...') | 4 + for [name, spec] in items(filter(copy(s:update.all), 'index(s:update.errors, v:key) < 0 && (s:update.force || s:update.pull || has_key(s:update.new, v:key))')) + let [pos, _] = s:logpos(name) + if !pos + continue + endif + if has_key(spec, 'commit') + call s:log4(name, 'Checking out '.spec.commit) + let out = s:checkout(spec) + elseif has_key(spec, 'tag') + let tag = spec.tag + if tag =~ '\*' + let tags = s:lines(s:system('git tag --list '.s:shellesc(tag).' --sort -version:refname 2>&1', spec.dir)) + if !v:shell_error && !empty(tags) + let tag = tags[0] + call s:log4(name, printf('Latest tag for %s -> %s', spec.tag, tag)) + call append(3, '') + endif + endif + call s:log4(name, 'Checking out '.tag) + let out = s:system('git checkout -q '.s:esc(tag).' -- 2>&1', spec.dir) + else + let branch = s:esc(get(spec, 'branch', 'master')) + call s:log4(name, 'Merging origin/'.branch) + let out = s:system('git checkout -q '.branch.' -- 2>&1' + \. (has_key(s:update.new, name) ? '' : ('&& git merge --ff-only origin/'.branch.' 2>&1')), spec.dir) + endif + if !v:shell_error && filereadable(spec.dir.'/.gitmodules') && + \ (s:update.force || has_key(s:update.new, name) || s:is_updated(spec.dir)) + call s:log4(name, 'Updating submodules. This may take a while.') + let out .= s:bang('git submodule update --init --recursive 2>&1', spec.dir) + endif + let msg = s:format_message(v:shell_error ? 'x': '-', name, out) + if v:shell_error + call add(s:update.errors, name) + call s:regress_bar() + silent execute pos 'd _' + call append(4, msg) | 4 + elseif !empty(out) + call setline(pos, msg[0]) + endif + redraw + endfor + silent 4 d _ + try + call s:do(s:update.pull, s:update.force, filter(copy(s:update.all), 'index(s:update.errors, v:key) < 0 && has_key(v:val, "do")')) + catch + call s:warn('echom', v:exception) + call s:warn('echo', '') + return + endtry + call s:finish(s:update.pull) + call setline(1, 'Updated. Elapsed time: ' . split(reltimestr(reltime(s:update.start)))[0] . ' sec.') + call s:switch_out('normal! gg') + endif +endfunction + +function! s:job_abort() + if (!s:nvim && !s:vim8) || !exists('s:jobs') + return + endif + + for [name, j] in items(s:jobs) + if s:nvim + silent! call jobstop(j.jobid) + elseif s:vim8 + silent! call job_stop(j.jobid) + endif + if j.new + call s:system('rm -rf ' . s:shellesc(g:plugs[name].dir)) + endif + endfor + let s:jobs = {} +endfunction + +function! s:last_non_empty_line(lines) + let len = len(a:lines) + for idx in range(len) + let line = a:lines[len-idx-1] + if !empty(line) + return line + endif + endfor + return '' +endfunction + +function! s:job_out_cb(self, data) abort + let self = a:self + let data = remove(self.lines, -1) . a:data + let lines = map(split(data, "\n", 1), 'split(v:val, "\r", 1)[-1]') + call extend(self.lines, lines) + " To reduce the number of buffer updates + let self.tick = get(self, 'tick', -1) + 1 + if !self.running || self.tick % len(s:jobs) == 0 + let bullet = self.running ? (self.new ? '+' : '*') : (self.error ? 'x' : '-') + let result = self.error ? join(self.lines, "\n") : s:last_non_empty_line(self.lines) + call s:log(bullet, self.name, result) + endif +endfunction + +function! s:job_exit_cb(self, data) abort + let a:self.running = 0 + let a:self.error = a:data != 0 + call s:reap(a:self.name) + call s:tick() +endfunction + +function! s:job_cb(fn, job, ch, data) + if !s:plug_window_exists() " plug window closed + return s:job_abort() + endif + call call(a:fn, [a:job, a:data]) +endfunction + +function! s:nvim_cb(job_id, data, event) dict abort + return a:event == 'stdout' ? + \ s:job_cb('s:job_out_cb', self, 0, join(a:data, "\n")) : + \ s:job_cb('s:job_exit_cb', self, 0, a:data) +endfunction + +function! s:spawn(name, cmd, opts) + let job = { 'name': a:name, 'running': 1, 'error': 0, 'lines': [''], + \ 'batchfile': (s:is_win && (s:nvim || s:vim8)) ? tempname().'.bat' : '', + \ 'new': get(a:opts, 'new', 0) } + let s:jobs[a:name] = job + let cmd = has_key(a:opts, 'dir') ? s:with_cd(a:cmd, a:opts.dir) : a:cmd + if !empty(job.batchfile) + call writefile(['@echo off', cmd], job.batchfile) + let cmd = job.batchfile + endif + let argv = add(s:is_win ? ['cmd', '/c'] : ['sh', '-c'], cmd) + + if s:nvim + call extend(job, { + \ 'on_stdout': function('s:nvim_cb'), + \ 'on_exit': function('s:nvim_cb'), + \ }) + let jid = jobstart(argv, job) + if jid > 0 + let job.jobid = jid + else + let job.running = 0 + let job.error = 1 + let job.lines = [jid < 0 ? argv[0].' is not executable' : + \ 'Invalid arguments (or job table is full)'] + endif + elseif s:vim8 + let jid = job_start(s:is_win ? join(argv, ' ') : argv, { + \ 'out_cb': function('s:job_cb', ['s:job_out_cb', job]), + \ 'exit_cb': function('s:job_cb', ['s:job_exit_cb', job]), + \ 'out_mode': 'raw' + \}) + if job_status(jid) == 'run' + let job.jobid = jid + else + let job.running = 0 + let job.error = 1 + let job.lines = ['Failed to start job'] + endif + else + let job.lines = s:lines(call('s:system', [cmd])) + let job.error = v:shell_error != 0 + let job.running = 0 + endif +endfunction + +function! s:reap(name) + let job = s:jobs[a:name] + if job.error + call add(s:update.errors, a:name) + elseif get(job, 'new', 0) + let s:update.new[a:name] = 1 + endif + let s:update.bar .= job.error ? 'x' : '=' + + let bullet = job.error ? 'x' : '-' + let result = job.error ? join(job.lines, "\n") : s:last_non_empty_line(job.lines) + call s:log(bullet, a:name, empty(result) ? 'OK' : result) + call s:bar() + + if has_key(job, 'batchfile') && !empty(job.batchfile) + call delete(job.batchfile) + endif + call remove(s:jobs, a:name) +endfunction + +function! s:bar() + if s:switch_in() + let total = len(s:update.all) + call setline(1, (s:update.pull ? 'Updating' : 'Installing'). + \ ' plugins ('.len(s:update.bar).'/'.total.')') + call s:progress_bar(2, s:update.bar, total) + call s:switch_out() + endif +endfunction + +function! s:logpos(name) + for i in range(4, line('$')) + if getline(i) =~# '^[-+x*] '.a:name.':' + for j in range(i + 1, line('$')) + if getline(j) !~ '^ ' + return [i, j - 1] + endif + endfor + return [i, i] + endif + endfor + return [0, 0] +endfunction + +function! s:log(bullet, name, lines) + if s:switch_in() + let [b, e] = s:logpos(a:name) + if b > 0 + silent execute printf('%d,%d d _', b, e) + if b > winheight('.') + let b = 4 + endif + else + let b = 4 + endif + " FIXME For some reason, nomodifiable is set after :d in vim8 + setlocal modifiable + call append(b - 1, s:format_message(a:bullet, a:name, a:lines)) + call s:switch_out() + endif +endfunction + +function! s:update_vim() + let s:jobs = {} + + call s:bar() + call s:tick() +endfunction + +function! s:tick() + let pull = s:update.pull + let prog = s:progress_opt(s:nvim || s:vim8) +while 1 " Without TCO, Vim stack is bound to explode + if empty(s:update.todo) + if empty(s:jobs) && !s:update.fin + call s:update_finish() + let s:update.fin = 1 + endif + return + endif + + let name = keys(s:update.todo)[0] + let spec = remove(s:update.todo, name) + let new = !isdirectory(spec.dir) + + call s:log(new ? '+' : '*', name, pull ? 'Updating ...' : 'Installing ...') + redraw + + let has_tag = has_key(spec, 'tag') + if !new + let [error, _] = s:git_validate(spec, 0) + if empty(error) + if pull + let fetch_opt = (has_tag && !empty(globpath(spec.dir, '.git/shallow'))) ? '--depth 99999999' : '' + call s:spawn(name, printf('git fetch %s %s 2>&1', fetch_opt, prog), { 'dir': spec.dir }) + else + let s:jobs[name] = { 'running': 0, 'lines': ['Already installed'], 'error': 0 } + endif + else + let s:jobs[name] = { 'running': 0, 'lines': s:lines(error), 'error': 1 } + endif + else + call s:spawn(name, + \ printf('git clone %s %s %s %s 2>&1', + \ has_tag ? '' : s:clone_opt, + \ prog, + \ s:shellesc(spec.uri), + \ s:shellesc(s:trim(spec.dir))), { 'new': 1 }) + endif + + if !s:jobs[name].running + call s:reap(name) + endif + if len(s:jobs) >= s:update.threads + break + endif +endwhile +endfunction + +function! s:update_python() +let py_exe = has('python') ? 'python' : 'python3' +execute py_exe "<< EOF" +import datetime +import functools +import os +try: + import queue +except ImportError: + import Queue as queue +import random +import re +import shutil +import signal +import subprocess +import tempfile +import threading as thr +import time +import traceback +import vim + +G_NVIM = vim.eval("has('nvim')") == '1' +G_PULL = vim.eval('s:update.pull') == '1' +G_RETRIES = int(vim.eval('get(g:, "plug_retries", 2)')) + 1 +G_TIMEOUT = int(vim.eval('get(g:, "plug_timeout", 60)')) +G_CLONE_OPT = vim.eval('s:clone_opt') +G_PROGRESS = vim.eval('s:progress_opt(1)') +G_LOG_PROB = 1.0 / int(vim.eval('s:update.threads')) +G_STOP = thr.Event() +G_IS_WIN = vim.eval('s:is_win') == '1' + +class PlugError(Exception): + def __init__(self, msg): + self.msg = msg +class CmdTimedOut(PlugError): + pass +class CmdFailed(PlugError): + pass +class InvalidURI(PlugError): + pass +class Action(object): + INSTALL, UPDATE, ERROR, DONE = ['+', '*', 'x', '-'] + +class Buffer(object): + def __init__(self, lock, num_plugs, is_pull): + self.bar = '' + self.event = 'Updating' if is_pull else 'Installing' + self.lock = lock + self.maxy = int(vim.eval('winheight(".")')) + self.num_plugs = num_plugs + + def __where(self, name): + """ Find first line with name in current buffer. Return line num. """ + found, lnum = False, 0 + matcher = re.compile('^[-+x*] {0}:'.format(name)) + for line in vim.current.buffer: + if matcher.search(line) is not None: + found = True + break + lnum += 1 + + if not found: + lnum = -1 + return lnum + + def header(self): + curbuf = vim.current.buffer + curbuf[0] = self.event + ' plugins ({0}/{1})'.format(len(self.bar), self.num_plugs) + + num_spaces = self.num_plugs - len(self.bar) + curbuf[1] = '[{0}{1}]'.format(self.bar, num_spaces * ' ') + + with self.lock: + vim.command('normal! 2G') + vim.command('redraw') + + def write(self, action, name, lines): + first, rest = lines[0], lines[1:] + msg = ['{0} {1}{2}{3}'.format(action, name, ': ' if first else '', first)] + msg.extend([' ' + line for line in rest]) + + try: + if action == Action.ERROR: + self.bar += 'x' + vim.command("call add(s:update.errors, '{0}')".format(name)) + elif action == Action.DONE: + self.bar += '=' + + curbuf = vim.current.buffer + lnum = self.__where(name) + if lnum != -1: # Found matching line num + del curbuf[lnum] + if lnum > self.maxy and action in set([Action.INSTALL, Action.UPDATE]): + lnum = 3 + else: + lnum = 3 + curbuf.append(msg, lnum) + + self.header() + except vim.error: + pass + +class Command(object): + CD = 'cd /d' if G_IS_WIN else 'cd' + + def __init__(self, cmd, cmd_dir=None, timeout=60, cb=None, clean=None): + self.cmd = cmd + if cmd_dir: + self.cmd = '{0} {1} && {2}'.format(Command.CD, cmd_dir, self.cmd) + self.timeout = timeout + self.callback = cb if cb else (lambda msg: None) + self.clean = clean if clean else (lambda: None) + self.proc = None + + @property + def alive(self): + """ Returns true only if command still running. """ + return self.proc and self.proc.poll() is None + + def execute(self, ntries=3): + """ Execute the command with ntries if CmdTimedOut. + Returns the output of the command if no Exception. + """ + attempt, finished, limit = 0, False, self.timeout + + while not finished: + try: + attempt += 1 + result = self.try_command() + finished = True + return result + except CmdTimedOut: + if attempt != ntries: + self.notify_retry() + self.timeout += limit + else: + raise + + def notify_retry(self): + """ Retry required for command, notify user. """ + for count in range(3, 0, -1): + if G_STOP.is_set(): + raise KeyboardInterrupt + msg = 'Timeout. Will retry in {0} second{1} ...'.format( + count, 's' if count != 1 else '') + self.callback([msg]) + time.sleep(1) + self.callback(['Retrying ...']) + + def try_command(self): + """ Execute a cmd & poll for callback. Returns list of output. + Raises CmdFailed -> return code for Popen isn't 0 + Raises CmdTimedOut -> command exceeded timeout without new output + """ + first_line = True + + try: + tfile = tempfile.NamedTemporaryFile(mode='w+b') + preexec_fn = not G_IS_WIN and os.setsid or None + self.proc = subprocess.Popen(self.cmd, stdout=tfile, + stderr=subprocess.STDOUT, + stdin=subprocess.PIPE, shell=True, + preexec_fn=preexec_fn) + thrd = thr.Thread(target=(lambda proc: proc.wait()), args=(self.proc,)) + thrd.start() + + thread_not_started = True + while thread_not_started: + try: + thrd.join(0.1) + thread_not_started = False + except RuntimeError: + pass + + while self.alive: + if G_STOP.is_set(): + raise KeyboardInterrupt + + if first_line or random.random() < G_LOG_PROB: + first_line = False + line = '' if G_IS_WIN else nonblock_read(tfile.name) + if line: + self.callback([line]) + + time_diff = time.time() - os.path.getmtime(tfile.name) + if time_diff > self.timeout: + raise CmdTimedOut(['Timeout!']) + + thrd.join(0.5) + + tfile.seek(0) + result = [line.decode('utf-8', 'replace').rstrip() for line in tfile] + + if self.proc.returncode != 0: + raise CmdFailed([''] + result) + + return result + except: + self.terminate() + raise + + def terminate(self): + """ Terminate process and cleanup. """ + if self.alive: + if G_IS_WIN: + os.kill(self.proc.pid, signal.SIGINT) + else: + os.killpg(self.proc.pid, signal.SIGTERM) + self.clean() + +class Plugin(object): + def __init__(self, name, args, buf_q, lock): + self.name = name + self.args = args + self.buf_q = buf_q + self.lock = lock + self.tag = args.get('tag', 0) + + def manage(self): + try: + if os.path.exists(self.args['dir']): + self.update() + else: + self.install() + with self.lock: + thread_vim_command("let s:update.new['{0}'] = 1".format(self.name)) + except PlugError as exc: + self.write(Action.ERROR, self.name, exc.msg) + except KeyboardInterrupt: + G_STOP.set() + self.write(Action.ERROR, self.name, ['Interrupted!']) + except: + # Any exception except those above print stack trace + msg = 'Trace:\n{0}'.format(traceback.format_exc().rstrip()) + self.write(Action.ERROR, self.name, msg.split('\n')) + raise + + def install(self): + target = self.args['dir'] + if target[-1] == '\\': + target = target[0:-1] + + def clean(target): + def _clean(): + try: + shutil.rmtree(target) + except OSError: + pass + return _clean + + self.write(Action.INSTALL, self.name, ['Installing ...']) + callback = functools.partial(self.write, Action.INSTALL, self.name) + cmd = 'git clone {0} {1} {2} {3} 2>&1'.format( + '' if self.tag else G_CLONE_OPT, G_PROGRESS, self.args['uri'], + esc(target)) + com = Command(cmd, None, G_TIMEOUT, callback, clean(target)) + result = com.execute(G_RETRIES) + self.write(Action.DONE, self.name, result[-1:]) + + def repo_uri(self): + cmd = 'git rev-parse --abbrev-ref HEAD 2>&1 && git config -f .git/config remote.origin.url' + command = Command(cmd, self.args['dir'], G_TIMEOUT,) + result = command.execute(G_RETRIES) + return result[-1] + + def update(self): + actual_uri = self.repo_uri() + expect_uri = self.args['uri'] + regex = re.compile(r'^(?:\w+://)?(?:[^@/]*@)?([^:/]*(?::[0-9]*)?)[:/](.*?)(?:\.git)?/?$') + ma = regex.match(actual_uri) + mb = regex.match(expect_uri) + if ma is None or mb is None or ma.groups() != mb.groups(): + msg = ['', + 'Invalid URI: {0}'.format(actual_uri), + 'Expected {0}'.format(expect_uri), + 'PlugClean required.'] + raise InvalidURI(msg) + + if G_PULL: + self.write(Action.UPDATE, self.name, ['Updating ...']) + callback = functools.partial(self.write, Action.UPDATE, self.name) + fetch_opt = '--depth 99999999' if self.tag and os.path.isfile(os.path.join(self.args['dir'], '.git/shallow')) else '' + cmd = 'git fetch {0} {1} 2>&1'.format(fetch_opt, G_PROGRESS) + com = Command(cmd, self.args['dir'], G_TIMEOUT, callback) + result = com.execute(G_RETRIES) + self.write(Action.DONE, self.name, result[-1:]) + else: + self.write(Action.DONE, self.name, ['Already installed']) + + def write(self, action, name, msg): + self.buf_q.put((action, name, msg)) + +class PlugThread(thr.Thread): + def __init__(self, tname, args): + super(PlugThread, self).__init__() + self.tname = tname + self.args = args + + def run(self): + thr.current_thread().name = self.tname + buf_q, work_q, lock = self.args + + try: + while not G_STOP.is_set(): + name, args = work_q.get_nowait() + plug = Plugin(name, args, buf_q, lock) + plug.manage() + work_q.task_done() + except queue.Empty: + pass + +class RefreshThread(thr.Thread): + def __init__(self, lock): + super(RefreshThread, self).__init__() + self.lock = lock + self.running = True + + def run(self): + while self.running: + with self.lock: + thread_vim_command('noautocmd normal! a') + time.sleep(0.33) + + def stop(self): + self.running = False + +if G_NVIM: + def thread_vim_command(cmd): + vim.session.threadsafe_call(lambda: vim.command(cmd)) +else: + def thread_vim_command(cmd): + vim.command(cmd) + +def esc(name): + return '"' + name.replace('"', '\"') + '"' + +def nonblock_read(fname): + """ Read a file with nonblock flag. Return the last line. """ + fread = os.open(fname, os.O_RDONLY | os.O_NONBLOCK) + buf = os.read(fread, 100000).decode('utf-8', 'replace') + os.close(fread) + + line = buf.rstrip('\r\n') + left = max(line.rfind('\r'), line.rfind('\n')) + if left != -1: + left += 1 + line = line[left:] + + return line + +def main(): + thr.current_thread().name = 'main' + nthreads = int(vim.eval('s:update.threads')) + plugs = vim.eval('s:update.todo') + mac_gui = vim.eval('s:mac_gui') == '1' + + lock = thr.Lock() + buf = Buffer(lock, len(plugs), G_PULL) + buf_q, work_q = queue.Queue(), queue.Queue() + for work in plugs.items(): + work_q.put(work) + + start_cnt = thr.active_count() + for num in range(nthreads): + tname = 'PlugT-{0:02}'.format(num) + thread = PlugThread(tname, (buf_q, work_q, lock)) + thread.start() + if mac_gui: + rthread = RefreshThread(lock) + rthread.start() + + while not buf_q.empty() or thr.active_count() != start_cnt: + try: + action, name, msg = buf_q.get(True, 0.25) + buf.write(action, name, ['OK'] if not msg else msg) + buf_q.task_done() + except queue.Empty: + pass + except KeyboardInterrupt: + G_STOP.set() + + if mac_gui: + rthread.stop() + rthread.join() + +main() +EOF +endfunction + +function! s:update_ruby() + ruby << EOF + module PlugStream + SEP = ["\r", "\n", nil] + def get_line + buffer = '' + loop do + char = readchar rescue return + if SEP.include? char.chr + buffer << $/ + break + else + buffer << char + end + end + buffer + end + end unless defined?(PlugStream) + + def esc arg + %["#{arg.gsub('"', '\"')}"] + end + + def killall pid + pids = [pid] + if /mswin|mingw|bccwin/ =~ RUBY_PLATFORM + pids.each { |pid| Process.kill 'INT', pid.to_i rescue nil } + else + unless `which pgrep 2> /dev/null`.empty? + children = pids + until children.empty? + children = children.map { |pid| + `pgrep -P #{pid}`.lines.map { |l| l.chomp } + }.flatten + pids += children + end + end + pids.each { |pid| Process.kill 'TERM', pid.to_i rescue nil } + end + end + + def compare_git_uri a, b + regex = %r{^(?:\w+://)?(?:[^@/]*@)?([^:/]*(?::[0-9]*)?)[:/](.*?)(?:\.git)?/?$} + regex.match(a).to_a.drop(1) == regex.match(b).to_a.drop(1) + end + + require 'thread' + require 'fileutils' + require 'timeout' + running = true + iswin = VIM::evaluate('s:is_win').to_i == 1 + pull = VIM::evaluate('s:update.pull').to_i == 1 + base = VIM::evaluate('g:plug_home') + all = VIM::evaluate('s:update.todo') + limit = VIM::evaluate('get(g:, "plug_timeout", 60)') + tries = VIM::evaluate('get(g:, "plug_retries", 2)') + 1 + nthr = VIM::evaluate('s:update.threads').to_i + maxy = VIM::evaluate('winheight(".")').to_i + vim7 = VIM::evaluate('v:version').to_i <= 703 && RUBY_PLATFORM =~ /darwin/ + cd = iswin ? 'cd /d' : 'cd' + tot = VIM::evaluate('len(s:update.todo)') || 0 + bar = '' + skip = 'Already installed' + mtx = Mutex.new + take1 = proc { mtx.synchronize { running && all.shift } } + logh = proc { + cnt = bar.length + $curbuf[1] = "#{pull ? 'Updating' : 'Installing'} plugins (#{cnt}/#{tot})" + $curbuf[2] = '[' + bar.ljust(tot) + ']' + VIM::command('normal! 2G') + VIM::command('redraw') + } + where = proc { |name| (1..($curbuf.length)).find { |l| $curbuf[l] =~ /^[-+x*] #{name}:/ } } + log = proc { |name, result, type| + mtx.synchronize do + ing = ![true, false].include?(type) + bar += type ? '=' : 'x' unless ing + b = case type + when :install then '+' when :update then '*' + when true, nil then '-' else + VIM::command("call add(s:update.errors, '#{name}')") + 'x' + end + result = + if type || type.nil? + ["#{b} #{name}: #{result.lines.to_a.last || 'OK'}"] + elsif result =~ /^Interrupted|^Timeout/ + ["#{b} #{name}: #{result}"] + else + ["#{b} #{name}"] + result.lines.map { |l| " " << l } + end + if lnum = where.call(name) + $curbuf.delete lnum + lnum = 4 if ing && lnum > maxy + end + result.each_with_index do |line, offset| + $curbuf.append((lnum || 4) - 1 + offset, line.gsub(/\e\[./, '').chomp) + end + logh.call + end + } + bt = proc { |cmd, name, type, cleanup| + tried = timeout = 0 + begin + tried += 1 + timeout += limit + fd = nil + data = '' + if iswin + Timeout::timeout(timeout) do + tmp = VIM::evaluate('tempname()') + system("(#{cmd}) > #{tmp}") + data = File.read(tmp).chomp + File.unlink tmp rescue nil + end + else + fd = IO.popen(cmd).extend(PlugStream) + first_line = true + log_prob = 1.0 / nthr + while line = Timeout::timeout(timeout) { fd.get_line } + data << line + log.call name, line.chomp, type if name && (first_line || rand < log_prob) + first_line = false + end + fd.close + end + [$? == 0, data.chomp] + rescue Timeout::Error, Interrupt => e + if fd && !fd.closed? + killall fd.pid + fd.close + end + cleanup.call if cleanup + if e.is_a?(Timeout::Error) && tried < tries + 3.downto(1) do |countdown| + s = countdown > 1 ? 's' : '' + log.call name, "Timeout. Will retry in #{countdown} second#{s} ...", type + sleep 1 + end + log.call name, 'Retrying ...', type + retry + end + [false, e.is_a?(Interrupt) ? "Interrupted!" : "Timeout!"] + end + } + main = Thread.current + threads = [] + watcher = Thread.new { + if vim7 + while VIM::evaluate('getchar(1)') + sleep 0.1 + end + else + require 'io/console' # >= Ruby 1.9 + nil until IO.console.getch == 3.chr + end + mtx.synchronize do + running = false + threads.each { |t| t.raise Interrupt } unless vim7 + end + threads.each { |t| t.join rescue nil } + main.kill + } + refresh = Thread.new { + while true + mtx.synchronize do + break unless running + VIM::command('noautocmd normal! a') + end + sleep 0.2 + end + } if VIM::evaluate('s:mac_gui') == 1 + + clone_opt = VIM::evaluate('s:clone_opt') + progress = VIM::evaluate('s:progress_opt(1)') + nthr.times do + mtx.synchronize do + threads << Thread.new { + while pair = take1.call + name = pair.first + dir, uri, tag = pair.last.values_at *%w[dir uri tag] + exists = File.directory? dir + ok, result = + if exists + chdir = "#{cd} #{iswin ? dir : esc(dir)}" + ret, data = bt.call "#{chdir} && git rev-parse --abbrev-ref HEAD 2>&1 && git config -f .git/config remote.origin.url", nil, nil, nil + current_uri = data.lines.to_a.last + if !ret + if data =~ /^Interrupted|^Timeout/ + [false, data] + else + [false, [data.chomp, "PlugClean required."].join($/)] + end + elsif !compare_git_uri(current_uri, uri) + [false, ["Invalid URI: #{current_uri}", + "Expected: #{uri}", + "PlugClean required."].join($/)] + else + if pull + log.call name, 'Updating ...', :update + fetch_opt = (tag && File.exist?(File.join(dir, '.git/shallow'))) ? '--depth 99999999' : '' + bt.call "#{chdir} && git fetch #{fetch_opt} #{progress} 2>&1", name, :update, nil + else + [true, skip] + end + end + else + d = esc dir.sub(%r{[\\/]+$}, '') + log.call name, 'Installing ...', :install + bt.call "git clone #{clone_opt unless tag} #{progress} #{uri} #{d} 2>&1", name, :install, proc { + FileUtils.rm_rf dir + } + end + mtx.synchronize { VIM::command("let s:update.new['#{name}'] = 1") } if !exists && ok + log.call name, result, ok + end + } if running + end + end + threads.each { |t| t.join rescue nil } + logh.call + refresh.kill if refresh + watcher.kill +EOF +endfunction + +function! s:shellesc_cmd(arg) + let escaped = substitute(a:arg, '[&|<>()@^]', '^&', 'g') + let escaped = substitute(escaped, '%', '%%', 'g') + let escaped = substitute(escaped, '"', '\\^&', 'g') + let escaped = substitute(escaped, '\(\\\+\)\(\\^\)', '\1\1\2', 'g') + return '^"'.substitute(escaped, '\(\\\+\)$', '\1\1', '').'^"' +endfunction + +function! s:shellesc(arg) + if &shell =~# 'cmd.exe$' + return s:shellesc_cmd(a:arg) + endif + return shellescape(a:arg) +endfunction + +function! s:glob_dir(path) + return map(filter(s:glob(a:path, '**'), 'isdirectory(v:val)'), 's:dirpath(v:val)') +endfunction + +function! s:progress_bar(line, bar, total) + call setline(a:line, '[' . s:lpad(a:bar, a:total) . ']') +endfunction + +function! s:compare_git_uri(a, b) + " See `git help clone' + " https:// [user@] github.com[:port] / junegunn/vim-plug [.git] + " [git@] github.com[:port] : junegunn/vim-plug [.git] + " file:// / junegunn/vim-plug [/] + " / junegunn/vim-plug [/] + let pat = '^\%(\w\+://\)\='.'\%([^@/]*@\)\='.'\([^:/]*\%(:[0-9]*\)\=\)'.'[:/]'.'\(.\{-}\)'.'\%(\.git\)\=/\?$' + let ma = matchlist(a:a, pat) + let mb = matchlist(a:b, pat) + return ma[1:2] ==# mb[1:2] +endfunction + +function! s:format_message(bullet, name, message) + if a:bullet != 'x' + return [printf('%s %s: %s', a:bullet, a:name, s:lastline(a:message))] + else + let lines = map(s:lines(a:message), '" ".v:val') + return extend([printf('x %s:', a:name)], lines) + endif +endfunction + +function! s:with_cd(cmd, dir) + return printf('cd%s %s && %s', s:is_win ? ' /d' : '', s:shellesc(a:dir), a:cmd) +endfunction + +function! s:system(cmd, ...) + try + let [sh, shellcmdflag, shrd] = s:chsh(1) + let cmd = a:0 > 0 ? s:with_cd(a:cmd, a:1) : a:cmd + if s:is_win + let batchfile = tempname().'.bat' + call writefile(['@echo off', cmd], batchfile) + let cmd = batchfile + endif + return system(s:is_win ? '('.cmd.')' : cmd) + finally + let [&shell, &shellcmdflag, &shellredir] = [sh, shellcmdflag, shrd] + if s:is_win + call delete(batchfile) + endif + endtry +endfunction + +function! s:system_chomp(...) + let ret = call('s:system', a:000) + return v:shell_error ? '' : substitute(ret, '\n$', '', '') +endfunction + +function! s:git_validate(spec, check_branch) + let err = '' + if isdirectory(a:spec.dir) + let result = s:lines(s:system('git rev-parse --abbrev-ref HEAD 2>&1 && git config -f .git/config remote.origin.url', a:spec.dir)) + let remote = result[-1] + if v:shell_error + let err = join([remote, 'PlugClean required.'], "\n") + elseif !s:compare_git_uri(remote, a:spec.uri) + let err = join(['Invalid URI: '.remote, + \ 'Expected: '.a:spec.uri, + \ 'PlugClean required.'], "\n") + elseif a:check_branch && has_key(a:spec, 'commit') + let result = s:lines(s:system('git rev-parse HEAD 2>&1', a:spec.dir)) + let sha = result[-1] + if v:shell_error + let err = join(add(result, 'PlugClean required.'), "\n") + elseif !s:hash_match(sha, a:spec.commit) + let err = join([printf('Invalid HEAD (expected: %s, actual: %s)', + \ a:spec.commit[:6], sha[:6]), + \ 'PlugUpdate required.'], "\n") + endif + elseif a:check_branch + let branch = result[0] + " Check tag + if has_key(a:spec, 'tag') + let tag = s:system_chomp('git describe --exact-match --tags HEAD 2>&1', a:spec.dir) + if a:spec.tag !=# tag && a:spec.tag !~ '\*' + let err = printf('Invalid tag: %s (expected: %s). Try PlugUpdate.', + \ (empty(tag) ? 'N/A' : tag), a:spec.tag) + endif + " Check branch + elseif a:spec.branch !=# branch + let err = printf('Invalid branch: %s (expected: %s). Try PlugUpdate.', + \ branch, a:spec.branch) + endif + if empty(err) + let [ahead, behind] = split(s:lastline(s:system(printf( + \ 'git rev-list --count --left-right HEAD...origin/%s', + \ a:spec.branch), a:spec.dir)), '\t') + if !v:shell_error && ahead + if behind + " Only mention PlugClean if diverged, otherwise it's likely to be + " pushable (and probably not that messed up). + let err = printf( + \ "Diverged from origin/%s (%d commit(s) ahead and %d commit(s) behind!\n" + \ .'Backup local changes and run PlugClean and PlugUpdate to reinstall it.', a:spec.branch, ahead, behind) + else + let err = printf("Ahead of origin/%s by %d commit(s).\n" + \ .'Cannot update until local changes are pushed.', + \ a:spec.branch, ahead) + endif + endif + endif + endif + else + let err = 'Not found' + endif + return [err, err =~# 'PlugClean'] +endfunction + +function! s:rm_rf(dir) + if isdirectory(a:dir) + call s:system((s:is_win ? 'rmdir /S /Q ' : 'rm -rf ') . s:shellesc(a:dir)) + endif +endfunction + +function! s:clean(force) + call s:prepare() + call append(0, 'Searching for invalid plugins in '.g:plug_home) + call append(1, '') + + " List of valid directories + let dirs = [] + let errs = {} + let [cnt, total] = [0, len(g:plugs)] + for [name, spec] in items(g:plugs) + if !s:is_managed(name) + call add(dirs, spec.dir) + else + let [err, clean] = s:git_validate(spec, 1) + if clean + let errs[spec.dir] = s:lines(err)[0] + else + call add(dirs, spec.dir) + endif + endif + let cnt += 1 + call s:progress_bar(2, repeat('=', cnt), total) + normal! 2G + redraw + endfor + + let allowed = {} + for dir in dirs + let allowed[s:dirpath(fnamemodify(dir, ':h:h'))] = 1 + let allowed[dir] = 1 + for child in s:glob_dir(dir) + let allowed[child] = 1 + endfor + endfor + + let todo = [] + let found = sort(s:glob_dir(g:plug_home)) + while !empty(found) + let f = remove(found, 0) + if !has_key(allowed, f) && isdirectory(f) + call add(todo, f) + call append(line('$'), '- ' . f) + if has_key(errs, f) + call append(line('$'), ' ' . errs[f]) + endif + let found = filter(found, 'stridx(v:val, f) != 0') + end + endwhile + + 4 + redraw + if empty(todo) + call append(line('$'), 'Already clean.') + else + let s:clean_count = 0 + call append(3, ['Directories to delete:', '']) + redraw! + if a:force || s:ask_no_interrupt('Delete all directories?') + call s:delete([6, line('$')], 1) + else + call setline(4, 'Cancelled.') + nnoremap d :set opfunc=delete_opg@ + nmap dd d_ + xnoremap d :call delete_op(visualmode(), 1) + echo 'Delete the lines (d{motion}) to delete the corresponding directories' + endif + endif + 4 + setlocal nomodifiable +endfunction + +function! s:delete_op(type, ...) + call s:delete(a:0 ? [line("'<"), line("'>")] : [line("'["), line("']")], 0) +endfunction + +function! s:delete(range, force) + let [l1, l2] = a:range + let force = a:force + while l1 <= l2 + let line = getline(l1) + if line =~ '^- ' && isdirectory(line[2:]) + execute l1 + redraw! + let answer = force ? 1 : s:ask('Delete '.line[2:].'?', 1) + let force = force || answer > 1 + if answer + call s:rm_rf(line[2:]) + setlocal modifiable + call setline(l1, '~'.line[1:]) + let s:clean_count += 1 + call setline(4, printf('Removed %d directories.', s:clean_count)) + setlocal nomodifiable + endif + endif + let l1 += 1 + endwhile +endfunction + +function! s:upgrade() + echo 'Downloading the latest version of vim-plug' + redraw + let tmp = tempname() + let new = tmp . '/plug.vim' + + try + let out = s:system(printf('git clone --depth 1 %s %s', s:plug_src, tmp)) + if v:shell_error + return s:err('Error upgrading vim-plug: '. out) + endif + + if readfile(s:me) ==# readfile(new) + echo 'vim-plug is already up-to-date' + return 0 + else + call rename(s:me, s:me . '.old') + call rename(new, s:me) + unlet g:loaded_plug + echo 'vim-plug has been upgraded' + return 1 + endif + finally + silent! call s:rm_rf(tmp) + endtry +endfunction + +function! s:upgrade_specs() + for spec in values(g:plugs) + let spec.frozen = get(spec, 'frozen', 0) + endfor +endfunction + +function! s:status() + call s:prepare() + call append(0, 'Checking plugins') + call append(1, '') + + let ecnt = 0 + let unloaded = 0 + let [cnt, total] = [0, len(g:plugs)] + for [name, spec] in items(g:plugs) + let is_dir = isdirectory(spec.dir) + if has_key(spec, 'uri') + if is_dir + let [err, _] = s:git_validate(spec, 1) + let [valid, msg] = [empty(err), empty(err) ? 'OK' : err] + else + let [valid, msg] = [0, 'Not found. Try PlugInstall.'] + endif + else + if is_dir + let [valid, msg] = [1, 'OK'] + else + let [valid, msg] = [0, 'Not found.'] + endif + endif + let cnt += 1 + let ecnt += !valid + " `s:loaded` entry can be missing if PlugUpgraded + if is_dir && get(s:loaded, name, -1) == 0 + let unloaded = 1 + let msg .= ' (not loaded)' + endif + call s:progress_bar(2, repeat('=', cnt), total) + call append(3, s:format_message(valid ? '-' : 'x', name, msg)) + normal! 2G + redraw + endfor + call setline(1, 'Finished. '.ecnt.' error(s).') + normal! gg + setlocal nomodifiable + if unloaded + echo "Press 'L' on each line to load plugin, or 'U' to update" + nnoremap L :call status_load(line('.')) + xnoremap L :call status_load(line('.')) + end +endfunction + +function! s:extract_name(str, prefix, suffix) + return matchstr(a:str, '^'.a:prefix.' \zs[^:]\+\ze:.*'.a:suffix.'$') +endfunction + +function! s:status_load(lnum) + let line = getline(a:lnum) + let name = s:extract_name(line, '-', '(not loaded)') + if !empty(name) + call plug#load(name) + setlocal modifiable + call setline(a:lnum, substitute(line, ' (not loaded)$', '', '')) + setlocal nomodifiable + endif +endfunction + +function! s:status_update() range + let lines = getline(a:firstline, a:lastline) + let names = filter(map(lines, 's:extract_name(v:val, "[x-]", "")'), '!empty(v:val)') + if !empty(names) + echo + execute 'PlugUpdate' join(names) + endif +endfunction + +function! s:is_preview_window_open() + silent! wincmd P + if &previewwindow + wincmd p + return 1 + endif +endfunction + +function! s:find_name(lnum) + for lnum in reverse(range(1, a:lnum)) + let line = getline(lnum) + if empty(line) + return '' + endif + let name = s:extract_name(line, '-', '') + if !empty(name) + return name + endif + endfor + return '' +endfunction + +function! s:preview_commit() + if b:plug_preview < 0 + let b:plug_preview = !s:is_preview_window_open() + endif + + let sha = matchstr(getline('.'), '^ \X*\zs[0-9a-f]\{7,9}') + if empty(sha) + return + endif + + let name = s:find_name(line('.')) + if empty(name) || !has_key(g:plugs, name) || !isdirectory(g:plugs[name].dir) + return + endif + + if exists('g:plug_pwindow') && !s:is_preview_window_open() + execute g:plug_pwindow + execute 'e' sha + else + execute 'pedit' sha + wincmd P + endif + setlocal previewwindow filetype=git buftype=nofile nobuflisted modifiable + try + let [sh, shellcmdflag, shrd] = s:chsh(1) + let cmd = 'cd '.s:shellesc(g:plugs[name].dir).' && git show --no-color --pretty=medium '.sha + if s:is_win + let batchfile = tempname().'.bat' + call writefile(['@echo off', cmd], batchfile) + let cmd = batchfile + endif + execute 'silent %!' cmd + finally + let [&shell, &shellcmdflag, &shellredir] = [sh, shellcmdflag, shrd] + if s:is_win + call delete(batchfile) + endif + endtry + setlocal nomodifiable + nnoremap q :q + wincmd p +endfunction + +function! s:section(flags) + call search('\(^[x-] \)\@<=[^:]\+:', a:flags) +endfunction + +function! s:format_git_log(line) + let indent = ' ' + let tokens = split(a:line, nr2char(1)) + if len(tokens) != 5 + return indent.substitute(a:line, '\s*$', '', '') + endif + let [graph, sha, refs, subject, date] = tokens + let tag = matchstr(refs, 'tag: [^,)]\+') + let tag = empty(tag) ? ' ' : ' ('.tag.') ' + return printf('%s%s%s%s%s (%s)', indent, graph, sha, tag, subject, date) +endfunction + +function! s:append_ul(lnum, text) + call append(a:lnum, ['', a:text, repeat('-', len(a:text))]) +endfunction + +function! s:diff() + call s:prepare() + call append(0, ['Collecting changes ...', '']) + let cnts = [0, 0] + let bar = '' + let total = filter(copy(g:plugs), 's:is_managed(v:key) && isdirectory(v:val.dir)') + call s:progress_bar(2, bar, len(total)) + for origin in [1, 0] + let plugs = reverse(sort(items(filter(copy(total), (origin ? '' : '!').'(has_key(v:val, "commit") || has_key(v:val, "tag"))')))) + if empty(plugs) + continue + endif + call s:append_ul(2, origin ? 'Pending updates:' : 'Last update:') + for [k, v] in plugs + let range = origin ? '..origin/'.v.branch : 'HEAD@{1}..' + let diff = s:system_chomp('git log --graph --color=never '.join(map(['--pretty=format:%x01%h%x01%d%x01%s%x01%cr', range], 's:shellesc(v:val)')), v.dir) + if !empty(diff) + let ref = has_key(v, 'tag') ? (' (tag: '.v.tag.')') : has_key(v, 'commit') ? (' '.v.commit) : '' + call append(5, extend(['', '- '.k.':'.ref], map(s:lines(diff), 's:format_git_log(v:val)'))) + let cnts[origin] += 1 + endif + let bar .= '=' + call s:progress_bar(2, bar, len(total)) + normal! 2G + redraw + endfor + if !cnts[origin] + call append(5, ['', 'N/A']) + endif + endfor + call setline(1, printf('%d plugin(s) updated.', cnts[0]) + \ . (cnts[1] ? printf(' %d plugin(s) have pending updates.', cnts[1]) : '')) + + if cnts[0] || cnts[1] + nnoremap :silent! call preview_commit() + nnoremap o :silent! call preview_commit() + endif + if cnts[0] + nnoremap X :call revert() + echo "Press 'X' on each block to revert the update" + endif + normal! gg + setlocal nomodifiable +endfunction + +function! s:revert() + if search('^Pending updates', 'bnW') + return + endif + + let name = s:find_name(line('.')) + if empty(name) || !has_key(g:plugs, name) || + \ input(printf('Revert the update of %s? (y/N) ', name)) !~? '^y' + return + endif + + call s:system('git reset --hard HEAD@{1} && git checkout '.s:esc(g:plugs[name].branch).' --', g:plugs[name].dir) + setlocal modifiable + normal! "_dap + setlocal nomodifiable + echo 'Reverted' +endfunction + +function! s:snapshot(force, ...) abort + call s:prepare() + setf vim + call append(0, ['" Generated by vim-plug', + \ '" '.strftime("%c"), + \ '" :source this file in vim to restore the snapshot', + \ '" or execute: vim -S snapshot.vim', + \ '', '', 'PlugUpdate!']) + 1 + let anchor = line('$') - 3 + let names = sort(keys(filter(copy(g:plugs), + \'has_key(v:val, "uri") && !has_key(v:val, "commit") && isdirectory(v:val.dir)'))) + for name in reverse(names) + let sha = s:system_chomp('git rev-parse --short HEAD', g:plugs[name].dir) + if !empty(sha) + call append(anchor, printf("silent! let g:plugs['%s'].commit = '%s'", name, sha)) + redraw + endif + endfor + + if a:0 > 0 + let fn = expand(a:1) + if filereadable(fn) && !(a:force || s:ask(a:1.' already exists. Overwrite?')) + return + endif + call writefile(getline(1, '$'), fn) + echo 'Saved as '.a:1 + silent execute 'e' s:esc(fn) + setf vim + endif +endfunction + +function! s:split_rtp() + return split(&rtp, '\\\@ -Original Author: Daniel Malea -License: Same terms as Vim itself (see |license|) - -INTRODUCTION *lldb-intro* - -The plugin provides an interface to the lldb debugger allowing for -convenient debugging sessions inside your favorite editor including -features such as breakpoints, stepping, watchpoints etc. - -The original plugin can be found here: - -http://llvm.org/svn/llvm-project/lldb/trunk/utils/vim-lldb/ - -Credit for pretty much all current functionality goes to the original -authors. Currently only minor modifications have been made to the -original plugin. - - -GETTING STARTED *lldb-start* - -To quickly get started compile (don't forget to compile with debugging -symbols) and start some program. Then open a source file belonging to the -program in vim and execute ':Lattach .' Then select some -line in the source file and execute ':Lbreakpoint' to set a breakpint at -the current line. - -Once the program reaches the specified breakpoint you will be able to -inspect state and step through the proram using the commands described below. - -COMMANDS *lldb-commands* - -The LLDB command interpreter is exposed to Vim's command mode using the -':L' prefix. Tab-completion is available and will cycle through commands. -Some commands have modified behaviour in Vim; for example, :Lbreakpoint -with no arguments will set a breakpoint at the current cursor, rather than -printing the standard help information for the LLDB command 'breakpoint'. - - *lldb-windows* - -In addition to the standard commands available under the LLDB interpreter, -there are also commands to display or hide informational debugger panes. - -Windows can be shown or hidden using the ':Lhide ' or ':Lshow ' -commands. - *lldb-:Lhide* -:Lhide [windowname] Hide informational debugger pane named 'windowname'. - - *lldb-:Lshow* -:Lshow [windowname] Show informational debugger pane named 'windowname'. - -Possible window name arguments to the Lhide and Lshow commands include: - - * backtrace - * breakpoints - * disassembly - * locals - * registers - * threads - *lldb-:Lattach* -:Lattach Attach to a process by name. - - *lldb-:Ldetach* -:Ldetach Detach from the current process. - - *lldb-:Ltarget* -:Ltarget [[create] executable] - Create a target with the specified executable. If - run with a single argument, that argument is assumed - to be a path to the executable to be debugged. - Otherwise, all arguments are passed into LLDB's command - interpreter. - - *lldb-:Lstart* -:Lstart Create a process by executing the current target - and wait for LLDB to attach. - - *lldb-:Lrun* -:Lrun Create a process by executing the current target - without waiting for LLDB to attach. - - *lldb-:Lcontinue* -:Lcontinue Continue execution of the process until the next - breakpoint is hit or the process exits. - - *lldb-:Lthread* -:Lthread Passes through to LLDB. See :Lhelp thread. - - *lldb-:Lstep* -:Lstep Step into the current function call. - - *lldb-:Lstepin* -:Lstepin Step into the current function call. - - *lldb-:Lstepinst* -:Lstepinst Step one instruction. - - *lldb-:Lstepinstover* -:Lstepinstover Step one instruction, but skip over jump or call - instructions. - - *lldb-:Lnext* -:Lnext Step to the next line. - - *lldb-:Lfinish* -:Lfinish Step out of the current function. - - *lldb-:Lbreakpoint* -:Lbreakpoint [args] When arguments are provided, the lldb breakpoint - command is invoked. If no arguments are provided, - a breakpoint at the location under the cursor. - - *lldb-:Lprint* - *lldb-:Lpo* - *lldb-:LpO* -:Lprint Aliases to the lldb print and po commands. Cursor -:Lpo word (cursor WORD for LpO) will be used when -:LpO expression omitted. - -MAPPINGS *lldb-mappings* - -There are no default mappings defined by the plugin. All commands described -above can be mapped by defining a respective variable: - - let g:lldb_map_Lframe = "f" - -This will map the Lframe command to "f". Other commands can be -mapped accordingly using 'lldb_map_' + . - - -LICENSE *lldb-license* - -Same as Vim itself. - -BUGS *lldb-bugs* - -If you run into a bug use the github issue tracker to report it: -http://github.com/gilligan/vim-lldb/issues/ - -CONTRIBUTING *lldb-contributing* - -If you want to help out you are more then welcome to do so. In fact -I am sure there are plenty of people out there that will do a better -job with this plugin than me. My C skills are more than rusty. I mostly -wanted to make this nice plugin more public and host it in a way in which -it is easy to install with your favorite plugin manager. - -Long story short: Bring on your forks and pull requests. - - vim:tw=78:sw=4:ft=help:norl: diff --git a/VimFiles/autoload/omni/common/debug.vim b/VimFiles/autoload/omni/common/debug.vim new file mode 100755 index 0000000..eded649 --- /dev/null +++ b/VimFiles/autoload/omni/common/debug.vim @@ -0,0 +1,32 @@ +" Description: Omni completion debug functions +" Maintainer: Vissale NEANG +" Last Change: 26 sept. 2007 + +let s:CACHE_DEBUG_TRACE = [] + +" Start debug, clear the debug file +function! omni#common#debug#Start() + let s:CACHE_DEBUG_TRACE = [] + call extend(s:CACHE_DEBUG_TRACE, ['============ Debug Start ============']) + call writefile(s:CACHE_DEBUG_TRACE, "Omni.dbg") +endfunc + +" End debug, write to debug file +function! omni#common#debug#End() + call extend(s:CACHE_DEBUG_TRACE, ["============= Debug End ============="]) + call extend(s:CACHE_DEBUG_TRACE, [""]) + call writefile(s:CACHE_DEBUG_TRACE, "Omni.dbg") +endfunc + +" Debug trace function +function! omni#common#debug#Trace(szFuncName, ...) + let szTrace = a:szFuncName + let paramNum = a:0 + if paramNum>0 + let szTrace .= ':' + endif + for i in range(paramNum) + let szTrace = szTrace .' ('. string(eval('a:'.string(i+1))).')' + endfor + call extend(s:CACHE_DEBUG_TRACE, [szTrace]) +endfunc diff --git a/VimFiles/autoload/omni/common/utils.vim b/VimFiles/autoload/omni/common/utils.vim new file mode 100755 index 0000000..c880ad2 --- /dev/null +++ b/VimFiles/autoload/omni/common/utils.vim @@ -0,0 +1,67 @@ +" Description: Omni completion utils +" Maintainer: Vissale NEANG +" Last Change: 26 sept. 2007 + +" For sort numbers in list +function! omni#common#utils#CompareNumber(i1, i2) + let num1 = eval(a:i1) + let num2 = eval(a:i2) + return num1 == num2 ? 0 : num1 > num2 ? 1 : -1 +endfunc + +" TagList function calling the vim taglist() with try catch +" The only throwed exception is 'TagList:UserInterrupt' +" We also force the noignorecase option to avoid linear search when calling +" taglist() +function! omni#common#utils#TagList(szTagQuery) + let result = [] + let bUserIgnoreCase = &ignorecase + " Forcing noignorecase search => binary search can be used in taglist() + " if tags in the tag file are sorted + if bUserIgnoreCase + set noignorecase + endif + try + let result = taglist(a:szTagQuery) + catch /^Vim:Interrupt$/ + " Restoring user's setting + if bUserIgnoreCase + set ignorecase + endif + throw 'TagList:UserInterrupt' + catch + "Note: it seems that ctags can generate corrupted files, in this case + "taglist() will fail to read the tagfile and an exception from + "has_add() is thrown + endtry + + " Restoring user's setting + if bUserIgnoreCase + set ignorecase + endif + return result +endfunc + +" Same as TagList but don't throw exception +function! omni#common#utils#TagListNoThrow(szTagQuery) + let result = [] + try + let result = omni#common#utils#TagList(a:szTagQuery) + catch + endtry + return result +endfunc + +" Get the word under the cursor +function! omni#common#utils#GetWordUnderCursor() + let szLine = getline('.') + let startPos = getpos('.')[2]-1 + let startPos = (startPos < 0)? 0 : startPos + if szLine[startPos] =~ '\w' + let startPos = searchpos('\<\w\+', 'cbn', line('.'))[1] - 1 + endif + + let startPos = (startPos < 0)? 0 : startPos + let szResult = matchstr(szLine, '\w\+', startPos) + return szResult +endfunc diff --git a/VimFiles/autoload/omni/cpp/complete.vim b/VimFiles/autoload/omni/cpp/complete.vim new file mode 100755 index 0000000..a7e4edc --- /dev/null +++ b/VimFiles/autoload/omni/cpp/complete.vim @@ -0,0 +1,569 @@ +" 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 diff --git a/VimFiles/autoload/omni/cpp/includes.vim b/VimFiles/autoload/omni/cpp/includes.vim new file mode 100755 index 0000000..10a89bc --- /dev/null +++ b/VimFiles/autoload/omni/cpp/includes.vim @@ -0,0 +1,126 @@ +" Description: Omni completion script for cpp files +" Maintainer: Vissale NEANG +" Last Change: 26 sept. 2007 + +let g:omni#cpp#includes#CACHE_INCLUDES = {} +let g:omni#cpp#includes#CACHE_FILE_TIME = {} + +let s:rePreprocIncludePart = '\C#\s*include\s*' +let s:reIncludeFilePart = '\(<\|"\)\(\f\|\s\)\+\(>\|"\)' +let s:rePreprocIncludeFile = s:rePreprocIncludePart . s:reIncludeFilePart + +" Get the include list of a file +function! omni#cpp#includes#GetList(...) + if a:0 > 0 + return s:GetIncludeListFromFile(a:1, (a:0 > 1)? a:2 : 0 ) + else + return s:GetIncludeListFromCurrentBuffer() + endif +endfunc + +" Get the include list from the current buffer +function! s:GetIncludeListFromCurrentBuffer() + let listIncludes = [] + let originalPos = getpos('.') + + call setpos('.', [0, 1, 1, 0]) + let curPos = [1,1] + let alreadyInclude = {} + while curPos != [0,0] + let curPos = searchpos('\C\(^'.s:rePreprocIncludeFile.'\)', 'W') + if curPos != [0,0] + let szLine = getline('.') + let startPos = curPos[1] + let endPos = matchend(szLine, s:reIncludeFilePart, startPos-1) + if endPos!=-1 + let szInclusion = szLine[startPos-1:endPos-1] + let szIncludeFile = substitute(szInclusion, '\('.s:rePreprocIncludePart.'\)\|[<>""]', '', 'g') + let szResolvedInclude = omni#cpp#utils#ResolveFilePath(szIncludeFile) + + " Protection over self inclusion + if szResolvedInclude != '' && szResolvedInclude != omni#cpp#utils#ResolveFilePath(getreg('%')) + let includePos = curPos + if !has_key(alreadyInclude, szResolvedInclude) + call extend(listIncludes, [{'pos' : includePos, 'include' : szResolvedInclude}]) + let alreadyInclude[szResolvedInclude] = 1 + endif + endif + endif + endif + endwhile + + call setpos('.', originalPos) + return listIncludes +endfunc + +" Get the include list from a file +function! s:GetIncludeListFromFile(szFilePath, bUpdate) + let listIncludes = [] + if a:szFilePath == '' + return listIncludes + endif + + if !a:bUpdate && has_key(g:omni#cpp#includes#CACHE_INCLUDES, a:szFilePath) + return copy(g:omni#cpp#includes#CACHE_INCLUDES[a:szFilePath]) + endif + + let g:omni#cpp#includes#CACHE_FILE_TIME[a:szFilePath] = getftime(a:szFilePath) + + let szFixedPath = escape(a:szFilePath, g:omni#cpp#utils#szEscapedCharacters) + execute 'silent! lvimgrep /\C\(^'.s:rePreprocIncludeFile.'\)/gj '.szFixedPath + + let listQuickFix = getloclist(0) + let alreadyInclude = {} + for qf in listQuickFix + let szLine = qf.text + let startPos = qf.col + let endPos = matchend(szLine, s:reIncludeFilePart, startPos-1) + if endPos!=-1 + let szInclusion = szLine[startPos-1:endPos-1] + let szIncludeFile = substitute(szInclusion, '\('.s:rePreprocIncludePart.'\)\|[<>""]', '', 'g') + let szResolvedInclude = omni#cpp#utils#ResolveFilePath(szIncludeFile) + + " Protection over self inclusion + if szResolvedInclude != '' && szResolvedInclude != a:szFilePath + let includePos = [qf.lnum, qf.col] + if !has_key(alreadyInclude, szResolvedInclude) + call extend(listIncludes, [{'pos' : includePos, 'include' : szResolvedInclude}]) + let alreadyInclude[szResolvedInclude] = 1 + endif + endif + endif + endfor + + let g:omni#cpp#includes#CACHE_INCLUDES[a:szFilePath] = listIncludes + + return copy(listIncludes) +endfunc + +" For debug purpose +function! omni#cpp#includes#Display() + let szPathBuffer = omni#cpp#utils#ResolveFilePath(getreg('%')) + call s:DisplayIncludeTree(szPathBuffer, 0) +endfunc + +" For debug purpose +function! s:DisplayIncludeTree(szFilePath, indent, ...) + let includeGuard = {} + if a:0 >0 + let includeGuard = a:1 + endif + let szFilePath = omni#cpp#utils#ResolveFilePath(a:szFilePath) + if has_key(includeGuard, szFilePath) + return + else + let includeGuard[szFilePath] = 1 + endif + + let szIndent = repeat(' ', a:indent) + echo szIndent . a:szFilePath + let incList = omni#cpp#includes#GetList(a:szFilePath) + for inc in incList + call s:DisplayIncludeTree(inc.include, a:indent+1, includeGuard) + endfor +endfunc + + diff --git a/VimFiles/autoload/omni/cpp/items.vim b/VimFiles/autoload/omni/cpp/items.vim new file mode 100755 index 0000000..b943ad4 --- /dev/null +++ b/VimFiles/autoload/omni/cpp/items.vim @@ -0,0 +1,660 @@ +" Description: Omni completion script for cpp files +" Maintainer: Vissale NEANG +" Last Change: 26 sept. 2007 + +" Build the item list of an instruction +" An item is an instruction between a -> or . or ->* or .* +" We can sort an item in different kinds: +" eg: ((MyClass1*)(pObject))->_memberOfClass1.get() ->show() +" | cast | | member | | method | | method | +" @return a list of item +" an item is a dictionnary where keys are: +" tokens = list of token +" kind = itemVariable|itemCast|itemCppCast|itemTemplate|itemFunction|itemUnknown|itemThis|itemScope +function! omni#cpp#items#Get(tokens, ...) + let bGetWordUnderCursor = (a:0>0)? a:1 : 0 + + let result = [] + let itemsDelimiters = ['->', '.', '->*', '.*'] + + let tokens = reverse(omni#cpp#utils#BuildParenthesisGroups(a:tokens)) + + " fsm states: + " 0 = initial state + " TODO: add description of fsm states + let state=(bGetWordUnderCursor)? 1 : 0 + let item = {'tokens' : [], 'kind' : 'itemUnknown'} + let parenGroup=-1 + for token in tokens + if state==0 + if index(itemsDelimiters, token.value)>=0 + let item = {'tokens' : [], 'kind' : 'itemUnknown'} + let state = 1 + elseif token.value=='::' + let state = 9 + let item.kind = 'itemScope' + " Maybe end of tokens + elseif token.kind =='cppOperatorPunctuator' + " If it's a cppOperatorPunctuator and the current token is not + " a itemsDelimiters or '::' we can exit + let state=-1 + break + endif + elseif state==1 + call insert(item.tokens, token) + if token.kind=='cppWord' + " It's an attribute member or a variable + let item.kind = 'itemVariable' + let state = 2 + " Maybe end of tokens + elseif token.value=='this' + let item.kind = 'itemThis' + let state = 2 + " Maybe end of tokens + elseif token.value==')' + let parenGroup = token.group + let state = 3 + elseif token.value==']' + let parenGroup = token.group + let state = 4 + elseif token.kind == 'cppDigit' + let state = -1 + break + endif + elseif state==2 + if index(itemsDelimiters, token.value)>=0 + call insert(result, item) + let item = {'tokens' : [], 'kind' : 'itemUnknown'} + let state = 1 + elseif token.value == '::' + call insert(item.tokens, token) + " We have to get namespace or classscope + let state = 8 + " Maybe end of tokens + else + call insert(result, item) + let state=-1 + break + endif + elseif state==3 + call insert(item.tokens, token) + if token.value=='(' && token.group == parenGroup + let state = 5 + " Maybe end of tokens + endif + elseif state==4 + call insert(item.tokens, token) + if token.value=='[' && token.group == parenGroup + let state = 1 + endif + elseif state==5 + if token.kind=='cppWord' + " It's a function or method + let item.kind = 'itemFunction' + call insert(item.tokens, token) + let state = 2 + " Maybe end of tokens + elseif token.value == '>' + " Maybe a cpp cast or template + let item.kind = 'itemTemplate' + call insert(item.tokens, token) + let parenGroup = token.group + let state = 6 + else + " Perhaps it's a C cast eg: ((void*)(pData)) or a variable eg:(*pData) + let item.kind = omni#cpp#utils#GetCastType(item.tokens) + let state=-1 + call insert(result, item) + break + endif + elseif state==6 + call insert(item.tokens, token) + if token.value == '<' && token.group == parenGroup + " Maybe a cpp cast or template + let state = 7 + endif + elseif state==7 + call insert(item.tokens, token) + if token.kind=='cppKeyword' + " It's a cpp cast + let item.kind = omni#cpp#utils#GetCastType(item.tokens) + let state=-1 + call insert(result, item) + break + else + " Template ? + let state=-1 + call insert(result, item) + break + endif + elseif state==8 + if token.kind=='cppWord' + call insert(item.tokens, token) + let state = 2 + " Maybe end of tokens + else + let state=-1 + call insert(result, item) + break + endif + elseif state==9 + if token.kind == 'cppWord' + call insert(item.tokens, token) + let state = 10 + " Maybe end of tokens + else + let state=-1 + call insert(result, item) + break + endif + elseif state==10 + if token.value == '::' + call insert(item.tokens, token) + let state = 9 + " Maybe end of tokens + else + let state=-1 + call insert(result, item) + break + endif + endif + endfor + + if index([2, 5, 8, 9, 10], state)>=0 + if state==5 + let item.kind = omni#cpp#utils#GetCastType(item.tokens) + endif + call insert(result, item) + endif + + return result +endfunc + +" Resolve type information of items +" @param namespaces: list of namespaces used in the file +" @param szCurrentClassScope: the current class scope, only used for the first +" item to detect if this item is a class member (attribute, method) +" @param items: list of item, can be an empty list @see GetItemsToComplete +function! omni#cpp#items#ResolveItemsTypeInfo(contextStack, items) + " Note: kind = itemVariable|cCast|cppCast|template|function|itemUnknown|this + " For the first item, if it's a variable we try to detect the type of the + " variable with the function searchdecl. If it fails, thanks to the + " current class scope, we try to detect if the variable is an attribute + " member. + " If the kind of the item is a function, we have to first check if the + " function is a method of the class, if it fails we try to get a match in + " the global namespace. After that we get the returned type of the + " function. + " It the kind is a C cast or C++ cast, there is no problem, it's the + " easiest case. We just extract the type of the cast. + + let szCurrentContext = '' + let typeInfo = {} + " Note: We search the decl only for the first item + let bSearchDecl = 1 + for item in a:items + let curItem = item + if index(['itemVariable', 'itemFunction'], curItem.kind)>=0 + " Note: a variable can be : MyNs::MyClass::_var or _var or (*pVar) + " or _var[0][0] + let szSymbol = s:GetSymbol(curItem.tokens) + + " If we have MyNamespace::myVar + " We add MyNamespace in the context stack set szSymbol to myVar + if match(szSymbol, '::\w\+$') >= 0 + let szCurrentContext = substitute(szSymbol, '::\w\+$', '', 'g') + let szSymbol = matchstr(szSymbol, '\w\+$') + endif + let tmpContextStack = a:contextStack + if szCurrentContext != '' + let tmpContextStack = [szCurrentContext] + a:contextStack + endif + + if curItem.kind == 'itemVariable' + let typeInfo = s:GetTypeInfoOfVariable(tmpContextStack, szSymbol, bSearchDecl) + else + let typeInfo = s:GetTypeInfoOfReturnedType(tmpContextStack, szSymbol) + endif + + elseif curItem.kind == 'itemThis' + if len(a:contextStack) + let typeInfo = omni#cpp#utils#CreateTypeInfo(substitute(a:contextStack[0], '^::', '', 'g')) + endif + elseif curItem.kind == 'itemCast' + let typeInfo = omni#cpp#utils#CreateTypeInfo(s:ResolveCCast(curItem.tokens)) + elseif curItem.kind == 'itemCppCast' + let typeInfo = omni#cpp#utils#CreateTypeInfo(s:ResolveCppCast(curItem.tokens)) + elseif curItem.kind == 'itemScope' + let typeInfo = omni#cpp#utils#CreateTypeInfo(substitute(s:TokensToString(curItem.tokens), '\s', '', 'g')) + endif + + if omni#cpp#utils#IsTypeInfoValid(typeInfo) + let szCurrentContext = omni#cpp#utils#GetTypeInfoString(typeInfo) + endif + let bSearchDecl = 0 + endfor + + return typeInfo +endfunc + +" Get symbol name +function! s:GetSymbol(tokens) + let szSymbol = '' + let state = 0 + for token in a:tokens + if state == 0 + if token.value == '::' + let szSymbol .= token.value + let state = 1 + elseif token.kind == 'cppWord' + let szSymbol .= token.value + let state = 2 + " Maybe end of token + endif + elseif state == 1 + if token.kind == 'cppWord' + let szSymbol .= token.value + let state = 2 + " Maybe end of token + else + " Error + break + endif + elseif state == 2 + if token.value == '::' + let szSymbol .= token.value + let state = 1 + else + break + endif + endif + endfor + return szSymbol +endfunc + +" Search a declaration. +" eg: std::map +" can be empty +" Note: The returned type info can be a typedef +" The typedef resolution is done later +" @return +" - a dictionnary where keys are +" - type: the type of value same as type() +" - value: the value +function! s:GetTypeInfoOfVariable(contextStack, szVariable, bSearchDecl) + let result = {} + + if a:bSearchDecl + " Search type of declaration + "let result = s:SearchTypeInfoOfDecl(a:szVariable) + let result = s:SearchDecl(a:szVariable) + endif + + if result=={} + let szFilter = "index(['m', 'v'], v:val.kind[0])>=0" + let tagItem = s:ResolveSymbol(a:contextStack, a:szVariable, szFilter) + if tagItem=={} + return result + endif + + let szCmdWithoutVariable = substitute(omni#cpp#utils#ExtractCmdFromTagItem(tagItem), '\C\<'.a:szVariable.'\>.*', '', 'g') + let tokens = omni#cpp#tokenizer#Tokenize(omni#cpp#utils#GetCodeFromLine(szCmdWithoutVariable)) + let result = omni#cpp#utils#CreateTypeInfo(omni#cpp#utils#ExtractTypeInfoFromTokens(tokens)) + " TODO: Namespace resolution for result + + if result != {} && result.value=='' + " result.value=='' + " eg: + " struct + " { + " }gVariable; + if has_key(tagItem, 'typeref') + " Maybe the variable is a global var of an + " unnamed class, struct or union. + " eg: + " 1) + " struct + " { + " }gVariable; + " In this case we need the tags (the patched version) + " Note: We can have a named type like this: + " 2) + " class A + " { + " }gVariable; + if s:IsUnnamedType(tagItem) + " It's an unnamed type we are in the case 1) + let result = omni#cpp#utils#CreateTypeInfo(tagItem) + else + " It's not an unnamed type we are in the case 2) + + " eg: tagItem.typeref = 'struct:MY_STRUCT::MY_SUBSTRUCT' + let szTypeRef = substitute(tagItem.typeref, '^\w\+:', '', '') + + " eg: szTypeRef = 'MY_STRUCT::MY_SUBSTRUCT' + let result = omni#cpp#utils#CreateTypeInfo(szTypeRef) + endif + endif + endif + endif + return result +endfunc + +" Get the type info string from the returned type of function +function! s:GetTypeInfoOfReturnedType(contextStack, szFunctionName) + let result = {} + + let szFilter = "index(['f', 'p'], v:val.kind[0])>=0" + let tagItem = s:ResolveSymbol(a:contextStack, a:szFunctionName, szFilter) + + if tagItem != {} + let szCmdWithoutVariable = substitute(omni#cpp#utils#ExtractCmdFromTagItem(tagItem), '\C\<'.a:szFunctionName.'\>.*', '', 'g') + let tokens = omni#cpp#tokenizer#Tokenize(omni#cpp#utils#GetCodeFromLine(szCmdWithoutVariable)) + let result = omni#cpp#utils#CreateTypeInfo(omni#cpp#utils#ExtractTypeInfoFromTokens(tokens)) + " TODO: Namespace resolution for result + return result + endif + return result +endfunc + +" Resolve a symbol, return a tagItem +" Gets the first symbol found in the context stack +function! s:ResolveSymbol(contextStack, szSymbol, szTagFilter) + let tagItem = {} + for szCurrentContext in a:contextStack + if szCurrentContext != '::' + let szTagQuery = substitute(szCurrentContext, '^::', '', 'g').'::'.a:szSymbol + else + let szTagQuery = a:szSymbol + endif + + let tagList = omni#common#utils#TagListNoThrow('^'.szTagQuery.'$') + call filter(tagList, a:szTagFilter) + if len(tagList) + let tagItem = tagList[0] + break + endif + endfor + return tagItem +endfunc + +" Return if the tag item represent an unnamed type +function! s:IsUnnamedType(tagItem) + let bResult = 0 + if has_key(a:tagItem, 'typeref') + " Note: Thanks for __anon ! + let bResult = match(a:tagItem.typeref, '\C\<__anon') >= 0 + endif + return bResult +endfunc + +" Search the declaration of a variable and return the type info +function! s:SearchTypeInfoOfDecl(szVariable) + let szReVariable = '\C\<'.a:szVariable.'\>' + + let originalPos = getpos('.') + let origPos = originalPos[1:2] + let curPos = origPos + let stopPos = origPos + + while curPos !=[0,0] + " We go to the start of the current scope + let curPos = searchpairpos('{', '', '}', 'bW', g:omni#cpp#utils#expIgnoreComments) + if curPos != [0,0] + let matchPos = curPos + " Now want to search our variable but we don't want to go in child + " scope + while matchPos != [0,0] + let matchPos = searchpos('{\|'.szReVariable, 'W', stopPos[0]) + if matchPos != [0,0] + " We ignore matches under comment + if omni#cpp#utils#IsCursorInCommentOrString() + continue + endif + + " Getting the current line + let szLine = getline('.') + if match(szLine, szReVariable)>=0 + " We found our variable + " Check if the current instruction is a decl instruction + let tokens = omni#cpp#utils#TokenizeCurrentInstruction() + let szTypeInfo = s:ExtractTypeInfoFromDecl(tokens) + if szTypeInfo != '' + call setpos('.', originalPos) + return omni#cpp#utils#CreateTypeInfo(szTypeInfo) + endif + else + " We found a child scope, we don't want to go in, thus + " we search for the end } of this child scope + let bracketEnd = searchpairpos('{', '', '}', 'nW', g:omni#cpp#utils#expIgnoreComments) + if bracketEnd == [0,0] + break + endif + + if bracketEnd[0] >= stopPos[0] + " The end of the scope is after our cursor we stop + " the search + break + else + " We move the cursor and continue to search our + " variable + call setpos('.', [0, bracketEnd[0], bracketEnd[1], 0]) + endif + endif + endif + endwhile + + " Backing to the start of the scope + call setpos('.', [0,curPos[0], curPos[1], 0]) + let stopPos = curPos + endif + endwhile + + let result = {} + if s:LocalSearchDecl(a:szVariable)==0 && !omni#cpp#utils#IsCursorInCommentOrString() + let tokens = omni#cpp#utils#TokenizeCurrentInstruction() + let szTypeInfo = s:ExtractTypeInfoFromDecl(tokens) + if szTypeInfo != '' + let result = omni#cpp#utils#CreateTypeInfo(szTypeInfo) + endif + endif + + call setpos('.', originalPos) + + return result +endfunc + +" Search a declaration +" @return +" - tokens of the current instruction if success +" - empty list if failure +function! s:SearchDecl(szVariable) + let result = {} + let originalPos = getpos('.') + let searchResult = s:LocalSearchDecl(a:szVariable) + if searchResult==0 + " searchdecl() may detect a decl if the variable is in a conditional + " instruction (if, elseif, while etc...) + " We have to check if the detected decl is really a decl instruction + let tokens = omni#cpp#utils#TokenizeCurrentInstruction() + + for token in tokens + " Simple test + if index(['if', 'elseif', 'while', 'for', 'switch'], token.value)>=0 + " Invalid declaration instruction + call setpos('.', originalPos) + return result + endif + endfor + + let szTypeInfo = s:ExtractTypeInfoFromDecl(tokens) + if szTypeInfo != '' + let result = omni#cpp#utils#CreateTypeInfo(szTypeInfo) + endif + endif + call setpos('.', originalPos) + return result +endfunc + +" Extract the type info string from an instruction. +" We use a small parser to extract the type +" We parse the code according to a C++ BNF from: http://www.nongnu.org/hcb/#basic.link +" @param tokens: token list of the current instruction +function! s:ExtractTypeInfoFromDecl(tokens) + return omni#cpp#utils#ExtractTypeInfoFromTokens(a:tokens) +endfunc + +" Convert tokens to string +function! s:TokensToString(tokens) + let result = '' + for token in a:tokens + let result = result . token.value . ' ' + endfor + return result[:-2] +endfunc + +" Resolve a cast. +" Resolve a C++ cast +" @param list of token. tokens must be a list that represents +" a cast expression (C++ cast) the function does not control +" if it's a cast or not +" eg: static_cast(something) +" @return type info string +function! s:ResolveCppCast(tokens) + return omni#cpp#utils#ExtractTypeInfoFromTokens(s:ResolveCast(a:tokens, '<', '>')) +endfunc + +" Resolve a cast. +" Resolve a C cast +" @param list of token. tokens must be a list that represents +" a cast expression (C cast) the function does not control +" if it's a cast or not +" eg: (MyClass*)something +" @return type info string +function! s:ResolveCCast(tokens) + return omni#cpp#utils#ExtractTypeInfoFromTokens(s:ResolveCast(a:tokens, '(', ')')) +endfunc + +" Resolve a cast. +" Resolve a C cast +" @param list of token. tokens must be a list that represents +" a cast expression (C cast) the function does not control +" if it's a cast or not +" eg: (MyClass*)something +" @return type tokens +function! s:ResolveCast(tokens, startChar, endChar) + let tokens = omni#cpp#utils#BuildParenthesisGroups(a:tokens) + + " We remove useless parenthesis eg: (((MyClass))) + let tokens = omni#cpp#utils#SimplifyParenthesis(tokens) + + let countItem=0 + let startIndex = -1 + let endIndex = -1 + let i = 0 + for token in tokens + if startIndex==-1 + if token.value==a:startChar + let countItem += 1 + let startIndex = i + endif + else + if token.value==a:startChar + let countItem += 1 + elseif token.value==a:endChar + let countItem -= 1 + endif + + if countItem==0 + let endIndex = i + break + endif + endif + let i+=1 + endfor + + return tokens[startIndex+1 : endIndex-1] +endfunc + +" Replacement for build-in function 'searchdecl' +" It does not require that the upper-level bracket is in the first column. +" Otherwise it should be equal to 'searchdecl(name, 0, 1)' +" @param name: name of variable to find declaration for +function! s:LocalSearchDecl(name) + + if g:OmniCpp_LocalSearchDecl == 0 + let bUserIgnoreCase = &ignorecase + + " Forcing the noignorecase option + " avoid bug when, for example, if we have a declaration like this : "A a;" + set noignorecase + + let result = searchdecl(a:name, 0, 1) + + " Restoring user's setting + let &ignorecase = bUserIgnoreCase + + return result + endif + + let lastpos = getpos('.') + let winview = winsaveview() + let lastfoldenable = &foldenable + let &foldenable = 0 + + " We add \C (noignorecase) to + " avoid bug when, for example, if we have a declaration like this : "A a;" + let varname = "\\C\\<" . a:name . "\\>" + + " Go to first blank line before begin of highest scope + normal 99[{ + let scopepos = getpos('.') + while (line('.') > 1) && (len(split(getline('.'))) > 0) + call cursor(line('.')-1, 0) + endwhile + + let declpos = [ 0, 0, 0, 0 ] + while search(varname, '', scopepos[1]) > 0 + " Check if we are a string or a comment + if omni#cpp#utils#IsCursorInCommentOrString() + continue + endif + + " Remember match + let declpos = getpos('.') + endwhile + if declpos[1] != 0 + " We found a match + call winrestview(winview) + call setpos('.', declpos) + let &foldenable = lastfoldenable + return 0 + endif + + while search(varname, '', lastpos[1]) > 0 + " Check if current scope is ending before variable + let old_cur = getpos('.') + normal ]} + let new_cur = getpos('.') + call setpos('.', old_cur) + if (new_cur[1] < lastpos[1]) || ((new_cur[1] == lastpos[1]) && (new_cur[2] < lastpos[2])) + continue + endif + + " Check if we are a string or a comment + if omni#cpp#utils#IsCursorInCommentOrString() + continue + endif + + " We found match + call winrestview(winview) + call setpos('.', old_cur) + let &foldenable = lastfoldenable + return 0 + endwhile + + " No match found. + call winrestview(winview) + let &foldenable = lastfoldenable + return 1 +endfunc diff --git a/VimFiles/autoload/omni/cpp/maycomplete.vim b/VimFiles/autoload/omni/cpp/maycomplete.vim new file mode 100755 index 0000000..610526b --- /dev/null +++ b/VimFiles/autoload/omni/cpp/maycomplete.vim @@ -0,0 +1,82 @@ +" Description: Omni completion script for cpp files +" Maintainer: Vissale NEANG +" Last Change: 26 sept. 2007 + +" Check if we can use omni completion in the current buffer +function! s:CanUseOmnicompletion() + " For C and C++ files and only if the omnifunc is omni#cpp#complete#Main + return (index(['c', 'cpp'], &filetype)>=0 && &omnifunc == 'omni#cpp#complete#Main' && !omni#cpp#utils#IsCursorInCommentOrString()) +endfunc + +" Return the mapping of omni completion +function! omni#cpp#maycomplete#Complete() + let szOmniMapping = "\\" + + " 0 = don't select first item + " 1 = select first item (inserting it to the text, default vim behaviour) + " 2 = select first item (without inserting it to the text) + if g:OmniCpp_SelectFirstItem == 0 + " We have to force the menuone option to avoid confusion when there is + " only one popup item + set completeopt-=menu + set completeopt+=menuone + let szOmniMapping .= "\" + elseif g:OmniCpp_SelectFirstItem == 2 + " We have to force the menuone option to avoid confusion when there is + " only one popup item + set completeopt-=menu + set completeopt+=menuone + let szOmniMapping .= "\" + let szOmniMapping .= "\=pumvisible() ? \"\\\" : \"\"\" + endif + return szOmniMapping +endfunc + +" May complete function for dot +function! omni#cpp#maycomplete#Dot() + if s:CanUseOmnicompletion() && g:OmniCpp_MayCompleteDot + let g:omni#cpp#items#data = omni#cpp#items#Get(omni#cpp#utils#TokenizeCurrentInstruction('.')) + if len(g:omni#cpp#items#data) + let s:bMayComplete = 1 + return '.' . omni#cpp#maycomplete#Complete() + endif + endif + return '.' +endfunc +" May complete function for arrow +function! omni#cpp#maycomplete#Arrow() + if s:CanUseOmnicompletion() && g:OmniCpp_MayCompleteArrow + let index = col('.') - 2 + if index >= 0 + let char = getline('.')[index] + if char == '-' + let g:omni#cpp#items#data = omni#cpp#items#Get(omni#cpp#utils#TokenizeCurrentInstruction('>')) + if len(g:omni#cpp#items#data) + let s:bMayComplete = 1 + return '>' . omni#cpp#maycomplete#Complete() + endif + endif + endif + endif + return '>' +endfunc + +" May complete function for double points +function! omni#cpp#maycomplete#Scope() + if s:CanUseOmnicompletion() && g:OmniCpp_MayCompleteScope + let index = col('.') - 2 + if index >= 0 + let char = getline('.')[index] + if char == ':' + let g:omni#cpp#items#data = omni#cpp#items#Get(omni#cpp#utils#TokenizeCurrentInstruction(':')) + if len(g:omni#cpp#items#data) + if len(g:omni#cpp#items#data[-1].tokens) && g:omni#cpp#items#data[-1].tokens[-1].value != '::' + let s:bMayComplete = 1 + return ':' . omni#cpp#maycomplete#Complete() + endif + endif + endif + endif + endif + return ':' +endfunc diff --git a/VimFiles/autoload/omni/cpp/namespaces.vim b/VimFiles/autoload/omni/cpp/namespaces.vim new file mode 100755 index 0000000..386b3f9 --- /dev/null +++ b/VimFiles/autoload/omni/cpp/namespaces.vim @@ -0,0 +1,838 @@ +" Description: Omni completion script for cpp files +" Maintainer: Vissale NEANG +" Last Change: 26 sept. 2007 + +let g:omni#cpp#namespaces#CacheResolve = {} +let g:omni#cpp#namespaces#CacheUsing = {} +" TODO: For the next release +"let g:omni#cpp#namespaces#CacheAlias = {} + +" Get the using namespace list from a line +function! s:GetNamespaceAliasListFromLine(szLine) + let result = {} + let tokens = omni#cpp#tokenizer#Tokenize(a:szLine) + let szAlias = '' + let szNamespace = '' + let state = 0 + for token in tokens + if state==0 + let szAlias = '' + let szNamespace = '' + if token.value == '/*' + let state = 1 + elseif token.value == '//' + " It's a comment + let state = -1 + break + elseif token.value == 'namespace' + let state = 2 + endif + elseif state==1 + if token.value == '*/' + let state=0 + endif + elseif state==2 + if token.kind == 'cppWord' + let szAlias .= token.value + let state = 3 + else + let state = -1 + break + endif + elseif state == 3 + if token.value == '=' + let state = 4 + else + let state = -1 + break + endif + elseif state == 4 + if token.value == '::' + let szNamespace .= token.value + let state = 5 + elseif token.kind == 'cppWord' + let szNamespace .= token.value + let state = 6 + " Maybe end of tokens + endif + elseif state==5 + if token.kind == 'cppWord' + let szNamespace .= token.value + let state = 6 + " Maybe end of tokens + else + " Error, we can't have 'namespace ALIAS = Something::' + let state = -1 + break + endif + elseif state==6 + if token.value == '::' + let szNamespace .= token.value + let state = 5 + else + call extend(result, {szAlias : szNamespace}) + let state = 0 + endif + endif + endfor + + if state == 6 + call extend(result, {szAlias : szNamespace}) + endif + + return result +endfunc + +" Get the using namespace list from a line +function! s:GetNamespaceListFromLine(szLine) + let result = [] + let tokens = omni#cpp#tokenizer#Tokenize(a:szLine) + let szNamespace = '' + let state = 0 + for token in tokens + if state==0 + let szNamespace = '' + if token.value == '/*' + let state = 1 + elseif token.value == '//' + " It's a comment + let state = -1 + break + elseif token.value == 'using' + let state = 2 + endif + elseif state==1 + if token.value == '*/' + let state=0 + endif + elseif state==2 + if token.value == 'namespace' + let state = 3 + else + " Error, 'using' must be followed by 'namespace' + let state = -1 + break + endif + elseif state==3 + if token.value == '::' + let szNamespace .= token.value + let state = 4 + elseif token.kind == 'cppWord' + let szNamespace .= token.value + let state = 5 + " Maybe end of tokens + endif + elseif state==4 + if token.kind == 'cppWord' + let szNamespace .= token.value + let state = 5 + " Maybe end of tokens + else + " Error, we can't have 'using namespace Something::' + let state = -1 + break + endif + elseif state==5 + if token.value == '::' + let szNamespace .= token.value + let state = 4 + else + call extend(result, [szNamespace]) + let state = 0 + endif + endif + endfor + + if state == 5 + call extend(result, [szNamespace]) + endif + + return result +endfunc + +" Get the namespace list from a namespace map +function! s:GetUsingNamespaceListFromMap(namespaceMap, ...) + let stopLine = 0 + if a:0>0 + let stopLine = a:1 + endif + + let result = [] + let keys = sort(keys(a:namespaceMap), 'omni#common#utils#CompareNumber') + for i in keys + if stopLine != 0 && i > stopLine + break + endif + call extend(result, a:namespaceMap[i]) + endfor + return result +endfunc + +" Get global using namespace list from the current buffer +function! omni#cpp#namespaces#GetListFromCurrentBuffer(...) + let namespaceMap = s:GetAllUsingNamespaceMapFromCurrentBuffer() + let result = [] + if namespaceMap != {} + let result = s:GetUsingNamespaceListFromMap(namespaceMap, (a:0 > 0)? a:1 : line('.')) + endif + return result +endfunc + +" Get global using namespace map from the current buffer and include files recursively +function! s:GetAllUsingNamespaceMapFromCurrentBuffer(...) + let includeGuard = (a:0>0)? a:1 : {} + + let szBufferName = getreg("%") + let szFilePath = omni#cpp#utils#ResolveFilePath(szBufferName) + let szFilePath = (szFilePath=='')? szBufferName : szFilePath + + let namespaceMap = {} + if has_key(includeGuard, szFilePath) + return namespaceMap + else + let includeGuard[szFilePath] = 1 + endif + + let namespaceMap = omni#cpp#namespaces#GetMapFromCurrentBuffer() + + if g:OmniCpp_NamespaceSearch != 2 + " We don't search included files if OmniCpp_NamespaceSearch != 2 + return namespaceMap + endif + + for inc in omni#cpp#includes#GetList() + let lnum = inc.pos[0] + let tmpMap = s:GetAllUsingNamespaceMapFromFile(inc.include, includeGuard) + if tmpMap != {} + if has_key(namespaceMap, lnum) + call extend(namespaceMap[lnum], s:GetUsingNamespaceListFromMap(tmpMap)) + else + let namespaceMap[lnum] = s:GetUsingNamespaceListFromMap(tmpMap) + endif + endif + endfor + + return namespaceMap +endfunc + +" Get global using namespace map from a file and include files recursively +function! s:GetAllUsingNamespaceMapFromFile(szFilePath, ...) + let includeGuard = {} + if a:0 >0 + let includeGuard = a:1 + endif + + let szFilePath = omni#cpp#utils#ResolveFilePath(a:szFilePath) + let szFilePath = (szFilePath=='')? a:szFilePath : szFilePath + + let namespaceMap = {} + if has_key(includeGuard, szFilePath) + return namespaceMap + else + let includeGuard[szFilePath] = 1 + endif + + " If g:OmniCpp_NamespaceSearch == 1 (search namespaces only in the current + " buffer) we don't use cache for the current buffer + let namespaceMap = omni#cpp#namespaces#GetMapFromBuffer(szFilePath, g:OmniCpp_NamespaceSearch==1) + + if g:OmniCpp_NamespaceSearch != 2 + " We don't search included files if OmniCpp_NamespaceSearch != 2 + return namespaceMap + endif + + for inc in omni#cpp#includes#GetList(szFilePath) + let lnum = inc.pos[0] + let tmpMap = s:GetAllUsingNamespaceMapFromFile(inc.include, includeGuard) + if tmpMap != {} + if has_key(namespaceMap, lnum) + call extend(namespaceMap[lnum], s:GetUsingNamespaceListFromMap(tmpMap)) + else + let namespaceMap[lnum] = s:GetUsingNamespaceListFromMap(tmpMap) + endif + endif + endfor + + return namespaceMap +endfunc + +" Get global using namespace map from a the current buffer +function! omni#cpp#namespaces#GetMapFromCurrentBuffer() + let namespaceMap = {} + let originalPos = getpos('.') + + call setpos('.', [0, 1, 1, 0]) + let curPos = [1,1] + while curPos != [0,0] + let curPos = searchpos('\C^using\s\+namespace', 'W') + if curPos != [0,0] + let szLine = getline('.') + let startPos = curPos[1] + let endPos = match(szLine, ';', startPos-1) + if endPos!=-1 + " We get the namespace list from the line + let namespaceMap[curPos[0]] = s:GetNamespaceListFromLine(szLine) + endif + endif + endwhile + + call setpos('.', originalPos) + return namespaceMap +endfunc + +" Get global using namespace map from a file +function! omni#cpp#namespaces#GetMapFromBuffer(szFilePath, ...) + let bUpdate = 0 + if a:0 > 0 + let bUpdate = a:1 + endif + + let szFilePath = omni#cpp#utils#ResolveFilePath(a:szFilePath) + let szFilePath = (szFilePath=='')? a:szFilePath : szFilePath + + if !bUpdate && has_key(g:omni#cpp#namespaces#CacheUsing, szFilePath) + return copy(g:omni#cpp#namespaces#CacheUsing[szFilePath]) + endif + + let namespaceMap = {} + " The file exists, we get the global namespaces in this file + let szFixedPath = escape(szFilePath, g:omni#cpp#utils#szEscapedCharacters) + execute 'silent! lvimgrep /\C^using\s\+namespace/gj '.szFixedPath + + " key = line number + " value = list of namespaces + let listQuickFix = getloclist(0) + for qf in listQuickFix + let szLine = qf.text + let startPos = qf.col + let endPos = match(szLine, ';', startPos-1) + if endPos!=-1 + " We get the namespace list from the line + let namespaceMap[qf.lnum] = s:GetNamespaceListFromLine(szLine) + endif + endfor + + if szFixedPath != '' + let g:omni#cpp#namespaces#CacheUsing[szFixedPath] = namespaceMap + endif + + return copy(namespaceMap) +endfunc + +" Get the stop position when searching for local variables +function! s:GetStopPositionForLocalSearch() + " Stop position when searching a local variable + let originalPos = getpos('.') + let origPos = originalPos[1:2] + let stopPosition = origPos + let curPos = origPos + while curPos !=[0,0] + let stopPosition = curPos + let curPos = searchpairpos('{', '', '}', 'bW', g:omni#cpp#utils#expIgnoreComments) + endwhile + call setpos('.', originalPos) + + return stopPosition +endfunc + +" Get namespaces alias used at the cursor postion in a vim buffer +" Note: The result depends on the current cursor position +" @return +" - Map of namespace alias +function! s:GetNamespaceAliasMap() + " We store the cursor position because searchpairpos() moves the cursor + let result = {} + let originalPos = getpos('.') + let origPos = originalPos[1:2] + + let stopPos = s:GetStopPositionForLocalSearch() + let stopLine = stopPos[0] + let curPos = origPos + let lastLine = 0 + let nextStopLine = origPos[0] + let szReAlias = '\Cnamespace\s\+\w\+\s\+=' + while curPos !=[0,0] + let curPos = searchpos('}\|\('. szReAlias .'\)', 'bW',stopLine) + if curPos!=[0,0] && curPos[0]!=lastLine + let lastLine = curPos[0] + + let szLine = getline('.') + if origPos[0] == curPos[0] + " We get the line until cursor position + let szLine = szLine[:origPos[1]] + endif + + let szLine = omni#cpp#utils#GetCodeFromLine(szLine) + if match(szLine, szReAlias)<0 + " We found a '}' + let curPos = searchpairpos('{', '', '}', 'bW', g:omni#cpp#utils#expIgnoreComments) + else + " We get the namespace alias from the line + call extend(result, s:GetNamespaceAliasListFromLine(szLine)) + let nextStopLine = curPos[0] + endif + endif + endwhile + + " Setting the cursor to the original position + call setpos('.', originalPos) + + call s:ResolveAliasKeys(result) + return result +endfunc + +" Resolve an alias +" eg: namespace IAmAnAlias1 = Ns1 +" eg: namespace IAmAnAlias2 = IAmAnAlias1::Ns2 +" => IAmAnAlias2 = Ns1::Ns2 +function! s:ResolveAliasKey(mapNamespaceAlias, szAlias) + let szResult = a:mapNamespaceAlias[a:szAlias] + " ::Ns1::Ns2::Ns3 => ['Ns1', 'Ns2', 'Ns3'] + let listNamespace = split(szResult, '::') + if len(listNamespace) + " szBeginPart = 'Ns1' + let szBeginPart = remove(listNamespace, 0) + + " Is 'Ns1' an alias ? + if has_key(a:mapNamespaceAlias, szBeginPart) && szBeginPart != a:szAlias + " Resolving alias 'Ns1' + " eg: Ns1 = NsResolved + let szResult = s:ResolveAliasKey(a:mapNamespaceAlias, szBeginPart) + " szEndPart = 'Ns2::Ns3' + let szEndPart = join(listNamespace, '::') + if szEndPart != '' + " Concatenation => szResult = 'NsResolved::Ns2::Ns3' + let szResult .= '::' . szEndPart + endif + endif + endif + return szResult +endfunc + +" Resolve all keys in the namespace alias map +function! s:ResolveAliasKeys(mapNamespaceAlias) + let mapNamespaceAlias = a:mapNamespaceAlias + call map(mapNamespaceAlias, 's:ResolveAliasKey(mapNamespaceAlias, v:key)') +endfunc + +" Resolve namespace alias +function! omni#cpp#namespaces#ResolveAlias(mapNamespaceAlias, szNamespace) + let szResult = a:szNamespace + " ::Ns1::Ns2::Ns3 => ['Ns1', 'Ns2', 'Ns3'] + let listNamespace = split(a:szNamespace, '::') + if len(listNamespace) + " szBeginPart = 'Ns1' + let szBeginPart = remove(listNamespace, 0) + + " Is 'Ns1' an alias ? + if has_key(a:mapNamespaceAlias, szBeginPart) + " Resolving alias 'Ns1' + " eg: Ns1 = NsResolved + let szResult = a:mapNamespaceAlias[szBeginPart] + " szEndPart = 'Ns2::Ns3' + let szEndPart = join(listNamespace, '::') + if szEndPart != '' + " Concatenation => szResult = 'NsResolved::Ns2::Ns3' + let szResult .= '::' . szEndPart + endif + + " If a:szNamespace starts with '::' we add '::' to the beginning + " of the result + if match(a:szNamespace, '^::')>=0 + let szResult = omni#cpp#utils#SimplifyScope('::' . szResult) + endif + endif + endif + return szResult +endfunc + +" Resolve namespace alias +function! s:ResolveAliasInNamespaceList(mapNamespaceAlias, listNamespaces) + call map(a:listNamespaces, 'omni#cpp#namespaces#ResolveAlias(a:mapNamespaceAlias, v:val)') +endfunc + +" Get namespaces used at the cursor postion in a vim buffer +" Note: The result depends on the current cursor position +" @return +" - List of namespace used in the reverse order +function! omni#cpp#namespaces#GetUsingNamespaces() + " We have to get local using namespace declarations + " We need the current cursor position and the position of the start of the + " current scope + + " We store the cursor position because searchpairpos() moves the cursor + let result = [] + let originalPos = getpos('.') + let origPos = originalPos[1:2] + + let stopPos = s:GetStopPositionForLocalSearch() + + let stopLine = stopPos[0] + let curPos = origPos + let lastLine = 0 + let nextStopLine = origPos[0] + while curPos !=[0,0] + let curPos = searchpos('\C}\|\(using\s\+namespace\)', 'bW',stopLine) + if curPos!=[0,0] && curPos[0]!=lastLine + let lastLine = curPos[0] + + let szLine = getline('.') + if origPos[0] == curPos[0] + " We get the line until cursor position + let szLine = szLine[:origPos[1]] + endif + + let szLine = omni#cpp#utils#GetCodeFromLine(szLine) + if match(szLine, '\Cusing\s\+namespace')<0 + " We found a '}' + let curPos = searchpairpos('{', '', '}', 'bW', g:omni#cpp#utils#expIgnoreComments) + else + " We get the namespace list from the line + let result = s:GetNamespaceListFromLine(szLine) + result + let nextStopLine = curPos[0] + endif + endif + endwhile + + " Setting the cursor to the original position + call setpos('.', originalPos) + + " 2) Now we can get all global using namespace declaration from the + " beginning of the file to nextStopLine + let result = omni#cpp#namespaces#GetListFromCurrentBuffer(nextStopLine) + result + + " Resolving alias in the namespace list + " TODO: For the next release + "let g:omni#cpp#namespaces#CacheAlias= s:GetNamespaceAliasMap() + "call s:ResolveAliasInNamespaceList(g:omni#cpp#namespaces#CacheAlias, result) + + return ['::'] + result +endfunc + +" Resolve a using namespace regarding the current context +" For each namespace used: +" - We get all possible contexts where the namespace +" can be define +" - We do a comparison test of each parent contexts with the current +" context list +" - If one and only one parent context is present in the +" current context list we add the namespace in the current +" context +" - If there is more than one of parent contexts in the +" current context the namespace is ambiguous +" @return +" - result item +" - kind = 0|1 +" - 0 = unresolved or error +" - 1 = resolved +" - value = resolved namespace +function! s:ResolveNamespace(namespace, mapCurrentContexts) + let result = {'kind':0, 'value': ''} + + " If the namespace is already resolved we add it in the list of + " current contexts + if match(a:namespace, '^::')>=0 + let result.kind = 1 + let result.value = a:namespace + return result + elseif match(a:namespace, '\w\+::\w\+')>=0 + let mapCurrentContextsTmp = copy(a:mapCurrentContexts) + let resolvedItem = {} + for nsTmp in split(a:namespace, '::') + let resolvedItem = s:ResolveNamespace(nsTmp, mapCurrentContextsTmp) + if resolvedItem.kind + " Note: We don't extend the map + let mapCurrentContextsTmp = {resolvedItem.value : 1} + else + break + endif + endfor + if resolvedItem!={} && resolvedItem.kind + let result.kind = 1 + let result.value = resolvedItem.value + endif + return result + endif + + " We get all possible parent contexts of this namespace + let listTagsOfNamespace = [] + if has_key(g:omni#cpp#namespaces#CacheResolve, a:namespace) + let listTagsOfNamespace = g:omni#cpp#namespaces#CacheResolve[a:namespace] + else + let listTagsOfNamespace = omni#common#utils#TagList('^'.a:namespace.'$') + let g:omni#cpp#namespaces#CacheResolve[a:namespace] = listTagsOfNamespace + endif + + if len(listTagsOfNamespace)==0 + return result + endif + call filter(listTagsOfNamespace, 'v:val.kind[0]=="n"') + + " We extract parent context from tags + " We use a map to avoid multiple entries + let mapContext = {} + for tagItem in listTagsOfNamespace + let szParentContext = omni#cpp#utils#ExtractScope(tagItem) + let mapContext[szParentContext] = 1 + endfor + let listParentContext = keys(mapContext) + + " Now for each parent context we test if the context is in the current + " contexts list + let listResolvedNamespace = [] + for szParentContext in listParentContext + if has_key(a:mapCurrentContexts, szParentContext) + call extend(listResolvedNamespace, [omni#cpp#utils#SimplifyScope(szParentContext.'::'.a:namespace)]) + endif + endfor + + " Now we know if the namespace is ambiguous or not + let len = len(listResolvedNamespace) + if len==1 + " Namespace resolved + let result.kind = 1 + let result.value = listResolvedNamespace[0] + elseif len > 1 + " Ambiguous namespace, possible matches are in listResolvedNamespace + else + " Other cases + endif + return result +endfunc + +" Resolve namespaces +"@return +" - List of resolved namespaces +function! omni#cpp#namespaces#ResolveAll(namespacesUsed) + + " We add the default context '::' + let contextOrder = 0 + let mapCurrentContexts = {} + + " For each namespace used: + " - We get all possible contexts where the namespace + " can be define + " - We do a comparison test of each parent contexts with the current + " context list + " - If one and only one parent context is present in the + " current context list we add the namespace in the current + " context + " - If there is more than one of parent contexts in the + " current context the namespace is ambiguous + for ns in a:namespacesUsed + let resolvedItem = s:ResolveNamespace(ns, mapCurrentContexts) + if resolvedItem.kind + let contextOrder+=1 + let mapCurrentContexts[resolvedItem.value] = contextOrder + endif + endfor + + " Build the list of current contexts from the map, we have to keep the + " order + let mapReorder = {} + for key in keys(mapCurrentContexts) + let mapReorder[ mapCurrentContexts[key] ] = key + endfor + let result = [] + for key in sort(keys(mapReorder)) + call extend(result, [mapReorder[key]]) + endfor + return result +endfunc + +" Build the context stack +function! s:BuildContextStack(namespaces, szCurrentScope) + let result = copy(a:namespaces) + if a:szCurrentScope != '::' + let tagItem = omni#cpp#utils#GetResolvedTagItem(a:namespaces, omni#cpp#utils#CreateTypeInfo(a:szCurrentScope)) + if has_key(tagItem, 'inherits') + let listBaseClass = omni#cpp#utils#GetClassInheritanceList(a:namespaces, omni#cpp#utils#CreateTypeInfo(a:szCurrentScope)) + let result = listBaseClass + result + elseif has_key(tagItem, 'kind') && index(['c', 's', 'u', 'n'], tagItem.kind[0])>=0 + call insert(result, omni#cpp#utils#ExtractTypeInfoFromTag(tagItem)) + endif + endif + return result +endfunc + +" Returns the class scope at the current position of the cursor +" @return a string that represents the class scope +" eg: ::NameSpace1::Class1 +" The returned string always starts with '::' +" Note: In term of performance it's the weak point of the script +function! s:GetClassScopeAtCursor() + " We store the cursor position because searchpairpos() moves the cursor + let originalPos = getpos('.') + let endPos = originalPos[1:2] + let listCode = [] + let result = {'namespaces': [], 'scope': ''} + + while endPos!=[0,0] + let endPos = searchpairpos('{', '', '}', 'bW', g:omni#cpp#utils#expIgnoreComments) + let szReStartPos = '[;{}]\|\%^' + let startPos = searchpairpos(szReStartPos, '', '{', 'bWn', g:omni#cpp#utils#expIgnoreComments) + + " If the file starts with a comment so the startPos can be [0,0] + " we change it to [1,1] + if startPos==[0,0] + let startPos = [1,1] + endif + + " Get lines backward from cursor position to last ; or { or } + " or when we are at the beginning of the file. + " We store lines in listCode + if endPos!=[0,0] + " We remove the last character which is a '{' + " We also remove starting { or } or ; if exits + let szCodeWithoutComments = substitute(omni#cpp#utils#GetCode(startPos, endPos)[:-2], '^[;{}]', '', 'g') + call insert(listCode, {'startLine' : startPos[0], 'code' : szCodeWithoutComments}) + endif + endwhile + " Setting the cursor to the original position + call setpos('.', originalPos) + + let listClassScope = [] + let bResolved = 0 + let startLine = 0 + " Now we can check in the list of code if there is a function + for code in listCode + " We get the name of the namespace, class, struct or union + " and we store it in listClassScope + let tokens = omni#cpp#tokenizer#Tokenize(code.code) + let bContinue=0 + let bAddNamespace = 0 + let state=0 + for token in tokens + if state==0 + if index(['namespace', 'class', 'struct', 'union'], token.value)>=0 + if token.value == 'namespace' + let bAddNamespace = 1 + endif + let state= 1 + " Maybe end of tokens + endif + elseif state==1 + if token.kind == 'cppWord' + " eg: namespace MyNs { class MyCl {}; } + " => listClassScope = [MyNs, MyCl] + call extend( listClassScope , [token.value] ) + + " Add the namespace in result + if bAddNamespace + call extend(result.namespaces, [token.value]) + let bAddNamespace = 0 + endif + + let bContinue=1 + break + endif + endif + endfor + if bContinue==1 + continue + endif + + " Simple test to check if we have a chance to find a + " class method + let aPos = matchend(code.code, '::\s*\~*\s*\w\+\s*(') + if aPos ==-1 + continue + endif + + let startLine = code.startLine + let listTmp = [] + " eg: 'void MyNamespace::MyClass::foo(' + " => tokens = ['MyClass', '::', 'MyNamespace', 'void'] + let tokens = reverse(omni#cpp#tokenizer#Tokenize(code.code[:aPos-1])[:-4]) + let state = 0 + " Reading tokens backward + for token in tokens + if state==0 + if token.kind=='cppWord' + call insert(listTmp, token.value) + let state=1 + endif + elseif state==1 + if token.value=='::' + let state=2 + else + break + endif + elseif state==2 + if token.kind=='cppWord' + call insert(listTmp, token.value) + let state=1 + else + break + endif + endif + endfor + + if len(listTmp) + if len(listClassScope) + let bResolved = 1 + " Merging class scopes + " eg: current class scope = 'MyNs::MyCl1' + " method class scope = 'MyCl1::MyCl2' + " If we add the method class scope to current class scope + " we'll have MyNs::MyCl1::MyCl1::MyCl2 => it's wrong + " we want MyNs::MyCl1::MyCl2 + let index = 0 + for methodClassScope in listTmp + if methodClassScope==listClassScope[-1] + let listTmp = listTmp[index+1:] + break + else + let index+=1 + endif + endfor + endif + call extend(listClassScope, listTmp) + break + endif + endfor + + let szClassScope = '::' + if len(listClassScope) + if bResolved + let szClassScope .= join(listClassScope, '::') + else + let szClassScope = join(listClassScope, '::') + + " The class scope is not resolved, we have to check using + " namespace declarations and search the class scope in each + " namespace + if startLine != 0 + let namespaces = ['::'] + omni#cpp#namespaces#GetListFromCurrentBuffer(startLine) + let namespaces = omni#cpp#namespaces#ResolveAll(namespaces) + let tagItem = omni#cpp#utils#GetResolvedTagItem(namespaces, omni#cpp#utils#CreateTypeInfo(szClassScope)) + if tagItem != {} + let szClassScope = omni#cpp#utils#ExtractTypeInfoFromTag(tagItem) + endif + endif + endif + endif + + let result.scope = szClassScope + return result +endfunc + +" Get all contexts at the cursor position +function! omni#cpp#namespaces#GetContexts() + " Get the current class scope at the cursor, the result depends on the current cursor position + let scopeItem = s:GetClassScopeAtCursor() + let listUsingNamespace = copy(g:OmniCpp_DefaultNamespaces) + call extend(listUsingNamespace, scopeItem.namespaces) + if g:OmniCpp_NamespaceSearch && &filetype != 'c' + " Get namespaces used in the file until the cursor position + let listUsingNamespace = omni#cpp#namespaces#GetUsingNamespaces() + listUsingNamespace + " Resolving namespaces, removing ambiguous namespaces + let namespaces = omni#cpp#namespaces#ResolveAll(listUsingNamespace) + else + let namespaces = ['::'] + listUsingNamespace + endif + call reverse(namespaces) + + " Building context stack from namespaces and the current class scope + return s:BuildContextStack(namespaces, scopeItem.scope) +endfunc diff --git a/VimFiles/autoload/omni/cpp/settings.vim b/VimFiles/autoload/omni/cpp/settings.vim new file mode 100755 index 0000000..6683d3a --- /dev/null +++ b/VimFiles/autoload/omni/cpp/settings.vim @@ -0,0 +1,96 @@ +" Description: Omni completion script for cpp files +" Maintainer: Vissale NEANG +" Last Change: 26 sept. 2007 + +function! omni#cpp#settings#Init() + " Global scope search on/off + " 0 = disabled + " 1 = enabled + if !exists('g:OmniCpp_GlobalScopeSearch') + let g:OmniCpp_GlobalScopeSearch = 1 + endif + + " Sets the namespace search method + " 0 = disabled + " 1 = search namespaces in the current file + " 2 = search namespaces in the current file and included files + if !exists('g:OmniCpp_NamespaceSearch') + let g:OmniCpp_NamespaceSearch = 1 + endif + + " Set the class scope completion mode + " 0 = auto + " 1 = show all members (static, public, protected and private) + if !exists('g:OmniCpp_DisplayMode') + let g:OmniCpp_DisplayMode = 0 + endif + + " Set if the scope is displayed in the abbr column of the popup + " 0 = no + " 1 = yes + if !exists('g:OmniCpp_ShowScopeInAbbr') + let g:OmniCpp_ShowScopeInAbbr = 0 + endif + + " Set if the function prototype is displayed in the abbr column of the popup + " 0 = no + " 1 = yes + if !exists('g:OmniCpp_ShowPrototypeInAbbr') + let g:OmniCpp_ShowPrototypeInAbbr = 0 + endif + + " Set if the access (+,#,-) is displayed + " 0 = no + " 1 = yes + if !exists('g:OmniCpp_ShowAccess') + let g:OmniCpp_ShowAccess = 1 + endif + + " Set the list of default namespaces + " eg: ['std'] + if !exists('g:OmniCpp_DefaultNamespaces') + let g:OmniCpp_DefaultNamespaces = [] + endif + + " Set MayComplete to '.' + " 0 = disabled + " 1 = enabled + " default = 1 + if !exists('g:OmniCpp_MayCompleteDot') + let g:OmniCpp_MayCompleteDot = 1 + endif + + " Set MayComplete to '->' + " 0 = disabled + " 1 = enabled + " default = 1 + if !exists('g:OmniCpp_MayCompleteArrow') + let g:OmniCpp_MayCompleteArrow = 1 + endif + + " Set MayComplete to dot + " 0 = disabled + " 1 = enabled + " default = 0 + if !exists('g:OmniCpp_MayCompleteScope') + let g:OmniCpp_MayCompleteScope = 0 + endif + + " When completeopt does not contain longest option, this setting + " controls the behaviour of the popup menu selection when starting the completion + " 0 = don't select first item + " 1 = select first item (inserting it to the text) + " 2 = select first item (without inserting it to the text) + " default = 0 + if !exists('g:OmniCpp_SelectFirstItem') + let g:OmniCpp_SelectFirstItem= 0 + endif + + " Use local search function for variable definitions + " 0 = use standard vim search function + " 1 = use local search function + " default = 0 + if !exists('g:OmniCpp_LocalSearchDecl') + let g:OmniCpp_LocalSearchDecl= 0 + endif +endfunc diff --git a/VimFiles/autoload/omni/cpp/tokenizer.vim b/VimFiles/autoload/omni/cpp/tokenizer.vim new file mode 100755 index 0000000..16e0be2 --- /dev/null +++ b/VimFiles/autoload/omni/cpp/tokenizer.vim @@ -0,0 +1,93 @@ +" Description: Omni completion tokenizer +" Maintainer: Vissale NEANG +" Last Change: 26 sept. 2007 +" TODO: Generic behaviour for Tokenize() + +" From the C++ BNF +let s:cppKeyword = ['asm', 'auto', 'bool', 'break', 'case', 'catch', 'char', 'class', 'const', 'const_cast', 'continue', 'default', 'delete', 'do', 'double', 'dynamic_cast', 'else', 'enum', 'explicit', 'export', 'extern', 'false', 'float', 'for', 'friend', 'goto', 'if', 'inline', 'int', 'long', 'mutable', 'namespace', 'new', 'operator', 'private', 'protected', 'public', 'register', 'reinterpret_cast', 'return', 'short', 'signed', 'sizeof', 'static', 'static_cast', 'struct', 'switch', 'template', 'this', 'throw', 'true', 'try', 'typedef', 'typeid', 'typename', 'union', 'unsigned', 'using', 'virtual', 'void', 'volatile', 'wchar_t', 'while', 'and', 'and_eq', 'bitand', 'bitor', 'compl', 'not', 'not_eq', 'or', 'or_eq', 'xor', 'xor_eq'] + +let s:reCppKeyword = '\C\<'.join(s:cppKeyword, '\>\|\<').'\>' + +" The order of items in this list is very important because we use this list to build a regular +" expression (see below) for tokenization +let s:cppOperatorPunctuator = ['->*', '->', '--', '-=', '-', '!=', '!', '##', '#', '%:%:', '%=', '%>', '%:', '%', '&&', '&=', '&', '(', ')', '*=', '*', ',', '...', '.*', '.', '/=', '/', '::', ':>', ':', ';', '?', '[', ']', '^=', '^', '{', '||', '|=', '|', '}', '~', '++', '+=', '+', '<<=', '<%', '<:', '<<', '<=', '<', '==', '=', '>>=', '>>', '>=', '>'] + +" We build the regexp for the tokenizer +let s:reCComment = '\/\*\|\*\/' +let s:reCppComment = '\/\/' +let s:reComment = s:reCComment.'\|'.s:reCppComment +let s:reCppOperatorOrPunctuator = escape(join(s:cppOperatorPunctuator, '\|'), '*./^~[]') + + +" Tokenize a c++ code +" a token is dictionary where keys are: +" - kind = cppKeyword|cppWord|cppOperatorPunctuator|unknown|cComment|cppComment|cppDigit +" - value = 'something' +" Note: a cppWord is any word that is not a cpp keyword +function! omni#cpp#tokenizer#Tokenize(szCode) + let result = [] + + " The regexp to find a token, a token is a keyword, word or + " c++ operator or punctuator. To work properly we have to put + " spaces and tabs to our regexp. + let reTokenSearch = '\(\w\+\)\|\s\+\|'.s:reComment.'\|'.s:reCppOperatorOrPunctuator + " eg: 'using namespace std;' + " ^ ^ + " start=0 end=5 + let startPos = 0 + let endPos = matchend(a:szCode, reTokenSearch) + let len = endPos-startPos + while endPos!=-1 + " eg: 'using namespace std;' + " ^ ^ + " start=0 end=5 + " token = 'using' + " We also remove space and tabs + let token = substitute(strpart(a:szCode, startPos, len), '\s', '', 'g') + + " eg: 'using namespace std;' + " ^ ^ + " start=5 end=15 + let startPos = endPos + let endPos = matchend(a:szCode, reTokenSearch, startPos) + let len = endPos-startPos + + " It the token is empty we continue + if token=='' + continue + endif + + " Building the token + let resultToken = {'kind' : 'unknown', 'value' : token} + + " Classify the token + if token =~ '^\d\+' + " It's a digit + let resultToken.kind = 'cppDigit' + elseif token=~'^\w\+$' + " It's a word + let resultToken.kind = 'cppWord' + + " But maybe it's a c++ keyword + if match(token, s:reCppKeyword)>=0 + let resultToken.kind = 'cppKeyword' + endif + else + if match(token, s:reComment)>=0 + if index(['/*','*/'],token)>=0 + let resultToken.kind = 'cComment' + else + let resultToken.kind = 'cppComment' + endif + else + " It's an operator + let resultToken.kind = 'cppOperatorPunctuator' + endif + endif + + " We have our token, let's add it to the result list + call extend(result, [resultToken]) + endwhile + + return result +endfunc diff --git a/VimFiles/autoload/omni/cpp/utils.vim b/VimFiles/autoload/omni/cpp/utils.vim new file mode 100755 index 0000000..5d74d34 --- /dev/null +++ b/VimFiles/autoload/omni/cpp/utils.vim @@ -0,0 +1,587 @@ +" Description: Omni completion script for cpp files +" Maintainer: Vissale NEANG +" Last Change: 26 sept. 2007 + +let g:omni#cpp#utils#CACHE_TAG_INHERITS = {} +let g:omni#cpp#utils#szFilterGlobalScope = "(!has_key(v:val, 'class') && !has_key(v:val, 'struct') && !has_key(v:val, 'union') && !has_key(v:val, 'namespace')" +let g:omni#cpp#utils#szFilterGlobalScope .= "&& (!has_key(v:val, 'enum') || (has_key(v:val, 'enum') && v:val.enum =~ '^\\w\\+$')))" + +" Expression used to ignore comments +" Note: this expression drop drastically the performance +"let omni#cpp#utils#expIgnoreComments = 'match(synIDattr(synID(line("."), col("."), 1), "name"), '\CcComment')!=-1' +" This one is faster but not really good for C comments +let omni#cpp#utils#reIgnoreComment = escape('\/\/\|\/\*\|\*\/', '*/\') +let omni#cpp#utils#expIgnoreComments = 'getline(".") =~ g:omni#cpp#utils#reIgnoreComment' + +" Characters to escape in a filename for vimgrep +"TODO: Find more characters to escape +let omni#cpp#utils#szEscapedCharacters = ' %#' + +" Resolve the path of the file +" TODO: absolute file path +function! omni#cpp#utils#ResolveFilePath(szFile) + let result = '' + let listPath = split(globpath(&path, a:szFile), "\n") + if len(listPath) + let result = listPath[0] + endif + return simplify(result) +endfunc + +" Get code without comments and with empty strings +" szSingleLine must not have carriage return +function! omni#cpp#utils#GetCodeFromLine(szSingleLine) + " We set all strings to empty strings, it's safer for + " the next of the process + let szResult = substitute(a:szSingleLine, '".*"', '""', 'g') + + " Removing c++ comments, we can use the pattern ".*" because + " we are modifying a line + let szResult = substitute(szResult, '\/\/.*', '', 'g') + + " Now we have the entire code in one line and we can remove C comments + return s:RemoveCComments(szResult) +endfunc + +" Remove C comments on a line +function! s:RemoveCComments(szLine) + let result = a:szLine + + " We have to match the first '/*' and first '*/' + let startCmt = match(result, '\/\*') + let endCmt = match(result, '\*\/') + while startCmt!=-1 && endCmt!=-1 && startCmt0 + let result = result[ : startCmt-1 ] . result[ endCmt+2 : ] + else + " Case where '/*' is at the start of the line + let result = result[ endCmt+2 : ] + endif + let startCmt = match(result, '\/\*') + let endCmt = match(result, '\*\/') + endwhile + return result +endfunc + +" Get a c++ code from current buffer from [lineStart, colStart] to +" [lineEnd, colEnd] without c++ and c comments, without end of line +" and with empty strings if any +" @return a string +function! omni#cpp#utils#GetCode(posStart, posEnd) + let posStart = a:posStart + let posEnd = a:posEnd + if a:posStart[0]>a:posEnd[0] + let posStart = a:posEnd + let posEnd = a:posStart + elseif a:posStart[0]==a:posEnd[0] && a:posStart[1]>a:posEnd[1] + let posStart = a:posEnd + let posEnd = a:posStart + endif + + " Getting the lines + let lines = getline(posStart[0], posEnd[0]) + let lenLines = len(lines) + + " Formatting the result + let result = '' + if lenLines==1 + let sStart = posStart[1]-1 + let sEnd = posEnd[1]-1 + let line = lines[0] + let lenLastLine = strlen(line) + let sEnd = (sEnd>lenLastLine)?lenLastLine : sEnd + if sStart >= 0 + let result = omni#cpp#utils#GetCodeFromLine(line[ sStart : sEnd ]) + endif + elseif lenLines>1 + let sStart = posStart[1]-1 + let sEnd = posEnd[1]-1 + let lenLastLine = strlen(lines[-1]) + let sEnd = (sEnd>lenLastLine)?lenLastLine : sEnd + if sStart >= 0 + let lines[0] = lines[0][ sStart : ] + let lines[-1] = lines[-1][ : sEnd ] + for aLine in lines + let result = result . omni#cpp#utils#GetCodeFromLine(aLine)." " + endfor + let result = result[:-2] + endif + endif + + " Now we have the entire code in one line and we can remove C comments + return s:RemoveCComments(result) +endfunc + +" Extract the scope (context) of a tag item +" eg: ::MyNamespace +" @return a string of the scope. a scope from tag always starts with '::' +function! omni#cpp#utils#ExtractScope(tagItem) + let listKindScope = ['class', 'struct', 'union', 'namespace', 'enum'] + let szResult = '::' + for scope in listKindScope + if has_key(a:tagItem, scope) + let szResult = szResult . a:tagItem[scope] + break + endif + endfor + return szResult +endfunc + +" Simplify scope string, remove consecutive '::' if any +function! omni#cpp#utils#SimplifyScope(szScope) + let szResult = substitute(a:szScope, '\(::\)\+', '::', 'g') + if szResult=='::' + return szResult + else + return substitute(szResult, '::$', '', 'g') + endif +endfunc + +" Check if the cursor is in comment +function! omni#cpp#utils#IsCursorInCommentOrString() + return match(synIDattr(synID(line("."), col(".")-1, 1), "name"), '\C\=0 +endfunc + +" Tokenize the current instruction until the cursor position. +" @return list of tokens +function! omni#cpp#utils#TokenizeCurrentInstruction(...) + let szAppendText = '' + if a:0>0 + let szAppendText = a:1 + endif + + let startPos = searchpos('[;{}]\|\%^', 'bWn') + let curPos = getpos('.')[1:2] + " We don't want the character under the cursor + let column = curPos[1]-1 + let curPos[1] = (column<1)?1:column + return omni#cpp#tokenizer#Tokenize(omni#cpp#utils#GetCode(startPos, curPos)[1:] . szAppendText) +endfunc + +" Tokenize the current instruction until the word under the cursor. +" @return list of tokens +function! omni#cpp#utils#TokenizeCurrentInstructionUntilWord() + let startPos = searchpos('[;{}]\|\%^', 'bWn') + + " Saving the current cursor pos + let originalPos = getpos('.') + + " We go at the end of the word + execute 'normal gee' + let curPos = getpos('.')[1:2] + + " Restoring the original cursor pos + call setpos('.', originalPos) + + let szCode = omni#cpp#utils#GetCode(startPos, curPos)[1:] + return omni#cpp#tokenizer#Tokenize(szCode) +endfunc + +" Build parenthesis groups +" add a new key 'group' in the token +" where value is the group number of the parenthesis +" eg: (void*)(MyClass*) +" group1 group0 +" if a parenthesis is unresolved the group id is -1 +" @return a copy of a:tokens with parenthesis group +function! omni#cpp#utils#BuildParenthesisGroups(tokens) + let tokens = copy(a:tokens) + let kinds = {'(': '()', ')' : '()', '[' : '[]', ']' : '[]', '<' : '<>', '>' : '<>', '{': '{}', '}': '{}'} + let unresolved = {'()' : [], '[]': [], '<>' : [], '{}' : []} + let groupId = 0 + + " Note: we build paren group in a backward way + " because we can often have parenthesis unbalanced + " instruction + " eg: doSomething(_member.get()-> + for token in reverse(tokens) + if index([')', ']', '>', '}'], token.value)>=0 + let token['group'] = groupId + call extend(unresolved[kinds[token.value]], [token]) + let groupId+=1 + elseif index(['(', '[', '<', '{'], token.value)>=0 + if len(unresolved[kinds[token.value]]) + let tokenResolved = remove(unresolved[kinds[token.value]], -1) + let token['group'] = tokenResolved.group + else + let token['group'] = -1 + endif + endif + endfor + + return reverse(tokens) +endfunc + +" Determine if tokens represent a C cast +" @return +" - itemCast +" - itemCppCast +" - itemVariable +" - itemThis +function! omni#cpp#utils#GetCastType(tokens) + " Note: a:tokens is not modified + let tokens = omni#cpp#utils#SimplifyParenthesis(omni#cpp#utils#BuildParenthesisGroups(a:tokens)) + + if tokens[0].value == '(' + return 'itemCast' + elseif index(['static_cast', 'dynamic_cast', 'reinterpret_cast', 'const_cast'], tokens[0].value)>=0 + return 'itemCppCast' + else + for token in tokens + if token.value=='this' + return 'itemThis' + endif + endfor + return 'itemVariable' + endif +endfunc + +" Remove useless parenthesis +function! omni#cpp#utils#SimplifyParenthesis(tokens) + "Note: a:tokens is not modified + let tokens = a:tokens + " We remove useless parenthesis eg: (((MyClass))) + if len(tokens)>2 + while tokens[0].value=='(' && tokens[-1].value==')' && tokens[0].group==tokens[-1].group + let tokens = tokens[1:-2] + endwhile + endif + return tokens +endfunc + +" Function create a type info +function! omni#cpp#utils#CreateTypeInfo(param) + let type = type(a:param) + return {'type': type, 'value':a:param} +endfunc + +" Extract type info from a tag item +" eg: ::MyNamespace::MyClass +function! omni#cpp#utils#ExtractTypeInfoFromTag(tagItem) + let szTypeInfo = omni#cpp#utils#ExtractScope(a:tagItem) . '::' . substitute(a:tagItem.name, '.*::', '', 'g') + return omni#cpp#utils#SimplifyScope(szTypeInfo) +endfunc + +" Build a class inheritance list +function! omni#cpp#utils#GetClassInheritanceList(namespaces, typeInfo) + let result = [] + for tagItem in omni#cpp#utils#GetResolvedTags(a:namespaces, a:typeInfo) + call extend(result, [omni#cpp#utils#ExtractTypeInfoFromTag(tagItem)]) + endfor + return result +endfunc + +" Get class inheritance list where items in the list are tag items. +" TODO: Verify inheritance order +function! omni#cpp#utils#GetResolvedTags(namespaces, typeInfo) + let result = [] + let tagItem = omni#cpp#utils#GetResolvedTagItem(a:namespaces, a:typeInfo) + if tagItem!={} + let szTypeInfo = omni#cpp#utils#ExtractTypeInfoFromTag(tagItem) + if has_key(g:omni#cpp#utils#CACHE_TAG_INHERITS, szTypeInfo) + let result = g:omni#cpp#utils#CACHE_TAG_INHERITS[szTypeInfo] + else + call extend(result, [tagItem]) + if has_key(tagItem, 'inherits') + for baseClassTypeInfo in split(tagItem.inherits, ',') + let namespaces = [omni#cpp#utils#ExtractScope(tagItem), '::'] + call extend(result, omni#cpp#utils#GetResolvedTags(namespaces, omni#cpp#utils#CreateTypeInfo(baseClassTypeInfo))) + endfor + endif + let g:omni#cpp#utils#CACHE_TAG_INHERITS[szTypeInfo] = result + endif + endif + return result +endfunc + +" Get a tag item after a scope resolution and typedef resolution +function! omni#cpp#utils#GetResolvedTagItem(namespaces, typeInfo) + let typeInfo = {} + if type(a:typeInfo) == 1 + let typeInfo = omni#cpp#utils#CreateTypeInfo(a:typeInfo) + else + let typeInfo = a:typeInfo + endif + + let result = {} + if !omni#cpp#utils#IsTypeInfoValid(typeInfo) + return result + endif + + " Unnamed type case eg: '1::2' + if typeInfo.type == 4 + " Here there is no typedef or namespace to resolve, the tagInfo.value is a tag item + " representing a variable ('v') a member ('m') or a typedef ('t') and the typename is + " always in global scope + return typeInfo.value + endif + + " Named type case eg: 'MyNamespace::MyClass' + let szTypeInfo = omni#cpp#utils#GetTypeInfoString(typeInfo) + + " Resolving namespace alias + " TODO: For the next release + "let szTypeInfo = omni#cpp#namespaces#ResolveAlias(g:omni#cpp#namespaces#CacheAlias, szTypeInfo) + + if szTypeInfo=='::' + return result + endif + + " We can only get members of class, struct, union and namespace + let szTagFilter = "index(['c', 's', 'u', 'n', 't'], v:val.kind[0])>=0" + let szTagQuery = szTypeInfo + + if s:IsTypeInfoResolved(szTypeInfo) + " The type info is already resolved, we remove the starting '::' + let szTagQuery = substitute(szTypeInfo, '^::', '', 'g') + if len(split(szTagQuery, '::'))==1 + " eg: ::MyClass + " Here we have to get tags that have no parent scope + " That's why we change the szTagFilter + let szTagFilter .= '&& ' . g:omni#cpp#utils#szFilterGlobalScope + let tagList = omni#common#utils#TagListNoThrow('^'.szTagQuery.'$') + call filter(tagList, szTagFilter) + if len(tagList) + let result = tagList[0] + endif + else + " eg: ::MyNamespace::MyClass + let tagList = omni#common#utils#TagListNoThrow('^'.szTagQuery.'$') + call filter(tagList, szTagFilter) + + if len(tagList) + let result = tagList[0] + endif + endif + else + " The type is not resolved + let tagList = omni#common#utils#TagListNoThrow('^'.szTagQuery.'$') + call filter(tagList, szTagFilter) + + if len(tagList) + " Resolving scope (namespace, nested class etc...) + let szScopeOfTypeInfo = s:ExtractScopeFromTypeInfo(szTypeInfo) + if s:IsTypeInfoResolved(szTypeInfo) + let result = s:GetTagOfSameScope(tagList, szScopeOfTypeInfo) + else + " For each namespace of the namespace list we try to get a tag + " that can be in the same scope + if g:OmniCpp_NamespaceSearch && &filetype != 'c' + for scope in a:namespaces + let szTmpScope = omni#cpp#utils#SimplifyScope(scope.'::'.szScopeOfTypeInfo) + let result = s:GetTagOfSameScope(tagList, szTmpScope) + if result!={} + break + endif + endfor + else + let szTmpScope = omni#cpp#utils#SimplifyScope('::'.szScopeOfTypeInfo) + let result = s:GetTagOfSameScope(tagList, szTmpScope) + endif + endif + endif + endif + + if result!={} + " We have our tagItem but maybe it's a typedef or an unnamed type + if result.kind[0]=='t' + " Here we can have a typedef to another typedef, a class, struct, union etc + " but we can also have a typedef to an unnamed type, in that + " case the result contains a 'typeref' key + let namespaces = [omni#cpp#utils#ExtractScope(result), '::'] + if has_key(result, 'typeref') + let result = omni#cpp#utils#GetResolvedTagItem(namespaces, omni#cpp#utils#CreateTypeInfo(result)) + else + let szCmd = omni#cpp#utils#ExtractCmdFromTagItem(result) + let szCode = substitute(omni#cpp#utils#GetCodeFromLine(szCmd), '\C\<'.result.name.'\>.*', '', 'g') + let szTypeInfo = omni#cpp#utils#ExtractTypeInfoFromTokens(omni#cpp#tokenizer#Tokenize(szCode)) + let result = omni#cpp#utils#GetResolvedTagItem(namespaces, omni#cpp#utils#CreateTypeInfo(szTypeInfo)) + " TODO: Namespace resolution for result + endif + endif + endif + + return result +endfunc + +" Returns if the type info is valid +" @return +" - 1 if valid +" - 0 otherwise +function! omni#cpp#utils#IsTypeInfoValid(typeInfo) + if a:typeInfo=={} + return 0 + else + if a:typeInfo.type == 1 && a:typeInfo.value=='' + " String case + return 0 + elseif a:typeInfo.type == 4 && a:typeInfo.value=={} + " Dictionary case + return 0 + endif + endif + return 1 +endfunc + +" Get the string of the type info +function! omni#cpp#utils#GetTypeInfoString(typeInfo) + if a:typeInfo.type == 1 + return a:typeInfo.value + else + return substitute(a:typeInfo.value.typeref, '^\w\+:', '', 'g') + endif +endfunc + +" A resolved type info starts with '::' +" @return +" - 1 if type info starts with '::' +" - 0 otherwise +function! s:IsTypeInfoResolved(szTypeInfo) + return match(a:szTypeInfo, '^::')!=-1 +endfunc + +" A returned type info's scope may not have the global namespace '::' +" eg: '::NameSpace1::NameSpace2::MyClass' => '::NameSpace1::NameSpace2' +" 'NameSpace1::NameSpace2::MyClass' => 'NameSpace1::NameSpace2' +function! s:ExtractScopeFromTypeInfo(szTypeInfo) + let szScope = substitute(a:szTypeInfo, '\w\+$', '', 'g') + if szScope =='::' + return szScope + else + return substitute(szScope, '::$', '', 'g') + endif +endfunc + +" @return +" - the tag with the same scope +" - {} otherwise +function! s:GetTagOfSameScope(listTags, szScopeToMatch) + for tagItem in a:listTags + let szScopeOfTag = omni#cpp#utils#ExtractScope(tagItem) + if szScopeOfTag == a:szScopeToMatch + return tagItem + endif + endfor + return {} +endfunc + +" Extract the cmd of a tag item without regexp +function! omni#cpp#utils#ExtractCmdFromTagItem(tagItem) + let line = a:tagItem.cmd + let re = '\(\/\^\)\|\(\$\/\)' + if match(line, re)!=-1 + let line = substitute(line, re, '', 'g') + return line + else + " TODO: the cmd is a line number + return '' + endif +endfunc + +" Extract type from tokens. +" eg: examples of tokens format +" 'const MyClass&' +" 'const map < int, int >&' +" 'MyNs::MyClass' +" '::MyClass**' +" 'MyClass a, *b = NULL, c[1] = {}; +" 'hello(MyClass a, MyClass* b' +" @return the type info string eg: ::std::map +" can be empty +function! omni#cpp#utils#ExtractTypeInfoFromTokens(tokens) + let szResult = '' + let state = 0 + + let tokens = omni#cpp#utils#BuildParenthesisGroups(a:tokens) + + " If there is an unbalanced parenthesis we are in a parameter list + let bParameterList = 0 + for token in tokens + if token.value == '(' && token.group==-1 + let bParameterList = 1 + break + endif + endfor + + if bParameterList + let tokens = reverse(tokens) + let state = 0 + let parenGroup = -1 + for token in tokens + if state==0 + if token.value=='>' + let parenGroup = token.group + let state=1 + elseif token.kind == 'cppWord' + let szResult = token.value.szResult + let state=2 + elseif index(['*', '&'], token.value)<0 + break + endif + elseif state==1 + if token.value=='<' && token.group==parenGroup + let state=0 + endif + elseif state==2 + if token.value=='::' + let szResult = token.value.szResult + let state=3 + else + break + endif + elseif state==3 + if token.kind == 'cppWord' + let szResult = token.value.szResult + let state=2 + else + break + endif + endif + endfor + return szResult + endif + + for token in tokens + if state==0 + if token.value == '::' + let szResult .= token.value + let state = 1 + elseif token.kind == 'cppWord' + let szResult .= token.value + let state = 2 + " Maybe end of token + endif + elseif state==1 + if token.kind == 'cppWord' + let szResult .= token.value + let state = 2 + " Maybe end of token + else + break + endif + elseif state==2 + if token.value == '::' + let szResult .= token.value + let state = 1 + else + break + endif + endif + endfor + return szResult +endfunc + +" Get the preview window string +function! omni#cpp#utils#GetPreviewWindowStringFromTagItem(tagItem) + let szResult = '' + + let szResult .= 'name: '.a:tagItem.name."\n" + for tagKey in keys(a:tagItem) + if index(['name', 'static'], tagKey)>=0 + continue + endif + let szResult .= tagKey.': '.a:tagItem[tagKey]."\n" + endfor + + return substitute(szResult, "\n$", '', 'g') +endfunc diff --git a/VimFiles/autoload/plug.vim b/VimFiles/autoload/plug.vim new file mode 100644 index 0000000..9ebcf53 --- /dev/null +++ b/VimFiles/autoload/plug.vim @@ -0,0 +1,2504 @@ +" vim-plug: Vim plugin manager +" ============================ +" +" Download plug.vim and put it in ~/.vim/autoload +" +" curl -fLo ~/.vim/autoload/plug.vim --create-dirs \ +" https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim +" +" Edit your .vimrc +" +" call plug#begin('~/.vim/plugged') +" +" " Make sure you use single quotes +" +" " Shorthand notation; fetches https://github.com/junegunn/vim-easy-align +" Plug 'junegunn/vim-easy-align' +" +" " Any valid git URL is allowed +" Plug 'https://github.com/junegunn/vim-github-dashboard.git' +" +" " Multiple Plug commands can be written in a single line using | separators +" Plug 'SirVer/ultisnips' | Plug 'honza/vim-snippets' +" +" " On-demand loading +" Plug 'scrooloose/nerdtree', { 'on': 'NERDTreeToggle' } +" Plug 'tpope/vim-fireplace', { 'for': 'clojure' } +" +" " Using a non-master branch +" Plug 'rdnetto/YCM-Generator', { 'branch': 'stable' } +" +" " Using a tagged release; wildcard allowed (requires git 1.9.2 or above) +" Plug 'fatih/vim-go', { 'tag': '*' } +" +" " Plugin options +" Plug 'nsf/gocode', { 'tag': 'v.20150303', 'rtp': 'vim' } +" +" " Plugin outside ~/.vim/plugged with post-update hook +" Plug 'junegunn/fzf', { 'dir': '~/.fzf', 'do': './install --all' } +" +" " Unmanaged plugin (manually installed and updated) +" Plug '~/my-prototype-plugin' +" +" " Initialize plugin system +" call plug#end() +" +" Then reload .vimrc and :PlugInstall to install plugins. +" +" Plug options: +" +"| Option | Description | +"| ----------------------- | ------------------------------------------------ | +"| `branch`/`tag`/`commit` | Branch/tag/commit of the repository to use | +"| `rtp` | Subdirectory that contains Vim plugin | +"| `dir` | Custom directory for the plugin | +"| `as` | Use different name for the plugin | +"| `do` | Post-update hook (string or funcref) | +"| `on` | On-demand loading: Commands or ``-mappings | +"| `for` | On-demand loading: File types | +"| `frozen` | Do not update unless explicitly specified | +" +" More information: https://github.com/junegunn/vim-plug +" +" +" Copyright (c) 2017 Junegunn Choi +" +" MIT License +" +" Permission is hereby granted, free of charge, to any person obtaining +" a copy of this software and associated documentation files (the +" "Software"), to deal in the Software without restriction, including +" without limitation the rights to use, copy, modify, merge, publish, +" distribute, sublicense, and/or sell copies of the Software, and to +" permit persons to whom the Software is furnished to do so, subject to +" the following conditions: +" +" The above copyright notice and this permission notice shall be +" included in all copies or substantial portions of the Software. +" +" THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +" EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +" MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +" NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +" LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +" OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +" WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +if exists('g:loaded_plug') + finish +endif +let g:loaded_plug = 1 + +let s:cpo_save = &cpo +set cpo&vim + +let s:plug_src = 'https://github.com/junegunn/vim-plug.git' +let s:plug_tab = get(s:, 'plug_tab', -1) +let s:plug_buf = get(s:, 'plug_buf', -1) +let s:mac_gui = has('gui_macvim') && has('gui_running') +let s:is_win = has('win32') || has('win64') +let s:nvim = has('nvim-0.2') || (has('nvim') && exists('*jobwait') && !s:is_win) +let s:vim8 = has('patch-8.0.0039') && exists('*job_start') +let s:me = resolve(expand(':p')) +let s:base_spec = { 'branch': 'master', 'frozen': 0 } +let s:TYPE = { +\ 'string': type(''), +\ 'list': type([]), +\ 'dict': type({}), +\ 'funcref': type(function('call')) +\ } +let s:loaded = get(s:, 'loaded', {}) +let s:triggers = get(s:, 'triggers', {}) + +function! plug#begin(...) + if a:0 > 0 + let s:plug_home_org = a:1 + let home = s:path(fnamemodify(expand(a:1), ':p')) + elseif exists('g:plug_home') + let home = s:path(g:plug_home) + elseif !empty(&rtp) + let home = s:path(split(&rtp, ',')[0]) . '/plugged' + else + return s:err('Unable to determine plug home. Try calling plug#begin() with a path argument.') + endif + if fnamemodify(home, ':t') ==# 'plugin' && fnamemodify(home, ':h') ==# s:first_rtp + return s:err('Invalid plug home. '.home.' is a standard Vim runtime path and is not allowed.') + endif + + let g:plug_home = home + let g:plugs = {} + let g:plugs_order = [] + let s:triggers = {} + + call s:define_commands() + return 1 +endfunction + +function! s:define_commands() + command! -nargs=+ -bar Plug call plug#() + if !executable('git') + return s:err('`git` executable not found. Most commands will not be available. To suppress this message, prepend `silent!` to `call plug#begin(...)`.') + endif + command! -nargs=* -bar -bang -complete=customlist,s:names PlugInstall call s:install(0, []) + command! -nargs=* -bar -bang -complete=customlist,s:names PlugUpdate call s:update(0, []) + command! -nargs=0 -bar -bang PlugClean call s:clean(0) + command! -nargs=0 -bar PlugUpgrade if s:upgrade() | execute 'source' s:esc(s:me) | endif + command! -nargs=0 -bar PlugStatus call s:status() + command! -nargs=0 -bar PlugDiff call s:diff() + command! -nargs=? -bar -bang -complete=file PlugSnapshot call s:snapshot(0, ) +endfunction + +function! s:to_a(v) + return type(a:v) == s:TYPE.list ? a:v : [a:v] +endfunction + +function! s:to_s(v) + return type(a:v) == s:TYPE.string ? a:v : join(a:v, "\n") . "\n" +endfunction + +function! s:glob(from, pattern) + return s:lines(globpath(a:from, a:pattern)) +endfunction + +function! s:source(from, ...) + let found = 0 + for pattern in a:000 + for vim in s:glob(a:from, pattern) + execute 'source' s:esc(vim) + let found = 1 + endfor + endfor + return found +endfunction + +function! s:assoc(dict, key, val) + let a:dict[a:key] = add(get(a:dict, a:key, []), a:val) +endfunction + +function! s:ask(message, ...) + call inputsave() + echohl WarningMsg + let answer = input(a:message.(a:0 ? ' (y/N/a) ' : ' (y/N) ')) + echohl None + call inputrestore() + echo "\r" + return (a:0 && answer =~? '^a') ? 2 : (answer =~? '^y') ? 1 : 0 +endfunction + +function! s:ask_no_interrupt(...) + try + return call('s:ask', a:000) + catch + return 0 + endtry +endfunction + +function! plug#end() + if !exists('g:plugs') + return s:err('Call plug#begin() first') + endif + + if exists('#PlugLOD') + augroup PlugLOD + autocmd! + augroup END + augroup! PlugLOD + endif + let lod = { 'ft': {}, 'map': {}, 'cmd': {} } + + if exists('g:did_load_filetypes') + filetype off + endif + for name in g:plugs_order + if !has_key(g:plugs, name) + continue + endif + let plug = g:plugs[name] + if get(s:loaded, name, 0) || !has_key(plug, 'on') && !has_key(plug, 'for') + let s:loaded[name] = 1 + continue + endif + + if has_key(plug, 'on') + let s:triggers[name] = { 'map': [], 'cmd': [] } + for cmd in s:to_a(plug.on) + if cmd =~? '^.\+' + if empty(mapcheck(cmd)) && empty(mapcheck(cmd, 'i')) + call s:assoc(lod.map, cmd, name) + endif + call add(s:triggers[name].map, cmd) + elseif cmd =~# '^[A-Z]' + let cmd = substitute(cmd, '!*$', '', '') + if exists(':'.cmd) != 2 + call s:assoc(lod.cmd, cmd, name) + endif + call add(s:triggers[name].cmd, cmd) + else + call s:err('Invalid `on` option: '.cmd. + \ '. Should start with an uppercase letter or ``.') + endif + endfor + endif + + if has_key(plug, 'for') + let types = s:to_a(plug.for) + if !empty(types) + augroup filetypedetect + call s:source(s:rtp(plug), 'ftdetect/**/*.vim', 'after/ftdetect/**/*.vim') + augroup END + endif + for type in types + call s:assoc(lod.ft, type, name) + endfor + endif + endfor + + for [cmd, names] in items(lod.cmd) + execute printf( + \ 'command! -nargs=* -range -bang -complete=file %s call s:lod_cmd(%s, "", , , , %s)', + \ cmd, string(cmd), string(names)) + endfor + + for [map, names] in items(lod.map) + for [mode, map_prefix, key_prefix] in + \ [['i', '', ''], ['n', '', ''], ['v', '', 'gv'], ['o', '', '']] + execute printf( + \ '%snoremap %s %s:call lod_map(%s, %s, %s, "%s")', + \ mode, map, map_prefix, string(map), string(names), mode != 'i', key_prefix) + endfor + endfor + + for [ft, names] in items(lod.ft) + augroup PlugLOD + execute printf('autocmd FileType %s call lod_ft(%s, %s)', + \ ft, string(ft), string(names)) + augroup END + endfor + + call s:reorg_rtp() + filetype plugin indent on + if has('vim_starting') + if has('syntax') && !exists('g:syntax_on') + syntax enable + end + else + call s:reload_plugins() + endif +endfunction + +function! s:loaded_names() + return filter(copy(g:plugs_order), 'get(s:loaded, v:val, 0)') +endfunction + +function! s:load_plugin(spec) + call s:source(s:rtp(a:spec), 'plugin/**/*.vim', 'after/plugin/**/*.vim') +endfunction + +function! s:reload_plugins() + for name in s:loaded_names() + call s:load_plugin(g:plugs[name]) + endfor +endfunction + +function! s:trim(str) + return substitute(a:str, '[\/]\+$', '', '') +endfunction + +function! s:version_requirement(val, min) + for idx in range(0, len(a:min) - 1) + let v = get(a:val, idx, 0) + if v < a:min[idx] | return 0 + elseif v > a:min[idx] | return 1 + endif + endfor + return 1 +endfunction + +function! s:git_version_requirement(...) + if !exists('s:git_version') + let s:git_version = map(split(split(s:system('git --version'))[2], '\.'), 'str2nr(v:val)') + endif + return s:version_requirement(s:git_version, a:000) +endfunction + +function! s:progress_opt(base) + return a:base && !s:is_win && + \ s:git_version_requirement(1, 7, 1) ? '--progress' : '' +endfunction + +if s:is_win + function! s:rtp(spec) + return s:path(a:spec.dir . get(a:spec, 'rtp', '')) + endfunction + + function! s:path(path) + return s:trim(substitute(a:path, '/', '\', 'g')) + endfunction + + function! s:dirpath(path) + return s:path(a:path) . '\' + endfunction + + function! s:is_local_plug(repo) + return a:repo =~? '^[a-z]:\|^[%~]' + endfunction +else + function! s:rtp(spec) + return s:dirpath(a:spec.dir . get(a:spec, 'rtp', '')) + endfunction + + function! s:path(path) + return s:trim(a:path) + endfunction + + function! s:dirpath(path) + return substitute(a:path, '[/\\]*$', '/', '') + endfunction + + function! s:is_local_plug(repo) + return a:repo[0] =~ '[/$~]' + endfunction +endif + +function! s:err(msg) + echohl ErrorMsg + echom '[vim-plug] '.a:msg + echohl None +endfunction + +function! s:warn(cmd, msg) + echohl WarningMsg + execute a:cmd 'a:msg' + echohl None +endfunction + +function! s:esc(path) + return escape(a:path, ' ') +endfunction + +function! s:escrtp(path) + return escape(a:path, ' ,') +endfunction + +function! s:remove_rtp() + for name in s:loaded_names() + let rtp = s:rtp(g:plugs[name]) + execute 'set rtp-='.s:escrtp(rtp) + let after = globpath(rtp, 'after') + if isdirectory(after) + execute 'set rtp-='.s:escrtp(after) + endif + endfor +endfunction + +function! s:reorg_rtp() + if !empty(s:first_rtp) + execute 'set rtp-='.s:first_rtp + execute 'set rtp-='.s:last_rtp + endif + + " &rtp is modified from outside + if exists('s:prtp') && s:prtp !=# &rtp + call s:remove_rtp() + unlet! s:middle + endif + + let s:middle = get(s:, 'middle', &rtp) + let rtps = map(s:loaded_names(), 's:rtp(g:plugs[v:val])') + let afters = filter(map(copy(rtps), 'globpath(v:val, "after")'), '!empty(v:val)') + let rtp = join(map(rtps, 'escape(v:val, ",")'), ',') + \ . ','.s:middle.',' + \ . join(map(afters, 'escape(v:val, ",")'), ',') + let &rtp = substitute(substitute(rtp, ',,*', ',', 'g'), '^,\|,$', '', 'g') + let s:prtp = &rtp + + if !empty(s:first_rtp) + execute 'set rtp^='.s:first_rtp + execute 'set rtp+='.s:last_rtp + endif +endfunction + +function! s:doautocmd(...) + if exists('#'.join(a:000, '#')) + execute 'doautocmd' ((v:version > 703 || has('patch442')) ? '' : '') join(a:000) + endif +endfunction + +function! s:dobufread(names) + for name in a:names + let path = s:rtp(g:plugs[name]).'/**' + for dir in ['ftdetect', 'ftplugin'] + if len(finddir(dir, path)) + if exists('#BufRead') + doautocmd BufRead + endif + return + endif + endfor + endfor +endfunction + +function! plug#load(...) + if a:0 == 0 + return s:err('Argument missing: plugin name(s) required') + endif + if !exists('g:plugs') + return s:err('plug#begin was not called') + endif + let names = a:0 == 1 && type(a:1) == s:TYPE.list ? a:1 : a:000 + let unknowns = filter(copy(names), '!has_key(g:plugs, v:val)') + if !empty(unknowns) + let s = len(unknowns) > 1 ? 's' : '' + return s:err(printf('Unknown plugin%s: %s', s, join(unknowns, ', '))) + end + let unloaded = filter(copy(names), '!get(s:loaded, v:val, 0)') + if !empty(unloaded) + for name in unloaded + call s:lod([name], ['ftdetect', 'after/ftdetect', 'plugin', 'after/plugin']) + endfor + call s:dobufread(unloaded) + return 1 + end + return 0 +endfunction + +function! s:remove_triggers(name) + if !has_key(s:triggers, a:name) + return + endif + for cmd in s:triggers[a:name].cmd + execute 'silent! delc' cmd + endfor + for map in s:triggers[a:name].map + execute 'silent! unmap' map + execute 'silent! iunmap' map + endfor + call remove(s:triggers, a:name) +endfunction + +function! s:lod(names, types, ...) + for name in a:names + call s:remove_triggers(name) + let s:loaded[name] = 1 + endfor + call s:reorg_rtp() + + for name in a:names + let rtp = s:rtp(g:plugs[name]) + for dir in a:types + call s:source(rtp, dir.'/**/*.vim') + endfor + if a:0 + if !s:source(rtp, a:1) && !empty(s:glob(rtp, a:2)) + execute 'runtime' a:1 + endif + call s:source(rtp, a:2) + endif + call s:doautocmd('User', name) + endfor +endfunction + +function! s:lod_ft(pat, names) + let syn = 'syntax/'.a:pat.'.vim' + call s:lod(a:names, ['plugin', 'after/plugin'], syn, 'after/'.syn) + execute 'autocmd! PlugLOD FileType' a:pat + call s:doautocmd('filetypeplugin', 'FileType') + call s:doautocmd('filetypeindent', 'FileType') +endfunction + +function! s:lod_cmd(cmd, bang, l1, l2, args, names) + call s:lod(a:names, ['ftdetect', 'after/ftdetect', 'plugin', 'after/plugin']) + call s:dobufread(a:names) + execute printf('%s%s%s %s', (a:l1 == a:l2 ? '' : (a:l1.','.a:l2)), a:cmd, a:bang, a:args) +endfunction + +function! s:lod_map(map, names, with_prefix, prefix) + call s:lod(a:names, ['ftdetect', 'after/ftdetect', 'plugin', 'after/plugin']) + call s:dobufread(a:names) + let extra = '' + while 1 + let c = getchar(0) + if c == 0 + break + endif + let extra .= nr2char(c) + endwhile + + if a:with_prefix + let prefix = v:count ? v:count : '' + let prefix .= '"'.v:register.a:prefix + if mode(1) == 'no' + if v:operator == 'c' + let prefix = "\" . prefix + endif + let prefix .= v:operator + endif + call feedkeys(prefix, 'n') + endif + call feedkeys(substitute(a:map, '^', "\", '') . extra) +endfunction + +function! plug#(repo, ...) + if a:0 > 1 + return s:err('Invalid number of arguments (1..2)') + endif + + try + let repo = s:trim(a:repo) + let opts = a:0 == 1 ? s:parse_options(a:1) : s:base_spec + let name = get(opts, 'as', fnamemodify(repo, ':t:s?\.git$??')) + let spec = extend(s:infer_properties(name, repo), opts) + if !has_key(g:plugs, name) + call add(g:plugs_order, name) + endif + let g:plugs[name] = spec + let s:loaded[name] = get(s:loaded, name, 0) + catch + return s:err(v:exception) + endtry +endfunction + +function! s:parse_options(arg) + let opts = copy(s:base_spec) + let type = type(a:arg) + if type == s:TYPE.string + let opts.tag = a:arg + elseif type == s:TYPE.dict + call extend(opts, a:arg) + if has_key(opts, 'dir') + let opts.dir = s:dirpath(expand(opts.dir)) + endif + else + throw 'Invalid argument type (expected: string or dictionary)' + endif + return opts +endfunction + +function! s:infer_properties(name, repo) + let repo = a:repo + if s:is_local_plug(repo) + return { 'dir': s:dirpath(expand(repo)) } + else + if repo =~ ':' + let uri = repo + else + if repo !~ '/' + throw printf('Invalid argument: %s (implicit `vim-scripts'' expansion is deprecated)', repo) + endif + let fmt = get(g:, 'plug_url_format', 'https://git::@github.com/%s.git') + let uri = printf(fmt, repo) + endif + return { 'dir': s:dirpath(g:plug_home.'/'.a:name), 'uri': uri } + endif +endfunction + +function! s:install(force, names) + call s:update_impl(0, a:force, a:names) +endfunction + +function! s:update(force, names) + call s:update_impl(1, a:force, a:names) +endfunction + +function! plug#helptags() + if !exists('g:plugs') + return s:err('plug#begin was not called') + endif + for spec in values(g:plugs) + let docd = join([s:rtp(spec), 'doc'], '/') + if isdirectory(docd) + silent! execute 'helptags' s:esc(docd) + endif + endfor + return 1 +endfunction + +function! s:syntax() + syntax clear + syntax region plug1 start=/\%1l/ end=/\%2l/ contains=plugNumber + syntax region plug2 start=/\%2l/ end=/\%3l/ contains=plugBracket,plugX + syn match plugNumber /[0-9]\+[0-9.]*/ contained + syn match plugBracket /[[\]]/ contained + syn match plugX /x/ contained + syn match plugDash /^-/ + syn match plugPlus /^+/ + syn match plugStar /^*/ + syn match plugMessage /\(^- \)\@<=.*/ + syn match plugName /\(^- \)\@<=[^ ]*:/ + syn match plugSha /\%(: \)\@<=[0-9a-f]\{4,}$/ + syn match plugTag /(tag: [^)]\+)/ + syn match plugInstall /\(^+ \)\@<=[^:]*/ + syn match plugUpdate /\(^* \)\@<=[^:]*/ + syn match plugCommit /^ \X*[0-9a-f]\{7,9} .*/ contains=plugRelDate,plugEdge,plugTag + syn match plugEdge /^ \X\+$/ + syn match plugEdge /^ \X*/ contained nextgroup=plugSha + syn match plugSha /[0-9a-f]\{7,9}/ contained + syn match plugRelDate /([^)]*)$/ contained + syn match plugNotLoaded /(not loaded)$/ + syn match plugError /^x.*/ + syn region plugDeleted start=/^\~ .*/ end=/^\ze\S/ + syn match plugH2 /^.*:\n-\+$/ + syn keyword Function PlugInstall PlugStatus PlugUpdate PlugClean + hi def link plug1 Title + hi def link plug2 Repeat + hi def link plugH2 Type + hi def link plugX Exception + hi def link plugBracket Structure + hi def link plugNumber Number + + hi def link plugDash Special + hi def link plugPlus Constant + hi def link plugStar Boolean + + hi def link plugMessage Function + hi def link plugName Label + hi def link plugInstall Function + hi def link plugUpdate Type + + hi def link plugError Error + hi def link plugDeleted Ignore + hi def link plugRelDate Comment + hi def link plugEdge PreProc + hi def link plugSha Identifier + hi def link plugTag Constant + + hi def link plugNotLoaded Comment +endfunction + +function! s:lpad(str, len) + return a:str . repeat(' ', a:len - len(a:str)) +endfunction + +function! s:lines(msg) + return split(a:msg, "[\r\n]") +endfunction + +function! s:lastline(msg) + return get(s:lines(a:msg), -1, '') +endfunction + +function! s:new_window() + execute get(g:, 'plug_window', 'vertical topleft new') +endfunction + +function! s:plug_window_exists() + let buflist = tabpagebuflist(s:plug_tab) + return !empty(buflist) && index(buflist, s:plug_buf) >= 0 +endfunction + +function! s:switch_in() + if !s:plug_window_exists() + return 0 + endif + + if winbufnr(0) != s:plug_buf + let s:pos = [tabpagenr(), winnr(), winsaveview()] + execute 'normal!' s:plug_tab.'gt' + let winnr = bufwinnr(s:plug_buf) + execute winnr.'wincmd w' + call add(s:pos, winsaveview()) + else + let s:pos = [winsaveview()] + endif + + setlocal modifiable + return 1 +endfunction + +function! s:switch_out(...) + call winrestview(s:pos[-1]) + setlocal nomodifiable + if a:0 > 0 + execute a:1 + endif + + if len(s:pos) > 1 + execute 'normal!' s:pos[0].'gt' + execute s:pos[1] 'wincmd w' + call winrestview(s:pos[2]) + endif +endfunction + +function! s:finish_bindings() + nnoremap R :call retry() + nnoremap D :PlugDiff + nnoremap S :PlugStatus + nnoremap U :call status_update() + xnoremap U :call status_update() + nnoremap ]] :silent! call section('') + nnoremap [[ :silent! call section('b') +endfunction + +function! s:prepare(...) + if empty(getcwd()) + throw 'Invalid current working directory. Cannot proceed.' + endif + + for evar in ['$GIT_DIR', '$GIT_WORK_TREE'] + if exists(evar) + throw evar.' detected. Cannot proceed.' + endif + endfor + + call s:job_abort() + if s:switch_in() + if b:plug_preview == 1 + pc + endif + enew + else + call s:new_window() + endif + + nnoremap q :if b:plug_preview==1pcendifbd + if a:0 == 0 + call s:finish_bindings() + endif + let b:plug_preview = -1 + let s:plug_tab = tabpagenr() + let s:plug_buf = winbufnr(0) + call s:assign_name() + + for k in ['', 'L', 'o', 'X', 'd', 'dd'] + execute 'silent! unmap ' k + endfor + setlocal buftype=nofile bufhidden=wipe nobuflisted nolist noswapfile nowrap cursorline modifiable nospell + setf vim-plug + if exists('g:syntax_on') + call s:syntax() + endif +endfunction + +function! s:assign_name() + " Assign buffer name + let prefix = '[Plugins]' + let name = prefix + let idx = 2 + while bufexists(name) + let name = printf('%s (%s)', prefix, idx) + let idx = idx + 1 + endwhile + silent! execute 'f' fnameescape(name) +endfunction + +function! s:chsh(swap) + let prev = [&shell, &shellcmdflag, &shellredir] + if s:is_win + set shell=cmd.exe shellcmdflag=/c shellredir=>%s\ 2>&1 + elseif a:swap + set shell=sh shellredir=>%s\ 2>&1 + endif + return prev +endfunction + +function! s:bang(cmd, ...) + try + let [sh, shellcmdflag, shrd] = s:chsh(a:0) + " FIXME: Escaping is incomplete. We could use shellescape with eval, + " but it won't work on Windows. + let cmd = a:0 ? s:with_cd(a:cmd, a:1) : a:cmd + if s:is_win + let batchfile = tempname().'.bat' + call writefile(['@echo off', cmd], batchfile) + let cmd = batchfile + endif + let g:_plug_bang = (s:is_win && has('gui_running') ? 'silent ' : '').'!'.escape(cmd, '#!%') + execute "normal! :execute g:_plug_bang\\" + finally + unlet g:_plug_bang + let [&shell, &shellcmdflag, &shellredir] = [sh, shellcmdflag, shrd] + if s:is_win + call delete(batchfile) + endif + endtry + return v:shell_error ? 'Exit status: ' . v:shell_error : '' +endfunction + +function! s:regress_bar() + let bar = substitute(getline(2)[1:-2], '.*\zs=', 'x', '') + call s:progress_bar(2, bar, len(bar)) +endfunction + +function! s:is_updated(dir) + return !empty(s:system_chomp('git log --pretty=format:"%h" "HEAD...HEAD@{1}"', a:dir)) +endfunction + +function! s:do(pull, force, todo) + for [name, spec] in items(a:todo) + if !isdirectory(spec.dir) + continue + endif + let installed = has_key(s:update.new, name) + let updated = installed ? 0 : + \ (a:pull && index(s:update.errors, name) < 0 && s:is_updated(spec.dir)) + if a:force || installed || updated + execute 'cd' s:esc(spec.dir) + call append(3, '- Post-update hook for '. name .' ... ') + let error = '' + let type = type(spec.do) + if type == s:TYPE.string + if spec.do[0] == ':' + if !get(s:loaded, name, 0) + let s:loaded[name] = 1 + call s:reorg_rtp() + endif + call s:load_plugin(spec) + try + execute spec.do[1:] + catch + let error = v:exception + endtry + if !s:plug_window_exists() + cd - + throw 'Warning: vim-plug was terminated by the post-update hook of '.name + endif + else + let error = s:bang(spec.do) + endif + elseif type == s:TYPE.funcref + try + let status = installed ? 'installed' : (updated ? 'updated' : 'unchanged') + call spec.do({ 'name': name, 'status': status, 'force': a:force }) + catch + let error = v:exception + endtry + else + let error = 'Invalid hook type' + endif + call s:switch_in() + call setline(4, empty(error) ? (getline(4) . 'OK') + \ : ('x' . getline(4)[1:] . error)) + if !empty(error) + call add(s:update.errors, name) + call s:regress_bar() + endif + cd - + endif + endfor +endfunction + +function! s:hash_match(a, b) + return stridx(a:a, a:b) == 0 || stridx(a:b, a:a) == 0 +endfunction + +function! s:checkout(spec) + let sha = a:spec.commit + let output = s:system('git rev-parse HEAD', a:spec.dir) + if !v:shell_error && !s:hash_match(sha, s:lines(output)[0]) + let output = s:system( + \ 'git fetch --depth 999999 && git checkout '.s:esc(sha).' --', a:spec.dir) + endif + return output +endfunction + +function! s:finish(pull) + let new_frozen = len(filter(keys(s:update.new), 'g:plugs[v:val].frozen')) + if new_frozen + let s = new_frozen > 1 ? 's' : '' + call append(3, printf('- Installed %d frozen plugin%s', new_frozen, s)) + endif + call append(3, '- Finishing ... ') | 4 + redraw + call plug#helptags() + call plug#end() + call setline(4, getline(4) . 'Done!') + redraw + let msgs = [] + if !empty(s:update.errors) + call add(msgs, "Press 'R' to retry.") + endif + if a:pull && len(s:update.new) < len(filter(getline(5, '$'), + \ "v:val =~ '^- ' && v:val !~# 'Already up.to.date'")) + call add(msgs, "Press 'D' to see the updated changes.") + endif + echo join(msgs, ' ') + call s:finish_bindings() +endfunction + +function! s:retry() + if empty(s:update.errors) + return + endif + echo + call s:update_impl(s:update.pull, s:update.force, + \ extend(copy(s:update.errors), [s:update.threads])) +endfunction + +function! s:is_managed(name) + return has_key(g:plugs[a:name], 'uri') +endfunction + +function! s:names(...) + return sort(filter(keys(g:plugs), 'stridx(v:val, a:1) == 0 && s:is_managed(v:val)')) +endfunction + +function! s:check_ruby() + silent! ruby require 'thread'; VIM::command("let g:plug_ruby = '#{RUBY_VERSION}'") + if !exists('g:plug_ruby') + redraw! + return s:warn('echom', 'Warning: Ruby interface is broken') + endif + let ruby_version = split(g:plug_ruby, '\.') + unlet g:plug_ruby + return s:version_requirement(ruby_version, [1, 8, 7]) +endfunction + +function! s:update_impl(pull, force, args) abort + let sync = index(a:args, '--sync') >= 0 || has('vim_starting') + let args = filter(copy(a:args), 'v:val != "--sync"') + let threads = (len(args) > 0 && args[-1] =~ '^[1-9][0-9]*$') ? + \ remove(args, -1) : get(g:, 'plug_threads', 16) + + let managed = filter(copy(g:plugs), 's:is_managed(v:key)') + let todo = empty(args) ? filter(managed, '!v:val.frozen || !isdirectory(v:val.dir)') : + \ filter(managed, 'index(args, v:key) >= 0') + + if empty(todo) + return s:warn('echo', 'No plugin to '. (a:pull ? 'update' : 'install')) + endif + + if !s:is_win && s:git_version_requirement(2, 3) + let s:git_terminal_prompt = exists('$GIT_TERMINAL_PROMPT') ? $GIT_TERMINAL_PROMPT : '' + let $GIT_TERMINAL_PROMPT = 0 + for plug in values(todo) + let plug.uri = substitute(plug.uri, + \ '^https://git::@github\.com', 'https://github.com', '') + endfor + endif + + if !isdirectory(g:plug_home) + try + call mkdir(g:plug_home, 'p') + catch + return s:err(printf('Invalid plug directory: %s. '. + \ 'Try to call plug#begin with a valid directory', g:plug_home)) + endtry + endif + + if has('nvim') && !exists('*jobwait') && threads > 1 + call s:warn('echom', '[vim-plug] Update Neovim for parallel installer') + endif + + let use_job = s:nvim || s:vim8 + let python = (has('python') || has('python3')) && !use_job + let ruby = has('ruby') && !use_job && (v:version >= 703 || v:version == 702 && has('patch374')) && !(s:is_win && has('gui_running')) && threads > 1 && s:check_ruby() + + let s:update = { + \ 'start': reltime(), + \ 'all': todo, + \ 'todo': copy(todo), + \ 'errors': [], + \ 'pull': a:pull, + \ 'force': a:force, + \ 'new': {}, + \ 'threads': (python || ruby || use_job) ? min([len(todo), threads]) : 1, + \ 'bar': '', + \ 'fin': 0 + \ } + + call s:prepare(1) + call append(0, ['', '']) + normal! 2G + silent! redraw + + let s:clone_opt = get(g:, 'plug_shallow', 1) ? + \ '--depth 1' . (s:git_version_requirement(1, 7, 10) ? ' --no-single-branch' : '') : '' + + if has('win32unix') + let s:clone_opt .= ' -c core.eol=lf -c core.autocrlf=input' + endif + + " Python version requirement (>= 2.7) + if python && !has('python3') && !ruby && !use_job && s:update.threads > 1 + redir => pyv + silent python import platform; print platform.python_version() + redir END + let python = s:version_requirement( + \ map(split(split(pyv)[0], '\.'), 'str2nr(v:val)'), [2, 6]) + endif + + if (python || ruby) && s:update.threads > 1 + try + let imd = &imd + if s:mac_gui + set noimd + endif + if ruby + call s:update_ruby() + else + call s:update_python() + endif + catch + let lines = getline(4, '$') + let printed = {} + silent! 4,$d _ + for line in lines + let name = s:extract_name(line, '.', '') + if empty(name) || !has_key(printed, name) + call append('$', line) + if !empty(name) + let printed[name] = 1 + if line[0] == 'x' && index(s:update.errors, name) < 0 + call add(s:update.errors, name) + end + endif + endif + endfor + finally + let &imd = imd + call s:update_finish() + endtry + else + call s:update_vim() + while use_job && sync + sleep 100m + if s:update.fin + break + endif + endwhile + endif +endfunction + +function! s:log4(name, msg) + call setline(4, printf('- %s (%s)', a:msg, a:name)) + redraw +endfunction + +function! s:update_finish() + if exists('s:git_terminal_prompt') + let $GIT_TERMINAL_PROMPT = s:git_terminal_prompt + endif + if s:switch_in() + call append(3, '- Updating ...') | 4 + for [name, spec] in items(filter(copy(s:update.all), 'index(s:update.errors, v:key) < 0 && (s:update.force || s:update.pull || has_key(s:update.new, v:key))')) + let [pos, _] = s:logpos(name) + if !pos + continue + endif + if has_key(spec, 'commit') + call s:log4(name, 'Checking out '.spec.commit) + let out = s:checkout(spec) + elseif has_key(spec, 'tag') + let tag = spec.tag + if tag =~ '\*' + let tags = s:lines(s:system('git tag --list '.s:shellesc(tag).' --sort -version:refname 2>&1', spec.dir)) + if !v:shell_error && !empty(tags) + let tag = tags[0] + call s:log4(name, printf('Latest tag for %s -> %s', spec.tag, tag)) + call append(3, '') + endif + endif + call s:log4(name, 'Checking out '.tag) + let out = s:system('git checkout -q '.s:esc(tag).' -- 2>&1', spec.dir) + else + let branch = s:esc(get(spec, 'branch', 'master')) + call s:log4(name, 'Merging origin/'.branch) + let out = s:system('git checkout -q '.branch.' -- 2>&1' + \. (has_key(s:update.new, name) ? '' : ('&& git merge --ff-only origin/'.branch.' 2>&1')), spec.dir) + endif + if !v:shell_error && filereadable(spec.dir.'/.gitmodules') && + \ (s:update.force || has_key(s:update.new, name) || s:is_updated(spec.dir)) + call s:log4(name, 'Updating submodules. This may take a while.') + let out .= s:bang('git submodule update --init --recursive 2>&1', spec.dir) + endif + let msg = s:format_message(v:shell_error ? 'x': '-', name, out) + if v:shell_error + call add(s:update.errors, name) + call s:regress_bar() + silent execute pos 'd _' + call append(4, msg) | 4 + elseif !empty(out) + call setline(pos, msg[0]) + endif + redraw + endfor + silent 4 d _ + try + call s:do(s:update.pull, s:update.force, filter(copy(s:update.all), 'index(s:update.errors, v:key) < 0 && has_key(v:val, "do")')) + catch + call s:warn('echom', v:exception) + call s:warn('echo', '') + return + endtry + call s:finish(s:update.pull) + call setline(1, 'Updated. Elapsed time: ' . split(reltimestr(reltime(s:update.start)))[0] . ' sec.') + call s:switch_out('normal! gg') + endif +endfunction + +function! s:job_abort() + if (!s:nvim && !s:vim8) || !exists('s:jobs') + return + endif + + for [name, j] in items(s:jobs) + if s:nvim + silent! call jobstop(j.jobid) + elseif s:vim8 + silent! call job_stop(j.jobid) + endif + if j.new + call s:system('rm -rf ' . s:shellesc(g:plugs[name].dir)) + endif + endfor + let s:jobs = {} +endfunction + +function! s:last_non_empty_line(lines) + let len = len(a:lines) + for idx in range(len) + let line = a:lines[len-idx-1] + if !empty(line) + return line + endif + endfor + return '' +endfunction + +function! s:job_out_cb(self, data) abort + let self = a:self + let data = remove(self.lines, -1) . a:data + let lines = map(split(data, "\n", 1), 'split(v:val, "\r", 1)[-1]') + call extend(self.lines, lines) + " To reduce the number of buffer updates + let self.tick = get(self, 'tick', -1) + 1 + if !self.running || self.tick % len(s:jobs) == 0 + let bullet = self.running ? (self.new ? '+' : '*') : (self.error ? 'x' : '-') + let result = self.error ? join(self.lines, "\n") : s:last_non_empty_line(self.lines) + call s:log(bullet, self.name, result) + endif +endfunction + +function! s:job_exit_cb(self, data) abort + let a:self.running = 0 + let a:self.error = a:data != 0 + call s:reap(a:self.name) + call s:tick() +endfunction + +function! s:job_cb(fn, job, ch, data) + if !s:plug_window_exists() " plug window closed + return s:job_abort() + endif + call call(a:fn, [a:job, a:data]) +endfunction + +function! s:nvim_cb(job_id, data, event) dict abort + return a:event == 'stdout' ? + \ s:job_cb('s:job_out_cb', self, 0, join(a:data, "\n")) : + \ s:job_cb('s:job_exit_cb', self, 0, a:data) +endfunction + +function! s:spawn(name, cmd, opts) + let job = { 'name': a:name, 'running': 1, 'error': 0, 'lines': [''], + \ 'batchfile': (s:is_win && (s:nvim || s:vim8)) ? tempname().'.bat' : '', + \ 'new': get(a:opts, 'new', 0) } + let s:jobs[a:name] = job + let cmd = has_key(a:opts, 'dir') ? s:with_cd(a:cmd, a:opts.dir) : a:cmd + if !empty(job.batchfile) + call writefile(['@echo off', cmd], job.batchfile) + let cmd = job.batchfile + endif + let argv = add(s:is_win ? ['cmd', '/c'] : ['sh', '-c'], cmd) + + if s:nvim + call extend(job, { + \ 'on_stdout': function('s:nvim_cb'), + \ 'on_exit': function('s:nvim_cb'), + \ }) + let jid = jobstart(argv, job) + if jid > 0 + let job.jobid = jid + else + let job.running = 0 + let job.error = 1 + let job.lines = [jid < 0 ? argv[0].' is not executable' : + \ 'Invalid arguments (or job table is full)'] + endif + elseif s:vim8 + let jid = job_start(s:is_win ? join(argv, ' ') : argv, { + \ 'out_cb': function('s:job_cb', ['s:job_out_cb', job]), + \ 'exit_cb': function('s:job_cb', ['s:job_exit_cb', job]), + \ 'out_mode': 'raw' + \}) + if job_status(jid) == 'run' + let job.jobid = jid + else + let job.running = 0 + let job.error = 1 + let job.lines = ['Failed to start job'] + endif + else + let job.lines = s:lines(call('s:system', [cmd])) + let job.error = v:shell_error != 0 + let job.running = 0 + endif +endfunction + +function! s:reap(name) + let job = s:jobs[a:name] + if job.error + call add(s:update.errors, a:name) + elseif get(job, 'new', 0) + let s:update.new[a:name] = 1 + endif + let s:update.bar .= job.error ? 'x' : '=' + + let bullet = job.error ? 'x' : '-' + let result = job.error ? join(job.lines, "\n") : s:last_non_empty_line(job.lines) + call s:log(bullet, a:name, empty(result) ? 'OK' : result) + call s:bar() + + if has_key(job, 'batchfile') && !empty(job.batchfile) + call delete(job.batchfile) + endif + call remove(s:jobs, a:name) +endfunction + +function! s:bar() + if s:switch_in() + let total = len(s:update.all) + call setline(1, (s:update.pull ? 'Updating' : 'Installing'). + \ ' plugins ('.len(s:update.bar).'/'.total.')') + call s:progress_bar(2, s:update.bar, total) + call s:switch_out() + endif +endfunction + +function! s:logpos(name) + for i in range(4, line('$')) + if getline(i) =~# '^[-+x*] '.a:name.':' + for j in range(i + 1, line('$')) + if getline(j) !~ '^ ' + return [i, j - 1] + endif + endfor + return [i, i] + endif + endfor + return [0, 0] +endfunction + +function! s:log(bullet, name, lines) + if s:switch_in() + let [b, e] = s:logpos(a:name) + if b > 0 + silent execute printf('%d,%d d _', b, e) + if b > winheight('.') + let b = 4 + endif + else + let b = 4 + endif + " FIXME For some reason, nomodifiable is set after :d in vim8 + setlocal modifiable + call append(b - 1, s:format_message(a:bullet, a:name, a:lines)) + call s:switch_out() + endif +endfunction + +function! s:update_vim() + let s:jobs = {} + + call s:bar() + call s:tick() +endfunction + +function! s:tick() + let pull = s:update.pull + let prog = s:progress_opt(s:nvim || s:vim8) +while 1 " Without TCO, Vim stack is bound to explode + if empty(s:update.todo) + if empty(s:jobs) && !s:update.fin + call s:update_finish() + let s:update.fin = 1 + endif + return + endif + + let name = keys(s:update.todo)[0] + let spec = remove(s:update.todo, name) + let new = !isdirectory(spec.dir) + + call s:log(new ? '+' : '*', name, pull ? 'Updating ...' : 'Installing ...') + redraw + + let has_tag = has_key(spec, 'tag') + if !new + let [error, _] = s:git_validate(spec, 0) + if empty(error) + if pull + let fetch_opt = (has_tag && !empty(globpath(spec.dir, '.git/shallow'))) ? '--depth 99999999' : '' + call s:spawn(name, printf('git fetch %s %s 2>&1', fetch_opt, prog), { 'dir': spec.dir }) + else + let s:jobs[name] = { 'running': 0, 'lines': ['Already installed'], 'error': 0 } + endif + else + let s:jobs[name] = { 'running': 0, 'lines': s:lines(error), 'error': 1 } + endif + else + call s:spawn(name, + \ printf('git clone %s %s %s %s 2>&1', + \ has_tag ? '' : s:clone_opt, + \ prog, + \ s:shellesc(spec.uri), + \ s:shellesc(s:trim(spec.dir))), { 'new': 1 }) + endif + + if !s:jobs[name].running + call s:reap(name) + endif + if len(s:jobs) >= s:update.threads + break + endif +endwhile +endfunction + +function! s:update_python() +let py_exe = has('python') ? 'python' : 'python3' +execute py_exe "<< EOF" +import datetime +import functools +import os +try: + import queue +except ImportError: + import Queue as queue +import random +import re +import shutil +import signal +import subprocess +import tempfile +import threading as thr +import time +import traceback +import vim + +G_NVIM = vim.eval("has('nvim')") == '1' +G_PULL = vim.eval('s:update.pull') == '1' +G_RETRIES = int(vim.eval('get(g:, "plug_retries", 2)')) + 1 +G_TIMEOUT = int(vim.eval('get(g:, "plug_timeout", 60)')) +G_CLONE_OPT = vim.eval('s:clone_opt') +G_PROGRESS = vim.eval('s:progress_opt(1)') +G_LOG_PROB = 1.0 / int(vim.eval('s:update.threads')) +G_STOP = thr.Event() +G_IS_WIN = vim.eval('s:is_win') == '1' + +class PlugError(Exception): + def __init__(self, msg): + self.msg = msg +class CmdTimedOut(PlugError): + pass +class CmdFailed(PlugError): + pass +class InvalidURI(PlugError): + pass +class Action(object): + INSTALL, UPDATE, ERROR, DONE = ['+', '*', 'x', '-'] + +class Buffer(object): + def __init__(self, lock, num_plugs, is_pull): + self.bar = '' + self.event = 'Updating' if is_pull else 'Installing' + self.lock = lock + self.maxy = int(vim.eval('winheight(".")')) + self.num_plugs = num_plugs + + def __where(self, name): + """ Find first line with name in current buffer. Return line num. """ + found, lnum = False, 0 + matcher = re.compile('^[-+x*] {0}:'.format(name)) + for line in vim.current.buffer: + if matcher.search(line) is not None: + found = True + break + lnum += 1 + + if not found: + lnum = -1 + return lnum + + def header(self): + curbuf = vim.current.buffer + curbuf[0] = self.event + ' plugins ({0}/{1})'.format(len(self.bar), self.num_plugs) + + num_spaces = self.num_plugs - len(self.bar) + curbuf[1] = '[{0}{1}]'.format(self.bar, num_spaces * ' ') + + with self.lock: + vim.command('normal! 2G') + vim.command('redraw') + + def write(self, action, name, lines): + first, rest = lines[0], lines[1:] + msg = ['{0} {1}{2}{3}'.format(action, name, ': ' if first else '', first)] + msg.extend([' ' + line for line in rest]) + + try: + if action == Action.ERROR: + self.bar += 'x' + vim.command("call add(s:update.errors, '{0}')".format(name)) + elif action == Action.DONE: + self.bar += '=' + + curbuf = vim.current.buffer + lnum = self.__where(name) + if lnum != -1: # Found matching line num + del curbuf[lnum] + if lnum > self.maxy and action in set([Action.INSTALL, Action.UPDATE]): + lnum = 3 + else: + lnum = 3 + curbuf.append(msg, lnum) + + self.header() + except vim.error: + pass + +class Command(object): + CD = 'cd /d' if G_IS_WIN else 'cd' + + def __init__(self, cmd, cmd_dir=None, timeout=60, cb=None, clean=None): + self.cmd = cmd + if cmd_dir: + self.cmd = '{0} {1} && {2}'.format(Command.CD, cmd_dir, self.cmd) + self.timeout = timeout + self.callback = cb if cb else (lambda msg: None) + self.clean = clean if clean else (lambda: None) + self.proc = None + + @property + def alive(self): + """ Returns true only if command still running. """ + return self.proc and self.proc.poll() is None + + def execute(self, ntries=3): + """ Execute the command with ntries if CmdTimedOut. + Returns the output of the command if no Exception. + """ + attempt, finished, limit = 0, False, self.timeout + + while not finished: + try: + attempt += 1 + result = self.try_command() + finished = True + return result + except CmdTimedOut: + if attempt != ntries: + self.notify_retry() + self.timeout += limit + else: + raise + + def notify_retry(self): + """ Retry required for command, notify user. """ + for count in range(3, 0, -1): + if G_STOP.is_set(): + raise KeyboardInterrupt + msg = 'Timeout. Will retry in {0} second{1} ...'.format( + count, 's' if count != 1 else '') + self.callback([msg]) + time.sleep(1) + self.callback(['Retrying ...']) + + def try_command(self): + """ Execute a cmd & poll for callback. Returns list of output. + Raises CmdFailed -> return code for Popen isn't 0 + Raises CmdTimedOut -> command exceeded timeout without new output + """ + first_line = True + + try: + tfile = tempfile.NamedTemporaryFile(mode='w+b') + preexec_fn = not G_IS_WIN and os.setsid or None + self.proc = subprocess.Popen(self.cmd, stdout=tfile, + stderr=subprocess.STDOUT, + stdin=subprocess.PIPE, shell=True, + preexec_fn=preexec_fn) + thrd = thr.Thread(target=(lambda proc: proc.wait()), args=(self.proc,)) + thrd.start() + + thread_not_started = True + while thread_not_started: + try: + thrd.join(0.1) + thread_not_started = False + except RuntimeError: + pass + + while self.alive: + if G_STOP.is_set(): + raise KeyboardInterrupt + + if first_line or random.random() < G_LOG_PROB: + first_line = False + line = '' if G_IS_WIN else nonblock_read(tfile.name) + if line: + self.callback([line]) + + time_diff = time.time() - os.path.getmtime(tfile.name) + if time_diff > self.timeout: + raise CmdTimedOut(['Timeout!']) + + thrd.join(0.5) + + tfile.seek(0) + result = [line.decode('utf-8', 'replace').rstrip() for line in tfile] + + if self.proc.returncode != 0: + raise CmdFailed([''] + result) + + return result + except: + self.terminate() + raise + + def terminate(self): + """ Terminate process and cleanup. """ + if self.alive: + if G_IS_WIN: + os.kill(self.proc.pid, signal.SIGINT) + else: + os.killpg(self.proc.pid, signal.SIGTERM) + self.clean() + +class Plugin(object): + def __init__(self, name, args, buf_q, lock): + self.name = name + self.args = args + self.buf_q = buf_q + self.lock = lock + self.tag = args.get('tag', 0) + + def manage(self): + try: + if os.path.exists(self.args['dir']): + self.update() + else: + self.install() + with self.lock: + thread_vim_command("let s:update.new['{0}'] = 1".format(self.name)) + except PlugError as exc: + self.write(Action.ERROR, self.name, exc.msg) + except KeyboardInterrupt: + G_STOP.set() + self.write(Action.ERROR, self.name, ['Interrupted!']) + except: + # Any exception except those above print stack trace + msg = 'Trace:\n{0}'.format(traceback.format_exc().rstrip()) + self.write(Action.ERROR, self.name, msg.split('\n')) + raise + + def install(self): + target = self.args['dir'] + if target[-1] == '\\': + target = target[0:-1] + + def clean(target): + def _clean(): + try: + shutil.rmtree(target) + except OSError: + pass + return _clean + + self.write(Action.INSTALL, self.name, ['Installing ...']) + callback = functools.partial(self.write, Action.INSTALL, self.name) + cmd = 'git clone {0} {1} {2} {3} 2>&1'.format( + '' if self.tag else G_CLONE_OPT, G_PROGRESS, self.args['uri'], + esc(target)) + com = Command(cmd, None, G_TIMEOUT, callback, clean(target)) + result = com.execute(G_RETRIES) + self.write(Action.DONE, self.name, result[-1:]) + + def repo_uri(self): + cmd = 'git rev-parse --abbrev-ref HEAD 2>&1 && git config -f .git/config remote.origin.url' + command = Command(cmd, self.args['dir'], G_TIMEOUT,) + result = command.execute(G_RETRIES) + return result[-1] + + def update(self): + actual_uri = self.repo_uri() + expect_uri = self.args['uri'] + regex = re.compile(r'^(?:\w+://)?(?:[^@/]*@)?([^:/]*(?::[0-9]*)?)[:/](.*?)(?:\.git)?/?$') + ma = regex.match(actual_uri) + mb = regex.match(expect_uri) + if ma is None or mb is None or ma.groups() != mb.groups(): + msg = ['', + 'Invalid URI: {0}'.format(actual_uri), + 'Expected {0}'.format(expect_uri), + 'PlugClean required.'] + raise InvalidURI(msg) + + if G_PULL: + self.write(Action.UPDATE, self.name, ['Updating ...']) + callback = functools.partial(self.write, Action.UPDATE, self.name) + fetch_opt = '--depth 99999999' if self.tag and os.path.isfile(os.path.join(self.args['dir'], '.git/shallow')) else '' + cmd = 'git fetch {0} {1} 2>&1'.format(fetch_opt, G_PROGRESS) + com = Command(cmd, self.args['dir'], G_TIMEOUT, callback) + result = com.execute(G_RETRIES) + self.write(Action.DONE, self.name, result[-1:]) + else: + self.write(Action.DONE, self.name, ['Already installed']) + + def write(self, action, name, msg): + self.buf_q.put((action, name, msg)) + +class PlugThread(thr.Thread): + def __init__(self, tname, args): + super(PlugThread, self).__init__() + self.tname = tname + self.args = args + + def run(self): + thr.current_thread().name = self.tname + buf_q, work_q, lock = self.args + + try: + while not G_STOP.is_set(): + name, args = work_q.get_nowait() + plug = Plugin(name, args, buf_q, lock) + plug.manage() + work_q.task_done() + except queue.Empty: + pass + +class RefreshThread(thr.Thread): + def __init__(self, lock): + super(RefreshThread, self).__init__() + self.lock = lock + self.running = True + + def run(self): + while self.running: + with self.lock: + thread_vim_command('noautocmd normal! a') + time.sleep(0.33) + + def stop(self): + self.running = False + +if G_NVIM: + def thread_vim_command(cmd): + vim.session.threadsafe_call(lambda: vim.command(cmd)) +else: + def thread_vim_command(cmd): + vim.command(cmd) + +def esc(name): + return '"' + name.replace('"', '\"') + '"' + +def nonblock_read(fname): + """ Read a file with nonblock flag. Return the last line. """ + fread = os.open(fname, os.O_RDONLY | os.O_NONBLOCK) + buf = os.read(fread, 100000).decode('utf-8', 'replace') + os.close(fread) + + line = buf.rstrip('\r\n') + left = max(line.rfind('\r'), line.rfind('\n')) + if left != -1: + left += 1 + line = line[left:] + + return line + +def main(): + thr.current_thread().name = 'main' + nthreads = int(vim.eval('s:update.threads')) + plugs = vim.eval('s:update.todo') + mac_gui = vim.eval('s:mac_gui') == '1' + + lock = thr.Lock() + buf = Buffer(lock, len(plugs), G_PULL) + buf_q, work_q = queue.Queue(), queue.Queue() + for work in plugs.items(): + work_q.put(work) + + start_cnt = thr.active_count() + for num in range(nthreads): + tname = 'PlugT-{0:02}'.format(num) + thread = PlugThread(tname, (buf_q, work_q, lock)) + thread.start() + if mac_gui: + rthread = RefreshThread(lock) + rthread.start() + + while not buf_q.empty() or thr.active_count() != start_cnt: + try: + action, name, msg = buf_q.get(True, 0.25) + buf.write(action, name, ['OK'] if not msg else msg) + buf_q.task_done() + except queue.Empty: + pass + except KeyboardInterrupt: + G_STOP.set() + + if mac_gui: + rthread.stop() + rthread.join() + +main() +EOF +endfunction + +function! s:update_ruby() + ruby << EOF + module PlugStream + SEP = ["\r", "\n", nil] + def get_line + buffer = '' + loop do + char = readchar rescue return + if SEP.include? char.chr + buffer << $/ + break + else + buffer << char + end + end + buffer + end + end unless defined?(PlugStream) + + def esc arg + %["#{arg.gsub('"', '\"')}"] + end + + def killall pid + pids = [pid] + if /mswin|mingw|bccwin/ =~ RUBY_PLATFORM + pids.each { |pid| Process.kill 'INT', pid.to_i rescue nil } + else + unless `which pgrep 2> /dev/null`.empty? + children = pids + until children.empty? + children = children.map { |pid| + `pgrep -P #{pid}`.lines.map { |l| l.chomp } + }.flatten + pids += children + end + end + pids.each { |pid| Process.kill 'TERM', pid.to_i rescue nil } + end + end + + def compare_git_uri a, b + regex = %r{^(?:\w+://)?(?:[^@/]*@)?([^:/]*(?::[0-9]*)?)[:/](.*?)(?:\.git)?/?$} + regex.match(a).to_a.drop(1) == regex.match(b).to_a.drop(1) + end + + require 'thread' + require 'fileutils' + require 'timeout' + running = true + iswin = VIM::evaluate('s:is_win').to_i == 1 + pull = VIM::evaluate('s:update.pull').to_i == 1 + base = VIM::evaluate('g:plug_home') + all = VIM::evaluate('s:update.todo') + limit = VIM::evaluate('get(g:, "plug_timeout", 60)') + tries = VIM::evaluate('get(g:, "plug_retries", 2)') + 1 + nthr = VIM::evaluate('s:update.threads').to_i + maxy = VIM::evaluate('winheight(".")').to_i + vim7 = VIM::evaluate('v:version').to_i <= 703 && RUBY_PLATFORM =~ /darwin/ + cd = iswin ? 'cd /d' : 'cd' + tot = VIM::evaluate('len(s:update.todo)') || 0 + bar = '' + skip = 'Already installed' + mtx = Mutex.new + take1 = proc { mtx.synchronize { running && all.shift } } + logh = proc { + cnt = bar.length + $curbuf[1] = "#{pull ? 'Updating' : 'Installing'} plugins (#{cnt}/#{tot})" + $curbuf[2] = '[' + bar.ljust(tot) + ']' + VIM::command('normal! 2G') + VIM::command('redraw') + } + where = proc { |name| (1..($curbuf.length)).find { |l| $curbuf[l] =~ /^[-+x*] #{name}:/ } } + log = proc { |name, result, type| + mtx.synchronize do + ing = ![true, false].include?(type) + bar += type ? '=' : 'x' unless ing + b = case type + when :install then '+' when :update then '*' + when true, nil then '-' else + VIM::command("call add(s:update.errors, '#{name}')") + 'x' + end + result = + if type || type.nil? + ["#{b} #{name}: #{result.lines.to_a.last || 'OK'}"] + elsif result =~ /^Interrupted|^Timeout/ + ["#{b} #{name}: #{result}"] + else + ["#{b} #{name}"] + result.lines.map { |l| " " << l } + end + if lnum = where.call(name) + $curbuf.delete lnum + lnum = 4 if ing && lnum > maxy + end + result.each_with_index do |line, offset| + $curbuf.append((lnum || 4) - 1 + offset, line.gsub(/\e\[./, '').chomp) + end + logh.call + end + } + bt = proc { |cmd, name, type, cleanup| + tried = timeout = 0 + begin + tried += 1 + timeout += limit + fd = nil + data = '' + if iswin + Timeout::timeout(timeout) do + tmp = VIM::evaluate('tempname()') + system("(#{cmd}) > #{tmp}") + data = File.read(tmp).chomp + File.unlink tmp rescue nil + end + else + fd = IO.popen(cmd).extend(PlugStream) + first_line = true + log_prob = 1.0 / nthr + while line = Timeout::timeout(timeout) { fd.get_line } + data << line + log.call name, line.chomp, type if name && (first_line || rand < log_prob) + first_line = false + end + fd.close + end + [$? == 0, data.chomp] + rescue Timeout::Error, Interrupt => e + if fd && !fd.closed? + killall fd.pid + fd.close + end + cleanup.call if cleanup + if e.is_a?(Timeout::Error) && tried < tries + 3.downto(1) do |countdown| + s = countdown > 1 ? 's' : '' + log.call name, "Timeout. Will retry in #{countdown} second#{s} ...", type + sleep 1 + end + log.call name, 'Retrying ...', type + retry + end + [false, e.is_a?(Interrupt) ? "Interrupted!" : "Timeout!"] + end + } + main = Thread.current + threads = [] + watcher = Thread.new { + if vim7 + while VIM::evaluate('getchar(1)') + sleep 0.1 + end + else + require 'io/console' # >= Ruby 1.9 + nil until IO.console.getch == 3.chr + end + mtx.synchronize do + running = false + threads.each { |t| t.raise Interrupt } unless vim7 + end + threads.each { |t| t.join rescue nil } + main.kill + } + refresh = Thread.new { + while true + mtx.synchronize do + break unless running + VIM::command('noautocmd normal! a') + end + sleep 0.2 + end + } if VIM::evaluate('s:mac_gui') == 1 + + clone_opt = VIM::evaluate('s:clone_opt') + progress = VIM::evaluate('s:progress_opt(1)') + nthr.times do + mtx.synchronize do + threads << Thread.new { + while pair = take1.call + name = pair.first + dir, uri, tag = pair.last.values_at *%w[dir uri tag] + exists = File.directory? dir + ok, result = + if exists + chdir = "#{cd} #{iswin ? dir : esc(dir)}" + ret, data = bt.call "#{chdir} && git rev-parse --abbrev-ref HEAD 2>&1 && git config -f .git/config remote.origin.url", nil, nil, nil + current_uri = data.lines.to_a.last + if !ret + if data =~ /^Interrupted|^Timeout/ + [false, data] + else + [false, [data.chomp, "PlugClean required."].join($/)] + end + elsif !compare_git_uri(current_uri, uri) + [false, ["Invalid URI: #{current_uri}", + "Expected: #{uri}", + "PlugClean required."].join($/)] + else + if pull + log.call name, 'Updating ...', :update + fetch_opt = (tag && File.exist?(File.join(dir, '.git/shallow'))) ? '--depth 99999999' : '' + bt.call "#{chdir} && git fetch #{fetch_opt} #{progress} 2>&1", name, :update, nil + else + [true, skip] + end + end + else + d = esc dir.sub(%r{[\\/]+$}, '') + log.call name, 'Installing ...', :install + bt.call "git clone #{clone_opt unless tag} #{progress} #{uri} #{d} 2>&1", name, :install, proc { + FileUtils.rm_rf dir + } + end + mtx.synchronize { VIM::command("let s:update.new['#{name}'] = 1") } if !exists && ok + log.call name, result, ok + end + } if running + end + end + threads.each { |t| t.join rescue nil } + logh.call + refresh.kill if refresh + watcher.kill +EOF +endfunction + +function! s:shellesc_cmd(arg) + let escaped = substitute(a:arg, '[&|<>()@^]', '^&', 'g') + let escaped = substitute(escaped, '%', '%%', 'g') + let escaped = substitute(escaped, '"', '\\^&', 'g') + let escaped = substitute(escaped, '\(\\\+\)\(\\^\)', '\1\1\2', 'g') + return '^"'.substitute(escaped, '\(\\\+\)$', '\1\1', '').'^"' +endfunction + +function! s:shellesc(arg) + if &shell =~# 'cmd.exe$' + return s:shellesc_cmd(a:arg) + endif + return shellescape(a:arg) +endfunction + +function! s:glob_dir(path) + return map(filter(s:glob(a:path, '**'), 'isdirectory(v:val)'), 's:dirpath(v:val)') +endfunction + +function! s:progress_bar(line, bar, total) + call setline(a:line, '[' . s:lpad(a:bar, a:total) . ']') +endfunction + +function! s:compare_git_uri(a, b) + " See `git help clone' + " https:// [user@] github.com[:port] / junegunn/vim-plug [.git] + " [git@] github.com[:port] : junegunn/vim-plug [.git] + " file:// / junegunn/vim-plug [/] + " / junegunn/vim-plug [/] + let pat = '^\%(\w\+://\)\='.'\%([^@/]*@\)\='.'\([^:/]*\%(:[0-9]*\)\=\)'.'[:/]'.'\(.\{-}\)'.'\%(\.git\)\=/\?$' + let ma = matchlist(a:a, pat) + let mb = matchlist(a:b, pat) + return ma[1:2] ==# mb[1:2] +endfunction + +function! s:format_message(bullet, name, message) + if a:bullet != 'x' + return [printf('%s %s: %s', a:bullet, a:name, s:lastline(a:message))] + else + let lines = map(s:lines(a:message), '" ".v:val') + return extend([printf('x %s:', a:name)], lines) + endif +endfunction + +function! s:with_cd(cmd, dir) + return printf('cd%s %s && %s', s:is_win ? ' /d' : '', s:shellesc(a:dir), a:cmd) +endfunction + +function! s:system(cmd, ...) + try + let [sh, shellcmdflag, shrd] = s:chsh(1) + let cmd = a:0 > 0 ? s:with_cd(a:cmd, a:1) : a:cmd + if s:is_win + let batchfile = tempname().'.bat' + call writefile(['@echo off', cmd], batchfile) + let cmd = batchfile + endif + return system(s:is_win ? '('.cmd.')' : cmd) + finally + let [&shell, &shellcmdflag, &shellredir] = [sh, shellcmdflag, shrd] + if s:is_win + call delete(batchfile) + endif + endtry +endfunction + +function! s:system_chomp(...) + let ret = call('s:system', a:000) + return v:shell_error ? '' : substitute(ret, '\n$', '', '') +endfunction + +function! s:git_validate(spec, check_branch) + let err = '' + if isdirectory(a:spec.dir) + let result = s:lines(s:system('git rev-parse --abbrev-ref HEAD 2>&1 && git config -f .git/config remote.origin.url', a:spec.dir)) + let remote = result[-1] + if v:shell_error + let err = join([remote, 'PlugClean required.'], "\n") + elseif !s:compare_git_uri(remote, a:spec.uri) + let err = join(['Invalid URI: '.remote, + \ 'Expected: '.a:spec.uri, + \ 'PlugClean required.'], "\n") + elseif a:check_branch && has_key(a:spec, 'commit') + let result = s:lines(s:system('git rev-parse HEAD 2>&1', a:spec.dir)) + let sha = result[-1] + if v:shell_error + let err = join(add(result, 'PlugClean required.'), "\n") + elseif !s:hash_match(sha, a:spec.commit) + let err = join([printf('Invalid HEAD (expected: %s, actual: %s)', + \ a:spec.commit[:6], sha[:6]), + \ 'PlugUpdate required.'], "\n") + endif + elseif a:check_branch + let branch = result[0] + " Check tag + if has_key(a:spec, 'tag') + let tag = s:system_chomp('git describe --exact-match --tags HEAD 2>&1', a:spec.dir) + if a:spec.tag !=# tag && a:spec.tag !~ '\*' + let err = printf('Invalid tag: %s (expected: %s). Try PlugUpdate.', + \ (empty(tag) ? 'N/A' : tag), a:spec.tag) + endif + " Check branch + elseif a:spec.branch !=# branch + let err = printf('Invalid branch: %s (expected: %s). Try PlugUpdate.', + \ branch, a:spec.branch) + endif + if empty(err) + let [ahead, behind] = split(s:lastline(s:system(printf( + \ 'git rev-list --count --left-right HEAD...origin/%s', + \ a:spec.branch), a:spec.dir)), '\t') + if !v:shell_error && ahead + if behind + " Only mention PlugClean if diverged, otherwise it's likely to be + " pushable (and probably not that messed up). + let err = printf( + \ "Diverged from origin/%s (%d commit(s) ahead and %d commit(s) behind!\n" + \ .'Backup local changes and run PlugClean and PlugUpdate to reinstall it.', a:spec.branch, ahead, behind) + else + let err = printf("Ahead of origin/%s by %d commit(s).\n" + \ .'Cannot update until local changes are pushed.', + \ a:spec.branch, ahead) + endif + endif + endif + endif + else + let err = 'Not found' + endif + return [err, err =~# 'PlugClean'] +endfunction + +function! s:rm_rf(dir) + if isdirectory(a:dir) + call s:system((s:is_win ? 'rmdir /S /Q ' : 'rm -rf ') . s:shellesc(a:dir)) + endif +endfunction + +function! s:clean(force) + call s:prepare() + call append(0, 'Searching for invalid plugins in '.g:plug_home) + call append(1, '') + + " List of valid directories + let dirs = [] + let errs = {} + let [cnt, total] = [0, len(g:plugs)] + for [name, spec] in items(g:plugs) + if !s:is_managed(name) + call add(dirs, spec.dir) + else + let [err, clean] = s:git_validate(spec, 1) + if clean + let errs[spec.dir] = s:lines(err)[0] + else + call add(dirs, spec.dir) + endif + endif + let cnt += 1 + call s:progress_bar(2, repeat('=', cnt), total) + normal! 2G + redraw + endfor + + let allowed = {} + for dir in dirs + let allowed[s:dirpath(fnamemodify(dir, ':h:h'))] = 1 + let allowed[dir] = 1 + for child in s:glob_dir(dir) + let allowed[child] = 1 + endfor + endfor + + let todo = [] + let found = sort(s:glob_dir(g:plug_home)) + while !empty(found) + let f = remove(found, 0) + if !has_key(allowed, f) && isdirectory(f) + call add(todo, f) + call append(line('$'), '- ' . f) + if has_key(errs, f) + call append(line('$'), ' ' . errs[f]) + endif + let found = filter(found, 'stridx(v:val, f) != 0') + end + endwhile + + 4 + redraw + if empty(todo) + call append(line('$'), 'Already clean.') + else + let s:clean_count = 0 + call append(3, ['Directories to delete:', '']) + redraw! + if a:force || s:ask_no_interrupt('Delete all directories?') + call s:delete([6, line('$')], 1) + else + call setline(4, 'Cancelled.') + nnoremap d :set opfunc=delete_opg@ + nmap dd d_ + xnoremap d :call delete_op(visualmode(), 1) + echo 'Delete the lines (d{motion}) to delete the corresponding directories' + endif + endif + 4 + setlocal nomodifiable +endfunction + +function! s:delete_op(type, ...) + call s:delete(a:0 ? [line("'<"), line("'>")] : [line("'["), line("']")], 0) +endfunction + +function! s:delete(range, force) + let [l1, l2] = a:range + let force = a:force + while l1 <= l2 + let line = getline(l1) + if line =~ '^- ' && isdirectory(line[2:]) + execute l1 + redraw! + let answer = force ? 1 : s:ask('Delete '.line[2:].'?', 1) + let force = force || answer > 1 + if answer + call s:rm_rf(line[2:]) + setlocal modifiable + call setline(l1, '~'.line[1:]) + let s:clean_count += 1 + call setline(4, printf('Removed %d directories.', s:clean_count)) + setlocal nomodifiable + endif + endif + let l1 += 1 + endwhile +endfunction + +function! s:upgrade() + echo 'Downloading the latest version of vim-plug' + redraw + let tmp = tempname() + let new = tmp . '/plug.vim' + + try + let out = s:system(printf('git clone --depth 1 %s %s', s:plug_src, tmp)) + if v:shell_error + return s:err('Error upgrading vim-plug: '. out) + endif + + if readfile(s:me) ==# readfile(new) + echo 'vim-plug is already up-to-date' + return 0 + else + call rename(s:me, s:me . '.old') + call rename(new, s:me) + unlet g:loaded_plug + echo 'vim-plug has been upgraded' + return 1 + endif + finally + silent! call s:rm_rf(tmp) + endtry +endfunction + +function! s:upgrade_specs() + for spec in values(g:plugs) + let spec.frozen = get(spec, 'frozen', 0) + endfor +endfunction + +function! s:status() + call s:prepare() + call append(0, 'Checking plugins') + call append(1, '') + + let ecnt = 0 + let unloaded = 0 + let [cnt, total] = [0, len(g:plugs)] + for [name, spec] in items(g:plugs) + let is_dir = isdirectory(spec.dir) + if has_key(spec, 'uri') + if is_dir + let [err, _] = s:git_validate(spec, 1) + let [valid, msg] = [empty(err), empty(err) ? 'OK' : err] + else + let [valid, msg] = [0, 'Not found. Try PlugInstall.'] + endif + else + if is_dir + let [valid, msg] = [1, 'OK'] + else + let [valid, msg] = [0, 'Not found.'] + endif + endif + let cnt += 1 + let ecnt += !valid + " `s:loaded` entry can be missing if PlugUpgraded + if is_dir && get(s:loaded, name, -1) == 0 + let unloaded = 1 + let msg .= ' (not loaded)' + endif + call s:progress_bar(2, repeat('=', cnt), total) + call append(3, s:format_message(valid ? '-' : 'x', name, msg)) + normal! 2G + redraw + endfor + call setline(1, 'Finished. '.ecnt.' error(s).') + normal! gg + setlocal nomodifiable + if unloaded + echo "Press 'L' on each line to load plugin, or 'U' to update" + nnoremap L :call status_load(line('.')) + xnoremap L :call status_load(line('.')) + end +endfunction + +function! s:extract_name(str, prefix, suffix) + return matchstr(a:str, '^'.a:prefix.' \zs[^:]\+\ze:.*'.a:suffix.'$') +endfunction + +function! s:status_load(lnum) + let line = getline(a:lnum) + let name = s:extract_name(line, '-', '(not loaded)') + if !empty(name) + call plug#load(name) + setlocal modifiable + call setline(a:lnum, substitute(line, ' (not loaded)$', '', '')) + setlocal nomodifiable + endif +endfunction + +function! s:status_update() range + let lines = getline(a:firstline, a:lastline) + let names = filter(map(lines, 's:extract_name(v:val, "[x-]", "")'), '!empty(v:val)') + if !empty(names) + echo + execute 'PlugUpdate' join(names) + endif +endfunction + +function! s:is_preview_window_open() + silent! wincmd P + if &previewwindow + wincmd p + return 1 + endif +endfunction + +function! s:find_name(lnum) + for lnum in reverse(range(1, a:lnum)) + let line = getline(lnum) + if empty(line) + return '' + endif + let name = s:extract_name(line, '-', '') + if !empty(name) + return name + endif + endfor + return '' +endfunction + +function! s:preview_commit() + if b:plug_preview < 0 + let b:plug_preview = !s:is_preview_window_open() + endif + + let sha = matchstr(getline('.'), '^ \X*\zs[0-9a-f]\{7,9}') + if empty(sha) + return + endif + + let name = s:find_name(line('.')) + if empty(name) || !has_key(g:plugs, name) || !isdirectory(g:plugs[name].dir) + return + endif + + if exists('g:plug_pwindow') && !s:is_preview_window_open() + execute g:plug_pwindow + execute 'e' sha + else + execute 'pedit' sha + wincmd P + endif + setlocal previewwindow filetype=git buftype=nofile nobuflisted modifiable + try + let [sh, shellcmdflag, shrd] = s:chsh(1) + let cmd = 'cd '.s:shellesc(g:plugs[name].dir).' && git show --no-color --pretty=medium '.sha + if s:is_win + let batchfile = tempname().'.bat' + call writefile(['@echo off', cmd], batchfile) + let cmd = batchfile + endif + execute 'silent %!' cmd + finally + let [&shell, &shellcmdflag, &shellredir] = [sh, shellcmdflag, shrd] + if s:is_win + call delete(batchfile) + endif + endtry + setlocal nomodifiable + nnoremap q :q + wincmd p +endfunction + +function! s:section(flags) + call search('\(^[x-] \)\@<=[^:]\+:', a:flags) +endfunction + +function! s:format_git_log(line) + let indent = ' ' + let tokens = split(a:line, nr2char(1)) + if len(tokens) != 5 + return indent.substitute(a:line, '\s*$', '', '') + endif + let [graph, sha, refs, subject, date] = tokens + let tag = matchstr(refs, 'tag: [^,)]\+') + let tag = empty(tag) ? ' ' : ' ('.tag.') ' + return printf('%s%s%s%s%s (%s)', indent, graph, sha, tag, subject, date) +endfunction + +function! s:append_ul(lnum, text) + call append(a:lnum, ['', a:text, repeat('-', len(a:text))]) +endfunction + +function! s:diff() + call s:prepare() + call append(0, ['Collecting changes ...', '']) + let cnts = [0, 0] + let bar = '' + let total = filter(copy(g:plugs), 's:is_managed(v:key) && isdirectory(v:val.dir)') + call s:progress_bar(2, bar, len(total)) + for origin in [1, 0] + let plugs = reverse(sort(items(filter(copy(total), (origin ? '' : '!').'(has_key(v:val, "commit") || has_key(v:val, "tag"))')))) + if empty(plugs) + continue + endif + call s:append_ul(2, origin ? 'Pending updates:' : 'Last update:') + for [k, v] in plugs + let range = origin ? '..origin/'.v.branch : 'HEAD@{1}..' + let diff = s:system_chomp('git log --graph --color=never '.join(map(['--pretty=format:%x01%h%x01%d%x01%s%x01%cr', range], 's:shellesc(v:val)')), v.dir) + if !empty(diff) + let ref = has_key(v, 'tag') ? (' (tag: '.v.tag.')') : has_key(v, 'commit') ? (' '.v.commit) : '' + call append(5, extend(['', '- '.k.':'.ref], map(s:lines(diff), 's:format_git_log(v:val)'))) + let cnts[origin] += 1 + endif + let bar .= '=' + call s:progress_bar(2, bar, len(total)) + normal! 2G + redraw + endfor + if !cnts[origin] + call append(5, ['', 'N/A']) + endif + endfor + call setline(1, printf('%d plugin(s) updated.', cnts[0]) + \ . (cnts[1] ? printf(' %d plugin(s) have pending updates.', cnts[1]) : '')) + + if cnts[0] || cnts[1] + nnoremap :silent! call preview_commit() + nnoremap o :silent! call preview_commit() + endif + if cnts[0] + nnoremap X :call revert() + echo "Press 'X' on each block to revert the update" + endif + normal! gg + setlocal nomodifiable +endfunction + +function! s:revert() + if search('^Pending updates', 'bnW') + return + endif + + let name = s:find_name(line('.')) + if empty(name) || !has_key(g:plugs, name) || + \ input(printf('Revert the update of %s? (y/N) ', name)) !~? '^y' + return + endif + + call s:system('git reset --hard HEAD@{1} && git checkout '.s:esc(g:plugs[name].branch).' --', g:plugs[name].dir) + setlocal modifiable + normal! "_dap + setlocal nomodifiable + echo 'Reverted' +endfunction + +function! s:snapshot(force, ...) abort + call s:prepare() + setf vim + call append(0, ['" Generated by vim-plug', + \ '" '.strftime("%c"), + \ '" :source this file in vim to restore the snapshot', + \ '" or execute: vim -S snapshot.vim', + \ '', '', 'PlugUpdate!']) + 1 + let anchor = line('$') - 3 + let names = sort(keys(filter(copy(g:plugs), + \'has_key(v:val, "uri") && !has_key(v:val, "commit") && isdirectory(v:val.dir)'))) + for name in reverse(names) + let sha = s:system_chomp('git rev-parse --short HEAD', g:plugs[name].dir) + if !empty(sha) + call append(anchor, printf("silent! let g:plugs['%s'].commit = '%s'", name, sha)) + redraw + endif + endfor + + if a:0 > 0 + let fn = expand(a:1) + if filereadable(fn) && !(a:force || s:ask(a:1.' already exists. Overwrite?')) + return + endif + call writefile(getline(1, '$'), fn) + echo 'Saved as '.a:1 + silent execute 'e' s:esc(fn) + setf vim + endif +endfunction + +function! s:split_rtp() + return split(&rtp, '\\\@ -Original Author: Daniel Malea -License: Same terms as Vim itself (see |license|) - -INTRODUCTION *lldb-intro* - -The plugin provides an interface to the lldb debugger allowing for -convenient debugging sessions inside your favorite editor including -features such as breakpoints, stepping, watchpoints etc. - -The original plugin can be found here: - -http://llvm.org/svn/llvm-project/lldb/trunk/utils/vim-lldb/ - -Credit for pretty much all current functionality goes to the original -authors. Currently only minor modifications have been made to the -original plugin. - - -GETTING STARTED *lldb-start* - -To quickly get started compile (don't forget to compile with debugging -symbols) and start some program. Then open a source file belonging to the -program in vim and execute ':Lattach .' Then select some -line in the source file and execute ':Lbreakpoint' to set a breakpint at -the current line. - -Once the program reaches the specified breakpoint you will be able to -inspect state and step through the proram using the commands described below. - -COMMANDS *lldb-commands* - -The LLDB command interpreter is exposed to Vim's command mode using the -':L' prefix. Tab-completion is available and will cycle through commands. -Some commands have modified behaviour in Vim; for example, :Lbreakpoint -with no arguments will set a breakpoint at the current cursor, rather than -printing the standard help information for the LLDB command 'breakpoint'. - - *lldb-windows* - -In addition to the standard commands available under the LLDB interpreter, -there are also commands to display or hide informational debugger panes. - -Windows can be shown or hidden using the ':Lhide ' or ':Lshow ' -commands. - *lldb-:Lhide* -:Lhide [windowname] Hide informational debugger pane named 'windowname'. - - *lldb-:Lshow* -:Lshow [windowname] Show informational debugger pane named 'windowname'. - -Possible window name arguments to the Lhide and Lshow commands include: - - * backtrace - * breakpoints - * disassembly - * locals - * registers - * threads - *lldb-:Lattach* -:Lattach Attach to a process by name. - - *lldb-:Ldetach* -:Ldetach Detach from the current process. - - *lldb-:Ltarget* -:Ltarget [[create] executable] - Create a target with the specified executable. If - run with a single argument, that argument is assumed - to be a path to the executable to be debugged. - Otherwise, all arguments are passed into LLDB's command - interpreter. - - *lldb-:Lstart* -:Lstart Create a process by executing the current target - and wait for LLDB to attach. - - *lldb-:Lrun* -:Lrun Create a process by executing the current target - without waiting for LLDB to attach. - - *lldb-:Lcontinue* -:Lcontinue Continue execution of the process until the next - breakpoint is hit or the process exits. - - *lldb-:Lthread* -:Lthread Passes through to LLDB. See :Lhelp thread. - - *lldb-:Lstep* -:Lstep Step into the current function call. - - *lldb-:Lstepin* -:Lstepin Step into the current function call. - - *lldb-:Lstepinst* -:Lstepinst Step one instruction. - - *lldb-:Lstepinstover* -:Lstepinstover Step one instruction, but skip over jump or call - instructions. - - *lldb-:Lnext* -:Lnext Step to the next line. - - *lldb-:Lfinish* -:Lfinish Step out of the current function. - - *lldb-:Lbreakpoint* -:Lbreakpoint [args] When arguments are provided, the lldb breakpoint - command is invoked. If no arguments are provided, - a breakpoint at the location under the cursor. - - *lldb-:Lprint* - *lldb-:Lpo* - *lldb-:LpO* -:Lprint Aliases to the lldb print and po commands. Cursor -:Lpo word (cursor WORD for LpO) will be used when -:LpO expression omitted. - -MAPPINGS *lldb-mappings* - -There are no default mappings defined by the plugin. All commands described -above can be mapped by defining a respective variable: - - let g:lldb_map_Lframe = "f" - -This will map the Lframe command to "f". Other commands can be -mapped accordingly using 'lldb_map_' + . - - -LICENSE *lldb-license* - -Same as Vim itself. - -BUGS *lldb-bugs* - -If you run into a bug use the github issue tracker to report it: -http://github.com/gilligan/vim-lldb/issues/ - -CONTRIBUTING *lldb-contributing* - -If you want to help out you are more then welcome to do so. In fact -I am sure there are plenty of people out there that will do a better -job with this plugin than me. My C skills are more than rusty. I mostly -wanted to make this nice plugin more public and host it in a way in which -it is easy to install with your favorite plugin manager. - -Long story short: Bring on your forks and pull requests. - - vim:tw=78:sw=4:ft=help:norl: diff --git a/VimFiles/doc/misc.txt b/VimFiles/doc/misc.txt new file mode 100644 index 0000000..3476c46 --- /dev/null +++ b/VimFiles/doc/misc.txt @@ -0,0 +1,1204 @@ +*misc.txt* Miscellaneous auto-load Vim scripts + +=============================================================================== +Contents ~ + + 1. Introduction |misc-introduction| + 2. Installation |misc-installation| + 3. Function documentation |misc-function-documentation| + 1. Asynchronous Vim script evaluation |misc-asynchronous-vim-script-evaluation| + 1. The |xolox#misc#async#call()| function + 2. The |xolox#misc#async#inside_child()| function + 3. The |xolox#misc#async#callback_to_parent()| function + 4. The |xolox#misc#async#periodic_callback()| function + 2. Handling of special buffers |misc-handling-of-special-buffers| + 1. The |xolox#misc#buffer#is_empty()| function + 2. The |xolox#misc#buffer#prepare()| function + 3. The |xolox#misc#buffer#lock()| function + 4. The |xolox#misc#buffer#unlock()| function + 3. Tab completion for user defined commands |misc-tab-completion-for-user-defined-commands| + 1. The |xolox#misc#complete#keywords()| function + 4. Rate limiting for Vim's CursorHold event |misc-rate-limiting-for-vims-cursorhold-event| + 1. The |xolox#misc#cursorhold#register()| function + 2. The |xolox#misc#cursorhold#autocmd()| function + 5. String escaping functions |misc-string-escaping-functions| + 1. The |xolox#misc#escape#pattern()| function + 2. The |xolox#misc#escape#substitute()| function + 3. The |xolox#misc#escape#shell()| function + 6. Human friendly string formatting for Vim |misc-human-friendly-string-formatting-for-vim| + 1. The |xolox#misc#format#pluralize()| function + 2. The |xolox#misc#format#timestamp()| function + 7. List handling functions |misc-list-handling-functions| + 1. The |xolox#misc#list#unique()| function + 2. The |xolox#misc#list#binsert()| function + 8. Functions to interact with the user |misc-functions-to-interact-with-user| + 1. The |xolox#misc#msg#info()| function + 2. The |xolox#misc#msg#warn()| function + 3. The |xolox#misc#msg#debug()| function + 9. Integration between Vim and its environment |misc-integration-between-vim-its-environment| + 1. The |xolox#misc#open#file()| function + 2. The |xolox#misc#open#url()| function + 10. Vim and plug-in option handling |misc-vim-plug-in-option-handling| + 1. The |xolox#misc#option#get()| function + 2. The |xolox#misc#option#split()| function + 3. The |xolox#misc#option#join()| function + 4. The |xolox#misc#option#split_tags()| function + 5. The |xolox#misc#option#join_tags()| function + 6. The |xolox#misc#option#eval_tags()| function + 11. Operating system interfaces |misc-operating-system-interfaces| + 1. The |xolox#misc#os#is_mac()| function + 2. The |xolox#misc#os#is_win()| function + 3. The |xolox#misc#os#find_vim()| function + 4. The |xolox#misc#os#exec()| function + 5. The |xolox#misc#os#can_use_dll()| function + 12. Pathname manipulation functions |misc-pathname-manipulation-functions| + 1. The |xolox#misc#path#which()| function + 2. The |xolox#misc#path#split()| function + 3. The |xolox#misc#path#join()| function + 4. The |xolox#misc#path#directory_separator()| function + 5. The |xolox#misc#path#absolute()| function + 6. The |xolox#misc#path#relative()| function + 7. The |xolox#misc#path#merge()| function + 8. The |xolox#misc#path#commonprefix()| function + 9. The |xolox#misc#path#starts_with()| function + 10. The |xolox#misc#path#encode()| function + 11. The |xolox#misc#path#decode()| function + 12. The |xolox#misc#path#is_relative()| function + 13. The |xolox#misc#path#tempdir()| function + 13. Manipulation of UNIX file permissions |misc-manipulation-of-unix-file-permissions| + 1. The |xolox#misc#perm#update()| function + 2. The |xolox#misc#perm#get()| function + 3. The |xolox#misc#perm#set()| function + 14. Persist/recall Vim values from/to files |misc-persist-recall-vim-values-from-to-files| + 1. The |xolox#misc#persist#load()| function + 2. The |xolox#misc#persist#save()| function + 15. String handling |misc-string-handling| + 1. The |xolox#misc#str#slug()| function + 2. The |xolox#misc#str#ucfirst()| function + 3. The |xolox#misc#str#unescape()| function + 4. The |xolox#misc#str#compact()| function + 5. The |xolox#misc#str#trim()| function + 6. The |xolox#misc#str#indent()| function + 7. The |xolox#misc#str#dedent()| function + 16. Test runner & infrastructure for Vim plug-ins |misc-test-runner-infrastructure-for-vim-plug-ins| + 1. The |xolox#misc#test#reset()| function + 2. The |xolox#misc#test#summarize()| function + 3. The |xolox#misc#test#wrap()| function + 4. The |xolox#misc#test#passed()| function + 5. The |xolox#misc#test#failed()| function + 6. The |xolox#misc#test#assert_true()| function + 7. The |xolox#misc#test#assert_equals()| function + 8. The |xolox#misc#test#assert_same_type()| function + 17. Tests for the miscellaneous Vim scripts |tests-for-miscellaneous-vim-scripts| + 1. The |xolox#misc#tests#run()| function + 2. The |xolox#misc#tests#pattern_escaping()| function + 3. The |xolox#misc#tests#substitute_escaping()| function + 4. The |xolox#misc#tests#shell_escaping()| function + 5. The |xolox#misc#tests#making_a_list_unique()| function + 6. The |xolox#misc#tests#binary_insertion()| function + 7. The |xolox#misc#tests#getting_configuration_options()| function + 8. The |xolox#misc#tests#splitting_of_multi_valued_options()| function + 9. The |xolox#misc#tests#joining_of_multi_valued_options()| function + 10. The |xolox#misc#tests#finding_vim_on_the_search_path()| function + 11. The |xolox#misc#tests#synchronous_command_execution()| function + 12. The |xolox#misc#tests#synchronous_command_execution_with_stderr()| function + 13. The |xolox#misc#tests#synchronous_command_execution_with_raising_of_errors()| +function + 14. The |xolox#misc#tests#synchronous_command_execution_without_raising_errors()| +function + 15. The |xolox#misc#tests#asynchronous_command_execution()| function + 16. The |xolox#misc#tests#string_case_transformation()| function + 17. The |xolox#misc#tests#string_whitespace_compaction()| function + 18. The |xolox#misc#tests#string_whitespace_trimming()| function + 19. The |xolox#misc#tests#multiline_string_dedent()| function + 20. The |xolox#misc#tests#version_string_parsing()| function + 21. The |xolox#misc#tests#version_string_comparison()| function + 18. Timing of long during operations |misc-timing-of-long-during-operations| + 1. The |xolox#misc#timer#resumable()| function + 2. The |xolox#misc#timer#start()| function + 3. The |xolox#misc#timer#stop()| function + 4. The |xolox#misc#timer#force()| function + 5. The |xolox#misc#timer#convert()| function + 19. Version string handling |misc-version-string-handling| + 1. The |xolox#misc#version#parse()| function + 2. The |xolox#misc#version#at_least()| function + 4. Contact |misc-contact| + 5. License |misc-license| + 6. References |misc-references| + +=============================================================================== + *misc-introduction* +Introduction ~ + +The vim-misc plug-in contains Vim scripts that are used by most of the Vim +plug-ins I've written [1] yet don't really belong with any single one of the +plug-ins. Basically it's an extended standard library of Vim script functions +that I wrote during the development of my Vim profile and plug-ins. + +In the past these scripts were bundled with each plug-in, however that turned +out to be a maintenance nightmare for me. That's why the miscellaneous scripts +are now a proper plug-in with their own page on Vim Online. + +Because the miscellaneous scripts are no longer bundled with my Vim plug-ins, +users are now required to install the miscellaneous scripts separately. This is +unfortunate for users who are upgrading from a previous release that did bundle +the miscellaneous scripts, but I don't see any way around this. Sorry! + +=============================================================================== + *misc-installation* +Installation ~ + +Please refer to the installation instructions [2] on GitHub. + +=============================================================================== + *misc-function-documentation* +Function documentation ~ + +Below is the documentation for the functions included in the miscellaneous +scripts. Anyone is free to use these functions in their own Vim profile and/or +plug-ins. I care about backwards compatibility so won't break it without a good +reason to do so. + +For those who are curious: The function descriptions given below were extracted +from the source code of the miscellaneous scripts using the Python module +'vimdoctool.py' included in vim-tools [3]. + +The documentation of the 95 functions below was extracted from 19 Vim scripts +on April 1, 2015 at 23:39. + +------------------------------------------------------------------------------- + *misc-asynchronous-vim-script-evaluation* +Asynchronous Vim script evaluation ~ + +The |xolox#misc#async#call()| function builds on top of |xolox#misc#os#exec()| +to support asynchronous evaluation of Vim scripts. The first (and for now only) +use case is my vim-easytags [4] plug-in which has a bunch of conflicting +requirements: + +1. I want the vim-easytags [4] plug-in to be as portable as possible. + Ideally everything is implemented in Vim script because that's the only + thing I can rely on to be available for all potential users of the plug- + in! + +2. Because of point one I've been forced to implement tags file reading, + parsing, (fold case) sorting and writing in Vim script. This is fine for + small tags files but once they grow to a couple of megabytes it becomes + annoying because Vim is unresponsive during tags file updates (key + presses are fortunately buffered due to Vim's input model but that + doesn't make it a nice user experience :-). + +3. I could (and did in the past) come up with all sorts of hacks to speed + things up without switching away from Vim script, but none of them are + going to solve the fundamental problem that Vim's unresponsive hiccups + become longer as tags files grow larger. + +By now it should be clear where this is heading: _Why not handle tags file +updates in a Vim process that runs in the background without blocking the Vim +process that the user is interacting with?_ It turns out that there are quite a +few details to take care of, but with those out of the way, it might just work! +I'm actually hoping to make asynchronous updates the default mode in vim- +easytags [4]. This means I need this functionality to be as portable and robust +as possible. + +**Status:** This code has seen little testing so I wouldn't trust it too much +just yet. On the other hand, as I said, my intention is to make this +functionality as portable and robust as possible. You be the judge :-). + +------------------------------------------------------------------------------- +The *xolox#misc#async#call()* function + +Call a Vim script function asynchronously by starting a hidden Vim process in +the background. Once the function returns the hidden Vim process terminates +itself. This function takes a single argument which is a dictionary with the +following key/value pairs: + +- **function** (required): The name of the Vim function to call inside the + child process (a string). I suggest using an |autoload| function for this, + see below. + +- **arguments** (optional): A list of arguments to pass to the function. This + list is serialized to a string using |string()| and deserialized using + |eval()|. + +- **callback** (optional): The name of a Vim function to call in the parent + process when the child process has completed (a string). + +- **clientserver** (optional): If this is true (1) the child process will + notify the parent process when it has finished (the default is true). This + works using Vim's client/server support which is not always available. As a + fall back Vim's |CursorHold| automatic command is also supported (although + the effect is not quite as instantaneous :-). + +This functionality is experimental and non trivial to use, so consider yourself +warned :-). + +**Limitations** + +I'm making this functionality available in vim-misc [5] because I think it can +be useful to other plug-ins, however if you are going to use it you should be +aware of the following limitations: + +- Because of the use of multiple processes this functionality is only + suitable for 'heavy' tasks. + +- The function arguments are serialized to a string which is passed to the + hidden Vim process as a command line argument, so the amount of data you + can pass will be limited by your operating environment. + +- The hidden Vim process is explicitly isolated from the user in several ways + (see below for more details). This is to make sure that the hidden Vim + processes are fast and don't clobber the user's editing sessions in any + way. + +**Changes to how Vim normally works** + +You have to be aware that the hidden Vim process is initialized in a specific +way that is very different from your regular Vim editing sessions: + +- Your |vimrc| file is ignored using the '-u NONE' command line option. + +- Your |gvimrc| file (if you even knew it existed ;-) is ignored using the + '-U NONE' command line option. + +- Plug-in loading is skipped using the '--noplugin' command line option. + +- Swap files (see |swap-file|) are disabled using the '-n' command line + option. This makes sure asynchronous Vim processes don't disturb the user's + editing session. + +- Your |viminfo| file is ignored using the '-i NONE' command line option. + Just like with swap files this makes sure asynchronous Vim processes don't + disturb the user's editing session. + +- No-compatible mode is enabled using the '-N' command line option (usually + the existence of your vimrc script would have achieved the same effect but + since we disable loading of your vimrc we need to spell things out for + Vim). + +**Use an auto-load function** + +The function you want to call is identified by its name which has to be +defined, but I just explained above that all regular initialization is disabled +for asynchronous Vim processes, so what gives? The answer is to use an +|autoload| function. This should work fine because the asynchronous Vim process +'inherits' the value of the |'runtimepath'| option from your editing session. + +------------------------------------------------------------------------------- +The *xolox#misc#async#inside_child()* function + +Entry point inside the hidden Vim process that runs in the background. Invoked +indirectly by |xolox#misc#async#call()| because it runs a command similar to +the following: +> + vim --cmd 'call xolox#misc#async#inside_child(...)' +< +This function is responsible for calling the user defined function, capturing +exceptions and reporting the results back to the parent Vim process using Vim's +client/server support or a temporary file. + +------------------------------------------------------------------------------- +The *xolox#misc#async#callback_to_parent()* function + +When Vim was compiled with client/server support this function (in the parent +process) will be called by |xolox#misc#async#inside_child()| (in the child +process) after the user defined function has returned. This enables more or +less instant callbacks after running an asynchronous function. + +------------------------------------------------------------------------------- +The *xolox#misc#async#periodic_callback()* function + +When client/server support is not being used the vim-misc plug-in improvises: +It uses Vim's |CursorHold| event to periodically check if an asynchronous +process has written its results to one of the expected temporary files. If a +response is found the temporary file is read and deleted and then +|xolox#misc#async#callback_to_parent()| is called to process the response. + +------------------------------------------------------------------------------- + *misc-handling-of-special-buffers* +Handling of special buffers ~ + +The functions defined here make it easier to deal with special Vim buffers that +contain text generated by a Vim plug-in. For example my vim-notes plug-in [6] +generates several such buffers: + +- :RecentNotes [7] lists recently modified notes +- :ShowTaggedNotes [8] lists notes grouped by tags +- etc. + +Because the text in these buffers is generated, Vim shouldn't bother with swap +files and it should never prompt the user whether to save changes to the +generated text. + +------------------------------------------------------------------------------- +The *xolox#misc#buffer#is_empty()* function + +Checks if the current buffer is an empty, unchanged buffer which can be reused. +Returns 1 if an empty buffer is found, 0 otherwise. + +------------------------------------------------------------------------------- +The *xolox#misc#buffer#prepare()* function + +Open a special buffer, i.e. a buffer that will hold generated contents, not +directly edited by the user. The buffer can be customized by passing a +dictionary with the following key/value pairs as the first argument: + +- **name** (required): The base name of the buffer (i.e. the base name of the + file loaded in the buffer, even though it isn't really a file and nothing + is really 'loaded' :-) + +- **path** (required): The pathname of the buffer. May be relevant if |:lcd| + or |'autochdir'| is being used. + +------------------------------------------------------------------------------- +The *xolox#misc#buffer#lock()* function + +Lock a special buffer so that its contents can no longer be edited. + +------------------------------------------------------------------------------- +The *xolox#misc#buffer#unlock()* function + +Unlock a special buffer so that its content can be updated. + +------------------------------------------------------------------------------- + *misc-tab-completion-for-user-defined-commands* +Tab completion for user defined commands ~ + +------------------------------------------------------------------------------- +The *xolox#misc#complete#keywords()* function + +This function can be used to perform keyword completion for user defined Vim +commands based on the contents of the current buffer. Here's an example of how +you would use it: +> + :command -nargs=* -complete=customlist,xolox#misc#complete#keywords MyCmd call s:MyCmd() +< +------------------------------------------------------------------------------- + *misc-rate-limiting-for-vims-cursorhold-event* +Rate limiting for Vim's CursorHold event ~ + +Several of my Vim plug-ins (e.g. vim-easytags [4], vim-notes [6] and vim- +session [9]) use Vim's |CursorHold| and |CursorHoldI| events to perform +periodic tasks when the user doesn't press any keys for a couple of seconds. +These events by default fire after four seconds, this is configurable using +Vim's |'updatetime'| option. The problem that this script solves is that there +are Vim plug-ins which set the |'updatetime'| option to unreasonably low +values, thereby breaking my Vim plug-ins and probably a lot of other Vim plug- +ins out there. When users complain about this I can tell them that another Vim +plug-in is to blame, but users don't care for the difference, their Vim is +broken! So I implemented a workaround. This script enables registration of +|CursorHold| event handlers with a configurable interval (expressed in +seconds). The event handlers will be called no more than once every interval. + +------------------------------------------------------------------------------- +The *xolox#misc#cursorhold#register()* function + +Register a |CursorHold| event handler with a custom interval. This function +takes a single argument which is a dictionary with the following fields: + +- **function** (required): The name of the event handler function (a string). + +- **arguments** (optional): A list of arguments to pass to the event handler + function (defaults to an empty list). + +- **interval** (optional): The number of seconds between calls to the event + handler (defaults to 4). + +------------------------------------------------------------------------------- +The *xolox#misc#cursorhold#autocmd()* function + +The 'top level event handler' that's called by Vim whenever the |CursorHold| or +|CursorHoldI| event fires. It iterates through the event handlers registered +using |xolox#misc#cursorhold#register()| and calls each event handler at the +appropriate interval, keeping track of the time when each event handler was +last run. + +------------------------------------------------------------------------------- + *misc-string-escaping-functions* +String escaping functions ~ + +------------------------------------------------------------------------------- +The *xolox#misc#escape#pattern()* function + +Takes a single string argument and converts it into a |:substitute| / +|substitute()| pattern string that matches the given string literally. + +------------------------------------------------------------------------------- +The *xolox#misc#escape#substitute()* function + +Takes a single string argument and converts it into a |:substitute| / +|substitute()| replacement string that inserts the given string literally. + +------------------------------------------------------------------------------- +The *xolox#misc#escape#shell()* function + +Takes a single string argument and converts it into a quoted command line +argument. + +I was going to add a long rant here about Vim's |'shellslash'| option, but +really, it won't make any difference. Let's just suffice to say that I have yet +to encounter a single person out there who uses this option for its intended +purpose (running a UNIX style shell on Microsoft Windows). + +------------------------------------------------------------------------------- + *misc-human-friendly-string-formatting-for-vim* +Human friendly string formatting for Vim ~ + +------------------------------------------------------------------------------- +The *xolox#misc#format#pluralize()* function + +Concatenate a counter (the first argument, expected to be an integer) with a +singular or plural label (the second and third arguments, both expected to be +strings). + +------------------------------------------------------------------------------- +The *xolox#misc#format#timestamp()* function + +Format a time stamp (a string containing a formatted floating point number) +into a human friendly format, for example 70 seconds is phrased as "1 minute +and 10 seconds". + +------------------------------------------------------------------------------- + *misc-list-handling-functions* +List handling functions ~ + +------------------------------------------------------------------------------- +The *xolox#misc#list#unique()* function + +Remove duplicate values from the given list in-place (preserves order). + +------------------------------------------------------------------------------- +The *xolox#misc#list#binsert()* function + +Performs in-place binary insertion, which depending on your use case can be +more efficient than calling Vim's |sort()| function after each insertion (in +cases where a single, final sort is not an option). Expects three arguments: + +1. A list +2. A value to insert +3. 1 (true) when case should be ignored, 0 (false) otherwise + +------------------------------------------------------------------------------- + *misc-functions-to-interact-with-user* +Functions to interact with the user ~ + +------------------------------------------------------------------------------- +The *xolox#misc#msg#info()* function + +Show a formatted informational message to the user. + +This function has the same argument handling as Vim's |printf()| function with +one notable difference: Any arguments which are not numbers or strings are +coerced to strings using Vim's |string()| function. + +In the case of |xolox#misc#msg#info()|, automatic string coercion simply makes +the function a bit easier to use. + +The messages emitted by this function have no highlighting. Previously these +messages were highlighted using the Title group (see |hl-Title|), but it was +pointed out in pull request 16 [10] that this group shouldn't be used for +informational messages because it is meant for titles and because of this some +color schemes use colors that stand out quite a bit, causing the informational +messages to look like errors. + +------------------------------------------------------------------------------- +The *xolox#misc#msg#warn()* function + +Show a formatted warning message to the user. + +This function has the same argument handling as the |xolox#misc#msg#info()| +function. + +------------------------------------------------------------------------------- +The *xolox#misc#msg#debug()* function + +Show a formatted debugging message to the user, _if the user has enabled +increased verbosity by setting Vim's |'verbose'| option to one (1) or higher_. + +This function has the same argument handling as the |xolox#misc#msg#info()| +function. + +In the case of |xolox#misc#msg#debug()|, automatic string coercion provides +lazy evaluation in the sense that complex data structures are only converted to +strings when the user has enabled increased verbosity. + +------------------------------------------------------------------------------- + *misc-integration-between-vim-its-environment* +Integration between Vim and its environment ~ + +------------------------------------------------------------------------------- +The *xolox#misc#open#file()* function + +Given a pathname or URL as the first argument, this opens the file with the +program associated with the file type. So for example a text file might open in +Vim, an '*.html' file would probably open in your web browser and a media file +would open in a media player. + +This should work on Windows, Mac OS X and most Linux distributions. If this +fails to find a file association, you can pass one or more external commands to +try as additional arguments. For example: +> + :call xolox#misc#open#file('/path/to/my/file', 'firefox', 'google-chrome') +< +This generally shouldn't be necessary but it might come in handy now and then. + +------------------------------------------------------------------------------- +The *xolox#misc#open#url()* function + +Given a URL as the first argument, this opens the URL in your preferred or best +available web browser: + +- In GUI environments a graphical web browser will open (or a new tab will be + created in an existing window) + +- In console Vim without a GUI environment, when you have any of 'lynx', + 'links' or 'w3m' installed it will launch a command line web browser in + front of Vim (temporarily suspending Vim) + +------------------------------------------------------------------------------- + *misc-vim-plug-in-option-handling* +Vim and plug-in option handling ~ + +------------------------------------------------------------------------------- +The *xolox#misc#option#get()* function + +Expects one or two arguments: 1. The name of a variable and 2. the default +value if the variable does not exist. + +Returns the value of the variable from a buffer local variable, global variable +or the default value, depending on which is defined. + +This is used by some of my Vim plug-ins for option handling, so that users can +customize options for specific buffers. + +------------------------------------------------------------------------------- +The *xolox#misc#option#split()* function + +Given a multi-value Vim option like |'runtimepath'| this returns a list of +strings. For example: +> + :echo xolox#misc#option#split(&runtimepath) + ['/home/peter/Projects/Vim/misc', + '/home/peter/Projects/Vim/colorscheme-switcher', + '/home/peter/Projects/Vim/easytags', + ...] +< +------------------------------------------------------------------------------- +The *xolox#misc#option#join()* function + +Given a list of strings like the ones returned by |xolox#misc#option#split()|, +this joins the strings together into a single value that can be used to set a +Vim option. + +------------------------------------------------------------------------------- +The *xolox#misc#option#split_tags()* function + +Customized version of |xolox#misc#option#split()| with specialized handling for +Vim's |'tags'| option. + +------------------------------------------------------------------------------- +The *xolox#misc#option#join_tags()* function + +Customized version of |xolox#misc#option#join()| with specialized handling for +Vim's |'tags'| option. + +------------------------------------------------------------------------------- +The *xolox#misc#option#eval_tags()* function + +Evaluate Vim's |'tags'| option without looking at the file system, i.e. this +will report tags files that don't exist yet. Expects the value of the |'tags'| +option as the first argument. If the optional second argument is 1 (true) only +the first match is returned, otherwise (so by default) a list with all matches +is returned. + +------------------------------------------------------------------------------- + *misc-operating-system-interfaces* +Operating system interfaces ~ + +------------------------------------------------------------------------------- +The *xolox#misc#os#is_mac()* function + +Returns 1 (true) when on Mac OS X, 0 (false) otherwise. You would expect this +to simply check the Vim feature list, but for some obscure reason the +'/usr/bin/vim' included in Mac OS X (verified on version 10.7.5) returns 0 +(false) in response to "has('mac')", so we check the output of 'uname' to avoid +false negatives. + +------------------------------------------------------------------------------- +The *xolox#misc#os#is_win()* function + +Returns 1 (true) when on Microsoft Windows, 0 (false) otherwise. + +------------------------------------------------------------------------------- +The *xolox#misc#os#find_vim()* function + +Returns the program name of Vim as a string. On Windows and UNIX this just +|v:progname| as an absolute pathname while on Mac OS X there is some special +magic to find MacVim's executable even though it's usually not on the +executable search path. If you want, you can override the value returned from +this function by setting the global variable 'g:xolox#misc#os#vim_progname'. + +By default the choice of console Vim vs graphical Vim is made based on the +value of |v:progname|, but if you have a preference you can pass the string +'vim' or 'gvim' as the first and only argument. + +------------------------------------------------------------------------------- +The *xolox#misc#os#exec()* function + +Execute an external command (hiding the console on Microsoft Windows when my +vim-shell plug-in [11] is installed). + +Expects a dictionary with the following key/value pairs as the first argument: + +- **command** (required): The command line to execute + +- **async** (optional): set this to 1 (true) to execute the command in the + background (asynchronously) + +- **stdin** (optional): a string or list of strings with the input for the + external command + +- **check** (optional): set this to 0 (false) to disable checking of the exit + code of the external command (by default an exception will be raised when + the command fails) + +Returns a dictionary with one or more of the following key/value pairs: + +- **command** (always available): the generated command line that was used to + run the external command + +- **exit_code** (only in synchronous mode): the exit status of the external + command (an integer, zero on success) + +- **stdout** (only in synchronous mode): the output of the command on the + standard output stream (a list of strings, one for each line) + +- **stderr** (only in synchronous mode): the output of the command on the + standard error stream (as a list of strings, one for each line) + +------------------------------------------------------------------------------- +The *xolox#misc#os#can_use_dll()* function + +If a) we're on Microsoft Windows, b) the vim-shell plug-in is installed and c) +the compiled DLL included in vim-shell works, we can use the vim-shell plug-in +to execute external commands! Returns 1 (true) if we can use the DLL, 0 (false) +otherwise. + +------------------------------------------------------------------------------- + *misc-pathname-manipulation-functions* +Pathname manipulation functions ~ + +------------------------------------------------------------------------------- +The *xolox#misc#path#which()* function + +Scan the executable search path ('$PATH') for one or more external programs. +Expects one or more string arguments with program names. Returns a list with +the absolute pathnames of all found programs. Here's an example: +> + :echo xolox#misc#path#which('gvim', 'vim') + ['/usr/local/bin/gvim', + '/usr/bin/gvim', + '/usr/local/bin/vim', + '/usr/bin/vim'] +< +------------------------------------------------------------------------------- +The *xolox#misc#path#split()* function + +Split a pathname (the first and only argument) into a list of pathname +components. + +On Windows, pathnames starting with two slashes or backslashes are UNC paths +where the leading slashes are significant... In this case we split like this: + +- Input: "'//server/share/directory'" +- Result: "['//server', 'share', 'directory']" + +Everything except Windows is treated like UNIX until someone has a better +suggestion :-). In this case we split like this: + +- Input: "'/foo/bar/baz'" +- Result: "['/', 'foo', 'bar', 'baz']" + +To join a list of pathname components back into a single pathname string, use +the |xolox#misc#path#join()| function. + +------------------------------------------------------------------------------- +The *xolox#misc#path#join()* function + +Join a list of pathname components (the first and only argument) into a single +pathname string. This is the counterpart to the |xolox#misc#path#split()| +function and it expects a list of pathname components as returned by +|xolox#misc#path#split()|. + +------------------------------------------------------------------------------- +The *xolox#misc#path#directory_separator()* function + +Find the preferred directory separator for the platform and settings. + +------------------------------------------------------------------------------- +The *xolox#misc#path#absolute()* function + +Canonicalize and resolve a pathname, _regardless of whether it exists_. This is +intended to support string comparison to determine whether two pathnames point +to the same directory or file. + +------------------------------------------------------------------------------- +The *xolox#misc#path#relative()* function + +Make an absolute pathname (the first argument) relative to a directory (the +second argument). + +------------------------------------------------------------------------------- +The *xolox#misc#path#merge()* function + +Join a directory pathname and filename into a single pathname. + +------------------------------------------------------------------------------- +The *xolox#misc#path#commonprefix()* function + +Find the common prefix of path components in a list of pathnames. + +------------------------------------------------------------------------------- +The *xolox#misc#path#starts_with()* function + +Check whether the first pathname starts with the second pathname (expected to +be a directory). This does not perform a regular string comparison; first it +normalizes both pathnames, then it splits them into their pathname segments and +then it compares the segments. + +------------------------------------------------------------------------------- +The *xolox#misc#path#encode()* function + +Encode a pathname so it can be used as a filename. This uses URL encoding to +encode special characters. + +------------------------------------------------------------------------------- +The *xolox#misc#path#decode()* function + +Decode a pathname previously encoded with |xolox#misc#path#encode()|. + +------------------------------------------------------------------------------- +The *xolox#misc#path#is_relative()* function + +Returns true (1) when the pathname given as the first argument is relative, +false (0) otherwise. + +------------------------------------------------------------------------------- +The *xolox#misc#path#tempdir()* function + +Create a temporary directory and return the pathname of the directory. + +------------------------------------------------------------------------------- + *misc-manipulation-of-unix-file-permissions* +Manipulation of UNIX file permissions ~ + +Vim's |writefile()| function cannot set file permissions for newly created +files and although Vim script has a function to get file permissions (see +|getfperm()|) there is no equivalent for changing a file's permissions. + +This omission breaks the otherwise very useful idiom of updating a file by +writing its new contents to a temporary file and then renaming the temporary +file into place (which is as close as you're going to get to atomically +updating a file's contents on UNIX) because the file's permissions will not be +preserved! + +**Here's a practical example:** My vim-easytags [4] plug-in writes tags file +updates to a temporary file and renames the temporary file into place. When I +use 'sudo -s' on Ubuntu Linux it preserves my environment variables so my +'~/.vimrc' and the vim-easytags [4] plug-in are still loaded. Now when a tags +file is written the file becomes owned by root (my effective user id in the +'sudo' session). Once I leave the 'sudo' session I can no longer update my tags +file because it's now owned by root … ಠ_ಠ + +------------------------------------------------------------------------------- +The *xolox#misc#perm#update()* function + +Atomically update a file's contents while preserving the owner, group and mode. +The first argument is the pathname of the file to update (a string). The second +argument is the list of lines to be written to the file. Writes the new +contents to a temporary file and renames the temporary file into place, thereby +preventing readers from reading a partially written file. Returns 1 if the file +is successfully updated, 0 otherwise. + +Note that if |xolox#misc#perm#get()| and |xolox#misc#perm#set()| cannot be used +to preserve the file owner/group/mode the file is still updated using a rename +(for compatibility with non-UNIX systems and incompatible '/usr/bin/stat' +implementations) so in that case you can still lose the file's +owner/group/mode. + +------------------------------------------------------------------------------- +The *xolox#misc#perm#get()* function + +Get the owner, group and permissions of the pathname given as the first +argument. Returns an opaque value which you can later pass to +|xolox#misc#perm#set()|. + +------------------------------------------------------------------------------- +The *xolox#misc#perm#set()* function + +Set the permissions (the second argument) of the pathname given as the first +argument. Expects a permissions value created by |xolox#misc#perm#get()|. + +------------------------------------------------------------------------------- + *misc-persist-recall-vim-values-from-to-files* +Persist/recall Vim values from/to files ~ + +Vim's |string()| function can be used to serialize Vim script values like +numbers, strings, lists, dictionaries and composites of them to a string which +can later be evaluated using the |eval()| function to turn it back into the +original value. This Vim script provides functions to use these functions to +persist and recall Vim values from/to files. This is very useful for +communication between (possibly concurrent) Vim processes. + +------------------------------------------------------------------------------- +The *xolox#misc#persist#load()* function + +Read a Vim value like a number, string, list or dictionary from a file using +|readfile()| and |eval()|. The first argument is the filename of the file to +read (a string). The optional second argument specifies the default value which +is returned when the file can't be loaded. This function returns the loaded +value or the default value (which itself defaults to the integer 0). + +------------------------------------------------------------------------------- +The *xolox#misc#persist#save()* function + +Write a Vim value like a number, string, list or dictionary to a file using +|string()| and |writefile()|. The first argument is the filename of the file to +write (a string) and the second argument is the value to write (any value). + +This function writes the serialized value to an intermediate file which is then +renamed into place atomically. This avoids issues with concurrent processes +where for example a producer has written a partial file which is read by a +consumer before the file is complete. In this case the consumer would read a +corrupt value. The rename trick avoids this problem. + +------------------------------------------------------------------------------- + *misc-string-handling* +String handling ~ + +------------------------------------------------------------------------------- +The *xolox#misc#str#slug()* function + +Convert a string to a "slug" - something that can be safely used in filenames +and URLs without worrying about quoting/escaping of special characters. + +------------------------------------------------------------------------------- +The *xolox#misc#str#ucfirst()* function + +Uppercase the first character in a string (the first argument). + +------------------------------------------------------------------------------- +The *xolox#misc#str#unescape()* function + +Remove back slash escapes from a string (the first argument). + +------------------------------------------------------------------------------- +The *xolox#misc#str#compact()* function + +Compact whitespace in a string (the first argument). + +------------------------------------------------------------------------------- +The *xolox#misc#str#trim()* function + +Trim all whitespace from the start and end of a string (the first argument). + +------------------------------------------------------------------------------- +The *xolox#misc#str#indent()* function + +Indent all lines in a multi-line string (the first argument) with a specific +number of _space characters_ (the second argument, an integer). + +------------------------------------------------------------------------------- +The *xolox#misc#str#dedent()* function + +Remove common whitespace from a multi line string. + +------------------------------------------------------------------------------- + *misc-test-runner-infrastructure-for-vim-plug-ins* +Test runner & infrastructure for Vim plug-ins ~ + +The Vim auto-load script 'autoload/xolox/misc/test.vim' contains infrastructure +that can be used to run an automated Vim plug-in test suite. It provides a +framework for running test functions, keeping track of the test status, making +assertions and reporting test results to the user. + +------------------------------------------------------------------------------- +The *xolox#misc#test#reset()* function + +Reset counters for executed tests and passed/failed assertions. + +------------------------------------------------------------------------------- +The *xolox#misc#test#summarize()* function + +Print a summary of test results, to be interpreted interactively. + +------------------------------------------------------------------------------- +The *xolox#misc#test#wrap()* function + +Call a function in a try/catch block and prevent exceptions from bubbling. The +name of the function should be passed as the first and only argument; it should +be a string containing the name of a Vim auto-load function. + +------------------------------------------------------------------------------- +The *xolox#misc#test#passed()* function + +Record a test which succeeded. + +------------------------------------------------------------------------------- +The *xolox#misc#test#failed()* function + +Record a test which failed. + +------------------------------------------------------------------------------- +The *xolox#misc#test#assert_true()* function + +Check whether an expression is true. + +------------------------------------------------------------------------------- +The *xolox#misc#test#assert_equals()* function + +Check whether two values are the same. + +------------------------------------------------------------------------------- +The *xolox#misc#test#assert_same_type()* function + +Check whether two values are of the same type. + +------------------------------------------------------------------------------- + *tests-for-miscellaneous-vim-scripts* +Tests for the miscellaneous Vim scripts ~ + +The Vim auto-load script 'autoload/xolox/misc/tests.vim' contains the automated +test suite of the miscellaneous Vim scripts. Right now the coverage is not very +high yet, but this will improve over time. + +------------------------------------------------------------------------------- +The *xolox#misc#tests#run()* function + +Run the automated test suite of the miscellaneous Vim scripts. To be used +interactively. Intended to be safe to execute irrespective of context. + +------------------------------------------------------------------------------- +The *xolox#misc#tests#pattern_escaping()* function + +Test escaping of regular expression patterns with +|xolox#misc#escape#pattern()|. + +------------------------------------------------------------------------------- +The *xolox#misc#tests#substitute_escaping()* function + +Test escaping of substitution strings with |xolox#misc#escape#substitute()|. + +------------------------------------------------------------------------------- +The *xolox#misc#tests#shell_escaping()* function + +Test escaping of shell arguments with |xolox#misc#escape#shell()|. + +------------------------------------------------------------------------------- +The *xolox#misc#tests#making_a_list_unique()* function + +Test removing of duplicate values from lists with |xolox#misc#list#unique()|. + +------------------------------------------------------------------------------- +The *xolox#misc#tests#binary_insertion()* function + +Test the binary insertion algorithm implemented in |xolox#misc#list#binsert()|. + +------------------------------------------------------------------------------- +The *xolox#misc#tests#getting_configuration_options()* function + +Test getting of scoped plug-in configuration "options" with +|xolox#misc#option#get()|. + +------------------------------------------------------------------------------- +The *xolox#misc#tests#splitting_of_multi_valued_options()* function + +Test splitting of multi-valued Vim options with |xolox#misc#option#split()|. + +------------------------------------------------------------------------------- +The *xolox#misc#tests#joining_of_multi_valued_options()* function + +Test joining of multi-valued Vim options with |xolox#misc#option#join()|. + +------------------------------------------------------------------------------- +The *xolox#misc#tests#finding_vim_on_the_search_path()* function + +Test looking up Vim's executable on the search path using |v:progname| with +|xolox#misc#os#find_vim()|. + +------------------------------------------------------------------------------- +The *xolox#misc#tests#synchronous_command_execution()* function + +Test basic functionality of synchronous command execution with +|xolox#misc#os#exec()|. + +------------------------------------------------------------------------------- +The *xolox#misc#tests#synchronous_command_execution_with_stderr()* function + +Test basic functionality of synchronous command execution with +|xolox#misc#os#exec()| including the standard error stream (not available on +Windows when vim-shell is not installed). + +------------------------------------------------------------------------------- +The *xolox#misc#tests#synchronous_command_execution_with_raising_of_errors()* +function + +Test raising of errors during synchronous command execution with +|xolox#misc#os#exec()|. + +------------------------------------------------------------------------------- +The *xolox#misc#tests#synchronous_command_execution_without_raising_errors()* +function + +Test synchronous command execution without raising of errors with +|xolox#misc#os#exec()|. + +------------------------------------------------------------------------------- +The *xolox#misc#tests#asynchronous_command_execution()* function + +Test the basic functionality of asynchronous command execution with +|xolox#misc#os#exec()|. This runs the external command 'mkdir' and tests that +the side effect of creating the directory takes place. This might seem like a +peculiar choice, but it's one of the few 100% portable commands (Windows + +UNIX) that doesn't involve input/output streams. + +------------------------------------------------------------------------------- +The *xolox#misc#tests#string_case_transformation()* function + +Test string case transformation with |xolox#misc#str#ucfirst()|. + +------------------------------------------------------------------------------- +The *xolox#misc#tests#string_whitespace_compaction()* function + +Test compaction of whitespace in strings with |xolox#misc#str#compact()|. + +------------------------------------------------------------------------------- +The *xolox#misc#tests#string_whitespace_trimming()* function + +Test trimming of whitespace in strings with |xolox#misc#str#trim()|. + +------------------------------------------------------------------------------- +The *xolox#misc#tests#multiline_string_dedent()* function + +Test dedenting of multi-line strings with |xolox#misc#str#dedent()|. + +------------------------------------------------------------------------------- +The *xolox#misc#tests#version_string_parsing()* function + +Test parsing of version strings with |xolox#misc#version#parse()|. + +------------------------------------------------------------------------------- +The *xolox#misc#tests#version_string_comparison()* function + +Test comparison of version strings with |xolox#misc#version#at_least()|. + +------------------------------------------------------------------------------- + *misc-timing-of-long-during-operations* +Timing of long during operations ~ + +------------------------------------------------------------------------------- +The *xolox#misc#timer#resumable()* function + +Create a resumable timer object. This returns an object (a dictionary with +functions) with the following "methods": + +- 'start()' instructs the timer object to start counting elapsed time (when a + timer object is created it is not automatically started). + +- 'stop()' instructs the timer object to stop counting elapsed time. This + adds the time elapsed since 'start()' was last called to the total elapsed + time. This method will raise an error if called out of sequence. + +- 'format()' takes the total elapsed time and reports it as a string + containing a formatted floating point number. + +Timer objects are meant to accurately time short running operations so they're +dependent on Vim's |reltime()| and |reltimestr()| functions. In order to make +it possible to use timer objects in my Vim plug-ins unconditionally there's a +fall back to |localtime()| when |reltime()| is not available. In this mode the +timer objects are not very useful but at least they shouldn't raise errors. + +------------------------------------------------------------------------------- +The *xolox#misc#timer#start()* function + +Start a timer. This returns a list which can later be passed to +|xolox#misc#timer#stop()|. + +------------------------------------------------------------------------------- +The *xolox#misc#timer#stop()* function + +Show a formatted debugging message to the user, if the user has enabled +increased verbosity by setting Vim's |'verbose'| option to one (1) or higher. + +This function has the same argument handling as Vim's |printf()| function with +one difference: At the point where you want the elapsed time to be embedded, +you write '%s' and you pass the list returned by |xolox#misc#timer#start()| as +an argument. + +------------------------------------------------------------------------------- +The *xolox#misc#timer#force()* function + +Show a formatted message to the user. This function has the same argument +handling as Vim's |printf()| function with one difference: At the point where +you want the elapsed time to be embedded, you write '%s' and you pass the list +returned by |xolox#misc#timer#start()| as an argument. + +------------------------------------------------------------------------------- +The *xolox#misc#timer#convert()* function + +Convert the value returned by |xolox#misc#timer#start()| to a string +representation of the elapsed time since |xolox#misc#timer#start()| was called. +Other values are returned unmodified (this allows using it with Vim's |map()| +function). + +------------------------------------------------------------------------------- + *misc-version-string-handling* +Version string handling ~ + +------------------------------------------------------------------------------- +The *xolox#misc#version#parse()* function + +Convert a version string to a list of integers. + +------------------------------------------------------------------------------- +The *xolox#misc#version#at_least()* function + +Check whether the second version string is equal to or greater than the first +version string. Returns 1 (true) when it is, 0 (false) otherwise. + +=============================================================================== + *misc-contact* +Contact ~ + +If you have questions, bug reports, suggestions, etc. please open an issue or +pull request on GitHub [12]. Download links and documentation can be found on +the plug-in's homepage [13]. If you like the script please vote for it on Vim +Online [14]. + +=============================================================================== + *misc-license* +License ~ + +This software is licensed under the MIT license [15]. © 2015 Peter Odding +. + +=============================================================================== + *misc-references* +References ~ + +[1] http://peterodding.com/code/vim/ +[2] https://github.com/xolox/vim-misc/blob/master/INSTALL.md +[3] http://peterodding.com/code/vim/tools/ +[4] http://peterodding.com/code/vim/easytags/ +[5] http://peterodding.com/code/vim/misc/ +[6] http://peterodding.com/code/vim/notes/ +[7] http://peterodding.com/code/vim/notes/#recentnotes_command +[8] http://peterodding.com/code/vim/notes/#showtaggednotes_command +[9] http://peterodding.com/code/vim/session/ +[10] https://github.com/xolox/vim-misc/pull/16 +[11] http://peterodding.com/code/vim/shell/ +[12] http://github.com/xolox/vim-misc +[13] http://peterodding.com/code/vim/misc +[14] http://www.vim.org/scripts/script.php?script_id=4597 +[15] http://en.wikipedia.org/wiki/MIT_License + +vim: ft=help diff --git a/VimFiles/autoload/omni/common/debug.vim b/VimFiles/autoload/omni/common/debug.vim new file mode 100755 index 0000000..eded649 --- /dev/null +++ b/VimFiles/autoload/omni/common/debug.vim @@ -0,0 +1,32 @@ +" Description: Omni completion debug functions +" Maintainer: Vissale NEANG +" Last Change: 26 sept. 2007 + +let s:CACHE_DEBUG_TRACE = [] + +" Start debug, clear the debug file +function! omni#common#debug#Start() + let s:CACHE_DEBUG_TRACE = [] + call extend(s:CACHE_DEBUG_TRACE, ['============ Debug Start ============']) + call writefile(s:CACHE_DEBUG_TRACE, "Omni.dbg") +endfunc + +" End debug, write to debug file +function! omni#common#debug#End() + call extend(s:CACHE_DEBUG_TRACE, ["============= Debug End ============="]) + call extend(s:CACHE_DEBUG_TRACE, [""]) + call writefile(s:CACHE_DEBUG_TRACE, "Omni.dbg") +endfunc + +" Debug trace function +function! omni#common#debug#Trace(szFuncName, ...) + let szTrace = a:szFuncName + let paramNum = a:0 + if paramNum>0 + let szTrace .= ':' + endif + for i in range(paramNum) + let szTrace = szTrace .' ('. string(eval('a:'.string(i+1))).')' + endfor + call extend(s:CACHE_DEBUG_TRACE, [szTrace]) +endfunc diff --git a/VimFiles/autoload/omni/common/utils.vim b/VimFiles/autoload/omni/common/utils.vim new file mode 100755 index 0000000..c880ad2 --- /dev/null +++ b/VimFiles/autoload/omni/common/utils.vim @@ -0,0 +1,67 @@ +" Description: Omni completion utils +" Maintainer: Vissale NEANG +" Last Change: 26 sept. 2007 + +" For sort numbers in list +function! omni#common#utils#CompareNumber(i1, i2) + let num1 = eval(a:i1) + let num2 = eval(a:i2) + return num1 == num2 ? 0 : num1 > num2 ? 1 : -1 +endfunc + +" TagList function calling the vim taglist() with try catch +" The only throwed exception is 'TagList:UserInterrupt' +" We also force the noignorecase option to avoid linear search when calling +" taglist() +function! omni#common#utils#TagList(szTagQuery) + let result = [] + let bUserIgnoreCase = &ignorecase + " Forcing noignorecase search => binary search can be used in taglist() + " if tags in the tag file are sorted + if bUserIgnoreCase + set noignorecase + endif + try + let result = taglist(a:szTagQuery) + catch /^Vim:Interrupt$/ + " Restoring user's setting + if bUserIgnoreCase + set ignorecase + endif + throw 'TagList:UserInterrupt' + catch + "Note: it seems that ctags can generate corrupted files, in this case + "taglist() will fail to read the tagfile and an exception from + "has_add() is thrown + endtry + + " Restoring user's setting + if bUserIgnoreCase + set ignorecase + endif + return result +endfunc + +" Same as TagList but don't throw exception +function! omni#common#utils#TagListNoThrow(szTagQuery) + let result = [] + try + let result = omni#common#utils#TagList(a:szTagQuery) + catch + endtry + return result +endfunc + +" Get the word under the cursor +function! omni#common#utils#GetWordUnderCursor() + let szLine = getline('.') + let startPos = getpos('.')[2]-1 + let startPos = (startPos < 0)? 0 : startPos + if szLine[startPos] =~ '\w' + let startPos = searchpos('\<\w\+', 'cbn', line('.'))[1] - 1 + endif + + let startPos = (startPos < 0)? 0 : startPos + let szResult = matchstr(szLine, '\w\+', startPos) + return szResult +endfunc diff --git a/VimFiles/autoload/omni/cpp/complete.vim b/VimFiles/autoload/omni/cpp/complete.vim new file mode 100755 index 0000000..a7e4edc --- /dev/null +++ b/VimFiles/autoload/omni/cpp/complete.vim @@ -0,0 +1,569 @@ +" 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 diff --git a/VimFiles/autoload/omni/cpp/includes.vim b/VimFiles/autoload/omni/cpp/includes.vim new file mode 100755 index 0000000..10a89bc --- /dev/null +++ b/VimFiles/autoload/omni/cpp/includes.vim @@ -0,0 +1,126 @@ +" Description: Omni completion script for cpp files +" Maintainer: Vissale NEANG +" Last Change: 26 sept. 2007 + +let g:omni#cpp#includes#CACHE_INCLUDES = {} +let g:omni#cpp#includes#CACHE_FILE_TIME = {} + +let s:rePreprocIncludePart = '\C#\s*include\s*' +let s:reIncludeFilePart = '\(<\|"\)\(\f\|\s\)\+\(>\|"\)' +let s:rePreprocIncludeFile = s:rePreprocIncludePart . s:reIncludeFilePart + +" Get the include list of a file +function! omni#cpp#includes#GetList(...) + if a:0 > 0 + return s:GetIncludeListFromFile(a:1, (a:0 > 1)? a:2 : 0 ) + else + return s:GetIncludeListFromCurrentBuffer() + endif +endfunc + +" Get the include list from the current buffer +function! s:GetIncludeListFromCurrentBuffer() + let listIncludes = [] + let originalPos = getpos('.') + + call setpos('.', [0, 1, 1, 0]) + let curPos = [1,1] + let alreadyInclude = {} + while curPos != [0,0] + let curPos = searchpos('\C\(^'.s:rePreprocIncludeFile.'\)', 'W') + if curPos != [0,0] + let szLine = getline('.') + let startPos = curPos[1] + let endPos = matchend(szLine, s:reIncludeFilePart, startPos-1) + if endPos!=-1 + let szInclusion = szLine[startPos-1:endPos-1] + let szIncludeFile = substitute(szInclusion, '\('.s:rePreprocIncludePart.'\)\|[<>""]', '', 'g') + let szResolvedInclude = omni#cpp#utils#ResolveFilePath(szIncludeFile) + + " Protection over self inclusion + if szResolvedInclude != '' && szResolvedInclude != omni#cpp#utils#ResolveFilePath(getreg('%')) + let includePos = curPos + if !has_key(alreadyInclude, szResolvedInclude) + call extend(listIncludes, [{'pos' : includePos, 'include' : szResolvedInclude}]) + let alreadyInclude[szResolvedInclude] = 1 + endif + endif + endif + endif + endwhile + + call setpos('.', originalPos) + return listIncludes +endfunc + +" Get the include list from a file +function! s:GetIncludeListFromFile(szFilePath, bUpdate) + let listIncludes = [] + if a:szFilePath == '' + return listIncludes + endif + + if !a:bUpdate && has_key(g:omni#cpp#includes#CACHE_INCLUDES, a:szFilePath) + return copy(g:omni#cpp#includes#CACHE_INCLUDES[a:szFilePath]) + endif + + let g:omni#cpp#includes#CACHE_FILE_TIME[a:szFilePath] = getftime(a:szFilePath) + + let szFixedPath = escape(a:szFilePath, g:omni#cpp#utils#szEscapedCharacters) + execute 'silent! lvimgrep /\C\(^'.s:rePreprocIncludeFile.'\)/gj '.szFixedPath + + let listQuickFix = getloclist(0) + let alreadyInclude = {} + for qf in listQuickFix + let szLine = qf.text + let startPos = qf.col + let endPos = matchend(szLine, s:reIncludeFilePart, startPos-1) + if endPos!=-1 + let szInclusion = szLine[startPos-1:endPos-1] + let szIncludeFile = substitute(szInclusion, '\('.s:rePreprocIncludePart.'\)\|[<>""]', '', 'g') + let szResolvedInclude = omni#cpp#utils#ResolveFilePath(szIncludeFile) + + " Protection over self inclusion + if szResolvedInclude != '' && szResolvedInclude != a:szFilePath + let includePos = [qf.lnum, qf.col] + if !has_key(alreadyInclude, szResolvedInclude) + call extend(listIncludes, [{'pos' : includePos, 'include' : szResolvedInclude}]) + let alreadyInclude[szResolvedInclude] = 1 + endif + endif + endif + endfor + + let g:omni#cpp#includes#CACHE_INCLUDES[a:szFilePath] = listIncludes + + return copy(listIncludes) +endfunc + +" For debug purpose +function! omni#cpp#includes#Display() + let szPathBuffer = omni#cpp#utils#ResolveFilePath(getreg('%')) + call s:DisplayIncludeTree(szPathBuffer, 0) +endfunc + +" For debug purpose +function! s:DisplayIncludeTree(szFilePath, indent, ...) + let includeGuard = {} + if a:0 >0 + let includeGuard = a:1 + endif + let szFilePath = omni#cpp#utils#ResolveFilePath(a:szFilePath) + if has_key(includeGuard, szFilePath) + return + else + let includeGuard[szFilePath] = 1 + endif + + let szIndent = repeat(' ', a:indent) + echo szIndent . a:szFilePath + let incList = omni#cpp#includes#GetList(a:szFilePath) + for inc in incList + call s:DisplayIncludeTree(inc.include, a:indent+1, includeGuard) + endfor +endfunc + + diff --git a/VimFiles/autoload/omni/cpp/items.vim b/VimFiles/autoload/omni/cpp/items.vim new file mode 100755 index 0000000..b943ad4 --- /dev/null +++ b/VimFiles/autoload/omni/cpp/items.vim @@ -0,0 +1,660 @@ +" Description: Omni completion script for cpp files +" Maintainer: Vissale NEANG +" Last Change: 26 sept. 2007 + +" Build the item list of an instruction +" An item is an instruction between a -> or . or ->* or .* +" We can sort an item in different kinds: +" eg: ((MyClass1*)(pObject))->_memberOfClass1.get() ->show() +" | cast | | member | | method | | method | +" @return a list of item +" an item is a dictionnary where keys are: +" tokens = list of token +" kind = itemVariable|itemCast|itemCppCast|itemTemplate|itemFunction|itemUnknown|itemThis|itemScope +function! omni#cpp#items#Get(tokens, ...) + let bGetWordUnderCursor = (a:0>0)? a:1 : 0 + + let result = [] + let itemsDelimiters = ['->', '.', '->*', '.*'] + + let tokens = reverse(omni#cpp#utils#BuildParenthesisGroups(a:tokens)) + + " fsm states: + " 0 = initial state + " TODO: add description of fsm states + let state=(bGetWordUnderCursor)? 1 : 0 + let item = {'tokens' : [], 'kind' : 'itemUnknown'} + let parenGroup=-1 + for token in tokens + if state==0 + if index(itemsDelimiters, token.value)>=0 + let item = {'tokens' : [], 'kind' : 'itemUnknown'} + let state = 1 + elseif token.value=='::' + let state = 9 + let item.kind = 'itemScope' + " Maybe end of tokens + elseif token.kind =='cppOperatorPunctuator' + " If it's a cppOperatorPunctuator and the current token is not + " a itemsDelimiters or '::' we can exit + let state=-1 + break + endif + elseif state==1 + call insert(item.tokens, token) + if token.kind=='cppWord' + " It's an attribute member or a variable + let item.kind = 'itemVariable' + let state = 2 + " Maybe end of tokens + elseif token.value=='this' + let item.kind = 'itemThis' + let state = 2 + " Maybe end of tokens + elseif token.value==')' + let parenGroup = token.group + let state = 3 + elseif token.value==']' + let parenGroup = token.group + let state = 4 + elseif token.kind == 'cppDigit' + let state = -1 + break + endif + elseif state==2 + if index(itemsDelimiters, token.value)>=0 + call insert(result, item) + let item = {'tokens' : [], 'kind' : 'itemUnknown'} + let state = 1 + elseif token.value == '::' + call insert(item.tokens, token) + " We have to get namespace or classscope + let state = 8 + " Maybe end of tokens + else + call insert(result, item) + let state=-1 + break + endif + elseif state==3 + call insert(item.tokens, token) + if token.value=='(' && token.group == parenGroup + let state = 5 + " Maybe end of tokens + endif + elseif state==4 + call insert(item.tokens, token) + if token.value=='[' && token.group == parenGroup + let state = 1 + endif + elseif state==5 + if token.kind=='cppWord' + " It's a function or method + let item.kind = 'itemFunction' + call insert(item.tokens, token) + let state = 2 + " Maybe end of tokens + elseif token.value == '>' + " Maybe a cpp cast or template + let item.kind = 'itemTemplate' + call insert(item.tokens, token) + let parenGroup = token.group + let state = 6 + else + " Perhaps it's a C cast eg: ((void*)(pData)) or a variable eg:(*pData) + let item.kind = omni#cpp#utils#GetCastType(item.tokens) + let state=-1 + call insert(result, item) + break + endif + elseif state==6 + call insert(item.tokens, token) + if token.value == '<' && token.group == parenGroup + " Maybe a cpp cast or template + let state = 7 + endif + elseif state==7 + call insert(item.tokens, token) + if token.kind=='cppKeyword' + " It's a cpp cast + let item.kind = omni#cpp#utils#GetCastType(item.tokens) + let state=-1 + call insert(result, item) + break + else + " Template ? + let state=-1 + call insert(result, item) + break + endif + elseif state==8 + if token.kind=='cppWord' + call insert(item.tokens, token) + let state = 2 + " Maybe end of tokens + else + let state=-1 + call insert(result, item) + break + endif + elseif state==9 + if token.kind == 'cppWord' + call insert(item.tokens, token) + let state = 10 + " Maybe end of tokens + else + let state=-1 + call insert(result, item) + break + endif + elseif state==10 + if token.value == '::' + call insert(item.tokens, token) + let state = 9 + " Maybe end of tokens + else + let state=-1 + call insert(result, item) + break + endif + endif + endfor + + if index([2, 5, 8, 9, 10], state)>=0 + if state==5 + let item.kind = omni#cpp#utils#GetCastType(item.tokens) + endif + call insert(result, item) + endif + + return result +endfunc + +" Resolve type information of items +" @param namespaces: list of namespaces used in the file +" @param szCurrentClassScope: the current class scope, only used for the first +" item to detect if this item is a class member (attribute, method) +" @param items: list of item, can be an empty list @see GetItemsToComplete +function! omni#cpp#items#ResolveItemsTypeInfo(contextStack, items) + " Note: kind = itemVariable|cCast|cppCast|template|function|itemUnknown|this + " For the first item, if it's a variable we try to detect the type of the + " variable with the function searchdecl. If it fails, thanks to the + " current class scope, we try to detect if the variable is an attribute + " member. + " If the kind of the item is a function, we have to first check if the + " function is a method of the class, if it fails we try to get a match in + " the global namespace. After that we get the returned type of the + " function. + " It the kind is a C cast or C++ cast, there is no problem, it's the + " easiest case. We just extract the type of the cast. + + let szCurrentContext = '' + let typeInfo = {} + " Note: We search the decl only for the first item + let bSearchDecl = 1 + for item in a:items + let curItem = item + if index(['itemVariable', 'itemFunction'], curItem.kind)>=0 + " Note: a variable can be : MyNs::MyClass::_var or _var or (*pVar) + " or _var[0][0] + let szSymbol = s:GetSymbol(curItem.tokens) + + " If we have MyNamespace::myVar + " We add MyNamespace in the context stack set szSymbol to myVar + if match(szSymbol, '::\w\+$') >= 0 + let szCurrentContext = substitute(szSymbol, '::\w\+$', '', 'g') + let szSymbol = matchstr(szSymbol, '\w\+$') + endif + let tmpContextStack = a:contextStack + if szCurrentContext != '' + let tmpContextStack = [szCurrentContext] + a:contextStack + endif + + if curItem.kind == 'itemVariable' + let typeInfo = s:GetTypeInfoOfVariable(tmpContextStack, szSymbol, bSearchDecl) + else + let typeInfo = s:GetTypeInfoOfReturnedType(tmpContextStack, szSymbol) + endif + + elseif curItem.kind == 'itemThis' + if len(a:contextStack) + let typeInfo = omni#cpp#utils#CreateTypeInfo(substitute(a:contextStack[0], '^::', '', 'g')) + endif + elseif curItem.kind == 'itemCast' + let typeInfo = omni#cpp#utils#CreateTypeInfo(s:ResolveCCast(curItem.tokens)) + elseif curItem.kind == 'itemCppCast' + let typeInfo = omni#cpp#utils#CreateTypeInfo(s:ResolveCppCast(curItem.tokens)) + elseif curItem.kind == 'itemScope' + let typeInfo = omni#cpp#utils#CreateTypeInfo(substitute(s:TokensToString(curItem.tokens), '\s', '', 'g')) + endif + + if omni#cpp#utils#IsTypeInfoValid(typeInfo) + let szCurrentContext = omni#cpp#utils#GetTypeInfoString(typeInfo) + endif + let bSearchDecl = 0 + endfor + + return typeInfo +endfunc + +" Get symbol name +function! s:GetSymbol(tokens) + let szSymbol = '' + let state = 0 + for token in a:tokens + if state == 0 + if token.value == '::' + let szSymbol .= token.value + let state = 1 + elseif token.kind == 'cppWord' + let szSymbol .= token.value + let state = 2 + " Maybe end of token + endif + elseif state == 1 + if token.kind == 'cppWord' + let szSymbol .= token.value + let state = 2 + " Maybe end of token + else + " Error + break + endif + elseif state == 2 + if token.value == '::' + let szSymbol .= token.value + let state = 1 + else + break + endif + endif + endfor + return szSymbol +endfunc + +" Search a declaration. +" eg: std::map +" can be empty +" Note: The returned type info can be a typedef +" The typedef resolution is done later +" @return +" - a dictionnary where keys are +" - type: the type of value same as type() +" - value: the value +function! s:GetTypeInfoOfVariable(contextStack, szVariable, bSearchDecl) + let result = {} + + if a:bSearchDecl + " Search type of declaration + "let result = s:SearchTypeInfoOfDecl(a:szVariable) + let result = s:SearchDecl(a:szVariable) + endif + + if result=={} + let szFilter = "index(['m', 'v'], v:val.kind[0])>=0" + let tagItem = s:ResolveSymbol(a:contextStack, a:szVariable, szFilter) + if tagItem=={} + return result + endif + + let szCmdWithoutVariable = substitute(omni#cpp#utils#ExtractCmdFromTagItem(tagItem), '\C\<'.a:szVariable.'\>.*', '', 'g') + let tokens = omni#cpp#tokenizer#Tokenize(omni#cpp#utils#GetCodeFromLine(szCmdWithoutVariable)) + let result = omni#cpp#utils#CreateTypeInfo(omni#cpp#utils#ExtractTypeInfoFromTokens(tokens)) + " TODO: Namespace resolution for result + + if result != {} && result.value=='' + " result.value=='' + " eg: + " struct + " { + " }gVariable; + if has_key(tagItem, 'typeref') + " Maybe the variable is a global var of an + " unnamed class, struct or union. + " eg: + " 1) + " struct + " { + " }gVariable; + " In this case we need the tags (the patched version) + " Note: We can have a named type like this: + " 2) + " class A + " { + " }gVariable; + if s:IsUnnamedType(tagItem) + " It's an unnamed type we are in the case 1) + let result = omni#cpp#utils#CreateTypeInfo(tagItem) + else + " It's not an unnamed type we are in the case 2) + + " eg: tagItem.typeref = 'struct:MY_STRUCT::MY_SUBSTRUCT' + let szTypeRef = substitute(tagItem.typeref, '^\w\+:', '', '') + + " eg: szTypeRef = 'MY_STRUCT::MY_SUBSTRUCT' + let result = omni#cpp#utils#CreateTypeInfo(szTypeRef) + endif + endif + endif + endif + return result +endfunc + +" Get the type info string from the returned type of function +function! s:GetTypeInfoOfReturnedType(contextStack, szFunctionName) + let result = {} + + let szFilter = "index(['f', 'p'], v:val.kind[0])>=0" + let tagItem = s:ResolveSymbol(a:contextStack, a:szFunctionName, szFilter) + + if tagItem != {} + let szCmdWithoutVariable = substitute(omni#cpp#utils#ExtractCmdFromTagItem(tagItem), '\C\<'.a:szFunctionName.'\>.*', '', 'g') + let tokens = omni#cpp#tokenizer#Tokenize(omni#cpp#utils#GetCodeFromLine(szCmdWithoutVariable)) + let result = omni#cpp#utils#CreateTypeInfo(omni#cpp#utils#ExtractTypeInfoFromTokens(tokens)) + " TODO: Namespace resolution for result + return result + endif + return result +endfunc + +" Resolve a symbol, return a tagItem +" Gets the first symbol found in the context stack +function! s:ResolveSymbol(contextStack, szSymbol, szTagFilter) + let tagItem = {} + for szCurrentContext in a:contextStack + if szCurrentContext != '::' + let szTagQuery = substitute(szCurrentContext, '^::', '', 'g').'::'.a:szSymbol + else + let szTagQuery = a:szSymbol + endif + + let tagList = omni#common#utils#TagListNoThrow('^'.szTagQuery.'$') + call filter(tagList, a:szTagFilter) + if len(tagList) + let tagItem = tagList[0] + break + endif + endfor + return tagItem +endfunc + +" Return if the tag item represent an unnamed type +function! s:IsUnnamedType(tagItem) + let bResult = 0 + if has_key(a:tagItem, 'typeref') + " Note: Thanks for __anon ! + let bResult = match(a:tagItem.typeref, '\C\<__anon') >= 0 + endif + return bResult +endfunc + +" Search the declaration of a variable and return the type info +function! s:SearchTypeInfoOfDecl(szVariable) + let szReVariable = '\C\<'.a:szVariable.'\>' + + let originalPos = getpos('.') + let origPos = originalPos[1:2] + let curPos = origPos + let stopPos = origPos + + while curPos !=[0,0] + " We go to the start of the current scope + let curPos = searchpairpos('{', '', '}', 'bW', g:omni#cpp#utils#expIgnoreComments) + if curPos != [0,0] + let matchPos = curPos + " Now want to search our variable but we don't want to go in child + " scope + while matchPos != [0,0] + let matchPos = searchpos('{\|'.szReVariable, 'W', stopPos[0]) + if matchPos != [0,0] + " We ignore matches under comment + if omni#cpp#utils#IsCursorInCommentOrString() + continue + endif + + " Getting the current line + let szLine = getline('.') + if match(szLine, szReVariable)>=0 + " We found our variable + " Check if the current instruction is a decl instruction + let tokens = omni#cpp#utils#TokenizeCurrentInstruction() + let szTypeInfo = s:ExtractTypeInfoFromDecl(tokens) + if szTypeInfo != '' + call setpos('.', originalPos) + return omni#cpp#utils#CreateTypeInfo(szTypeInfo) + endif + else + " We found a child scope, we don't want to go in, thus + " we search for the end } of this child scope + let bracketEnd = searchpairpos('{', '', '}', 'nW', g:omni#cpp#utils#expIgnoreComments) + if bracketEnd == [0,0] + break + endif + + if bracketEnd[0] >= stopPos[0] + " The end of the scope is after our cursor we stop + " the search + break + else + " We move the cursor and continue to search our + " variable + call setpos('.', [0, bracketEnd[0], bracketEnd[1], 0]) + endif + endif + endif + endwhile + + " Backing to the start of the scope + call setpos('.', [0,curPos[0], curPos[1], 0]) + let stopPos = curPos + endif + endwhile + + let result = {} + if s:LocalSearchDecl(a:szVariable)==0 && !omni#cpp#utils#IsCursorInCommentOrString() + let tokens = omni#cpp#utils#TokenizeCurrentInstruction() + let szTypeInfo = s:ExtractTypeInfoFromDecl(tokens) + if szTypeInfo != '' + let result = omni#cpp#utils#CreateTypeInfo(szTypeInfo) + endif + endif + + call setpos('.', originalPos) + + return result +endfunc + +" Search a declaration +" @return +" - tokens of the current instruction if success +" - empty list if failure +function! s:SearchDecl(szVariable) + let result = {} + let originalPos = getpos('.') + let searchResult = s:LocalSearchDecl(a:szVariable) + if searchResult==0 + " searchdecl() may detect a decl if the variable is in a conditional + " instruction (if, elseif, while etc...) + " We have to check if the detected decl is really a decl instruction + let tokens = omni#cpp#utils#TokenizeCurrentInstruction() + + for token in tokens + " Simple test + if index(['if', 'elseif', 'while', 'for', 'switch'], token.value)>=0 + " Invalid declaration instruction + call setpos('.', originalPos) + return result + endif + endfor + + let szTypeInfo = s:ExtractTypeInfoFromDecl(tokens) + if szTypeInfo != '' + let result = omni#cpp#utils#CreateTypeInfo(szTypeInfo) + endif + endif + call setpos('.', originalPos) + return result +endfunc + +" Extract the type info string from an instruction. +" We use a small parser to extract the type +" We parse the code according to a C++ BNF from: http://www.nongnu.org/hcb/#basic.link +" @param tokens: token list of the current instruction +function! s:ExtractTypeInfoFromDecl(tokens) + return omni#cpp#utils#ExtractTypeInfoFromTokens(a:tokens) +endfunc + +" Convert tokens to string +function! s:TokensToString(tokens) + let result = '' + for token in a:tokens + let result = result . token.value . ' ' + endfor + return result[:-2] +endfunc + +" Resolve a cast. +" Resolve a C++ cast +" @param list of token. tokens must be a list that represents +" a cast expression (C++ cast) the function does not control +" if it's a cast or not +" eg: static_cast(something) +" @return type info string +function! s:ResolveCppCast(tokens) + return omni#cpp#utils#ExtractTypeInfoFromTokens(s:ResolveCast(a:tokens, '<', '>')) +endfunc + +" Resolve a cast. +" Resolve a C cast +" @param list of token. tokens must be a list that represents +" a cast expression (C cast) the function does not control +" if it's a cast or not +" eg: (MyClass*)something +" @return type info string +function! s:ResolveCCast(tokens) + return omni#cpp#utils#ExtractTypeInfoFromTokens(s:ResolveCast(a:tokens, '(', ')')) +endfunc + +" Resolve a cast. +" Resolve a C cast +" @param list of token. tokens must be a list that represents +" a cast expression (C cast) the function does not control +" if it's a cast or not +" eg: (MyClass*)something +" @return type tokens +function! s:ResolveCast(tokens, startChar, endChar) + let tokens = omni#cpp#utils#BuildParenthesisGroups(a:tokens) + + " We remove useless parenthesis eg: (((MyClass))) + let tokens = omni#cpp#utils#SimplifyParenthesis(tokens) + + let countItem=0 + let startIndex = -1 + let endIndex = -1 + let i = 0 + for token in tokens + if startIndex==-1 + if token.value==a:startChar + let countItem += 1 + let startIndex = i + endif + else + if token.value==a:startChar + let countItem += 1 + elseif token.value==a:endChar + let countItem -= 1 + endif + + if countItem==0 + let endIndex = i + break + endif + endif + let i+=1 + endfor + + return tokens[startIndex+1 : endIndex-1] +endfunc + +" Replacement for build-in function 'searchdecl' +" It does not require that the upper-level bracket is in the first column. +" Otherwise it should be equal to 'searchdecl(name, 0, 1)' +" @param name: name of variable to find declaration for +function! s:LocalSearchDecl(name) + + if g:OmniCpp_LocalSearchDecl == 0 + let bUserIgnoreCase = &ignorecase + + " Forcing the noignorecase option + " avoid bug when, for example, if we have a declaration like this : "A a;" + set noignorecase + + let result = searchdecl(a:name, 0, 1) + + " Restoring user's setting + let &ignorecase = bUserIgnoreCase + + return result + endif + + let lastpos = getpos('.') + let winview = winsaveview() + let lastfoldenable = &foldenable + let &foldenable = 0 + + " We add \C (noignorecase) to + " avoid bug when, for example, if we have a declaration like this : "A a;" + let varname = "\\C\\<" . a:name . "\\>" + + " Go to first blank line before begin of highest scope + normal 99[{ + let scopepos = getpos('.') + while (line('.') > 1) && (len(split(getline('.'))) > 0) + call cursor(line('.')-1, 0) + endwhile + + let declpos = [ 0, 0, 0, 0 ] + while search(varname, '', scopepos[1]) > 0 + " Check if we are a string or a comment + if omni#cpp#utils#IsCursorInCommentOrString() + continue + endif + + " Remember match + let declpos = getpos('.') + endwhile + if declpos[1] != 0 + " We found a match + call winrestview(winview) + call setpos('.', declpos) + let &foldenable = lastfoldenable + return 0 + endif + + while search(varname, '', lastpos[1]) > 0 + " Check if current scope is ending before variable + let old_cur = getpos('.') + normal ]} + let new_cur = getpos('.') + call setpos('.', old_cur) + if (new_cur[1] < lastpos[1]) || ((new_cur[1] == lastpos[1]) && (new_cur[2] < lastpos[2])) + continue + endif + + " Check if we are a string or a comment + if omni#cpp#utils#IsCursorInCommentOrString() + continue + endif + + " We found match + call winrestview(winview) + call setpos('.', old_cur) + let &foldenable = lastfoldenable + return 0 + endwhile + + " No match found. + call winrestview(winview) + let &foldenable = lastfoldenable + return 1 +endfunc diff --git a/VimFiles/autoload/omni/cpp/maycomplete.vim b/VimFiles/autoload/omni/cpp/maycomplete.vim new file mode 100755 index 0000000..610526b --- /dev/null +++ b/VimFiles/autoload/omni/cpp/maycomplete.vim @@ -0,0 +1,82 @@ +" Description: Omni completion script for cpp files +" Maintainer: Vissale NEANG +" Last Change: 26 sept. 2007 + +" Check if we can use omni completion in the current buffer +function! s:CanUseOmnicompletion() + " For C and C++ files and only if the omnifunc is omni#cpp#complete#Main + return (index(['c', 'cpp'], &filetype)>=0 && &omnifunc == 'omni#cpp#complete#Main' && !omni#cpp#utils#IsCursorInCommentOrString()) +endfunc + +" Return the mapping of omni completion +function! omni#cpp#maycomplete#Complete() + let szOmniMapping = "\\" + + " 0 = don't select first item + " 1 = select first item (inserting it to the text, default vim behaviour) + " 2 = select first item (without inserting it to the text) + if g:OmniCpp_SelectFirstItem == 0 + " We have to force the menuone option to avoid confusion when there is + " only one popup item + set completeopt-=menu + set completeopt+=menuone + let szOmniMapping .= "\" + elseif g:OmniCpp_SelectFirstItem == 2 + " We have to force the menuone option to avoid confusion when there is + " only one popup item + set completeopt-=menu + set completeopt+=menuone + let szOmniMapping .= "\" + let szOmniMapping .= "\=pumvisible() ? \"\\\" : \"\"\" + endif + return szOmniMapping +endfunc + +" May complete function for dot +function! omni#cpp#maycomplete#Dot() + if s:CanUseOmnicompletion() && g:OmniCpp_MayCompleteDot + let g:omni#cpp#items#data = omni#cpp#items#Get(omni#cpp#utils#TokenizeCurrentInstruction('.')) + if len(g:omni#cpp#items#data) + let s:bMayComplete = 1 + return '.' . omni#cpp#maycomplete#Complete() + endif + endif + return '.' +endfunc +" May complete function for arrow +function! omni#cpp#maycomplete#Arrow() + if s:CanUseOmnicompletion() && g:OmniCpp_MayCompleteArrow + let index = col('.') - 2 + if index >= 0 + let char = getline('.')[index] + if char == '-' + let g:omni#cpp#items#data = omni#cpp#items#Get(omni#cpp#utils#TokenizeCurrentInstruction('>')) + if len(g:omni#cpp#items#data) + let s:bMayComplete = 1 + return '>' . omni#cpp#maycomplete#Complete() + endif + endif + endif + endif + return '>' +endfunc + +" May complete function for double points +function! omni#cpp#maycomplete#Scope() + if s:CanUseOmnicompletion() && g:OmniCpp_MayCompleteScope + let index = col('.') - 2 + if index >= 0 + let char = getline('.')[index] + if char == ':' + let g:omni#cpp#items#data = omni#cpp#items#Get(omni#cpp#utils#TokenizeCurrentInstruction(':')) + if len(g:omni#cpp#items#data) + if len(g:omni#cpp#items#data[-1].tokens) && g:omni#cpp#items#data[-1].tokens[-1].value != '::' + let s:bMayComplete = 1 + return ':' . omni#cpp#maycomplete#Complete() + endif + endif + endif + endif + endif + return ':' +endfunc diff --git a/VimFiles/autoload/omni/cpp/namespaces.vim b/VimFiles/autoload/omni/cpp/namespaces.vim new file mode 100755 index 0000000..386b3f9 --- /dev/null +++ b/VimFiles/autoload/omni/cpp/namespaces.vim @@ -0,0 +1,838 @@ +" Description: Omni completion script for cpp files +" Maintainer: Vissale NEANG +" Last Change: 26 sept. 2007 + +let g:omni#cpp#namespaces#CacheResolve = {} +let g:omni#cpp#namespaces#CacheUsing = {} +" TODO: For the next release +"let g:omni#cpp#namespaces#CacheAlias = {} + +" Get the using namespace list from a line +function! s:GetNamespaceAliasListFromLine(szLine) + let result = {} + let tokens = omni#cpp#tokenizer#Tokenize(a:szLine) + let szAlias = '' + let szNamespace = '' + let state = 0 + for token in tokens + if state==0 + let szAlias = '' + let szNamespace = '' + if token.value == '/*' + let state = 1 + elseif token.value == '//' + " It's a comment + let state = -1 + break + elseif token.value == 'namespace' + let state = 2 + endif + elseif state==1 + if token.value == '*/' + let state=0 + endif + elseif state==2 + if token.kind == 'cppWord' + let szAlias .= token.value + let state = 3 + else + let state = -1 + break + endif + elseif state == 3 + if token.value == '=' + let state = 4 + else + let state = -1 + break + endif + elseif state == 4 + if token.value == '::' + let szNamespace .= token.value + let state = 5 + elseif token.kind == 'cppWord' + let szNamespace .= token.value + let state = 6 + " Maybe end of tokens + endif + elseif state==5 + if token.kind == 'cppWord' + let szNamespace .= token.value + let state = 6 + " Maybe end of tokens + else + " Error, we can't have 'namespace ALIAS = Something::' + let state = -1 + break + endif + elseif state==6 + if token.value == '::' + let szNamespace .= token.value + let state = 5 + else + call extend(result, {szAlias : szNamespace}) + let state = 0 + endif + endif + endfor + + if state == 6 + call extend(result, {szAlias : szNamespace}) + endif + + return result +endfunc + +" Get the using namespace list from a line +function! s:GetNamespaceListFromLine(szLine) + let result = [] + let tokens = omni#cpp#tokenizer#Tokenize(a:szLine) + let szNamespace = '' + let state = 0 + for token in tokens + if state==0 + let szNamespace = '' + if token.value == '/*' + let state = 1 + elseif token.value == '//' + " It's a comment + let state = -1 + break + elseif token.value == 'using' + let state = 2 + endif + elseif state==1 + if token.value == '*/' + let state=0 + endif + elseif state==2 + if token.value == 'namespace' + let state = 3 + else + " Error, 'using' must be followed by 'namespace' + let state = -1 + break + endif + elseif state==3 + if token.value == '::' + let szNamespace .= token.value + let state = 4 + elseif token.kind == 'cppWord' + let szNamespace .= token.value + let state = 5 + " Maybe end of tokens + endif + elseif state==4 + if token.kind == 'cppWord' + let szNamespace .= token.value + let state = 5 + " Maybe end of tokens + else + " Error, we can't have 'using namespace Something::' + let state = -1 + break + endif + elseif state==5 + if token.value == '::' + let szNamespace .= token.value + let state = 4 + else + call extend(result, [szNamespace]) + let state = 0 + endif + endif + endfor + + if state == 5 + call extend(result, [szNamespace]) + endif + + return result +endfunc + +" Get the namespace list from a namespace map +function! s:GetUsingNamespaceListFromMap(namespaceMap, ...) + let stopLine = 0 + if a:0>0 + let stopLine = a:1 + endif + + let result = [] + let keys = sort(keys(a:namespaceMap), 'omni#common#utils#CompareNumber') + for i in keys + if stopLine != 0 && i > stopLine + break + endif + call extend(result, a:namespaceMap[i]) + endfor + return result +endfunc + +" Get global using namespace list from the current buffer +function! omni#cpp#namespaces#GetListFromCurrentBuffer(...) + let namespaceMap = s:GetAllUsingNamespaceMapFromCurrentBuffer() + let result = [] + if namespaceMap != {} + let result = s:GetUsingNamespaceListFromMap(namespaceMap, (a:0 > 0)? a:1 : line('.')) + endif + return result +endfunc + +" Get global using namespace map from the current buffer and include files recursively +function! s:GetAllUsingNamespaceMapFromCurrentBuffer(...) + let includeGuard = (a:0>0)? a:1 : {} + + let szBufferName = getreg("%") + let szFilePath = omni#cpp#utils#ResolveFilePath(szBufferName) + let szFilePath = (szFilePath=='')? szBufferName : szFilePath + + let namespaceMap = {} + if has_key(includeGuard, szFilePath) + return namespaceMap + else + let includeGuard[szFilePath] = 1 + endif + + let namespaceMap = omni#cpp#namespaces#GetMapFromCurrentBuffer() + + if g:OmniCpp_NamespaceSearch != 2 + " We don't search included files if OmniCpp_NamespaceSearch != 2 + return namespaceMap + endif + + for inc in omni#cpp#includes#GetList() + let lnum = inc.pos[0] + let tmpMap = s:GetAllUsingNamespaceMapFromFile(inc.include, includeGuard) + if tmpMap != {} + if has_key(namespaceMap, lnum) + call extend(namespaceMap[lnum], s:GetUsingNamespaceListFromMap(tmpMap)) + else + let namespaceMap[lnum] = s:GetUsingNamespaceListFromMap(tmpMap) + endif + endif + endfor + + return namespaceMap +endfunc + +" Get global using namespace map from a file and include files recursively +function! s:GetAllUsingNamespaceMapFromFile(szFilePath, ...) + let includeGuard = {} + if a:0 >0 + let includeGuard = a:1 + endif + + let szFilePath = omni#cpp#utils#ResolveFilePath(a:szFilePath) + let szFilePath = (szFilePath=='')? a:szFilePath : szFilePath + + let namespaceMap = {} + if has_key(includeGuard, szFilePath) + return namespaceMap + else + let includeGuard[szFilePath] = 1 + endif + + " If g:OmniCpp_NamespaceSearch == 1 (search namespaces only in the current + " buffer) we don't use cache for the current buffer + let namespaceMap = omni#cpp#namespaces#GetMapFromBuffer(szFilePath, g:OmniCpp_NamespaceSearch==1) + + if g:OmniCpp_NamespaceSearch != 2 + " We don't search included files if OmniCpp_NamespaceSearch != 2 + return namespaceMap + endif + + for inc in omni#cpp#includes#GetList(szFilePath) + let lnum = inc.pos[0] + let tmpMap = s:GetAllUsingNamespaceMapFromFile(inc.include, includeGuard) + if tmpMap != {} + if has_key(namespaceMap, lnum) + call extend(namespaceMap[lnum], s:GetUsingNamespaceListFromMap(tmpMap)) + else + let namespaceMap[lnum] = s:GetUsingNamespaceListFromMap(tmpMap) + endif + endif + endfor + + return namespaceMap +endfunc + +" Get global using namespace map from a the current buffer +function! omni#cpp#namespaces#GetMapFromCurrentBuffer() + let namespaceMap = {} + let originalPos = getpos('.') + + call setpos('.', [0, 1, 1, 0]) + let curPos = [1,1] + while curPos != [0,0] + let curPos = searchpos('\C^using\s\+namespace', 'W') + if curPos != [0,0] + let szLine = getline('.') + let startPos = curPos[1] + let endPos = match(szLine, ';', startPos-1) + if endPos!=-1 + " We get the namespace list from the line + let namespaceMap[curPos[0]] = s:GetNamespaceListFromLine(szLine) + endif + endif + endwhile + + call setpos('.', originalPos) + return namespaceMap +endfunc + +" Get global using namespace map from a file +function! omni#cpp#namespaces#GetMapFromBuffer(szFilePath, ...) + let bUpdate = 0 + if a:0 > 0 + let bUpdate = a:1 + endif + + let szFilePath = omni#cpp#utils#ResolveFilePath(a:szFilePath) + let szFilePath = (szFilePath=='')? a:szFilePath : szFilePath + + if !bUpdate && has_key(g:omni#cpp#namespaces#CacheUsing, szFilePath) + return copy(g:omni#cpp#namespaces#CacheUsing[szFilePath]) + endif + + let namespaceMap = {} + " The file exists, we get the global namespaces in this file + let szFixedPath = escape(szFilePath, g:omni#cpp#utils#szEscapedCharacters) + execute 'silent! lvimgrep /\C^using\s\+namespace/gj '.szFixedPath + + " key = line number + " value = list of namespaces + let listQuickFix = getloclist(0) + for qf in listQuickFix + let szLine = qf.text + let startPos = qf.col + let endPos = match(szLine, ';', startPos-1) + if endPos!=-1 + " We get the namespace list from the line + let namespaceMap[qf.lnum] = s:GetNamespaceListFromLine(szLine) + endif + endfor + + if szFixedPath != '' + let g:omni#cpp#namespaces#CacheUsing[szFixedPath] = namespaceMap + endif + + return copy(namespaceMap) +endfunc + +" Get the stop position when searching for local variables +function! s:GetStopPositionForLocalSearch() + " Stop position when searching a local variable + let originalPos = getpos('.') + let origPos = originalPos[1:2] + let stopPosition = origPos + let curPos = origPos + while curPos !=[0,0] + let stopPosition = curPos + let curPos = searchpairpos('{', '', '}', 'bW', g:omni#cpp#utils#expIgnoreComments) + endwhile + call setpos('.', originalPos) + + return stopPosition +endfunc + +" Get namespaces alias used at the cursor postion in a vim buffer +" Note: The result depends on the current cursor position +" @return +" - Map of namespace alias +function! s:GetNamespaceAliasMap() + " We store the cursor position because searchpairpos() moves the cursor + let result = {} + let originalPos = getpos('.') + let origPos = originalPos[1:2] + + let stopPos = s:GetStopPositionForLocalSearch() + let stopLine = stopPos[0] + let curPos = origPos + let lastLine = 0 + let nextStopLine = origPos[0] + let szReAlias = '\Cnamespace\s\+\w\+\s\+=' + while curPos !=[0,0] + let curPos = searchpos('}\|\('. szReAlias .'\)', 'bW',stopLine) + if curPos!=[0,0] && curPos[0]!=lastLine + let lastLine = curPos[0] + + let szLine = getline('.') + if origPos[0] == curPos[0] + " We get the line until cursor position + let szLine = szLine[:origPos[1]] + endif + + let szLine = omni#cpp#utils#GetCodeFromLine(szLine) + if match(szLine, szReAlias)<0 + " We found a '}' + let curPos = searchpairpos('{', '', '}', 'bW', g:omni#cpp#utils#expIgnoreComments) + else + " We get the namespace alias from the line + call extend(result, s:GetNamespaceAliasListFromLine(szLine)) + let nextStopLine = curPos[0] + endif + endif + endwhile + + " Setting the cursor to the original position + call setpos('.', originalPos) + + call s:ResolveAliasKeys(result) + return result +endfunc + +" Resolve an alias +" eg: namespace IAmAnAlias1 = Ns1 +" eg: namespace IAmAnAlias2 = IAmAnAlias1::Ns2 +" => IAmAnAlias2 = Ns1::Ns2 +function! s:ResolveAliasKey(mapNamespaceAlias, szAlias) + let szResult = a:mapNamespaceAlias[a:szAlias] + " ::Ns1::Ns2::Ns3 => ['Ns1', 'Ns2', 'Ns3'] + let listNamespace = split(szResult, '::') + if len(listNamespace) + " szBeginPart = 'Ns1' + let szBeginPart = remove(listNamespace, 0) + + " Is 'Ns1' an alias ? + if has_key(a:mapNamespaceAlias, szBeginPart) && szBeginPart != a:szAlias + " Resolving alias 'Ns1' + " eg: Ns1 = NsResolved + let szResult = s:ResolveAliasKey(a:mapNamespaceAlias, szBeginPart) + " szEndPart = 'Ns2::Ns3' + let szEndPart = join(listNamespace, '::') + if szEndPart != '' + " Concatenation => szResult = 'NsResolved::Ns2::Ns3' + let szResult .= '::' . szEndPart + endif + endif + endif + return szResult +endfunc + +" Resolve all keys in the namespace alias map +function! s:ResolveAliasKeys(mapNamespaceAlias) + let mapNamespaceAlias = a:mapNamespaceAlias + call map(mapNamespaceAlias, 's:ResolveAliasKey(mapNamespaceAlias, v:key)') +endfunc + +" Resolve namespace alias +function! omni#cpp#namespaces#ResolveAlias(mapNamespaceAlias, szNamespace) + let szResult = a:szNamespace + " ::Ns1::Ns2::Ns3 => ['Ns1', 'Ns2', 'Ns3'] + let listNamespace = split(a:szNamespace, '::') + if len(listNamespace) + " szBeginPart = 'Ns1' + let szBeginPart = remove(listNamespace, 0) + + " Is 'Ns1' an alias ? + if has_key(a:mapNamespaceAlias, szBeginPart) + " Resolving alias 'Ns1' + " eg: Ns1 = NsResolved + let szResult = a:mapNamespaceAlias[szBeginPart] + " szEndPart = 'Ns2::Ns3' + let szEndPart = join(listNamespace, '::') + if szEndPart != '' + " Concatenation => szResult = 'NsResolved::Ns2::Ns3' + let szResult .= '::' . szEndPart + endif + + " If a:szNamespace starts with '::' we add '::' to the beginning + " of the result + if match(a:szNamespace, '^::')>=0 + let szResult = omni#cpp#utils#SimplifyScope('::' . szResult) + endif + endif + endif + return szResult +endfunc + +" Resolve namespace alias +function! s:ResolveAliasInNamespaceList(mapNamespaceAlias, listNamespaces) + call map(a:listNamespaces, 'omni#cpp#namespaces#ResolveAlias(a:mapNamespaceAlias, v:val)') +endfunc + +" Get namespaces used at the cursor postion in a vim buffer +" Note: The result depends on the current cursor position +" @return +" - List of namespace used in the reverse order +function! omni#cpp#namespaces#GetUsingNamespaces() + " We have to get local using namespace declarations + " We need the current cursor position and the position of the start of the + " current scope + + " We store the cursor position because searchpairpos() moves the cursor + let result = [] + let originalPos = getpos('.') + let origPos = originalPos[1:2] + + let stopPos = s:GetStopPositionForLocalSearch() + + let stopLine = stopPos[0] + let curPos = origPos + let lastLine = 0 + let nextStopLine = origPos[0] + while curPos !=[0,0] + let curPos = searchpos('\C}\|\(using\s\+namespace\)', 'bW',stopLine) + if curPos!=[0,0] && curPos[0]!=lastLine + let lastLine = curPos[0] + + let szLine = getline('.') + if origPos[0] == curPos[0] + " We get the line until cursor position + let szLine = szLine[:origPos[1]] + endif + + let szLine = omni#cpp#utils#GetCodeFromLine(szLine) + if match(szLine, '\Cusing\s\+namespace')<0 + " We found a '}' + let curPos = searchpairpos('{', '', '}', 'bW', g:omni#cpp#utils#expIgnoreComments) + else + " We get the namespace list from the line + let result = s:GetNamespaceListFromLine(szLine) + result + let nextStopLine = curPos[0] + endif + endif + endwhile + + " Setting the cursor to the original position + call setpos('.', originalPos) + + " 2) Now we can get all global using namespace declaration from the + " beginning of the file to nextStopLine + let result = omni#cpp#namespaces#GetListFromCurrentBuffer(nextStopLine) + result + + " Resolving alias in the namespace list + " TODO: For the next release + "let g:omni#cpp#namespaces#CacheAlias= s:GetNamespaceAliasMap() + "call s:ResolveAliasInNamespaceList(g:omni#cpp#namespaces#CacheAlias, result) + + return ['::'] + result +endfunc + +" Resolve a using namespace regarding the current context +" For each namespace used: +" - We get all possible contexts where the namespace +" can be define +" - We do a comparison test of each parent contexts with the current +" context list +" - If one and only one parent context is present in the +" current context list we add the namespace in the current +" context +" - If there is more than one of parent contexts in the +" current context the namespace is ambiguous +" @return +" - result item +" - kind = 0|1 +" - 0 = unresolved or error +" - 1 = resolved +" - value = resolved namespace +function! s:ResolveNamespace(namespace, mapCurrentContexts) + let result = {'kind':0, 'value': ''} + + " If the namespace is already resolved we add it in the list of + " current contexts + if match(a:namespace, '^::')>=0 + let result.kind = 1 + let result.value = a:namespace + return result + elseif match(a:namespace, '\w\+::\w\+')>=0 + let mapCurrentContextsTmp = copy(a:mapCurrentContexts) + let resolvedItem = {} + for nsTmp in split(a:namespace, '::') + let resolvedItem = s:ResolveNamespace(nsTmp, mapCurrentContextsTmp) + if resolvedItem.kind + " Note: We don't extend the map + let mapCurrentContextsTmp = {resolvedItem.value : 1} + else + break + endif + endfor + if resolvedItem!={} && resolvedItem.kind + let result.kind = 1 + let result.value = resolvedItem.value + endif + return result + endif + + " We get all possible parent contexts of this namespace + let listTagsOfNamespace = [] + if has_key(g:omni#cpp#namespaces#CacheResolve, a:namespace) + let listTagsOfNamespace = g:omni#cpp#namespaces#CacheResolve[a:namespace] + else + let listTagsOfNamespace = omni#common#utils#TagList('^'.a:namespace.'$') + let g:omni#cpp#namespaces#CacheResolve[a:namespace] = listTagsOfNamespace + endif + + if len(listTagsOfNamespace)==0 + return result + endif + call filter(listTagsOfNamespace, 'v:val.kind[0]=="n"') + + " We extract parent context from tags + " We use a map to avoid multiple entries + let mapContext = {} + for tagItem in listTagsOfNamespace + let szParentContext = omni#cpp#utils#ExtractScope(tagItem) + let mapContext[szParentContext] = 1 + endfor + let listParentContext = keys(mapContext) + + " Now for each parent context we test if the context is in the current + " contexts list + let listResolvedNamespace = [] + for szParentContext in listParentContext + if has_key(a:mapCurrentContexts, szParentContext) + call extend(listResolvedNamespace, [omni#cpp#utils#SimplifyScope(szParentContext.'::'.a:namespace)]) + endif + endfor + + " Now we know if the namespace is ambiguous or not + let len = len(listResolvedNamespace) + if len==1 + " Namespace resolved + let result.kind = 1 + let result.value = listResolvedNamespace[0] + elseif len > 1 + " Ambiguous namespace, possible matches are in listResolvedNamespace + else + " Other cases + endif + return result +endfunc + +" Resolve namespaces +"@return +" - List of resolved namespaces +function! omni#cpp#namespaces#ResolveAll(namespacesUsed) + + " We add the default context '::' + let contextOrder = 0 + let mapCurrentContexts = {} + + " For each namespace used: + " - We get all possible contexts where the namespace + " can be define + " - We do a comparison test of each parent contexts with the current + " context list + " - If one and only one parent context is present in the + " current context list we add the namespace in the current + " context + " - If there is more than one of parent contexts in the + " current context the namespace is ambiguous + for ns in a:namespacesUsed + let resolvedItem = s:ResolveNamespace(ns, mapCurrentContexts) + if resolvedItem.kind + let contextOrder+=1 + let mapCurrentContexts[resolvedItem.value] = contextOrder + endif + endfor + + " Build the list of current contexts from the map, we have to keep the + " order + let mapReorder = {} + for key in keys(mapCurrentContexts) + let mapReorder[ mapCurrentContexts[key] ] = key + endfor + let result = [] + for key in sort(keys(mapReorder)) + call extend(result, [mapReorder[key]]) + endfor + return result +endfunc + +" Build the context stack +function! s:BuildContextStack(namespaces, szCurrentScope) + let result = copy(a:namespaces) + if a:szCurrentScope != '::' + let tagItem = omni#cpp#utils#GetResolvedTagItem(a:namespaces, omni#cpp#utils#CreateTypeInfo(a:szCurrentScope)) + if has_key(tagItem, 'inherits') + let listBaseClass = omni#cpp#utils#GetClassInheritanceList(a:namespaces, omni#cpp#utils#CreateTypeInfo(a:szCurrentScope)) + let result = listBaseClass + result + elseif has_key(tagItem, 'kind') && index(['c', 's', 'u', 'n'], tagItem.kind[0])>=0 + call insert(result, omni#cpp#utils#ExtractTypeInfoFromTag(tagItem)) + endif + endif + return result +endfunc + +" Returns the class scope at the current position of the cursor +" @return a string that represents the class scope +" eg: ::NameSpace1::Class1 +" The returned string always starts with '::' +" Note: In term of performance it's the weak point of the script +function! s:GetClassScopeAtCursor() + " We store the cursor position because searchpairpos() moves the cursor + let originalPos = getpos('.') + let endPos = originalPos[1:2] + let listCode = [] + let result = {'namespaces': [], 'scope': ''} + + while endPos!=[0,0] + let endPos = searchpairpos('{', '', '}', 'bW', g:omni#cpp#utils#expIgnoreComments) + let szReStartPos = '[;{}]\|\%^' + let startPos = searchpairpos(szReStartPos, '', '{', 'bWn', g:omni#cpp#utils#expIgnoreComments) + + " If the file starts with a comment so the startPos can be [0,0] + " we change it to [1,1] + if startPos==[0,0] + let startPos = [1,1] + endif + + " Get lines backward from cursor position to last ; or { or } + " or when we are at the beginning of the file. + " We store lines in listCode + if endPos!=[0,0] + " We remove the last character which is a '{' + " We also remove starting { or } or ; if exits + let szCodeWithoutComments = substitute(omni#cpp#utils#GetCode(startPos, endPos)[:-2], '^[;{}]', '', 'g') + call insert(listCode, {'startLine' : startPos[0], 'code' : szCodeWithoutComments}) + endif + endwhile + " Setting the cursor to the original position + call setpos('.', originalPos) + + let listClassScope = [] + let bResolved = 0 + let startLine = 0 + " Now we can check in the list of code if there is a function + for code in listCode + " We get the name of the namespace, class, struct or union + " and we store it in listClassScope + let tokens = omni#cpp#tokenizer#Tokenize(code.code) + let bContinue=0 + let bAddNamespace = 0 + let state=0 + for token in tokens + if state==0 + if index(['namespace', 'class', 'struct', 'union'], token.value)>=0 + if token.value == 'namespace' + let bAddNamespace = 1 + endif + let state= 1 + " Maybe end of tokens + endif + elseif state==1 + if token.kind == 'cppWord' + " eg: namespace MyNs { class MyCl {}; } + " => listClassScope = [MyNs, MyCl] + call extend( listClassScope , [token.value] ) + + " Add the namespace in result + if bAddNamespace + call extend(result.namespaces, [token.value]) + let bAddNamespace = 0 + endif + + let bContinue=1 + break + endif + endif + endfor + if bContinue==1 + continue + endif + + " Simple test to check if we have a chance to find a + " class method + let aPos = matchend(code.code, '::\s*\~*\s*\w\+\s*(') + if aPos ==-1 + continue + endif + + let startLine = code.startLine + let listTmp = [] + " eg: 'void MyNamespace::MyClass::foo(' + " => tokens = ['MyClass', '::', 'MyNamespace', 'void'] + let tokens = reverse(omni#cpp#tokenizer#Tokenize(code.code[:aPos-1])[:-4]) + let state = 0 + " Reading tokens backward + for token in tokens + if state==0 + if token.kind=='cppWord' + call insert(listTmp, token.value) + let state=1 + endif + elseif state==1 + if token.value=='::' + let state=2 + else + break + endif + elseif state==2 + if token.kind=='cppWord' + call insert(listTmp, token.value) + let state=1 + else + break + endif + endif + endfor + + if len(listTmp) + if len(listClassScope) + let bResolved = 1 + " Merging class scopes + " eg: current class scope = 'MyNs::MyCl1' + " method class scope = 'MyCl1::MyCl2' + " If we add the method class scope to current class scope + " we'll have MyNs::MyCl1::MyCl1::MyCl2 => it's wrong + " we want MyNs::MyCl1::MyCl2 + let index = 0 + for methodClassScope in listTmp + if methodClassScope==listClassScope[-1] + let listTmp = listTmp[index+1:] + break + else + let index+=1 + endif + endfor + endif + call extend(listClassScope, listTmp) + break + endif + endfor + + let szClassScope = '::' + if len(listClassScope) + if bResolved + let szClassScope .= join(listClassScope, '::') + else + let szClassScope = join(listClassScope, '::') + + " The class scope is not resolved, we have to check using + " namespace declarations and search the class scope in each + " namespace + if startLine != 0 + let namespaces = ['::'] + omni#cpp#namespaces#GetListFromCurrentBuffer(startLine) + let namespaces = omni#cpp#namespaces#ResolveAll(namespaces) + let tagItem = omni#cpp#utils#GetResolvedTagItem(namespaces, omni#cpp#utils#CreateTypeInfo(szClassScope)) + if tagItem != {} + let szClassScope = omni#cpp#utils#ExtractTypeInfoFromTag(tagItem) + endif + endif + endif + endif + + let result.scope = szClassScope + return result +endfunc + +" Get all contexts at the cursor position +function! omni#cpp#namespaces#GetContexts() + " Get the current class scope at the cursor, the result depends on the current cursor position + let scopeItem = s:GetClassScopeAtCursor() + let listUsingNamespace = copy(g:OmniCpp_DefaultNamespaces) + call extend(listUsingNamespace, scopeItem.namespaces) + if g:OmniCpp_NamespaceSearch && &filetype != 'c' + " Get namespaces used in the file until the cursor position + let listUsingNamespace = omni#cpp#namespaces#GetUsingNamespaces() + listUsingNamespace + " Resolving namespaces, removing ambiguous namespaces + let namespaces = omni#cpp#namespaces#ResolveAll(listUsingNamespace) + else + let namespaces = ['::'] + listUsingNamespace + endif + call reverse(namespaces) + + " Building context stack from namespaces and the current class scope + return s:BuildContextStack(namespaces, scopeItem.scope) +endfunc diff --git a/VimFiles/autoload/omni/cpp/settings.vim b/VimFiles/autoload/omni/cpp/settings.vim new file mode 100755 index 0000000..6683d3a --- /dev/null +++ b/VimFiles/autoload/omni/cpp/settings.vim @@ -0,0 +1,96 @@ +" Description: Omni completion script for cpp files +" Maintainer: Vissale NEANG +" Last Change: 26 sept. 2007 + +function! omni#cpp#settings#Init() + " Global scope search on/off + " 0 = disabled + " 1 = enabled + if !exists('g:OmniCpp_GlobalScopeSearch') + let g:OmniCpp_GlobalScopeSearch = 1 + endif + + " Sets the namespace search method + " 0 = disabled + " 1 = search namespaces in the current file + " 2 = search namespaces in the current file and included files + if !exists('g:OmniCpp_NamespaceSearch') + let g:OmniCpp_NamespaceSearch = 1 + endif + + " Set the class scope completion mode + " 0 = auto + " 1 = show all members (static, public, protected and private) + if !exists('g:OmniCpp_DisplayMode') + let g:OmniCpp_DisplayMode = 0 + endif + + " Set if the scope is displayed in the abbr column of the popup + " 0 = no + " 1 = yes + if !exists('g:OmniCpp_ShowScopeInAbbr') + let g:OmniCpp_ShowScopeInAbbr = 0 + endif + + " Set if the function prototype is displayed in the abbr column of the popup + " 0 = no + " 1 = yes + if !exists('g:OmniCpp_ShowPrototypeInAbbr') + let g:OmniCpp_ShowPrototypeInAbbr = 0 + endif + + " Set if the access (+,#,-) is displayed + " 0 = no + " 1 = yes + if !exists('g:OmniCpp_ShowAccess') + let g:OmniCpp_ShowAccess = 1 + endif + + " Set the list of default namespaces + " eg: ['std'] + if !exists('g:OmniCpp_DefaultNamespaces') + let g:OmniCpp_DefaultNamespaces = [] + endif + + " Set MayComplete to '.' + " 0 = disabled + " 1 = enabled + " default = 1 + if !exists('g:OmniCpp_MayCompleteDot') + let g:OmniCpp_MayCompleteDot = 1 + endif + + " Set MayComplete to '->' + " 0 = disabled + " 1 = enabled + " default = 1 + if !exists('g:OmniCpp_MayCompleteArrow') + let g:OmniCpp_MayCompleteArrow = 1 + endif + + " Set MayComplete to dot + " 0 = disabled + " 1 = enabled + " default = 0 + if !exists('g:OmniCpp_MayCompleteScope') + let g:OmniCpp_MayCompleteScope = 0 + endif + + " When completeopt does not contain longest option, this setting + " controls the behaviour of the popup menu selection when starting the completion + " 0 = don't select first item + " 1 = select first item (inserting it to the text) + " 2 = select first item (without inserting it to the text) + " default = 0 + if !exists('g:OmniCpp_SelectFirstItem') + let g:OmniCpp_SelectFirstItem= 0 + endif + + " Use local search function for variable definitions + " 0 = use standard vim search function + " 1 = use local search function + " default = 0 + if !exists('g:OmniCpp_LocalSearchDecl') + let g:OmniCpp_LocalSearchDecl= 0 + endif +endfunc diff --git a/VimFiles/autoload/omni/cpp/tokenizer.vim b/VimFiles/autoload/omni/cpp/tokenizer.vim new file mode 100755 index 0000000..16e0be2 --- /dev/null +++ b/VimFiles/autoload/omni/cpp/tokenizer.vim @@ -0,0 +1,93 @@ +" Description: Omni completion tokenizer +" Maintainer: Vissale NEANG +" Last Change: 26 sept. 2007 +" TODO: Generic behaviour for Tokenize() + +" From the C++ BNF +let s:cppKeyword = ['asm', 'auto', 'bool', 'break', 'case', 'catch', 'char', 'class', 'const', 'const_cast', 'continue', 'default', 'delete', 'do', 'double', 'dynamic_cast', 'else', 'enum', 'explicit', 'export', 'extern', 'false', 'float', 'for', 'friend', 'goto', 'if', 'inline', 'int', 'long', 'mutable', 'namespace', 'new', 'operator', 'private', 'protected', 'public', 'register', 'reinterpret_cast', 'return', 'short', 'signed', 'sizeof', 'static', 'static_cast', 'struct', 'switch', 'template', 'this', 'throw', 'true', 'try', 'typedef', 'typeid', 'typename', 'union', 'unsigned', 'using', 'virtual', 'void', 'volatile', 'wchar_t', 'while', 'and', 'and_eq', 'bitand', 'bitor', 'compl', 'not', 'not_eq', 'or', 'or_eq', 'xor', 'xor_eq'] + +let s:reCppKeyword = '\C\<'.join(s:cppKeyword, '\>\|\<').'\>' + +" The order of items in this list is very important because we use this list to build a regular +" expression (see below) for tokenization +let s:cppOperatorPunctuator = ['->*', '->', '--', '-=', '-', '!=', '!', '##', '#', '%:%:', '%=', '%>', '%:', '%', '&&', '&=', '&', '(', ')', '*=', '*', ',', '...', '.*', '.', '/=', '/', '::', ':>', ':', ';', '?', '[', ']', '^=', '^', '{', '||', '|=', '|', '}', '~', '++', '+=', '+', '<<=', '<%', '<:', '<<', '<=', '<', '==', '=', '>>=', '>>', '>=', '>'] + +" We build the regexp for the tokenizer +let s:reCComment = '\/\*\|\*\/' +let s:reCppComment = '\/\/' +let s:reComment = s:reCComment.'\|'.s:reCppComment +let s:reCppOperatorOrPunctuator = escape(join(s:cppOperatorPunctuator, '\|'), '*./^~[]') + + +" Tokenize a c++ code +" a token is dictionary where keys are: +" - kind = cppKeyword|cppWord|cppOperatorPunctuator|unknown|cComment|cppComment|cppDigit +" - value = 'something' +" Note: a cppWord is any word that is not a cpp keyword +function! omni#cpp#tokenizer#Tokenize(szCode) + let result = [] + + " The regexp to find a token, a token is a keyword, word or + " c++ operator or punctuator. To work properly we have to put + " spaces and tabs to our regexp. + let reTokenSearch = '\(\w\+\)\|\s\+\|'.s:reComment.'\|'.s:reCppOperatorOrPunctuator + " eg: 'using namespace std;' + " ^ ^ + " start=0 end=5 + let startPos = 0 + let endPos = matchend(a:szCode, reTokenSearch) + let len = endPos-startPos + while endPos!=-1 + " eg: 'using namespace std;' + " ^ ^ + " start=0 end=5 + " token = 'using' + " We also remove space and tabs + let token = substitute(strpart(a:szCode, startPos, len), '\s', '', 'g') + + " eg: 'using namespace std;' + " ^ ^ + " start=5 end=15 + let startPos = endPos + let endPos = matchend(a:szCode, reTokenSearch, startPos) + let len = endPos-startPos + + " It the token is empty we continue + if token=='' + continue + endif + + " Building the token + let resultToken = {'kind' : 'unknown', 'value' : token} + + " Classify the token + if token =~ '^\d\+' + " It's a digit + let resultToken.kind = 'cppDigit' + elseif token=~'^\w\+$' + " It's a word + let resultToken.kind = 'cppWord' + + " But maybe it's a c++ keyword + if match(token, s:reCppKeyword)>=0 + let resultToken.kind = 'cppKeyword' + endif + else + if match(token, s:reComment)>=0 + if index(['/*','*/'],token)>=0 + let resultToken.kind = 'cComment' + else + let resultToken.kind = 'cppComment' + endif + else + " It's an operator + let resultToken.kind = 'cppOperatorPunctuator' + endif + endif + + " We have our token, let's add it to the result list + call extend(result, [resultToken]) + endwhile + + return result +endfunc diff --git a/VimFiles/autoload/omni/cpp/utils.vim b/VimFiles/autoload/omni/cpp/utils.vim new file mode 100755 index 0000000..5d74d34 --- /dev/null +++ b/VimFiles/autoload/omni/cpp/utils.vim @@ -0,0 +1,587 @@ +" Description: Omni completion script for cpp files +" Maintainer: Vissale NEANG +" Last Change: 26 sept. 2007 + +let g:omni#cpp#utils#CACHE_TAG_INHERITS = {} +let g:omni#cpp#utils#szFilterGlobalScope = "(!has_key(v:val, 'class') && !has_key(v:val, 'struct') && !has_key(v:val, 'union') && !has_key(v:val, 'namespace')" +let g:omni#cpp#utils#szFilterGlobalScope .= "&& (!has_key(v:val, 'enum') || (has_key(v:val, 'enum') && v:val.enum =~ '^\\w\\+$')))" + +" Expression used to ignore comments +" Note: this expression drop drastically the performance +"let omni#cpp#utils#expIgnoreComments = 'match(synIDattr(synID(line("."), col("."), 1), "name"), '\CcComment')!=-1' +" This one is faster but not really good for C comments +let omni#cpp#utils#reIgnoreComment = escape('\/\/\|\/\*\|\*\/', '*/\') +let omni#cpp#utils#expIgnoreComments = 'getline(".") =~ g:omni#cpp#utils#reIgnoreComment' + +" Characters to escape in a filename for vimgrep +"TODO: Find more characters to escape +let omni#cpp#utils#szEscapedCharacters = ' %#' + +" Resolve the path of the file +" TODO: absolute file path +function! omni#cpp#utils#ResolveFilePath(szFile) + let result = '' + let listPath = split(globpath(&path, a:szFile), "\n") + if len(listPath) + let result = listPath[0] + endif + return simplify(result) +endfunc + +" Get code without comments and with empty strings +" szSingleLine must not have carriage return +function! omni#cpp#utils#GetCodeFromLine(szSingleLine) + " We set all strings to empty strings, it's safer for + " the next of the process + let szResult = substitute(a:szSingleLine, '".*"', '""', 'g') + + " Removing c++ comments, we can use the pattern ".*" because + " we are modifying a line + let szResult = substitute(szResult, '\/\/.*', '', 'g') + + " Now we have the entire code in one line and we can remove C comments + return s:RemoveCComments(szResult) +endfunc + +" Remove C comments on a line +function! s:RemoveCComments(szLine) + let result = a:szLine + + " We have to match the first '/*' and first '*/' + let startCmt = match(result, '\/\*') + let endCmt = match(result, '\*\/') + while startCmt!=-1 && endCmt!=-1 && startCmt0 + let result = result[ : startCmt-1 ] . result[ endCmt+2 : ] + else + " Case where '/*' is at the start of the line + let result = result[ endCmt+2 : ] + endif + let startCmt = match(result, '\/\*') + let endCmt = match(result, '\*\/') + endwhile + return result +endfunc + +" Get a c++ code from current buffer from [lineStart, colStart] to +" [lineEnd, colEnd] without c++ and c comments, without end of line +" and with empty strings if any +" @return a string +function! omni#cpp#utils#GetCode(posStart, posEnd) + let posStart = a:posStart + let posEnd = a:posEnd + if a:posStart[0]>a:posEnd[0] + let posStart = a:posEnd + let posEnd = a:posStart + elseif a:posStart[0]==a:posEnd[0] && a:posStart[1]>a:posEnd[1] + let posStart = a:posEnd + let posEnd = a:posStart + endif + + " Getting the lines + let lines = getline(posStart[0], posEnd[0]) + let lenLines = len(lines) + + " Formatting the result + let result = '' + if lenLines==1 + let sStart = posStart[1]-1 + let sEnd = posEnd[1]-1 + let line = lines[0] + let lenLastLine = strlen(line) + let sEnd = (sEnd>lenLastLine)?lenLastLine : sEnd + if sStart >= 0 + let result = omni#cpp#utils#GetCodeFromLine(line[ sStart : sEnd ]) + endif + elseif lenLines>1 + let sStart = posStart[1]-1 + let sEnd = posEnd[1]-1 + let lenLastLine = strlen(lines[-1]) + let sEnd = (sEnd>lenLastLine)?lenLastLine : sEnd + if sStart >= 0 + let lines[0] = lines[0][ sStart : ] + let lines[-1] = lines[-1][ : sEnd ] + for aLine in lines + let result = result . omni#cpp#utils#GetCodeFromLine(aLine)." " + endfor + let result = result[:-2] + endif + endif + + " Now we have the entire code in one line and we can remove C comments + return s:RemoveCComments(result) +endfunc + +" Extract the scope (context) of a tag item +" eg: ::MyNamespace +" @return a string of the scope. a scope from tag always starts with '::' +function! omni#cpp#utils#ExtractScope(tagItem) + let listKindScope = ['class', 'struct', 'union', 'namespace', 'enum'] + let szResult = '::' + for scope in listKindScope + if has_key(a:tagItem, scope) + let szResult = szResult . a:tagItem[scope] + break + endif + endfor + return szResult +endfunc + +" Simplify scope string, remove consecutive '::' if any +function! omni#cpp#utils#SimplifyScope(szScope) + let szResult = substitute(a:szScope, '\(::\)\+', '::', 'g') + if szResult=='::' + return szResult + else + return substitute(szResult, '::$', '', 'g') + endif +endfunc + +" Check if the cursor is in comment +function! omni#cpp#utils#IsCursorInCommentOrString() + return match(synIDattr(synID(line("."), col(".")-1, 1), "name"), '\C\=0 +endfunc + +" Tokenize the current instruction until the cursor position. +" @return list of tokens +function! omni#cpp#utils#TokenizeCurrentInstruction(...) + let szAppendText = '' + if a:0>0 + let szAppendText = a:1 + endif + + let startPos = searchpos('[;{}]\|\%^', 'bWn') + let curPos = getpos('.')[1:2] + " We don't want the character under the cursor + let column = curPos[1]-1 + let curPos[1] = (column<1)?1:column + return omni#cpp#tokenizer#Tokenize(omni#cpp#utils#GetCode(startPos, curPos)[1:] . szAppendText) +endfunc + +" Tokenize the current instruction until the word under the cursor. +" @return list of tokens +function! omni#cpp#utils#TokenizeCurrentInstructionUntilWord() + let startPos = searchpos('[;{}]\|\%^', 'bWn') + + " Saving the current cursor pos + let originalPos = getpos('.') + + " We go at the end of the word + execute 'normal gee' + let curPos = getpos('.')[1:2] + + " Restoring the original cursor pos + call setpos('.', originalPos) + + let szCode = omni#cpp#utils#GetCode(startPos, curPos)[1:] + return omni#cpp#tokenizer#Tokenize(szCode) +endfunc + +" Build parenthesis groups +" add a new key 'group' in the token +" where value is the group number of the parenthesis +" eg: (void*)(MyClass*) +" group1 group0 +" if a parenthesis is unresolved the group id is -1 +" @return a copy of a:tokens with parenthesis group +function! omni#cpp#utils#BuildParenthesisGroups(tokens) + let tokens = copy(a:tokens) + let kinds = {'(': '()', ')' : '()', '[' : '[]', ']' : '[]', '<' : '<>', '>' : '<>', '{': '{}', '}': '{}'} + let unresolved = {'()' : [], '[]': [], '<>' : [], '{}' : []} + let groupId = 0 + + " Note: we build paren group in a backward way + " because we can often have parenthesis unbalanced + " instruction + " eg: doSomething(_member.get()-> + for token in reverse(tokens) + if index([')', ']', '>', '}'], token.value)>=0 + let token['group'] = groupId + call extend(unresolved[kinds[token.value]], [token]) + let groupId+=1 + elseif index(['(', '[', '<', '{'], token.value)>=0 + if len(unresolved[kinds[token.value]]) + let tokenResolved = remove(unresolved[kinds[token.value]], -1) + let token['group'] = tokenResolved.group + else + let token['group'] = -1 + endif + endif + endfor + + return reverse(tokens) +endfunc + +" Determine if tokens represent a C cast +" @return +" - itemCast +" - itemCppCast +" - itemVariable +" - itemThis +function! omni#cpp#utils#GetCastType(tokens) + " Note: a:tokens is not modified + let tokens = omni#cpp#utils#SimplifyParenthesis(omni#cpp#utils#BuildParenthesisGroups(a:tokens)) + + if tokens[0].value == '(' + return 'itemCast' + elseif index(['static_cast', 'dynamic_cast', 'reinterpret_cast', 'const_cast'], tokens[0].value)>=0 + return 'itemCppCast' + else + for token in tokens + if token.value=='this' + return 'itemThis' + endif + endfor + return 'itemVariable' + endif +endfunc + +" Remove useless parenthesis +function! omni#cpp#utils#SimplifyParenthesis(tokens) + "Note: a:tokens is not modified + let tokens = a:tokens + " We remove useless parenthesis eg: (((MyClass))) + if len(tokens)>2 + while tokens[0].value=='(' && tokens[-1].value==')' && tokens[0].group==tokens[-1].group + let tokens = tokens[1:-2] + endwhile + endif + return tokens +endfunc + +" Function create a type info +function! omni#cpp#utils#CreateTypeInfo(param) + let type = type(a:param) + return {'type': type, 'value':a:param} +endfunc + +" Extract type info from a tag item +" eg: ::MyNamespace::MyClass +function! omni#cpp#utils#ExtractTypeInfoFromTag(tagItem) + let szTypeInfo = omni#cpp#utils#ExtractScope(a:tagItem) . '::' . substitute(a:tagItem.name, '.*::', '', 'g') + return omni#cpp#utils#SimplifyScope(szTypeInfo) +endfunc + +" Build a class inheritance list +function! omni#cpp#utils#GetClassInheritanceList(namespaces, typeInfo) + let result = [] + for tagItem in omni#cpp#utils#GetResolvedTags(a:namespaces, a:typeInfo) + call extend(result, [omni#cpp#utils#ExtractTypeInfoFromTag(tagItem)]) + endfor + return result +endfunc + +" Get class inheritance list where items in the list are tag items. +" TODO: Verify inheritance order +function! omni#cpp#utils#GetResolvedTags(namespaces, typeInfo) + let result = [] + let tagItem = omni#cpp#utils#GetResolvedTagItem(a:namespaces, a:typeInfo) + if tagItem!={} + let szTypeInfo = omni#cpp#utils#ExtractTypeInfoFromTag(tagItem) + if has_key(g:omni#cpp#utils#CACHE_TAG_INHERITS, szTypeInfo) + let result = g:omni#cpp#utils#CACHE_TAG_INHERITS[szTypeInfo] + else + call extend(result, [tagItem]) + if has_key(tagItem, 'inherits') + for baseClassTypeInfo in split(tagItem.inherits, ',') + let namespaces = [omni#cpp#utils#ExtractScope(tagItem), '::'] + call extend(result, omni#cpp#utils#GetResolvedTags(namespaces, omni#cpp#utils#CreateTypeInfo(baseClassTypeInfo))) + endfor + endif + let g:omni#cpp#utils#CACHE_TAG_INHERITS[szTypeInfo] = result + endif + endif + return result +endfunc + +" Get a tag item after a scope resolution and typedef resolution +function! omni#cpp#utils#GetResolvedTagItem(namespaces, typeInfo) + let typeInfo = {} + if type(a:typeInfo) == 1 + let typeInfo = omni#cpp#utils#CreateTypeInfo(a:typeInfo) + else + let typeInfo = a:typeInfo + endif + + let result = {} + if !omni#cpp#utils#IsTypeInfoValid(typeInfo) + return result + endif + + " Unnamed type case eg: '1::2' + if typeInfo.type == 4 + " Here there is no typedef or namespace to resolve, the tagInfo.value is a tag item + " representing a variable ('v') a member ('m') or a typedef ('t') and the typename is + " always in global scope + return typeInfo.value + endif + + " Named type case eg: 'MyNamespace::MyClass' + let szTypeInfo = omni#cpp#utils#GetTypeInfoString(typeInfo) + + " Resolving namespace alias + " TODO: For the next release + "let szTypeInfo = omni#cpp#namespaces#ResolveAlias(g:omni#cpp#namespaces#CacheAlias, szTypeInfo) + + if szTypeInfo=='::' + return result + endif + + " We can only get members of class, struct, union and namespace + let szTagFilter = "index(['c', 's', 'u', 'n', 't'], v:val.kind[0])>=0" + let szTagQuery = szTypeInfo + + if s:IsTypeInfoResolved(szTypeInfo) + " The type info is already resolved, we remove the starting '::' + let szTagQuery = substitute(szTypeInfo, '^::', '', 'g') + if len(split(szTagQuery, '::'))==1 + " eg: ::MyClass + " Here we have to get tags that have no parent scope + " That's why we change the szTagFilter + let szTagFilter .= '&& ' . g:omni#cpp#utils#szFilterGlobalScope + let tagList = omni#common#utils#TagListNoThrow('^'.szTagQuery.'$') + call filter(tagList, szTagFilter) + if len(tagList) + let result = tagList[0] + endif + else + " eg: ::MyNamespace::MyClass + let tagList = omni#common#utils#TagListNoThrow('^'.szTagQuery.'$') + call filter(tagList, szTagFilter) + + if len(tagList) + let result = tagList[0] + endif + endif + else + " The type is not resolved + let tagList = omni#common#utils#TagListNoThrow('^'.szTagQuery.'$') + call filter(tagList, szTagFilter) + + if len(tagList) + " Resolving scope (namespace, nested class etc...) + let szScopeOfTypeInfo = s:ExtractScopeFromTypeInfo(szTypeInfo) + if s:IsTypeInfoResolved(szTypeInfo) + let result = s:GetTagOfSameScope(tagList, szScopeOfTypeInfo) + else + " For each namespace of the namespace list we try to get a tag + " that can be in the same scope + if g:OmniCpp_NamespaceSearch && &filetype != 'c' + for scope in a:namespaces + let szTmpScope = omni#cpp#utils#SimplifyScope(scope.'::'.szScopeOfTypeInfo) + let result = s:GetTagOfSameScope(tagList, szTmpScope) + if result!={} + break + endif + endfor + else + let szTmpScope = omni#cpp#utils#SimplifyScope('::'.szScopeOfTypeInfo) + let result = s:GetTagOfSameScope(tagList, szTmpScope) + endif + endif + endif + endif + + if result!={} + " We have our tagItem but maybe it's a typedef or an unnamed type + if result.kind[0]=='t' + " Here we can have a typedef to another typedef, a class, struct, union etc + " but we can also have a typedef to an unnamed type, in that + " case the result contains a 'typeref' key + let namespaces = [omni#cpp#utils#ExtractScope(result), '::'] + if has_key(result, 'typeref') + let result = omni#cpp#utils#GetResolvedTagItem(namespaces, omni#cpp#utils#CreateTypeInfo(result)) + else + let szCmd = omni#cpp#utils#ExtractCmdFromTagItem(result) + let szCode = substitute(omni#cpp#utils#GetCodeFromLine(szCmd), '\C\<'.result.name.'\>.*', '', 'g') + let szTypeInfo = omni#cpp#utils#ExtractTypeInfoFromTokens(omni#cpp#tokenizer#Tokenize(szCode)) + let result = omni#cpp#utils#GetResolvedTagItem(namespaces, omni#cpp#utils#CreateTypeInfo(szTypeInfo)) + " TODO: Namespace resolution for result + endif + endif + endif + + return result +endfunc + +" Returns if the type info is valid +" @return +" - 1 if valid +" - 0 otherwise +function! omni#cpp#utils#IsTypeInfoValid(typeInfo) + if a:typeInfo=={} + return 0 + else + if a:typeInfo.type == 1 && a:typeInfo.value=='' + " String case + return 0 + elseif a:typeInfo.type == 4 && a:typeInfo.value=={} + " Dictionary case + return 0 + endif + endif + return 1 +endfunc + +" Get the string of the type info +function! omni#cpp#utils#GetTypeInfoString(typeInfo) + if a:typeInfo.type == 1 + return a:typeInfo.value + else + return substitute(a:typeInfo.value.typeref, '^\w\+:', '', 'g') + endif +endfunc + +" A resolved type info starts with '::' +" @return +" - 1 if type info starts with '::' +" - 0 otherwise +function! s:IsTypeInfoResolved(szTypeInfo) + return match(a:szTypeInfo, '^::')!=-1 +endfunc + +" A returned type info's scope may not have the global namespace '::' +" eg: '::NameSpace1::NameSpace2::MyClass' => '::NameSpace1::NameSpace2' +" 'NameSpace1::NameSpace2::MyClass' => 'NameSpace1::NameSpace2' +function! s:ExtractScopeFromTypeInfo(szTypeInfo) + let szScope = substitute(a:szTypeInfo, '\w\+$', '', 'g') + if szScope =='::' + return szScope + else + return substitute(szScope, '::$', '', 'g') + endif +endfunc + +" @return +" - the tag with the same scope +" - {} otherwise +function! s:GetTagOfSameScope(listTags, szScopeToMatch) + for tagItem in a:listTags + let szScopeOfTag = omni#cpp#utils#ExtractScope(tagItem) + if szScopeOfTag == a:szScopeToMatch + return tagItem + endif + endfor + return {} +endfunc + +" Extract the cmd of a tag item without regexp +function! omni#cpp#utils#ExtractCmdFromTagItem(tagItem) + let line = a:tagItem.cmd + let re = '\(\/\^\)\|\(\$\/\)' + if match(line, re)!=-1 + let line = substitute(line, re, '', 'g') + return line + else + " TODO: the cmd is a line number + return '' + endif +endfunc + +" Extract type from tokens. +" eg: examples of tokens format +" 'const MyClass&' +" 'const map < int, int >&' +" 'MyNs::MyClass' +" '::MyClass**' +" 'MyClass a, *b = NULL, c[1] = {}; +" 'hello(MyClass a, MyClass* b' +" @return the type info string eg: ::std::map +" can be empty +function! omni#cpp#utils#ExtractTypeInfoFromTokens(tokens) + let szResult = '' + let state = 0 + + let tokens = omni#cpp#utils#BuildParenthesisGroups(a:tokens) + + " If there is an unbalanced parenthesis we are in a parameter list + let bParameterList = 0 + for token in tokens + if token.value == '(' && token.group==-1 + let bParameterList = 1 + break + endif + endfor + + if bParameterList + let tokens = reverse(tokens) + let state = 0 + let parenGroup = -1 + for token in tokens + if state==0 + if token.value=='>' + let parenGroup = token.group + let state=1 + elseif token.kind == 'cppWord' + let szResult = token.value.szResult + let state=2 + elseif index(['*', '&'], token.value)<0 + break + endif + elseif state==1 + if token.value=='<' && token.group==parenGroup + let state=0 + endif + elseif state==2 + if token.value=='::' + let szResult = token.value.szResult + let state=3 + else + break + endif + elseif state==3 + if token.kind == 'cppWord' + let szResult = token.value.szResult + let state=2 + else + break + endif + endif + endfor + return szResult + endif + + for token in tokens + if state==0 + if token.value == '::' + let szResult .= token.value + let state = 1 + elseif token.kind == 'cppWord' + let szResult .= token.value + let state = 2 + " Maybe end of token + endif + elseif state==1 + if token.kind == 'cppWord' + let szResult .= token.value + let state = 2 + " Maybe end of token + else + break + endif + elseif state==2 + if token.value == '::' + let szResult .= token.value + let state = 1 + else + break + endif + endif + endfor + return szResult +endfunc + +" Get the preview window string +function! omni#cpp#utils#GetPreviewWindowStringFromTagItem(tagItem) + let szResult = '' + + let szResult .= 'name: '.a:tagItem.name."\n" + for tagKey in keys(a:tagItem) + if index(['name', 'static'], tagKey)>=0 + continue + endif + let szResult .= tagKey.': '.a:tagItem[tagKey]."\n" + endfor + + return substitute(szResult, "\n$", '', 'g') +endfunc diff --git a/VimFiles/autoload/plug.vim b/VimFiles/autoload/plug.vim new file mode 100644 index 0000000..9ebcf53 --- /dev/null +++ b/VimFiles/autoload/plug.vim @@ -0,0 +1,2504 @@ +" vim-plug: Vim plugin manager +" ============================ +" +" Download plug.vim and put it in ~/.vim/autoload +" +" curl -fLo ~/.vim/autoload/plug.vim --create-dirs \ +" https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim +" +" Edit your .vimrc +" +" call plug#begin('~/.vim/plugged') +" +" " Make sure you use single quotes +" +" " Shorthand notation; fetches https://github.com/junegunn/vim-easy-align +" Plug 'junegunn/vim-easy-align' +" +" " Any valid git URL is allowed +" Plug 'https://github.com/junegunn/vim-github-dashboard.git' +" +" " Multiple Plug commands can be written in a single line using | separators +" Plug 'SirVer/ultisnips' | Plug 'honza/vim-snippets' +" +" " On-demand loading +" Plug 'scrooloose/nerdtree', { 'on': 'NERDTreeToggle' } +" Plug 'tpope/vim-fireplace', { 'for': 'clojure' } +" +" " Using a non-master branch +" Plug 'rdnetto/YCM-Generator', { 'branch': 'stable' } +" +" " Using a tagged release; wildcard allowed (requires git 1.9.2 or above) +" Plug 'fatih/vim-go', { 'tag': '*' } +" +" " Plugin options +" Plug 'nsf/gocode', { 'tag': 'v.20150303', 'rtp': 'vim' } +" +" " Plugin outside ~/.vim/plugged with post-update hook +" Plug 'junegunn/fzf', { 'dir': '~/.fzf', 'do': './install --all' } +" +" " Unmanaged plugin (manually installed and updated) +" Plug '~/my-prototype-plugin' +" +" " Initialize plugin system +" call plug#end() +" +" Then reload .vimrc and :PlugInstall to install plugins. +" +" Plug options: +" +"| Option | Description | +"| ----------------------- | ------------------------------------------------ | +"| `branch`/`tag`/`commit` | Branch/tag/commit of the repository to use | +"| `rtp` | Subdirectory that contains Vim plugin | +"| `dir` | Custom directory for the plugin | +"| `as` | Use different name for the plugin | +"| `do` | Post-update hook (string or funcref) | +"| `on` | On-demand loading: Commands or ``-mappings | +"| `for` | On-demand loading: File types | +"| `frozen` | Do not update unless explicitly specified | +" +" More information: https://github.com/junegunn/vim-plug +" +" +" Copyright (c) 2017 Junegunn Choi +" +" MIT License +" +" Permission is hereby granted, free of charge, to any person obtaining +" a copy of this software and associated documentation files (the +" "Software"), to deal in the Software without restriction, including +" without limitation the rights to use, copy, modify, merge, publish, +" distribute, sublicense, and/or sell copies of the Software, and to +" permit persons to whom the Software is furnished to do so, subject to +" the following conditions: +" +" The above copyright notice and this permission notice shall be +" included in all copies or substantial portions of the Software. +" +" THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +" EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +" MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +" NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +" LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +" OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +" WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +if exists('g:loaded_plug') + finish +endif +let g:loaded_plug = 1 + +let s:cpo_save = &cpo +set cpo&vim + +let s:plug_src = 'https://github.com/junegunn/vim-plug.git' +let s:plug_tab = get(s:, 'plug_tab', -1) +let s:plug_buf = get(s:, 'plug_buf', -1) +let s:mac_gui = has('gui_macvim') && has('gui_running') +let s:is_win = has('win32') || has('win64') +let s:nvim = has('nvim-0.2') || (has('nvim') && exists('*jobwait') && !s:is_win) +let s:vim8 = has('patch-8.0.0039') && exists('*job_start') +let s:me = resolve(expand(':p')) +let s:base_spec = { 'branch': 'master', 'frozen': 0 } +let s:TYPE = { +\ 'string': type(''), +\ 'list': type([]), +\ 'dict': type({}), +\ 'funcref': type(function('call')) +\ } +let s:loaded = get(s:, 'loaded', {}) +let s:triggers = get(s:, 'triggers', {}) + +function! plug#begin(...) + if a:0 > 0 + let s:plug_home_org = a:1 + let home = s:path(fnamemodify(expand(a:1), ':p')) + elseif exists('g:plug_home') + let home = s:path(g:plug_home) + elseif !empty(&rtp) + let home = s:path(split(&rtp, ',')[0]) . '/plugged' + else + return s:err('Unable to determine plug home. Try calling plug#begin() with a path argument.') + endif + if fnamemodify(home, ':t') ==# 'plugin' && fnamemodify(home, ':h') ==# s:first_rtp + return s:err('Invalid plug home. '.home.' is a standard Vim runtime path and is not allowed.') + endif + + let g:plug_home = home + let g:plugs = {} + let g:plugs_order = [] + let s:triggers = {} + + call s:define_commands() + return 1 +endfunction + +function! s:define_commands() + command! -nargs=+ -bar Plug call plug#() + if !executable('git') + return s:err('`git` executable not found. Most commands will not be available. To suppress this message, prepend `silent!` to `call plug#begin(...)`.') + endif + command! -nargs=* -bar -bang -complete=customlist,s:names PlugInstall call s:install(0, []) + command! -nargs=* -bar -bang -complete=customlist,s:names PlugUpdate call s:update(0, []) + command! -nargs=0 -bar -bang PlugClean call s:clean(0) + command! -nargs=0 -bar PlugUpgrade if s:upgrade() | execute 'source' s:esc(s:me) | endif + command! -nargs=0 -bar PlugStatus call s:status() + command! -nargs=0 -bar PlugDiff call s:diff() + command! -nargs=? -bar -bang -complete=file PlugSnapshot call s:snapshot(0, ) +endfunction + +function! s:to_a(v) + return type(a:v) == s:TYPE.list ? a:v : [a:v] +endfunction + +function! s:to_s(v) + return type(a:v) == s:TYPE.string ? a:v : join(a:v, "\n") . "\n" +endfunction + +function! s:glob(from, pattern) + return s:lines(globpath(a:from, a:pattern)) +endfunction + +function! s:source(from, ...) + let found = 0 + for pattern in a:000 + for vim in s:glob(a:from, pattern) + execute 'source' s:esc(vim) + let found = 1 + endfor + endfor + return found +endfunction + +function! s:assoc(dict, key, val) + let a:dict[a:key] = add(get(a:dict, a:key, []), a:val) +endfunction + +function! s:ask(message, ...) + call inputsave() + echohl WarningMsg + let answer = input(a:message.(a:0 ? ' (y/N/a) ' : ' (y/N) ')) + echohl None + call inputrestore() + echo "\r" + return (a:0 && answer =~? '^a') ? 2 : (answer =~? '^y') ? 1 : 0 +endfunction + +function! s:ask_no_interrupt(...) + try + return call('s:ask', a:000) + catch + return 0 + endtry +endfunction + +function! plug#end() + if !exists('g:plugs') + return s:err('Call plug#begin() first') + endif + + if exists('#PlugLOD') + augroup PlugLOD + autocmd! + augroup END + augroup! PlugLOD + endif + let lod = { 'ft': {}, 'map': {}, 'cmd': {} } + + if exists('g:did_load_filetypes') + filetype off + endif + for name in g:plugs_order + if !has_key(g:plugs, name) + continue + endif + let plug = g:plugs[name] + if get(s:loaded, name, 0) || !has_key(plug, 'on') && !has_key(plug, 'for') + let s:loaded[name] = 1 + continue + endif + + if has_key(plug, 'on') + let s:triggers[name] = { 'map': [], 'cmd': [] } + for cmd in s:to_a(plug.on) + if cmd =~? '^.\+' + if empty(mapcheck(cmd)) && empty(mapcheck(cmd, 'i')) + call s:assoc(lod.map, cmd, name) + endif + call add(s:triggers[name].map, cmd) + elseif cmd =~# '^[A-Z]' + let cmd = substitute(cmd, '!*$', '', '') + if exists(':'.cmd) != 2 + call s:assoc(lod.cmd, cmd, name) + endif + call add(s:triggers[name].cmd, cmd) + else + call s:err('Invalid `on` option: '.cmd. + \ '. Should start with an uppercase letter or ``.') + endif + endfor + endif + + if has_key(plug, 'for') + let types = s:to_a(plug.for) + if !empty(types) + augroup filetypedetect + call s:source(s:rtp(plug), 'ftdetect/**/*.vim', 'after/ftdetect/**/*.vim') + augroup END + endif + for type in types + call s:assoc(lod.ft, type, name) + endfor + endif + endfor + + for [cmd, names] in items(lod.cmd) + execute printf( + \ 'command! -nargs=* -range -bang -complete=file %s call s:lod_cmd(%s, "", , , , %s)', + \ cmd, string(cmd), string(names)) + endfor + + for [map, names] in items(lod.map) + for [mode, map_prefix, key_prefix] in + \ [['i', '', ''], ['n', '', ''], ['v', '', 'gv'], ['o', '', '']] + execute printf( + \ '%snoremap %s %s:call lod_map(%s, %s, %s, "%s")', + \ mode, map, map_prefix, string(map), string(names), mode != 'i', key_prefix) + endfor + endfor + + for [ft, names] in items(lod.ft) + augroup PlugLOD + execute printf('autocmd FileType %s call lod_ft(%s, %s)', + \ ft, string(ft), string(names)) + augroup END + endfor + + call s:reorg_rtp() + filetype plugin indent on + if has('vim_starting') + if has('syntax') && !exists('g:syntax_on') + syntax enable + end + else + call s:reload_plugins() + endif +endfunction + +function! s:loaded_names() + return filter(copy(g:plugs_order), 'get(s:loaded, v:val, 0)') +endfunction + +function! s:load_plugin(spec) + call s:source(s:rtp(a:spec), 'plugin/**/*.vim', 'after/plugin/**/*.vim') +endfunction + +function! s:reload_plugins() + for name in s:loaded_names() + call s:load_plugin(g:plugs[name]) + endfor +endfunction + +function! s:trim(str) + return substitute(a:str, '[\/]\+$', '', '') +endfunction + +function! s:version_requirement(val, min) + for idx in range(0, len(a:min) - 1) + let v = get(a:val, idx, 0) + if v < a:min[idx] | return 0 + elseif v > a:min[idx] | return 1 + endif + endfor + return 1 +endfunction + +function! s:git_version_requirement(...) + if !exists('s:git_version') + let s:git_version = map(split(split(s:system('git --version'))[2], '\.'), 'str2nr(v:val)') + endif + return s:version_requirement(s:git_version, a:000) +endfunction + +function! s:progress_opt(base) + return a:base && !s:is_win && + \ s:git_version_requirement(1, 7, 1) ? '--progress' : '' +endfunction + +if s:is_win + function! s:rtp(spec) + return s:path(a:spec.dir . get(a:spec, 'rtp', '')) + endfunction + + function! s:path(path) + return s:trim(substitute(a:path, '/', '\', 'g')) + endfunction + + function! s:dirpath(path) + return s:path(a:path) . '\' + endfunction + + function! s:is_local_plug(repo) + return a:repo =~? '^[a-z]:\|^[%~]' + endfunction +else + function! s:rtp(spec) + return s:dirpath(a:spec.dir . get(a:spec, 'rtp', '')) + endfunction + + function! s:path(path) + return s:trim(a:path) + endfunction + + function! s:dirpath(path) + return substitute(a:path, '[/\\]*$', '/', '') + endfunction + + function! s:is_local_plug(repo) + return a:repo[0] =~ '[/$~]' + endfunction +endif + +function! s:err(msg) + echohl ErrorMsg + echom '[vim-plug] '.a:msg + echohl None +endfunction + +function! s:warn(cmd, msg) + echohl WarningMsg + execute a:cmd 'a:msg' + echohl None +endfunction + +function! s:esc(path) + return escape(a:path, ' ') +endfunction + +function! s:escrtp(path) + return escape(a:path, ' ,') +endfunction + +function! s:remove_rtp() + for name in s:loaded_names() + let rtp = s:rtp(g:plugs[name]) + execute 'set rtp-='.s:escrtp(rtp) + let after = globpath(rtp, 'after') + if isdirectory(after) + execute 'set rtp-='.s:escrtp(after) + endif + endfor +endfunction + +function! s:reorg_rtp() + if !empty(s:first_rtp) + execute 'set rtp-='.s:first_rtp + execute 'set rtp-='.s:last_rtp + endif + + " &rtp is modified from outside + if exists('s:prtp') && s:prtp !=# &rtp + call s:remove_rtp() + unlet! s:middle + endif + + let s:middle = get(s:, 'middle', &rtp) + let rtps = map(s:loaded_names(), 's:rtp(g:plugs[v:val])') + let afters = filter(map(copy(rtps), 'globpath(v:val, "after")'), '!empty(v:val)') + let rtp = join(map(rtps, 'escape(v:val, ",")'), ',') + \ . ','.s:middle.',' + \ . join(map(afters, 'escape(v:val, ",")'), ',') + let &rtp = substitute(substitute(rtp, ',,*', ',', 'g'), '^,\|,$', '', 'g') + let s:prtp = &rtp + + if !empty(s:first_rtp) + execute 'set rtp^='.s:first_rtp + execute 'set rtp+='.s:last_rtp + endif +endfunction + +function! s:doautocmd(...) + if exists('#'.join(a:000, '#')) + execute 'doautocmd' ((v:version > 703 || has('patch442')) ? '' : '') join(a:000) + endif +endfunction + +function! s:dobufread(names) + for name in a:names + let path = s:rtp(g:plugs[name]).'/**' + for dir in ['ftdetect', 'ftplugin'] + if len(finddir(dir, path)) + if exists('#BufRead') + doautocmd BufRead + endif + return + endif + endfor + endfor +endfunction + +function! plug#load(...) + if a:0 == 0 + return s:err('Argument missing: plugin name(s) required') + endif + if !exists('g:plugs') + return s:err('plug#begin was not called') + endif + let names = a:0 == 1 && type(a:1) == s:TYPE.list ? a:1 : a:000 + let unknowns = filter(copy(names), '!has_key(g:plugs, v:val)') + if !empty(unknowns) + let s = len(unknowns) > 1 ? 's' : '' + return s:err(printf('Unknown plugin%s: %s', s, join(unknowns, ', '))) + end + let unloaded = filter(copy(names), '!get(s:loaded, v:val, 0)') + if !empty(unloaded) + for name in unloaded + call s:lod([name], ['ftdetect', 'after/ftdetect', 'plugin', 'after/plugin']) + endfor + call s:dobufread(unloaded) + return 1 + end + return 0 +endfunction + +function! s:remove_triggers(name) + if !has_key(s:triggers, a:name) + return + endif + for cmd in s:triggers[a:name].cmd + execute 'silent! delc' cmd + endfor + for map in s:triggers[a:name].map + execute 'silent! unmap' map + execute 'silent! iunmap' map + endfor + call remove(s:triggers, a:name) +endfunction + +function! s:lod(names, types, ...) + for name in a:names + call s:remove_triggers(name) + let s:loaded[name] = 1 + endfor + call s:reorg_rtp() + + for name in a:names + let rtp = s:rtp(g:plugs[name]) + for dir in a:types + call s:source(rtp, dir.'/**/*.vim') + endfor + if a:0 + if !s:source(rtp, a:1) && !empty(s:glob(rtp, a:2)) + execute 'runtime' a:1 + endif + call s:source(rtp, a:2) + endif + call s:doautocmd('User', name) + endfor +endfunction + +function! s:lod_ft(pat, names) + let syn = 'syntax/'.a:pat.'.vim' + call s:lod(a:names, ['plugin', 'after/plugin'], syn, 'after/'.syn) + execute 'autocmd! PlugLOD FileType' a:pat + call s:doautocmd('filetypeplugin', 'FileType') + call s:doautocmd('filetypeindent', 'FileType') +endfunction + +function! s:lod_cmd(cmd, bang, l1, l2, args, names) + call s:lod(a:names, ['ftdetect', 'after/ftdetect', 'plugin', 'after/plugin']) + call s:dobufread(a:names) + execute printf('%s%s%s %s', (a:l1 == a:l2 ? '' : (a:l1.','.a:l2)), a:cmd, a:bang, a:args) +endfunction + +function! s:lod_map(map, names, with_prefix, prefix) + call s:lod(a:names, ['ftdetect', 'after/ftdetect', 'plugin', 'after/plugin']) + call s:dobufread(a:names) + let extra = '' + while 1 + let c = getchar(0) + if c == 0 + break + endif + let extra .= nr2char(c) + endwhile + + if a:with_prefix + let prefix = v:count ? v:count : '' + let prefix .= '"'.v:register.a:prefix + if mode(1) == 'no' + if v:operator == 'c' + let prefix = "\" . prefix + endif + let prefix .= v:operator + endif + call feedkeys(prefix, 'n') + endif + call feedkeys(substitute(a:map, '^', "\", '') . extra) +endfunction + +function! plug#(repo, ...) + if a:0 > 1 + return s:err('Invalid number of arguments (1..2)') + endif + + try + let repo = s:trim(a:repo) + let opts = a:0 == 1 ? s:parse_options(a:1) : s:base_spec + let name = get(opts, 'as', fnamemodify(repo, ':t:s?\.git$??')) + let spec = extend(s:infer_properties(name, repo), opts) + if !has_key(g:plugs, name) + call add(g:plugs_order, name) + endif + let g:plugs[name] = spec + let s:loaded[name] = get(s:loaded, name, 0) + catch + return s:err(v:exception) + endtry +endfunction + +function! s:parse_options(arg) + let opts = copy(s:base_spec) + let type = type(a:arg) + if type == s:TYPE.string + let opts.tag = a:arg + elseif type == s:TYPE.dict + call extend(opts, a:arg) + if has_key(opts, 'dir') + let opts.dir = s:dirpath(expand(opts.dir)) + endif + else + throw 'Invalid argument type (expected: string or dictionary)' + endif + return opts +endfunction + +function! s:infer_properties(name, repo) + let repo = a:repo + if s:is_local_plug(repo) + return { 'dir': s:dirpath(expand(repo)) } + else + if repo =~ ':' + let uri = repo + else + if repo !~ '/' + throw printf('Invalid argument: %s (implicit `vim-scripts'' expansion is deprecated)', repo) + endif + let fmt = get(g:, 'plug_url_format', 'https://git::@github.com/%s.git') + let uri = printf(fmt, repo) + endif + return { 'dir': s:dirpath(g:plug_home.'/'.a:name), 'uri': uri } + endif +endfunction + +function! s:install(force, names) + call s:update_impl(0, a:force, a:names) +endfunction + +function! s:update(force, names) + call s:update_impl(1, a:force, a:names) +endfunction + +function! plug#helptags() + if !exists('g:plugs') + return s:err('plug#begin was not called') + endif + for spec in values(g:plugs) + let docd = join([s:rtp(spec), 'doc'], '/') + if isdirectory(docd) + silent! execute 'helptags' s:esc(docd) + endif + endfor + return 1 +endfunction + +function! s:syntax() + syntax clear + syntax region plug1 start=/\%1l/ end=/\%2l/ contains=plugNumber + syntax region plug2 start=/\%2l/ end=/\%3l/ contains=plugBracket,plugX + syn match plugNumber /[0-9]\+[0-9.]*/ contained + syn match plugBracket /[[\]]/ contained + syn match plugX /x/ contained + syn match plugDash /^-/ + syn match plugPlus /^+/ + syn match plugStar /^*/ + syn match plugMessage /\(^- \)\@<=.*/ + syn match plugName /\(^- \)\@<=[^ ]*:/ + syn match plugSha /\%(: \)\@<=[0-9a-f]\{4,}$/ + syn match plugTag /(tag: [^)]\+)/ + syn match plugInstall /\(^+ \)\@<=[^:]*/ + syn match plugUpdate /\(^* \)\@<=[^:]*/ + syn match plugCommit /^ \X*[0-9a-f]\{7,9} .*/ contains=plugRelDate,plugEdge,plugTag + syn match plugEdge /^ \X\+$/ + syn match plugEdge /^ \X*/ contained nextgroup=plugSha + syn match plugSha /[0-9a-f]\{7,9}/ contained + syn match plugRelDate /([^)]*)$/ contained + syn match plugNotLoaded /(not loaded)$/ + syn match plugError /^x.*/ + syn region plugDeleted start=/^\~ .*/ end=/^\ze\S/ + syn match plugH2 /^.*:\n-\+$/ + syn keyword Function PlugInstall PlugStatus PlugUpdate PlugClean + hi def link plug1 Title + hi def link plug2 Repeat + hi def link plugH2 Type + hi def link plugX Exception + hi def link plugBracket Structure + hi def link plugNumber Number + + hi def link plugDash Special + hi def link plugPlus Constant + hi def link plugStar Boolean + + hi def link plugMessage Function + hi def link plugName Label + hi def link plugInstall Function + hi def link plugUpdate Type + + hi def link plugError Error + hi def link plugDeleted Ignore + hi def link plugRelDate Comment + hi def link plugEdge PreProc + hi def link plugSha Identifier + hi def link plugTag Constant + + hi def link plugNotLoaded Comment +endfunction + +function! s:lpad(str, len) + return a:str . repeat(' ', a:len - len(a:str)) +endfunction + +function! s:lines(msg) + return split(a:msg, "[\r\n]") +endfunction + +function! s:lastline(msg) + return get(s:lines(a:msg), -1, '') +endfunction + +function! s:new_window() + execute get(g:, 'plug_window', 'vertical topleft new') +endfunction + +function! s:plug_window_exists() + let buflist = tabpagebuflist(s:plug_tab) + return !empty(buflist) && index(buflist, s:plug_buf) >= 0 +endfunction + +function! s:switch_in() + if !s:plug_window_exists() + return 0 + endif + + if winbufnr(0) != s:plug_buf + let s:pos = [tabpagenr(), winnr(), winsaveview()] + execute 'normal!' s:plug_tab.'gt' + let winnr = bufwinnr(s:plug_buf) + execute winnr.'wincmd w' + call add(s:pos, winsaveview()) + else + let s:pos = [winsaveview()] + endif + + setlocal modifiable + return 1 +endfunction + +function! s:switch_out(...) + call winrestview(s:pos[-1]) + setlocal nomodifiable + if a:0 > 0 + execute a:1 + endif + + if len(s:pos) > 1 + execute 'normal!' s:pos[0].'gt' + execute s:pos[1] 'wincmd w' + call winrestview(s:pos[2]) + endif +endfunction + +function! s:finish_bindings() + nnoremap R :call retry() + nnoremap D :PlugDiff + nnoremap S :PlugStatus + nnoremap U :call status_update() + xnoremap U :call status_update() + nnoremap ]] :silent! call section('') + nnoremap [[ :silent! call section('b') +endfunction + +function! s:prepare(...) + if empty(getcwd()) + throw 'Invalid current working directory. Cannot proceed.' + endif + + for evar in ['$GIT_DIR', '$GIT_WORK_TREE'] + if exists(evar) + throw evar.' detected. Cannot proceed.' + endif + endfor + + call s:job_abort() + if s:switch_in() + if b:plug_preview == 1 + pc + endif + enew + else + call s:new_window() + endif + + nnoremap q :if b:plug_preview==1pcendifbd + if a:0 == 0 + call s:finish_bindings() + endif + let b:plug_preview = -1 + let s:plug_tab = tabpagenr() + let s:plug_buf = winbufnr(0) + call s:assign_name() + + for k in ['', 'L', 'o', 'X', 'd', 'dd'] + execute 'silent! unmap ' k + endfor + setlocal buftype=nofile bufhidden=wipe nobuflisted nolist noswapfile nowrap cursorline modifiable nospell + setf vim-plug + if exists('g:syntax_on') + call s:syntax() + endif +endfunction + +function! s:assign_name() + " Assign buffer name + let prefix = '[Plugins]' + let name = prefix + let idx = 2 + while bufexists(name) + let name = printf('%s (%s)', prefix, idx) + let idx = idx + 1 + endwhile + silent! execute 'f' fnameescape(name) +endfunction + +function! s:chsh(swap) + let prev = [&shell, &shellcmdflag, &shellredir] + if s:is_win + set shell=cmd.exe shellcmdflag=/c shellredir=>%s\ 2>&1 + elseif a:swap + set shell=sh shellredir=>%s\ 2>&1 + endif + return prev +endfunction + +function! s:bang(cmd, ...) + try + let [sh, shellcmdflag, shrd] = s:chsh(a:0) + " FIXME: Escaping is incomplete. We could use shellescape with eval, + " but it won't work on Windows. + let cmd = a:0 ? s:with_cd(a:cmd, a:1) : a:cmd + if s:is_win + let batchfile = tempname().'.bat' + call writefile(['@echo off', cmd], batchfile) + let cmd = batchfile + endif + let g:_plug_bang = (s:is_win && has('gui_running') ? 'silent ' : '').'!'.escape(cmd, '#!%') + execute "normal! :execute g:_plug_bang\\" + finally + unlet g:_plug_bang + let [&shell, &shellcmdflag, &shellredir] = [sh, shellcmdflag, shrd] + if s:is_win + call delete(batchfile) + endif + endtry + return v:shell_error ? 'Exit status: ' . v:shell_error : '' +endfunction + +function! s:regress_bar() + let bar = substitute(getline(2)[1:-2], '.*\zs=', 'x', '') + call s:progress_bar(2, bar, len(bar)) +endfunction + +function! s:is_updated(dir) + return !empty(s:system_chomp('git log --pretty=format:"%h" "HEAD...HEAD@{1}"', a:dir)) +endfunction + +function! s:do(pull, force, todo) + for [name, spec] in items(a:todo) + if !isdirectory(spec.dir) + continue + endif + let installed = has_key(s:update.new, name) + let updated = installed ? 0 : + \ (a:pull && index(s:update.errors, name) < 0 && s:is_updated(spec.dir)) + if a:force || installed || updated + execute 'cd' s:esc(spec.dir) + call append(3, '- Post-update hook for '. name .' ... ') + let error = '' + let type = type(spec.do) + if type == s:TYPE.string + if spec.do[0] == ':' + if !get(s:loaded, name, 0) + let s:loaded[name] = 1 + call s:reorg_rtp() + endif + call s:load_plugin(spec) + try + execute spec.do[1:] + catch + let error = v:exception + endtry + if !s:plug_window_exists() + cd - + throw 'Warning: vim-plug was terminated by the post-update hook of '.name + endif + else + let error = s:bang(spec.do) + endif + elseif type == s:TYPE.funcref + try + let status = installed ? 'installed' : (updated ? 'updated' : 'unchanged') + call spec.do({ 'name': name, 'status': status, 'force': a:force }) + catch + let error = v:exception + endtry + else + let error = 'Invalid hook type' + endif + call s:switch_in() + call setline(4, empty(error) ? (getline(4) . 'OK') + \ : ('x' . getline(4)[1:] . error)) + if !empty(error) + call add(s:update.errors, name) + call s:regress_bar() + endif + cd - + endif + endfor +endfunction + +function! s:hash_match(a, b) + return stridx(a:a, a:b) == 0 || stridx(a:b, a:a) == 0 +endfunction + +function! s:checkout(spec) + let sha = a:spec.commit + let output = s:system('git rev-parse HEAD', a:spec.dir) + if !v:shell_error && !s:hash_match(sha, s:lines(output)[0]) + let output = s:system( + \ 'git fetch --depth 999999 && git checkout '.s:esc(sha).' --', a:spec.dir) + endif + return output +endfunction + +function! s:finish(pull) + let new_frozen = len(filter(keys(s:update.new), 'g:plugs[v:val].frozen')) + if new_frozen + let s = new_frozen > 1 ? 's' : '' + call append(3, printf('- Installed %d frozen plugin%s', new_frozen, s)) + endif + call append(3, '- Finishing ... ') | 4 + redraw + call plug#helptags() + call plug#end() + call setline(4, getline(4) . 'Done!') + redraw + let msgs = [] + if !empty(s:update.errors) + call add(msgs, "Press 'R' to retry.") + endif + if a:pull && len(s:update.new) < len(filter(getline(5, '$'), + \ "v:val =~ '^- ' && v:val !~# 'Already up.to.date'")) + call add(msgs, "Press 'D' to see the updated changes.") + endif + echo join(msgs, ' ') + call s:finish_bindings() +endfunction + +function! s:retry() + if empty(s:update.errors) + return + endif + echo + call s:update_impl(s:update.pull, s:update.force, + \ extend(copy(s:update.errors), [s:update.threads])) +endfunction + +function! s:is_managed(name) + return has_key(g:plugs[a:name], 'uri') +endfunction + +function! s:names(...) + return sort(filter(keys(g:plugs), 'stridx(v:val, a:1) == 0 && s:is_managed(v:val)')) +endfunction + +function! s:check_ruby() + silent! ruby require 'thread'; VIM::command("let g:plug_ruby = '#{RUBY_VERSION}'") + if !exists('g:plug_ruby') + redraw! + return s:warn('echom', 'Warning: Ruby interface is broken') + endif + let ruby_version = split(g:plug_ruby, '\.') + unlet g:plug_ruby + return s:version_requirement(ruby_version, [1, 8, 7]) +endfunction + +function! s:update_impl(pull, force, args) abort + let sync = index(a:args, '--sync') >= 0 || has('vim_starting') + let args = filter(copy(a:args), 'v:val != "--sync"') + let threads = (len(args) > 0 && args[-1] =~ '^[1-9][0-9]*$') ? + \ remove(args, -1) : get(g:, 'plug_threads', 16) + + let managed = filter(copy(g:plugs), 's:is_managed(v:key)') + let todo = empty(args) ? filter(managed, '!v:val.frozen || !isdirectory(v:val.dir)') : + \ filter(managed, 'index(args, v:key) >= 0') + + if empty(todo) + return s:warn('echo', 'No plugin to '. (a:pull ? 'update' : 'install')) + endif + + if !s:is_win && s:git_version_requirement(2, 3) + let s:git_terminal_prompt = exists('$GIT_TERMINAL_PROMPT') ? $GIT_TERMINAL_PROMPT : '' + let $GIT_TERMINAL_PROMPT = 0 + for plug in values(todo) + let plug.uri = substitute(plug.uri, + \ '^https://git::@github\.com', 'https://github.com', '') + endfor + endif + + if !isdirectory(g:plug_home) + try + call mkdir(g:plug_home, 'p') + catch + return s:err(printf('Invalid plug directory: %s. '. + \ 'Try to call plug#begin with a valid directory', g:plug_home)) + endtry + endif + + if has('nvim') && !exists('*jobwait') && threads > 1 + call s:warn('echom', '[vim-plug] Update Neovim for parallel installer') + endif + + let use_job = s:nvim || s:vim8 + let python = (has('python') || has('python3')) && !use_job + let ruby = has('ruby') && !use_job && (v:version >= 703 || v:version == 702 && has('patch374')) && !(s:is_win && has('gui_running')) && threads > 1 && s:check_ruby() + + let s:update = { + \ 'start': reltime(), + \ 'all': todo, + \ 'todo': copy(todo), + \ 'errors': [], + \ 'pull': a:pull, + \ 'force': a:force, + \ 'new': {}, + \ 'threads': (python || ruby || use_job) ? min([len(todo), threads]) : 1, + \ 'bar': '', + \ 'fin': 0 + \ } + + call s:prepare(1) + call append(0, ['', '']) + normal! 2G + silent! redraw + + let s:clone_opt = get(g:, 'plug_shallow', 1) ? + \ '--depth 1' . (s:git_version_requirement(1, 7, 10) ? ' --no-single-branch' : '') : '' + + if has('win32unix') + let s:clone_opt .= ' -c core.eol=lf -c core.autocrlf=input' + endif + + " Python version requirement (>= 2.7) + if python && !has('python3') && !ruby && !use_job && s:update.threads > 1 + redir => pyv + silent python import platform; print platform.python_version() + redir END + let python = s:version_requirement( + \ map(split(split(pyv)[0], '\.'), 'str2nr(v:val)'), [2, 6]) + endif + + if (python || ruby) && s:update.threads > 1 + try + let imd = &imd + if s:mac_gui + set noimd + endif + if ruby + call s:update_ruby() + else + call s:update_python() + endif + catch + let lines = getline(4, '$') + let printed = {} + silent! 4,$d _ + for line in lines + let name = s:extract_name(line, '.', '') + if empty(name) || !has_key(printed, name) + call append('$', line) + if !empty(name) + let printed[name] = 1 + if line[0] == 'x' && index(s:update.errors, name) < 0 + call add(s:update.errors, name) + end + endif + endif + endfor + finally + let &imd = imd + call s:update_finish() + endtry + else + call s:update_vim() + while use_job && sync + sleep 100m + if s:update.fin + break + endif + endwhile + endif +endfunction + +function! s:log4(name, msg) + call setline(4, printf('- %s (%s)', a:msg, a:name)) + redraw +endfunction + +function! s:update_finish() + if exists('s:git_terminal_prompt') + let $GIT_TERMINAL_PROMPT = s:git_terminal_prompt + endif + if s:switch_in() + call append(3, '- Updating ...') | 4 + for [name, spec] in items(filter(copy(s:update.all), 'index(s:update.errors, v:key) < 0 && (s:update.force || s:update.pull || has_key(s:update.new, v:key))')) + let [pos, _] = s:logpos(name) + if !pos + continue + endif + if has_key(spec, 'commit') + call s:log4(name, 'Checking out '.spec.commit) + let out = s:checkout(spec) + elseif has_key(spec, 'tag') + let tag = spec.tag + if tag =~ '\*' + let tags = s:lines(s:system('git tag --list '.s:shellesc(tag).' --sort -version:refname 2>&1', spec.dir)) + if !v:shell_error && !empty(tags) + let tag = tags[0] + call s:log4(name, printf('Latest tag for %s -> %s', spec.tag, tag)) + call append(3, '') + endif + endif + call s:log4(name, 'Checking out '.tag) + let out = s:system('git checkout -q '.s:esc(tag).' -- 2>&1', spec.dir) + else + let branch = s:esc(get(spec, 'branch', 'master')) + call s:log4(name, 'Merging origin/'.branch) + let out = s:system('git checkout -q '.branch.' -- 2>&1' + \. (has_key(s:update.new, name) ? '' : ('&& git merge --ff-only origin/'.branch.' 2>&1')), spec.dir) + endif + if !v:shell_error && filereadable(spec.dir.'/.gitmodules') && + \ (s:update.force || has_key(s:update.new, name) || s:is_updated(spec.dir)) + call s:log4(name, 'Updating submodules. This may take a while.') + let out .= s:bang('git submodule update --init --recursive 2>&1', spec.dir) + endif + let msg = s:format_message(v:shell_error ? 'x': '-', name, out) + if v:shell_error + call add(s:update.errors, name) + call s:regress_bar() + silent execute pos 'd _' + call append(4, msg) | 4 + elseif !empty(out) + call setline(pos, msg[0]) + endif + redraw + endfor + silent 4 d _ + try + call s:do(s:update.pull, s:update.force, filter(copy(s:update.all), 'index(s:update.errors, v:key) < 0 && has_key(v:val, "do")')) + catch + call s:warn('echom', v:exception) + call s:warn('echo', '') + return + endtry + call s:finish(s:update.pull) + call setline(1, 'Updated. Elapsed time: ' . split(reltimestr(reltime(s:update.start)))[0] . ' sec.') + call s:switch_out('normal! gg') + endif +endfunction + +function! s:job_abort() + if (!s:nvim && !s:vim8) || !exists('s:jobs') + return + endif + + for [name, j] in items(s:jobs) + if s:nvim + silent! call jobstop(j.jobid) + elseif s:vim8 + silent! call job_stop(j.jobid) + endif + if j.new + call s:system('rm -rf ' . s:shellesc(g:plugs[name].dir)) + endif + endfor + let s:jobs = {} +endfunction + +function! s:last_non_empty_line(lines) + let len = len(a:lines) + for idx in range(len) + let line = a:lines[len-idx-1] + if !empty(line) + return line + endif + endfor + return '' +endfunction + +function! s:job_out_cb(self, data) abort + let self = a:self + let data = remove(self.lines, -1) . a:data + let lines = map(split(data, "\n", 1), 'split(v:val, "\r", 1)[-1]') + call extend(self.lines, lines) + " To reduce the number of buffer updates + let self.tick = get(self, 'tick', -1) + 1 + if !self.running || self.tick % len(s:jobs) == 0 + let bullet = self.running ? (self.new ? '+' : '*') : (self.error ? 'x' : '-') + let result = self.error ? join(self.lines, "\n") : s:last_non_empty_line(self.lines) + call s:log(bullet, self.name, result) + endif +endfunction + +function! s:job_exit_cb(self, data) abort + let a:self.running = 0 + let a:self.error = a:data != 0 + call s:reap(a:self.name) + call s:tick() +endfunction + +function! s:job_cb(fn, job, ch, data) + if !s:plug_window_exists() " plug window closed + return s:job_abort() + endif + call call(a:fn, [a:job, a:data]) +endfunction + +function! s:nvim_cb(job_id, data, event) dict abort + return a:event == 'stdout' ? + \ s:job_cb('s:job_out_cb', self, 0, join(a:data, "\n")) : + \ s:job_cb('s:job_exit_cb', self, 0, a:data) +endfunction + +function! s:spawn(name, cmd, opts) + let job = { 'name': a:name, 'running': 1, 'error': 0, 'lines': [''], + \ 'batchfile': (s:is_win && (s:nvim || s:vim8)) ? tempname().'.bat' : '', + \ 'new': get(a:opts, 'new', 0) } + let s:jobs[a:name] = job + let cmd = has_key(a:opts, 'dir') ? s:with_cd(a:cmd, a:opts.dir) : a:cmd + if !empty(job.batchfile) + call writefile(['@echo off', cmd], job.batchfile) + let cmd = job.batchfile + endif + let argv = add(s:is_win ? ['cmd', '/c'] : ['sh', '-c'], cmd) + + if s:nvim + call extend(job, { + \ 'on_stdout': function('s:nvim_cb'), + \ 'on_exit': function('s:nvim_cb'), + \ }) + let jid = jobstart(argv, job) + if jid > 0 + let job.jobid = jid + else + let job.running = 0 + let job.error = 1 + let job.lines = [jid < 0 ? argv[0].' is not executable' : + \ 'Invalid arguments (or job table is full)'] + endif + elseif s:vim8 + let jid = job_start(s:is_win ? join(argv, ' ') : argv, { + \ 'out_cb': function('s:job_cb', ['s:job_out_cb', job]), + \ 'exit_cb': function('s:job_cb', ['s:job_exit_cb', job]), + \ 'out_mode': 'raw' + \}) + if job_status(jid) == 'run' + let job.jobid = jid + else + let job.running = 0 + let job.error = 1 + let job.lines = ['Failed to start job'] + endif + else + let job.lines = s:lines(call('s:system', [cmd])) + let job.error = v:shell_error != 0 + let job.running = 0 + endif +endfunction + +function! s:reap(name) + let job = s:jobs[a:name] + if job.error + call add(s:update.errors, a:name) + elseif get(job, 'new', 0) + let s:update.new[a:name] = 1 + endif + let s:update.bar .= job.error ? 'x' : '=' + + let bullet = job.error ? 'x' : '-' + let result = job.error ? join(job.lines, "\n") : s:last_non_empty_line(job.lines) + call s:log(bullet, a:name, empty(result) ? 'OK' : result) + call s:bar() + + if has_key(job, 'batchfile') && !empty(job.batchfile) + call delete(job.batchfile) + endif + call remove(s:jobs, a:name) +endfunction + +function! s:bar() + if s:switch_in() + let total = len(s:update.all) + call setline(1, (s:update.pull ? 'Updating' : 'Installing'). + \ ' plugins ('.len(s:update.bar).'/'.total.')') + call s:progress_bar(2, s:update.bar, total) + call s:switch_out() + endif +endfunction + +function! s:logpos(name) + for i in range(4, line('$')) + if getline(i) =~# '^[-+x*] '.a:name.':' + for j in range(i + 1, line('$')) + if getline(j) !~ '^ ' + return [i, j - 1] + endif + endfor + return [i, i] + endif + endfor + return [0, 0] +endfunction + +function! s:log(bullet, name, lines) + if s:switch_in() + let [b, e] = s:logpos(a:name) + if b > 0 + silent execute printf('%d,%d d _', b, e) + if b > winheight('.') + let b = 4 + endif + else + let b = 4 + endif + " FIXME For some reason, nomodifiable is set after :d in vim8 + setlocal modifiable + call append(b - 1, s:format_message(a:bullet, a:name, a:lines)) + call s:switch_out() + endif +endfunction + +function! s:update_vim() + let s:jobs = {} + + call s:bar() + call s:tick() +endfunction + +function! s:tick() + let pull = s:update.pull + let prog = s:progress_opt(s:nvim || s:vim8) +while 1 " Without TCO, Vim stack is bound to explode + if empty(s:update.todo) + if empty(s:jobs) && !s:update.fin + call s:update_finish() + let s:update.fin = 1 + endif + return + endif + + let name = keys(s:update.todo)[0] + let spec = remove(s:update.todo, name) + let new = !isdirectory(spec.dir) + + call s:log(new ? '+' : '*', name, pull ? 'Updating ...' : 'Installing ...') + redraw + + let has_tag = has_key(spec, 'tag') + if !new + let [error, _] = s:git_validate(spec, 0) + if empty(error) + if pull + let fetch_opt = (has_tag && !empty(globpath(spec.dir, '.git/shallow'))) ? '--depth 99999999' : '' + call s:spawn(name, printf('git fetch %s %s 2>&1', fetch_opt, prog), { 'dir': spec.dir }) + else + let s:jobs[name] = { 'running': 0, 'lines': ['Already installed'], 'error': 0 } + endif + else + let s:jobs[name] = { 'running': 0, 'lines': s:lines(error), 'error': 1 } + endif + else + call s:spawn(name, + \ printf('git clone %s %s %s %s 2>&1', + \ has_tag ? '' : s:clone_opt, + \ prog, + \ s:shellesc(spec.uri), + \ s:shellesc(s:trim(spec.dir))), { 'new': 1 }) + endif + + if !s:jobs[name].running + call s:reap(name) + endif + if len(s:jobs) >= s:update.threads + break + endif +endwhile +endfunction + +function! s:update_python() +let py_exe = has('python') ? 'python' : 'python3' +execute py_exe "<< EOF" +import datetime +import functools +import os +try: + import queue +except ImportError: + import Queue as queue +import random +import re +import shutil +import signal +import subprocess +import tempfile +import threading as thr +import time +import traceback +import vim + +G_NVIM = vim.eval("has('nvim')") == '1' +G_PULL = vim.eval('s:update.pull') == '1' +G_RETRIES = int(vim.eval('get(g:, "plug_retries", 2)')) + 1 +G_TIMEOUT = int(vim.eval('get(g:, "plug_timeout", 60)')) +G_CLONE_OPT = vim.eval('s:clone_opt') +G_PROGRESS = vim.eval('s:progress_opt(1)') +G_LOG_PROB = 1.0 / int(vim.eval('s:update.threads')) +G_STOP = thr.Event() +G_IS_WIN = vim.eval('s:is_win') == '1' + +class PlugError(Exception): + def __init__(self, msg): + self.msg = msg +class CmdTimedOut(PlugError): + pass +class CmdFailed(PlugError): + pass +class InvalidURI(PlugError): + pass +class Action(object): + INSTALL, UPDATE, ERROR, DONE = ['+', '*', 'x', '-'] + +class Buffer(object): + def __init__(self, lock, num_plugs, is_pull): + self.bar = '' + self.event = 'Updating' if is_pull else 'Installing' + self.lock = lock + self.maxy = int(vim.eval('winheight(".")')) + self.num_plugs = num_plugs + + def __where(self, name): + """ Find first line with name in current buffer. Return line num. """ + found, lnum = False, 0 + matcher = re.compile('^[-+x*] {0}:'.format(name)) + for line in vim.current.buffer: + if matcher.search(line) is not None: + found = True + break + lnum += 1 + + if not found: + lnum = -1 + return lnum + + def header(self): + curbuf = vim.current.buffer + curbuf[0] = self.event + ' plugins ({0}/{1})'.format(len(self.bar), self.num_plugs) + + num_spaces = self.num_plugs - len(self.bar) + curbuf[1] = '[{0}{1}]'.format(self.bar, num_spaces * ' ') + + with self.lock: + vim.command('normal! 2G') + vim.command('redraw') + + def write(self, action, name, lines): + first, rest = lines[0], lines[1:] + msg = ['{0} {1}{2}{3}'.format(action, name, ': ' if first else '', first)] + msg.extend([' ' + line for line in rest]) + + try: + if action == Action.ERROR: + self.bar += 'x' + vim.command("call add(s:update.errors, '{0}')".format(name)) + elif action == Action.DONE: + self.bar += '=' + + curbuf = vim.current.buffer + lnum = self.__where(name) + if lnum != -1: # Found matching line num + del curbuf[lnum] + if lnum > self.maxy and action in set([Action.INSTALL, Action.UPDATE]): + lnum = 3 + else: + lnum = 3 + curbuf.append(msg, lnum) + + self.header() + except vim.error: + pass + +class Command(object): + CD = 'cd /d' if G_IS_WIN else 'cd' + + def __init__(self, cmd, cmd_dir=None, timeout=60, cb=None, clean=None): + self.cmd = cmd + if cmd_dir: + self.cmd = '{0} {1} && {2}'.format(Command.CD, cmd_dir, self.cmd) + self.timeout = timeout + self.callback = cb if cb else (lambda msg: None) + self.clean = clean if clean else (lambda: None) + self.proc = None + + @property + def alive(self): + """ Returns true only if command still running. """ + return self.proc and self.proc.poll() is None + + def execute(self, ntries=3): + """ Execute the command with ntries if CmdTimedOut. + Returns the output of the command if no Exception. + """ + attempt, finished, limit = 0, False, self.timeout + + while not finished: + try: + attempt += 1 + result = self.try_command() + finished = True + return result + except CmdTimedOut: + if attempt != ntries: + self.notify_retry() + self.timeout += limit + else: + raise + + def notify_retry(self): + """ Retry required for command, notify user. """ + for count in range(3, 0, -1): + if G_STOP.is_set(): + raise KeyboardInterrupt + msg = 'Timeout. Will retry in {0} second{1} ...'.format( + count, 's' if count != 1 else '') + self.callback([msg]) + time.sleep(1) + self.callback(['Retrying ...']) + + def try_command(self): + """ Execute a cmd & poll for callback. Returns list of output. + Raises CmdFailed -> return code for Popen isn't 0 + Raises CmdTimedOut -> command exceeded timeout without new output + """ + first_line = True + + try: + tfile = tempfile.NamedTemporaryFile(mode='w+b') + preexec_fn = not G_IS_WIN and os.setsid or None + self.proc = subprocess.Popen(self.cmd, stdout=tfile, + stderr=subprocess.STDOUT, + stdin=subprocess.PIPE, shell=True, + preexec_fn=preexec_fn) + thrd = thr.Thread(target=(lambda proc: proc.wait()), args=(self.proc,)) + thrd.start() + + thread_not_started = True + while thread_not_started: + try: + thrd.join(0.1) + thread_not_started = False + except RuntimeError: + pass + + while self.alive: + if G_STOP.is_set(): + raise KeyboardInterrupt + + if first_line or random.random() < G_LOG_PROB: + first_line = False + line = '' if G_IS_WIN else nonblock_read(tfile.name) + if line: + self.callback([line]) + + time_diff = time.time() - os.path.getmtime(tfile.name) + if time_diff > self.timeout: + raise CmdTimedOut(['Timeout!']) + + thrd.join(0.5) + + tfile.seek(0) + result = [line.decode('utf-8', 'replace').rstrip() for line in tfile] + + if self.proc.returncode != 0: + raise CmdFailed([''] + result) + + return result + except: + self.terminate() + raise + + def terminate(self): + """ Terminate process and cleanup. """ + if self.alive: + if G_IS_WIN: + os.kill(self.proc.pid, signal.SIGINT) + else: + os.killpg(self.proc.pid, signal.SIGTERM) + self.clean() + +class Plugin(object): + def __init__(self, name, args, buf_q, lock): + self.name = name + self.args = args + self.buf_q = buf_q + self.lock = lock + self.tag = args.get('tag', 0) + + def manage(self): + try: + if os.path.exists(self.args['dir']): + self.update() + else: + self.install() + with self.lock: + thread_vim_command("let s:update.new['{0}'] = 1".format(self.name)) + except PlugError as exc: + self.write(Action.ERROR, self.name, exc.msg) + except KeyboardInterrupt: + G_STOP.set() + self.write(Action.ERROR, self.name, ['Interrupted!']) + except: + # Any exception except those above print stack trace + msg = 'Trace:\n{0}'.format(traceback.format_exc().rstrip()) + self.write(Action.ERROR, self.name, msg.split('\n')) + raise + + def install(self): + target = self.args['dir'] + if target[-1] == '\\': + target = target[0:-1] + + def clean(target): + def _clean(): + try: + shutil.rmtree(target) + except OSError: + pass + return _clean + + self.write(Action.INSTALL, self.name, ['Installing ...']) + callback = functools.partial(self.write, Action.INSTALL, self.name) + cmd = 'git clone {0} {1} {2} {3} 2>&1'.format( + '' if self.tag else G_CLONE_OPT, G_PROGRESS, self.args['uri'], + esc(target)) + com = Command(cmd, None, G_TIMEOUT, callback, clean(target)) + result = com.execute(G_RETRIES) + self.write(Action.DONE, self.name, result[-1:]) + + def repo_uri(self): + cmd = 'git rev-parse --abbrev-ref HEAD 2>&1 && git config -f .git/config remote.origin.url' + command = Command(cmd, self.args['dir'], G_TIMEOUT,) + result = command.execute(G_RETRIES) + return result[-1] + + def update(self): + actual_uri = self.repo_uri() + expect_uri = self.args['uri'] + regex = re.compile(r'^(?:\w+://)?(?:[^@/]*@)?([^:/]*(?::[0-9]*)?)[:/](.*?)(?:\.git)?/?$') + ma = regex.match(actual_uri) + mb = regex.match(expect_uri) + if ma is None or mb is None or ma.groups() != mb.groups(): + msg = ['', + 'Invalid URI: {0}'.format(actual_uri), + 'Expected {0}'.format(expect_uri), + 'PlugClean required.'] + raise InvalidURI(msg) + + if G_PULL: + self.write(Action.UPDATE, self.name, ['Updating ...']) + callback = functools.partial(self.write, Action.UPDATE, self.name) + fetch_opt = '--depth 99999999' if self.tag and os.path.isfile(os.path.join(self.args['dir'], '.git/shallow')) else '' + cmd = 'git fetch {0} {1} 2>&1'.format(fetch_opt, G_PROGRESS) + com = Command(cmd, self.args['dir'], G_TIMEOUT, callback) + result = com.execute(G_RETRIES) + self.write(Action.DONE, self.name, result[-1:]) + else: + self.write(Action.DONE, self.name, ['Already installed']) + + def write(self, action, name, msg): + self.buf_q.put((action, name, msg)) + +class PlugThread(thr.Thread): + def __init__(self, tname, args): + super(PlugThread, self).__init__() + self.tname = tname + self.args = args + + def run(self): + thr.current_thread().name = self.tname + buf_q, work_q, lock = self.args + + try: + while not G_STOP.is_set(): + name, args = work_q.get_nowait() + plug = Plugin(name, args, buf_q, lock) + plug.manage() + work_q.task_done() + except queue.Empty: + pass + +class RefreshThread(thr.Thread): + def __init__(self, lock): + super(RefreshThread, self).__init__() + self.lock = lock + self.running = True + + def run(self): + while self.running: + with self.lock: + thread_vim_command('noautocmd normal! a') + time.sleep(0.33) + + def stop(self): + self.running = False + +if G_NVIM: + def thread_vim_command(cmd): + vim.session.threadsafe_call(lambda: vim.command(cmd)) +else: + def thread_vim_command(cmd): + vim.command(cmd) + +def esc(name): + return '"' + name.replace('"', '\"') + '"' + +def nonblock_read(fname): + """ Read a file with nonblock flag. Return the last line. """ + fread = os.open(fname, os.O_RDONLY | os.O_NONBLOCK) + buf = os.read(fread, 100000).decode('utf-8', 'replace') + os.close(fread) + + line = buf.rstrip('\r\n') + left = max(line.rfind('\r'), line.rfind('\n')) + if left != -1: + left += 1 + line = line[left:] + + return line + +def main(): + thr.current_thread().name = 'main' + nthreads = int(vim.eval('s:update.threads')) + plugs = vim.eval('s:update.todo') + mac_gui = vim.eval('s:mac_gui') == '1' + + lock = thr.Lock() + buf = Buffer(lock, len(plugs), G_PULL) + buf_q, work_q = queue.Queue(), queue.Queue() + for work in plugs.items(): + work_q.put(work) + + start_cnt = thr.active_count() + for num in range(nthreads): + tname = 'PlugT-{0:02}'.format(num) + thread = PlugThread(tname, (buf_q, work_q, lock)) + thread.start() + if mac_gui: + rthread = RefreshThread(lock) + rthread.start() + + while not buf_q.empty() or thr.active_count() != start_cnt: + try: + action, name, msg = buf_q.get(True, 0.25) + buf.write(action, name, ['OK'] if not msg else msg) + buf_q.task_done() + except queue.Empty: + pass + except KeyboardInterrupt: + G_STOP.set() + + if mac_gui: + rthread.stop() + rthread.join() + +main() +EOF +endfunction + +function! s:update_ruby() + ruby << EOF + module PlugStream + SEP = ["\r", "\n", nil] + def get_line + buffer = '' + loop do + char = readchar rescue return + if SEP.include? char.chr + buffer << $/ + break + else + buffer << char + end + end + buffer + end + end unless defined?(PlugStream) + + def esc arg + %["#{arg.gsub('"', '\"')}"] + end + + def killall pid + pids = [pid] + if /mswin|mingw|bccwin/ =~ RUBY_PLATFORM + pids.each { |pid| Process.kill 'INT', pid.to_i rescue nil } + else + unless `which pgrep 2> /dev/null`.empty? + children = pids + until children.empty? + children = children.map { |pid| + `pgrep -P #{pid}`.lines.map { |l| l.chomp } + }.flatten + pids += children + end + end + pids.each { |pid| Process.kill 'TERM', pid.to_i rescue nil } + end + end + + def compare_git_uri a, b + regex = %r{^(?:\w+://)?(?:[^@/]*@)?([^:/]*(?::[0-9]*)?)[:/](.*?)(?:\.git)?/?$} + regex.match(a).to_a.drop(1) == regex.match(b).to_a.drop(1) + end + + require 'thread' + require 'fileutils' + require 'timeout' + running = true + iswin = VIM::evaluate('s:is_win').to_i == 1 + pull = VIM::evaluate('s:update.pull').to_i == 1 + base = VIM::evaluate('g:plug_home') + all = VIM::evaluate('s:update.todo') + limit = VIM::evaluate('get(g:, "plug_timeout", 60)') + tries = VIM::evaluate('get(g:, "plug_retries", 2)') + 1 + nthr = VIM::evaluate('s:update.threads').to_i + maxy = VIM::evaluate('winheight(".")').to_i + vim7 = VIM::evaluate('v:version').to_i <= 703 && RUBY_PLATFORM =~ /darwin/ + cd = iswin ? 'cd /d' : 'cd' + tot = VIM::evaluate('len(s:update.todo)') || 0 + bar = '' + skip = 'Already installed' + mtx = Mutex.new + take1 = proc { mtx.synchronize { running && all.shift } } + logh = proc { + cnt = bar.length + $curbuf[1] = "#{pull ? 'Updating' : 'Installing'} plugins (#{cnt}/#{tot})" + $curbuf[2] = '[' + bar.ljust(tot) + ']' + VIM::command('normal! 2G') + VIM::command('redraw') + } + where = proc { |name| (1..($curbuf.length)).find { |l| $curbuf[l] =~ /^[-+x*] #{name}:/ } } + log = proc { |name, result, type| + mtx.synchronize do + ing = ![true, false].include?(type) + bar += type ? '=' : 'x' unless ing + b = case type + when :install then '+' when :update then '*' + when true, nil then '-' else + VIM::command("call add(s:update.errors, '#{name}')") + 'x' + end + result = + if type || type.nil? + ["#{b} #{name}: #{result.lines.to_a.last || 'OK'}"] + elsif result =~ /^Interrupted|^Timeout/ + ["#{b} #{name}: #{result}"] + else + ["#{b} #{name}"] + result.lines.map { |l| " " << l } + end + if lnum = where.call(name) + $curbuf.delete lnum + lnum = 4 if ing && lnum > maxy + end + result.each_with_index do |line, offset| + $curbuf.append((lnum || 4) - 1 + offset, line.gsub(/\e\[./, '').chomp) + end + logh.call + end + } + bt = proc { |cmd, name, type, cleanup| + tried = timeout = 0 + begin + tried += 1 + timeout += limit + fd = nil + data = '' + if iswin + Timeout::timeout(timeout) do + tmp = VIM::evaluate('tempname()') + system("(#{cmd}) > #{tmp}") + data = File.read(tmp).chomp + File.unlink tmp rescue nil + end + else + fd = IO.popen(cmd).extend(PlugStream) + first_line = true + log_prob = 1.0 / nthr + while line = Timeout::timeout(timeout) { fd.get_line } + data << line + log.call name, line.chomp, type if name && (first_line || rand < log_prob) + first_line = false + end + fd.close + end + [$? == 0, data.chomp] + rescue Timeout::Error, Interrupt => e + if fd && !fd.closed? + killall fd.pid + fd.close + end + cleanup.call if cleanup + if e.is_a?(Timeout::Error) && tried < tries + 3.downto(1) do |countdown| + s = countdown > 1 ? 's' : '' + log.call name, "Timeout. Will retry in #{countdown} second#{s} ...", type + sleep 1 + end + log.call name, 'Retrying ...', type + retry + end + [false, e.is_a?(Interrupt) ? "Interrupted!" : "Timeout!"] + end + } + main = Thread.current + threads = [] + watcher = Thread.new { + if vim7 + while VIM::evaluate('getchar(1)') + sleep 0.1 + end + else + require 'io/console' # >= Ruby 1.9 + nil until IO.console.getch == 3.chr + end + mtx.synchronize do + running = false + threads.each { |t| t.raise Interrupt } unless vim7 + end + threads.each { |t| t.join rescue nil } + main.kill + } + refresh = Thread.new { + while true + mtx.synchronize do + break unless running + VIM::command('noautocmd normal! a') + end + sleep 0.2 + end + } if VIM::evaluate('s:mac_gui') == 1 + + clone_opt = VIM::evaluate('s:clone_opt') + progress = VIM::evaluate('s:progress_opt(1)') + nthr.times do + mtx.synchronize do + threads << Thread.new { + while pair = take1.call + name = pair.first + dir, uri, tag = pair.last.values_at *%w[dir uri tag] + exists = File.directory? dir + ok, result = + if exists + chdir = "#{cd} #{iswin ? dir : esc(dir)}" + ret, data = bt.call "#{chdir} && git rev-parse --abbrev-ref HEAD 2>&1 && git config -f .git/config remote.origin.url", nil, nil, nil + current_uri = data.lines.to_a.last + if !ret + if data =~ /^Interrupted|^Timeout/ + [false, data] + else + [false, [data.chomp, "PlugClean required."].join($/)] + end + elsif !compare_git_uri(current_uri, uri) + [false, ["Invalid URI: #{current_uri}", + "Expected: #{uri}", + "PlugClean required."].join($/)] + else + if pull + log.call name, 'Updating ...', :update + fetch_opt = (tag && File.exist?(File.join(dir, '.git/shallow'))) ? '--depth 99999999' : '' + bt.call "#{chdir} && git fetch #{fetch_opt} #{progress} 2>&1", name, :update, nil + else + [true, skip] + end + end + else + d = esc dir.sub(%r{[\\/]+$}, '') + log.call name, 'Installing ...', :install + bt.call "git clone #{clone_opt unless tag} #{progress} #{uri} #{d} 2>&1", name, :install, proc { + FileUtils.rm_rf dir + } + end + mtx.synchronize { VIM::command("let s:update.new['#{name}'] = 1") } if !exists && ok + log.call name, result, ok + end + } if running + end + end + threads.each { |t| t.join rescue nil } + logh.call + refresh.kill if refresh + watcher.kill +EOF +endfunction + +function! s:shellesc_cmd(arg) + let escaped = substitute(a:arg, '[&|<>()@^]', '^&', 'g') + let escaped = substitute(escaped, '%', '%%', 'g') + let escaped = substitute(escaped, '"', '\\^&', 'g') + let escaped = substitute(escaped, '\(\\\+\)\(\\^\)', '\1\1\2', 'g') + return '^"'.substitute(escaped, '\(\\\+\)$', '\1\1', '').'^"' +endfunction + +function! s:shellesc(arg) + if &shell =~# 'cmd.exe$' + return s:shellesc_cmd(a:arg) + endif + return shellescape(a:arg) +endfunction + +function! s:glob_dir(path) + return map(filter(s:glob(a:path, '**'), 'isdirectory(v:val)'), 's:dirpath(v:val)') +endfunction + +function! s:progress_bar(line, bar, total) + call setline(a:line, '[' . s:lpad(a:bar, a:total) . ']') +endfunction + +function! s:compare_git_uri(a, b) + " See `git help clone' + " https:// [user@] github.com[:port] / junegunn/vim-plug [.git] + " [git@] github.com[:port] : junegunn/vim-plug [.git] + " file:// / junegunn/vim-plug [/] + " / junegunn/vim-plug [/] + let pat = '^\%(\w\+://\)\='.'\%([^@/]*@\)\='.'\([^:/]*\%(:[0-9]*\)\=\)'.'[:/]'.'\(.\{-}\)'.'\%(\.git\)\=/\?$' + let ma = matchlist(a:a, pat) + let mb = matchlist(a:b, pat) + return ma[1:2] ==# mb[1:2] +endfunction + +function! s:format_message(bullet, name, message) + if a:bullet != 'x' + return [printf('%s %s: %s', a:bullet, a:name, s:lastline(a:message))] + else + let lines = map(s:lines(a:message), '" ".v:val') + return extend([printf('x %s:', a:name)], lines) + endif +endfunction + +function! s:with_cd(cmd, dir) + return printf('cd%s %s && %s', s:is_win ? ' /d' : '', s:shellesc(a:dir), a:cmd) +endfunction + +function! s:system(cmd, ...) + try + let [sh, shellcmdflag, shrd] = s:chsh(1) + let cmd = a:0 > 0 ? s:with_cd(a:cmd, a:1) : a:cmd + if s:is_win + let batchfile = tempname().'.bat' + call writefile(['@echo off', cmd], batchfile) + let cmd = batchfile + endif + return system(s:is_win ? '('.cmd.')' : cmd) + finally + let [&shell, &shellcmdflag, &shellredir] = [sh, shellcmdflag, shrd] + if s:is_win + call delete(batchfile) + endif + endtry +endfunction + +function! s:system_chomp(...) + let ret = call('s:system', a:000) + return v:shell_error ? '' : substitute(ret, '\n$', '', '') +endfunction + +function! s:git_validate(spec, check_branch) + let err = '' + if isdirectory(a:spec.dir) + let result = s:lines(s:system('git rev-parse --abbrev-ref HEAD 2>&1 && git config -f .git/config remote.origin.url', a:spec.dir)) + let remote = result[-1] + if v:shell_error + let err = join([remote, 'PlugClean required.'], "\n") + elseif !s:compare_git_uri(remote, a:spec.uri) + let err = join(['Invalid URI: '.remote, + \ 'Expected: '.a:spec.uri, + \ 'PlugClean required.'], "\n") + elseif a:check_branch && has_key(a:spec, 'commit') + let result = s:lines(s:system('git rev-parse HEAD 2>&1', a:spec.dir)) + let sha = result[-1] + if v:shell_error + let err = join(add(result, 'PlugClean required.'), "\n") + elseif !s:hash_match(sha, a:spec.commit) + let err = join([printf('Invalid HEAD (expected: %s, actual: %s)', + \ a:spec.commit[:6], sha[:6]), + \ 'PlugUpdate required.'], "\n") + endif + elseif a:check_branch + let branch = result[0] + " Check tag + if has_key(a:spec, 'tag') + let tag = s:system_chomp('git describe --exact-match --tags HEAD 2>&1', a:spec.dir) + if a:spec.tag !=# tag && a:spec.tag !~ '\*' + let err = printf('Invalid tag: %s (expected: %s). Try PlugUpdate.', + \ (empty(tag) ? 'N/A' : tag), a:spec.tag) + endif + " Check branch + elseif a:spec.branch !=# branch + let err = printf('Invalid branch: %s (expected: %s). Try PlugUpdate.', + \ branch, a:spec.branch) + endif + if empty(err) + let [ahead, behind] = split(s:lastline(s:system(printf( + \ 'git rev-list --count --left-right HEAD...origin/%s', + \ a:spec.branch), a:spec.dir)), '\t') + if !v:shell_error && ahead + if behind + " Only mention PlugClean if diverged, otherwise it's likely to be + " pushable (and probably not that messed up). + let err = printf( + \ "Diverged from origin/%s (%d commit(s) ahead and %d commit(s) behind!\n" + \ .'Backup local changes and run PlugClean and PlugUpdate to reinstall it.', a:spec.branch, ahead, behind) + else + let err = printf("Ahead of origin/%s by %d commit(s).\n" + \ .'Cannot update until local changes are pushed.', + \ a:spec.branch, ahead) + endif + endif + endif + endif + else + let err = 'Not found' + endif + return [err, err =~# 'PlugClean'] +endfunction + +function! s:rm_rf(dir) + if isdirectory(a:dir) + call s:system((s:is_win ? 'rmdir /S /Q ' : 'rm -rf ') . s:shellesc(a:dir)) + endif +endfunction + +function! s:clean(force) + call s:prepare() + call append(0, 'Searching for invalid plugins in '.g:plug_home) + call append(1, '') + + " List of valid directories + let dirs = [] + let errs = {} + let [cnt, total] = [0, len(g:plugs)] + for [name, spec] in items(g:plugs) + if !s:is_managed(name) + call add(dirs, spec.dir) + else + let [err, clean] = s:git_validate(spec, 1) + if clean + let errs[spec.dir] = s:lines(err)[0] + else + call add(dirs, spec.dir) + endif + endif + let cnt += 1 + call s:progress_bar(2, repeat('=', cnt), total) + normal! 2G + redraw + endfor + + let allowed = {} + for dir in dirs + let allowed[s:dirpath(fnamemodify(dir, ':h:h'))] = 1 + let allowed[dir] = 1 + for child in s:glob_dir(dir) + let allowed[child] = 1 + endfor + endfor + + let todo = [] + let found = sort(s:glob_dir(g:plug_home)) + while !empty(found) + let f = remove(found, 0) + if !has_key(allowed, f) && isdirectory(f) + call add(todo, f) + call append(line('$'), '- ' . f) + if has_key(errs, f) + call append(line('$'), ' ' . errs[f]) + endif + let found = filter(found, 'stridx(v:val, f) != 0') + end + endwhile + + 4 + redraw + if empty(todo) + call append(line('$'), 'Already clean.') + else + let s:clean_count = 0 + call append(3, ['Directories to delete:', '']) + redraw! + if a:force || s:ask_no_interrupt('Delete all directories?') + call s:delete([6, line('$')], 1) + else + call setline(4, 'Cancelled.') + nnoremap d :set opfunc=delete_opg@ + nmap dd d_ + xnoremap d :call delete_op(visualmode(), 1) + echo 'Delete the lines (d{motion}) to delete the corresponding directories' + endif + endif + 4 + setlocal nomodifiable +endfunction + +function! s:delete_op(type, ...) + call s:delete(a:0 ? [line("'<"), line("'>")] : [line("'["), line("']")], 0) +endfunction + +function! s:delete(range, force) + let [l1, l2] = a:range + let force = a:force + while l1 <= l2 + let line = getline(l1) + if line =~ '^- ' && isdirectory(line[2:]) + execute l1 + redraw! + let answer = force ? 1 : s:ask('Delete '.line[2:].'?', 1) + let force = force || answer > 1 + if answer + call s:rm_rf(line[2:]) + setlocal modifiable + call setline(l1, '~'.line[1:]) + let s:clean_count += 1 + call setline(4, printf('Removed %d directories.', s:clean_count)) + setlocal nomodifiable + endif + endif + let l1 += 1 + endwhile +endfunction + +function! s:upgrade() + echo 'Downloading the latest version of vim-plug' + redraw + let tmp = tempname() + let new = tmp . '/plug.vim' + + try + let out = s:system(printf('git clone --depth 1 %s %s', s:plug_src, tmp)) + if v:shell_error + return s:err('Error upgrading vim-plug: '. out) + endif + + if readfile(s:me) ==# readfile(new) + echo 'vim-plug is already up-to-date' + return 0 + else + call rename(s:me, s:me . '.old') + call rename(new, s:me) + unlet g:loaded_plug + echo 'vim-plug has been upgraded' + return 1 + endif + finally + silent! call s:rm_rf(tmp) + endtry +endfunction + +function! s:upgrade_specs() + for spec in values(g:plugs) + let spec.frozen = get(spec, 'frozen', 0) + endfor +endfunction + +function! s:status() + call s:prepare() + call append(0, 'Checking plugins') + call append(1, '') + + let ecnt = 0 + let unloaded = 0 + let [cnt, total] = [0, len(g:plugs)] + for [name, spec] in items(g:plugs) + let is_dir = isdirectory(spec.dir) + if has_key(spec, 'uri') + if is_dir + let [err, _] = s:git_validate(spec, 1) + let [valid, msg] = [empty(err), empty(err) ? 'OK' : err] + else + let [valid, msg] = [0, 'Not found. Try PlugInstall.'] + endif + else + if is_dir + let [valid, msg] = [1, 'OK'] + else + let [valid, msg] = [0, 'Not found.'] + endif + endif + let cnt += 1 + let ecnt += !valid + " `s:loaded` entry can be missing if PlugUpgraded + if is_dir && get(s:loaded, name, -1) == 0 + let unloaded = 1 + let msg .= ' (not loaded)' + endif + call s:progress_bar(2, repeat('=', cnt), total) + call append(3, s:format_message(valid ? '-' : 'x', name, msg)) + normal! 2G + redraw + endfor + call setline(1, 'Finished. '.ecnt.' error(s).') + normal! gg + setlocal nomodifiable + if unloaded + echo "Press 'L' on each line to load plugin, or 'U' to update" + nnoremap L :call status_load(line('.')) + xnoremap L :call status_load(line('.')) + end +endfunction + +function! s:extract_name(str, prefix, suffix) + return matchstr(a:str, '^'.a:prefix.' \zs[^:]\+\ze:.*'.a:suffix.'$') +endfunction + +function! s:status_load(lnum) + let line = getline(a:lnum) + let name = s:extract_name(line, '-', '(not loaded)') + if !empty(name) + call plug#load(name) + setlocal modifiable + call setline(a:lnum, substitute(line, ' (not loaded)$', '', '')) + setlocal nomodifiable + endif +endfunction + +function! s:status_update() range + let lines = getline(a:firstline, a:lastline) + let names = filter(map(lines, 's:extract_name(v:val, "[x-]", "")'), '!empty(v:val)') + if !empty(names) + echo + execute 'PlugUpdate' join(names) + endif +endfunction + +function! s:is_preview_window_open() + silent! wincmd P + if &previewwindow + wincmd p + return 1 + endif +endfunction + +function! s:find_name(lnum) + for lnum in reverse(range(1, a:lnum)) + let line = getline(lnum) + if empty(line) + return '' + endif + let name = s:extract_name(line, '-', '') + if !empty(name) + return name + endif + endfor + return '' +endfunction + +function! s:preview_commit() + if b:plug_preview < 0 + let b:plug_preview = !s:is_preview_window_open() + endif + + let sha = matchstr(getline('.'), '^ \X*\zs[0-9a-f]\{7,9}') + if empty(sha) + return + endif + + let name = s:find_name(line('.')) + if empty(name) || !has_key(g:plugs, name) || !isdirectory(g:plugs[name].dir) + return + endif + + if exists('g:plug_pwindow') && !s:is_preview_window_open() + execute g:plug_pwindow + execute 'e' sha + else + execute 'pedit' sha + wincmd P + endif + setlocal previewwindow filetype=git buftype=nofile nobuflisted modifiable + try + let [sh, shellcmdflag, shrd] = s:chsh(1) + let cmd = 'cd '.s:shellesc(g:plugs[name].dir).' && git show --no-color --pretty=medium '.sha + if s:is_win + let batchfile = tempname().'.bat' + call writefile(['@echo off', cmd], batchfile) + let cmd = batchfile + endif + execute 'silent %!' cmd + finally + let [&shell, &shellcmdflag, &shellredir] = [sh, shellcmdflag, shrd] + if s:is_win + call delete(batchfile) + endif + endtry + setlocal nomodifiable + nnoremap q :q + wincmd p +endfunction + +function! s:section(flags) + call search('\(^[x-] \)\@<=[^:]\+:', a:flags) +endfunction + +function! s:format_git_log(line) + let indent = ' ' + let tokens = split(a:line, nr2char(1)) + if len(tokens) != 5 + return indent.substitute(a:line, '\s*$', '', '') + endif + let [graph, sha, refs, subject, date] = tokens + let tag = matchstr(refs, 'tag: [^,)]\+') + let tag = empty(tag) ? ' ' : ' ('.tag.') ' + return printf('%s%s%s%s%s (%s)', indent, graph, sha, tag, subject, date) +endfunction + +function! s:append_ul(lnum, text) + call append(a:lnum, ['', a:text, repeat('-', len(a:text))]) +endfunction + +function! s:diff() + call s:prepare() + call append(0, ['Collecting changes ...', '']) + let cnts = [0, 0] + let bar = '' + let total = filter(copy(g:plugs), 's:is_managed(v:key) && isdirectory(v:val.dir)') + call s:progress_bar(2, bar, len(total)) + for origin in [1, 0] + let plugs = reverse(sort(items(filter(copy(total), (origin ? '' : '!').'(has_key(v:val, "commit") || has_key(v:val, "tag"))')))) + if empty(plugs) + continue + endif + call s:append_ul(2, origin ? 'Pending updates:' : 'Last update:') + for [k, v] in plugs + let range = origin ? '..origin/'.v.branch : 'HEAD@{1}..' + let diff = s:system_chomp('git log --graph --color=never '.join(map(['--pretty=format:%x01%h%x01%d%x01%s%x01%cr', range], 's:shellesc(v:val)')), v.dir) + if !empty(diff) + let ref = has_key(v, 'tag') ? (' (tag: '.v.tag.')') : has_key(v, 'commit') ? (' '.v.commit) : '' + call append(5, extend(['', '- '.k.':'.ref], map(s:lines(diff), 's:format_git_log(v:val)'))) + let cnts[origin] += 1 + endif + let bar .= '=' + call s:progress_bar(2, bar, len(total)) + normal! 2G + redraw + endfor + if !cnts[origin] + call append(5, ['', 'N/A']) + endif + endfor + call setline(1, printf('%d plugin(s) updated.', cnts[0]) + \ . (cnts[1] ? printf(' %d plugin(s) have pending updates.', cnts[1]) : '')) + + if cnts[0] || cnts[1] + nnoremap :silent! call preview_commit() + nnoremap o :silent! call preview_commit() + endif + if cnts[0] + nnoremap X :call revert() + echo "Press 'X' on each block to revert the update" + endif + normal! gg + setlocal nomodifiable +endfunction + +function! s:revert() + if search('^Pending updates', 'bnW') + return + endif + + let name = s:find_name(line('.')) + if empty(name) || !has_key(g:plugs, name) || + \ input(printf('Revert the update of %s? (y/N) ', name)) !~? '^y' + return + endif + + call s:system('git reset --hard HEAD@{1} && git checkout '.s:esc(g:plugs[name].branch).' --', g:plugs[name].dir) + setlocal modifiable + normal! "_dap + setlocal nomodifiable + echo 'Reverted' +endfunction + +function! s:snapshot(force, ...) abort + call s:prepare() + setf vim + call append(0, ['" Generated by vim-plug', + \ '" '.strftime("%c"), + \ '" :source this file in vim to restore the snapshot', + \ '" or execute: vim -S snapshot.vim', + \ '', '', 'PlugUpdate!']) + 1 + let anchor = line('$') - 3 + let names = sort(keys(filter(copy(g:plugs), + \'has_key(v:val, "uri") && !has_key(v:val, "commit") && isdirectory(v:val.dir)'))) + for name in reverse(names) + let sha = s:system_chomp('git rev-parse --short HEAD', g:plugs[name].dir) + if !empty(sha) + call append(anchor, printf("silent! let g:plugs['%s'].commit = '%s'", name, sha)) + redraw + endif + endfor + + if a:0 > 0 + let fn = expand(a:1) + if filereadable(fn) && !(a:force || s:ask(a:1.' already exists. Overwrite?')) + return + endif + call writefile(getline(1, '$'), fn) + echo 'Saved as '.a:1 + silent execute 'e' s:esc(fn) + setf vim + endif +endfunction + +function! s:split_rtp() + return split(&rtp, '\\\@ -Original Author: Daniel Malea -License: Same terms as Vim itself (see |license|) - -INTRODUCTION *lldb-intro* - -The plugin provides an interface to the lldb debugger allowing for -convenient debugging sessions inside your favorite editor including -features such as breakpoints, stepping, watchpoints etc. - -The original plugin can be found here: - -http://llvm.org/svn/llvm-project/lldb/trunk/utils/vim-lldb/ - -Credit for pretty much all current functionality goes to the original -authors. Currently only minor modifications have been made to the -original plugin. - - -GETTING STARTED *lldb-start* - -To quickly get started compile (don't forget to compile with debugging -symbols) and start some program. Then open a source file belonging to the -program in vim and execute ':Lattach .' Then select some -line in the source file and execute ':Lbreakpoint' to set a breakpint at -the current line. - -Once the program reaches the specified breakpoint you will be able to -inspect state and step through the proram using the commands described below. - -COMMANDS *lldb-commands* - -The LLDB command interpreter is exposed to Vim's command mode using the -':L' prefix. Tab-completion is available and will cycle through commands. -Some commands have modified behaviour in Vim; for example, :Lbreakpoint -with no arguments will set a breakpoint at the current cursor, rather than -printing the standard help information for the LLDB command 'breakpoint'. - - *lldb-windows* - -In addition to the standard commands available under the LLDB interpreter, -there are also commands to display or hide informational debugger panes. - -Windows can be shown or hidden using the ':Lhide ' or ':Lshow ' -commands. - *lldb-:Lhide* -:Lhide [windowname] Hide informational debugger pane named 'windowname'. - - *lldb-:Lshow* -:Lshow [windowname] Show informational debugger pane named 'windowname'. - -Possible window name arguments to the Lhide and Lshow commands include: - - * backtrace - * breakpoints - * disassembly - * locals - * registers - * threads - *lldb-:Lattach* -:Lattach Attach to a process by name. - - *lldb-:Ldetach* -:Ldetach Detach from the current process. - - *lldb-:Ltarget* -:Ltarget [[create] executable] - Create a target with the specified executable. If - run with a single argument, that argument is assumed - to be a path to the executable to be debugged. - Otherwise, all arguments are passed into LLDB's command - interpreter. - - *lldb-:Lstart* -:Lstart Create a process by executing the current target - and wait for LLDB to attach. - - *lldb-:Lrun* -:Lrun Create a process by executing the current target - without waiting for LLDB to attach. - - *lldb-:Lcontinue* -:Lcontinue Continue execution of the process until the next - breakpoint is hit or the process exits. - - *lldb-:Lthread* -:Lthread Passes through to LLDB. See :Lhelp thread. - - *lldb-:Lstep* -:Lstep Step into the current function call. - - *lldb-:Lstepin* -:Lstepin Step into the current function call. - - *lldb-:Lstepinst* -:Lstepinst Step one instruction. - - *lldb-:Lstepinstover* -:Lstepinstover Step one instruction, but skip over jump or call - instructions. - - *lldb-:Lnext* -:Lnext Step to the next line. - - *lldb-:Lfinish* -:Lfinish Step out of the current function. - - *lldb-:Lbreakpoint* -:Lbreakpoint [args] When arguments are provided, the lldb breakpoint - command is invoked. If no arguments are provided, - a breakpoint at the location under the cursor. - - *lldb-:Lprint* - *lldb-:Lpo* - *lldb-:LpO* -:Lprint Aliases to the lldb print and po commands. Cursor -:Lpo word (cursor WORD for LpO) will be used when -:LpO expression omitted. - -MAPPINGS *lldb-mappings* - -There are no default mappings defined by the plugin. All commands described -above can be mapped by defining a respective variable: - - let g:lldb_map_Lframe = "f" - -This will map the Lframe command to "f". Other commands can be -mapped accordingly using 'lldb_map_' + . - - -LICENSE *lldb-license* - -Same as Vim itself. - -BUGS *lldb-bugs* - -If you run into a bug use the github issue tracker to report it: -http://github.com/gilligan/vim-lldb/issues/ - -CONTRIBUTING *lldb-contributing* - -If you want to help out you are more then welcome to do so. In fact -I am sure there are plenty of people out there that will do a better -job with this plugin than me. My C skills are more than rusty. I mostly -wanted to make this nice plugin more public and host it in a way in which -it is easy to install with your favorite plugin manager. - -Long story short: Bring on your forks and pull requests. - - vim:tw=78:sw=4:ft=help:norl: diff --git a/VimFiles/doc/misc.txt b/VimFiles/doc/misc.txt new file mode 100644 index 0000000..3476c46 --- /dev/null +++ b/VimFiles/doc/misc.txt @@ -0,0 +1,1204 @@ +*misc.txt* Miscellaneous auto-load Vim scripts + +=============================================================================== +Contents ~ + + 1. Introduction |misc-introduction| + 2. Installation |misc-installation| + 3. Function documentation |misc-function-documentation| + 1. Asynchronous Vim script evaluation |misc-asynchronous-vim-script-evaluation| + 1. The |xolox#misc#async#call()| function + 2. The |xolox#misc#async#inside_child()| function + 3. The |xolox#misc#async#callback_to_parent()| function + 4. The |xolox#misc#async#periodic_callback()| function + 2. Handling of special buffers |misc-handling-of-special-buffers| + 1. The |xolox#misc#buffer#is_empty()| function + 2. The |xolox#misc#buffer#prepare()| function + 3. The |xolox#misc#buffer#lock()| function + 4. The |xolox#misc#buffer#unlock()| function + 3. Tab completion for user defined commands |misc-tab-completion-for-user-defined-commands| + 1. The |xolox#misc#complete#keywords()| function + 4. Rate limiting for Vim's CursorHold event |misc-rate-limiting-for-vims-cursorhold-event| + 1. The |xolox#misc#cursorhold#register()| function + 2. The |xolox#misc#cursorhold#autocmd()| function + 5. String escaping functions |misc-string-escaping-functions| + 1. The |xolox#misc#escape#pattern()| function + 2. The |xolox#misc#escape#substitute()| function + 3. The |xolox#misc#escape#shell()| function + 6. Human friendly string formatting for Vim |misc-human-friendly-string-formatting-for-vim| + 1. The |xolox#misc#format#pluralize()| function + 2. The |xolox#misc#format#timestamp()| function + 7. List handling functions |misc-list-handling-functions| + 1. The |xolox#misc#list#unique()| function + 2. The |xolox#misc#list#binsert()| function + 8. Functions to interact with the user |misc-functions-to-interact-with-user| + 1. The |xolox#misc#msg#info()| function + 2. The |xolox#misc#msg#warn()| function + 3. The |xolox#misc#msg#debug()| function + 9. Integration between Vim and its environment |misc-integration-between-vim-its-environment| + 1. The |xolox#misc#open#file()| function + 2. The |xolox#misc#open#url()| function + 10. Vim and plug-in option handling |misc-vim-plug-in-option-handling| + 1. The |xolox#misc#option#get()| function + 2. The |xolox#misc#option#split()| function + 3. The |xolox#misc#option#join()| function + 4. The |xolox#misc#option#split_tags()| function + 5. The |xolox#misc#option#join_tags()| function + 6. The |xolox#misc#option#eval_tags()| function + 11. Operating system interfaces |misc-operating-system-interfaces| + 1. The |xolox#misc#os#is_mac()| function + 2. The |xolox#misc#os#is_win()| function + 3. The |xolox#misc#os#find_vim()| function + 4. The |xolox#misc#os#exec()| function + 5. The |xolox#misc#os#can_use_dll()| function + 12. Pathname manipulation functions |misc-pathname-manipulation-functions| + 1. The |xolox#misc#path#which()| function + 2. The |xolox#misc#path#split()| function + 3. The |xolox#misc#path#join()| function + 4. The |xolox#misc#path#directory_separator()| function + 5. The |xolox#misc#path#absolute()| function + 6. The |xolox#misc#path#relative()| function + 7. The |xolox#misc#path#merge()| function + 8. The |xolox#misc#path#commonprefix()| function + 9. The |xolox#misc#path#starts_with()| function + 10. The |xolox#misc#path#encode()| function + 11. The |xolox#misc#path#decode()| function + 12. The |xolox#misc#path#is_relative()| function + 13. The |xolox#misc#path#tempdir()| function + 13. Manipulation of UNIX file permissions |misc-manipulation-of-unix-file-permissions| + 1. The |xolox#misc#perm#update()| function + 2. The |xolox#misc#perm#get()| function + 3. The |xolox#misc#perm#set()| function + 14. Persist/recall Vim values from/to files |misc-persist-recall-vim-values-from-to-files| + 1. The |xolox#misc#persist#load()| function + 2. The |xolox#misc#persist#save()| function + 15. String handling |misc-string-handling| + 1. The |xolox#misc#str#slug()| function + 2. The |xolox#misc#str#ucfirst()| function + 3. The |xolox#misc#str#unescape()| function + 4. The |xolox#misc#str#compact()| function + 5. The |xolox#misc#str#trim()| function + 6. The |xolox#misc#str#indent()| function + 7. The |xolox#misc#str#dedent()| function + 16. Test runner & infrastructure for Vim plug-ins |misc-test-runner-infrastructure-for-vim-plug-ins| + 1. The |xolox#misc#test#reset()| function + 2. The |xolox#misc#test#summarize()| function + 3. The |xolox#misc#test#wrap()| function + 4. The |xolox#misc#test#passed()| function + 5. The |xolox#misc#test#failed()| function + 6. The |xolox#misc#test#assert_true()| function + 7. The |xolox#misc#test#assert_equals()| function + 8. The |xolox#misc#test#assert_same_type()| function + 17. Tests for the miscellaneous Vim scripts |tests-for-miscellaneous-vim-scripts| + 1. The |xolox#misc#tests#run()| function + 2. The |xolox#misc#tests#pattern_escaping()| function + 3. The |xolox#misc#tests#substitute_escaping()| function + 4. The |xolox#misc#tests#shell_escaping()| function + 5. The |xolox#misc#tests#making_a_list_unique()| function + 6. The |xolox#misc#tests#binary_insertion()| function + 7. The |xolox#misc#tests#getting_configuration_options()| function + 8. The |xolox#misc#tests#splitting_of_multi_valued_options()| function + 9. The |xolox#misc#tests#joining_of_multi_valued_options()| function + 10. The |xolox#misc#tests#finding_vim_on_the_search_path()| function + 11. The |xolox#misc#tests#synchronous_command_execution()| function + 12. The |xolox#misc#tests#synchronous_command_execution_with_stderr()| function + 13. The |xolox#misc#tests#synchronous_command_execution_with_raising_of_errors()| +function + 14. The |xolox#misc#tests#synchronous_command_execution_without_raising_errors()| +function + 15. The |xolox#misc#tests#asynchronous_command_execution()| function + 16. The |xolox#misc#tests#string_case_transformation()| function + 17. The |xolox#misc#tests#string_whitespace_compaction()| function + 18. The |xolox#misc#tests#string_whitespace_trimming()| function + 19. The |xolox#misc#tests#multiline_string_dedent()| function + 20. The |xolox#misc#tests#version_string_parsing()| function + 21. The |xolox#misc#tests#version_string_comparison()| function + 18. Timing of long during operations |misc-timing-of-long-during-operations| + 1. The |xolox#misc#timer#resumable()| function + 2. The |xolox#misc#timer#start()| function + 3. The |xolox#misc#timer#stop()| function + 4. The |xolox#misc#timer#force()| function + 5. The |xolox#misc#timer#convert()| function + 19. Version string handling |misc-version-string-handling| + 1. The |xolox#misc#version#parse()| function + 2. The |xolox#misc#version#at_least()| function + 4. Contact |misc-contact| + 5. License |misc-license| + 6. References |misc-references| + +=============================================================================== + *misc-introduction* +Introduction ~ + +The vim-misc plug-in contains Vim scripts that are used by most of the Vim +plug-ins I've written [1] yet don't really belong with any single one of the +plug-ins. Basically it's an extended standard library of Vim script functions +that I wrote during the development of my Vim profile and plug-ins. + +In the past these scripts were bundled with each plug-in, however that turned +out to be a maintenance nightmare for me. That's why the miscellaneous scripts +are now a proper plug-in with their own page on Vim Online. + +Because the miscellaneous scripts are no longer bundled with my Vim plug-ins, +users are now required to install the miscellaneous scripts separately. This is +unfortunate for users who are upgrading from a previous release that did bundle +the miscellaneous scripts, but I don't see any way around this. Sorry! + +=============================================================================== + *misc-installation* +Installation ~ + +Please refer to the installation instructions [2] on GitHub. + +=============================================================================== + *misc-function-documentation* +Function documentation ~ + +Below is the documentation for the functions included in the miscellaneous +scripts. Anyone is free to use these functions in their own Vim profile and/or +plug-ins. I care about backwards compatibility so won't break it without a good +reason to do so. + +For those who are curious: The function descriptions given below were extracted +from the source code of the miscellaneous scripts using the Python module +'vimdoctool.py' included in vim-tools [3]. + +The documentation of the 95 functions below was extracted from 19 Vim scripts +on April 1, 2015 at 23:39. + +------------------------------------------------------------------------------- + *misc-asynchronous-vim-script-evaluation* +Asynchronous Vim script evaluation ~ + +The |xolox#misc#async#call()| function builds on top of |xolox#misc#os#exec()| +to support asynchronous evaluation of Vim scripts. The first (and for now only) +use case is my vim-easytags [4] plug-in which has a bunch of conflicting +requirements: + +1. I want the vim-easytags [4] plug-in to be as portable as possible. + Ideally everything is implemented in Vim script because that's the only + thing I can rely on to be available for all potential users of the plug- + in! + +2. Because of point one I've been forced to implement tags file reading, + parsing, (fold case) sorting and writing in Vim script. This is fine for + small tags files but once they grow to a couple of megabytes it becomes + annoying because Vim is unresponsive during tags file updates (key + presses are fortunately buffered due to Vim's input model but that + doesn't make it a nice user experience :-). + +3. I could (and did in the past) come up with all sorts of hacks to speed + things up without switching away from Vim script, but none of them are + going to solve the fundamental problem that Vim's unresponsive hiccups + become longer as tags files grow larger. + +By now it should be clear where this is heading: _Why not handle tags file +updates in a Vim process that runs in the background without blocking the Vim +process that the user is interacting with?_ It turns out that there are quite a +few details to take care of, but with those out of the way, it might just work! +I'm actually hoping to make asynchronous updates the default mode in vim- +easytags [4]. This means I need this functionality to be as portable and robust +as possible. + +**Status:** This code has seen little testing so I wouldn't trust it too much +just yet. On the other hand, as I said, my intention is to make this +functionality as portable and robust as possible. You be the judge :-). + +------------------------------------------------------------------------------- +The *xolox#misc#async#call()* function + +Call a Vim script function asynchronously by starting a hidden Vim process in +the background. Once the function returns the hidden Vim process terminates +itself. This function takes a single argument which is a dictionary with the +following key/value pairs: + +- **function** (required): The name of the Vim function to call inside the + child process (a string). I suggest using an |autoload| function for this, + see below. + +- **arguments** (optional): A list of arguments to pass to the function. This + list is serialized to a string using |string()| and deserialized using + |eval()|. + +- **callback** (optional): The name of a Vim function to call in the parent + process when the child process has completed (a string). + +- **clientserver** (optional): If this is true (1) the child process will + notify the parent process when it has finished (the default is true). This + works using Vim's client/server support which is not always available. As a + fall back Vim's |CursorHold| automatic command is also supported (although + the effect is not quite as instantaneous :-). + +This functionality is experimental and non trivial to use, so consider yourself +warned :-). + +**Limitations** + +I'm making this functionality available in vim-misc [5] because I think it can +be useful to other plug-ins, however if you are going to use it you should be +aware of the following limitations: + +- Because of the use of multiple processes this functionality is only + suitable for 'heavy' tasks. + +- The function arguments are serialized to a string which is passed to the + hidden Vim process as a command line argument, so the amount of data you + can pass will be limited by your operating environment. + +- The hidden Vim process is explicitly isolated from the user in several ways + (see below for more details). This is to make sure that the hidden Vim + processes are fast and don't clobber the user's editing sessions in any + way. + +**Changes to how Vim normally works** + +You have to be aware that the hidden Vim process is initialized in a specific +way that is very different from your regular Vim editing sessions: + +- Your |vimrc| file is ignored using the '-u NONE' command line option. + +- Your |gvimrc| file (if you even knew it existed ;-) is ignored using the + '-U NONE' command line option. + +- Plug-in loading is skipped using the '--noplugin' command line option. + +- Swap files (see |swap-file|) are disabled using the '-n' command line + option. This makes sure asynchronous Vim processes don't disturb the user's + editing session. + +- Your |viminfo| file is ignored using the '-i NONE' command line option. + Just like with swap files this makes sure asynchronous Vim processes don't + disturb the user's editing session. + +- No-compatible mode is enabled using the '-N' command line option (usually + the existence of your vimrc script would have achieved the same effect but + since we disable loading of your vimrc we need to spell things out for + Vim). + +**Use an auto-load function** + +The function you want to call is identified by its name which has to be +defined, but I just explained above that all regular initialization is disabled +for asynchronous Vim processes, so what gives? The answer is to use an +|autoload| function. This should work fine because the asynchronous Vim process +'inherits' the value of the |'runtimepath'| option from your editing session. + +------------------------------------------------------------------------------- +The *xolox#misc#async#inside_child()* function + +Entry point inside the hidden Vim process that runs in the background. Invoked +indirectly by |xolox#misc#async#call()| because it runs a command similar to +the following: +> + vim --cmd 'call xolox#misc#async#inside_child(...)' +< +This function is responsible for calling the user defined function, capturing +exceptions and reporting the results back to the parent Vim process using Vim's +client/server support or a temporary file. + +------------------------------------------------------------------------------- +The *xolox#misc#async#callback_to_parent()* function + +When Vim was compiled with client/server support this function (in the parent +process) will be called by |xolox#misc#async#inside_child()| (in the child +process) after the user defined function has returned. This enables more or +less instant callbacks after running an asynchronous function. + +------------------------------------------------------------------------------- +The *xolox#misc#async#periodic_callback()* function + +When client/server support is not being used the vim-misc plug-in improvises: +It uses Vim's |CursorHold| event to periodically check if an asynchronous +process has written its results to one of the expected temporary files. If a +response is found the temporary file is read and deleted and then +|xolox#misc#async#callback_to_parent()| is called to process the response. + +------------------------------------------------------------------------------- + *misc-handling-of-special-buffers* +Handling of special buffers ~ + +The functions defined here make it easier to deal with special Vim buffers that +contain text generated by a Vim plug-in. For example my vim-notes plug-in [6] +generates several such buffers: + +- :RecentNotes [7] lists recently modified notes +- :ShowTaggedNotes [8] lists notes grouped by tags +- etc. + +Because the text in these buffers is generated, Vim shouldn't bother with swap +files and it should never prompt the user whether to save changes to the +generated text. + +------------------------------------------------------------------------------- +The *xolox#misc#buffer#is_empty()* function + +Checks if the current buffer is an empty, unchanged buffer which can be reused. +Returns 1 if an empty buffer is found, 0 otherwise. + +------------------------------------------------------------------------------- +The *xolox#misc#buffer#prepare()* function + +Open a special buffer, i.e. a buffer that will hold generated contents, not +directly edited by the user. The buffer can be customized by passing a +dictionary with the following key/value pairs as the first argument: + +- **name** (required): The base name of the buffer (i.e. the base name of the + file loaded in the buffer, even though it isn't really a file and nothing + is really 'loaded' :-) + +- **path** (required): The pathname of the buffer. May be relevant if |:lcd| + or |'autochdir'| is being used. + +------------------------------------------------------------------------------- +The *xolox#misc#buffer#lock()* function + +Lock a special buffer so that its contents can no longer be edited. + +------------------------------------------------------------------------------- +The *xolox#misc#buffer#unlock()* function + +Unlock a special buffer so that its content can be updated. + +------------------------------------------------------------------------------- + *misc-tab-completion-for-user-defined-commands* +Tab completion for user defined commands ~ + +------------------------------------------------------------------------------- +The *xolox#misc#complete#keywords()* function + +This function can be used to perform keyword completion for user defined Vim +commands based on the contents of the current buffer. Here's an example of how +you would use it: +> + :command -nargs=* -complete=customlist,xolox#misc#complete#keywords MyCmd call s:MyCmd() +< +------------------------------------------------------------------------------- + *misc-rate-limiting-for-vims-cursorhold-event* +Rate limiting for Vim's CursorHold event ~ + +Several of my Vim plug-ins (e.g. vim-easytags [4], vim-notes [6] and vim- +session [9]) use Vim's |CursorHold| and |CursorHoldI| events to perform +periodic tasks when the user doesn't press any keys for a couple of seconds. +These events by default fire after four seconds, this is configurable using +Vim's |'updatetime'| option. The problem that this script solves is that there +are Vim plug-ins which set the |'updatetime'| option to unreasonably low +values, thereby breaking my Vim plug-ins and probably a lot of other Vim plug- +ins out there. When users complain about this I can tell them that another Vim +plug-in is to blame, but users don't care for the difference, their Vim is +broken! So I implemented a workaround. This script enables registration of +|CursorHold| event handlers with a configurable interval (expressed in +seconds). The event handlers will be called no more than once every interval. + +------------------------------------------------------------------------------- +The *xolox#misc#cursorhold#register()* function + +Register a |CursorHold| event handler with a custom interval. This function +takes a single argument which is a dictionary with the following fields: + +- **function** (required): The name of the event handler function (a string). + +- **arguments** (optional): A list of arguments to pass to the event handler + function (defaults to an empty list). + +- **interval** (optional): The number of seconds between calls to the event + handler (defaults to 4). + +------------------------------------------------------------------------------- +The *xolox#misc#cursorhold#autocmd()* function + +The 'top level event handler' that's called by Vim whenever the |CursorHold| or +|CursorHoldI| event fires. It iterates through the event handlers registered +using |xolox#misc#cursorhold#register()| and calls each event handler at the +appropriate interval, keeping track of the time when each event handler was +last run. + +------------------------------------------------------------------------------- + *misc-string-escaping-functions* +String escaping functions ~ + +------------------------------------------------------------------------------- +The *xolox#misc#escape#pattern()* function + +Takes a single string argument and converts it into a |:substitute| / +|substitute()| pattern string that matches the given string literally. + +------------------------------------------------------------------------------- +The *xolox#misc#escape#substitute()* function + +Takes a single string argument and converts it into a |:substitute| / +|substitute()| replacement string that inserts the given string literally. + +------------------------------------------------------------------------------- +The *xolox#misc#escape#shell()* function + +Takes a single string argument and converts it into a quoted command line +argument. + +I was going to add a long rant here about Vim's |'shellslash'| option, but +really, it won't make any difference. Let's just suffice to say that I have yet +to encounter a single person out there who uses this option for its intended +purpose (running a UNIX style shell on Microsoft Windows). + +------------------------------------------------------------------------------- + *misc-human-friendly-string-formatting-for-vim* +Human friendly string formatting for Vim ~ + +------------------------------------------------------------------------------- +The *xolox#misc#format#pluralize()* function + +Concatenate a counter (the first argument, expected to be an integer) with a +singular or plural label (the second and third arguments, both expected to be +strings). + +------------------------------------------------------------------------------- +The *xolox#misc#format#timestamp()* function + +Format a time stamp (a string containing a formatted floating point number) +into a human friendly format, for example 70 seconds is phrased as "1 minute +and 10 seconds". + +------------------------------------------------------------------------------- + *misc-list-handling-functions* +List handling functions ~ + +------------------------------------------------------------------------------- +The *xolox#misc#list#unique()* function + +Remove duplicate values from the given list in-place (preserves order). + +------------------------------------------------------------------------------- +The *xolox#misc#list#binsert()* function + +Performs in-place binary insertion, which depending on your use case can be +more efficient than calling Vim's |sort()| function after each insertion (in +cases where a single, final sort is not an option). Expects three arguments: + +1. A list +2. A value to insert +3. 1 (true) when case should be ignored, 0 (false) otherwise + +------------------------------------------------------------------------------- + *misc-functions-to-interact-with-user* +Functions to interact with the user ~ + +------------------------------------------------------------------------------- +The *xolox#misc#msg#info()* function + +Show a formatted informational message to the user. + +This function has the same argument handling as Vim's |printf()| function with +one notable difference: Any arguments which are not numbers or strings are +coerced to strings using Vim's |string()| function. + +In the case of |xolox#misc#msg#info()|, automatic string coercion simply makes +the function a bit easier to use. + +The messages emitted by this function have no highlighting. Previously these +messages were highlighted using the Title group (see |hl-Title|), but it was +pointed out in pull request 16 [10] that this group shouldn't be used for +informational messages because it is meant for titles and because of this some +color schemes use colors that stand out quite a bit, causing the informational +messages to look like errors. + +------------------------------------------------------------------------------- +The *xolox#misc#msg#warn()* function + +Show a formatted warning message to the user. + +This function has the same argument handling as the |xolox#misc#msg#info()| +function. + +------------------------------------------------------------------------------- +The *xolox#misc#msg#debug()* function + +Show a formatted debugging message to the user, _if the user has enabled +increased verbosity by setting Vim's |'verbose'| option to one (1) or higher_. + +This function has the same argument handling as the |xolox#misc#msg#info()| +function. + +In the case of |xolox#misc#msg#debug()|, automatic string coercion provides +lazy evaluation in the sense that complex data structures are only converted to +strings when the user has enabled increased verbosity. + +------------------------------------------------------------------------------- + *misc-integration-between-vim-its-environment* +Integration between Vim and its environment ~ + +------------------------------------------------------------------------------- +The *xolox#misc#open#file()* function + +Given a pathname or URL as the first argument, this opens the file with the +program associated with the file type. So for example a text file might open in +Vim, an '*.html' file would probably open in your web browser and a media file +would open in a media player. + +This should work on Windows, Mac OS X and most Linux distributions. If this +fails to find a file association, you can pass one or more external commands to +try as additional arguments. For example: +> + :call xolox#misc#open#file('/path/to/my/file', 'firefox', 'google-chrome') +< +This generally shouldn't be necessary but it might come in handy now and then. + +------------------------------------------------------------------------------- +The *xolox#misc#open#url()* function + +Given a URL as the first argument, this opens the URL in your preferred or best +available web browser: + +- In GUI environments a graphical web browser will open (or a new tab will be + created in an existing window) + +- In console Vim without a GUI environment, when you have any of 'lynx', + 'links' or 'w3m' installed it will launch a command line web browser in + front of Vim (temporarily suspending Vim) + +------------------------------------------------------------------------------- + *misc-vim-plug-in-option-handling* +Vim and plug-in option handling ~ + +------------------------------------------------------------------------------- +The *xolox#misc#option#get()* function + +Expects one or two arguments: 1. The name of a variable and 2. the default +value if the variable does not exist. + +Returns the value of the variable from a buffer local variable, global variable +or the default value, depending on which is defined. + +This is used by some of my Vim plug-ins for option handling, so that users can +customize options for specific buffers. + +------------------------------------------------------------------------------- +The *xolox#misc#option#split()* function + +Given a multi-value Vim option like |'runtimepath'| this returns a list of +strings. For example: +> + :echo xolox#misc#option#split(&runtimepath) + ['/home/peter/Projects/Vim/misc', + '/home/peter/Projects/Vim/colorscheme-switcher', + '/home/peter/Projects/Vim/easytags', + ...] +< +------------------------------------------------------------------------------- +The *xolox#misc#option#join()* function + +Given a list of strings like the ones returned by |xolox#misc#option#split()|, +this joins the strings together into a single value that can be used to set a +Vim option. + +------------------------------------------------------------------------------- +The *xolox#misc#option#split_tags()* function + +Customized version of |xolox#misc#option#split()| with specialized handling for +Vim's |'tags'| option. + +------------------------------------------------------------------------------- +The *xolox#misc#option#join_tags()* function + +Customized version of |xolox#misc#option#join()| with specialized handling for +Vim's |'tags'| option. + +------------------------------------------------------------------------------- +The *xolox#misc#option#eval_tags()* function + +Evaluate Vim's |'tags'| option without looking at the file system, i.e. this +will report tags files that don't exist yet. Expects the value of the |'tags'| +option as the first argument. If the optional second argument is 1 (true) only +the first match is returned, otherwise (so by default) a list with all matches +is returned. + +------------------------------------------------------------------------------- + *misc-operating-system-interfaces* +Operating system interfaces ~ + +------------------------------------------------------------------------------- +The *xolox#misc#os#is_mac()* function + +Returns 1 (true) when on Mac OS X, 0 (false) otherwise. You would expect this +to simply check the Vim feature list, but for some obscure reason the +'/usr/bin/vim' included in Mac OS X (verified on version 10.7.5) returns 0 +(false) in response to "has('mac')", so we check the output of 'uname' to avoid +false negatives. + +------------------------------------------------------------------------------- +The *xolox#misc#os#is_win()* function + +Returns 1 (true) when on Microsoft Windows, 0 (false) otherwise. + +------------------------------------------------------------------------------- +The *xolox#misc#os#find_vim()* function + +Returns the program name of Vim as a string. On Windows and UNIX this just +|v:progname| as an absolute pathname while on Mac OS X there is some special +magic to find MacVim's executable even though it's usually not on the +executable search path. If you want, you can override the value returned from +this function by setting the global variable 'g:xolox#misc#os#vim_progname'. + +By default the choice of console Vim vs graphical Vim is made based on the +value of |v:progname|, but if you have a preference you can pass the string +'vim' or 'gvim' as the first and only argument. + +------------------------------------------------------------------------------- +The *xolox#misc#os#exec()* function + +Execute an external command (hiding the console on Microsoft Windows when my +vim-shell plug-in [11] is installed). + +Expects a dictionary with the following key/value pairs as the first argument: + +- **command** (required): The command line to execute + +- **async** (optional): set this to 1 (true) to execute the command in the + background (asynchronously) + +- **stdin** (optional): a string or list of strings with the input for the + external command + +- **check** (optional): set this to 0 (false) to disable checking of the exit + code of the external command (by default an exception will be raised when + the command fails) + +Returns a dictionary with one or more of the following key/value pairs: + +- **command** (always available): the generated command line that was used to + run the external command + +- **exit_code** (only in synchronous mode): the exit status of the external + command (an integer, zero on success) + +- **stdout** (only in synchronous mode): the output of the command on the + standard output stream (a list of strings, one for each line) + +- **stderr** (only in synchronous mode): the output of the command on the + standard error stream (as a list of strings, one for each line) + +------------------------------------------------------------------------------- +The *xolox#misc#os#can_use_dll()* function + +If a) we're on Microsoft Windows, b) the vim-shell plug-in is installed and c) +the compiled DLL included in vim-shell works, we can use the vim-shell plug-in +to execute external commands! Returns 1 (true) if we can use the DLL, 0 (false) +otherwise. + +------------------------------------------------------------------------------- + *misc-pathname-manipulation-functions* +Pathname manipulation functions ~ + +------------------------------------------------------------------------------- +The *xolox#misc#path#which()* function + +Scan the executable search path ('$PATH') for one or more external programs. +Expects one or more string arguments with program names. Returns a list with +the absolute pathnames of all found programs. Here's an example: +> + :echo xolox#misc#path#which('gvim', 'vim') + ['/usr/local/bin/gvim', + '/usr/bin/gvim', + '/usr/local/bin/vim', + '/usr/bin/vim'] +< +------------------------------------------------------------------------------- +The *xolox#misc#path#split()* function + +Split a pathname (the first and only argument) into a list of pathname +components. + +On Windows, pathnames starting with two slashes or backslashes are UNC paths +where the leading slashes are significant... In this case we split like this: + +- Input: "'//server/share/directory'" +- Result: "['//server', 'share', 'directory']" + +Everything except Windows is treated like UNIX until someone has a better +suggestion :-). In this case we split like this: + +- Input: "'/foo/bar/baz'" +- Result: "['/', 'foo', 'bar', 'baz']" + +To join a list of pathname components back into a single pathname string, use +the |xolox#misc#path#join()| function. + +------------------------------------------------------------------------------- +The *xolox#misc#path#join()* function + +Join a list of pathname components (the first and only argument) into a single +pathname string. This is the counterpart to the |xolox#misc#path#split()| +function and it expects a list of pathname components as returned by +|xolox#misc#path#split()|. + +------------------------------------------------------------------------------- +The *xolox#misc#path#directory_separator()* function + +Find the preferred directory separator for the platform and settings. + +------------------------------------------------------------------------------- +The *xolox#misc#path#absolute()* function + +Canonicalize and resolve a pathname, _regardless of whether it exists_. This is +intended to support string comparison to determine whether two pathnames point +to the same directory or file. + +------------------------------------------------------------------------------- +The *xolox#misc#path#relative()* function + +Make an absolute pathname (the first argument) relative to a directory (the +second argument). + +------------------------------------------------------------------------------- +The *xolox#misc#path#merge()* function + +Join a directory pathname and filename into a single pathname. + +------------------------------------------------------------------------------- +The *xolox#misc#path#commonprefix()* function + +Find the common prefix of path components in a list of pathnames. + +------------------------------------------------------------------------------- +The *xolox#misc#path#starts_with()* function + +Check whether the first pathname starts with the second pathname (expected to +be a directory). This does not perform a regular string comparison; first it +normalizes both pathnames, then it splits them into their pathname segments and +then it compares the segments. + +------------------------------------------------------------------------------- +The *xolox#misc#path#encode()* function + +Encode a pathname so it can be used as a filename. This uses URL encoding to +encode special characters. + +------------------------------------------------------------------------------- +The *xolox#misc#path#decode()* function + +Decode a pathname previously encoded with |xolox#misc#path#encode()|. + +------------------------------------------------------------------------------- +The *xolox#misc#path#is_relative()* function + +Returns true (1) when the pathname given as the first argument is relative, +false (0) otherwise. + +------------------------------------------------------------------------------- +The *xolox#misc#path#tempdir()* function + +Create a temporary directory and return the pathname of the directory. + +------------------------------------------------------------------------------- + *misc-manipulation-of-unix-file-permissions* +Manipulation of UNIX file permissions ~ + +Vim's |writefile()| function cannot set file permissions for newly created +files and although Vim script has a function to get file permissions (see +|getfperm()|) there is no equivalent for changing a file's permissions. + +This omission breaks the otherwise very useful idiom of updating a file by +writing its new contents to a temporary file and then renaming the temporary +file into place (which is as close as you're going to get to atomically +updating a file's contents on UNIX) because the file's permissions will not be +preserved! + +**Here's a practical example:** My vim-easytags [4] plug-in writes tags file +updates to a temporary file and renames the temporary file into place. When I +use 'sudo -s' on Ubuntu Linux it preserves my environment variables so my +'~/.vimrc' and the vim-easytags [4] plug-in are still loaded. Now when a tags +file is written the file becomes owned by root (my effective user id in the +'sudo' session). Once I leave the 'sudo' session I can no longer update my tags +file because it's now owned by root … ಠ_ಠ + +------------------------------------------------------------------------------- +The *xolox#misc#perm#update()* function + +Atomically update a file's contents while preserving the owner, group and mode. +The first argument is the pathname of the file to update (a string). The second +argument is the list of lines to be written to the file. Writes the new +contents to a temporary file and renames the temporary file into place, thereby +preventing readers from reading a partially written file. Returns 1 if the file +is successfully updated, 0 otherwise. + +Note that if |xolox#misc#perm#get()| and |xolox#misc#perm#set()| cannot be used +to preserve the file owner/group/mode the file is still updated using a rename +(for compatibility with non-UNIX systems and incompatible '/usr/bin/stat' +implementations) so in that case you can still lose the file's +owner/group/mode. + +------------------------------------------------------------------------------- +The *xolox#misc#perm#get()* function + +Get the owner, group and permissions of the pathname given as the first +argument. Returns an opaque value which you can later pass to +|xolox#misc#perm#set()|. + +------------------------------------------------------------------------------- +The *xolox#misc#perm#set()* function + +Set the permissions (the second argument) of the pathname given as the first +argument. Expects a permissions value created by |xolox#misc#perm#get()|. + +------------------------------------------------------------------------------- + *misc-persist-recall-vim-values-from-to-files* +Persist/recall Vim values from/to files ~ + +Vim's |string()| function can be used to serialize Vim script values like +numbers, strings, lists, dictionaries and composites of them to a string which +can later be evaluated using the |eval()| function to turn it back into the +original value. This Vim script provides functions to use these functions to +persist and recall Vim values from/to files. This is very useful for +communication between (possibly concurrent) Vim processes. + +------------------------------------------------------------------------------- +The *xolox#misc#persist#load()* function + +Read a Vim value like a number, string, list or dictionary from a file using +|readfile()| and |eval()|. The first argument is the filename of the file to +read (a string). The optional second argument specifies the default value which +is returned when the file can't be loaded. This function returns the loaded +value or the default value (which itself defaults to the integer 0). + +------------------------------------------------------------------------------- +The *xolox#misc#persist#save()* function + +Write a Vim value like a number, string, list or dictionary to a file using +|string()| and |writefile()|. The first argument is the filename of the file to +write (a string) and the second argument is the value to write (any value). + +This function writes the serialized value to an intermediate file which is then +renamed into place atomically. This avoids issues with concurrent processes +where for example a producer has written a partial file which is read by a +consumer before the file is complete. In this case the consumer would read a +corrupt value. The rename trick avoids this problem. + +------------------------------------------------------------------------------- + *misc-string-handling* +String handling ~ + +------------------------------------------------------------------------------- +The *xolox#misc#str#slug()* function + +Convert a string to a "slug" - something that can be safely used in filenames +and URLs without worrying about quoting/escaping of special characters. + +------------------------------------------------------------------------------- +The *xolox#misc#str#ucfirst()* function + +Uppercase the first character in a string (the first argument). + +------------------------------------------------------------------------------- +The *xolox#misc#str#unescape()* function + +Remove back slash escapes from a string (the first argument). + +------------------------------------------------------------------------------- +The *xolox#misc#str#compact()* function + +Compact whitespace in a string (the first argument). + +------------------------------------------------------------------------------- +The *xolox#misc#str#trim()* function + +Trim all whitespace from the start and end of a string (the first argument). + +------------------------------------------------------------------------------- +The *xolox#misc#str#indent()* function + +Indent all lines in a multi-line string (the first argument) with a specific +number of _space characters_ (the second argument, an integer). + +------------------------------------------------------------------------------- +The *xolox#misc#str#dedent()* function + +Remove common whitespace from a multi line string. + +------------------------------------------------------------------------------- + *misc-test-runner-infrastructure-for-vim-plug-ins* +Test runner & infrastructure for Vim plug-ins ~ + +The Vim auto-load script 'autoload/xolox/misc/test.vim' contains infrastructure +that can be used to run an automated Vim plug-in test suite. It provides a +framework for running test functions, keeping track of the test status, making +assertions and reporting test results to the user. + +------------------------------------------------------------------------------- +The *xolox#misc#test#reset()* function + +Reset counters for executed tests and passed/failed assertions. + +------------------------------------------------------------------------------- +The *xolox#misc#test#summarize()* function + +Print a summary of test results, to be interpreted interactively. + +------------------------------------------------------------------------------- +The *xolox#misc#test#wrap()* function + +Call a function in a try/catch block and prevent exceptions from bubbling. The +name of the function should be passed as the first and only argument; it should +be a string containing the name of a Vim auto-load function. + +------------------------------------------------------------------------------- +The *xolox#misc#test#passed()* function + +Record a test which succeeded. + +------------------------------------------------------------------------------- +The *xolox#misc#test#failed()* function + +Record a test which failed. + +------------------------------------------------------------------------------- +The *xolox#misc#test#assert_true()* function + +Check whether an expression is true. + +------------------------------------------------------------------------------- +The *xolox#misc#test#assert_equals()* function + +Check whether two values are the same. + +------------------------------------------------------------------------------- +The *xolox#misc#test#assert_same_type()* function + +Check whether two values are of the same type. + +------------------------------------------------------------------------------- + *tests-for-miscellaneous-vim-scripts* +Tests for the miscellaneous Vim scripts ~ + +The Vim auto-load script 'autoload/xolox/misc/tests.vim' contains the automated +test suite of the miscellaneous Vim scripts. Right now the coverage is not very +high yet, but this will improve over time. + +------------------------------------------------------------------------------- +The *xolox#misc#tests#run()* function + +Run the automated test suite of the miscellaneous Vim scripts. To be used +interactively. Intended to be safe to execute irrespective of context. + +------------------------------------------------------------------------------- +The *xolox#misc#tests#pattern_escaping()* function + +Test escaping of regular expression patterns with +|xolox#misc#escape#pattern()|. + +------------------------------------------------------------------------------- +The *xolox#misc#tests#substitute_escaping()* function + +Test escaping of substitution strings with |xolox#misc#escape#substitute()|. + +------------------------------------------------------------------------------- +The *xolox#misc#tests#shell_escaping()* function + +Test escaping of shell arguments with |xolox#misc#escape#shell()|. + +------------------------------------------------------------------------------- +The *xolox#misc#tests#making_a_list_unique()* function + +Test removing of duplicate values from lists with |xolox#misc#list#unique()|. + +------------------------------------------------------------------------------- +The *xolox#misc#tests#binary_insertion()* function + +Test the binary insertion algorithm implemented in |xolox#misc#list#binsert()|. + +------------------------------------------------------------------------------- +The *xolox#misc#tests#getting_configuration_options()* function + +Test getting of scoped plug-in configuration "options" with +|xolox#misc#option#get()|. + +------------------------------------------------------------------------------- +The *xolox#misc#tests#splitting_of_multi_valued_options()* function + +Test splitting of multi-valued Vim options with |xolox#misc#option#split()|. + +------------------------------------------------------------------------------- +The *xolox#misc#tests#joining_of_multi_valued_options()* function + +Test joining of multi-valued Vim options with |xolox#misc#option#join()|. + +------------------------------------------------------------------------------- +The *xolox#misc#tests#finding_vim_on_the_search_path()* function + +Test looking up Vim's executable on the search path using |v:progname| with +|xolox#misc#os#find_vim()|. + +------------------------------------------------------------------------------- +The *xolox#misc#tests#synchronous_command_execution()* function + +Test basic functionality of synchronous command execution with +|xolox#misc#os#exec()|. + +------------------------------------------------------------------------------- +The *xolox#misc#tests#synchronous_command_execution_with_stderr()* function + +Test basic functionality of synchronous command execution with +|xolox#misc#os#exec()| including the standard error stream (not available on +Windows when vim-shell is not installed). + +------------------------------------------------------------------------------- +The *xolox#misc#tests#synchronous_command_execution_with_raising_of_errors()* +function + +Test raising of errors during synchronous command execution with +|xolox#misc#os#exec()|. + +------------------------------------------------------------------------------- +The *xolox#misc#tests#synchronous_command_execution_without_raising_errors()* +function + +Test synchronous command execution without raising of errors with +|xolox#misc#os#exec()|. + +------------------------------------------------------------------------------- +The *xolox#misc#tests#asynchronous_command_execution()* function + +Test the basic functionality of asynchronous command execution with +|xolox#misc#os#exec()|. This runs the external command 'mkdir' and tests that +the side effect of creating the directory takes place. This might seem like a +peculiar choice, but it's one of the few 100% portable commands (Windows + +UNIX) that doesn't involve input/output streams. + +------------------------------------------------------------------------------- +The *xolox#misc#tests#string_case_transformation()* function + +Test string case transformation with |xolox#misc#str#ucfirst()|. + +------------------------------------------------------------------------------- +The *xolox#misc#tests#string_whitespace_compaction()* function + +Test compaction of whitespace in strings with |xolox#misc#str#compact()|. + +------------------------------------------------------------------------------- +The *xolox#misc#tests#string_whitespace_trimming()* function + +Test trimming of whitespace in strings with |xolox#misc#str#trim()|. + +------------------------------------------------------------------------------- +The *xolox#misc#tests#multiline_string_dedent()* function + +Test dedenting of multi-line strings with |xolox#misc#str#dedent()|. + +------------------------------------------------------------------------------- +The *xolox#misc#tests#version_string_parsing()* function + +Test parsing of version strings with |xolox#misc#version#parse()|. + +------------------------------------------------------------------------------- +The *xolox#misc#tests#version_string_comparison()* function + +Test comparison of version strings with |xolox#misc#version#at_least()|. + +------------------------------------------------------------------------------- + *misc-timing-of-long-during-operations* +Timing of long during operations ~ + +------------------------------------------------------------------------------- +The *xolox#misc#timer#resumable()* function + +Create a resumable timer object. This returns an object (a dictionary with +functions) with the following "methods": + +- 'start()' instructs the timer object to start counting elapsed time (when a + timer object is created it is not automatically started). + +- 'stop()' instructs the timer object to stop counting elapsed time. This + adds the time elapsed since 'start()' was last called to the total elapsed + time. This method will raise an error if called out of sequence. + +- 'format()' takes the total elapsed time and reports it as a string + containing a formatted floating point number. + +Timer objects are meant to accurately time short running operations so they're +dependent on Vim's |reltime()| and |reltimestr()| functions. In order to make +it possible to use timer objects in my Vim plug-ins unconditionally there's a +fall back to |localtime()| when |reltime()| is not available. In this mode the +timer objects are not very useful but at least they shouldn't raise errors. + +------------------------------------------------------------------------------- +The *xolox#misc#timer#start()* function + +Start a timer. This returns a list which can later be passed to +|xolox#misc#timer#stop()|. + +------------------------------------------------------------------------------- +The *xolox#misc#timer#stop()* function + +Show a formatted debugging message to the user, if the user has enabled +increased verbosity by setting Vim's |'verbose'| option to one (1) or higher. + +This function has the same argument handling as Vim's |printf()| function with +one difference: At the point where you want the elapsed time to be embedded, +you write '%s' and you pass the list returned by |xolox#misc#timer#start()| as +an argument. + +------------------------------------------------------------------------------- +The *xolox#misc#timer#force()* function + +Show a formatted message to the user. This function has the same argument +handling as Vim's |printf()| function with one difference: At the point where +you want the elapsed time to be embedded, you write '%s' and you pass the list +returned by |xolox#misc#timer#start()| as an argument. + +------------------------------------------------------------------------------- +The *xolox#misc#timer#convert()* function + +Convert the value returned by |xolox#misc#timer#start()| to a string +representation of the elapsed time since |xolox#misc#timer#start()| was called. +Other values are returned unmodified (this allows using it with Vim's |map()| +function). + +------------------------------------------------------------------------------- + *misc-version-string-handling* +Version string handling ~ + +------------------------------------------------------------------------------- +The *xolox#misc#version#parse()* function + +Convert a version string to a list of integers. + +------------------------------------------------------------------------------- +The *xolox#misc#version#at_least()* function + +Check whether the second version string is equal to or greater than the first +version string. Returns 1 (true) when it is, 0 (false) otherwise. + +=============================================================================== + *misc-contact* +Contact ~ + +If you have questions, bug reports, suggestions, etc. please open an issue or +pull request on GitHub [12]. Download links and documentation can be found on +the plug-in's homepage [13]. If you like the script please vote for it on Vim +Online [14]. + +=============================================================================== + *misc-license* +License ~ + +This software is licensed under the MIT license [15]. © 2015 Peter Odding +. + +=============================================================================== + *misc-references* +References ~ + +[1] http://peterodding.com/code/vim/ +[2] https://github.com/xolox/vim-misc/blob/master/INSTALL.md +[3] http://peterodding.com/code/vim/tools/ +[4] http://peterodding.com/code/vim/easytags/ +[5] http://peterodding.com/code/vim/misc/ +[6] http://peterodding.com/code/vim/notes/ +[7] http://peterodding.com/code/vim/notes/#recentnotes_command +[8] http://peterodding.com/code/vim/notes/#showtaggednotes_command +[9] http://peterodding.com/code/vim/session/ +[10] https://github.com/xolox/vim-misc/pull/16 +[11] http://peterodding.com/code/vim/shell/ +[12] http://github.com/xolox/vim-misc +[13] http://peterodding.com/code/vim/misc +[14] http://www.vim.org/scripts/script.php?script_id=4597 +[15] http://en.wikipedia.org/wiki/MIT_License + +vim: ft=help diff --git a/VimFiles/doc/omnicppcomplete.txt b/VimFiles/doc/omnicppcomplete.txt new file mode 100755 index 0000000..b11e006 --- /dev/null +++ b/VimFiles/doc/omnicppcomplete.txt @@ -0,0 +1,1078 @@ +*omnicppcomplete.txt* Plugin for C/C++ omnicompletion +*omnicppcomplete* + +Author: Vissale NEANG (fromtonrouge AT gmail DOT com) +Last Change: 26 sept. 2007 + +OmniCppComplete version 0.41 + +For Vim version 7.0 and above + +============================================================================== + +1. Overview |omnicpp-overview| +2. Downloads |omnicpp-download| +3. Installation |omnicpp-installation| +4. Options |omnicpp-options| +5. Features |omnicpp-features| +6. Limitations |omnicpp-limitations| +7. FAQ & TIPS |omnicpp-faq| +8. History |omnicpp-history| +9. Thanks |omnicpp-thanks| + +============================================================================== +1. Overview~ + *omnicpp-overview* +The purpose of this script is to provide an 'omnifunc' function for C and C++ +language. In a C++ file, while in insert mode, you can use CTRL-X CTRL-O to: + + * Complete namespaces, classes, structs and unions + * Complete attribute members and return type of functions + * Complete the "this" pointer + * Complete an object after a cast (C and C++ cast) + * Complete typedefs and anonymous types + +You can set a "may complete" behaviour to start a completion automatically +after a '.', '->' or '::'. Please see |omnicpp-may-complete| for more details. + +The script needs an |Exuberant_ctags| database to work properly. + +============================================================================== +2. Downloads~ + *omnicpp-download* +You can download the latest release of the script from this url : + + http://www.vim.org/scripts/script.php?script_id=1520 + +You can download |Exuberant_ctags| from : + + http://ctags.sourceforge.net + +============================================================================== +3. Installation~ + *omnicpp-installation* +3.1. Script installation~ + +Unzip the downloaded file in your personal |vimfiles| directory (~/.vim under +unix or %HOMEPATH%\vimfiles under windows). The 'omnifunc' will be +automatically set for C and C++ files. + +You also have to enable plugins by adding these two lines in your|.vimrc|file: > + + set nocp + filetype plugin on +< +Please see |cp| and |filetype-plugin-on| sections for more details. + +3.1.1. Files~ + +After installation you should find these files : + + after\ftplugin\cpp.vim + after\ftplugin\c.vim + + autoload\omni\common\debug.vim + \utils.vim + + autoload\omni\cpp\complete.vim + \includes.vim + \items.vim + \maycomplete.vim + \namespaces.vim + \settings.vim + \tokenizer.vim + \utils.vim + + doc\omnicppcomplete.txt + +3.2. Building the Exuberant Ctags database~ + +To extract C/C++ symbols information, the script needs an |Exuberant_ctags| +database. + +You have to build your database with at least the following options: + --c++-kinds=+p : Adds prototypes in the database for C/C++ files. + --fields=+iaS : Adds inheritance (i), access (a) and function + signatures (S) information. + --extra=+q : Adds context to the tag name. Note: Without this + option, the script cannot get class members. + +Thus to build recursively a ctags database from the current directory, the +command looks like this: +> + ctags -R --c++-kinds=+p --fields=+iaS --extra=+q . +< +You can add a map in your |.vimrc| file, eg: > + + map :!ctags -R --c++-kinds=+p --fields=+iaS --extra=+q . +< +Or you can add these options in your ctags config file (~/.ctags under unix or +%HOMEPATH%\ctags.cnf under windows) and execute the command : > + + :!ctags -R . +< +If your project contains files of other languages you may add the following +options: + --languages=c++ : Builds only the tags for C++ files. + +If your project contains macros you may also use the -I option. + +Please read the ctags help or ctags man page for more details. + +3.3. Setting the 'tags' option~ + +The default value of the option 'tags' is "./tags,tags" ("./tags,./TAGS,tags,TAGS" +when |+emacs_tags| is enabled), if you build your tag database with the cmd above, +you normally don't have to change this setting (The cmd used above generates a +file with the name "tags"). In this case your current working directory must be +the directory where the tags file reside. + +Note: When |+emacs_tags| is enabled, the script may display members twice, it's + recommended to set tags to "./tags,tags' or "./TAGS,TAGS". + +If your tags file is not named "tags" you have to add it in the 'tags' +option eg: > + + set tags+=/usr/tagsdir/mytagfile +< +You can ensure that the 'tags' option is set properly by executing the following +command: > + + :tselect MyClass +< +Where MyClass is a class of your project. This command should display all +possible tags for the type MyClass. + +3.4. Simple test~ + +Now you can do a simple test. Edit a C++ file and write the simplest case : > + + MyClass myObject; + myObject. +< +You should see class members of MyClass. + +============================================================================== +4. Options~ + *omnicpp-options* + +You can change completion behaviour by setting script options in your |.vimrc| +configuration file. + +4.1. Global scope search toggle~ + *OmniCpp_GlobalScopeSearch* + +You can enable/disable the global scope search by setting the +OmniCpp_GlobalScopeSearch option. + +Possible values are : + 0 = disabled + 1 = enabled + [default=1] > + + let OmniCpp_GlobalScopeSearch = 1 +< +4.2. Namespace search method~ + *OmniCpp_NamespaceSearch* + +You can change the 'using namespace' search behaviour by setting the +OmniCpp_NamespaceSearch option. + +Possible values are : + 0 = namespaces disabled + 1 = search namespaces in the current buffer + 2 = search namespaces in the current buffer and in included files + [default=1] > + + let OmniCpp_NamespaceSearch = 1 +< +When OmniCpp_NamespaceSearch is 2, "using namespace" declarations are parsed +in the current buffer and also in included files. To find included files, the +script use the vim env 'path', so you have to set it properly. + +Note: included files are searched with lvimgrep, thus the location list of the +current window is changed. + +Note: When the 'filetype' is "c", namespace search is always disabled even if +OmniCpp_NamespaceSearch != 0 + +4.3. Class scope completion mode~ + *OmniCpp_DisplayMode* + +When you are completing a class scope (eg: MyClass::), depending on +the current scope, you may see sometimes static, public, protected or private +members and sometimes you may see all members. By default the choice is done +automatically by the script but you can override it with the +OmniCpp_DisplayMode option. + +Note: This option can be use when you have friend classes in your project (the +script does not support friend classes). + +Possible values are : + 0 = auto + 1 = always show all members + [default=0] > + + let OmniCpp_DisplayMode = 0 +< +4.4. Show scope in abbreviation~ + *OmniCpp_ShowScopeInAbbr* + +By default, in the |omnicpp-popup| menu, you will see the scope of a match in +the last column. You can remove this column and add the scope at the beginning +of match abbreviation. +eg: + +OmniCpp_ShowScopeInAbbr = 0 + +-------------------------------------+ + |method1( f + MyNamespace::MyClass| + |_member1 m + MyNamespace::MyClass| + |_member2 m # MyNamespace::MyClass| + |_member3 m - MyNamespace::MyClass| + +-------------------------------------+ + +OmniCpp_ShowScopeInAbbr = 1 + +-------------------------------------+ + |MyNamespace::MyClass::method1( f + | + |MyNamespace::MyClass::_member1 m + | + |MyNamespace::MyClass::_member2 m # | + |MyNamespace::MyClass::_member3 m - | + +-------------------------------------+ + +Possible values are : + 0 = don't show scope in abbreviation + 1 = show scope in abbreviation and remove the last column + [default=0] > + + let OmniCpp_ShowScopeInAbbr = 0 +< +4.5. Show prototype in abbreviation~ + *OmniCpp_ShowPrototypeInAbbr* + +This option allows to display the prototype of a function in the abbreviation +part of the popup menu. + +Possible values are: + 0 = don't display prototype in abbreviation + 1 = display prototype in abbreviation + [default=0] > + + let OmniCpp_ShowPrototypeInAbbr = 0 +< +4.6. Show access~ + *OmniCpp_ShowAccess* + +This option allows to show/hide the access information ('+', '#', '-') in the +popup menu. + +Possible values are: + 0 = hide access + 1 = show access + [default=1] > + + let OmniCpp_ShowAccess = 1 + +4.7. Default using namespace list~ + *OmniCpp_DefaultNamespaces* + +When |OmniCpp_NamespaceSearch| is not 0, the script will parse using namespace +declarations in the current buffer and maybe in included files. +You can specify manually a default namespace list if you want with the +OmniCpp_DefaultNamespaces option. Each item in the list is a namespace name. +eg: If you have + + let OmniCpp_DefaultNamespaces = ["std", "MyNamespace"] + + It will be the same as inserting this declarations at the top of the + current buffer : + + using namespace std; + using namespace MyNamespace; + +This option can be use if you don't want to parse using namespace declarations +in included files and want to add namespaces that are always used in your +project. + +Possible values are : + List of String + [default=[]] > + + let OmniCpp_DefaultNamespaces = [] +< +4.8. May complete behaviour~ + *omnicpp-may-complete* + +This feature allows you to run automatically a completion after a '.', '->' +or '::'. By default, the "may complete" feature is set automatically for '.' +and '->'. The reason to not set this feature for the scope operator '::' is +sometimes you don't want to complete a namespace that contains many members. + +To enable/disable the "may complete" behaviour for dot, arrow and scope +operator, you can change the option OmniCpp_MayCompleteDot, +OmniCpp_MayCompleteArrow and OmniCpp_MayCompleteScope respectively. + + *OmniCpp_MayCompleteDot* +Possible values are : + 0 = May complete disabled for dot + 1 = May complete enabled for dot + [default=1] > + + let OmniCpp_MayCompleteDot = 1 +< + *OmniCpp_MayCompleteArrow* +Possible values are : + 0 = May complete disabled for arrow + 1 = May complete enabled for arrow + [default=1] > + + let OmniCpp_MayCompleteArrow = 1 +< + *OmniCpp_MayCompleteScope* +Possible values are : + 0 = May complete disabled for scope + 1 = May complete enabled for scope + [default=0] > + + let OmniCpp_MayCompleteScope = 0 +< + +Note: You can obviously continue to use + +4.9. Select/Don't select first popup item~ + *OmniCpp_SelectFirstItem* + +Note: This option is only used when 'completeopt' does not contain "longest". + +When 'completeopt' does not contain "longest", Vim automatically select the +first entry of the popup menu. You can change this behaviour with the +OmniCpp_SelectFirstItem option. + +Possible values are: + 0 = don't select first popup item + 1 = select first popup item (inserting it to the text) + 2 = select first popup item (without inserting it to the text) + [default=0] > + + let OmniCpp_SelectFirstItem = 0 + +4.10 Use local search function for variable definitions~ + *OmniCpp_LocalSearchDecl* + +The internal search function for variable definitions of vim requires that the +enclosing braces of the function are located in the first column. You can +change this behaviour with the OmniCpp_LocalSearchDecl option. The local +version works irrespective the position of braces. + +Possible values are: + 0 = use standard vim search function + 1 = use local search function + [default=0] > + +============================================================================== +5. Features~ + *omnicpp-features* +5.1. Popup menu~ + *omnicpp-popup* +Popup menu format: + +-------------------------------------+ + |method1( f + MyNamespace::MyClass| + |_member1 m + MyNamespace::MyClass| + |_member2 m # MyNamespace::MyClass| + |_member3 m - MyNamespace::MyClass| + +-------------------------------------+ + ^ ^ ^ ^ + (1) (2)(3) (4) + +(1) name of the symbol, when a match ends with '(' it's a function. + +(2) kind of the symbol, possible kinds are : + * c = classes + * d = macro definitions + * e = enumerators (values inside an enumeration) + * f = function definitions + * g = enumeration names + * m = class, struct, and union members + * n = namespaces + * p = function prototypes + * s = structure names + * t = typedefs + * u = union names + * v = variable definitions + +(3) access, possible values are : + * + = public + * # = protected + * - = private +Note: enumerators have no access information + +(4) scope where the symbol is defined. +Note: If the scope is empty it's a global symbol +Note: anonymous scope may end with __anon[number] +eg: If you have an anonymous enum in MyNamespace::MyClass : > + + namespace MyNamespace + { + class MyClass + { + private: + + enum + { + E_ENUM0, + E_ENUM1, + E_ENUM2 + }; + }; + } +< + +You should see : + + +----------------------------------------------+ + |E_ENUM0 e MyNamespace::MyClass::__anon1| + |E_ENUM1 e MyNamespace::MyClass::__anon1| + |E_ENUM2 e MyNamespace::MyClass::__anon1| + +----------------------------------------------+ + ^ + __anon[number] + +5.2. Global scope completion~ + +The global scope completion allows you to complete global symbols for the base +you are currently typing. The base can start with '::' or not. +Note: Global scope completion only works with a non empty base, if you run a +completion just after a '::' the completion will fail. The reason is that if +there is no base to complete the script will try to display all the tags in +the database. For small project it could be not a problem but for others you +may wait 5 minutes or more for a result. + +eg1 : > + + pthread_cr => pthread_create +< +Where pthread_create is a global function. +eg2: > + ::globa => ::global_func( + +----------------+ + |global_func( f| + |global_var1 v| + |global_var2 v| + +----------------+ +< +Where global_var1, global_var2 and global_func are global symbols +eg3: > + :: => [NO MATCH] +< +No match because a global completion from an empty base is not allowed. + +5.3. Namespace scope completion~ + +You can complete namespace members after a 'MyNamespace::'. Contrary to global +scope completion you can run a completion from an empty base. +Possible members are: + * Namespaces + * Classes + * Structs + * Unions + * Enums + * Functions + * Variables + * Typedefs + +eg: > + MyNamespace:: + +--------------------------------+ + |E_ENUM0 e MyNamespace| + |E_ENUM1 e MyNamespace| + |E_ENUM2 e MyNamespace| + |MyClass c MyNamespace| + |MyEnum g MyNamespace| + |MyStruct s MyNamespace| + |MyUnion u MyNamespace| + |SubNamespace n MyNamespace| + |doSomething( f MyNamespace| + |myVar v MyNamespace| + |something_t t MyNamespace| + +--------------------------------+ + +5.4. Class scope completion~ + +You can complete class members after a 'MyClass::'. Contrary to global scope +completion you can run a completion from an empty base. +By default, there is two behaviours for class scope completion. + + a) Completion of a base class of the current class scope + + When you are completing a base class of the current class scope, you + will see all members of this class in the popup menu. + eg: > + + class A + { + public: + enum + { + E_ENUM0, + E_ENUM1, + E_ENUM2, + }; + + void func1(); + static int _staticMember; + + private: + int _member; + }; + + class B : public A + { + public: + void doSomething(); + }; + + + void MyClassB::doSomething() + { + MyClassA:: + +---------------------------+ + |E_ENUM0 e MyClassA| + |E_ENUM1 e MyClassA| + |E_ENUM2 e MyClassA| + |func1( f + MyClassA| + |_member m - MyClassA| + |_staticMember m + MyClassA| + +---------------------------+ + } +< + + b) Completion of a non base class of the current class scope + + When you are completing a class that is not a base class of the + current class you will see only enumerators and static members. + eg: > + + class C + { + public: + void doSomething(); + }; + + void MyClassC::doSomething() + { + MyClassA:: + +---------------------------+ + |E_ENUM0 e MyClassA| + |E_ENUM1 e MyClassA| + |E_ENUM2 e MyClassA| + |_staticMember m + MyClassA| + +---------------------------+ + } +< +You can override the default behaviour by setting the +|OmniCpp_DisplayMode| option. + +5.5. Current scope completion~ + +When you start a completion from an empty instruction you are in "Current +scope completion" mode. You will see possible members of each context in +the context stack. +eg: > + void MyClass::doSomething() + { + using namespace MyNamespace; + using namespace SubNamespace; + + // You will see members of each context in the context stack + // 1) MyClass members + // 2) MyNamespace::SubNamespace members + // 3) MyNamespace members + + + +------------------------------------------+ + |_member1 m + MyClass | + |_member2 m # MyClass | + |func1( f MyNamespace::SubNamespace| + |var v MyNamespace::SubNamespace| + |func1( f MyNamespace | + |var v MyNamespace | + +------------------------------------------+ + } +< + +5.6. Class, Struct and Union members completion~ + +You can complete members of class, struct and union instances after a '->' or +'.'. +eg: > + MyClass myObject; + myObject. + +-----------------------+ + |_member1 m + MyClass | + |_member2 m # MyClass | + +-----------------------+ +< + +5.7. Attribute members and returned type completion~ + +You can complete a class member or a return type of a function. +eg: > + MyClass myObject; + + // Completion of the member _member1 + myObject._member1-> + +------------------------+ + |get( m + AnotherClass1| + +------------------------+ + + // Completion of the return type of the function get() + myObject._member1->get()-> + +--------------------------+ + |_member1 m + AnotherClass2| + |_member2 m # AnotherClass2| + |_member3 m - AnotherClass2| + +--------------------------+ + +5.8. Anonymous type completion~ + +Note: To use this feature you need at least|Exuberant_ctags| version 5.6 + +You can complete an anonymous type like this : > + struct + { + int a; + int b; + int c; + }globalVar; + + void func() + { + globalVar. + +---------------+ + |a m + __anon1| + |b m + __anon1| + |c m + __anon1| + +---------------+ + } +< +Where globalVar is a global variable of an anonymous type + +5.9. Typedef completion~ + +You can complete a typedef. The typedef is resolved recursively, thus typedef +of typedef of... may not be a problem. + +You can also complete a typedef of an anonymous type, eg : > + typedef struct + { + int a; + int b; + int c; + }something_t; + + something_t globalVar; + + void func() + { + globalVar. + +---------------+ + |a m + __anon1| + |b m + __anon1| + |c m + __anon1| + +---------------+ + } +< +Where globalVar is a global variable of typedef of an anonymous type. + +5.10. Completion of the "this" pointer~ + +You can complete the "this" pointer. +eg: > + this-> + +-----------------------+ + |_member1 m + MyClass | + |_member2 m # MyClass | + +-----------------------+ + + (*this). + +-----------------------+ + |_member1 m + MyClass | + |_member2 m # MyClass | + +-----------------------+ +< + +5.11. Completion after a cast~ + +You can complete an object after a C or C++ cast. +eg: > + // C cast style + ((AnotherStruct*)pStruct)-> + + // C++ cast style + static_cast(pStruct)-> +< + +5.12. Preview window~ + +If the 'completeopt' option contains the setting "preview" (this is the +default value), you will see a preview window during the completion. +This window shows useful information like function signature, filename where +the symbol is define etc... + +The preview window contains tag information, the list below is non exhaustive. + + * name : name of the tag + * cmd : regexp or line number that helps to find the tag + * signature : signature for prototypes and functions + * kind : kind of the tag (eg: namespace, class etc...) + * access : access information (eg: public, protected, private) + * inherits : list of base classes + * filename : filename where the tag is define + +5.13. Code tokenization~ + +When you start a completion, the current instruction is tokenized ignoring +spaces, tabs, carriage returns and comments. Thus you can complete a symbol +even if the current instruction is on multiple lines, has comments between +words etc... : +eg: this case is unrealistic but it's just for illustration > + + myObject [ 0 ]/* Why is there a comment here ?*/ + ->_member + -> +< + +============================================================================== +6. Limitations~ + *omnicpp-limitations* +Some C++ features are not supported by the script, some implemented features +may not work properly in some conditions. They are multiple reasons like a +lack of information in the database, performance issues and so on... + +6.1. Attribute members and returned type completion~ + +To work properly, the completion of attribute members and returned type of +functions depends on how you write your code in the class declaration. +Because the tags database does not contain information like return type or +type of a member, the script use the cmd information of the tag to determine +the type of an attribute member or the return type of a function. + +Thus, because the cmd is a regular expression (or line number for #define) if +you write your code like this : > + + class MyClass + { + public: + + MyOtherClass + _member; + }; +< +The type of _member will not be recognized, because the cmd will be +/^ _member;$/ and does not contain the type MyOtherClass. +The correct case should be : > + + class MyClass + { + public: + + MyOtherClass _member; + }; +< +It's the same problem for return type of function : > + + class MyClass + { + public: + + MyOtherClass + getOtherClass(); + }; +< +Here the cmd will be /^ getOtherClass();$/ and the script won't find the +return type. +The correct case should be : > + class MyClass + { + public: + + MyOtherClass getOtherClass(); + }; +< + +6.2. Static members~ + +It's the same problem as above, tags database does not contain information +about static members. The only fast way to get this information is to use the +cmd. + +6.3. Typedef~ + +It's the same problem as above, tags database does not contain information +about the type of a typedef. The script use the cmd information to resolve the +typedef. + +6.4. Restricted inheritance access~ + +Tags database contains inheritance information but unfortunately inheritance +access are not available. We could use the cmd but we often find code +indentation like this : > + + class A : + public B, + protected C, + private D + { + }; +< +Here the cmd will be /^class A :$/, we can't extract inheritance access. + +6.5. Using namespace parsing~ + +When you start a completion, using namespace declarations are parsed from the +cursor position to the first scope to detect local using namespace +declarations. After that, global using namespace declarations are parsed in the +file and included files. + +There is a limitation for global using namespace detection, for performance +issues only using namespace that starts a line will be detected. + +6.6. Friend classes~ + +Tags database does not contain information about friend classes. The script +does not support friend classes. + +6.7. Templates~ + +At the moment, |Exuberant_ctags| does not provide additional information for +templates. That's why the script does not handle templates. + +============================================================================== +7. FAQ & TIPS~ + *omnicpp-faq* + +* How to complete STL objects ? + If you have some troubles to generate a good ctags database for STL you + can try this solution : + + 1) Download SGI's STL from SGI's site + (http://www.sgi.com/tech/stl/download.html) + 2) Replace all __STL_BEGIN_NAMESPACE by "namespace std {" and + __STL_END_NAMESPACE by "}" from header and source files. (with Vim, + or with tar and sed or another tool) + 3) Run ctags and put the generated tags file in a directory eg: + ~/MyTags/stl.tags + 4) set tags+=~/MyTags/stl.tags + + The main problem is that you can't tell to ctags that + __STL_BEGIN_NAMESPACE = "namespace std {" even with the option -I. + That's why you need the step 2). + + Here is another solution if you have STL sources using _GLIBCXX_STD macro + (Tip by Nicola Bonelli) : > + + let OmniCpp_DefaultNamespaces = ["std", "_GLIBCXX_STD"] +< +* How to close automatically the preview window after a completion ? + (Tip by Kamil Renczewski) + + You can add to your |vimrc| the following lines : > + + autocmd CursorMovedI * if pumvisible() == 0|pclose|endif + autocmd InsertLeave * if pumvisible() == 0|pclose|endif +< +============================================================================== +8. History~ + *omnicpp-history* +Version O.41 + - It's recommended to update ctags to version 5.7 or higher + - The plugin is now activated for C files + - New value for OmniCpp_SelectFirstItem when the option is equal to + 2 the first item is selected without inserting it to + the text (patch from Marek Olszewski) + - Bug when completing union members fixed with ctags 5.7 + (reported by Willem-Jan de Hoog) + - New option OmniCpp_LocalSearchDecl (patch from Roland Kuck) + - Bug when tags=something,,somethingelse (reported by Tobias Pflug) + - Bug with nested structure (reported by Mikhail Daen) + - Bug where the script fails to detect the type of a variable when + the ignorecase option is on (reported by Alexey Vakhov) + - Error message when trying to use completion on a not yet saved + Vim buffer (reported by Neil Bird) + - Error message when trying to use completion on an file opened from + a tselect command (reported by Henrique Andrade) + +Version 0.4 + - The script is renamed to OmniCppComplete according to the library + script directory structure. + - OmniCpp_ClassScopeCompletionMethod renamed to OmniCpp_DisplayMode + - Fixed a bug where the quickfix list is modified after a completion. + - OmniCpp_ShowPrototypeInAbbr option added. It allows to show the + function signature in the abbreviation. + - OmniCpp_ShowAccess option added. It allows to hide the access + information in the popup menu. + - The tags database format must be a ctags 5.6 database if you want to + complete anonymous types. + - Fixed current scope detection not working properly in destructors. + - Don't show protected and private members according to the current scope. + - Overloaded functions are now filtered properly. + - New cache system using less memory. + - The class scope of a method is now resolved properly with "using + namespace" declarations. + - OmniCpp_SelectFirstItem option added. It allows to not select the first + item in the popup menu when 'completeopt' does not contain "longest". + - Fixed the bug where a "random" item in the popup menu is selected + by default when 'completeopt' does not contain "longest" option. + - The script is now split in library scripts. + - Cache added for 'using namespace' search in included files + - Default value for OmniCpp_NamespaceSearch is now 1 (search only in the + current buffer). + - Namespace search automatically disabled for C files even if + OmniCpp_NamespaceSearch != 0. + - To avoid linear search in tags files, the ignorecase option is now + disabled when getting tags datas (the user setting is restored after). + - Fixed a bug where friend functions may crash the script and also crash vim. + +Version 0.32 + - Optimizations in search members methods. + - 'May complete' behaviour is now set to default for dot '.' and arrow + '->' (mappings are set in after/ftplugin/cpp.vim) + - Fixed the option CppOmni_ShowScopeInAbbr not detected after the first + completion. + - Exceptions catched from taglist() when a tag file is corrupted. + - Fixed a bug where enumerators in global scope didn't appear in the + popup menu. + +Version 0.31 + WARNING: For this release and future releases you have to build your tags + database with this cmd : + "ctags -R --c++-kinds=+p --fields=+iaS --extra=+q ." + Please read installation instructions in the documentation for details + + - May complete added, please see installation notes for details. + - Fixed a bug where the completion works while in a comment or in a string. + +Version 0.3 + WARNING: For this release and future releases you have to build your tags + database with this cmd : + "ctags -R --c++-kinds=+p --fields=+iaS --extra=+q ." + Please read installation instructions in the documentation for details + + - Documentation added. + - Fixed a bug where typedefs were not correctly resolved in namespaces + in some cases. + - Fixed a bug where the type can not be detected when we have a decl + like this: class A {}globalVar; + - Fixed a bug in type detection where searchdecl() (gd) find + incorrect declaration instruction. + - Global scope completion now only works with non-empty base. + - Using namespace list is now parsed in the current buffer and in + included files. + - Fixed a bug where the completion fails in some cases when the user + sets the ignorecase to on + - Preview window information added + - Some improvements in type detection, the type can be properly detected + with a declaration like this: + 'Class1 *class1A = NULL, **class1B = NULL, class1C[9], class1D[1] = {};' + - Fixed a bug where parent scopes were not displayed in the popup menu + in the current scope completion mode. + - Fixed a bug where an error message was displayed when the last + instruction was not finished. + - Fixed a bug where the completion fails if a punctuator or operator was + immediately after the cursor. + - The script can now detect parent contexts at the cursor position + thanks to 'using namespace' declarations. + It can also detect ambiguous namespaces. They are not included in + the context list. + - Fixed a bug where the current scope is not properly detected when + a file starts with a comment + - Fixed a bug where the type is not detected when we have myObject[0] + - Removed the system() call in SearchMembers(), no more calls to the + ctags binary. The user have to build correctly his database with the cmd: + "ctags -R --c++-kinds=+p --fields=+iaS --extra=+q ." + - File time cache removed, the user have to rebuild his data base after a + modification. + +Version 0.22 + - Completion of unnamed type (eg: You can complete g_Var defined like + this 'struct {int a; int b;}g_Var;'). It also works for a typedef of + an unnamed type (eg: 'typedef struct {int a; int b;}t_mytype; t_mytype + g_Var;'). + - Tag file's time cache added, if a tag file has changed the global + scope result cache is cleared. + - Fixed a bug where the tokenization process enter in an infinite loop + when a file starts with '/*'. + +Version 0.21 + - Improvements on the global scope completion. + The user can now see the progression of the search and complete + matches are stored in a cache for optimization. The cache is cleared + when the tag env is modified. + - Within a class scope when the user complete an empty word, the popup + menu displays the members of the class then members of the global + scope. + - Fixed a bug where a current scope completion failed after a punctuator + or operator (eg: after a '=' or '!='). + +Version 0.2 + - Improvements in type detection (eg: when a variable is declared in a + parameter list, a catch clause, etc...) + - Code tokenization => ignoring spaces, tabs, carriage returns and comments + You can complete a code even if the instruction has bad + indentation, spaces or carriage returns between words + - Completion of class members added + - Detection of the current scope at the cursor position. + If you run a completion from en empty line, members of the current + scope are displayed. It works on the global namespace and the current + class scope (but there is not the combination of the 2 for the moment) + - Basic completion on the global namespace (very slow) + - Completion of returned type added + - this pointer completion added + - Completion after a cast added (C and C++ cast) + - Fixed a bug where the matches of the complete menu are not filtered + according to what the user typed + - Change the output of the popup menu. The type of the member + (function, member, enum etc...) is now display as a single letter. + The access information is display like this : '+' for a public member + '#' for a protected member and '-' for a private member. + The last information is the class, namespace or enum where the member is define. + +Version 0.12: + - Complete check added to the search process, you can now cancel + the search during a complete search. + +Version 0.1: + - First release + +============================================================================== +9. Thanks~ + *omnicpp-thanks* + * For advices, bug report, documentation, help, ideas : + Alexey Vakhov (bug report) + Arthur Axel "fREW" Schmidt (documentation) + Dennis Lubert (bug report) + Henrique Andrade (bug report) + Kamil Renczewski (tips) + Marek Olszewski (patch) + Markus Trenkwalder (bug report) + Martin Stubenschrott (bug report) + Mikhail Daen (bug report) + Neil Bird (bug report) + Nicola Bonelli (tips) + Robert Webb (bug report) + Roland Kuck (patch) + Tobias Pflug (bug report) + Willem-Jan de Hoog (bug report) + Yegappan Lakshmanan (advices) + + + * Darren Hiebert for Exuberant Ctags + + * All Vim devs for Vim + + * Bram Moolenaar for Vim + + * You for using this script :) + +============================================================================== + + vim:tw=78:fo=tcq2:isk=!-~,^*,^\|,^\":ts=8:ft=help:norl: