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