Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Coverage overlay in buffers #785

Closed
wants to merge 21 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
source 'https://rubygems.org'

gem 'vim-flavor', '~> 2.2.1'
13 changes: 13 additions & 0 deletions Rakefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#!/usr/bin/env rake

task :ci => [:dump, :test]

task :dump do
sh 'vim --version'
end

# Firstly, `bundle install; bundle install --deployment`
# Then, `rake test`
task :test do
sh 'bundle exec vim-flavor test'
end
Empty file added VimFlavor
Empty file.
174 changes: 174 additions & 0 deletions autoload/go/coverlay.vim
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
if !exists("g:go_gopath")
let g:go_gopath = $GOPATH
endif

augroup plugin-go-coverlay
autocmd!
autocmd BufEnter,BufWinEnter,BufFilePost * call go#coverlay#draw()
autocmd BufWinLeave * call go#coverlay#clear()
augroup END
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be moved to our autogroup section in file plugin/go.vim


function! go#coverlay#draw()
call go#coverlay#hook()
call go#coverlay#clear()
for m in b:go_coverlay_matches
let id = matchadd(m.group, m.pattern, m.priority)
call add(b:go_coverlay_match_ids, id)
endfor
endfunction

function! go#coverlay#hook()
"TODO: can we initialize buf local vars more smartly?
if !exists("b:go_coverlay_matches")
let b:go_coverlay_matches = []
endif
if !exists("b:go_coverlay_match_ids")
let b:go_coverlay_match_ids = []
endif
endfunction

"findbufnr look for the number of buffer that opens `file`,
" as it is displayed by the ":ls" command.
"If the buffer doesn't exist, -1 is returned.
function! go#coverlay#findbufnr(file)
if a:file[0] == "_"
return bufnr(a:file[1:])
endif
for path in split(g:go_gopath, ':')
let nr = bufnr(path . '/src/' . a:file)
if nr != -1
return nr
endif
endfor
return -1
endfunction

function! go#coverlay#isopenedon(file, bufnr)
if a:file[0] == "_"
if bufnr(a:file[1:]) == a:bufnr
return 1
endif
return 0
endif
for path in split(g:go_gopath, ':')
if bufnr(path . '/src/' . a:file) == a:bufnr
return 1
endif
endfor
return 0
endfunction

function! go#coverlay#parsegocoverline(line)
" file:startline.col,endline.col numstmt count
let mx = '\([^:]\+\):\(\d\+\)\.\(\d\+\),\(\d\+\)\.\(\d\+\)\s\(\d\+\)\s\(\d\+\)'
let l = matchstr(a:line, mx)
let ret = {}
let ret.file = substitute(l, mx, '\1', '')
let ret.startline = substitute(l, mx, '\2', '')
let ret.startcol = substitute(l, mx, '\3', '')
let ret.endline = substitute(l, mx, '\4', '')
let ret.endcol = substitute(l, mx, '\5', '')
let ret.numstmt = substitute(l, mx, '\6', '')
let ret.cnt = substitute(l, mx, '\7', '')
return ret
endfunction
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can simplify it like:

function! go#coverlay#parsegocoverline(line)
    " 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  = tokens[2]
    let ret.startcol = tokens[3]
    let ret.endline = tokens[4]
    let ret.endcol = tokens[5]
    let ret.numstmt = tokens[6]
    let ret.cnt = tokens[7]
    return ret
endfunction


function! go#coverlay#genmatch(cov)
let pat1 = '\%>' . a:cov.startline . 'l'
let pat2 = '\%<' . a:cov.endline . 'l'
let pat3 = '\|\%' . a:cov.startline . 'l\_^\s\+\|\%' . a:cov.endline . 'l\_^\s\+\(\}$\)\@!'
let color = 'covered'
let prio = 6
if a:cov.cnt == 0
let color = 'uncover'
let prio = 5
endif
return {'group': color, 'pattern': pat1 . '\_^\s\+' . pat2 . pat3, 'priority': prio}
endfunction

function! go#coverlay#overlay(file)
call go#coverlay#hook()

highlight covered term=bold ctermbg=green guibg=green
highlight uncover term=bold ctermbg=red guibg=red

if !filereadable(a:file)
return
endif
let lines = readfile(a:file)
let mode = lines[0]
for line in lines[1:]
let c = go#coverlay#parsegocoverline(line)
let nr = go#coverlay#findbufnr(c.file)
if nr == -1
"should we records cov data
" even if it is not opened currently?
continue
endif
let m = go#coverlay#genmatch(c)
let matches = get(getbufvar(nr, ""), "go_coverlay_matches", [])
call add(matches, m)
call setbufvar(nr, "go_coverlay_matches", matches)
endfor
"TODO: can we draw other window for split windows mode?
call go#coverlay#draw()
endfunction

let s:coverlay_handler_id = ''
let s:coverlay_handler_jobs = {}

function! s:coverlay_handler(job, exit_status, data)
if !has_key(s:coverlay_handler_jobs, a:job.id)
return
endif
let l:tmpname = s:coverlay_handler_jobs[a:job.id]
if a:exit_status == 0
call go#coverlay#overlay(l:tmpname)
endif

