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