1" 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 <[email protected]> 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 313 let tagpat='</\=\(\k\|[.-]\)\+\|/>' 314 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 429