call delete(l:tmpname)
unlet s:coverlay_handler_jobs[a:job.id]
endfunction

function! go#coverlay#Coverlay(bang, ...)
call go#coverlay#Clearlay()
let l:tmpname=tempname()
let args = [a:bang, 0, "-coverprofile", l:tmpname]

if a:0
call extend(args, a:000)
endif
"TODO: add -coverpkg options based on current buf list
let id = call('go#cmd#Test', args)
if has('nvim')
if s:coverlay_handler_id == ''
let s:coverlay_handler_id = go#jobcontrol#AddHandler(function('s:coverlay_handler'))
endif
let s:coverlay_handler_jobs[id] = l:tmpname
return
endif
if !v:shell_error
call go#coverlay#overlay(l:tmpname)
endif
call delete(l:tmpname)
endfunction

function! go#coverlay#Clearlay()
call go#coverlay#hook()
call go#coverlay#clear()
let b:go_coverlay_matches = []
endfunction

function! go#coverlay#clear(...)
for id in b:go_coverlay_match_ids
call matchdelete(id)
endfor
let b:go_coverlay_match_ids = []
endfunction

function! go#coverlay#matches()
call go#coverlay#hook()
return b:go_coverlay_matches
endfunction

" vim:ts=4:sw=4:et
14 changes: 14 additions & 0 deletions ftplugin/go/coverlay.vim
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
if exists("b:did_ftplugin_go_coverlay")
finish
endif
let b:did_ftplugin_go_coverlay = 1

" Some handy plug mappings
nnoremap <silent> <Plug>(go-coverlay) :<C-u>call go#coverlay#Coverlay(!g:go_jump_to_error)<CR>
nnoremap <silent> <Plug>(go-clearlay) :<C-u>call go#coverlay#Clearlay()<CR>

" coverlay
command! -nargs=* -bang GoCoverlay call go#coverlay#Coverlay(<bang>0, <f-args>)
command! -nargs=* GoClearlay call go#coverlay#Clearlay(<f-args>)

" vim:ts=4:sw=4:et
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some notes:

  1. Mappings should be moved to mappings.vim
  2. Commands should be moved to commands.vim
  3. Commands should be renamed to:
    • :GoCoverage -> :GoCoverageBrowser
    • :GoCoverlay -> :GoCoverage
    • :GoClearlay -> :GoCoverageClear

Our current command will not open the browser anymore, instead it will use this current implementation. However if people want they can still access them via :GoCoverageBrowser

191 changes: 191 additions & 0 deletions t/coverlay.vim
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
" to execute, `rake test` on parent dir

describe 'go#coverlay#Coverlay'
before
new
let g:curdir = expand('<sfile>:p:h') . '/'
let g:srcpath = 't/fixtures/src/'
let g:sample = 'pkg1/sample.go'
let g:sampleabs = g:curdir . g:srcpath . 'pkg1/sample.go'
let g:samplecover = g:curdir . g:srcpath . 'pkg1/sample.out'
let g:go_gopath = g:curdir . 't/fixtures'
execute "badd " . g:srcpath . g:sample
execute "buffer " . bufnr("$")
end
after
execute "bprev"
execute "bdelete " . g:srcpath . g:sample
close!
end

