588 lines
20 KiB
VimL
588 lines
20 KiB
VimL
" 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 && startCmt<endCmt
|
|
if startCmt>0
|
|
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\<cComment\|\<cCppString\|\<cIncluded')>=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
|