blob: 6b7b804e36836febd732dba80414ea52ba1d6408 [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 Moolenaar7e8fd632006-02-18 22:14:51 +00004" Last Change: 2006 Feb 18
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 == ''
Bram Moolenaar7e8fd632006-02-18 22:14:51 +0000322 "return []
323 let tags = keys(g:xmldata{'_'.g:xmldata_connection[b:xml_namespace]})
324 call filter(tags, 'v:val !~ "^vimxml"')
325 else
326 let tags = g:xmldata{'_'.g:xmldata_connection[b:xml_namespace]}[opentag][0]
Bram Moolenaard12f5c12006-01-25 22:10:52 +0000327 endif
Bram Moolenaara5792f52005-11-23 21:25:05 +0000328
Bram Moolenaara5792f52005-11-23 21:25:05 +0000329 let context = substitute(context, '^\k*:', '', '')
330
Bram Moolenaara5792f52005-11-23 21:25:05 +0000331 for m in tags
332 if m =~ '^'.context
Bram Moolenaar1d2ba7f2006-02-14 22:29:30 +0000333 call add(res, m)
Bram Moolenaara5792f52005-11-23 21:25:05 +0000334 elseif m =~ context
Bram Moolenaar1d2ba7f2006-02-14 22:29:30 +0000335 call add(res2, m)
Bram Moolenaara5792f52005-11-23 21:25:05 +0000336 endif
337 endfor
Bram Moolenaar1d2ba7f2006-02-14 22:29:30 +0000338 let menu = res + res2
339 if has_key(g:xmldata{'_'.g:xmldata_connection[b:xml_namespace]}, 'vimxmltaginfo')
340 let final_menu = []
341 for i in range(len(menu))
342 let item = menu[i]
343 if has_key(g:xmldata{'_'.g:xmldata_connection[b:xml_namespace]}['vimxmltaginfo'], item)
344 let m_menu = g:xmldata{'_'.g:xmldata_connection[b:xml_namespace]}['vimxmltaginfo'][item][0]
345 let m_info = g:xmldata{'_'.g:xmldata_connection[b:xml_namespace]}['vimxmltaginfo'][item][1]
346 else
347 let m_menu = ''
348 let m_info = ''
349 endif
350 if b:xml_namespace == 'DEFAULT'
351 let xml_namespace = ''
352 else
353 let xml_namespace = b:xml_namespace.':'
354 endif
355 let final_menu += [{'word':xml_namespace.item, 'menu':m_menu, 'info':m_info}]
356 endfor
357 else
358 let final_menu = menu
359 endif
360 return final_menu
Bram Moolenaara5792f52005-11-23 21:25:05 +0000361
362 endif
363endfunction
364
365" MM: This is greatly reduced closetag.vim used with kind permission of Steven
366" Mueller
367" Changes: strip all comments; delete error messages; add checking for
368" namespace
369" Author: Steven Mueller <diffusor@ugcs.caltech.edu>
370" Last Modified: Tue May 24 13:29:48 PDT 2005
371" Version: 0.9.1
372
373function! xmlcomplete#GetLastOpenTag(unaryTagsStack)
374 let linenum=line('.')
375 let lineend=col('.') - 1 " start: cursor position
376 let first=1 " flag for first line searched
377 let b:TagStack='' " main stack of tags
378 let startInComment=s:InComment()
379
380 if exists("b:xml_namespace")
381 if b:xml_namespace == 'DEFAULT'
382 let tagpat='</\=\(\k\|[.-]\)\+\|/>'
383 else
384 let tagpat='</\='.b:xml_namespace.':\(\k\|[.-]\)\+\|/>'
385 endif
386 else
Bram Moolenaar9372a112005-12-06 19:59:18 +0000387 let tagpat='</\=\(\k\|[.-]\)\+\|/>'
Bram Moolenaara5792f52005-11-23 21:25:05 +0000388 endif
389 while (linenum>0)
390 let line=getline(linenum)
391 if first
392 let line=strpart(line,0,lineend)
393 else
394 let lineend=strlen(line)
395 endif
396 let b:lineTagStack=''
397 let mpos=0
398 let b:TagCol=0
399 while (mpos > -1)
400 let mpos=matchend(line,tagpat)
401 if mpos > -1
402 let b:TagCol=b:TagCol+mpos
403 let tag=matchstr(line,tagpat)
404
405 if exists('b:closetag_disable_synID') || startInComment==s:InCommentAt(linenum, b:TagCol)
406 let b:TagLine=linenum
407 call s:Push(matchstr(tag,'[^<>]\+'),'b:lineTagStack')
408 endif
409 let lineend=lineend-mpos
410 let line=strpart(line,mpos,lineend)
411 endif
412 endwhile
413 while (!s:EmptystackP('b:lineTagStack'))
414 let tag=s:Pop('b:lineTagStack')
415 if match(tag, '^/') == 0 "found end tag
416 call s:Push(tag,'b:TagStack')
417 elseif s:EmptystackP('b:TagStack') && !s:Instack(tag, a:unaryTagsStack) "found unclosed tag
418 return tag
419 else
420 let endtag=s:Peekstack('b:TagStack')
421 if endtag == '/'.tag || endtag == '/'
422 call s:Pop('b:TagStack') "found a open/close tag pair
423 elseif !s:Instack(tag, a:unaryTagsStack) "we have a mismatch error
424 return ''
425 endif
426 endif
427 endwhile
428 let linenum=linenum-1 | let first=0
429 endwhile
430return ''
431endfunction
432
433function! s:InComment()
Bram Moolenaar8b6144b2006-02-08 09:20:24 +0000434 return synIDattr(synID(line('.'), col('.'), 0), 'name') =~ 'Comment\|String'
Bram Moolenaara5792f52005-11-23 21:25:05 +0000435endfunction
436
437function! s:InCommentAt(line, col)
Bram Moolenaar8b6144b2006-02-08 09:20:24 +0000438 return synIDattr(synID(a:line, a:col, 0), 'name') =~ 'Comment\|String'
Bram Moolenaara5792f52005-11-23 21:25:05 +0000439endfunction
440
441function! s:SetKeywords()
442 let g:IsKeywordBak=&iskeyword
443 let &iskeyword='33-255'
444endfunction
445
446function! s:RestoreKeywords()
447 let &iskeyword=g:IsKeywordBak
448endfunction
449
450function! s:Push(el, sname)
451 if !s:EmptystackP(a:sname)
452 exe 'let '.a:sname."=a:el.' '.".a:sname
453 else
454 exe 'let '.a:sname.'=a:el'
455 endif
456endfunction
457
458function! s:EmptystackP(sname)
459 exe 'let stack='.a:sname
460 if match(stack,'^ *$') == 0
461 return 1
462 else
463 return 0
464 endif
465endfunction
466
467function! s:Instack(el, sname)
468 exe 'let stack='.a:sname
469 call s:SetKeywords()
470 let m=match(stack, '\<'.a:el.'\>')
471 call s:RestoreKeywords()
472 if m < 0
473 return 0
474 else
475 return 1
476 endif
477endfunction
478
479function! s:Peekstack(sname)
480 call s:SetKeywords()
481 exe 'let stack='.a:sname
482 let top=matchstr(stack, '\<.\{-1,}\>')
483 call s:RestoreKeywords()
484 return top
485endfunction
486
487function! s:Pop(sname)
488 if s:EmptystackP(a:sname)
489 return ''
490 endif
491 exe 'let stack='.a:sname
492 call s:SetKeywords()
493 let loc=matchend(stack,'\<.\{-1,}\>')
494 exe 'let '.a:sname.'=strpart(stack, loc+1, strlen(stack))'
495 let top=strpart(stack, match(stack, '\<'), loc)
496 call s:RestoreKeywords()
497 return top
498endfunction
499
500function! s:Clearstack(sname)
501 exe 'let '.a:sname."=''"
502endfunction