home *** CD-ROM | disk | FTP | other *** search
/ vim.ftp.fu-berlin.de / 2015-02-03.vim.ftp.fu-berlin.de.tar / vim.ftp.fu-berlin.de / runtime / autoload / xmlcomplete.vim < prev    next >
Encoding:
Text File  |  2010-08-14  |  14.6 KB  |  540 lines

  1. " Vim completion script
  2. " Language:    XML
  3. " Maintainer:    Mikolaj Machowski ( mikmach AT wp DOT pl )
  4. " Last Change:    2006 Aug 15
  5. " Version: 1.9
  6. "
  7. " Changelog:
  8. " 1.9 - 2007 Aug 15
  9. "         - fix closing of namespaced tags (Johannes Weiss)
  10. " 1.8 - 2006 Jul 18
  11. "       - allow for closing of xml tags even when data file isn't available
  12.  
  13. " This function will create Dictionary with users namespace strings and values
  14. " canonical (system) names of data files.  Names should be lowercase,
  15. " descriptive to avoid any future conflicts. For example 'xhtml10s' should be
  16. " name for data of XHTML 1.0 Strict and 'xhtml10t' for XHTML 1.0 Transitional
  17. " User interface will be provided by XMLns command defined in ftplugin/xml.vim
  18. " Currently supported canonicals are:
  19. " xhtml10s - XHTML 1.0 Strict
  20. " xsl      - XSL
  21. function! xmlcomplete#CreateConnection(canonical, ...) " {{{
  22.  
  23.     " When only one argument provided treat name as default namespace (without
  24.     " 'prefix:').
  25.     if exists("a:1")
  26.         let users = a:1
  27.     else
  28.         let users = 'DEFAULT'
  29.     endif
  30.  
  31.     " Source data file. Due to suspected errors in autoload do it with
  32.     " :runtime.
  33.     " TODO: make it properly (using autoload, that is) later
  34.     exe "runtime autoload/xml/".a:canonical.".vim"
  35.  
  36.     " Remove all traces of unexisting files to return [] when trying
  37.     " omnicomplete something
  38.     " TODO: give warning about non-existing canonicals - should it be?
  39.     if !exists("g:xmldata_".a:canonical)
  40.         unlet! g:xmldata_connection
  41.         return 0
  42.     endif
  43.  
  44.     " We need to initialize Dictionary to add key-value pair
  45.     if !exists("g:xmldata_connection")
  46.         let g:xmldata_connection = {}
  47.     endif
  48.  
  49.     let g:xmldata_connection[users] = a:canonical
  50.  
  51. endfunction
  52. " }}}
  53.  
  54. function! xmlcomplete#CreateEntConnection(...) " {{{
  55.     if a:0 > 0
  56.         let g:xmldata_entconnect = a:1
  57.     else
  58.         let g:xmldata_entconnect = 'DEFAULT'
  59.     endif
  60. endfunction
  61. " }}}
  62.  
  63. function! xmlcomplete#CompleteTags(findstart, base)
  64.   if a:findstart
  65.     " locate the start of the word
  66.     let curline = line('.')
  67.     let line = getline('.')
  68.     let start = col('.') - 1
  69.     let compl_begin = col('.') - 2
  70.  
  71.     while start >= 0 && line[start - 1] =~ '\(\k\|[:.-]\)'
  72.         let start -= 1
  73.     endwhile
  74.  
  75.     if start >= 0 && line[start - 1] =~ '&'
  76.         let b:entitiescompl = 1
  77.         let b:compl_context = ''
  78.         return start
  79.     endif
  80.  
  81.     let b:compl_context = getline('.')[0:(compl_begin)]
  82.     if b:compl_context !~ '<[^>]*$'
  83.         " Look like we may have broken tag. Check previous lines. Up to
  84.         " 10?
  85.         let i = 1
  86.         while 1
  87.             let context_line = getline(curline-i)
  88.             if context_line =~ '<[^>]*$'
  89.                 " Yep, this is this line
  90.                 let context_lines = getline(curline-i, curline-1) + [b:compl_context]
  91.                 let b:compl_context = join(context_lines, ' ')
  92.                 break
  93.             elseif context_line =~ '>[^<]*$' || i == curline
  94.                 " Normal tag line, no need for completion at all
  95.                 " OR reached first line without tag at all
  96.                 let b:compl_context = ''
  97.                 break
  98.             endif
  99.             let i += 1
  100.         endwhile
  101.         " Make sure we don't have counter
  102.         unlet! i
  103.     endif
  104.     let b:compl_context = matchstr(b:compl_context, '.*\zs<.*')
  105.  
  106.     " Make sure we will have only current namespace
  107.     unlet! b:xml_namespace
  108.     let b:xml_namespace = matchstr(b:compl_context, '^<\zs\k*\ze:')
  109.     if b:xml_namespace == ''
  110.         let b:xml_namespace = 'DEFAULT'
  111.     endif
  112.  
  113.     return start
  114.  
  115.   else
  116.     " Initialize base return lists
  117.     let res = []
  118.     let res2 = []
  119.     " a:base is very short - we need context
  120.     if len(b:compl_context) == 0  && !exists("b:entitiescompl")
  121.         return []
  122.     endif
  123.     let context = matchstr(b:compl_context, '^<\zs.*')
  124.     unlet! b:compl_context
  125.     " There is no connection of namespace and data file.
  126.     if !exists("g:xmldata_connection") || g:xmldata_connection == {}
  127.         " There is still possibility we may do something - eg. close tag
  128.         let b:unaryTagsStack = "base meta link hr br param img area input col"
  129.         if context =~ '^\/'
  130.             let opentag = xmlcomplete#GetLastOpenTag("b:unaryTagsStack")
  131.             return [opentag.">"]
  132.         else
  133.             return []
  134.         endif
  135.     endif
  136.  
  137.     " Make entities completion
  138.     if exists("b:entitiescompl")
  139.         unlet! b:entitiescompl
  140.  
  141.         if !exists("g:xmldata_entconnect") || g:xmldata_entconnect == 'DEFAULT'
  142.             let values =  g:xmldata{'_'.g:xmldata_connection['DEFAULT']}['vimxmlentities']
  143.         else
  144.             let values =  g:xmldata{'_'.g:xmldata_entconnect}['vimxmlentities']
  145.         endif
  146.  
  147.         " Get only lines with entity declarations but throw out
  148.         " parameter-entities - they may be completed in future
  149.         let entdecl = filter(getline(1, "$"), 'v:val =~ "<!ENTITY\\s\\+[^%]"')
  150.  
  151.         if len(entdecl) > 0
  152.             let intent = map(copy(entdecl), 'matchstr(v:val, "<!ENTITY\\s\\+\\zs\\(\\k\\|[.-:]\\)\\+\\ze")')
  153.             let values = intent + values
  154.         endif
  155.  
  156.         if len(a:base) == 1
  157.             for m in values
  158.                 if m =~ '^'.a:base
  159.                     call add(res, m.';')
  160.                 endif
  161.             endfor
  162.             return res
  163.         else
  164.             for m in values
  165.                 if m =~? '^'.a:base
  166.                     call add(res, m.';')
  167.                 elseif m =~? a:base
  168.                     call add(res2, m.';')
  169.                 endif
  170.             endfor
  171.  
  172.             return res + res2
  173.         endif
  174.  
  175.     endif
  176.     if context =~ '>'
  177.         " Generally if context contains > it means we are outside of tag and
  178.         " should abandon action
  179.         return []
  180.     endif
  181.  
  182.     " find tags matching with "a:base"
  183.     " If a:base contains white space it is attribute.
  184.     " It could be also value of attribute...
  185.     " We have to get first word to offer
  186.     " proper completions
  187.     if context == ''
  188.         let tag = ''
  189.     else
  190.         let tag = split(context)[0]
  191.     endif
  192.     " Get rid of namespace
  193.     let tag = substitute(tag, '^'.b:xml_namespace.':', '', '')
  194.  
  195.  
  196.     " Get last word, it should be attr name
  197.     let attr = matchstr(context, '.*\s\zs.*')
  198.     " Possible situations where any prediction would be difficult:
  199.     " 1. Events attributes
  200.     if context =~ '\s'
  201.  
  202.         " If attr contains =\s*[\"'] we catched value of attribute
  203.         if attr =~ "=\s*[\"']" || attr =~ "=\s*$"
  204.             " Let do attribute specific completion
  205.             let attrname = matchstr(attr, '.*\ze\s*=')
  206.             let entered_value = matchstr(attr, ".*=\\s*[\"']\\?\\zs.*")
  207.  
  208.             if tag =~ '^[?!]'
  209.                 " Return nothing if we are inside of ! or ? tag
  210.                 return []
  211.             else
  212.                 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)
  213.                     let values = g:xmldata{'_'.g:xmldata_connection[b:xml_namespace]}[tag][1][attrname]
  214.                 else
  215.                     return []
  216.                 endif
  217.             endif
  218.  
  219.             if len(values) == 0
  220.                 return []
  221.             endif
  222.  
  223.             " We need special version of sbase
  224.             let attrbase = matchstr(context, ".*[\"']")
  225.             let attrquote = matchstr(attrbase, '.$')
  226.             if attrquote !~ "['\"]"
  227.                 let attrquoteopen = '"'
  228.                 let attrquote = '"'
  229.             else
  230.                 let attrquoteopen = ''
  231.             endif
  232.  
  233.             for m in values
  234.                 " This if is needed to not offer all completions as-is
  235.                 " alphabetically but sort them. Those beginning with entered
  236.                 " part will be as first choices
  237.                 if m =~ '^'.entered_value
  238.                     call add(res, attrquoteopen . m . attrquote.' ')
  239.                 elseif m =~ entered_value
  240.                     call add(res2, attrquoteopen . m . attrquote.' ')
  241.                 endif
  242.             endfor
  243.  
  244.             return res + res2
  245.  
  246.         endif
  247.  
  248.         if tag =~ '?xml'
  249.             " Two possible arguments for <?xml> plus variation
  250.             let attrs = ['encoding', 'version="1.0"', 'version']
  251.         elseif tag =~ '^!'
  252.             " Don't make completion at all
  253.             "
  254.             return []
  255.         else
  256.             if !has_key(g:xmldata{'_'.g:xmldata_connection[b:xml_namespace]}, tag)
  257.                 " Abandon when data file isn't complete
  258.                  return []
  259.              endif
  260.             let attrs = keys(g:xmldata{'_'.g:xmldata_connection[b:xml_namespace]}[tag][1])
  261.         endif
  262.  
  263.         for m in sort(attrs)
  264.             if m =~ '^'.attr
  265.                 call add(res, m)
  266.             elseif m =~ attr
  267.                 call add(res2, m)
  268.             endif
  269.         endfor
  270.         let menu = res + res2
  271.         let final_menu = []
  272.         if has_key(g:xmldata{'_'.g:xmldata_connection[b:xml_namespace]}, 'vimxmlattrinfo')
  273.             for i in range(len(menu))
  274.                 let item = menu[i]
  275.                 if has_key(g:xmldata{'_'.g:xmldata_connection[b:xml_namespace]}['vimxmlattrinfo'], item)
  276.                     let m_menu = g:xmldata{'_'.g:xmldata_connection[b:xml_namespace]}['vimxmlattrinfo'][item][0]
  277.                     let m_info = g:xmldata{'_'.g:xmldata_connection[b:xml_namespace]}['vimxmlattrinfo'][item][1]
  278.                 else
  279.                     let m_menu = ''
  280.                     let m_info = ''
  281.                 endif
  282.                 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.'\)$'
  283.                     let item = item
  284.                 else
  285.                     let item .= '="'
  286.                 endif
  287.                 let final_menu += [{'word':item, 'menu':m_menu, 'info':m_info}]
  288.             endfor
  289.         else
  290.             for i in range(len(menu))
  291.                 let item = menu[i]
  292.                 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.'\)$'
  293.                     let item = item
  294.                 else
  295.                     let item .= '="'
  296.                 endif
  297.                 let final_menu += [item]
  298.             endfor
  299.         endif
  300.         return final_menu
  301.  
  302.     endif
  303.     " Close tag
  304.     let b:unaryTagsStack = "base meta link hr br param img area input col"
  305.     if context =~ '^\/'
  306.         let opentag = xmlcomplete#GetLastOpenTag("b:unaryTagsStack")
  307.         return [opentag.">"]
  308.     endif
  309.  
  310.     " Complete elements of XML structure
  311.     " TODO: #REQUIRED, #IMPLIED, #FIXED, #PCDATA - but these should be detected like
  312.     " entities - in first run
  313.     " keywords: CDATA, ID, IDREF, IDREFS, ENTITY, ENTITIES, NMTOKEN, NMTOKENS
  314.     " are hardly recognizable but keep it in reserve
  315.     " also: EMPTY ANY SYSTEM PUBLIC DATA
  316.     if context =~ '^!'
  317.         let tags = ['!ELEMENT', '!DOCTYPE', '!ATTLIST', '!ENTITY', '!NOTATION', '![CDATA[', '![INCLUDE[', '![IGNORE[']
  318.  
  319.         for m in tags
  320.             if m =~ '^'.context
  321.                 let m = substitute(m, '^!\[\?', '', '')
  322.                 call add(res, m)
  323.             elseif m =~ context
  324.                 let m = substitute(m, '^!\[\?', '', '')
  325.                 call add(res2, m)
  326.             endif
  327.         endfor
  328.  
  329.         return res + res2
  330.  
  331.     endif
  332.  
  333.     " Complete text declaration
  334.     if context =~ '^?'
  335.         let tags = ['?xml']
  336.  
  337.         for m in tags
  338.             if m =~ '^'.context
  339.                 call add(res, substitute(m, '^?', '', ''))
  340.             elseif m =~ context
  341.                 call add(res, substitute(m, '^?', '', ''))
  342.             endif
  343.         endfor
  344.  
  345.         return res + res2
  346.  
  347.     endif
  348.  
  349.     " Deal with tag completion.
  350.     let opentag = xmlcomplete#GetLastOpenTag("b:unaryTagsStack")
  351.     let opentag = substitute(opentag, '^\k*:', '', '')
  352.     if opentag == ''
  353.         "return []
  354.         let tags = keys(g:xmldata{'_'.g:xmldata_connection[b:xml_namespace]})
  355.         call filter(tags, 'v:val !~ "^vimxml"')
  356.     else
  357.         if !has_key(g:xmldata{'_'.g:xmldata_connection[b:xml_namespace]}, opentag)
  358.             " Abandon when data file isn't complete
  359.             return []
  360.         endif
  361.         let tags = g:xmldata{'_'.g:xmldata_connection[b:xml_namespace]}[opentag][0]
  362.     endif
  363.  
  364.     let context = substitute(context, '^\k*:', '', '')
  365.  
  366.     for m in tags
  367.         if m =~ '^'.context
  368.             call add(res, m)
  369.         elseif m =~ context
  370.             call add(res2, m)
  371.         endif
  372.     endfor
  373.     let menu = res + res2
  374.     if b:xml_namespace == 'DEFAULT'
  375.         let xml_namespace = ''
  376.     else
  377.         let xml_namespace = b:xml_namespace.':'
  378.     endif
  379.     if has_key(g:xmldata{'_'.g:xmldata_connection[b:xml_namespace]}, 'vimxmltaginfo')
  380.         let final_menu = []
  381.         for i in range(len(menu))
  382.             let item = menu[i]
  383.             if has_key(g:xmldata{'_'.g:xmldata_connection[b:xml_namespace]}['vimxmltaginfo'], item)
  384.                 let m_menu = g:xmldata{'_'.g:xmldata_connection[b:xml_namespace]}['vimxmltaginfo'][item][0]
  385.                 let m_info = g:xmldata{'_'.g:xmldata_connection[b:xml_namespace]}['vimxmltaginfo'][item][1]
  386.             else
  387.                 let m_menu = ''
  388.                 let m_info = ''
  389.             endif
  390.             let final_menu += [{'word':xml_namespace.item, 'menu':m_menu, 'info':m_info}]
  391.         endfor
  392.     else
  393.         let final_menu = map(menu, 'xml_namespace.v:val')
  394.     endif
  395.  
  396.     return final_menu
  397.  
  398.   endif
  399. endfunction
  400.  
  401. " MM: This is severely reduced closetag.vim used with kind permission of Steven
  402. "     Mueller
  403. "     Changes: strip all comments; delete error messages; add checking for
  404. "     namespace
  405. " Author: Steven Mueller <diffusor@ugcs.caltech.edu>
  406. " Last Modified: Tue May 24 13:29:48 PDT 2005 
  407. " Version: 0.9.1
  408.  
  409. function! xmlcomplete#GetLastOpenTag(unaryTagsStack)
  410.     let linenum=line('.')
  411.     let lineend=col('.') - 1 " start: cursor position
  412.     let first=1              " flag for first line searched
  413.     let b:TagStack=''        " main stack of tags
  414.     let startInComment=s:InComment()
  415.  
  416.     if exists("b:xml_namespace")
  417.         if b:xml_namespace == 'DEFAULT'
  418.             let tagpat='</\=\(\k\|[.:-]\)\+\|/>'
  419.         else
  420.             let tagpat='</\='.b:xml_namespace.':\(\k\|[.-]\)\+\|/>'
  421.         endif
  422.     else
  423.         let tagpat='</\=\(\k\|[.:-]\)\+\|/>'
  424.     endif
  425.     while (linenum>0)
  426.         let line=getline(linenum)
  427.         if first
  428.             let line=strpart(line,0,lineend)
  429.         else
  430.             let lineend=strlen(line)
  431.         endif
  432.         let b:lineTagStack=''
  433.         let mpos=0
  434.         let b:TagCol=0
  435.         while (mpos > -1)
  436.             let mpos=matchend(line,tagpat)
  437.             if mpos > -1
  438.                 let b:TagCol=b:TagCol+mpos
  439.                 let tag=matchstr(line,tagpat)
  440.  
  441.                 if exists('b:closetag_disable_synID') || startInComment==s:InCommentAt(linenum, b:TagCol)
  442.                     let b:TagLine=linenum
  443.                     call s:Push(matchstr(tag,'[^<>]\+'),'b:lineTagStack')
  444.                 endif
  445.                 let lineend=lineend-mpos
  446.                 let line=strpart(line,mpos,lineend)
  447.             endif
  448.         endwhile
  449.         while (!s:EmptystackP('b:lineTagStack'))
  450.             let tag=s:Pop('b:lineTagStack')
  451.             if match(tag, '^/') == 0        "found end tag
  452.                 call s:Push(tag,'b:TagStack')
  453.             elseif s:EmptystackP('b:TagStack') && !s:Instack(tag, a:unaryTagsStack)    "found unclosed tag
  454.                 return tag
  455.             else
  456.                 let endtag=s:Peekstack('b:TagStack')
  457.                 if endtag == '/'.tag || endtag == '/'
  458.                     call s:Pop('b:TagStack')    "found a open/close tag pair
  459.                 elseif !s:Instack(tag, a:unaryTagsStack) "we have a mismatch error
  460.                     return ''
  461.                 endif
  462.             endif
  463.         endwhile
  464.         let linenum=linenum-1 | let first=0
  465.     endwhile
  466. return ''
  467. endfunction
  468.  
  469. function! s:InComment()
  470.     return synIDattr(synID(line('.'), col('.'), 0), 'name') =~ 'Comment\|String'
  471. endfunction
  472.  
  473. function! s:InCommentAt(line, col)
  474.     return synIDattr(synID(a:line, a:col, 0), 'name') =~ 'Comment\|String'
  475. endfunction
  476.  
  477. function! s:SetKeywords()
  478.     let g:IsKeywordBak=&iskeyword
  479.     let &iskeyword='33-255'
  480. endfunction
  481.  
  482. function! s:RestoreKeywords()
  483.     let &iskeyword=g:IsKeywordBak
  484. endfunction
  485.  
  486. function! s:Push(el, sname)
  487.     if !s:EmptystackP(a:sname)
  488.         exe 'let '.a:sname."=a:el.' '.".a:sname
  489.     else
  490.         exe 'let '.a:sname.'=a:el'
  491.     endif
  492. endfunction
  493.  
  494. function! s:EmptystackP(sname)
  495.     exe 'let stack='.a:sname
  496.     if match(stack,'^ *$') == 0
  497.         return 1
  498.     else
  499.         return 0
  500.     endif
  501. endfunction
  502.  
  503. function! s:Instack(el, sname)
  504.     exe 'let stack='.a:sname
  505.     call s:SetKeywords()
  506.     let m=match(stack, '\<'.a:el.'\>')
  507.     call s:RestoreKeywords()
  508.     if m < 0
  509.         return 0
  510.     else
  511.         return 1
  512.     endif
  513. endfunction
  514.  
  515. function! s:Peekstack(sname)
  516.     call s:SetKeywords()
  517.     exe 'let stack='.a:sname
  518.     let top=matchstr(stack, '\<.\{-1,}\>')
  519.     call s:RestoreKeywords()
  520.     return top
  521. endfunction
  522.  
  523. function! s:Pop(sname)
  524.     if s:EmptystackP(a:sname)
  525.         return ''
  526.     endif
  527.     exe 'let stack='.a:sname
  528.     call s:SetKeywords()
  529.     let loc=matchend(stack,'\<.\{-1,}\>')
  530.     exe 'let '.a:sname.'=strpart(stack, loc+1, strlen(stack))'
  531.     let top=strpart(stack, match(stack, '\<'), loc)
  532.     call s:RestoreKeywords()
  533.     return top
  534. endfunction
  535.  
  536. function! s:Clearstack(sname)
  537.     exe 'let '.a:sname."=''"
  538. endfunction
  539. " vim:set foldmethod=marker:
  540.