123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378 |
- let s:toggle = 0
- " Buffer creates a new cover profile with 'go test -coverprofile' and changes
- " the current buffers highlighting to show covered and uncovered sections of
- " the code. If run again it clears the annotation.
- function! go#coverage#BufferToggle(bang, ...) abort
- if s:toggle
- call go#coverage#Clear()
- return
- endif
- if a:0 == 0
- return call(function('go#coverage#Buffer'), [a:bang])
- endif
- return call(function('go#coverage#Buffer'), [a:bang] + a:000)
- endfunction
- " Buffer creates a new cover profile with 'go test -coverprofile' and changes
- " the current buffers highlighting to show covered and uncovered sections of
- " the code. Calling it again reruns the tests and shows the last updated
- " coverage.
- function! go#coverage#Buffer(bang, ...) abort
- " we use matchaddpos() which was introduce with 7.4.330, be sure we have
- " it: http://ftp.vim.org/vim/patches/7.4/7.4.330
- if !exists("*matchaddpos")
- call go#util#EchoError("GoCoverage is supported with Vim version 7.4-330 or later")
- return -1
- endif
- " check if there is any test file, if not we just return
- let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd '
- let dir = getcwd()
- try
- execute cd . fnameescape(expand("%:p:h"))
- if empty(glob("*_test.go"))
- call go#util#EchoError("no test files available")
- return
- endif
- finally
- execute cd . fnameescape(dir)
- endtry
- let s:toggle = 1
- let l:tmpname = tempname()
- if get(g:, 'go_echo_command_info', 1)
- echon "vim-go: " | echohl Identifier | echon "testing ..." | echohl None
- endif
- if go#util#has_job()
- call s:coverage_job({
- \ 'cmd': ['go', 'test', '-coverprofile', l:tmpname] + a:000,
- \ 'complete': function('s:coverage_callback', [l:tmpname]),
- \ 'bang': a:bang,
- \ 'for': 'GoTest',
- \ })
- return
- endif
- let args = [a:bang, 0, "-coverprofile", l:tmpname]
- if a:0
- call extend(args, a:000)
- endif
- let disabled_term = 0
- if get(g:, 'go_term_enabled')
- let disabled_term = 1
- let g:go_term_enabled = 0
- endif
- let id = call('go#test#Test', args)
- if disabled_term
- let g:go_term_enabled = 1
- endif
- if has('nvim')
- call go#jobcontrol#AddHandler(function('s:coverage_handler'))
- let s:coverage_handler_jobs[id] = l:tmpname
- return
- endif
- if go#util#ShellError() == 0
- call go#coverage#overlay(l:tmpname)
- endif
- call delete(l:tmpname)
- endfunction
- " Clear clears and resets the buffer annotation matches
- function! go#coverage#Clear() abort
- call clearmatches()
- if exists("s:toggle") | let s:toggle = 0 | endif
- " remove the autocmd we defined
- augroup vim-go-coverage
- autocmd!
- augroup end
- endfunction
- " Browser creates a new cover profile with 'go test -coverprofile' and opens
- " a new HTML coverage page from that profile in a new browser
- function! go#coverage#Browser(bang, ...) abort
- let l:tmpname = tempname()
- if go#util#has_job()
- call s:coverage_job({
- \ 'cmd': ['go', 'test', '-coverprofile', l:tmpname],
- \ 'complete': function('s:coverage_browser_callback', [l:tmpname]),
- \ 'bang': a:bang,
- \ 'for': 'GoTest',
- \ })
- return
- endif
- let args = [a:bang, 0, "-coverprofile", l:tmpname]
- if a:0
- call extend(args, a:000)
- endif
- let id = call('go#test#Test', args)
- if has('nvim')
- call go#jobcontrol#AddHandler(function('s:coverage_browser_handler'))
- let s:coverage_browser_handler_jobs[id] = l:tmpname
- return
- endif
- if go#util#ShellError() == 0
- let openHTML = 'go tool cover -html='.l:tmpname
- call go#tool#ExecuteInDir(openHTML)
- endif
- call delete(l:tmpname)
- endfunction
- " Parses a single line from the cover file generated via go test -coverprofile
- " and returns a single coverage profile block.
- function! go#coverage#parsegocoverline(line) abort
- " file:startline.col,endline.col numstmt count
- let mx = '\([^:]\+\):\(\d\+\)\.\(\d\+\),\(\d\+\)\.\(\d\+\)\s\(\d\+\)\s\(\d\+\)'
- let tokens = matchlist(a:line, mx)
- let ret = {}
- let ret.file = tokens[1]
- let ret.startline = str2nr(tokens[2])
- let ret.startcol = str2nr(tokens[3])
- let ret.endline = str2nr(tokens[4])
- let ret.endcol = str2nr(tokens[5])
- let ret.numstmt = tokens[6]
- let ret.cnt = tokens[7]
- return ret
- endfunction
- " Generates matches to be added to matchaddpos for the given coverage profile
- " block
- function! go#coverage#genmatch(cov) abort
- let color = 'goCoverageCovered'
- if a:cov.cnt == 0
- let color = 'goCoverageUncover'
- endif
- let matches = []
- " if start and end are the same, also specify the byte length
- " example: foo.go:92.2,92.65 1 0
- if a:cov.startline == a:cov.endline
- call add(matches, {
- \ 'group': color,
- \ 'pos': [[a:cov.startline, a:cov.startcol, a:cov.endcol - a:cov.startcol]],
- \ 'priority': 2,
- \ })
- return matches
- endif
- " add start columns. Because we don't know the length of the of
- " the line, we assume it is at maximum 200 bytes. I know this is hacky,
- " but that's only way of fixing the issue
- call add(matches, {
- \ 'group': color,
- \ 'pos': [[a:cov.startline, a:cov.startcol, 200]],
- \ 'priority': 2,
- \ })
- " and then the remaining lines
- let start_line = a:cov.startline
- while start_line < a:cov.endline
- let start_line += 1
- call add(matches, {
- \ 'group': color,
- \ 'pos': [[start_line]],
- \ 'priority': 2,
- \ })
- endwhile
- " finally end columns
- call add(matches, {
- \ 'group': color,
- \ 'pos': [[a:cov.endline, a:cov.endcol-1]],
- \ 'priority': 2,
- \ })
- return matches
- endfunction
- " Reads the given coverprofile file and annotates the current buffer
- function! go#coverage#overlay(file) abort
- if !filereadable(a:file)
- return
- endif
- let lines = readfile(a:file)
- " cover mode, by default it's 'set'. Just here for debugging purposes
- let mode = lines[0]
- " contains matches for matchaddpos()
- let matches = []
- " first mark all lines as goCoverageNormalText. We use a custom group to not
- " interfere with other buffers highlightings. Because the priority is
- " lower than the cover and uncover matches, it'll be overridden.
- let cnt = 1
- while cnt <= line('$')
- call add(matches, {'group': 'goCoverageNormalText', 'pos': [cnt], 'priority': 1})
- let cnt += 1
- endwhile
- let fname = expand('%')
- " when called for a _test.go file, run the coverage for the actuall file
- " file
- if fname =~# '^\f\+_test\.go$'
- let l:root = split(fname, '_test.go$')[0]
- let fname = l:root . ".go"
- if !filereadable(fname)
- call go#util#EchoError("couldn't find ".fname)
- return
- endif
- " open the alternate file to show the coverage
- exe ":edit ". fnamemodify(fname, ":p")
- endif
- " cov.file includes only the filename itself, without full path
- let fname = fnamemodify(fname, ":t")
- for line in lines[1:]
- let cov = go#coverage#parsegocoverline(line)
- " TODO(arslan): for now only include the coverage for the current
- " buffer
- if fname != fnamemodify(cov.file, ':t')
- continue
- endif
- call extend(matches, go#coverage#genmatch(cov))
- endfor
- " clear the matches if we leave the buffer
- augroup vim-go-coverage
- autocmd!
- autocmd BufWinLeave <buffer> call go#coverage#Clear()
- augroup end
- for m in matches
- call matchaddpos(m.group, m.pos)
- endfor
- endfunction
- " ---------------------
- " | Vim job callbacks |
- " ---------------------
- "
- function s:coverage_job(args)
- " autowrite is not enabled for jobs
- call go#cmd#autowrite()
- let status_dir = expand('%:p:h')
- let Complete = a:args.complete
- function! s:complete(job, exit_status, data) closure
- let status = {
- \ 'desc': 'last status',
- \ 'type': "coverage",
- \ 'state': "finished",
- \ }
- if a:exit_status
- let status.state = "failed"
- endif
- call go#statusline#Update(status_dir, status)
- return Complete(a:job, a:exit_status, a:data)
- endfunction
- let a:args.complete = funcref('s:complete')
- let callbacks = go#job#Spawn(a:args)
- let start_options = {
- \ 'callback': callbacks.callback,
- \ 'exit_cb': callbacks.exit_cb,
- \ 'close_cb': callbacks.close_cb,
- \ }
- " pre start
- let dir = getcwd()
- let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd '
- let jobdir = fnameescape(expand("%:p:h"))
- execute cd . jobdir
- call go#statusline#Update(status_dir, {
- \ 'desc': "current status",
- \ 'type': "coverage",
- \ 'state': "started",
- \})
- call job_start(a:args.cmd, start_options)
- " post start
- execute cd . fnameescape(dir)
- endfunction
- " coverage_callback is called when the coverage execution is finished
- function! s:coverage_callback(coverfile, job, exit_status, data)
- if a:exit_status == 0
- call go#coverage#overlay(a:coverfile)
- endif
- call delete(a:coverfile)
- endfunction
- function! s:coverage_browser_callback(coverfile, job, exit_status, data)
- if a:exit_status == 0
- let openHTML = 'go tool cover -html='.a:coverfile
- call go#tool#ExecuteInDir(openHTML)
- endif
- call delete(a:coverfile)
- endfunction
- " -----------------------
- " | Neovim job handlers |
- " -----------------------
- let s:coverage_handler_jobs = {}
- let s:coverage_browser_handler_jobs = {}
- function! s:coverage_handler(job, exit_status, data) abort
- if !has_key(s:coverage_handler_jobs, a:job.id)
- return
- endif
- let l:tmpname = s:coverage_handler_jobs[a:job.id]
- if a:exit_status == 0
- call go#coverage#overlay(l:tmpname)
- endif
- call delete(l:tmpname)
- unlet s:coverage_handler_jobs[a:job.id]
- endfunction
- function! s:coverage_browser_handler(job, exit_status, data) abort
- if !has_key(s:coverage_browser_handler_jobs, a:job.id)
- return
- endif
- let l:tmpname = s:coverage_browser_handler_jobs[a:job.id]
- if a:exit_status == 0
- let openHTML = 'go tool cover -html='.l:tmpname
- call go#tool#ExecuteInDir(openHTML)
- endif
- call delete(l:tmpname)
- unlet s:coverage_browser_handler_jobs[a:job.id]
- endfunction
- " vim: sw=2 ts=2 et
|