1" AnsiHighlight: Allows for marking up a file, using ANSI color escapes when 2" the syntax changes colors, for easy, faithful reproduction. 3" Author: Matthew Wozniski (mjw@drexel.edu) 4" Date: Fri, 01 Aug 2008 05:22:55 -0400 5" Version: 1.0 FIXME 6" History: FIXME see :help marklines-history 7" License: BSD. Completely open source, but I would like to be 8" credited if you use some of this code elsewhere. 9 10" Copyright (c) 2015, Matthew J. Wozniski {{{1 11" 12" Redistribution and use in source and binary forms, with or without 13" modification, are permitted provided that the following conditions are met: 14" * Redistributions of source code must retain the above copyright 15" notice, this list of conditions and the following disclaimer. 16" * Redistributions in binary form must reproduce the above copyright 17" notice, this list of conditions and the following disclaimer in the 18" documentation and/or other materials provided with the distribution. 19" 20" THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER ``AS IS'' AND ANY 21" EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 22" WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23" DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY 24" DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 25" (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 26" LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 27" ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 29" SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 31" Converts info for a highlight group to a string of ANSI color escapes {{{1 32 33scriptencoding utf-8 34 35let s:save_cpo = &cpo 36set cpo&vim 37 38function! s:GroupToAnsi(groupnum) 39 if ! exists("s:ansicache") 40 let s:ansicache = {} 41 endif 42 43 let groupnum = a:groupnum 44 45 if groupnum == 0 46 let groupnum = hlID('Normal') 47 endif 48 49 if has_key(s:ansicache, groupnum) 50 return s:ansicache[groupnum] 51 endif 52 53 let fg = synIDattr(groupnum, 'fg', s:type) 54 let bg = synIDattr(groupnum, 'bg', s:type) 55 let rv = synIDattr(groupnum, 'reverse', s:type) 56 let bd = synIDattr(groupnum, 'bold', s:type) 57 58 " FIXME other attributes? 59 60 if rv == "" || rv == -1 61 let rv = 0 62 endif 63 64 if bd == "" || bd == -1 65 let bd = 0 66 endif 67 68 if rv 69 let temp = bg 70 let bg = fg 71 let fg = temp 72 endif 73 74 if fg == "" || fg == -1 75 unlet fg 76 endif 77 78 if !exists('fg') && groupnum != hlID('Normal') 79 let fg = synIDattr(hlID('Normal'), 'fg', s:type) 80 if fg == "" || fg == -1 81 unlet fg 82 endif 83 endif 84 85 if bg == "" || bg == -1 86 unlet bg 87 endif 88 89 if !exists('bg') 90 let bg = synIDattr(hlID('Normal'), 'bg', s:type) 91 if bg == "" || bg == -1 92 unlet bg 93 endif 94 endif 95 96 let retv = "\<Esc>[22;24;25;27;28" 97 98 if bd 99 let retv .= ";1" 100 endif 101 102 if exists('fg') && fg < 8 103 let retv .= ";3" . fg 104 elseif exists('fg') && fg < 16 "use aixterm codes 105 let retv .= ";9" . (fg - 8) 106 elseif exists('fg') "use xterm256 codes 107 let retv .= ";38;5;" . fg 108 else 109 let retv .= ";39" 110 endif 111 112 if exists('bg') && bg < 8 113 let retv .= ";4" . bg 114 elseif exists('bg') && bg < 16 "use aixterm codes 115 let retv .= ";10" . (bg - 8) 116 elseif exists('bg') "use xterm256 codes 117 let retv .= ";48;5;" . bg 118 else 119 let retv .= ";49" 120 endif 121 122 let retv .= "m" 123 124 let s:ansicache[groupnum] = retv 125 126 return retv 127endfunction 128 129function! s:ReadChunks() 130 let chunk_files = filter(split(glob(s:pipeline_dir . '/*', 1), '\n'), 'getfsize(v:val) > 0') 131 132 if !len(chunk_files) 133 return 0 134 endif 135 136 call sort(chunk_files) 137 138 for chunk_file in chunk_files 139 let block = readfile(chunk_file, 'b') 140 141 let chunk_newline = 0 142 143 if block[-1] == '' 144 let chunk_newline = 1 145 call remove(block, -1) 146 endif 147 148 let lines = [] 149 150 if line('$') == 1 && getline(1) == '' 151 let append_to = 0 152 else 153 if !s:chunk_newline 154 let lines = [ getline(line('$')) . remove(block, 0) ] 155 $d 156 endif 157 158 let append_to = line('$') 159 endif 160 161 let lines += block 162 163 call append(append_to, lines) 164 165 if append_to == 0 166 $d 167 endif 168 169 if s:chunk_num > 20 170 normal '0ggd/\_.\{4096}/e' 171 endif 172 173 let s:chunk_newline = chunk_newline 174 175 let s:chunk_num += 1 176 endfor 177 178 call map(chunk_files, 'delete(v:val)') 179 180 return 1 181endfunction 182 183function! s:AnsiHighlight(output_file, line_numbers, pipeline_dir) 184 let s:pipeline_dir = a:pipeline_dir 185 186 setlocal modifiable noreadonly buflisted buftype=nowrite 187 188 if a:pipeline_dir != '' 189 let s:chunk_num = 1 190 let s:chunk_newline = 0 191 192 while !s:ReadChunks() 193 sleep 100 m 194 endwhile 195 196 let ln_field_len = 7 197 else 198 let ln_field_len = len(line('$')) 199 endif 200 201 if &l:ft == '' 202 filetype detect 203 endif 204 syntax enable 205 syntax sync minlines=500 maxlines=500 206 207 let done = 0 208 let lnum = 1 209 210 while !done 211 let last = hlID('Normal') 212 let output = s:GroupToAnsi(last) . "\<Esc>[K" " Clear to right 213 214 " Hopefully fix highlighting sync issues 215 exe "norm! " . lnum . "G$" 216 217 let line = getline(lnum) 218 let cnum = 1 219 220 while cnum <=# col('.') 221 " skip ansi codes in the file 222 if cnum <=# col('.') - 1 && line[cnum-1] ==# "\e" && line[cnum] ==# '[' 223 let cnum += 2 224 while match(line[cnum-1], '[A-Za-z]') ==# -1 225 let cnum += 1 226 endwhile 227 228 let cnum += 1 229 continue 230 endif 231 232 let concealed = synconcealed(lnum, cnum) 233 234 if empty(concealed) " no conceal feature 235 let concealed = [0] 236 endif 237 238 if concealed[0] !=# 1 && synIDtrans(synID(lnum, cnum, 1)) != last 239 let last = synIDtrans(synID(lnum, cnum, 1)) 240 let output .= s:GroupToAnsi(last) 241 endif 242 243 if concealed[0] ==# 1 && &conceallevel !=# 0 244 if &conceallevel ==# 1 || &conceallevel ==# 2 245 let output .= concealed[1] 246 endif 247 else 248 let output .= line[cnum-1] 249 endif 250 "let line = substitute(line, '.', '', '') 251 "let line = matchstr(line, '^\@<!.*') 252 let cnum += 1 253 endwhile 254 255 if a:line_numbers 256 let output = printf("\<Esc>[0m\<Esc>[37;1m%" . ln_field_len . "d ", lnum) . output 257 endif 258 259 call writefile([output . "\<Esc>[0m\r"], a:output_file, 'a') 260 261 let lnum += 1 262 263 if a:pipeline_dir == '' 264 if lnum > line('$') 265 let done = 1 266 endif 267 else 268 if lnum % 30 == 0 269 call s:ReadChunks() 270 endif 271 272 if lnum > line('$') 273 let stream_underrun = 1 274 275 while stream_underrun 276 let stream_underrun = !s:ReadChunks() 277 278 if stream_underrun 279 if filereadable(a:pipeline_dir . '/PIPELINE_DONE') 280 let done = 1 281 break 282 endif 283 284 sleep 100 m 285 endif 286 endwhile 287 endif 288 endif 289 endwhile 290 291 return 1 292endfunction 293 294function! vimcat#Init(opts) 295 " save for other functions 296 let s:opts = a:opts 297 298 " Turn off vi-compatible mode, unless it's already off {{{1 299 if !has('nvim') && &cp 300 set nocp 301 endif 302 303 let s:type = 'cterm' 304 if &t_Co == 0 305 let s:type = 'term' 306 endif 307 308 set foldlevel=9999 309endfunction 310 311function! s:SetHighlightOpts() 312 " set sensible default highlight options for people without an rc 313 if (s:opts.rc =~? '^ *$' || s:opts.rc =~? '^ *NO\(NE\|RC\) *$') && $MYVIMRC =~? '^ *$' 314 set bg=dark 315 highlight Normal ctermbg=NONE 316 endif 317 318 syntax enable 319endfunction 320 321function! vimcat#Run(output_file, line_numbers, pipeline_dir, pipeline_start) 322 call s:SetHighlightOpts() 323 silent! execute 'file ' . fnameescape(a:pipeline_start) 324 setlocal buftype=nowrite modifiable noreadonly viminfo= 325 call s:AnsiHighlight(a:output_file, a:line_numbers, a:pipeline_dir) 326 if !exists('$VIMCAT_DEBUG') || $VIMCAT_DEBUG == 0 327 quitall! 328 endif 329endfunction 330 331let &cpo = s:save_cpo 332 333" See copyright in the vim script above (for the vim script) and in 334" vimcat.md for the whole script. 335" 336" The list of contributors is at the bottom of the vimpager script in this 337" project. 338" 339" vim: sw=4 et ft=vim: 340