blob: 78d30582a1e714a3db5647b139bb85e1239df966 [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 Moolenaarc9b4b052006-04-30 18:54:39 +00004" Last Change: 2006 Apr 30
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
Bram Moolenaarc9b4b052006-04-30 18:54:39 +000025 " :runtime.
Bram Moolenaara5792f52005-11-23 21:25:05 +000026 " 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"
Bram Moolenaarc9b4b052006-04-30 18:54:39 +0000169 " If a:base contains white space it is attribute.
Bram Moolenaara5792f52005-11-23 21:25:05 +0000170 " 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
Bram Moolenaar8424a622006-04-19 21:23:36 +0000189 if attr =~ "=\s*[\"']" || attr =~ "=\s*$"
Bram Moolenaara5792f52005-11-23 21:25:05 +0000190 " Let do attribute specific completion
191 let attrname = matchstr(attr, '.*\ze\s*=')
Bram Moolenaar8424a622006-04-19 21:23:36 +0000192 let entered_value = matchstr(attr, ".*=\\s*[\"']\\?\\zs.*")
Bram Moolenaara5792f52005-11-23 21:25:05 +0000193
194 if tag =~ '^[?!]'
195 " Return nothing if we are inside of ! or ? tag
196 return []
197 else
Bram Moolenaar8424a622006-04-19 21:23:36 +0000198 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)
199 let values = g:xmldata{'_'.g:xmldata_connection[b:xml_namespace]}[tag][1][attrname]
200 else
201 return []
202 endif
Bram Moolenaara5792f52005-11-23 21:25:05 +0000203 endif
204
205 if len(values) == 0
206 return []
207 endif
208
209 " We need special version of sbase
210 let attrbase = matchstr(context, ".*[\"']")
211 let attrquote = matchstr(attrbase, '.$')
Bram Moolenaar8424a622006-04-19 21:23:36 +0000212 if attrquote !~ "['\"]"
213 let attrquoteopen = '"'
214 let attrquote = '"'
215 else
216 let attrquoteopen = ''
217 endif
Bram Moolenaara5792f52005-11-23 21:25:05 +0000218
219 for m in values
220 " This if is needed to not offer all completions as-is
221 " alphabetically but sort them. Those beginning with entered
222 " part will be as first choices
223 if m =~ '^'.entered_value
Bram Moolenaar8424a622006-04-19 21:23:36 +0000224 call add(res, attrquoteopen . m . attrquote.' ')
Bram Moolenaara5792f52005-11-23 21:25:05 +0000225 elseif m =~ entered_value
Bram Moolenaar8424a622006-04-19 21:23:36 +0000226 call add(res2, attrquoteopen . m . attrquote.' ')
Bram Moolenaara5792f52005-11-23 21:25:05 +0000227 endif
228 endfor
229
230 return res + res2
231
232 endif
233
234 if tag =~ '?xml'
235 " Two possible arguments for <?xml> plus variation
236 let attrs = ['encoding', 'version="1.0"', 'version']
237 elseif tag =~ '^!'
238 " Don't make completion at all
Bram Moolenaar910f66f2006-04-05 20:41:53 +0000239 "
Bram Moolenaara5792f52005-11-23 21:25:05 +0000240 return []
241 else
Bram Moolenaar910f66f2006-04-05 20:41:53 +0000242 if !has_key(g:xmldata{'_'.g:xmldata_connection[b:xml_namespace]}, tag)
243 " Abandon when data file isn't complete
244 return []
245 endif
Bram Moolenaara5792f52005-11-23 21:25:05 +0000246 let attrs = keys(g:xmldata{'_'.g:xmldata_connection[b:xml_namespace]}[tag][1])
247 endif
248
249 for m in sort(attrs)
250 if m =~ '^'.attr
Bram Moolenaar1d2ba7f2006-02-14 22:29:30 +0000251 call add(res, m)
Bram Moolenaara5792f52005-11-23 21:25:05 +0000252 elseif m =~ attr
Bram Moolenaar1d2ba7f2006-02-14 22:29:30 +0000253 call add(res2, m)
Bram Moolenaara5792f52005-11-23 21:25:05 +0000254 endif
255 endfor
Bram Moolenaar1d2ba7f2006-02-14 22:29:30 +0000256 let menu = res + res2
257 let final_menu = []
258 if has_key(g:xmldata{'_'.g:xmldata_connection[b:xml_namespace]}, 'vimxmlattrinfo')
259 for i in range(len(menu))
260 let item = menu[i]
261 if has_key(g:xmldata{'_'.g:xmldata_connection[b:xml_namespace]}['vimxmlattrinfo'], item)
262 let m_menu = g:xmldata{'_'.g:xmldata_connection[b:xml_namespace]}['vimxmlattrinfo'][item][0]
263 let m_info = g:xmldata{'_'.g:xmldata_connection[b:xml_namespace]}['vimxmlattrinfo'][item][1]
264 else
265 let m_menu = ''
266 let m_info = ''
267 endif
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 += [{'word':item, 'menu':m_menu, 'info':m_info}]
274 endfor
275 else
276 for i in range(len(menu))
277 let item = menu[i]
278 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.'\)$'
279 let item = item
280 else
281 let item .= '="'
282 endif
283 let final_menu += [item]
284 endfor
285 endif
286 return final_menu
Bram Moolenaara5792f52005-11-23 21:25:05 +0000287
288 endif
289 " Close tag
290 let b:unaryTagsStack = "base meta link hr br param img area input col"
291 if context =~ '^\/'
292 let opentag = xmlcomplete#GetLastOpenTag("b:unaryTagsStack")
293 return [opentag.">"]
294 endif
295
296 " Complete elements of XML structure
297 " TODO: #REQUIRED, #IMPLIED, #FIXED, #PCDATA - but these should be detected like
298 " entities - in first run
299 " keywords: CDATA, ID, IDREF, IDREFS, ENTITY, ENTITIES, NMTOKEN, NMTOKENS
300 " are hardly recognizable but keep it in reserve
301 " also: EMPTY ANY SYSTEM PUBLIC DATA
302 if context =~ '^!'
303 let tags = ['!ELEMENT', '!DOCTYPE', '!ATTLIST', '!ENTITY', '!NOTATION', '![CDATA[', '![INCLUDE[', '![IGNORE[']
304
305 for m in tags
306 if m =~ '^'.context
307 let m = substitute(m, '^!\[\?', '', '')
308 call add(res, m)
309 elseif m =~ context
310 let m = substitute(m, '^!\[\?', '', '')
311 call add(res2, m)
312 endif
313 endfor
314
315 return res + res2
316
317 endif
318
319 " Complete text declaration
Bram Moolenaara5792f52005-11-23 21:25:05 +0000320 if context =~ '^?'
321 let tags = ['?xml']
322
323 for m in tags
324 if m =~ '^'.context
325 call add(res, substitute(m, '^?', '', ''))
326 elseif m =~ context
327 call add(res, substitute(m, '^?', '', ''))
328 endif
329 endfor
330
331 return res + res2
332
333 endif
334
335 " Deal with tag completion.
336 let opentag = xmlcomplete#GetLastOpenTag("b:unaryTagsStack")
337 let opentag = substitute(opentag, '^\k*:', '', '')
Bram Moolenaard12f5c12006-01-25 22:10:52 +0000338 if opentag == ''
Bram Moolenaar7e8fd632006-02-18 22:14:51 +0000339 "return []
340 let tags = keys(g:xmldata{'_'.g:xmldata_connection[b:xml_namespace]})
341 call filter(tags, 'v:val !~ "^vimxml"')
342 else
Bram Moolenaar18144c82006-04-12 21:52:12 +0000343 if !has_key(g:xmldata{'_'.g:xmldata_connection[b:xml_namespace]}, opentag)
Bram Moolenaar910f66f2006-04-05 20:41:53 +0000344 " Abandon when data file isn't complete
345 return []
346 endif
Bram Moolenaar7e8fd632006-02-18 22:14:51 +0000347 let tags = g:xmldata{'_'.g:xmldata_connection[b:xml_namespace]}[opentag][0]
Bram Moolenaard12f5c12006-01-25 22:10:52 +0000348 endif
Bram Moolenaara5792f52005-11-23 21:25:05 +0000349
Bram Moolenaara5792f52005-11-23 21:25:05 +0000350 let context = substitute(context, '^\k*:', '', '')
351
Bram Moolenaara5792f52005-11-23 21:25:05 +0000352 for m in tags
353 if m =~ '^'.context
Bram Moolenaar1d2ba7f2006-02-14 22:29:30 +0000354 call add(res, m)
Bram Moolenaara5792f52005-11-23 21:25:05 +0000355 elseif m =~ context
Bram Moolenaar1d2ba7f2006-02-14 22:29:30 +0000356 call add(res2, m)
Bram Moolenaara5792f52005-11-23 21:25:05 +0000357 endif
358 endfor
Bram Moolenaar1d2ba7f2006-02-14 22:29:30 +0000359 let menu = res + res2
Bram Moolenaar18144c82006-04-12 21:52:12 +0000360 if b:xml_namespace == 'DEFAULT'
361 let xml_namespace = ''
362 else
363 let xml_namespace = b:xml_namespace.':'
364 endif
Bram Moolenaar1d2ba7f2006-02-14 22:29:30 +0000365 if has_key(g:xmldata{'_'.g:xmldata_connection[b:xml_namespace]}, 'vimxmltaginfo')
366 let final_menu = []
367 for i in range(len(menu))
368 let item = menu[i]
369 if has_key(g:xmldata{'_'.g:xmldata_connection[b:xml_namespace]}['vimxmltaginfo'], item)
370 let m_menu = g:xmldata{'_'.g:xmldata_connection[b:xml_namespace]}['vimxmltaginfo'][item][0]
371 let m_info = g:xmldata{'_'.g:xmldata_connection[b:xml_namespace]}['vimxmltaginfo'][item][1]
372 else
373 let m_menu = ''
374 let m_info = ''
375 endif
Bram Moolenaar1d2ba7f2006-02-14 22:29:30 +0000376 let final_menu += [{'word':xml_namespace.item, 'menu':m_menu, 'info':m_info}]
377 endfor
378 else
Bram Moolenaar18144c82006-04-12 21:52:12 +0000379 let final_menu = map(menu, 'xml_namespace.v:val')
Bram Moolenaar1d2ba7f2006-02-14 22:29:30 +0000380 endif
Bram Moolenaar18144c82006-04-12 21:52:12 +0000381
Bram Moolenaar1d2ba7f2006-02-14 22:29:30 +0000382 return final_menu
Bram Moolenaara5792f52005-11-23 21:25:05 +0000383
384 endif
385endfunction
386
Bram Moolenaar18144c82006-04-12 21:52:12 +0000387" MM: This is severely reduced closetag.vim used with kind permission of Steven
Bram Moolenaara5792f52005-11-23 21:25:05 +0000388" Mueller
389" Changes: strip all comments; delete error messages; add checking for
390" namespace
391" Author: Steven Mueller <diffusor@ugcs.caltech.edu>
392" Last Modified: Tue May 24 13:29:48 PDT 2005
393" Version: 0.9.1
394
395function! xmlcomplete#GetLastOpenTag(unaryTagsStack)
396 let linenum=line('.')
397 let lineend=col('.') - 1 " start: cursor position
398 let first=1 " flag for first line searched
399 let b:TagStack='' " main stack of tags
400 let startInComment=s:InComment()
401
402 if exists("b:xml_namespace")
403 if b:xml_namespace == 'DEFAULT'
404 let tagpat='</\=\(\k\|[.-]\)\+\|/>'
405 else
406 let tagpat='</\='.b:xml_namespace.':\(\k\|[.-]\)\+\|/>'
407 endif
408 else
Bram Moolenaar9372a112005-12-06 19:59:18 +0000409 let tagpat='</\=\(\k\|[.-]\)\+\|/>'
Bram Moolenaara5792f52005-11-23 21:25:05 +0000410 endif
411 while (linenum>0)
412 let line=getline(linenum)
413 if first
414 let line=strpart(line,0,lineend)
415 else
416 let lineend=strlen(line)
417 endif
418 let b:lineTagStack=''
419 let mpos=0
420 let b:TagCol=0
421 while (mpos > -1)
422 let mpos=matchend(line,tagpat)
423 if mpos > -1
424 let b:TagCol=b:TagCol+mpos
425 let tag=matchstr(line,tagpat)
426
427 if exists('b:closetag_disable_synID') || startInComment==s:InCommentAt(linenum, b:TagCol)
428 let b:TagLine=linenum
429 call s:Push(matchstr(tag,'[^<>]\+'),'b:lineTagStack')
430 endif
431 let lineend=lineend-mpos
432 let line=strpart(line,mpos,lineend)
433 endif
434 endwhile
435 while (!s:EmptystackP('b:lineTagStack'))
436 let tag=s:Pop('b:lineTagStack')
437 if match(tag, '^/') == 0 "found end tag
438 call s:Push(tag,'b:TagStack')
439 elseif s:EmptystackP('b:TagStack') && !s:Instack(tag, a:unaryTagsStack) "found unclosed tag
440 return tag
441 else
442 let endtag=s:Peekstack('b:TagStack')
443 if endtag == '/'.tag || endtag == '/'
444 call s:Pop('b:TagStack') "found a open/close tag pair
445 elseif !s:Instack(tag, a:unaryTagsStack) "we have a mismatch error
446 return ''
447 endif
448 endif
449 endwhile
450 let linenum=linenum-1 | let first=0
451 endwhile
452return ''
453endfunction
454
455function! s:InComment()
Bram Moolenaar8b6144b2006-02-08 09:20:24 +0000456 return synIDattr(synID(line('.'), col('.'), 0), 'name') =~ 'Comment\|String'
Bram Moolenaara5792f52005-11-23 21:25:05 +0000457endfunction
458
459function! s:InCommentAt(line, col)
Bram Moolenaar8b6144b2006-02-08 09:20:24 +0000460 return synIDattr(synID(a:line, a:col, 0), 'name') =~ 'Comment\|String'
Bram Moolenaara5792f52005-11-23 21:25:05 +0000461endfunction
462
463function! s:SetKeywords()
464 let g:IsKeywordBak=&iskeyword
465 let &iskeyword='33-255'
466endfunction
467
468function! s:RestoreKeywords()
469 let &iskeyword=g:IsKeywordBak
470endfunction
471
472function! s:Push(el, sname)
473 if !s:EmptystackP(a:sname)
474 exe 'let '.a:sname."=a:el.' '.".a:sname
475 else
476 exe 'let '.a:sname.'=a:el'
477 endif
478endfunction
479
480function! s:EmptystackP(sname)
481 exe 'let stack='.a:sname
482 if match(stack,'^ *$') == 0
483 return 1
484 else
485 return 0
486 endif
487endfunction
488
489function! s:Instack(el, sname)
490 exe 'let stack='.a:sname
491 call s:SetKeywords()
492 let m=match(stack, '\<'.a:el.'\>')
493 call s:RestoreKeywords()
494 if m < 0
495 return 0
496 else
497 return 1
498 endif
499endfunction
500
501function! s:Peekstack(sname)
502 call s:SetKeywords()
503 exe 'let stack='.a:sname
504 let top=matchstr(stack, '\<.\{-1,}\>')
505 call s:RestoreKeywords()
506 return top
507endfunction
508
509function! s:Pop(sname)
510 if s:EmptystackP(a:sname)
511 return ''
512 endif
513 exe 'let stack='.a:sname
514 call s:SetKeywords()
515 let loc=matchend(stack,'\<.\{-1,}\>')
516 exe 'let '.a:sname.'=strpart(stack, loc+1, strlen(stack))'
517 let top=strpart(stack, match(stack, '\<'), loc)
518 call s:RestoreKeywords()
519 return top
520endfunction
521
522function! s:Clearstack(sname)
523 exe 'let '.a:sname."=''"
524endfunction
Bram Moolenaar18144c82006-04-12 21:52:12 +0000525" vim:set foldmethod=marker: