1" Vim completion script 2" Language: XML 3" Maintainer: Mikolaj Machowski ( mikmach AT wp DOT pl ) 4" Last Change: 2006 Feb 6 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 curline = line('.') 58 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)] 73 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<.*') 95 96 " Make sure we will have only current namespace 97 unlet! b:xml_namespace 98 let b:xml_namespace = matchstr(b:compl_context, '^<\zs\k*\ze:') 99 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 114 if len(b:compl_context) == 0 && !exists("b:entitiescompl") 115 return [] 116 endif 117 let context = matchstr(b:compl_context, '^<\zs.*') 118 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 139 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 154 155 return res + res2 156 endif 157 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 233 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$' 234 call add(res, m) 235 elseif m =~ '=' 236 call add(res, m) 237 else 238 call add(res, m.'="') 239 endif 240 elseif m =~ attr 241 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$' 242 call add(res, m) 243 elseif m =~ '=' 244 call add(res, m) 245 else 246 call add(res2, m.'="') 247 endif 248 endif 249 endfor 250 251 return res + res2 252 253 endif 254 " Close tag 255 let b:unaryTagsStack = "base meta link hr br param img area input col" 256 if context =~ '^\/' 257 let opentag = xmlcomplete#GetLastOpenTag("b:unaryTagsStack") 258 return [opentag.">"] 259 endif 260 261 " Complete elements of XML structure 262 " TODO: #REQUIRED, #IMPLIED, #FIXED, #PCDATA - but these should be detected like 263 " entities - in first run 264 " keywords: CDATA, ID, IDREF, IDREFS, ENTITY, ENTITIES, NMTOKEN, NMTOKENS 265 " are hardly recognizable but keep it in reserve 266 " also: EMPTY ANY SYSTEM PUBLIC DATA 267 if context =~ '^!' 268 let tags = ['!ELEMENT', '!DOCTYPE', '!ATTLIST', '!ENTITY', '!NOTATION', '![CDATA[', '![INCLUDE[', '![IGNORE['] 269 270 for m in tags 271 if m =~ '^'.context 272 let m = substitute(m, '^!\[\?', '', '') 273 call add(res, m) 274 elseif m =~ context 275 let m = substitute(m, '^!\[\?', '', '') 276 call add(res2, m) 277 endif 278 endfor 279 280 return res + res2 281 282 endif 283 284 " Complete text declaration 285 let g:co = context 286 if context =~ '^?' 287 let tags = ['?xml'] 288 289 for m in tags 290 if m =~ '^'.context 291 call add(res, substitute(m, '^?', '', '')) 292 elseif m =~ context 293 call add(res, substitute(m, '^?', '', '')) 294 endif 295 endfor 296 297 return res + res2 298 299 endif 300 301 " Deal with tag completion. 302 let opentag = xmlcomplete#GetLastOpenTag("b:unaryTagsStack") 303 let opentag = substitute(opentag, '^\k*:', '', '') 304 if opentag == '' 305 return [] 306 endif 307 308 let tags = g:xmldata{'_'.g:xmldata_connection[b:xml_namespace]}[opentag][0] 309 let context = substitute(context, '^\k*:', '', '') 310 311 if b:xml_namespace == 'DEFAULT' 312 let b:xml_namespace = '' 313 else 314 let b:xml_namespace .= ':' 315 endif 316 317 for m in tags 318 if m =~ '^'.context 319 call add(res, b:xml_namespace.m) 320 elseif m =~ context 321 call add(res2, b:xml_namespace.m) 322 endif 323 endfor 324 325 return res + res2 326 327 endif 328endfunction 329 330" MM: This is greatly reduced closetag.vim used with kind permission of Steven 331" Mueller 332" Changes: strip all comments; delete error messages; add checking for 333" namespace 334" Author: Steven Mueller <[email protected]> 335" Last Modified: Tue May 24 13:29:48 PDT 2005 336" Version: 0.9.1 337 338function! xmlcomplete#GetLastOpenTag(unaryTagsStack) 339 let linenum=line('.') 340 let lineend=col('.') - 1 " start: cursor position 341 let first=1 " flag for first line searched 342 let b:TagStack='' " main stack of tags 343 let startInComment=s:InComment() 344 345 if exists("b:xml_namespace") 346 if b:xml_namespace == 'DEFAULT' 347 let tagpat='</\=\(\k\|[.-]\)\+\|/>' 348 else 349 let tagpat='</\='.b:xml_namespace.':\(\k\|[.-]\)\+\|/>' 350 endif 351 else 352 let tagpat='</\=\(\k\|[.-]\)\+\|/>' 353 endif 354 while (linenum>0) 355 let line=getline(linenum) 356 if first 357 let line=strpart(line,0,lineend) 358 else 359 let lineend=strlen(line) 360 endif 361 let b:lineTagStack='' 362 let mpos=0 363 let b:TagCol=0 364 while (mpos > -1) 365 let mpos=matchend(line,tagpat) 366 if mpos > -1 367 let b:TagCol=b:TagCol+mpos 368 let tag=matchstr(line,tagpat) 369 370 if exists('b:closetag_disable_synID') || startInComment==s:InCommentAt(linenum, b:TagCol) 371 let b:TagLine=linenum 372 call s:Push(matchstr(tag,'[^<>]\+'),'b:lineTagStack') 373 endif 374 let lineend=lineend-mpos 375 let line=strpart(line,mpos,lineend) 376 endif 377 endwhile 378 while (!s:EmptystackP('b:lineTagStack')) 379 let tag=s:Pop('b:lineTagStack') 380 if match(tag, '^/') == 0 "found end tag 381 call s:Push(tag,'b:TagStack') 382 elseif s:EmptystackP('b:TagStack') && !s:Instack(tag, a:unaryTagsStack) "found unclosed tag 383 return tag 384 else 385 let endtag=s:Peekstack('b:TagStack') 386 if endtag == '/'.tag || endtag == '/' 387 call s:Pop('b:TagStack') "found a open/close tag pair 388 elseif !s:Instack(tag, a:unaryTagsStack) "we have a mismatch error 389 return '' 390 endif 391 endif 392 endwhile 393 let linenum=linenum-1 | let first=0 394 endwhile 395return '' 396endfunction 397 398function! s:InComment() 399 return synIDattr(synID(line('.'), col('.'), 0), 'name') =~ 'Comment\|String' 400endfunction 401 402function! s:InCommentAt(line, col) 403 return synIDattr(synID(a:line, a:col, 0), 'name') =~ 'Comment\|String' 404endfunction 405 406function! s:SetKeywords() 407 let g:IsKeywordBak=&iskeyword 408 let &iskeyword='33-255' 409endfunction 410 411function! s:RestoreKeywords() 412 let &iskeyword=g:IsKeywordBak 413endfunction 414 415function! s:Push(el, sname) 416 if !s:EmptystackP(a:sname) 417 exe 'let '.a:sname."=a:el.' '.".a:sname 418 else 419 exe 'let '.a:sname.'=a:el' 420 endif 421endfunction 422 423function! s:EmptystackP(sname) 424 exe 'let stack='.a:sname 425 if match(stack,'^ *$') == 0 426 return 1 427 else 428 return 0 429 endif 430endfunction 431 432function! s:Instack(el, sname) 433 exe 'let stack='.a:sname 434 call s:SetKeywords() 435 let m=match(stack, '\<'.a:el.'\>') 436 call s:RestoreKeywords() 437 if m < 0 438 return 0 439 else 440 return 1 441 endif 442endfunction 443 444function! s:Peekstack(sname) 445 call s:SetKeywords() 446 exe 'let stack='.a:sname 447 let top=matchstr(stack, '\<.\{-1,}\>') 448 call s:RestoreKeywords() 449 return top 450endfunction 451 452function! s:Pop(sname) 453 if s:EmptystackP(a:sname) 454 return '' 455 endif 456 exe 'let stack='.a:sname 457 call s:SetKeywords() 458 let loc=matchend(stack,'\<.\{-1,}\>') 459 exe 'let '.a:sname.'=strpart(stack, loc+1, strlen(stack))' 460 let top=strpart(stack, match(stack, '\<'), loc) 461 call s:RestoreKeywords() 462 return top 463endfunction 464 465function! s:Clearstack(sname) 466 exe 'let '.a:sname."=''" 467endfunction 468