folding.vim 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252
  1. " Folding - Modeline and Notes {{{
  2. " vim: set sw=2 ts=2 sts=2 et tw=78 foldmarker={{{,}}} foldlevel=0 foldmethod=marker:
  3. "
  4. " cinaeco/dotfiles Folding Settings
  5. "
  6. " These are settings to make folding easier to use and look at:
  7. " - Indented Folds to match their first line.
  8. " - Statbox to right displays line count and fold level.
  9. " - Coloured distinctly red!
  10. " (sounds harsh, but actually works well with solarized-dark!)
  11. " - Fillchar is forced to '.' rather than '-'.
  12. " (easier on eyes)
  13. " - SpaceBar toggles folds, if any.
  14. " (much more convenient than the 'z' commands)
  15. " - SPF13-VIM provides quick foldlevel setting map: <Leader>f[0-9]
  16. " (useful with statbox lvl)
  17. "
  18. " Note that custom foldtext will only work for vim 7.3+. For earlier
  19. " versions of vim, only the colouring and spacebar mapping will take effect.
  20. "
  21. " TODO:
  22. " - Fold description is just the first line found. Haven't really
  23. " understood the regex, but could perhaps make it better.
  24. " - Fold description current truncation rule is 1/3 of the window width.
  25. " Perhaps this could be some less arbitrary rule?
  26. "
  27. " }}}
  28. if has('folding')
  29. " Default Settings {{{
  30. set foldenable
  31. set foldmethod=indent
  32. set foldlevel=10
  33. " }}}
  34. " Keyboard Shortcuts {{{
  35. " Space as a Folding toggle in normal mode.
  36. nnoremap <silent> <space> @=(foldlevel('.')?'za':"\<space>")<CR>
  37. " Code folding options (spf13-vim)
  38. nmap <leader>f0 :set foldlevel=0<CR>
  39. nmap <leader>f1 :set foldlevel=1<CR>
  40. nmap <leader>f2 :set foldlevel=2<CR>
  41. nmap <leader>f3 :set foldlevel=3<CR>
  42. nmap <leader>f4 :set foldlevel=4<CR>
  43. nmap <leader>f5 :set foldlevel=5<CR>
  44. nmap <leader>f6 :set foldlevel=6<CR>
  45. nmap <leader>f7 :set foldlevel=7<CR>
  46. nmap <leader>f8 :set foldlevel=8<CR>
  47. nmap <leader>f9 :set foldlevel=9<CR>
  48. " }}}
  49. " Fold Highlighting {{{
  50. highlight Folded term=none cterm=none ctermfg=darkgrey ctermbg=none guifg=darkgrey guibg=none
  51. " }}}
  52. " Fold Text {{{
  53. set foldtext=CustomFoldText()
  54. function! CustomFoldText(...)
  55. " At least vim 7.3 {{{
  56. " - Requirement for strdisplaywidth().
  57. " - strdisplaywidth() seems to work. If multi-byte characters start to give
  58. " trouble, consider checking the more primitive solution in strlen() help.
  59. if v:version < 703
  60. return foldtext()
  61. endif
  62. " }}}
  63. " Common variables for all foldmethods {{{
  64. let lineCount = v:foldend - v:foldstart + 1
  65. let displayWidth = winwidth(0) - &foldcolumn
  66. if (&number || &relativenumber)
  67. let displayWidth -= &numberwidth
  68. endif
  69. let foldChar = '┄'
  70. " }}}
  71. " Set fold fillchar {{{
  72. " - This complicated line is to ensure we replace the fold fillchar only.
  73. let &l:fillchars = substitute(&l:fillchars,',\?fold:.','','gi')
  74. exec 'setlocal fillchars+=fold:' . foldChar
  75. " }}}
  76. " Handle diff foldmethod {{{
  77. " - Display a centre-aligned statbox with the number of lines.
  78. if &foldmethod == 'diff'
  79. " Prepare the statbox {{{
  80. let statBox = printf('[ %s matching lines ]', lineCount)
  81. " }}}
  82. " Prepare filler lines {{{
  83. let filler = repeat(foldChar, (displayWidth - strchars(statBox)) / 2)
  84. " }}}
  85. " Output the combined fold text {{{
  86. return filler.statBox
  87. " }}}
  88. endif
  89. " }}}
  90. " Handle all other foldmethods {{{
  91. " Prepare fold indent and indicator {{{
  92. " - If indent allows, build the indicator into it.
  93. let foldIndicator = '▸ '
  94. let indLen = strdisplaywidth(foldIndicator)
  95. if indent(v:foldstart) >= indLen
  96. let indent = repeat(' ', indent(v:foldstart) - indLen) . foldIndicator
  97. else
  98. let indent = repeat(' ', indent(v:foldstart))
  99. endif
  100. " }}}
  101. " Prepare the statbox {{{
  102. " - Fixed statbox width at 18 characters.
  103. " - Count width by display cells instead of bytes if at least vim 7.4
  104. if v:version >= 704
  105. let countType = 'S'
  106. else
  107. let countType = 's'
  108. endif
  109. let statBox = '[ ' . printf('%14'.countType, lineCount.' lns, lv '.v:foldlevel) . ' ]'
  110. " }}}
  111. " Prepare fold description {{{
  112. " - Remove fold markers and comment markers.
  113. " - Truncate to 1/3 of the current window width.
  114. " Use function argument as line text if provided {{{
  115. let line = a:0 > 0 ? a:1 : getline(v:foldstart)
  116. " }}}
  117. " Remove fold markers {{{
  118. let foldmarkers = split(&foldmarker, ',')
  119. let line = substitute(line, '\V' . foldmarkers[0] . '\%(\d\+\)\?\s\*', '', '')
  120. " }}}
  121. " Remove surrounding whitespace {{{
  122. let line = substitute(line, '^\s*\(.\{-}\)\s*$', '\1', '')
  123. " }}}
  124. " Add an extra space at the end {{{
  125. let foldDesc = line.' '
  126. " }}}
  127. " }}}
  128. " Prepare filler lines {{{
  129. " - midFiller is the fill between the description and statbox.
  130. " - midFiller compensates for column widths generated by foldcolumn, number
  131. " and relativenumber.
  132. let endFiller = repeat(foldChar, 1)
  133. let midFillerLength = displayWidth - strdisplaywidth(indent.foldDesc.statBox.endFiller)
  134. let midFiller = repeat(foldChar, midFillerLength)
  135. " }}}
  136. " Output the combined fold text {{{
  137. return indent.foldDesc.midFiller.statBox.endFiller
  138. " }}}
  139. " }}}
  140. endfunction
  141. " }}}
  142. " Lokaltog's Fold Text for learning more stuff about fold description preparation {{{
  143. function! FoldText(...)
  144. " This function uses code from doy's vim-foldtext: https://github.com/doy/vim-foldtext
  145. " Prepare fold variables {{{
  146. " Use function argument as line text if provided
  147. let l:line = a:0 > 0 ? a:1 : getline(v:foldstart)
  148. let l:line_count = v:foldend - v:foldstart + 1
  149. let l:indent = repeat(' ', indent(v:foldstart))
  150. let l:w_win = winwidth(0)
  151. let l:w_num = getwinvar(0, '&number') * getwinvar(0, '&numberwidth')
  152. let l:w_fold = getwinvar(0, '&foldcolumn')
  153. " }}}
  154. " Handle diff foldmethod {{{
  155. if &fdm == 'diff'
  156. let l:text = printf('┤ %s matching lines ├', l:line_count)
  157. " Center-align the foldtext
  158. return repeat('┄', (l:w_win - strchars(l:text) - l:w_num - l:w_fold) / 2) . l:text
  159. endif
  160. " }}}
  161. " Handle other foldmethods {{{
  162. let l:text = l:line
  163. " Remove foldmarkers {{{
  164. let l:foldmarkers = split(&foldmarker, ',')
  165. let l:text = substitute(l:text, '\V' . l:foldmarkers[0] . '\%(\d\+\)\?\s\*', '', '')
  166. " }}}
  167. " Remove comments {{{
  168. let l:comment = split(&commentstring, '%s')
  169. if l:comment[0] != ''
  170. let l:comment_begin = l:comment[0]
  171. let l:comment_end = ''
  172. if len(l:comment) > 1
  173. let l:comment_end = l:comment[1]
  174. endif
  175. let l:pattern = '\V' . l:comment_begin . '\s\*' . l:comment_end . '\s\*\$'
  176. if l:text =~ l:pattern
  177. let l:text = substitute(l:text, l:pattern, ' ', '')
  178. else
  179. let l:text = substitute(l:text, '.*\V' . l:comment_begin, ' ', '')
  180. if l:comment_end != ''
  181. let l:text = substitute(l:text, '\V' . l:comment_end, ' ', '')
  182. endif
  183. endif
  184. endif
  185. " }}}
  186. " Remove preceding non-word characters {{{
  187. let l:text = substitute(l:text, '^\W*', '', '')
  188. " }}}
  189. " Remove surrounding whitespace {{{
  190. let l:text = substitute(l:text, '^\s*\(.\{-}\)\s*$', '\1', '')
  191. " }}}
  192. " Make unmatched block delimiters prettier {{{
  193. let l:text = substitute(l:text, '([^)]*$', '⟯ ⋯ ⟮', '')
  194. let l:text = substitute(l:text, '{[^}]*$', '⟯ ⋯ ⟮', '')
  195. let l:text = substitute(l:text, '\[[^\]]*$', '⟯ ⋯ ⟮', '')
  196. " }}}
  197. " Add arrows when indent level > 2 spaces {{{
  198. if indent(v:foldstart) > 2
  199. let l:cline = substitute(l:line, '^\s*\(.\{-}\)\s*$', '\1', '')
  200. let l:clen = strlen(matchstr(l:cline, '^\W*'))
  201. let l:indent = repeat(' ', indent(v:foldstart) - 2)
  202. let l:text = '▸ ' . l:text
  203. endif
  204. " }}}
  205. " Prepare fold text {{{
  206. let l:fnum = printf('┤ %s ⭡ ', printf('%4s', l:line_count))
  207. let l:ftext = printf('%s%s ', l:indent, l:text)
  208. " }}}
  209. return l:ftext . repeat('┄', l:w_win - strchars(l:fnum) - strchars(l:ftext) - l:w_num - l:w_fold) . l:fnum
  210. " }}}
  211. endfunction
  212. " }}}
  213. endif