blob: 2d09eff096c4c9886f2b3d2667d044c44194830a [file] [log] [blame]
Bram Moolenaara5792f52005-11-23 21:25:05 +00001" Vim completion script
2" Language: XHTML 1.0 Strict
3" Maintainer: Mikolaj Machowski ( mikmach AT wp DOT pl )
4" Last Change: 2005 Nov 22
5
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
57 let line = getline('.')
58 let start = col('.') - 1
59 let compl_begin = col('.') - 2
60
61 while start >= 0 && line[start - 1] =~ '\(\k\|[:.-]\)'
62 let start -= 1
63 endwhile
64
65 if start >= 0 && line[start - 1] =~ '&'
66 let b:entitiescompl = 1
67 let b:compl_context = ''
68 return start
69 endif
70
71 let b:compl_context = getline('.')[0:(compl_begin)]
72 let b:compl_context = matchstr(b:compl_context, '.*<\zs.*')
73
74 " Make sure we will have only current namespace
75 unlet! b:xml_namespace
76 let b:xml_namespace = matchstr(b:compl_context, '^\k*\ze:')
77 if b:xml_namespace == ''
78 let b:xml_namespace = 'DEFAULT'
79 endif
80
81 return start
82
83 else
84 " There is no connction of namespace and data file. Abandon action
85 if !exists("g:xmldata_connection") || g:xmldata_connection == {}
86 return []
87 endif
88 " Initialize base return lists
89 let res = []
90 let res2 = []
91 " a:base is very short - we need context
92 let context = b:compl_context
93 unlet! b:compl_context
94
95 " Make entities completion
96 if exists("b:entitiescompl")
97 unlet! b:entitiescompl
98
99 if !exists("g:xmldata_entconnect") || g:xmldata_entconnect == 'DEFAULT'
100 let values = g:xmldata{'_'.g:xmldata_connection['DEFAULT']}['vimxmlentities']
101 else
102 let values = g:xmldata{'_'.g:xmldata_entconnect}['vimxmlentities']
103 endif
104
105 " Get only lines with entity declarations but throw out
106 " parameter-entities - they may be completed in future
107 let entdecl = filter(getline(1, "$"), 'v:val =~ "<!ENTITY\\s\\+[^%]"')
108
109 if len(entdecl) > 0
110 let intent = map(copy(entdecl), 'matchstr(v:val, "<!ENTITY\\s\\+\\zs\\(\\k\\|[.-:]\\)\\+\\ze")')
111 let values = intent + values
112 endif
113
114 for m in values
115 if m =~ '^'.a:base
116 call add(res, m.';')
117 endif
118 endfor
119
120 return res
121
122 endif
123 if context =~ '>'
124 " Generally if context contains > it means we are outside of tag and
125 " should abandon action
126 return []
127 endif
128
129 " find tags matching with "a:base"
130 " If a:base contains white space it is attribute.
131 " It could be also value of attribute...
132 " We have to get first word to offer
133 " proper completions
134 if context == ''
135 let tag = ''
136 else
137 let tag = split(context)[0]
138 endif
139 " Get rid of namespace
140 let tag = substitute(tag, '^'.b:xml_namespace.':', '', '')
141
142
143 " Get last word, it should be attr name
144 let attr = matchstr(context, '.*\s\zs.*')
145 " Possible situations where any prediction would be difficult:
146 " 1. Events attributes
147 if context =~ '\s'
148
149 " If attr contains =\s*[\"'] we catched value of attribute
150 if attr =~ "=\s*[\"']"
151 " Let do attribute specific completion
152 let attrname = matchstr(attr, '.*\ze\s*=')
153 let entered_value = matchstr(attr, ".*=\\s*[\"']\\zs.*")
154
155 if tag =~ '^[?!]'
156 " Return nothing if we are inside of ! or ? tag
157 return []
158 else
159 let values = g:xmldata{'_'.g:xmldata_connection[b:xml_namespace]}[tag][1][attrname]
160 endif
161
162 if len(values) == 0
163 return []
164 endif
165
166 " We need special version of sbase
167 let attrbase = matchstr(context, ".*[\"']")
168 let attrquote = matchstr(attrbase, '.$')
169
170 for m in values
171 " This if is needed to not offer all completions as-is
172 " alphabetically but sort them. Those beginning with entered
173 " part will be as first choices
174 if m =~ '^'.entered_value
175 call add(res, m . attrquote.' ')
176 elseif m =~ entered_value
177 call add(res2, m . attrquote.' ')
178 endif
179 endfor
180
181 return res + res2
182
183 endif
184
185 if tag =~ '?xml'
186 " Two possible arguments for <?xml> plus variation
187 let attrs = ['encoding', 'version="1.0"', 'version']
188 elseif tag =~ '^!'
189 " Don't make completion at all
190 return []
191 else
192 let attrs = keys(g:xmldata{'_'.g:xmldata_connection[b:xml_namespace]}[tag][1])
193 endif
194
195 for m in sort(attrs)
196 if m =~ '^'.attr
197 if tag !~ '^[?!]' && len(g:xmldata{'_'.g:xmldata_connection[b:xml_namespace]}[tag][1][m]) > 0 && g:xmldata{'_'.g:xmldata_connection[b:xml_namespace]}[tag][1][m][0] =~ '^BOOL$'
198 call add(res, m)
199 elseif m =~ '='
200 call add(res, m)
201 else
202 call add(res, m.'="')
203 endif
204 elseif m =~ attr
205 if tag !~ '^[?!]' && len(g:xmldata{'_'.g:xmldata_connection[b:xml_namespace]}[tag][1][m]) > 0 && g:xmldata{'_'.g:xmldata_connection[b:xml_namespace]}[tag][1][m][0] =~ '^BOOL$'
206 call add(res, m)
207 elseif m =~ '='
208 call add(res, m)
209 else
210 call add(res2, m.'="')
211 endif
212 endif
213 endfor
214
215 return res + res2
216
217 endif
218 " Close tag
219 let b:unaryTagsStack = "base meta link hr br param img area input col"
220 if context =~ '^\/'
221 let opentag = xmlcomplete#GetLastOpenTag("b:unaryTagsStack")
222 return [opentag.">"]
223 endif
224
225 " Complete elements of XML structure
226 " TODO: #REQUIRED, #IMPLIED, #FIXED, #PCDATA - but these should be detected like
227 " entities - in first run
228 " keywords: CDATA, ID, IDREF, IDREFS, ENTITY, ENTITIES, NMTOKEN, NMTOKENS
229 " are hardly recognizable but keep it in reserve
230 " also: EMPTY ANY SYSTEM PUBLIC DATA
231 if context =~ '^!'
232 let tags = ['!ELEMENT', '!DOCTYPE', '!ATTLIST', '!ENTITY', '!NOTATION', '![CDATA[', '![INCLUDE[', '![IGNORE[']
233
234 for m in tags
235 if m =~ '^'.context
236 let m = substitute(m, '^!\[\?', '', '')
237 call add(res, m)
238 elseif m =~ context
239 let m = substitute(m, '^!\[\?', '', '')
240 call add(res2, m)
241 endif
242 endfor
243
244 return res + res2
245
246 endif
247
248 " Complete text declaration
249 let g:co = context
250 if context =~ '^?'
251 let tags = ['?xml']
252
253 for m in tags
254 if m =~ '^'.context
255 call add(res, substitute(m, '^?', '', ''))
256 elseif m =~ context
257 call add(res, substitute(m, '^?', '', ''))
258 endif
259 endfor
260
261 return res + res2
262
263 endif
264
265 " Deal with tag completion.
266 let opentag = xmlcomplete#GetLastOpenTag("b:unaryTagsStack")
267 let opentag = substitute(opentag, '^\k*:', '', '')
268
269 let tags = g:xmldata{'_'.g:xmldata_connection[b:xml_namespace]}[opentag][0]
270 let context = substitute(context, '^\k*:', '', '')
271
272 if b:xml_namespace == 'DEFAULT'
273 let b:xml_namespace = ''
274 else
275 let b:xml_namespace .= ':'
276 endif
277
278 for m in tags
279 if m =~ '^'.context
280 call add(res, b:xml_namespace.m)
281 elseif m =~ context
282 call add(res2, b:xml_namespace.m)
283 endif
284 endfor
285
286 return res + res2
287
288 endif
289endfunction
290
291" MM: This is greatly reduced closetag.vim used with kind permission of Steven
292" Mueller
293" Changes: strip all comments; delete error messages; add checking for
294" namespace
295" Author: Steven Mueller <diffusor@ugcs.caltech.edu>
296" Last Modified: Tue May 24 13:29:48 PDT 2005
297" Version: 0.9.1
298
299function! xmlcomplete#GetLastOpenTag(unaryTagsStack)
300 let linenum=line('.')
301 let lineend=col('.') - 1 " start: cursor position
302 let first=1 " flag for first line searched
303 let b:TagStack='' " main stack of tags
304 let startInComment=s:InComment()
305
306 if exists("b:xml_namespace")
307 if b:xml_namespace == 'DEFAULT'
308 let tagpat='</\=\(\k\|[.-]\)\+\|/>'
309 else
310 let tagpat='</\='.b:xml_namespace.':\(\k\|[.-]\)\+\|/>'
311 endif
312 else
Bram Moolenaar9372a112005-12-06 19:59:18 +0000313 let tagpat='</\=\(\k\|[.-]\)\+\|/>'
Bram Moolenaara5792f52005-11-23 21:25:05 +0000314 endif
315 while (linenum>0)
316 let line=getline(linenum)
317 if first
318 let line=strpart(line,0,lineend)
319 else
320 let lineend=strlen(line)
321 endif
322 let b:lineTagStack=''
323 let mpos=0
324 let b:TagCol=0
325 while (mpos > -1)
326 let mpos=matchend(line,tagpat)
327 if mpos > -1
328 let b:TagCol=b:TagCol+mpos
329 let tag=matchstr(line,tagpat)
330
331 if exists('b:closetag_disable_synID') || startInComment==s:InCommentAt(linenum, b:TagCol)
332 let b:TagLine=linenum
333 call s:Push(matchstr(tag,'[^<>]\+'),'b:lineTagStack')
334 endif
335 let lineend=lineend-mpos
336 let line=strpart(line,mpos,lineend)
337 endif
338 endwhile
339 while (!s:EmptystackP('b:lineTagStack'))
340 let tag=s:Pop('b:lineTagStack')
341 if match(tag, '^/') == 0 "found end tag
342 call s:Push(tag,'b:TagStack')
343 elseif s:EmptystackP('b:TagStack') && !s:Instack(tag, a:unaryTagsStack) "found unclosed tag
344 return tag
345 else
346 let endtag=s:Peekstack('b:TagStack')
347 if endtag == '/'.tag || endtag == '/'
348 call s:Pop('b:TagStack') "found a open/close tag pair
349 elseif !s:Instack(tag, a:unaryTagsStack) "we have a mismatch error
350 return ''
351 endif
352 endif
353 endwhile
354 let linenum=linenum-1 | let first=0
355 endwhile
356return ''
357endfunction
358
359function! s:InComment()
360 return synIDattr(synID(line('.'), col('.'), 0), 'name') =~ 'Comment'
361endfunction
362
363function! s:InCommentAt(line, col)
364 return synIDattr(synID(a:line, a:col, 0), 'name') =~ 'Comment'
365endfunction
366
367function! s:SetKeywords()
368 let g:IsKeywordBak=&iskeyword
369 let &iskeyword='33-255'
370endfunction
371
372function! s:RestoreKeywords()
373 let &iskeyword=g:IsKeywordBak
374endfunction
375
376function! s:Push(el, sname)
377 if !s:EmptystackP(a:sname)
378 exe 'let '.a:sname."=a:el.' '.".a:sname
379 else
380 exe 'let '.a:sname.'=a:el'
381 endif
382endfunction
383
384function! s:EmptystackP(sname)
385 exe 'let stack='.a:sname
386 if match(stack,'^ *$') == 0
387 return 1
388 else
389 return 0
390 endif
391endfunction
392
393function! s:Instack(el, sname)
394 exe 'let stack='.a:sname
395 call s:SetKeywords()
396 let m=match(stack, '\<'.a:el.'\>')
397 call s:RestoreKeywords()
398 if m < 0
399 return 0
400 else
401 return 1
402 endif
403endfunction
404
405function! s:Peekstack(sname)
406 call s:SetKeywords()
407 exe 'let stack='.a:sname
408 let top=matchstr(stack, '\<.\{-1,}\>')
409 call s:RestoreKeywords()
410 return top
411endfunction
412
413function! s:Pop(sname)
414 if s:EmptystackP(a:sname)
415 return ''
416 endif
417 exe 'let stack='.a:sname
418 call s:SetKeywords()
419 let loc=matchend(stack,'\<.\{-1,}\>')
420 exe 'let '.a:sname.'=strpart(stack, loc+1, strlen(stack))'
421 let top=strpart(stack, match(stack, '\<'), loc)
422 call s:RestoreKeywords()
423 return top
424endfunction
425
426function! s:Clearstack(sname)
427 exe 'let '.a:sname."=''"
428endfunction