htmlcomplete.vim 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810
  1. " Vim completion script
  2. " Language: HTML and XHTML
  3. " Maintainer: Mikolaj Machowski ( mikmach AT wp DOT pl )
  4. " Last Change: 2019 Sep 27
  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 =~ '^\s*$'
  239. " empty or whitespace line
  240. let tag = ''
  241. else
  242. let tag = split(context)[0]
  243. " Detect if tag is uppercase to return in proper case,
  244. " we need to make it lowercase for processing
  245. if tag =~ '^[A-Z]*$'
  246. let uppercase_tag = 1
  247. let tag = tolower(tag)
  248. else
  249. let uppercase_tag = 0
  250. endif
  251. endif
  252. " Get last word, it should be attr name
  253. let attr = matchstr(context, '.*\s\zs.*')
  254. " Possible situations where any prediction would be difficult:
  255. " 1. Events attributes
  256. if context =~ '\s'
  257. " Sort out style, class, and on* cases
  258. if context =~? "\\(on[a-z]*\\|id\\|style\\|class\\)\\s*=\\s*[\"']"
  259. " Id, class completion {{{
  260. if context =~? "\\(id\\|class\\)\\s*=\\s*[\"'][a-zA-Z0-9_ -]*$"
  261. if context =~? "class\\s*=\\s*[\"'][a-zA-Z0-9_ -]*$"
  262. let search_for = "class"
  263. elseif context =~? "id\\s*=\\s*[\"'][a-zA-Z0-9_ -]*$"
  264. let search_for = "id"
  265. endif
  266. " Handle class name completion
  267. " 1. Find lines of <link stylesheet>
  268. " 1a. Check file for @import
  269. " 2. Extract filename(s?) of stylesheet,
  270. call cursor(1,1)
  271. let head = getline(search('<head\>'), search('<\/head>'))
  272. let headjoined = join(copy(head), ' ')
  273. if headjoined =~ '<style'
  274. " Remove possibly confusing CSS operators
  275. let stylehead = substitute(headjoined, '+>\*[,', ' ', 'g')
  276. if search_for == 'class'
  277. let styleheadlines = split(stylehead)
  278. let headclasslines = filter(copy(styleheadlines), "v:val =~ '\\([a-zA-Z0-9:]\\+\\)\\?\\.[a-zA-Z0-9_-]\\+'")
  279. else
  280. let stylesheet = split(headjoined, '[{}]')
  281. " Get all lines which fit id syntax
  282. let classlines = filter(copy(stylesheet), "v:val =~ '#[a-zA-Z0-9_-]\\+'")
  283. " Filter out possible color definitions
  284. call filter(classlines, "v:val !~ ':\\s*#[a-zA-Z0-9_-]\\+'")
  285. " Filter out complex border definitions
  286. call filter(classlines, "v:val !~ '\\(none\\|hidden\\|dotted\\|dashed\\|solid\\|double\\|groove\\|ridge\\|inset\\|outset\\)\\s*#[a-zA-Z0-9_-]\\+'")
  287. let templines = join(classlines, ' ')
  288. let headclasslines = split(templines)
  289. call filter(headclasslines, "v:val =~ '#[a-zA-Z0-9_-]\\+'")
  290. endif
  291. let internal = 1
  292. else
  293. let internal = 0
  294. endif
  295. let styletable = []
  296. let secimportfiles = []
  297. let filestable = filter(copy(head), "v:val =~ '\\(@import\\|link.*stylesheet\\)'")
  298. for line in filestable
  299. if line =~ "@import"
  300. let styletable += [matchstr(line, "import\\s\\+\\(url(\\)\\?[\"']\\?\\zs\\f\\+\\ze")]
  301. elseif line =~ "<link"
  302. let styletable += [matchstr(line, "href\\s*=\\s*[\"']\\zs\\f\\+\\ze")]
  303. endif
  304. endfor
  305. for file in styletable
  306. if filereadable(file)
  307. let stylesheet = readfile(file)
  308. let secimport = filter(copy(stylesheet), "v:val =~ '@import'")
  309. if len(secimport) > 0
  310. for line in secimport
  311. let secfile = matchstr(line, "import\\s\\+\\(url(\\)\\?[\"']\\?\\zs\\f\\+\\ze")
  312. let secfile = fnamemodify(file, ":p:h").'/'.secfile
  313. let secimportfiles += [secfile]
  314. endfor
  315. endif
  316. endif
  317. endfor
  318. let cssfiles = styletable + secimportfiles
  319. let classes = []
  320. for file in cssfiles
  321. let classlines = []
  322. if filereadable(file)
  323. let stylesheet = readfile(file)
  324. let stylefile = join(stylesheet, ' ')
  325. let stylefile = substitute(stylefile, '+>\*[,', ' ', 'g')
  326. if search_for == 'class'
  327. let stylesheet = split(stylefile)
  328. let classlines = filter(copy(stylesheet), "v:val =~ '\\([a-zA-Z0-9:]\\+\\)\\?\\.[a-zA-Z0-9_-]\\+'")
  329. else
  330. let stylesheet = split(stylefile, '[{}]')
  331. " Get all lines which fit id syntax
  332. let classlines = filter(copy(stylesheet), "v:val =~ '#[a-zA-Z0-9_-]\\+'")
  333. " Filter out possible color definitions
  334. call filter(classlines, "v:val !~ ':\\s*#[a-zA-Z0-9_-]\\+'")
  335. " Filter out complex border definitions
  336. call filter(classlines, "v:val !~ '\\(none\\|hidden\\|dotted\\|dashed\\|solid\\|double\\|groove\\|ridge\\|inset\\|outset\\)\\s*#[a-zA-Z0-9_-]\\+'")
  337. let templines = join(classlines, ' ')
  338. let stylelines = split(templines)
  339. let classlines = filter(stylelines, "v:val =~ '#[a-zA-Z0-9_-]\\+'")
  340. endif
  341. endif
  342. " We gathered classes definitions from all external files
  343. let classes += classlines
  344. endfor
  345. if internal == 1
  346. let classes += headclasslines
  347. endif
  348. if search_for == 'class'
  349. let elements = {}
  350. for element in classes
  351. if element =~ '^\.'
  352. let class = matchstr(element, '^\.\zs[a-zA-Z][a-zA-Z0-9_-]*\ze')
  353. let class = substitute(class, ':.*', '', '')
  354. if has_key(elements, 'common')
  355. let elements['common'] .= ' '.class
  356. else
  357. let elements['common'] = class
  358. endif
  359. else
  360. let class = matchstr(element, '[a-zA-Z1-6]*\.\zs[a-zA-Z][a-zA-Z0-9_-]*\ze')
  361. let tagname = tolower(matchstr(element, '[a-zA-Z1-6]*\ze.'))
  362. if tagname != ''
  363. if has_key(elements, tagname)
  364. let elements[tagname] .= ' '.class
  365. else
  366. let elements[tagname] = class
  367. endif
  368. endif
  369. endif
  370. endfor
  371. if has_key(elements, tag) && has_key(elements, 'common')
  372. let values = split(elements[tag]." ".elements['common'])
  373. elseif has_key(elements, tag) && !has_key(elements, 'common')
  374. let values = split(elements[tag])
  375. elseif !has_key(elements, tag) && has_key(elements, 'common')
  376. let values = split(elements['common'])
  377. else
  378. return []
  379. endif
  380. elseif search_for == 'id'
  381. " Find used IDs
  382. " 1. Catch whole file
  383. let filelines = getline(1, line('$'))
  384. " 2. Find lines with possible id
  385. let used_id_lines = filter(filelines, 'v:val =~ "id\\s*=\\s*[\"''][a-zA-Z0-9_-]\\+"')
  386. " 3a. Join all filtered lines
  387. let id_string = join(used_id_lines, ' ')
  388. " 3b. And split them to be sure each id is in separate item
  389. let id_list = split(id_string, 'id\s*=\s*')
  390. " 4. Extract id values
  391. let used_id = map(id_list, 'matchstr(v:val, "[\"'']\\zs[a-zA-Z0-9_-]\\+\\ze")')
  392. let joined_used_id = ','.join(used_id, ',').','
  393. let allvalues = map(classes, 'matchstr(v:val, ".*#\\zs[a-zA-Z0-9_-]\\+")')
  394. let values = []
  395. for element in classes
  396. if joined_used_id !~ ','.element.','
  397. let values += [element]
  398. endif
  399. endfor
  400. endif
  401. " We need special version of sbase
  402. let classbase = matchstr(context, ".*[\"']")
  403. let classquote = matchstr(classbase, '.$')
  404. let entered_class = matchstr(attr, ".*=\\s*[\"']\\zs.*")
  405. for m in sort(values)
  406. if m =~? '^'.entered_class
  407. call add(res, m . classquote)
  408. elseif m =~? entered_class
  409. call add(res2, m . classquote)
  410. endif
  411. endfor
  412. return res + res2
  413. elseif context =~? "style\\s*=\\s*[\"'][^\"']*$"
  414. return csscomplete#CompleteCSS(0, context)
  415. endif
  416. " }}}
  417. " Complete on-events {{{
  418. if context =~? 'on[a-z]*\s*=\s*\(''[^'']*\|"[^"]*\)$'
  419. " We have to:
  420. " 1. Find external files
  421. let b:js_extfiles = []
  422. let l = line('.')
  423. let c = col('.')
  424. call cursor(1,1)
  425. while search('<\@<=script\>', 'W') && line('.') <= l
  426. if synIDattr(synID(line('.'),col('.')-1,0),"name") !~? 'comment'
  427. let sname = matchstr(getline('.'), '<script[^>]*src\s*=\s*\([''"]\)\zs.\{-}\ze\1')
  428. if filereadable(sname)
  429. let b:js_extfiles += readfile(sname)
  430. endif
  431. endif
  432. endwhile
  433. " 2. Find at least one <script> tag
  434. call cursor(1,1)
  435. let js_scripttags = []
  436. while search('<script\>', 'W') && line('.') < l
  437. if matchstr(getline('.'), '<script[^>]*src') == ''
  438. let js_scripttag = getline(line('.'), search('</script>', 'W'))
  439. let js_scripttags += js_scripttag
  440. endif
  441. endwhile
  442. let b:js_extfiles += js_scripttags
  443. " 3. Proper call for javascriptcomplete#CompleteJS
  444. call cursor(l,c)
  445. let js_context = matchstr(a:base, '\k\+$')
  446. let js_shortcontext = substitute(a:base, js_context.'$', '', '')
  447. let b:compl_context = context
  448. let b:jsrange = [l, l]
  449. unlet! l c
  450. return javascriptcomplete#CompleteJS(0, js_context)
  451. endif
  452. " }}}
  453. let stripbase = matchstr(context, ".*\\(on[a-zA-Z]*\\|style\\|class\\)\\s*=\\s*[\"']\\zs.*")
  454. " Now we have context stripped from all chars up to style/class.
  455. " It may fail with some strange style value combinations.
  456. if stripbase !~ "[\"']"
  457. return []
  458. endif
  459. endif
  460. " Value of attribute completion {{{
  461. " If attr contains =\s*[\"'] we match value of attribute
  462. if attr =~ "=\s*[\"']" || attr =~ "=\s*$"
  463. " Let do attribute specific completion
  464. let attrname = matchstr(attr, '.*\ze\s*=')
  465. let entered_value = matchstr(attr, ".*=\\s*[\"']\\?\\zs.*")
  466. let values = []
  467. " Load data {{{
  468. if !exists("b:html_doctype")
  469. call htmlcomplete#CheckDoctype()
  470. endif
  471. if !exists("b:html_omni")
  472. "runtime! autoload/xml/xhtml10s.vim
  473. call htmlcomplete#LoadData()
  474. endif
  475. " }}}
  476. if attrname == 'href'
  477. " Now we are looking for local anchors defined by name or id
  478. if entered_value =~ '^#'
  479. let file = join(getline(1, line('$')), ' ')
  480. " Split it be sure there will be one id/name element in
  481. " item, it will be also first word [a-zA-Z0-9_-] in element
  482. let oneelement = split(file, "\\(meta \\)\\@<!\\(name\\|id\\)\\s*=\\s*[\"']")
  483. for i in oneelement
  484. let values += ['#'.matchstr(i, "^[a-zA-Z][a-zA-Z0-9%_-]*")]
  485. endfor
  486. endif
  487. else
  488. if has_key(b:html_omni, tag) && has_key(b:html_omni[tag][1], attrname)
  489. let values = b:html_omni[tag][1][attrname]
  490. else
  491. return []
  492. endif
  493. endif
  494. if len(values) == 0
  495. return []
  496. endif
  497. " We need special version of sbase
  498. let attrbase = matchstr(context, ".*[\"']")
  499. let attrquote = matchstr(attrbase, '.$')
  500. if attrquote !~ "['\"]"
  501. let attrquoteopen = '"'
  502. let attrquote = '"'
  503. else
  504. let attrquoteopen = ''
  505. endif
  506. for m in values
  507. " This if is needed to not offer all completions as-is
  508. " alphabetically but sort them. Those beginning with entered
  509. " part will be as first choices
  510. if m =~ '^'.entered_value
  511. call add(res, attrquoteopen . m . attrquote)
  512. elseif m =~ entered_value
  513. call add(res2, attrquoteopen . m . attrquote)
  514. endif
  515. endfor
  516. return res + res2
  517. endif
  518. " }}}
  519. " Attribute completion {{{
  520. " Shorten context to not include last word
  521. let sbase = matchstr(context, '.*\ze\s.*')
  522. " Load data {{{
  523. if !exists("b:html_doctype")
  524. call htmlcomplete#CheckDoctype()
  525. endif
  526. if !exists("b:html_omni")
  527. call htmlcomplete#LoadData()
  528. endif
  529. " }}}
  530. if has_key(b:html_omni, tag)
  531. let attrs = keys(b:html_omni[tag][1])
  532. else
  533. return []
  534. endif
  535. for m in sort(attrs)
  536. if m =~ '^'.attr
  537. call add(res, m)
  538. elseif m =~ attr
  539. call add(res2, m)
  540. endif
  541. endfor
  542. let menu = res + res2
  543. if has_key(b:html_omni, 'vimxmlattrinfo')
  544. let final_menu = []
  545. for i in range(len(menu))
  546. let item = menu[i]
  547. if has_key(b:html_omni['vimxmlattrinfo'], item)
  548. let m_menu = b:html_omni['vimxmlattrinfo'][item][0]
  549. let m_info = b:html_omni['vimxmlattrinfo'][item][1]
  550. else
  551. let m_menu = ''
  552. let m_info = ''
  553. endif
  554. if len(b:html_omni[tag][1][item]) > 0 && b:html_omni[tag][1][item][0] =~ '^\(BOOL\|'.item.'\)$'
  555. let item = item
  556. let m_menu = 'Bool'
  557. else
  558. let item .= '="'
  559. endif
  560. let final_menu += [{'word':item, 'menu':m_menu, 'info':m_info}]
  561. endfor
  562. else
  563. let final_menu = []
  564. for i in range(len(menu))
  565. let item = menu[i]
  566. if len(b:html_omni[tag][1][item]) > 0 && b:html_omni[tag][1][item][0] =~ '^\(BOOL\|'.item.'\)$'
  567. let item = item
  568. else
  569. let item .= '="'
  570. endif
  571. let final_menu += [item]
  572. endfor
  573. return final_menu
  574. endif
  575. return final_menu
  576. endif
  577. " }}}
  578. " Close tag {{{
  579. let b:unaryTagsStack = "base meta link hr br param img area input col"
  580. if context =~ '^\/'
  581. if context =~ '^\/.'
  582. return []
  583. else
  584. let opentag = xmlcomplete#GetLastOpenTag("b:unaryTagsStack")
  585. return [opentag.">"]
  586. endif
  587. endif
  588. " }}}
  589. " Load data {{{
  590. if !exists("b:html_doctype")
  591. call htmlcomplete#CheckDoctype()
  592. endif
  593. if !exists("b:html_omni")
  594. "runtime! autoload/xml/xhtml10s.vim
  595. call htmlcomplete#LoadData()
  596. endif
  597. " }}}
  598. " Tag completion {{{
  599. " Deal with tag completion.
  600. let opentag = tolower(xmlcomplete#GetLastOpenTag("b:unaryTagsStack"))
  601. " MM: TODO: GLOT works always the same but with some weird situation it
  602. " behaves as intended in HTML but screws in PHP
  603. if opentag == '' || &filetype == 'php' && !has_key(b:html_omni, opentag)
  604. " Hack for sometimes failing GetLastOpenTag.
  605. " As far as I tested fail isn't GLOT fault but problem
  606. " of invalid document - not properly closed tags and other mish-mash.
  607. " Also when document is empty. Return list of *all* tags.
  608. let tags = keys(b:html_omni)
  609. call filter(tags, 'v:val !~ "^vimxml"')
  610. else
  611. if has_key(b:html_omni, opentag)
  612. let tags = b:html_omni[opentag][0]
  613. else
  614. return []
  615. endif
  616. endif
  617. " }}}
  618. if exists("uppercase_tag") && uppercase_tag == 1
  619. let context = tolower(context)
  620. endif
  621. " Handle XML keywords: DOCTYPE
  622. if opentag == ''
  623. let tags += [
  624. \ '!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">',
  625. \ '!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">',
  626. \ '!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">',
  627. \ '!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Frameset//EN" "http://www.w3.org/TR/REC-html40/frameset.dtd">',
  628. \ '!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">',
  629. \ '!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">',
  630. \ '!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN" "http://www.w3.org/TR/html4/frameset.dtd">',
  631. \ '!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">',
  632. \ '!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">',
  633. \ '!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">',
  634. \ '!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/1999/xhtml">'
  635. \ ]
  636. endif
  637. for m in sort(tags)
  638. if m =~ '^'.context
  639. call add(res, m)
  640. elseif m =~ context
  641. call add(res2, m)
  642. endif
  643. endfor
  644. let menu = res + res2
  645. if has_key(b:html_omni, 'vimxmltaginfo')
  646. let final_menu = []
  647. for i in range(len(menu))
  648. let item = menu[i]
  649. if has_key(b:html_omni['vimxmltaginfo'], item)
  650. let m_menu = b:html_omni['vimxmltaginfo'][item][0]
  651. let m_info = b:html_omni['vimxmltaginfo'][item][1]
  652. else
  653. let m_menu = ''
  654. let m_info = ''
  655. endif
  656. if &filetype == 'html' && exists("uppercase_tag") && uppercase_tag == 1 && item !~ 'DOCTYPE'
  657. let item = toupper(item)
  658. endif
  659. if item =~ 'DOCTYPE'
  660. let abbr = 'DOCTYPE '.matchstr(item, 'DTD \zsX\?HTML .\{-}\ze\/\/')
  661. else
  662. let abbr = item
  663. endif
  664. let final_menu += [{'abbr':abbr, 'word':item, 'menu':m_menu, 'info':m_info}]
  665. endfor
  666. else
  667. let final_menu = menu
  668. endif
  669. return final_menu
  670. " }}}
  671. endif
  672. endfunction
  673. function! htmlcomplete#LoadData() " {{{
  674. if !exists("b:html_omni_flavor")
  675. if &filetype == 'html'
  676. let b:html_omni_flavor = 'html401t'
  677. else
  678. let b:html_omni_flavor = 'xhtml10s'
  679. endif
  680. endif
  681. " With that if we still have bloated memory but create new buffer
  682. " variables only by linking to existing g:variable, not sourcing whole
  683. " file.
  684. if exists('g:xmldata_'.b:html_omni_flavor)
  685. exe 'let b:html_omni = g:xmldata_'.b:html_omni_flavor
  686. else
  687. exe 'runtime! autoload/xml/'.b:html_omni_flavor.'.vim'
  688. exe 'let b:html_omni = g:xmldata_'.b:html_omni_flavor
  689. endif
  690. endfunction
  691. " }}}
  692. function! htmlcomplete#CheckDoctype() " {{{
  693. if exists('b:html_omni_flavor')
  694. let old_flavor = b:html_omni_flavor
  695. else
  696. let old_flavor = ''
  697. endif
  698. let i = 1
  699. while i < 10 && i < line("$")
  700. let line = getline(i)
  701. if line =~ '<!DOCTYPE.*\<DTD HTML 3\.2'
  702. let b:html_omni_flavor = 'html32'
  703. let b:html_doctype = 1
  704. break
  705. elseif line =~ '<!DOCTYPE.*\<DTD HTML 4\.0 Transitional'
  706. let b:html_omni_flavor = 'html40t'
  707. let b:html_doctype = 1
  708. break
  709. elseif line =~ '<!DOCTYPE.*\<DTD HTML 4\.0 Frameset'
  710. let b:html_omni_flavor = 'html40f'
  711. let b:html_doctype = 1
  712. break
  713. elseif line =~ '<!DOCTYPE.*\<DTD HTML 4\.0'
  714. let b:html_omni_flavor = 'html40s'
  715. let b:html_doctype = 1
  716. break
  717. elseif line =~ '<!DOCTYPE.*\<DTD HTML 4\.01 Transitional'
  718. let b:html_omni_flavor = 'html401t'
  719. let b:html_doctype = 1
  720. break
  721. elseif line =~ '<!DOCTYPE.*\<DTD HTML 4\.01 Frameset'
  722. let b:html_omni_flavor = 'html401f'
  723. let b:html_doctype = 1
  724. break
  725. elseif line =~ '<!DOCTYPE.*\<DTD HTML 4\.01'
  726. let b:html_omni_flavor = 'html401s'
  727. let b:html_doctype = 1
  728. break
  729. elseif line =~ '<!DOCTYPE.*\<DTD XHTML 1\.0 Transitional'
  730. let b:html_omni_flavor = 'xhtml10t'
  731. let b:html_doctype = 1
  732. break
  733. elseif line =~ '<!DOCTYPE.*\<DTD XHTML 1\.0 Frameset'
  734. let b:html_omni_flavor = 'xhtml10f'
  735. let b:html_doctype = 1
  736. break
  737. elseif line =~ '<!DOCTYPE.*\<DTD XHTML 1\.0 Strict'
  738. let b:html_omni_flavor = 'xhtml10s'
  739. let b:html_doctype = 1
  740. break
  741. elseif line =~ '<!DOCTYPE.*\<DTD XHTML 1\.1'
  742. let b:html_omni_flavor = 'xhtml11'
  743. let b:html_doctype = 1
  744. break
  745. endif
  746. let i += 1
  747. endwhile
  748. if !exists("b:html_doctype")
  749. return
  750. else
  751. " Tie g:xmldata with b:html_omni this way we need to sourca data file only
  752. " once, not every time per buffer.
  753. if old_flavor == b:html_omni_flavor
  754. return
  755. else
  756. if exists('g:xmldata_'.b:html_omni_flavor)
  757. exe 'let b:html_omni = g:xmldata_'.b:html_omni_flavor
  758. else
  759. exe 'runtime! autoload/xml/'.b:html_omni_flavor.'.vim'
  760. exe 'let b:html_omni = g:xmldata_'.b:html_omni_flavor
  761. endif
  762. return
  763. endif
  764. endif
  765. endfunction
  766. " }}}
  767. " vim:set foldmethod=marker: