1" Vim completion script 2" Language: XML 3" Maintainer: Mikolaj Machowski ( mikmach AT wp DOT pl ) 4" Last Change: 2006 Mar 19 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 =~ '>[^<]*$' || i == curline 85 " Normal tag line, no need for completion at all 86 " OR reached first line without tag at all 87 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<.*') 96 97 " Make sure we will have only current namespace 98 unlet! b:xml_namespace 99 let b:xml_namespace = matchstr(b:compl_context, '^<\zs\k*\ze:') 100 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 115 if len(b:compl_context) == 0 && !exists("b:entitiescompl") 116 return [] 117 endif 118 let context = matchstr(b:compl_context, '^<\zs.*') 119 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 140 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 155 156 return res + res2 157 endif 158 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 234 call add(res, m) 235 elseif m =~ attr 236 call add(res2, m) 237 endif 238 endfor 239 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 270 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*:', '', '') 322 if opentag == '' 323 "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] 328 endif 329 330 let context = substitute(context, '^\k*:', '', '') 331 332 for m in tags 333 if m =~ '^'.context 334 call add(res, m) 335 elseif m =~ context 336 call add(res2, m) 337 endif 338 endfor 339 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 362 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 <[email protected]> 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 388 let tagpat='</\=\(\k\|[.-]\)\+\|/>' 389 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() 435 return synIDattr(synID(line('.'), col('.'), 0), 'name') =~ 'Comment\|String' 436endfunction 437 438function! s:InCommentAt(line, col) 439 return synIDattr(synID(a:line, a:col, 0), 'name') =~ 'Comment\|String' 440endfunction 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 504