blob: 60ed919e1f6c79bf09619d45a76542c5995db55b [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 Moolenaar18144c82006-04-12 21:52:12 +00004" Last Change: 2006 Apr 12
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
Bram Moolenaar910f66f2006-04-05 20:41:53 +000010" User interface will be provided by XMLns command defined in ftplugin/xml.vim
Bram Moolenaara5792f52005-11-23 21:25:05 +000011" Currently supported canonicals are:
12" xhtml10s - XHTML 1.0 Strict
13" xsl - XSL
Bram Moolenaar18144c82006-04-12 21:52:12 +000014function! xmlcomplete#CreateConnection(canonical, ...) " {{{
Bram Moolenaara5792f52005-11-23 21:25:05 +000015
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
Bram Moolenaar18144c82006-04-12 21:52:12 +000045" }}}
Bram Moolenaara5792f52005-11-23 21:25:05 +000046
Bram Moolenaar18144c82006-04-12 21:52:12 +000047function! xmlcomplete#CreateEntConnection(...) " {{{
Bram Moolenaara5792f52005-11-23 21:25:05 +000048 if a:0 > 0
49 let g:xmldata_entconnect = a:1
50 else
51 let g:xmldata_entconnect = 'DEFAULT'
52 endif
53endfunction
Bram Moolenaar18144c82006-04-12 21:52:12 +000054" }}}
Bram Moolenaara5792f52005-11-23 21:25:05 +000055
56function! xmlcomplete#CompleteTags(findstart, base)
57 if a:findstart
58 " locate the start of the word
Bram Moolenaard12f5c12006-01-25 22:10:52 +000059 let curline = line('.')
Bram Moolenaara5792f52005-11-23 21:25:05 +000060 let line = getline('.')
61 let start = col('.') - 1
62 let compl_begin = col('.') - 2
63
64 while start >= 0 && line[start - 1] =~ '\(\k\|[:.-]\)'
65 let start -= 1
66 endwhile
67
68 if start >= 0 && line[start - 1] =~ '&'
69 let b:entitiescompl = 1
70 let b:compl_context = ''
71 return start
72 endif
73
74 let b:compl_context = getline('.')[0:(compl_begin)]
Bram Moolenaard12f5c12006-01-25 22:10:52 +000075 if b:compl_context !~ '<[^>]*$'
76 " Look like we may have broken tag. Check previous lines. Up to
77 " 10?
78 let i = 1
79 while 1
80 let context_line = getline(curline-i)
81 if context_line =~ '<[^>]*$'
82 " Yep, this is this line
83 let context_lines = getline(curline-i, curline)
84 let b:compl_context = join(context_lines, ' ')
85 break
Bram Moolenaarc15ef302006-03-19 22:11:16 +000086 elseif context_line =~ '>[^<]*$' || i == curline
Bram Moolenaard12f5c12006-01-25 22:10:52 +000087 " Normal tag line, no need for completion at all
Bram Moolenaarc15ef302006-03-19 22:11:16 +000088 " OR reached first line without tag at all
Bram Moolenaard12f5c12006-01-25 22:10:52 +000089 let b:compl_context = ''
90 break
91 endif
92 let i += 1
93 endwhile
94 " Make sure we don't have counter
95 unlet! i
96 endif
97 let b:compl_context = matchstr(b:compl_context, '.*\zs<.*')
Bram Moolenaara5792f52005-11-23 21:25:05 +000098
99 " Make sure we will have only current namespace
100 unlet! b:xml_namespace
Bram Moolenaard12f5c12006-01-25 22:10:52 +0000101 let b:xml_namespace = matchstr(b:compl_context, '^<\zs\k*\ze:')
Bram Moolenaara5792f52005-11-23 21:25:05 +0000102 if b:xml_namespace == ''
103 let b:xml_namespace = 'DEFAULT'
104 endif
105
106 return start
107
108 else
Bram Moolenaar18144c82006-04-12 21:52:12 +0000109 " There is no connection of namespace and data file. Abandon action
Bram Moolenaara5792f52005-11-23 21:25:05 +0000110 if !exists("g:xmldata_connection") || g:xmldata_connection == {}
111 return []
112 endif
113 " Initialize base return lists
114 let res = []
115 let res2 = []
116 " a:base is very short - we need context
Bram Moolenaard12f5c12006-01-25 22:10:52 +0000117 if len(b:compl_context) == 0 && !exists("b:entitiescompl")
118 return []
119 endif
120 let context = matchstr(b:compl_context, '^<\zs.*')
Bram Moolenaara5792f52005-11-23 21:25:05 +0000121 unlet! b:compl_context
122
123 " Make entities completion
124 if exists("b:entitiescompl")
125 unlet! b:entitiescompl
126
127 if !exists("g:xmldata_entconnect") || g:xmldata_entconnect == 'DEFAULT'
128 let values = g:xmldata{'_'.g:xmldata_connection['DEFAULT']}['vimxmlentities']
129 else
130 let values = g:xmldata{'_'.g:xmldata_entconnect}['vimxmlentities']
131 endif
132
133 " Get only lines with entity declarations but throw out
134 " parameter-entities - they may be completed in future
135 let entdecl = filter(getline(1, "$"), 'v:val =~ "<!ENTITY\\s\\+[^%]"')
136
137 if len(entdecl) > 0
138 let intent = map(copy(entdecl), 'matchstr(v:val, "<!ENTITY\\s\\+\\zs\\(\\k\\|[.-:]\\)\\+\\ze")')
139 let values = intent + values
140 endif
141
Bram Moolenaard12f5c12006-01-25 22:10:52 +0000142 if len(a:base) == 1
143 for m in values
144 if m =~ '^'.a:base
145 call add(res, m.';')
146 endif
147 endfor
148 return res
149 else
150 for m in values
151 if m =~? '^'.a:base
152 call add(res, m.';')
153 elseif m =~? a:base
154 call add(res2, m.';')
155 endif
156 endfor
Bram Moolenaara5792f52005-11-23 21:25:05 +0000157
Bram Moolenaard12f5c12006-01-25 22:10:52 +0000158 return res + res2
159 endif
Bram Moolenaara5792f52005-11-23 21:25:05 +0000160
161 endif
162 if context =~ '>'
163 " Generally if context contains > it means we are outside of tag and
164 " should abandon action
165 return []
166 endif
167
168 " find tags matching with "a:base"
169 " If a:base contains white space it is attribute.
170 " It could be also value of attribute...
171 " We have to get first word to offer
172 " proper completions
173 if context == ''
174 let tag = ''
175 else
176 let tag = split(context)[0]
177 endif
178 " Get rid of namespace
179 let tag = substitute(tag, '^'.b:xml_namespace.':', '', '')
180
181
182 " Get last word, it should be attr name
183 let attr = matchstr(context, '.*\s\zs.*')
184 " Possible situations where any prediction would be difficult:
185 " 1. Events attributes
186 if context =~ '\s'
187
188 " If attr contains =\s*[\"'] we catched value of attribute
189 if attr =~ "=\s*[\"']"
190 " Let do attribute specific completion
191 let attrname = matchstr(attr, '.*\ze\s*=')
192 let entered_value = matchstr(attr, ".*=\\s*[\"']\\zs.*")
193
194 if tag =~ '^[?!]'
195 " Return nothing if we are inside of ! or ? tag
196 return []
197 else
198 let values = g:xmldata{'_'.g:xmldata_connection[b:xml_namespace]}[tag][1][attrname]
199 endif
200
201 if len(values) == 0
202 return []
203 endif
204
205 " We need special version of sbase
206 let attrbase = matchstr(context, ".*[\"']")
207 let attrquote = matchstr(attrbase, '.$')
208
209 for m in values
210 " This if is needed to not offer all completions as-is
211 " alphabetically but sort them. Those beginning with entered
212 " part will be as first choices
213 if m =~ '^'.entered_value
214 call add(res, m . attrquote.' ')
215 elseif m =~ entered_value
216 call add(res2, m . attrquote.' ')
217 endif
218 endfor
219
220 return res + res2
221
222 endif
223
224 if tag =~ '?xml'
225 " Two possible arguments for <?xml> plus variation
226 let attrs = ['encoding', 'version="1.0"', 'version']
227 elseif tag =~ '^!'
228 " Don't make completion at all
Bram Moolenaar910f66f2006-04-05 20:41:53 +0000229 "
Bram Moolenaara5792f52005-11-23 21:25:05 +0000230 return []
231 else
Bram Moolenaar910f66f2006-04-05 20:41:53 +0000232 if !has_key(g:xmldata{'_'.g:xmldata_connection[b:xml_namespace]}, tag)
233 " Abandon when data file isn't complete
234 return []
235 endif
Bram Moolenaara5792f52005-11-23 21:25:05 +0000236 let attrs = keys(g:xmldata{'_'.g:xmldata_connection[b:xml_namespace]}[tag][1])
237 endif
238
239 for m in sort(attrs)
240 if m =~ '^'.attr
Bram Moolenaar1d2ba7f2006-02-14 22:29:30 +0000241 call add(res, m)
Bram Moolenaara5792f52005-11-23 21:25:05 +0000242 elseif m =~ attr
Bram Moolenaar1d2ba7f2006-02-14 22:29:30 +0000243 call add(res2, m)
Bram Moolenaara5792f52005-11-23 21:25:05 +0000244 endif
245 endfor
Bram Moolenaar1d2ba7f2006-02-14 22:29:30 +0000246 let menu = res + res2
247 let final_menu = []
248 if has_key(g:xmldata{'_'.g:xmldata_connection[b:xml_namespace]}, 'vimxmlattrinfo')
249 for i in range(len(menu))
250 let item = menu[i]
251 if has_key(g:xmldata{'_'.g:xmldata_connection[b:xml_namespace]}['vimxmlattrinfo'], item)
252 let m_menu = g:xmldata{'_'.g:xmldata_connection[b:xml_namespace]}['vimxmlattrinfo'][item][0]
253 let m_info = g:xmldata{'_'.g:xmldata_connection[b:xml_namespace]}['vimxmlattrinfo'][item][1]
254 else
255 let m_menu = ''
256 let m_info = ''
257 endif
258 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.'\)$'
259 let item = item
260 else
261 let item .= '="'
262 endif
263 let final_menu += [{'word':item, 'menu':m_menu, 'info':m_info}]
264 endfor
265 else
266 for i in range(len(menu))
267 let item = menu[i]
268 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.'\)$'
269 let item = item
270 else
271 let item .= '="'
272 endif
273 let final_menu += [item]
274 endfor
275 endif
276 return final_menu
Bram Moolenaara5792f52005-11-23 21:25:05 +0000277
278 endif
279 " Close tag
280 let b:unaryTagsStack = "base meta link hr br param img area input col"
281 if context =~ '^\/'
282 let opentag = xmlcomplete#GetLastOpenTag("b:unaryTagsStack")
283 return [opentag.">"]
284 endif
285
286 " Complete elements of XML structure
287 " TODO: #REQUIRED, #IMPLIED, #FIXED, #PCDATA - but these should be detected like
288 " entities - in first run
289 " keywords: CDATA, ID, IDREF, IDREFS, ENTITY, ENTITIES, NMTOKEN, NMTOKENS
290 " are hardly recognizable but keep it in reserve
291 " also: EMPTY ANY SYSTEM PUBLIC DATA
292 if context =~ '^!'
293 let tags = ['!ELEMENT', '!DOCTYPE', '!ATTLIST', '!ENTITY', '!NOTATION', '![CDATA[', '![INCLUDE[', '![IGNORE[']
294
295 for m in tags
296 if m =~ '^'.context
297 let m = substitute(m, '^!\[\?', '', '')
298 call add(res, m)
299 elseif m =~ context
300 let m = substitute(m, '^!\[\?', '', '')
301 call add(res2, m)
302 endif
303 endfor
304
305 return res + res2
306
307 endif
308
309 " Complete text declaration
Bram Moolenaara5792f52005-11-23 21:25:05 +0000310 if context =~ '^?'
311 let tags = ['?xml']
312
313 for m in tags
314 if m =~ '^'.context
315 call add(res, substitute(m, '^?', '', ''))
316 elseif m =~ context
317 call add(res, substitute(m, '^?', '', ''))
318 endif
319 endfor
320
321 return res + res2
322
323 endif
324
325 " Deal with tag completion.
326 let opentag = xmlcomplete#GetLastOpenTag("b:unaryTagsStack")
327 let opentag = substitute(opentag, '^\k*:', '', '')
Bram Moolenaard12f5c12006-01-25 22:10:52 +0000328 if opentag == ''
Bram Moolenaar7e8fd632006-02-18 22:14:51 +0000329 "return []
330 let tags = keys(g:xmldata{'_'.g:xmldata_connection[b:xml_namespace]})
331 call filter(tags, 'v:val !~ "^vimxml"')
332 else
Bram Moolenaar18144c82006-04-12 21:52:12 +0000333 if !has_key(g:xmldata{'_'.g:xmldata_connection[b:xml_namespace]}, opentag)
Bram Moolenaar910f66f2006-04-05 20:41:53 +0000334 " Abandon when data file isn't complete
335 return []
336 endif
Bram Moolenaar7e8fd632006-02-18 22:14:51 +0000337 let tags = g:xmldata{'_'.g:xmldata_connection[b:xml_namespace]}[opentag][0]
Bram Moolenaard12f5c12006-01-25 22:10:52 +0000338 endif
Bram Moolenaara5792f52005-11-23 21:25:05 +0000339
Bram Moolenaara5792f52005-11-23 21:25:05 +0000340 let context = substitute(context, '^\k*:', '', '')
341
Bram Moolenaara5792f52005-11-23 21:25:05 +0000342 for m in tags
343 if m =~ '^'.context
Bram Moolenaar1d2ba7f2006-02-14 22:29:30 +0000344 call add(res, m)
Bram Moolenaara5792f52005-11-23 21:25:05 +0000345 elseif m =~ context
Bram Moolenaar1d2ba7f2006-02-14 22:29:30 +0000346 call add(res2, m)
Bram Moolenaara5792f52005-11-23 21:25:05 +0000347 endif
348 endfor
Bram Moolenaar1d2ba7f2006-02-14 22:29:30 +0000349 let menu = res + res2
Bram Moolenaar18144c82006-04-12 21:52:12 +0000350 if b:xml_namespace == 'DEFAULT'
351 let xml_namespace = ''
352 else
353 let xml_namespace = b:xml_namespace.':'
354 endif
Bram Moolenaar1d2ba7f2006-02-14 22:29:30 +0000355 if has_key(g:xmldata{'_'.g:xmldata_connection[b:xml_namespace]}, 'vimxmltaginfo')
356 let final_menu = []
357 for i in range(len(menu))
358 let item = menu[i]
359 if has_key(g:xmldata{'_'.g:xmldata_connection[b:xml_namespace]}['vimxmltaginfo'], item)
360 let m_menu = g:xmldata{'_'.g:xmldata_connection[b:xml_namespace]}['vimxmltaginfo'][item][0]
361 let m_info = g:xmldata{'_'.g:xmldata_connection[b:xml_namespace]}['vimxmltaginfo'][item][1]
362 else
363 let m_menu = ''
364 let m_info = ''
365 endif
Bram Moolenaar1d2ba7f2006-02-14 22:29:30 +0000366 let final_menu += [{'word':xml_namespace.item, 'menu':m_menu, 'info':m_info}]
367 endfor
368 else
Bram Moolenaar18144c82006-04-12 21:52:12 +0000369 let final_menu = map(menu, 'xml_namespace.v:val')
Bram Moolenaar1d2ba7f2006-02-14 22:29:30 +0000370 endif
Bram Moolenaar18144c82006-04-12 21:52:12 +0000371
Bram Moolenaar1d2ba7f2006-02-14 22:29:30 +0000372 return final_menu
Bram Moolenaara5792f52005-11-23 21:25:05 +0000373
374 endif
375endfunction
376
Bram Moolenaar18144c82006-04-12 21:52:12 +0000377" MM: This is severely reduced closetag.vim used with kind permission of Steven
Bram Moolenaara5792f52005-11-23 21:25:05 +0000378" Mueller
379" Changes: strip all comments; delete error messages; add checking for
380" namespace
381" Author: Steven Mueller <diffusor@ugcs.caltech.edu>
382" Last Modified: Tue May 24 13:29:48 PDT 2005
383" Version: 0.9.1
384
385function! xmlcomplete#GetLastOpenTag(unaryTagsStack)
386 let linenum=line('.')
387 let lineend=col('.') - 1 " start: cursor position
388 let first=1 " flag for first line searched
389 let b:TagStack='' " main stack of tags
390 let startInComment=s:InComment()
391
392 if exists("b:xml_namespace")
393 if b:xml_namespace == 'DEFAULT'
394 let tagpat='</\=\(\k\|[.-]\)\+\|/>'
395 else
396 let tagpat='</\='.b:xml_namespace.':\(\k\|[.-]\)\+\|/>'
397 endif
398 else
Bram Moolenaar9372a112005-12-06 19:59:18 +0000399 let tagpat='</\=\(\k\|[.-]\)\+\|/>'
Bram Moolenaara5792f52005-11-23 21:25:05 +0000400 endif
401 while (linenum>0)
402 let line=getline(linenum)
403 if first
404 let line=strpart(line,0,lineend)
405 else
406 let lineend=strlen(line)
407 endif
408 let b:lineTagStack=''
409 let mpos=0
410 let b:TagCol=0
411 while (mpos > -1)
412 let mpos=matchend(line,tagpat)
413 if mpos > -1
414 let b:TagCol=b:TagCol+mpos
415 let tag=matchstr(line,tagpat)
416
417 if exists('b:closetag_disable_synID') || startInComment==s:InCommentAt(linenum, b:TagCol)
418 let b:TagLine=linenum
419 call s:Push(matchstr(tag,'[^<>]\+'),'b:lineTagStack')
420 endif
421 let lineend=lineend-mpos
422 let line=strpart(line,mpos,lineend)
423 endif
424 endwhile
425 while (!s:EmptystackP('b:lineTagStack'))
426 let tag=s:Pop('b:lineTagStack')
427 if match(tag, '^/') == 0 "found end tag
428 call s:Push(tag,'b:TagStack')
429 elseif s:EmptystackP('b:TagStack') && !s:Instack(tag, a:unaryTagsStack) "found unclosed tag
430 return tag
431 else
432 let endtag=s:Peekstack('b:TagStack')
433 if endtag == '/'.tag || endtag == '/'
434 call s:Pop('b:TagStack') "found a open/close tag pair
435 elseif !s:Instack(tag, a:unaryTagsStack) "we have a mismatch error
436 return ''
437 endif
438 endif
439 endwhile
440 let linenum=linenum-1 | let first=0
441 endwhile
442return ''
443endfunction
444
445function! s:InComment()
Bram Moolenaar8b6144b2006-02-08 09:20:24 +0000446 return synIDattr(synID(line('.'), col('.'), 0), 'name') =~ 'Comment\|String'
Bram Moolenaara5792f52005-11-23 21:25:05 +0000447endfunction
448
449function! s:InCommentAt(line, col)
Bram Moolenaar8b6144b2006-02-08 09:20:24 +0000450 return synIDattr(synID(a:line, a:col, 0), 'name') =~ 'Comment\|String'
Bram Moolenaara5792f52005-11-23 21:25:05 +0000451endfunction
452
453function! s:SetKeywords()
454 let g:IsKeywordBak=&iskeyword
455 let &iskeyword='33-255'
456endfunction
457
458function! s:RestoreKeywords()
459 let &iskeyword=g:IsKeywordBak
460endfunction
461
462function! s:Push(el, sname)
463 if !s:EmptystackP(a:sname)
464 exe 'let '.a:sname."=a:el.' '.".a:sname
465 else
466 exe 'let '.a:sname.'=a:el'
467 endif
468endfunction
469
470function! s:EmptystackP(sname)
471 exe 'let stack='.a:sname
472 if match(stack,'^ *$') == 0
473 return 1
474 else
475 return 0
476 endif
477endfunction
478
479function! s:Instack(el, sname)
480 exe 'let stack='.a:sname
481 call s:SetKeywords()
482 let m=match(stack, '\<'.a:el.'\>')
483 call s:RestoreKeywords()
484 if m < 0
485 return 0
486 else
487 return 1
488 endif
489endfunction
490
491function! s:Peekstack(sname)
492 call s:SetKeywords()
493 exe 'let stack='.a:sname
494 let top=matchstr(stack, '\<.\{-1,}\>')
495 call s:RestoreKeywords()
496 return top
497endfunction
498
499function! s:Pop(sname)
500 if s:EmptystackP(a:sname)
501 return ''
502 endif
503 exe 'let stack='.a:sname
504 call s:SetKeywords()
505 let loc=matchend(stack,'\<.\{-1,}\>')
506 exe 'let '.a:sname.'=strpart(stack, loc+1, strlen(stack))'
507 let top=strpart(stack, match(stack, '\<'), loc)
508 call s:RestoreKeywords()
509 return top
510endfunction
511
512function! s:Clearstack(sname)
513 exe 'let '.a:sname."=''"
514endfunction
Bram Moolenaar18144c82006-04-12 21:52:12 +0000515" vim:set foldmethod=marker: