123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540 |
- " Vim completion script
- " Language: XML
- " Maintainer: Mikolaj Machowski ( mikmach AT wp DOT pl )
- " Last Change: 2013 Jun 29
- " Version: 1.9
- "
- " Changelog:
- " 1.9 - 2007 Aug 15
- " - fix closing of namespaced tags (Johannes Weiss)
- " 1.8 - 2006 Jul 18
- " - allow for closing of xml tags even when data file isn't available
- " This function will create Dictionary with users namespace strings and values
- " canonical (system) names of data files. Names should be lowercase,
- " descriptive to avoid any future conflicts. For example 'xhtml10s' should be
- " name for data of XHTML 1.0 Strict and 'xhtml10t' for XHTML 1.0 Transitional
- " User interface will be provided by XMLns command defined in ftplugin/xml.vim
- " Currently supported canonicals are:
- " xhtml10s - XHTML 1.0 Strict
- " xsl - XSL
- function! xmlcomplete#CreateConnection(canonical, ...) " {{{
- " When only one argument provided treat name as default namespace (without
- " 'prefix:').
- if exists("a:1")
- let users = a:1
- else
- let users = 'DEFAULT'
- endif
- " Source data file. Due to suspected errors in autoload do it with
- " :runtime.
- " TODO: make it properly (using autoload, that is) later
- exe "runtime autoload/xml/".a:canonical.".vim"
- " Remove all traces of unexisting files to return [] when trying
- " omnicomplete something
- " TODO: give warning about non-existing canonicals - should it be?
- if !exists("g:xmldata_".a:canonical)
- unlet! g:xmldata_connection
- return 0
- endif
- " We need to initialize Dictionary to add key-value pair
- if !exists("g:xmldata_connection")
- let g:xmldata_connection = {}
- endif
- let g:xmldata_connection[users] = a:canonical
- endfunction
- " }}}
- function! xmlcomplete#CreateEntConnection(...) " {{{
- if a:0 > 0
- let g:xmldata_entconnect = a:1
- else
- let g:xmldata_entconnect = 'DEFAULT'
- endif
- endfunction
- " }}}
- function! xmlcomplete#CompleteTags(findstart, base)
- if a:findstart
- " locate the start of the word
- let curline = line('.')
- let line = getline('.')
- let start = col('.') - 1
- let compl_begin = col('.') - 2
- while start >= 0 && line[start - 1] =~ '\(\k\|[:.-]\)'
- let start -= 1
- endwhile
- if start >= 0 && line[start - 1] =~ '&'
- let b:entitiescompl = 1
- let b:compl_context = ''
- return start
- endif
- let b:compl_context = getline('.')[0:(compl_begin)]
- if b:compl_context !~ '<[^>]*$'
- " Look like we may have broken tag. Check previous lines. Up to
- " 10?
- let i = 1
- while 1
- let context_line = getline(curline-i)
- if context_line =~ '<[^>]*$'
- " Yep, this is this line
- let context_lines = getline(curline-i, curline-1) + [b:compl_context]
- let b:compl_context = join(context_lines, ' ')
- break
- elseif context_line =~ '>[^<]*$' || i == curline
- " Normal tag line, no need for completion at all
- " OR reached first line without tag at all
- let b:compl_context = ''
- break
- endif
- let i += 1
- endwhile
- " Make sure we don't have counter
- unlet! i
- endif
- let b:compl_context = matchstr(b:compl_context, '.*\zs<.*')
- " Make sure we will have only current namespace
- unlet! b:xml_namespace
- let b:xml_namespace = matchstr(b:compl_context, '^<\zs\k*\ze:')
- if b:xml_namespace == ''
- let b:xml_namespace = 'DEFAULT'
- endif
- return start
- else
- " Initialize base return lists
- let res = []
- let res2 = []
- " a:base is very short - we need context
- if len(b:compl_context) == 0 && !exists("b:entitiescompl")
- return []
- endif
- let context = matchstr(b:compl_context, '^<\zs.*')
- unlet! b:compl_context
- " There is no connection of namespace and data file.
- if !exists("g:xmldata_connection") || g:xmldata_connection == {}
- " There is still possibility we may do something - eg. close tag
- let b:unaryTagsStack = "base meta link hr br param img area input col"
- if context =~ '^\/'
- let opentag = xmlcomplete#GetLastOpenTag("b:unaryTagsStack")
- return [opentag.">"]
- else
- return []
- endif
- endif
- " Make entities completion
- if exists("b:entitiescompl")
- unlet! b:entitiescompl
- if !exists("g:xmldata_entconnect") || g:xmldata_entconnect == 'DEFAULT'
- let values = g:xmldata{'_'.g:xmldata_connection['DEFAULT']}['vimxmlentities']
- else
- let values = g:xmldata{'_'.g:xmldata_entconnect}['vimxmlentities']
- endif
- " Get only lines with entity declarations but throw out
- " parameter-entities - they may be completed in future
- let entdecl = filter(getline(1, "$"), 'v:val =~ "<!ENTITY\\s\\+[^%]"')
- if len(entdecl) > 0
- let intent = map(copy(entdecl), 'matchstr(v:val, "<!ENTITY\\s\\+\\zs\\(\\k\\|[.-:]\\)\\+\\ze")')
- let values = intent + values
- endif
- if len(a:base) == 1
- for m in values
- if m =~ '^'.a:base
- call add(res, m.';')
- endif
- endfor
- return res
- else
- for m in values
- if m =~? '^'.a:base
- call add(res, m.';')
- elseif m =~? a:base
- call add(res2, m.';')
- endif
- endfor
- return res + res2
- endif
- endif
- if context =~ '>'
- " Generally if context contains > it means we are outside of tag and
- " should abandon action
- return []
- endif
- " find tags matching with "a:base"
- " If a:base contains white space it is attribute.
- " It could be also value of attribute...
- " We have to get first word to offer
- " proper completions
- if context == ''
- let tag = ''
- else
- let tag = split(context)[0]
- endif
- " Get rid of namespace
- let tag = substitute(tag, '^'.b:xml_namespace.':', '', '')
- " Get last word, it should be attr name
- let attr = matchstr(context, '.*\s\zs.*')
- " Possible situations where any prediction would be difficult:
- " 1. Events attributes
- if context =~ '\s'
- " If attr contains =\s*[\"'] we catch value of attribute
- if attr =~ "=\s*[\"']" || attr =~ "=\s*$"
- " Let do attribute specific completion
- let attrname = matchstr(attr, '.*\ze\s*=')
- let entered_value = matchstr(attr, ".*=\\s*[\"']\\?\\zs.*")
- if tag =~ '^[?!]'
- " Return nothing if we are inside of ! or ? tag
- return []
- else
- if has_key(g:xmldata{'_'.g:xmldata_connection[b:xml_namespace]}, tag) && has_key(g:xmldata{'_'.g:xmldata_connection[b:xml_namespace]}[tag][1], attrname)
- let values = g:xmldata{'_'.g:xmldata_connection[b:xml_namespace]}[tag][1][attrname]
- else
- return []
- endif
- endif
- if len(values) == 0
- return []
- endif
- " We need special version of sbase
- let attrbase = matchstr(context, ".*[\"']")
- let attrquote = matchstr(attrbase, '.$')
- if attrquote !~ "['\"]"
- let attrquoteopen = '"'
- let attrquote = '"'
- else
- let attrquoteopen = ''
- endif
- for m in values
- " This if is needed to not offer all completions as-is
- " alphabetically but sort them. Those beginning with entered
- " part will be as first choices
- if m =~ '^'.entered_value
- call add(res, attrquoteopen . m . attrquote.' ')
- elseif m =~ entered_value
- call add(res2, attrquoteopen . m . attrquote.' ')
- endif
- endfor
- return res + res2
- endif
- if tag =~ '?xml'
- " Two possible arguments for <?xml> plus variation
- let attrs = ['encoding', 'version="1.0"', 'version']
- elseif tag =~ '^!'
- " Don't make completion at all
- "
- return []
- else
- if !has_key(g:xmldata{'_'.g:xmldata_connection[b:xml_namespace]}, tag)
- " Abandon when data file isn't complete
- return []
- endif
- let attrs = keys(g:xmldata{'_'.g:xmldata_connection[b:xml_namespace]}[tag][1])
- endif
- for m in sort(attrs)
- if m =~ '^'.attr
- call add(res, m)
- elseif m =~ attr
- call add(res2, m)
- endif
- endfor
- let menu = res + res2
- let final_menu = []
- if has_key(g:xmldata{'_'.g:xmldata_connection[b:xml_namespace]}, 'vimxmlattrinfo')
- for i in range(len(menu))
- let item = menu[i]
- if has_key(g:xmldata{'_'.g:xmldata_connection[b:xml_namespace]}['vimxmlattrinfo'], item)
- let m_menu = g:xmldata{'_'.g:xmldata_connection[b:xml_namespace]}['vimxmlattrinfo'][item][0]
- let m_info = g:xmldata{'_'.g:xmldata_connection[b:xml_namespace]}['vimxmlattrinfo'][item][1]
- else
- let m_menu = ''
- let m_info = ''
- endif
- if tag !~ '^[?!]' && len(g:xmldata{'_'.g:xmldata_connection[b:xml_namespace]}[tag][1][item]) > 0 && g:xmldata{'_'.g:xmldata_connection[b:xml_namespace]}[tag][1][item][0] =~ '^\(BOOL\|'.item.'\)$'
- let item = item
- else
- let item .= '="'
- endif
- let final_menu += [{'word':item, 'menu':m_menu, 'info':m_info}]
- endfor
- else
- for i in range(len(menu))
- let item = menu[i]
- if tag !~ '^[?!]' && len(g:xmldata{'_'.g:xmldata_connection[b:xml_namespace]}[tag][1][item]) > 0 && g:xmldata{'_'.g:xmldata_connection[b:xml_namespace]}[tag][1][item][0] =~ '^\(BOOL\|'.item.'\)$'
- let item = item
- else
- let item .= '="'
- endif
- let final_menu += [item]
- endfor
- endif
- return final_menu
- endif
- " Close tag
- let b:unaryTagsStack = "base meta link hr br param img area input col"
- if context =~ '^\/'
- let opentag = xmlcomplete#GetLastOpenTag("b:unaryTagsStack")
- return [opentag.">"]
- endif
- " Complete elements of XML structure
- " TODO: #REQUIRED, #IMPLIED, #FIXED, #PCDATA - but these should be detected like
- " entities - in first run
- " keywords: CDATA, ID, IDREF, IDREFS, ENTITY, ENTITIES, NMTOKEN, NMTOKENS
- " are hardly recognizable but keep it in reserve
- " also: EMPTY ANY SYSTEM PUBLIC DATA
- if context =~ '^!'
- let tags = ['!ELEMENT', '!DOCTYPE', '!ATTLIST', '!ENTITY', '!NOTATION', '![CDATA[', '![INCLUDE[', '![IGNORE[']
- for m in tags
- if m =~ '^'.context
- let m = substitute(m, '^!\[\?', '', '')
- call add(res, m)
- elseif m =~ context
- let m = substitute(m, '^!\[\?', '', '')
- call add(res2, m)
- endif
- endfor
- return res + res2
- endif
- " Complete text declaration
- if context =~ '^?'
- let tags = ['?xml']
- for m in tags
- if m =~ '^'.context
- call add(res, substitute(m, '^?', '', ''))
- elseif m =~ context
- call add(res, substitute(m, '^?', '', ''))
- endif
- endfor
- return res + res2
- endif
- " Deal with tag completion.
- let opentag = xmlcomplete#GetLastOpenTag("b:unaryTagsStack")
- let opentag = substitute(opentag, '^\k*:', '', '')
- if opentag == ''
- "return []
- let tags = keys(g:xmldata{'_'.g:xmldata_connection[b:xml_namespace]})
- call filter(tags, 'v:val !~ "^vimxml"')
- else
- if !has_key(g:xmldata{'_'.g:xmldata_connection[b:xml_namespace]}, opentag)
- " Abandon when data file isn't complete
- return []
- endif
- let tags = g:xmldata{'_'.g:xmldata_connection[b:xml_namespace]}[opentag][0]
- endif
- let context = substitute(context, '^\k*:', '', '')
- for m in tags
- if m =~ '^'.context
- call add(res, m)
- elseif m =~ context
- call add(res2, m)
- endif
- endfor
- let menu = res + res2
- if b:xml_namespace == 'DEFAULT'
- let xml_namespace = ''
- else
- let xml_namespace = b:xml_namespace.':'
- endif
- if has_key(g:xmldata{'_'.g:xmldata_connection[b:xml_namespace]}, 'vimxmltaginfo')
- let final_menu = []
- for i in range(len(menu))
- let item = menu[i]
- if has_key(g:xmldata{'_'.g:xmldata_connection[b:xml_namespace]}['vimxmltaginfo'], item)
- let m_menu = g:xmldata{'_'.g:xmldata_connection[b:xml_namespace]}['vimxmltaginfo'][item][0]
- let m_info = g:xmldata{'_'.g:xmldata_connection[b:xml_namespace]}['vimxmltaginfo'][item][1]
- else
- let m_menu = ''
- let m_info = ''
- endif
- let final_menu += [{'word':xml_namespace.item, 'menu':m_menu, 'info':m_info}]
- endfor
- else
- let final_menu = map(menu, 'xml_namespace.v:val')
- endif
- return final_menu
- endif
- endfunction
- " MM: This is severely reduced closetag.vim used with kind permission of Steven
- " Mueller
- " Changes: strip all comments; delete error messages; add checking for
- " namespace
- " Author: Steven Mueller <diffusor@ugcs.caltech.edu>
- " Last Modified: Tue May 24 13:29:48 PDT 2005
- " Version: 0.9.1
- function! xmlcomplete#GetLastOpenTag(unaryTagsStack)
- let linenum=line('.')
- let lineend=col('.') - 1 " start: cursor position
- let first=1 " flag for first line searched
- let b:TagStack='' " main stack of tags
- let startInComment=s:InComment()
- if exists("b:xml_namespace")
- if b:xml_namespace == 'DEFAULT'
- let tagpat='</\=\(\k\|[.:-]\)\+\|/>'
- else
- let tagpat='</\='.b:xml_namespace.':\(\k\|[.-]\)\+\|/>'
- endif
- else
- let tagpat='</\=\(\k\|[.:-]\)\+\|/>'
- endif
- while (linenum>0)
- let line=getline(linenum)
- if first
- let line=strpart(line,0,lineend)
- else
- let lineend=strlen(line)
- endif
- let b:lineTagStack=''
- let mpos=0
- let b:TagCol=0
- while (mpos > -1)
- let mpos=matchend(line,tagpat)
- if mpos > -1
- let b:TagCol=b:TagCol+mpos
- let tag=matchstr(line,tagpat)
- if exists('b:closetag_disable_synID') || startInComment==s:InCommentAt(linenum, b:TagCol)
- let b:TagLine=linenum
- call s:Push(matchstr(tag,'[^<>]\+'),'b:lineTagStack')
- endif
- let lineend=lineend-mpos
- let line=strpart(line,mpos,lineend)
- endif
- endwhile
- while (!s:EmptystackP('b:lineTagStack'))
- let tag=s:Pop('b:lineTagStack')
- if match(tag, '^/') == 0 "found end tag
- call s:Push(tag,'b:TagStack')
- elseif s:EmptystackP('b:TagStack') && !s:Instack(tag, a:unaryTagsStack) "found unclosed tag
- return tag
- else
- let endtag=s:Peekstack('b:TagStack')
- if endtag == '/'.tag || endtag == '/'
- call s:Pop('b:TagStack') "found a open/close tag pair
- elseif !s:Instack(tag, a:unaryTagsStack) "we have a mismatch error
- return ''
- endif
- endif
- endwhile
- let linenum=linenum-1 | let first=0
- endwhile
- return ''
- endfunction
- function! s:InComment()
- return synIDattr(synID(line('.'), col('.'), 0), 'name') =~ 'Comment\|String'
- endfunction
- function! s:InCommentAt(line, col)
- return synIDattr(synID(a:line, a:col, 0), 'name') =~ 'Comment\|String'
- endfunction
- function! s:SetKeywords()
- let s:IsKeywordBak=&l:iskeyword
- let &l:iskeyword='33-255'
- endfunction
- function! s:RestoreKeywords()
- let &l:iskeyword=s:IsKeywordBak
- endfunction
- function! s:Push(el, sname)
- if !s:EmptystackP(a:sname)
- exe 'let '.a:sname."=a:el.' '.".a:sname
- else
- exe 'let '.a:sname.'=a:el'
- endif
- endfunction
- function! s:EmptystackP(sname)
- exe 'let stack='.a:sname
- if match(stack,'^ *$') == 0
- return 1
- else
- return 0
- endif
- endfunction
- function! s:Instack(el, sname)
- exe 'let stack='.a:sname
- call s:SetKeywords()
- let m=match(stack, '\<'.a:el.'\>')
- call s:RestoreKeywords()
- if m < 0
- return 0
- else
- return 1
- endif
- endfunction
- function! s:Peekstack(sname)
- call s:SetKeywords()
- exe 'let stack='.a:sname
- let top=matchstr(stack, '\<.\{-1,}\>')
- call s:RestoreKeywords()
- return top
- endfunction
- function! s:Pop(sname)
- if s:EmptystackP(a:sname)
- return ''
- endif
- exe 'let stack='.a:sname
- call s:SetKeywords()
- let loc=matchend(stack,'\<.\{-1,}\>')
- exe 'let '.a:sname.'=strpart(stack, loc+1, strlen(stack))'
- let top=strpart(stack, match(stack, '\<'), loc)
- call s:RestoreKeywords()
- return top
- endfunction
- function! s:Clearstack(sname)
- exe 'let '.a:sname."=''"
- endfunction
- " vim:set foldmethod=marker:
|