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