xmlcomplete.vim 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540
  1. " Vim completion script
  2. " Language: XML
  3. " Maintainer: Mikolaj Machowski ( mikmach AT wp DOT pl )
  4. " Last Change: 2013 Jun 29
  5. " Version: 1.9
  6. "
  7. " Changelog:
  8. " 1.9 - 2007 Aug 15
  9. " - fix closing of namespaced tags (Johannes Weiss)
  10. " 1.8 - 2006 Jul 18
  11. " - allow for closing of xml tags even when data file isn't available
  12. " This function will create Dictionary with users namespace strings and values
  13. " canonical (system) names of data files. Names should be lowercase,
  14. " descriptive to avoid any future conflicts. For example 'xhtml10s' should be
  15. " name for data of XHTML 1.0 Strict and 'xhtml10t' for XHTML 1.0 Transitional
  16. " User interface will be provided by XMLns command defined in ftplugin/xml.vim
  17. " Currently supported canonicals are:
  18. " xhtml10s - XHTML 1.0 Strict
  19. " xsl - XSL
  20. function! xmlcomplete#CreateConnection(canonical, ...) " {{{
  21. " When only one argument provided treat name as default namespace (without
  22. " 'prefix:').
  23. if exists("a:1")
  24. let users = a:1
  25. else
  26. let users = 'DEFAULT'
  27. endif
  28. " Source data file. Due to suspected errors in autoload do it with
  29. " :runtime.
  30. " TODO: make it properly (using autoload, that is) later
  31. exe "runtime autoload/xml/".a:canonical.".vim"
  32. " Remove all traces of unexisting files to return [] when trying
  33. " omnicomplete something
  34. " TODO: give warning about non-existing canonicals - should it be?
  35. if !exists("g:xmldata_".a:canonical)
  36. unlet! g:xmldata_connection
  37. return 0
  38. endif
  39. " We need to initialize Dictionary to add key-value pair
  40. if !exists("g:xmldata_connection")
  41. let g:xmldata_connection = {}
  42. endif
  43. let g:xmldata_connection[users] = a:canonical
  44. endfunction
  45. " }}}
  46. function! xmlcomplete#CreateEntConnection(...) " {{{
  47. if a:0 > 0
  48. let g:xmldata_entconnect = a:1
  49. else
  50. let g:xmldata_entconnect = 'DEFAULT'
  51. endif
  52. endfunction
  53. " }}}
  54. function! 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. while start >= 0 && line[start - 1] =~ '\(\k\|[:.-]\)'
  62. let start -= 1
  63. endwhile
  64. if start >= 0 && line[start - 1] =~ '&'
  65. let b:entitiescompl = 1
  66. let b:compl_context = ''
  67. return start
  68. endif
  69. let b:compl_context = getline('.')[0:(compl_begin)]
  70. if b:compl_context !~ '<[^>]*$'
  71. " Look like we may have broken tag. Check previous lines. Up to
  72. " 10?
  73. let i = 1
  74. while 1
  75. let context_line = getline(curline-i)
  76. if context_line =~ '<[^>]*$'
  77. " Yep, this is this line
  78. let context_lines = getline(curline-i, curline-1) + [b:compl_context]
  79. let b:compl_context = join(context_lines, ' ')
  80. break
  81. elseif context_line =~ '>[^<]*$' || i == curline
  82. " Normal tag line, no need for completion at all
  83. " OR reached first line without tag at all
  84. let b:compl_context = ''
  85. break
  86. endif
  87. let i += 1
  88. endwhile
  89. " Make sure we don't have counter
  90. unlet! i
  91. endif
  92. let b:compl_context = matchstr(b:compl_context, '.*\zs<.*')
  93. " Make sure we will have only current namespace
  94. unlet! b:xml_namespace
  95. let b:xml_namespace = matchstr(b:compl_context, '^<\zs\k*\ze:')
  96. if b:xml_namespace == ''
  97. let b:xml_namespace = 'DEFAULT'
  98. endif
  99. return start
  100. else
  101. " Initialize base return lists
  102. let res = []
  103. let res2 = []
  104. " a:base is very short - we need context
  105. if len(b:compl_context) == 0 && !exists("b:entitiescompl")
  106. return []
  107. endif
  108. let context = matchstr(b:compl_context, '^<\zs.*')
  109. unlet! b:compl_context
  110. " There is no connection of namespace and data file.
  111. if !exists("g:xmldata_connection") || g:xmldata_connection == {}
  112. " There is still possibility we may do something - eg. close tag
  113. let b:unaryTagsStack = "base meta link hr br param img area input col"
  114. if context =~ '^\/'
  115. let opentag = xmlcomplete#GetLastOpenTag("b:unaryTagsStack")
  116. return [opentag.">"]
  117. else
  118. return []
  119. endif
  120. endif
  121. " Make entities completion
  122. if exists("b:entitiescompl")
  123. unlet! b:entitiescompl
  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. " Get only lines with entity declarations but throw out
  130. " parameter-entities - they may be completed in future
  131. let entdecl = filter(getline(1, "$"), 'v:val =~ "<!ENTITY\\s\\+[^%]"')
  132. if len(entdecl) > 0
  133. let intent = map(copy(entdecl), 'matchstr(v:val, "<!ENTITY\\s\\+\\zs\\(\\k\\|[.-:]\\)\\+\\ze")')
  134. let values = intent + values
  135. endif
  136. if len(a:base) == 1
  137. for m in values
  138. if m =~ '^'.a:base
  139. call add(res, m.';')
  140. endif
  141. endfor
  142. return res
  143. else
  144. for m in values
  145. if m =~? '^'.a:base
  146. call add(res, m.';')
  147. elseif m =~? a:base
  148. call add(res2, m.';')
  149. endif
  150. endfor
  151. return res + res2
  152. endif
  153. endif
  154. if context =~ '>'
  155. " Generally if context contains > it means we are outside of tag and
  156. " should abandon action
  157. return []
  158. endif
  159. " find tags matching with "a:base"
  160. " If a:base contains white space it is attribute.
  161. " It could be also value of attribute...
  162. " We have to get first word to offer
  163. " proper completions
  164. if context == ''
  165. let tag = ''
  166. else
  167. let tag = split(context)[0]
  168. endif
  169. " Get rid of namespace
  170. let tag = substitute(tag, '^'.b:xml_namespace.':', '', '')
  171. " Get last word, it should be attr name
  172. let attr = matchstr(context, '.*\s\zs.*')
  173. " Possible situations where any prediction would be difficult:
  174. " 1. Events attributes
  175. if context =~ '\s'
  176. " If attr contains =\s*[\"'] we catch value of attribute
  177. if attr =~ "=\s*[\"']" || attr =~ "=\s*$"
  178. " Let do attribute specific completion
  179. let attrname = matchstr(attr, '.*\ze\s*=')
  180. let entered_value = matchstr(attr, ".*=\\s*[\"']\\?\\zs.*")
  181. if tag =~ '^[?!]'
  182. " Return nothing if we are inside of ! or ? tag
  183. return []
  184. else
  185. if has_key(g:xmldata{'_'.g:xmldata_connection[b:xml_namespace]}, tag) && has_key(g:xmldata{'_'.g:xmldata_connection[b:xml_namespace]}[tag][1], attrname)
  186. let values = g:xmldata{'_'.g:xmldata_connection[b:xml_namespace]}[tag][1][attrname]
  187. else
  188. return []
  189. endif
  190. endif
  191. if len(values) == 0
  192. return []
  193. endif
  194. " We need special version of sbase
  195. let attrbase = matchstr(context, ".*[\"']")
  196. let attrquote = matchstr(attrbase, '.$')
  197. if attrquote !~ "['\"]"
  198. let attrquoteopen = '"'
  199. let attrquote = '"'
  200. else
  201. let attrquoteopen = ''
  202. endif
  203. for m in values
  204. " This if is needed to not offer all completions as-is
  205. " alphabetically but sort them. Those beginning with entered
  206. " part will be as first choices
  207. if m =~ '^'.entered_value
  208. call add(res, attrquoteopen . m . attrquote.' ')
  209. elseif m =~ entered_value
  210. call add(res2, attrquoteopen . m . attrquote.' ')
  211. endif
  212. endfor
  213. return res + res2
  214. endif
  215. if tag =~ '?xml'
  216. " Two possible arguments for <?xml> plus variation
  217. let attrs = ['encoding', 'version="1.0"', 'version']
  218. elseif tag =~ '^!'
  219. " Don't make completion at all
  220. "
  221. return []
  222. else
  223. if !has_key(g:xmldata{'_'.g:xmldata_connection[b:xml_namespace]}, tag)
  224. " Abandon when data file isn't complete
  225. return []
  226. endif
  227. let attrs = keys(g:xmldata{'_'.g:xmldata_connection[b:xml_namespace]}[tag][1])
  228. endif
  229. for m in sort(attrs)
  230. if m =~ '^'.attr
  231. call add(res, m)
  232. elseif m =~ attr
  233. call add(res2, m)
  234. endif
  235. endfor
  236. let menu = res + res2
  237. let final_menu = []
  238. if has_key(g:xmldata{'_'.g:xmldata_connection[b:xml_namespace]}, 'vimxmlattrinfo')
  239. for i in range(len(menu))
  240. let item = menu[i]
  241. if has_key(g:xmldata{'_'.g:xmldata_connection[b:xml_namespace]}['vimxmlattrinfo'], item)
  242. let m_menu = g:xmldata{'_'.g:xmldata_connection[b:xml_namespace]}['vimxmlattrinfo'][item][0]
  243. let m_info = g:xmldata{'_'.g:xmldata_connection[b:xml_namespace]}['vimxmlattrinfo'][item][1]
  244. else
  245. let m_menu = ''
  246. let m_info = ''
  247. endif
  248. 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.'\)$'
  249. let item = item
  250. else
  251. let item .= '="'
  252. endif
  253. let final_menu += [{'word':item, 'menu':m_menu, 'info':m_info}]
  254. endfor
  255. else
  256. for i in range(len(menu))
  257. let item = menu[i]
  258. 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.'\)$'
  259. let item = item
  260. else
  261. let item .= '="'
  262. endif
  263. let final_menu += [item]
  264. endfor
  265. endif
  266. return final_menu
  267. endif
  268. " Close tag
  269. let b:unaryTagsStack = "base meta link hr br param img area input col"
  270. if context =~ '^\/'
  271. let opentag = xmlcomplete#GetLastOpenTag("b:unaryTagsStack")
  272. return [opentag.">"]
  273. endif
  274. " Complete elements of XML structure
  275. " TODO: #REQUIRED, #IMPLIED, #FIXED, #PCDATA - but these should be detected like
  276. " entities - in first run
  277. " keywords: CDATA, ID, IDREF, IDREFS, ENTITY, ENTITIES, NMTOKEN, NMTOKENS
  278. " are hardly recognizable but keep it in reserve
  279. " also: EMPTY ANY SYSTEM PUBLIC DATA
  280. if context =~ '^!'
  281. let tags = ['!ELEMENT', '!DOCTYPE', '!ATTLIST', '!ENTITY', '!NOTATION', '![CDATA[', '![INCLUDE[', '![IGNORE[']
  282. for m in tags
  283. if m =~ '^'.context
  284. let m = substitute(m, '^!\[\?', '', '')
  285. call add(res, m)
  286. elseif m =~ context
  287. let m = substitute(m, '^!\[\?', '', '')
  288. call add(res2, m)
  289. endif
  290. endfor
  291. return res + res2
  292. endif
  293. " Complete text declaration
  294. if context =~ '^?'
  295. let tags = ['?xml']
  296. for m in tags
  297. if m =~ '^'.context
  298. call add(res, substitute(m, '^?', '', ''))
  299. elseif m =~ context
  300. call add(res, substitute(m, '^?', '', ''))
  301. endif
  302. endfor
  303. return res + res2
  304. endif
  305. " Deal with tag completion.
  306. let opentag = xmlcomplete#GetLastOpenTag("b:unaryTagsStack")
  307. let opentag = substitute(opentag, '^\k*:', '', '')
  308. if opentag == ''
  309. "return []
  310. let tags = keys(g:xmldata{'_'.g:xmldata_connection[b:xml_namespace]})
  311. call filter(tags, 'v:val !~ "^vimxml"')
  312. else
  313. if !has_key(g:xmldata{'_'.g:xmldata_connection[b:xml_namespace]}, opentag)
  314. " Abandon when data file isn't complete
  315. return []
  316. endif
  317. let tags = g:xmldata{'_'.g:xmldata_connection[b:xml_namespace]}[opentag][0]
  318. endif
  319. let context = substitute(context, '^\k*:', '', '')
  320. for m in tags
  321. if m =~ '^'.context
  322. call add(res, m)
  323. elseif m =~ context
  324. call add(res2, m)
  325. endif
  326. endfor
  327. let menu = res + res2
  328. if b:xml_namespace == 'DEFAULT'
  329. let xml_namespace = ''
  330. else
  331. let xml_namespace = b:xml_namespace.':'
  332. endif
  333. if has_key(g:xmldata{'_'.g:xmldata_connection[b:xml_namespace]}, 'vimxmltaginfo')
  334. let final_menu = []
  335. for i in range(len(menu))
  336. let item = menu[i]
  337. if has_key(g:xmldata{'_'.g:xmldata_connection[b:xml_namespace]}['vimxmltaginfo'], item)
  338. let m_menu = g:xmldata{'_'.g:xmldata_connection[b:xml_namespace]}['vimxmltaginfo'][item][0]
  339. let m_info = g:xmldata{'_'.g:xmldata_connection[b:xml_namespace]}['vimxmltaginfo'][item][1]
  340. else
  341. let m_menu = ''
  342. let m_info = ''
  343. endif
  344. let final_menu += [{'word':xml_namespace.item, 'menu':m_menu, 'info':m_info}]
  345. endfor
  346. else
  347. let final_menu = map(menu, 'xml_namespace.v:val')
  348. endif
  349. return final_menu
  350. endif
  351. endfunction
  352. " MM: This is severely reduced closetag.vim used with kind permission of Steven
  353. " Mueller
  354. " Changes: strip all comments; delete error messages; add checking for
  355. " namespace
  356. " Author: Steven Mueller <diffusor@ugcs.caltech.edu>
  357. " Last Modified: Tue May 24 13:29:48 PDT 2005
  358. " Version: 0.9.1
  359. function! xmlcomplete#GetLastOpenTag(unaryTagsStack)
  360. let linenum=line('.')
  361. let lineend=col('.') - 1 " start: cursor position
  362. let first=1 " flag for first line searched
  363. let b:TagStack='' " main stack of tags
  364. let startInComment=s:InComment()
  365. if exists("b:xml_namespace")
  366. if b:xml_namespace == 'DEFAULT'
  367. let tagpat='</\=\(\k\|[.:-]\)\+\|/>'
  368. else
  369. let tagpat='</\='.b:xml_namespace.':\(\k\|[.-]\)\+\|/>'
  370. endif
  371. else
  372. let tagpat='</\=\(\k\|[.:-]\)\+\|/>'
  373. endif
  374. while (linenum>0)
  375. let line=getline(linenum)
  376. if first
  377. let line=strpart(line,0,lineend)
  378. else
  379. let lineend=strlen(line)
  380. endif
  381. let b:lineTagStack=''
  382. let mpos=0
  383. let b:TagCol=0
  384. while (mpos > -1)
  385. let mpos=matchend(line,tagpat)
  386. if mpos > -1
  387. let b:TagCol=b:TagCol+mpos
  388. let tag=matchstr(line,tagpat)
  389. if exists('b:closetag_disable_synID') || startInComment==s:InCommentAt(linenum, b:TagCol)
  390. let b:TagLine=linenum
  391. call s:Push(matchstr(tag,'[^<>]\+'),'b:lineTagStack')
  392. endif
  393. let lineend=lineend-mpos
  394. let line=strpart(line,mpos,lineend)
  395. endif
  396. endwhile
  397. while (!s:EmptystackP('b:lineTagStack'))
  398. let tag=s:Pop('b:lineTagStack')
  399. if match(tag, '^/') == 0 "found end tag
  400. call s:Push(tag,'b:TagStack')
  401. elseif s:EmptystackP('b:TagStack') && !s:Instack(tag, a:unaryTagsStack) "found unclosed tag
  402. return tag
  403. else
  404. let endtag=s:Peekstack('b:TagStack')
  405. if endtag == '/'.tag || endtag == '/'
  406. call s:Pop('b:TagStack') "found a open/close tag pair
  407. elseif !s:Instack(tag, a:unaryTagsStack) "we have a mismatch error
  408. return ''
  409. endif
  410. endif
  411. endwhile
  412. let linenum=linenum-1 | let first=0
  413. endwhile
  414. return ''
  415. endfunction
  416. function! s:InComment()
  417. return synIDattr(synID(line('.'), col('.'), 0), 'name') =~ 'Comment\|String'
  418. endfunction
  419. function! s:InCommentAt(line, col)
  420. return synIDattr(synID(a:line, a:col, 0), 'name') =~ 'Comment\|String'
  421. endfunction
  422. function! s:SetKeywords()
  423. let s:IsKeywordBak=&l:iskeyword
  424. let &l:iskeyword='33-255'
  425. endfunction
  426. function! s:RestoreKeywords()
  427. let &l:iskeyword=s:IsKeywordBak
  428. endfunction
  429. function! s:Push(el, sname)
  430. if !s:EmptystackP(a:sname)
  431. exe 'let '.a:sname."=a:el.' '.".a:sname
  432. else
  433. exe 'let '.a:sname.'=a:el'
  434. endif
  435. endfunction
  436. function! s:EmptystackP(sname)
  437. exe 'let stack='.a:sname
  438. if match(stack,'^ *$') == 0
  439. return 1
  440. else
  441. return 0
  442. endif
  443. endfunction
  444. function! s:Instack(el, sname)
  445. exe 'let stack='.a:sname
  446. call s:SetKeywords()
  447. let m=match(stack, '\<'.a:el.'\>')
  448. call s:RestoreKeywords()
  449. if m < 0
  450. return 0
  451. else
  452. return 1
  453. endif
  454. endfunction
  455. function! s:Peekstack(sname)
  456. call s:SetKeywords()
  457. exe 'let stack='.a:sname
  458. let top=matchstr(stack, '\<.\{-1,}\>')
  459. call s:RestoreKeywords()
  460. return top
  461. endfunction
  462. function! s:Pop(sname)
  463. if s:EmptystackP(a:sname)
  464. return ''
  465. endif
  466. exe 'let stack='.a:sname
  467. call s:SetKeywords()
  468. let loc=matchend(stack,'\<.\{-1,}\>')
  469. exe 'let '.a:sname.'=strpart(stack, loc+1, strlen(stack))'
  470. let top=strpart(stack, match(stack, '\<'), loc)
  471. call s:RestoreKeywords()
  472. return top
  473. endfunction
  474. function! s:Clearstack(sname)
  475. exe 'let '.a:sname."=''"
  476. endfunction
  477. " vim:set foldmethod=marker: