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