blob: 2551cae30db12bf67b67e9ed99a3be42c090f785 [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 Moolenaarc15ef302006-03-19 22:11:16 +00004" Last Change: 2006 Mar 19
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
Bram Moolenaarc15ef302006-03-19 22:11:16 +000084 elseif context_line =~ '>[^<]*$' || i == curline
Bram Moolenaard12f5c12006-01-25 22:10:52 +000085 " Normal tag line, no need for completion at all
Bram Moolenaarc15ef302006-03-19 22:11:16 +000086 " OR reached first line without tag at all
Bram Moolenaard12f5c12006-01-25 22:10:52 +000087 let b:compl_context = ''
88 break
89 endif
90 let i += 1
91 endwhile
92 " Make sure we don't have counter
93 unlet! i
94 endif
95 let b:compl_context = matchstr(b:compl_context, '.*\zs<.*')
Bram Moolenaara5792f52005-11-23 21:25:05 +000096
97 " Make sure we will have only current namespace
98 unlet! b:xml_namespace
Bram Moolenaard12f5c12006-01-25 22:10:52 +000099 let b:xml_namespace = matchstr(b:compl_context, '^<\zs\k*\ze:')
Bram Moolenaara5792f52005-11-23 21:25:05 +0000100 if b:xml_namespace == ''
101 let b:xml_namespace = 'DEFAULT'
102 endif
103
104 return start
105
106 else
107 " There is no connction of namespace and data file. Abandon action
108 if !exists("g:xmldata_connection") || g:xmldata_connection == {}
109 return []
110 endif
111 " Initialize base return lists
112 let res = []
113 let res2 = []
114 " a:base is very short - we need context
Bram Moolenaard12f5c12006-01-25 22:10:52 +0000115 if len(b:compl_context) == 0 && !exists("b:entitiescompl")
116 return []
117 endif
118 let context = matchstr(b:compl_context, '^<\zs.*')
Bram Moolenaara5792f52005-11-23 21:25:05 +0000119 unlet! b:compl_context
120
121 " Make entities completion
122 if exists("b:entitiescompl")
123 unlet! b:entitiescompl
124
125 if !exists("g:xmldata_entconnect") || g:xmldata_entconnect == 'DEFAULT'
126 let values = g:xmldata{'_'.g:xmldata_connection['DEFAULT']}['vimxmlentities']
127 else
128 let values = g:xmldata{'_'.g:xmldata_entconnect}['vimxmlentities']
129 endif
130
131 " Get only lines with entity declarations but throw out
132 " parameter-entities - they may be completed in future
133 let entdecl = filter(getline(1, "$"), 'v:val =~ "<!ENTITY\\s\\+[^%]"')
134
135 if len(entdecl) > 0
136 let intent = map(copy(entdecl), 'matchstr(v:val, "<!ENTITY\\s\\+\\zs\\(\\k\\|[.-:]\\)\\+\\ze")')
137 let values = intent + values
138 endif
139
Bram Moolenaard12f5c12006-01-25 22:10:52 +0000140 if len(a:base) == 1
141 for m in values
142 if m =~ '^'.a:base
143 call add(res, m.';')
144 endif
145 endfor
146 return res
147 else
148 for m in values
149 if m =~? '^'.a:base
150 call add(res, m.';')
151 elseif m =~? a:base
152 call add(res2, m.';')
153 endif
154 endfor
Bram Moolenaara5792f52005-11-23 21:25:05 +0000155
Bram Moolenaard12f5c12006-01-25 22:10:52 +0000156 return res + res2
157 endif
Bram Moolenaara5792f52005-11-23 21:25:05 +0000158
159 endif
160 if context =~ '>'
161 " Generally if context contains > it means we are outside of tag and
162 " should abandon action
163 return []
164 endif
165
166 " find tags matching with "a:base"
167 " If a:base contains white space it is attribute.
168 " It could be also value of attribute...
169 " We have to get first word to offer
170 " proper completions
171 if context == ''
172 let tag = ''
173 else
174 let tag = split(context)[0]
175 endif
176 " Get rid of namespace
177 let tag = substitute(tag, '^'.b:xml_namespace.':', '', '')
178
179
180 " Get last word, it should be attr name
181 let attr = matchstr(context, '.*\s\zs.*')
182 " Possible situations where any prediction would be difficult:
183 " 1. Events attributes
184 if context =~ '\s'
185
186 " If attr contains =\s*[\"'] we catched value of attribute
187 if attr =~ "=\s*[\"']"
188 " Let do attribute specific completion
189 let attrname = matchstr(attr, '.*\ze\s*=')
190 let entered_value = matchstr(attr, ".*=\\s*[\"']\\zs.*")
191
192 if tag =~ '^[?!]'
193 " Return nothing if we are inside of ! or ? tag
194 return []
195 else
196 let values = g:xmldata{'_'.g:xmldata_connection[b:xml_namespace]}[tag][1][attrname]
197 endif
198
199 if len(values) == 0
200 return []
201 endif
202
203 " We need special version of sbase
204 let attrbase = matchstr(context, ".*[\"']")
205 let attrquote = matchstr(attrbase, '.$')
206
207 for m in values
208 " This if is needed to not offer all completions as-is
209 " alphabetically but sort them. Those beginning with entered
210 " part will be as first choices
211 if m =~ '^'.entered_value
212 call add(res, m . attrquote.' ')
213 elseif m =~ entered_value
214 call add(res2, m . attrquote.' ')
215 endif
216 endfor
217
218 return res + res2
219
220 endif
221
222 if tag =~ '?xml'
223 " Two possible arguments for <?xml> plus variation
224 let attrs = ['encoding', 'version="1.0"', 'version']
225 elseif tag =~ '^!'
226 " Don't make completion at all
227 return []
228 else
229 let attrs = keys(g:xmldata{'_'.g:xmldata_connection[b:xml_namespace]}[tag][1])
230 endif
231
232 for m in sort(attrs)
233 if m =~ '^'.attr
Bram Moolenaar1d2ba7f2006-02-14 22:29:30 +0000234 call add(res, m)
Bram Moolenaara5792f52005-11-23 21:25:05 +0000235 elseif m =~ attr
Bram Moolenaar1d2ba7f2006-02-14 22:29:30 +0000236 call add(res2, m)
Bram Moolenaara5792f52005-11-23 21:25:05 +0000237 endif
238 endfor
Bram Moolenaar1d2ba7f2006-02-14 22:29:30 +0000239 let menu = res + res2
240 let final_menu = []
241 if has_key(g:xmldata{'_'.g:xmldata_connection[b:xml_namespace]}, 'vimxmlattrinfo')
242 for i in range(len(menu))
243 let item = menu[i]
244 if has_key(g:xmldata{'_'.g:xmldata_connection[b:xml_namespace]}['vimxmlattrinfo'], item)
245 let m_menu = g:xmldata{'_'.g:xmldata_connection[b:xml_namespace]}['vimxmlattrinfo'][item][0]
246 let m_info = g:xmldata{'_'.g:xmldata_connection[b:xml_namespace]}['vimxmlattrinfo'][item][1]
247 else
248 let m_menu = ''
249 let m_info = ''
250 endif
251 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.'\)$'
252 let item = item
253 else
254 let item .= '="'
255 endif
256 let final_menu += [{'word':item, 'menu':m_menu, 'info':m_info}]
257 endfor
258 else
259 for i in range(len(menu))
260 let item = menu[i]
261 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.'\)$'
262 let item = item
263 else
264 let item .= '="'
265 endif
266 let final_menu += [item]
267 endfor
268 endif
269 return final_menu
Bram Moolenaara5792f52005-11-23 21:25:05 +0000270
271 endif
272 " Close tag
273 let b:unaryTagsStack = "base meta link hr br param img area input col"
274 if context =~ '^\/'
275 let opentag = xmlcomplete#GetLastOpenTag("b:unaryTagsStack")
276 return [opentag.">"]
277 endif
278
279 " Complete elements of XML structure
280 " TODO: #REQUIRED, #IMPLIED, #FIXED, #PCDATA - but these should be detected like
281 " entities - in first run
282 " keywords: CDATA, ID, IDREF, IDREFS, ENTITY, ENTITIES, NMTOKEN, NMTOKENS
283 " are hardly recognizable but keep it in reserve
284 " also: EMPTY ANY SYSTEM PUBLIC DATA
285 if context =~ '^!'
286 let tags = ['!ELEMENT', '!DOCTYPE', '!ATTLIST', '!ENTITY', '!NOTATION', '![CDATA[', '![INCLUDE[', '![IGNORE[']
287
288 for m in tags
289 if m =~ '^'.context
290 let m = substitute(m, '^!\[\?', '', '')
291 call add(res, m)
292 elseif m =~ context
293 let m = substitute(m, '^!\[\?', '', '')
294 call add(res2, m)
295 endif
296 endfor
297
298 return res + res2
299
300 endif
301
302 " Complete text declaration
303 let g:co = context
304 if context =~ '^?'
305 let tags = ['?xml']
306
307 for m in tags
308 if m =~ '^'.context
309 call add(res, substitute(m, '^?', '', ''))
310 elseif m =~ context
311 call add(res, substitute(m, '^?', '', ''))
312 endif
313 endfor
314
315 return res + res2
316
317 endif
318
319 " Deal with tag completion.
320 let opentag = xmlcomplete#GetLastOpenTag("b:unaryTagsStack")
321 let opentag = substitute(opentag, '^\k*:', '', '')
Bram Moolenaard12f5c12006-01-25 22:10:52 +0000322 if opentag == ''
Bram Moolenaar7e8fd632006-02-18 22:14:51 +0000323 "return []
324 let tags = keys(g:xmldata{'_'.g:xmldata_connection[b:xml_namespace]})
325 call filter(tags, 'v:val !~ "^vimxml"')
326 else
327 let tags = g:xmldata{'_'.g:xmldata_connection[b:xml_namespace]}[opentag][0]
Bram Moolenaard12f5c12006-01-25 22:10:52 +0000328 endif
Bram Moolenaara5792f52005-11-23 21:25:05 +0000329
Bram Moolenaara5792f52005-11-23 21:25:05 +0000330 let context = substitute(context, '^\k*:', '', '')
331
Bram Moolenaara5792f52005-11-23 21:25:05 +0000332 for m in tags
333 if m =~ '^'.context
Bram Moolenaar1d2ba7f2006-02-14 22:29:30 +0000334 call add(res, m)
Bram Moolenaara5792f52005-11-23 21:25:05 +0000335 elseif m =~ context
Bram Moolenaar1d2ba7f2006-02-14 22:29:30 +0000336 call add(res2, m)
Bram Moolenaara5792f52005-11-23 21:25:05 +0000337 endif
338 endfor
Bram Moolenaar1d2ba7f2006-02-14 22:29:30 +0000339 let menu = res + res2
340 if has_key(g:xmldata{'_'.g:xmldata_connection[b:xml_namespace]}, 'vimxmltaginfo')
341 let final_menu = []
342 for i in range(len(menu))
343 let item = menu[i]
344 if has_key(g:xmldata{'_'.g:xmldata_connection[b:xml_namespace]}['vimxmltaginfo'], item)
345 let m_menu = g:xmldata{'_'.g:xmldata_connection[b:xml_namespace]}['vimxmltaginfo'][item][0]
346 let m_info = g:xmldata{'_'.g:xmldata_connection[b:xml_namespace]}['vimxmltaginfo'][item][1]
347 else
348 let m_menu = ''
349 let m_info = ''
350 endif
351 if b:xml_namespace == 'DEFAULT'
352 let xml_namespace = ''
353 else
354 let xml_namespace = b:xml_namespace.':'
355 endif
356 let final_menu += [{'word':xml_namespace.item, 'menu':m_menu, 'info':m_info}]
357 endfor
358 else
359 let final_menu = menu
360 endif
361 return final_menu
Bram Moolenaara5792f52005-11-23 21:25:05 +0000362
363 endif
364endfunction
365
366" MM: This is greatly reduced closetag.vim used with kind permission of Steven
367" Mueller
368" Changes: strip all comments; delete error messages; add checking for
369" namespace
370" Author: Steven Mueller <diffusor@ugcs.caltech.edu>
371" Last Modified: Tue May 24 13:29:48 PDT 2005
372" Version: 0.9.1
373
374function! xmlcomplete#GetLastOpenTag(unaryTagsStack)
375 let linenum=line('.')
376 let lineend=col('.') - 1 " start: cursor position
377 let first=1 " flag for first line searched
378 let b:TagStack='' " main stack of tags
379 let startInComment=s:InComment()
380
381 if exists("b:xml_namespace")
382 if b:xml_namespace == 'DEFAULT'
383 let tagpat='</\=\(\k\|[.-]\)\+\|/>'
384 else
385 let tagpat='</\='.b:xml_namespace.':\(\k\|[.-]\)\+\|/>'
386 endif
387 else
Bram Moolenaar9372a112005-12-06 19:59:18 +0000388 let tagpat='</\=\(\k\|[.-]\)\+\|/>'
Bram Moolenaara5792f52005-11-23 21:25:05 +0000389 endif
390 while (linenum>0)
391 let line=getline(linenum)
392 if first
393 let line=strpart(line,0,lineend)
394 else
395 let lineend=strlen(line)
396 endif
397 let b:lineTagStack=''
398 let mpos=0
399 let b:TagCol=0
400 while (mpos > -1)
401 let mpos=matchend(line,tagpat)
402 if mpos > -1
403 let b:TagCol=b:TagCol+mpos
404 let tag=matchstr(line,tagpat)
405
406 if exists('b:closetag_disable_synID') || startInComment==s:InCommentAt(linenum, b:TagCol)
407 let b:TagLine=linenum
408 call s:Push(matchstr(tag,'[^<>]\+'),'b:lineTagStack')
409 endif
410 let lineend=lineend-mpos
411 let line=strpart(line,mpos,lineend)
412 endif
413 endwhile
414 while (!s:EmptystackP('b:lineTagStack'))
415 let tag=s:Pop('b:lineTagStack')
416 if match(tag, '^/') == 0 "found end tag
417 call s:Push(tag,'b:TagStack')
418 elseif s:EmptystackP('b:TagStack') && !s:Instack(tag, a:unaryTagsStack) "found unclosed tag
419 return tag
420 else
421 let endtag=s:Peekstack('b:TagStack')
422 if endtag == '/'.tag || endtag == '/'
423 call s:Pop('b:TagStack') "found a open/close tag pair
424 elseif !s:Instack(tag, a:unaryTagsStack) "we have a mismatch error
425 return ''
426 endif
427 endif
428 endwhile
429 let linenum=linenum-1 | let first=0
430 endwhile
431return ''
432endfunction
433
434function! s:InComment()
Bram Moolenaar8b6144b2006-02-08 09:20:24 +0000435 return synIDattr(synID(line('.'), col('.'), 0), 'name') =~ 'Comment\|String'
Bram Moolenaara5792f52005-11-23 21:25:05 +0000436endfunction
437
438function! s:InCommentAt(line, col)
Bram Moolenaar8b6144b2006-02-08 09:20:24 +0000439 return synIDattr(synID(a:line, a:col, 0), 'name') =~ 'Comment\|String'
Bram Moolenaara5792f52005-11-23 21:25:05 +0000440endfunction
441
442function! s:SetKeywords()
443 let g:IsKeywordBak=&iskeyword
444 let &iskeyword='33-255'
445endfunction
446
447function! s:RestoreKeywords()
448 let &iskeyword=g:IsKeywordBak
449endfunction
450
451function! s:Push(el, sname)
452 if !s:EmptystackP(a:sname)
453 exe 'let '.a:sname."=a:el.' '.".a:sname
454 else
455 exe 'let '.a:sname.'=a:el'
456 endif
457endfunction
458
459function! s:EmptystackP(sname)
460 exe 'let stack='.a:sname
461 if match(stack,'^ *$') == 0
462 return 1
463 else
464 return 0
465 endif
466endfunction
467
468function! s:Instack(el, sname)
469 exe 'let stack='.a:sname
470 call s:SetKeywords()
471 let m=match(stack, '\<'.a:el.'\>')
472 call s:RestoreKeywords()
473 if m < 0
474 return 0
475 else
476 return 1
477 endif
478endfunction
479
480function! s:Peekstack(sname)
481 call s:SetKeywords()
482 exe 'let stack='.a:sname
483 let top=matchstr(stack, '\<.\{-1,}\>')
484 call s:RestoreKeywords()
485 return top
486endfunction
487
488function! s:Pop(sname)
489 if s:EmptystackP(a:sname)
490 return ''
491 endif
492 exe 'let stack='.a:sname
493 call s:SetKeywords()
494 let loc=matchend(stack,'\<.\{-1,}\>')
495 exe 'let '.a:sname.'=strpart(stack, loc+1, strlen(stack))'
496 let top=strpart(stack, match(stack, '\<'), loc)
497 call s:RestoreKeywords()
498 return top
499endfunction
500
501function! s:Clearstack(sname)
502 exe 'let '.a:sname."=''"
503endfunction