You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
376 lines
9.8 KiB
VimL
376 lines
9.8 KiB
VimL
7 years ago
|
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,
|
||
|
\ 'custom_cb': 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],
|
||
|
\ 'custom_cb': 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')
|
||
|
function! s:error_info_cb(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)
|
||
|
endfunction
|
||
|
|
||
|
let a:args.error_info_cb = funcref('s:error_info_cb')
|
||
|
let callbacks = go#job#Spawn(a:args)
|
||
|
|
||
|
let start_options = {
|
||
|
\ 'callback': callbacks.callback,
|
||
|
\ 'exit_cb': callbacks.exit_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
|