it 'puts match to the list'
call go#coverlay#Coverlay(0)
Expect len(go#coverlay#matches()) == 5
call go#coverlay#Clearlay()
Expect len(go#coverlay#matches()) == 0

call go#coverlay#Coverlay(0)
Expect len(go#coverlay#matches()) == 5
call go#coverlay#Clearlay()
Expect len(go#coverlay#matches()) == 0

call go#coverlay#Coverlay(0)
Expect len(go#coverlay#matches()) == 5
call go#coverlay#Coverlay(0)
Expect len(go#coverlay#matches()) == 5
call go#coverlay#Clearlay()
Expect len(go#coverlay#matches()) == 0
end
end

describe 'go#coverlay#Coverlay fail'
before
new
let g:curdir = expand('<sfile>:p:h') . '/'
let g:srcpath = 't/fixtures/src/'
let g:sample = 'failtest/sample.go'
let g:sampletest = 'failtest/sample_test.go'
let g:sampleabs = g:curdir . g:srcpath . 'failtest/sample.go'
let g:go_gopath = g:curdir . 't/fixtures'
execute "badd " . g:srcpath . g:sample
execute "buffer " . bufnr("$")
end
after
execute "bprev"
execute "bdelete " . g:srcpath . g:sampletest
execute "bdelete " . g:srcpath . g:sample
end

it 'does nothing if test fail'
call go#coverlay#Coverlay(0)
Expect len(go#coverlay#matches()) == 0
Expect len(getqflist()) == 1
end
end

describe 'go#coverlay#Coverlay build fail'
before
new
let g:curdir = expand('<sfile>:p:h') . '/'
let g:srcpath = 't/fixtures/src/'
let g:sample = 'buildfail/sample.go'
let g:sampleabs = g:curdir . g:srcpath . 'buildfail/sample.go'
let g:go_gopath = g:curdir . 't/fixtures'
execute "badd " . g:srcpath . g:sample
execute "buffer " . bufnr("$")
end
after
execute "bprev"
execute "bdelete " . g:srcpath . g:sample
end

it 'does nothing if test fail'
call go#coverlay#Coverlay(0)
Expect len(go#coverlay#matches()) == 0
end
end

describe 'go#coverlay#Coverlay build test fail'
before
new
let g:curdir = expand('<sfile>:p:h') . '/'
let g:srcpath = 't/fixtures/src/'
let g:sample = 'buildtestfail/sample.go'
let g:sampleabs = g:curdir . g:srcpath . 'buildtestfail/sample.go'
let g:go_gopath = g:curdir . 't/fixtures'
execute "badd " . g:srcpath . g:sample
execute "buffer " . bufnr("$")
end
after
execute "bprev"
execute "bdelete " . g:srcpath . g:sample
end

it 'does nothing if test fail'
call go#coverlay#Coverlay(0)
Expect len(go#coverlay#matches()) == 0
end
end

describe 'go#coverlay#findbufnr'
before
new
let g:curdir = expand('<sfile>:p:h') . '/'
let g:srcpath = 't/fixtures/src/'
let g:sample = 'pkg1/sample.go'
let g:sample2 = 'pkg2/sample.go'
let g:sampleabs = g:curdir . g:srcpath . 'pkg1/sample.go'
let g:sampleabs2 = g:curdir . g:srcpath . 'pkg2/sample.go'
let g:go_gopath = g:curdir . 't/fixtures'
execute "badd " . g:srcpath . g:sample
execute "badd " . g:srcpath . g:sample2
end
after
execute "bdelete " . g:srcpath . g:sample2
execute "bdelete " . g:srcpath . g:sample
close!
end

it 'returns BUFNR if FILE is opened at BUFNR'
Expect go#coverlay#findbufnr('_' . g:sampleabs) == bufnr(g:sampleabs)
Expect go#coverlay#findbufnr(g:sample) == bufnr(g:sampleabs)

Expect go#coverlay#findbufnr('_' . g:sampleabs2) == bufnr(g:sampleabs2)
Expect go#coverlay#findbufnr(g:sample2) == bufnr(g:sampleabs2)
end

it 'returns -1 if FILE is not exists'
Expect go#coverlay#findbufnr('pkg1/NOTEXISTS.go') == -1
Expect go#coverlay#findbufnr('_' . g:curdir . g:srcpath . 'pkg1/NOTEXISTS.go') == -1
end
end

describe 'go#coverlay#isopenedon'
before
new
let g:curdir = expand('<sfile>:p:h') . '/'
let g:srcpath = 't/fixtures/src/'
let g:sample = 'pkg1/sample.go'
let g:sampleabs = g:curdir . g:srcpath . 'pkg1/sample.go'
let g:go_gopath = g:curdir . 't/fixtures'
execute "badd " . g:srcpath . g:sample
end
after
execute "bdelete " . g:srcpath . g:sample
close!
end

it 'returns 1 if FILE is opened at BUFNR'
Expect go#coverlay#isopenedon('_' . g:sampleabs, bufnr(g:sampleabs)) == 1
Expect go#coverlay#isopenedon(g:sample, bufnr(g:sampleabs)) == 1
end

it 'returns 0 if FILE is not opened at BUFNR'
Expect go#coverlay#isopenedon('_' . g:sampleabs, 42) == 0
Expect go#coverlay#isopenedon(g:sample, 42) == 0
end

it 'returns 0 if FILE is not exists'
Expect go#coverlay#isopenedon('_' . g:curdir . g:srcpath . 'pkg1/NOTEXISTS', bufnr(g:sampleabs)) == 0
Expect go#coverlay#isopenedon('pkg1/NOTEXISTS.go', bufnr(g:sampleabs)) == 0
end
end



describe 'go#coverlay#parsegocoverline'
it 'parses a go cover output line and returns as dict'
let d = {'file': 'f',"startline": "1", "startcol": "2", "endline": "3", "endcol": "4", "numstmt": "5", "cnt": "6"}
" file:startline.col,endline.col numstmt count
Expect go#coverlay#parsegocoverline("f:1.2,3.4 5 6") == d
end
end

describe 'go#coverlay#genmatch'
it 'generate mark pattern from cover data'
let d = {'file': 'f',"startline": "1", "startcol": "2", "endline": "3", "endcol": "4", "numstmt": "5", "cnt": "6"}
Expect go#coverlay#genmatch(d) == {'group': 'covered', "pattern": '\%>1l\_^\s\+\%<3l\|\%1l\_^\s\+\|\%3l\_^\s\+\(\}$\)\@!', "priority": 6}
let d = {'file': 'f',"startline": "1", "startcol": "2", "endline": "3", "endcol": "4", "numstmt": "5", "cnt": "0"}
Expect go#coverlay#genmatch(d) == {'group': 'uncover', "pattern": '\%>1l\_^\s\+\%<3l\|\%1l\_^\s\+\|\%3l\_^\s\+\(\}$\)\@!', "priority": 5}
end
end
Loading