1
0

latte.vim 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505
  1. " Vim indent script for HTML
  2. " General: "{{{
  3. " File: html.vim (Vimscript #2075)
  4. " Author: Andy Wokula <anwoku@yahoo.de>
  5. " Last Change: 2013 Jun 12
  6. " Rev Days: 13
  7. " Version: 0.9
  8. " Vim Version: Vim7
  9. " Description:
  10. " Improved version of the distributed html indent script, faster on a
  11. " range of lines.
  12. "
  13. " Credits:
  14. " indent/html.vim (2006 Jun 05) from J. Zellner
  15. " indent/css.vim (2006 Dec 20) from N. Weibull
  16. "
  17. " History:
  18. " 2012 Oct 21 (v0.9) added support for shiftwidth()
  19. " 2011 Sep 09 (v0.8) added HTML5 tags (thx to J. Zuckerman)
  20. " 2008 Apr 28 (v0.6) revised customization
  21. " 2008 Mar 09 (v0.5) fixed 'indk' issue (thx to C.J. Robinson)
  22. " }}}
  23. " Init Folklore, check user settings (2nd time ++) "{{{
  24. if exists("b:did_indent")
  25. finish
  26. endif
  27. let b:did_indent = 1
  28. setlocal indentexpr=HtmlIndent()
  29. setlocal indentkeys=o,O,<Return>,<>>,{,},!^F
  30. let b:indent = {"lnum": -1}
  31. let b:undo_indent = "set inde< indk<| unlet b:indent"
  32. " Load Once:
  33. if exists("*HtmlIndent")
  34. call HtmlIndent_CheckUserSettings()
  35. finish
  36. endif
  37. " Patch 7.3.694
  38. if exists('*shiftwidth')
  39. let s:ShiftWidth = function('shiftwidth')
  40. else
  41. func! s:ShiftWidth()
  42. return &shiftwidth
  43. endfunc
  44. endif
  45. let s:cpo_save = &cpo
  46. set cpo-=C
  47. "}}}
  48. func! HtmlIndent_CheckUserSettings() "{{{
  49. if exists("g:html_indent_inctags")
  50. call s:AddITags(split(g:html_indent_inctags, ","))
  51. endif
  52. if exists("g:html_indent_autotags")
  53. call s:RemoveITags(split(g:html_indent_autotags, ","))
  54. endif
  55. let indone = {"zero": 0
  56. \,"auto": "indent(prevnonblank(v:lnum-1))"
  57. \,"inc": "b:indent.blocktagind + s:ShiftWidth()"}
  58. if exists("g:html_indent_script1")
  59. let s:js1indent = get(indone, g:html_indent_script1, indone.zero)
  60. endif
  61. if exists("g:html_indent_style1")
  62. let s:css1indent = get(indone, g:html_indent_style1, indone.zero)
  63. endif
  64. endfunc "}}}
  65. " Init Script Vars "{{{
  66. let s:usestate = 1
  67. let s:css1indent = 0
  68. let s:js1indent = 0
  69. " not to be changed:
  70. let s:endtags = [0,0,0,0,0,0,0,0] " some places unused
  71. let s:newstate = {}
  72. let s:countonly = 0
  73. "}}}
  74. func! s:AddITags(taglist) "{{{
  75. for itag in a:taglist
  76. let s:indent_tags[itag] = 1
  77. let s:indent_tags['/'.itag] = -1
  78. endfor
  79. endfunc "}}}
  80. func! s:AddBlockTag(tag, id, ...) "{{{
  81. if !(a:id >= 2 && a:id < 2+len(s:endtags))
  82. return
  83. endif
  84. let s:indent_tags[a:tag] = a:id
  85. if a:0 == 0
  86. let s:indent_tags['/'.a:tag] = -a:id
  87. let s:endtags[a:id-2] = "</".a:tag.">"
  88. else
  89. let s:indent_tags[a:1] = -a:id
  90. let s:endtags[a:id-2] = a:1
  91. endif
  92. endfunc "}}}
  93. func! s:RemoveITags(taglist) "{{{
  94. " remove itags (protect blocktags from being removed)
  95. for itag in a:taglist
  96. if !has_key(s:indent_tags, itag) || s:indent_tags[itag] != 1
  97. continue
  98. endif
  99. unlet s:indent_tags[itag]
  100. if itag =~ '^\w\+$'
  101. unlet s:indent_tags["/".itag]
  102. endif
  103. endfor
  104. endfunc "}}}
  105. " Add Indent Tags: {{{
  106. if !exists("s:indent_tags")
  107. let s:indent_tags = {}
  108. endif
  109. " old tags:
  110. call s:AddITags(['a', 'abbr', 'acronym', 'address', 'b', 'bdo', 'big',
  111. \ 'blockquote', 'button', 'caption', 'center', 'cite', 'code', 'colgroup',
  112. \ 'del', 'dfn', 'dir', 'div', 'dl', 'em', 'fieldset', 'font', 'form',
  113. \ 'frameset', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'i', 'iframe', 'ins', 'kbd',
  114. \ 'label', 'legend', 'map', 'menu', 'noframes', 'noscript', 'object', 'ol',
  115. \ 'optgroup', 'q', 's', 'samp', 'select', 'small', 'span', 'strong', 'sub',
  116. \ 'sup', 'table', 'textarea', 'title', 'tt', 'u', 'ul', 'var', 'th', 'td',
  117. \ 'tr', 'tfoot', 'thead'])
  118. " tags added 2011 Sep 09 (especially HTML5 tags):
  119. call s:AddITags(['area', 'article', 'aside', 'audio', 'bdi', 'canvas',
  120. \ 'command', 'datalist', 'details', 'embed', 'figure', 'footer',
  121. \ 'header', 'group', 'keygen', 'mark', 'math', 'meter', 'nav', 'output',
  122. \ 'progress', 'ruby', 'section', 'svg', 'texture', 'time', 'video',
  123. \ 'wbr', 'text'])
  124. "}}}
  125. " Add Block Tags: contain alien content "{{{
  126. call s:AddBlockTag('pre', 2)
  127. call s:AddBlockTag('script', 3)
  128. call s:AddBlockTag('style', 4)
  129. call s:AddBlockTag('<!--', 5, '-->')
  130. "}}}
  131. func! s:CountITags(...) "{{{
  132. " relative indent steps for current line [unit &sw]:
  133. let s:curind = 0
  134. " relative indent steps for next line [unit &sw]:
  135. let s:nextrel = 0
  136. if a:0==0
  137. let s:block = s:newstate.block
  138. let tmpline = substitute(s:curline, '<\zs\/\=\w\+\>\|<!--\|-->', '\=s:CheckTag(submatch(0))', 'g')
  139. if s:block == 3
  140. let s:newstate.scripttype = s:GetScriptType(matchstr(tmpline, '\C.*<SCRIPT\>\zs[^>]*'))
  141. endif
  142. let s:newstate.block = s:block
  143. else
  144. let s:block = 0 " assume starting outside of a block
  145. let s:countonly = 1 " don't change state
  146. let tmpline = substitute(s:altline, '<\zs\/\=\w\+\>\|<!--\|-->', '\=s:CheckTag(submatch(0))', 'g')
  147. let s:countonly = 0
  148. endif
  149. endfunc "}}}
  150. func! s:CheckTag(itag) "{{{
  151. " "tag" or "/tag" or "<!--" or "-->"
  152. let ind = get(s:indent_tags, a:itag)
  153. if ind == -1
  154. " closing tag
  155. if s:block != 0
  156. " ignore itag within a block
  157. return "foo"
  158. endif
  159. if s:nextrel == 0
  160. let s:curind -= 1
  161. else
  162. let s:nextrel -= 1
  163. endif
  164. " if s:curind >= 1
  165. " let s:curind -= 1
  166. " else
  167. " let s:nextrel -= 1
  168. " endif
  169. elseif ind == 1
  170. " opening tag
  171. if s:block != 0
  172. return "foo"
  173. endif
  174. let s:nextrel += 1
  175. elseif ind != 0
  176. " block-tag (opening or closing)
  177. return s:Blocktag(a:itag, ind)
  178. endif
  179. " else ind==0 (other tag found): keep indent
  180. return "foo" " no matter
  181. endfunc "}}}
  182. func! s:Blocktag(blocktag, ind) "{{{
  183. if a:ind > 0
  184. " a block starts here
  185. if s:block != 0
  186. " already in a block (nesting) - ignore
  187. " especially ignore comments after other blocktags
  188. return "foo"
  189. endif
  190. let s:block = a:ind " block type
  191. if s:countonly
  192. return "foo"
  193. endif
  194. let s:newstate.blocklnr = v:lnum
  195. " save allover indent for the endtag
  196. let s:newstate.blocktagind = b:indent.baseindent + (s:nextrel + s:curind) * s:ShiftWidth()
  197. if a:ind == 3
  198. return "SCRIPT" " all except this must be lowercase
  199. " line is to be checked again for the type attribute
  200. endif
  201. else
  202. let s:block = 0
  203. " we get here if starting and closing block-tag on same line
  204. endif
  205. return "foo"
  206. endfunc "}}}
  207. func! s:GetScriptType(str) "{{{
  208. if a:str == "" || a:str =~ "java"
  209. return "javascript"
  210. else
  211. return ""
  212. endif
  213. endfunc "}}}
  214. func! s:FreshState(lnum) "{{{
  215. " Look back in the file (lines 1 to a:lnum-1) to calc a state for line
  216. " a:lnum. A state is to know ALL relevant details about the lines
  217. " 1..a:lnum-1, initial calculating (here!) can be slow, but updating is
  218. " fast (incremental).
  219. " State:
  220. " lnum last indented line == prevnonblank(a:lnum - 1)
  221. " block = 0 a:lnum located within special tag: 0:none, 2:<pre>,
  222. " 3:<script>, 4:<style>, 5:<!--
  223. " baseindent use this indent for line a:lnum as a start - kind of
  224. " autoindent (if block==0)
  225. " scripttype = '' type attribute of a script tag (if block==3)
  226. " blocktagind indent for current opening (get) and closing (set)
  227. " blocktag (if block!=0)
  228. " blocklnr lnum of starting blocktag (if block!=0)
  229. " inattr line {lnum} starts with attributes of a tag
  230. let state = {}
  231. let state.lnum = prevnonblank(a:lnum - 1)
  232. let state.scripttype = ""
  233. let state.blocktagind = -1
  234. let state.block = 0
  235. let state.baseindent = 0
  236. let state.blocklnr = 0
  237. let state.inattr = 0
  238. if state.lnum == 0
  239. return state
  240. endif
  241. " Heuristic:
  242. " remember startline state.lnum
  243. " look back for <pre, </pre, <script, </script, <style, </style tags
  244. " remember stopline
  245. " if opening tag found,
  246. " assume a:lnum within block
  247. " else
  248. " look back in result range (stopline, startline) for comment
  249. " \ delimiters (<!--, -->)
  250. " if comment opener found,
  251. " assume a:lnum within comment
  252. " else
  253. " assume usual html for a:lnum
  254. " if a:lnum-1 has a closing comment
  255. " look back to get indent of comment opener
  256. " FI
  257. " look back for blocktag
  258. call cursor(a:lnum, 1)
  259. let [stopline, stopcol] = searchpos('\c<\zs\/\=\%(pre\>\|script\>\|style\>\)', "bW")
  260. " fugly ... why isn't there searchstr()
  261. let tagline = tolower(getline(stopline))
  262. let blocktag = matchstr(tagline, '\/\=\%(pre\>\|script\>\|style\>\)', stopcol-1)
  263. if stopline > 0 && blocktag[0] != "/"
  264. " opening tag found, assume a:lnum within block
  265. let state.block = s:indent_tags[blocktag]
  266. if state.block == 3
  267. let state.scripttype = s:GetScriptType(matchstr(tagline, '\>[^>]*', stopcol))
  268. endif
  269. let state.blocklnr = stopline
  270. " check preceding tags in the line:
  271. let s:altline = tagline[: stopcol-2]
  272. call s:CountITags(1)
  273. let state.blocktagind = indent(stopline) + (s:curind + s:nextrel) * s:ShiftWidth()
  274. return state
  275. elseif stopline == state.lnum
  276. " handle special case: previous line (= state.lnum) contains a
  277. " closing blocktag which is preceded by line-noise;
  278. " blocktag == "/..."
  279. let swendtag = match(tagline, '^\s*</') >= 0
  280. if !swendtag
  281. let [bline, bcol] = searchpos('<'.blocktag[1:].'\>', "bW")
  282. let s:altline = tolower(getline(bline)[: bcol-2])
  283. call s:CountITags(1)
  284. let state.baseindent = indent(bline) + (s:nextrel+s:curline) * s:ShiftWidth()
  285. return state
  286. endif
  287. endif
  288. " else look back for comment
  289. call cursor(a:lnum, 1)
  290. let [comline, comcol, found] = searchpos('\(<!--\)\|-->', 'bpW', stopline)
  291. if found == 2
  292. " comment opener found, assume a:lnum within comment
  293. let state.block = 5
  294. let state.blocklnr = comline
  295. " check preceding tags in the line:
  296. let s:altline = tolower(getline(comline)[: comcol-2])
  297. call s:CountITags(1)
  298. let state.blocktagind = indent(comline) + (s:curind + s:nextrel) * s:ShiftWidth()
  299. return state
  300. endif
  301. " else within usual html
  302. let s:altline = tolower(getline(state.lnum))
  303. " check a:lnum-1 for closing comment (we need indent from the opening line)
  304. let comcol = stridx(s:altline, '-->')
  305. if comcol >= 0
  306. call cursor(state.lnum, comcol+1)
  307. let [comline, comcol] = searchpos('<!--', 'bW')
  308. if comline == state.lnum
  309. let s:altline = s:altline[: comcol-2]
  310. else
  311. let s:altline = tolower(getline(comline)[: comcol-2])
  312. endif
  313. call s:CountITags(1)
  314. let state.baseindent = indent(comline) + (s:nextrel+s:curline) * s:ShiftWidth()
  315. return state
  316. " TODO check tags that follow "-->"
  317. endif
  318. " else no comments
  319. call s:CountITags(1)
  320. let state.baseindent = indent(state.lnum) + s:nextrel * s:ShiftWidth()
  321. " line starts with end tag
  322. let swendtag = match(s:altline, '^\s*</') >= 0
  323. if !swendtag
  324. let state.baseindent += s:curind * s:ShiftWidth()
  325. endif
  326. return state
  327. endfunc "}}}
  328. func! s:Alien2() "{{{
  329. " <pre> block
  330. return -1
  331. endfunc "}}}
  332. func! s:Alien3() "{{{
  333. " <script> javascript
  334. if prevnonblank(v:lnum-1) == b:indent.blocklnr
  335. " indent for the first line after <script>
  336. return eval(s:js1indent)
  337. endif
  338. if b:indent.scripttype == "javascript"
  339. return cindent(v:lnum)
  340. else
  341. return -1
  342. endif
  343. endfunc "}}}
  344. func! s:Alien4() "{{{
  345. " <style>
  346. if prevnonblank(v:lnum-1) == b:indent.blocklnr
  347. " indent for first content line
  348. return eval(s:css1indent)
  349. endif
  350. return s:CSSIndent()
  351. endfunc
  352. func! s:CSSIndent() "{{{
  353. " adopted $VIMRUNTIME/indent/css.vim
  354. if getline(v:lnum) =~ '^\s*[*}]'
  355. return cindent(v:lnum)
  356. endif
  357. let minline = b:indent.blocklnr
  358. let pnum = s:css_prevnoncomment(v:lnum - 1, minline)
  359. if pnum <= minline
  360. " < is to catch errors
  361. " indent for first content line after comments
  362. return eval(s:css1indent)
  363. endif
  364. let ind = indent(pnum) + s:css_countbraces(pnum, 1) * s:ShiftWidth()
  365. let pline = getline(pnum)
  366. if pline =~ '}\s*$'
  367. let ind -= (s:css_countbraces(pnum, 0) - (pline =~ '^\s*}')) * s:ShiftWidth()
  368. endif
  369. return ind
  370. endfunc "}}}
  371. func! s:css_prevnoncomment(lnum, stopline) "{{{
  372. " caller starts from a line a:lnum-1 that is not a comment
  373. let lnum = prevnonblank(a:lnum)
  374. let ccol = match(getline(lnum), '\*/')
  375. if ccol < 0
  376. return lnum
  377. endif
  378. call cursor(lnum, ccol+1)
  379. let lnum = search('/\*', 'bW', a:stopline)
  380. if indent(".") == virtcol(".")-1
  381. return prevnonblank(lnum-1)
  382. else
  383. return lnum
  384. endif
  385. endfunc "}}}
  386. func! s:css_countbraces(lnum, count_open) "{{{
  387. let brs = substitute(getline(a:lnum),'[''"].\{-}[''"]\|/\*.\{-}\*/\|/\*.*$\|[^{}]','','g')
  388. let n_open = 0
  389. let n_close = 0
  390. for brace in split(brs, '\zs')
  391. if brace == "{"
  392. let n_open += 1
  393. elseif brace == "}"
  394. if n_open > 0
  395. let n_open -= 1
  396. else
  397. let n_close += 1
  398. endif
  399. endif
  400. endfor
  401. return a:count_open ? n_open : n_close
  402. endfunc "}}}
  403. "}}}
  404. func! s:Alien5() "{{{
  405. " <!-- -->
  406. return -1
  407. endfunc "}}}
  408. func! HtmlIndent() "{{{
  409. let s:curline = tolower(getline(v:lnum))
  410. let indentunit = s:ShiftWidth()
  411. let s:newstate = {}
  412. let s:newstate.lnum = v:lnum
  413. " does the line start with a closing tag?
  414. let swendtag = match(s:curline, '^\s*</') >= 0
  415. if prevnonblank(v:lnum-1) == b:indent.lnum && s:usestate
  416. " use state (continue from previous line)
  417. else
  418. " start over (know nothing)
  419. let b:indent = s:FreshState(v:lnum)
  420. endif
  421. if b:indent.block >= 2
  422. " within block
  423. let endtag = s:endtags[b:indent.block-2]
  424. let blockend = stridx(s:curline, endtag)
  425. if blockend >= 0
  426. " block ends here
  427. let s:newstate.block = 0
  428. " calc indent for REST OF LINE (may start more blocks):
  429. let s:curline = strpart(s:curline, blockend+strlen(endtag))
  430. call s:CountITags()
  431. if swendtag && b:indent.block != 5
  432. let indent = b:indent.blocktagind + s:curind * indentunit
  433. let s:newstate.baseindent = indent + s:nextrel * indentunit
  434. else
  435. let indent = s:Alien{b:indent.block}()
  436. let s:newstate.baseindent = b:indent.blocktagind + s:nextrel * indentunit
  437. endif
  438. call extend(b:indent, s:newstate, "force")
  439. return indent
  440. else
  441. " block continues
  442. " indent this line with alien method
  443. let indent = s:Alien{b:indent.block}()
  444. call extend(b:indent, s:newstate, "force")
  445. return indent
  446. endif
  447. else
  448. " not within a block - within usual html
  449. " if < 2 then always 0
  450. let s:newstate.block = b:indent.block
  451. call s:CountITags()
  452. if swendtag
  453. let indent = b:indent.baseindent + s:curind * indentunit
  454. let s:newstate.baseindent = indent + s:nextrel * indentunit
  455. else
  456. let indent = b:indent.baseindent
  457. let s:newstate.baseindent = indent + (s:curind + s:nextrel) * indentunit
  458. endif
  459. call extend(b:indent, s:newstate, "force")
  460. return indent
  461. endif
  462. endfunc "}}}
  463. " check user settings (first time), clear cpo, Modeline: {{{1
  464. " DEBUG:
  465. com! -nargs=* IndHtmlLocal <args>
  466. call HtmlIndent_CheckUserSettings()
  467. let &cpo = s:cpo_save
  468. unlet s:cpo_save
  469. " vim:set fdm=marker ts=8: