htmlcomplete.vim 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809
  1. " Vim completion script
  2. " Language: HTML and XHTML
  3. " Maintainer: Mikolaj Machowski ( mikmach AT wp DOT pl )
  4. " Last Change: 2014 Jun 20
  5. " Distinguish between HTML versions.
  6. " To use with other HTML versions add another "elseif" condition to match
  7. " proper DOCTYPE.
  8. function! htmlcomplete#DetectOmniFlavor()
  9. if &filetype == 'xhtml'
  10. let b:html_omni_flavor = 'xhtml10s'
  11. else
  12. let b:html_omni_flavor = 'html401t'
  13. endif
  14. let i = 1
  15. let line = ""
  16. while i < 10 && i < line("$")
  17. let line = getline(i)
  18. if line =~ '<!DOCTYPE.*\<DTD '
  19. break
  20. endif
  21. let i += 1
  22. endwhile
  23. if line =~ '<!DOCTYPE.*\<DTD ' " doctype line found above
  24. if line =~ ' HTML 3\.2'
  25. let b:html_omni_flavor = 'html32'
  26. elseif line =~ ' XHTML 1\.1'
  27. let b:html_omni_flavor = 'xhtml11'
  28. else " two-step detection with strict/frameset/transitional
  29. if line =~ ' XHTML 1\.0'
  30. let b:html_omni_flavor = 'xhtml10'
  31. elseif line =~ ' HTML 4\.01'
  32. let b:html_omni_flavor = 'html401'
  33. elseif line =~ ' HTML 4.0\>'
  34. let b:html_omni_flavor = 'html40'
  35. endif
  36. if line =~ '\<Transitional\>'
  37. let b:html_omni_flavor .= 't'
  38. elseif line =~ '\<Frameset\>'
  39. let b:html_omni_flavor .= 'f'
  40. else
  41. let b:html_omni_flavor .= 's'
  42. endif
  43. endif
  44. endif
  45. endfunction
  46. function! htmlcomplete#CompleteTags(findstart, base)
  47. if a:findstart
  48. " locate the start of the word
  49. let line = getline('.')
  50. let start = col('.') - 1
  51. let curline = line('.')
  52. let compl_begin = col('.') - 2
  53. while start >= 0 && line[start - 1] =~ '\(\k\|[!:.-]\)'
  54. let start -= 1
  55. endwhile
  56. " Handling of entities {{{
  57. if start >= 0 && line[start - 1] =~ '&'
  58. let b:entitiescompl = 1
  59. let b:compl_context = ''
  60. return start
  61. endif
  62. " }}}
  63. " Handling of <style> tag {{{
  64. let stylestart = searchpair('<style\>', '', '<\/style\>', "bnW")
  65. let styleend = searchpair('<style\>', '', '<\/style\>', "nW")
  66. if stylestart != 0 && styleend != 0
  67. if stylestart <= curline && styleend >= curline
  68. let start = col('.') - 1
  69. let b:csscompl = 1
  70. while start >= 0 && line[start - 1] =~ '\(\k\|-\)'
  71. let start -= 1
  72. endwhile
  73. endif
  74. endif
  75. " }}}
  76. " Handling of <script> tag {{{
  77. let scriptstart = searchpair('<script\>', '', '<\/script\>', "bnW")
  78. let scriptend = searchpair('<script\>', '', '<\/script\>', "nW")
  79. if scriptstart != 0 && scriptend != 0
  80. if scriptstart <= curline && scriptend >= curline
  81. let start = col('.') - 1
  82. let b:jscompl = 1
  83. let b:jsrange = [scriptstart, scriptend]
  84. while start >= 0 && line[start - 1] =~ '\k'
  85. let start -= 1
  86. endwhile
  87. " We are inside of <script> tag. But we should also get contents
  88. " of all linked external files and (secondary, less probably) other <script> tags
  89. " This logic could possible be done in separate function - may be
  90. " reused in events scripting (also with option could be reused for
  91. " CSS
  92. let b:js_extfiles = []
  93. let l = line('.')
  94. let c = col('.')
  95. call cursor(1,1)
  96. while search('<\@<=script\>', 'W') && line('.') <= l
  97. if synIDattr(synID(line('.'),col('.')-1,0),"name") !~? 'comment'
  98. let sname = matchstr(getline('.'), '<script[^>]*src\s*=\s*\([''"]\)\zs.\{-}\ze\1')
  99. if filereadable(sname)
  100. let b:js_extfiles += readfile(sname)
  101. endif
  102. endif
  103. endwhile
  104. call cursor(1,1)
  105. let js_scripttags = []
  106. while search('<script\>', 'W') && line('.') < l
  107. if matchstr(getline('.'), '<script[^>]*src') == ''
  108. let js_scripttag = getline(line('.'), search('</script>', 'W'))
  109. let js_scripttags += js_scripttag
  110. endif
  111. endwhile
  112. let b:js_extfiles += js_scripttags
  113. call cursor(l,c)
  114. unlet! l c
  115. endif
  116. endif
  117. " }}}
  118. if !exists("b:csscompl") && !exists("b:jscompl")
  119. let b:compl_context = getline('.')[0:(compl_begin)]
  120. if b:compl_context !~ '<[^>]*$'
  121. " Look like we may have broken tag. Check previous lines.
  122. let i = 1
  123. while 1
  124. let context_line = getline(curline-i)
  125. if context_line =~ '<[^>]*$'
  126. " Yep, this is this line
  127. let context_lines = getline(curline-i, curline-1) + [b:compl_context]
  128. let b:compl_context = join(context_lines, ' ')
  129. break
  130. elseif context_line =~ '>[^<]*$' || i == curline
  131. " We are in normal tag line, no need for completion at all
  132. " OR reached first line without tag at all
  133. let b:compl_context = ''
  134. break
  135. endif
  136. let i += 1
  137. endwhile
  138. " Make sure we don't have counter
  139. unlet! i
  140. endif
  141. let b:compl_context = matchstr(b:compl_context, '.*\zs<.*')
  142. " Return proper start for on-events. Without that beginning of
  143. " completion will be badly reported
  144. if b:compl_context =~? 'on[a-z]*\s*=\s*\(''[^'']*\|"[^"]*\)$'
  145. let start = col('.') - 1
  146. while start >= 0 && line[start - 1] =~ '\k'
  147. let start -= 1
  148. endwhile
  149. endif
  150. " If b:compl_context begins with <? we are inside of PHP code. It
  151. " wasn't closed so PHP completion passed it to HTML
  152. if &filetype =~? 'php' && b:compl_context =~ '^<?'
  153. let b:phpcompl = 1
  154. let start = col('.') - 1
  155. while start >= 0 && line[start - 1] =~ '[a-zA-Z_0-9\x7f-\xff$]'
  156. let start -= 1
  157. endwhile
  158. endif
  159. else
  160. let b:compl_context = getline('.')[0:compl_begin]
  161. endif
  162. return start
  163. else
  164. " Initialize base return lists
  165. let res = []
  166. let res2 = []
  167. " a:base is very short - we need context
  168. let context = b:compl_context
  169. " Check if we should do CSS completion inside of <style> tag
  170. " or JS completion inside of <script> tag or PHP completion in case of <?
  171. " tag AND &ft==php
  172. if exists("b:csscompl")
  173. unlet! b:csscompl
  174. let context = b:compl_context
  175. unlet! b:compl_context
  176. return csscomplete#CompleteCSS(0, context)
  177. elseif exists("b:jscompl")
  178. unlet! b:jscompl
  179. return javascriptcomplete#CompleteJS(0, a:base)
  180. elseif exists("b:phpcompl")
  181. unlet! b:phpcompl
  182. let context = b:compl_context
  183. return phpcomplete#CompletePHP(0, a:base)
  184. else
  185. if len(b:compl_context) == 0 && !exists("b:entitiescompl")
  186. return []
  187. endif
  188. let context = matchstr(b:compl_context, '.\zs.*')
  189. endif
  190. unlet! b:compl_context
  191. " Entities completion {{{
  192. if exists("b:entitiescompl")
  193. unlet! b:entitiescompl
  194. if !exists("b:html_doctype")
  195. call htmlcomplete#CheckDoctype()
  196. endif
  197. if !exists("b:html_omni")
  198. "runtime! autoload/xml/xhtml10s.vim
  199. call htmlcomplete#LoadData()
  200. endif
  201. let entities = b:html_omni['vimxmlentities']
  202. if len(a:base) == 1
  203. for m in entities
  204. if m =~ '^'.a:base
  205. call add(res, m.';')
  206. endif
  207. endfor
  208. return res
  209. else
  210. for m in entities
  211. if m =~? '^'.a:base
  212. call add(res, m.';')
  213. elseif m =~? a:base
  214. call add(res2, m.';')
  215. endif
  216. endfor
  217. return res + res2
  218. endif
  219. endif
  220. " }}}
  221. if context =~ '>'
  222. " Generally if context contains > it means we are outside of tag and
  223. " should abandon action - with one exception: <style> span { bo
  224. if context =~ 'style[^>]\{-}>[^<]\{-}$'
  225. return csscomplete#CompleteCSS(0, context)
  226. elseif context =~ 'script[^>]\{-}>[^<]\{-}$'
  227. let b:jsrange = [line('.'), search('<\/script\>', 'nW')]
  228. return javascriptcomplete#CompleteJS(0, context)
  229. else
  230. return []
  231. endif
  232. endif
  233. " If context contains > it means we are already outside of tag and we
  234. " should abandon action
  235. " If context contains white space it is attribute.
  236. " It can be also value of attribute.
  237. " We have to get first word to offer proper completions
  238. if context == ''
  239. let tag = ''
  240. else
  241. let tag = split(context)[0]
  242. " Detect if tag is uppercase to return in proper case,
  243. " we need to make it lowercase for processing
  244. if tag =~ '^[A-Z]*$'
  245. let uppercase_tag = 1
  246. let tag = tolower(tag)
  247. else
  248. let uppercase_tag = 0
  249. endif
  250. endif
  251. " Get last word, it should be attr name
  252. let attr = matchstr(context, '.*\s\zs.*')
  253. " Possible situations where any prediction would be difficult:
  254. " 1. Events attributes
  255. if context =~ '\s'
  256. " Sort out style, class, and on* cases
  257. if context =~? "\\(on[a-z]*\\|id\\|style\\|class\\)\\s*=\\s*[\"']"
  258. " Id, class completion {{{
  259. if context =~? "\\(id\\|class\\)\\s*=\\s*[\"'][a-zA-Z0-9_ -]*$"
  260. if context =~? "class\\s*=\\s*[\"'][a-zA-Z0-9_ -]*$"
  261. let search_for = "class"
  262. elseif context =~? "id\\s*=\\s*[\"'][a-zA-Z0-9_ -]*$"
  263. let search_for = "id"
  264. endif
  265. " Handle class name completion
  266. " 1. Find lines of <link stylesheet>
  267. " 1a. Check file for @import
  268. " 2. Extract filename(s?) of stylesheet,
  269. call cursor(1,1)
  270. let head = getline(search('<head\>'), search('<\/head>'))
  271. let headjoined = join(copy(head), ' ')
  272. if headjoined =~ '<style'
  273. " Remove possibly confusing CSS operators
  274. let stylehead = substitute(headjoined, '+>\*[,', ' ', 'g')
  275. if search_for == 'class'
  276. let styleheadlines = split(stylehead)
  277. let headclasslines = filter(copy(styleheadlines), "v:val =~ '\\([a-zA-Z0-9:]\\+\\)\\?\\.[a-zA-Z0-9_-]\\+'")
  278. else
  279. let stylesheet = split(headjoined, '[{}]')
  280. " Get all lines which fit id syntax
  281. let classlines = filter(copy(stylesheet), "v:val =~ '#[a-zA-Z0-9_-]\\+'")
  282. " Filter out possible color definitions
  283. call filter(classlines, "v:val !~ ':\\s*#[a-zA-Z0-9_-]\\+'")
  284. " Filter out complex border definitions
  285. call filter(classlines, "v:val !~ '\\(none\\|hidden\\|dotted\\|dashed\\|solid\\|double\\|groove\\|ridge\\|inset\\|outset\\)\\s*#[a-zA-Z0-9_-]\\+'")
  286. let templines = join(classlines, ' ')
  287. let headclasslines = split(templines)
  288. call filter(headclasslines, "v:val =~ '#[a-zA-Z0-9_-]\\+'")
  289. endif
  290. let internal = 1
  291. else
  292. let internal = 0
  293. endif
  294. let styletable = []
  295. let secimportfiles = []
  296. let filestable = filter(copy(head), "v:val =~ '\\(@import\\|link.*stylesheet\\)'")
  297. for line in filestable
  298. if line =~ "@import"
  299. let styletable += [matchstr(line, "import\\s\\+\\(url(\\)\\?[\"']\\?\\zs\\f\\+\\ze")]
  300. elseif line =~ "<link"
  301. let styletable += [matchstr(line, "href\\s*=\\s*[\"']\\zs\\f\\+\\ze")]
  302. endif
  303. endfor
  304. for file in styletable
  305. if filereadable(file)
  306. let stylesheet = readfile(file)
  307. let secimport = filter(copy(stylesheet), "v:val =~ '@import'")
  308. if len(secimport) > 0
  309. for line in secimport
  310. let secfile = matchstr(line, "import\\s\\+\\(url(\\)\\?[\"']\\?\\zs\\f\\+\\ze")
  311. let secfile = fnamemodify(file, ":p:h").'/'.secfile
  312. let secimportfiles += [secfile]
  313. endfor
  314. endif
  315. endif
  316. endfor
  317. let cssfiles = styletable + secimportfiles
  318. let classes = []
  319. for file in cssfiles
  320. let classlines = []
  321. if filereadable(file)
  322. let stylesheet = readfile(file)
  323. let stylefile = join(stylesheet, ' ')
  324. let stylefile = substitute(stylefile, '+>\*[,', ' ', 'g')
  325. if search_for == 'class'
  326. let stylesheet = split(stylefile)
  327. let classlines = filter(copy(stylesheet), "v:val =~ '\\([a-zA-Z0-9:]\\+\\)\\?\\.[a-zA-Z0-9_-]\\+'")
  328. else
  329. let stylesheet = split(stylefile, '[{}]')
  330. " Get all lines which fit id syntax
  331. let classlines = filter(copy(stylesheet), "v:val =~ '#[a-zA-Z0-9_-]\\+'")
  332. " Filter out possible color definitions
  333. call filter(classlines, "v:val !~ ':\\s*#[a-zA-Z0-9_-]\\+'")
  334. " Filter out complex border definitions
  335. call filter(classlines, "v:val !~ '\\(none\\|hidden\\|dotted\\|dashed\\|solid\\|double\\|groove\\|ridge\\|inset\\|outset\\)\\s*#[a-zA-Z0-9_-]\\+'")
  336. let templines = join(classlines, ' ')
  337. let stylelines = split(templines)
  338. let classlines = filter(stylelines, "v:val =~ '#[a-zA-Z0-9_-]\\+'")
  339. endif
  340. endif
  341. " We gathered classes definitions from all external files
  342. let classes += classlines
  343. endfor
  344. if internal == 1
  345. let classes += headclasslines
  346. endif
  347. if search_for == 'class'
  348. let elements = {}
  349. for element in classes
  350. if element =~ '^\.'
  351. let class = matchstr(element, '^\.\zs[a-zA-Z][a-zA-Z0-9_-]*\ze')
  352. let class = substitute(class, ':.*', '', '')
  353. if has_key(elements, 'common')
  354. let elements['common'] .= ' '.class
  355. else
  356. let elements['common'] = class
  357. endif
  358. else
  359. let class = matchstr(element, '[a-zA-Z1-6]*\.\zs[a-zA-Z][a-zA-Z0-9_-]*\ze')
  360. let tagname = tolower(matchstr(element, '[a-zA-Z1-6]*\ze.'))
  361. if tagname != ''
  362. if has_key(elements, tagname)
  363. let elements[tagname] .= ' '.class
  364. else
  365. let elements[tagname] = class
  366. endif
  367. endif
  368. endif
  369. endfor
  370. if has_key(elements, tag) && has_key(elements, 'common')
  371. let values = split(elements[tag]." ".elements['common'])
  372. elseif has_key(elements, tag) && !has_key(elements, 'common')
  373. let values = split(elements[tag])
  374. elseif !has_key(elements, tag) && has_key(elements, 'common')
  375. let values = split(elements['common'])
  376. else
  377. return []
  378. endif
  379. elseif search_for == 'id'
  380. " Find used IDs
  381. " 1. Catch whole file
  382. let filelines = getline(1, line('$'))
  383. " 2. Find lines with possible id
  384. let used_id_lines = filter(filelines, 'v:val =~ "id\\s*=\\s*[\"''][a-zA-Z0-9_-]\\+"')
  385. " 3a. Join all filtered lines
  386. let id_string = join(used_id_lines, ' ')
  387. " 3b. And split them to be sure each id is in separate item
  388. let id_list = split(id_string, 'id\s*=\s*')
  389. " 4. Extract id values
  390. let used_id = map(id_list, 'matchstr(v:val, "[\"'']\\zs[a-zA-Z0-9_-]\\+\\ze")')
  391. let joined_used_id = ','.join(used_id, ',').','
  392. let allvalues = map(classes, 'matchstr(v:val, ".*#\\zs[a-zA-Z0-9_-]\\+")')
  393. let values = []
  394. for element in classes
  395. if joined_used_id !~ ','.element.','
  396. let values += [element]
  397. endif
  398. endfor
  399. endif
  400. " We need special version of sbase
  401. let classbase = matchstr(context, ".*[\"']")
  402. let classquote = matchstr(classbase, '.$')
  403. let entered_class = matchstr(attr, ".*=\\s*[\"']\\zs.*")
  404. for m in sort(values)
  405. if m =~? '^'.entered_class
  406. call add(res, m . classquote)
  407. elseif m =~? entered_class
  408. call add(res2, m . classquote)
  409. endif
  410. endfor
  411. return res + res2
  412. elseif context =~? "style\\s*=\\s*[\"'][^\"']*$"
  413. return csscomplete#CompleteCSS(0, context)
  414. endif
  415. " }}}
  416. " Complete on-events {{{
  417. if context =~? 'on[a-z]*\s*=\s*\(''[^'']*\|"[^"]*\)$'
  418. " We have to:
  419. " 1. Find external files
  420. let b:js_extfiles = []
  421. let l = line('.')
  422. let c = col('.')
  423. call cursor(1,1)
  424. while search('<\@<=script\>', 'W') && line('.') <= l
  425. if synIDattr(synID(line('.'),col('.')-1,0),"name") !~? 'comment'
  426. let sname = matchstr(getline('.'), '<script[^>]*src\s*=\s*\([''"]\)\zs.\{-}\ze\1')
  427. if filereadable(sname)
  428. let b:js_extfiles += readfile(sname)
  429. endif
  430. endif
  431. endwhile
  432. " 2. Find at least one <script> tag
  433. call cursor(1,1)
  434. let js_scripttags = []
  435. while search('<script\>', 'W') && line('.') < l
  436. if matchstr(getline('.'), '<script[^>]*src') == ''
  437. let js_scripttag = getline(line('.'), search('</script>', 'W'))
  438. let js_scripttags += js_scripttag
  439. endif
  440. endwhile
  441. let b:js_extfiles += js_scripttags
  442. " 3. Proper call for javascriptcomplete#CompleteJS
  443. call cursor(l,c)
  444. let js_context = matchstr(a:base, '\k\+$')
  445. let js_shortcontext = substitute(a:base, js_context.'$', '', '')
  446. let b:compl_context = context
  447. let b:jsrange = [l, l]
  448. unlet! l c
  449. return javascriptcomplete#CompleteJS(0, js_context)
  450. endif
  451. " }}}
  452. let stripbase = matchstr(context, ".*\\(on[a-zA-Z]*\\|style\\|class\\)\\s*=\\s*[\"']\\zs.*")
  453. " Now we have context stripped from all chars up to style/class.
  454. " It may fail with some strange style value combinations.
  455. if stripbase !~ "[\"']"
  456. return []
  457. endif
  458. endif
  459. " Value of attribute completion {{{
  460. " If attr contains =\s*[\"'] we catched value of attribute
  461. if attr =~ "=\s*[\"']" || attr =~ "=\s*$"
  462. " Let do attribute specific completion
  463. let attrname = matchstr(attr, '.*\ze\s*=')
  464. let entered_value = matchstr(attr, ".*=\\s*[\"']\\?\\zs.*")
  465. let values = []
  466. " Load data {{{
  467. if !exists("b:html_doctype")
  468. call htmlcomplete#CheckDoctype()
  469. endif
  470. if !exists("b:html_omni")
  471. "runtime! autoload/xml/xhtml10s.vim
  472. call htmlcomplete#LoadData()
  473. endif
  474. " }}}
  475. if attrname == 'href'
  476. " Now we are looking for local anchors defined by name or id
  477. if entered_value =~ '^#'
  478. let file = join(getline(1, line('$')), ' ')
  479. " Split it be sure there will be one id/name element in
  480. " item, it will be also first word [a-zA-Z0-9_-] in element
  481. let oneelement = split(file, "\\(meta \\)\\@<!\\(name\\|id\\)\\s*=\\s*[\"']")
  482. for i in oneelement
  483. let values += ['#'.matchstr(i, "^[a-zA-Z][a-zA-Z0-9%_-]*")]
  484. endfor
  485. endif
  486. else
  487. if has_key(b:html_omni, tag) && has_key(b:html_omni[tag][1], attrname)
  488. let values = b:html_omni[tag][1][attrname]
  489. else
  490. return []
  491. endif
  492. endif
  493. if len(values) == 0
  494. return []
  495. endif
  496. " We need special version of sbase
  497. let attrbase = matchstr(context, ".*[\"']")
  498. let attrquote = matchstr(attrbase, '.$')
  499. if attrquote !~ "['\"]"
  500. let attrquoteopen = '"'
  501. let attrquote = '"'
  502. else
  503. let attrquoteopen = ''
  504. endif
  505. for m in values
  506. " This if is needed to not offer all completions as-is
  507. " alphabetically but sort them. Those beginning with entered
  508. " part will be as first choices
  509. if m =~ '^'.entered_value
  510. call add(res, attrquoteopen . m . attrquote)
  511. elseif m =~ entered_value
  512. call add(res2, attrquoteopen . m . attrquote)
  513. endif
  514. endfor
  515. return res + res2
  516. endif
  517. " }}}
  518. " Attribute completion {{{
  519. " Shorten context to not include last word
  520. let sbase = matchstr(context, '.*\ze\s.*')
  521. " Load data {{{
  522. if !exists("b:html_doctype")
  523. call htmlcomplete#CheckDoctype()
  524. endif
  525. if !exists("b:html_omni")
  526. call htmlcomplete#LoadData()
  527. endif
  528. " }}}
  529. if has_key(b:html_omni, tag)
  530. let attrs = keys(b:html_omni[tag][1])
  531. else
  532. return []
  533. endif
  534. for m in sort(attrs)
  535. if m =~ '^'.attr
  536. call add(res, m)
  537. elseif m =~ attr
  538. call add(res2, m)
  539. endif
  540. endfor
  541. let menu = res + res2
  542. if has_key(b:html_omni, 'vimxmlattrinfo')
  543. let final_menu = []
  544. for i in range(len(menu))
  545. let item = menu[i]
  546. if has_key(b:html_omni['vimxmlattrinfo'], item)
  547. let m_menu = b:html_omni['vimxmlattrinfo'][item][0]
  548. let m_info = b:html_omni['vimxmlattrinfo'][item][1]
  549. else
  550. let m_menu = ''
  551. let m_info = ''
  552. endif
  553. if len(b:html_omni[tag][1][item]) > 0 && b:html_omni[tag][1][item][0] =~ '^\(BOOL\|'.item.'\)$'
  554. let item = item
  555. let m_menu = 'Bool'
  556. else
  557. let item .= '="'
  558. endif
  559. let final_menu += [{'word':item, 'menu':m_menu, 'info':m_info}]
  560. endfor
  561. else
  562. let final_menu = []
  563. for i in range(len(menu))
  564. let item = menu[i]
  565. if len(b:html_omni[tag][1][item]) > 0 && b:html_omni[tag][1][item][0] =~ '^\(BOOL\|'.item.'\)$'
  566. let item = item
  567. else
  568. let item .= '="'
  569. endif
  570. let final_menu += [item]
  571. endfor
  572. return final_menu
  573. endif
  574. return final_menu
  575. endif
  576. " }}}
  577. " Close tag {{{
  578. let b:unaryTagsStack = "base meta link hr br param img area input col"
  579. if context =~ '^\/'
  580. if context =~ '^\/.'
  581. return []
  582. else
  583. let opentag = xmlcomplete#GetLastOpenTag("b:unaryTagsStack")
  584. return [opentag.">"]
  585. endif
  586. endif
  587. " }}}
  588. " Load data {{{
  589. if !exists("b:html_doctype")
  590. call htmlcomplete#CheckDoctype()
  591. endif
  592. if !exists("b:html_omni")
  593. "runtime! autoload/xml/xhtml10s.vim
  594. call htmlcomplete#LoadData()
  595. endif
  596. " }}}
  597. " Tag completion {{{
  598. " Deal with tag completion.
  599. let opentag = tolower(xmlcomplete#GetLastOpenTag("b:unaryTagsStack"))
  600. " MM: TODO: GLOT works always the same but with some weird situation it
  601. " behaves as intended in HTML but screws in PHP
  602. if opentag == '' || &filetype == 'php' && !has_key(b:html_omni, opentag)
  603. " Hack for sometimes failing GetLastOpenTag.
  604. " As far as I tested fail isn't GLOT fault but problem
  605. " of invalid document - not properly closed tags and other mish-mash.
  606. " Also when document is empty. Return list of *all* tags.
  607. let tags = keys(b:html_omni)
  608. call filter(tags, 'v:val !~ "^vimxml"')
  609. else
  610. if has_key(b:html_omni, opentag)
  611. let tags = b:html_omni[opentag][0]
  612. else
  613. return []
  614. endif
  615. endif
  616. " }}}
  617. if exists("uppercase_tag") && uppercase_tag == 1
  618. let context = tolower(context)
  619. endif
  620. " Handle XML keywords: DOCTYPE
  621. if opentag == ''
  622. let tags += [
  623. \ '!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">',
  624. \ '!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">',
  625. \ '!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">',
  626. \ '!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Frameset//EN" "http://www.w3.org/TR/REC-html40/frameset.dtd">',
  627. \ '!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">',
  628. \ '!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">',
  629. \ '!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN" "http://www.w3.org/TR/html4/frameset.dtd">',
  630. \ '!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">',
  631. \ '!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">',
  632. \ '!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">',
  633. \ '!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/1999/xhtml">'
  634. \ ]
  635. endif
  636. for m in sort(tags)
  637. if m =~ '^'.context
  638. call add(res, m)
  639. elseif m =~ context
  640. call add(res2, m)
  641. endif
  642. endfor
  643. let menu = res + res2
  644. if has_key(b:html_omni, 'vimxmltaginfo')
  645. let final_menu = []
  646. for i in range(len(menu))
  647. let item = menu[i]
  648. if has_key(b:html_omni['vimxmltaginfo'], item)
  649. let m_menu = b:html_omni['vimxmltaginfo'][item][0]
  650. let m_info = b:html_omni['vimxmltaginfo'][item][1]
  651. else
  652. let m_menu = ''
  653. let m_info = ''
  654. endif
  655. if &filetype == 'html' && exists("uppercase_tag") && uppercase_tag == 1 && item !~ 'DOCTYPE'
  656. let item = toupper(item)
  657. endif
  658. if item =~ 'DOCTYPE'
  659. let abbr = 'DOCTYPE '.matchstr(item, 'DTD \zsX\?HTML .\{-}\ze\/\/')
  660. else
  661. let abbr = item
  662. endif
  663. let final_menu += [{'abbr':abbr, 'word':item, 'menu':m_menu, 'info':m_info}]
  664. endfor
  665. else
  666. let final_menu = menu
  667. endif
  668. return final_menu
  669. " }}}
  670. endif
  671. endfunction
  672. function! htmlcomplete#LoadData() " {{{
  673. if !exists("b:html_omni_flavor")
  674. if &filetype == 'html'
  675. let b:html_omni_flavor = 'html401t'
  676. else
  677. let b:html_omni_flavor = 'xhtml10s'
  678. endif
  679. endif
  680. " With that if we still have bloated memory but create new buffer
  681. " variables only by linking to existing g:variable, not sourcing whole
  682. " file.
  683. if exists('g:xmldata_'.b:html_omni_flavor)
  684. exe 'let b:html_omni = g:xmldata_'.b:html_omni_flavor
  685. else
  686. exe 'runtime! autoload/xml/'.b:html_omni_flavor.'.vim'
  687. exe 'let b:html_omni = g:xmldata_'.b:html_omni_flavor
  688. endif
  689. endfunction
  690. " }}}
  691. function! htmlcomplete#CheckDoctype() " {{{
  692. if exists('b:html_omni_flavor')
  693. let old_flavor = b:html_omni_flavor
  694. else
  695. let old_flavor = ''
  696. endif
  697. let i = 1
  698. while i < 10 && i < line("$")
  699. let line = getline(i)
  700. if line =~ '<!DOCTYPE.*\<DTD HTML 3\.2'
  701. let b:html_omni_flavor = 'html32'
  702. let b:html_doctype = 1
  703. break
  704. elseif line =~ '<!DOCTYPE.*\<DTD HTML 4\.0 Transitional'
  705. let b:html_omni_flavor = 'html40t'
  706. let b:html_doctype = 1
  707. break
  708. elseif line =~ '<!DOCTYPE.*\<DTD HTML 4\.0 Frameset'
  709. let b:html_omni_flavor = 'html40f'
  710. let b:html_doctype = 1
  711. break
  712. elseif line =~ '<!DOCTYPE.*\<DTD HTML 4\.0'
  713. let b:html_omni_flavor = 'html40s'
  714. let b:html_doctype = 1
  715. break
  716. elseif line =~ '<!DOCTYPE.*\<DTD HTML 4\.01 Transitional'
  717. let b:html_omni_flavor = 'html401t'
  718. let b:html_doctype = 1
  719. break
  720. elseif line =~ '<!DOCTYPE.*\<DTD HTML 4\.01 Frameset'
  721. let b:html_omni_flavor = 'html401f'
  722. let b:html_doctype = 1
  723. break
  724. elseif line =~ '<!DOCTYPE.*\<DTD HTML 4\.01'
  725. let b:html_omni_flavor = 'html401s'
  726. let b:html_doctype = 1
  727. break
  728. elseif line =~ '<!DOCTYPE.*\<DTD XHTML 1\.0 Transitional'
  729. let b:html_omni_flavor = 'xhtml10t'
  730. let b:html_doctype = 1
  731. break
  732. elseif line =~ '<!DOCTYPE.*\<DTD XHTML 1\.0 Frameset'
  733. let b:html_omni_flavor = 'xhtml10f'
  734. let b:html_doctype = 1
  735. break
  736. elseif line =~ '<!DOCTYPE.*\<DTD XHTML 1\.0 Strict'
  737. let b:html_omni_flavor = 'xhtml10s'
  738. let b:html_doctype = 1
  739. break
  740. elseif line =~ '<!DOCTYPE.*\<DTD XHTML 1\.1'
  741. let b:html_omni_flavor = 'xhtml11'
  742. let b:html_doctype = 1
  743. break
  744. endif
  745. let i += 1
  746. endwhile
  747. if !exists("b:html_doctype")
  748. return
  749. else
  750. " Tie g:xmldata with b:html_omni this way we need to sourca data file only
  751. " once, not every time per buffer.
  752. if old_flavor == b:html_omni_flavor
  753. return
  754. else
  755. if exists('g:xmldata_'.b:html_omni_flavor)
  756. exe 'let b:html_omni = g:xmldata_'.b:html_omni_flavor
  757. else
  758. exe 'runtime! autoload/xml/'.b:html_omni_flavor.'.vim'
  759. exe 'let b:html_omni = g:xmldata_'.b:html_omni_flavor
  760. endif
  761. return
  762. endif
  763. endif
  764. endfunction
  765. " }}}
  766. " vim:set foldmethod=marker: