1" Vim completion script 2" Language: XML 3" Maintainer: Mikolaj Machowski ( mikmach AT wp DOT pl ) 4" Last Change: 2006 Feb 18 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 call add(res, m) 234 elseif m =~ attr 235 call add(res2, m) 236 endif 237 endfor 238 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 269 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*:', '', '') 321 if opentag == '' 322 "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] 327 endif 328 329 let context = substitute(context, '^\k*:', '', '') 330 331 for m in tags 332 if m =~ '^'.context 333 call add(res, m) 334 elseif m =~ context 335 call add(res2, m) 336 endif 337 endfor 338 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 361 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 <[email protected]> 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 387 let tagpat='</\=\(\k\|[.-]\)\+\|/>' 388 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() 434 return synIDattr(synID(line('.'), col('.'), 0), 'name') =~ 'Comment\|String' 435endfunction 436 437function! s:InCommentAt(line, col) 438 return synIDattr(synID(a:line, a:col, 0), 'name') =~ 'Comment\|String' 439endfunction 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 503