limelight.vim 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282
  1. " Copyright (c) 2015 Junegunn Choi
  2. "
  3. " MIT License
  4. "
  5. " Permission is hereby granted, free of charge, to any person obtaining
  6. " a copy of this software and associated documentation files (the
  7. " "Software"), to deal in the Software without restriction, including
  8. " without limitation the rights to use, copy, modify, merge, publish,
  9. " distribute, sublicense, and/or sell copies of the Software, and to
  10. " permit persons to whom the Software is furnished to do so, subject to
  11. " the following conditions:
  12. "
  13. " The above copyright notice and this permission notice shall be
  14. " included in all copies or substantial portions of the Software.
  15. "
  16. " THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  17. " EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  18. " MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  19. " NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  20. " LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  21. " OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  22. " WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  23. if exists('g:loaded_limelight')
  24. finish
  25. endif
  26. let g:loaded_limelight = 1
  27. let s:cpo_save = &cpo
  28. set cpo&vim
  29. let s:default_coeff = str2float('0.5')
  30. let s:invalid_coefficient = 'Invalid coefficient. Expected: 0.0 ~ 1.0'
  31. function! s:unsupported()
  32. let var = 'g:limelight_conceal_'.(has('gui_running') ? 'gui' : 'cterm').'fg'
  33. if exists(var)
  34. return 'Cannot calculate background color.'
  35. else
  36. return 'Unsupported color scheme. '.var.' required.'
  37. endif
  38. endfunction
  39. function! s:getpos()
  40. let bop = get(g:, 'limelight_bop', '^\s*$\n\zs')
  41. let eop = get(g:, 'limelight_eop', '^\s*$')
  42. let span = max([0, get(g:, 'limelight_paragraph_span', 0) - s:empty(getline('.'))])
  43. let pos = exists('*getcurpos')? getcurpos() : getpos('.')
  44. for i in range(0, span)
  45. let start = searchpos(bop, i == 0 ? 'cbW' : 'bW')[0]
  46. endfor
  47. call setpos('.', pos)
  48. for _ in range(0, span)
  49. let end = searchpos(eop, 'W')[0]
  50. endfor
  51. call setpos('.', pos)
  52. return [start, end]
  53. endfunction
  54. function! s:empty(line)
  55. return (a:line =~# '^\s*$')
  56. endfunction
  57. function! s:limelight()
  58. if !empty(get(w:, 'limelight_range', []))
  59. return
  60. endif
  61. if !exists('w:limelight_prev')
  62. let w:limelight_prev = [0, 0, 0, 0]
  63. endif
  64. let curr = [line('.'), line('$')]
  65. if curr ==# w:limelight_prev[0 : 1]
  66. return
  67. endif
  68. let paragraph = s:getpos()
  69. if paragraph ==# w:limelight_prev[2 : 3]
  70. return
  71. endif
  72. call s:clear_hl()
  73. call call('s:hl', paragraph)
  74. let w:limelight_prev = extend(curr, paragraph)
  75. endfunction
  76. function! s:hl(startline, endline)
  77. let w:limelight_match_ids = get(w:, 'limelight_match_ids', [])
  78. let priority = get(g:, 'limelight_priority', 10)
  79. call add(w:limelight_match_ids, matchadd('LimelightDim', '\%<'.a:startline.'l', priority))
  80. if a:endline > 0
  81. call add(w:limelight_match_ids, matchadd('LimelightDim', '\%>'.a:endline.'l', priority))
  82. endif
  83. endfunction
  84. function! s:clear_hl()
  85. while exists('w:limelight_match_ids') && !empty(w:limelight_match_ids)
  86. silent! call matchdelete(remove(w:limelight_match_ids, -1))
  87. endwhile
  88. endfunction
  89. function! s:hex2rgb(str)
  90. let str = substitute(a:str, '^#', '', '')
  91. return [eval('0x'.str[0:1]), eval('0x'.str[2:3]), eval('0x'.str[4:5])]
  92. endfunction
  93. let s:gray_converter = {
  94. \ 0: 231,
  95. \ 7: 254,
  96. \ 15: 256,
  97. \ 16: 231,
  98. \ 231: 256
  99. \ }
  100. function! s:gray_contiguous(col)
  101. let val = get(s:gray_converter, a:col, a:col)
  102. if val < 231 || val > 256
  103. throw s:unsupported()
  104. endif
  105. return val
  106. endfunction
  107. function! s:gray_ansi(col)
  108. return a:col == 231 ? 0 : (a:col == 256 ? 231 : a:col)
  109. endfunction
  110. function! s:coeff(coeff)
  111. let coeff = a:coeff < 0 ?
  112. \ get(g:, 'limelight_default_coefficient', s:default_coeff) : a:coeff
  113. if coeff < 0 || coeff > 1
  114. throw 'Invalid g:limelight_default_coefficient. Expected: 0.0 ~ 1.0'
  115. endif
  116. return coeff
  117. endfunction
  118. function! s:dim(coeff)
  119. let synid = synIDtrans(hlID('Normal'))
  120. let fg = synIDattr(synid, 'fg#')
  121. let bg = synIDattr(synid, 'bg#')
  122. if has('gui_running') || has('termguicolors') && &termguicolors || has('nvim') && $NVIM_TUI_ENABLE_TRUE_COLOR
  123. if a:coeff < 0 && exists('g:limelight_conceal_guifg')
  124. let dim = g:limelight_conceal_guifg
  125. elseif empty(fg) || empty(bg)
  126. throw s:unsupported()
  127. else
  128. let coeff = s:coeff(a:coeff)
  129. let fg_rgb = s:hex2rgb(fg)
  130. let bg_rgb = s:hex2rgb(bg)
  131. let dim_rgb = [
  132. \ bg_rgb[0] * coeff + fg_rgb[0] * (1 - coeff),
  133. \ bg_rgb[1] * coeff + fg_rgb[1] * (1 - coeff),
  134. \ bg_rgb[2] * coeff + fg_rgb[2] * (1 - coeff)]
  135. let dim = '#'.join(map(dim_rgb, 'printf("%x", float2nr(v:val))'), '')
  136. endif
  137. execute printf('hi LimelightDim guifg=%s guisp=bg', dim)
  138. elseif &t_Co == 256
  139. if a:coeff < 0 && exists('g:limelight_conceal_ctermfg')
  140. let dim = g:limelight_conceal_ctermfg
  141. elseif fg <= -1 || bg <= -1
  142. throw s:unsupported()
  143. else
  144. let coeff = s:coeff(a:coeff)
  145. let fg = s:gray_contiguous(fg)
  146. let bg = s:gray_contiguous(bg)
  147. let dim = s:gray_ansi(float2nr(bg * coeff + fg * (1 - coeff)))
  148. endif
  149. if type(dim) == 1
  150. execute printf('hi LimelightDim ctermfg=%s', dim)
  151. else
  152. execute printf('hi LimelightDim ctermfg=%d', dim)
  153. endif
  154. else
  155. throw 'Unsupported terminal. Sorry.'
  156. endif
  157. endfunction
  158. function! s:error(msg)
  159. echohl ErrorMsg
  160. echo a:msg
  161. echohl None
  162. endfunction
  163. function! s:parse_coeff(coeff)
  164. let t = type(a:coeff)
  165. if t == 1
  166. if a:coeff =~ '^ *[0-9.]\+ *$'
  167. let c = str2float(a:coeff)
  168. else
  169. throw s:invalid_coefficient
  170. endif
  171. elseif index([0, 5], t) >= 0
  172. let c = t
  173. else
  174. throw s:invalid_coefficient
  175. endif
  176. return c
  177. endfunction
  178. function! s:on(range, ...)
  179. try
  180. let s:limelight_coeff = a:0 > 0 ? s:parse_coeff(a:1) : -1
  181. call s:dim(s:limelight_coeff)
  182. catch
  183. return s:error(v:exception)
  184. endtry
  185. let w:limelight_range = a:range
  186. if !empty(a:range)
  187. call s:clear_hl()
  188. call call('s:hl', a:range)
  189. endif
  190. augroup limelight
  191. let was_on = exists('#limelight#CursorMoved')
  192. autocmd!
  193. if empty(a:range) || was_on
  194. autocmd CursorMoved,CursorMovedI * call s:limelight()
  195. endif
  196. autocmd ColorScheme * try
  197. \| call s:dim(s:limelight_coeff)
  198. \| catch
  199. \| call s:off()
  200. \| throw v:exception
  201. \| endtry
  202. augroup END
  203. " FIXME: We cannot safely remove this group once Limelight started
  204. augroup limelight_cleanup
  205. autocmd!
  206. autocmd WinEnter * call s:cleanup()
  207. augroup END
  208. doautocmd CursorMoved
  209. endfunction
  210. function! s:off()
  211. call s:clear_hl()
  212. augroup limelight
  213. autocmd!
  214. augroup END
  215. augroup! limelight
  216. unlet! w:limelight_prev w:limelight_match_ids w:limelight_range
  217. endfunction
  218. function! s:is_on()
  219. return exists('#limelight')
  220. endfunction
  221. function! s:cleanup()
  222. if !s:is_on()
  223. call s:clear_hl()
  224. end
  225. endfunction
  226. function! limelight#execute(bang, visual, line1, line2, ...)
  227. let range = a:visual ? [a:line1, a:line2] : []
  228. if a:bang
  229. if a:0 > 0 && a:1 =~ '^!' && !s:is_on()
  230. if len(a:1) > 1
  231. call s:on(range, a:1[1:-1])
  232. else
  233. call s:on(range)
  234. endif
  235. else
  236. call s:off()
  237. endif
  238. elseif a:0 > 0
  239. call s:on(range, a:1)
  240. else
  241. call s:on(range)
  242. endif
  243. endfunction
  244. function! limelight#operator(...)
  245. call limelight#execute(0, 1, line("'["), line("']"))
  246. endfunction
  247. let &cpo = s:cpo_save
  248. unlet s:cpo_save