-
Notifications
You must be signed in to change notification settings - Fork 22
/
coverage.vim
319 lines (285 loc) · 9.71 KB
/
coverage.vim
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
" Copyright 2014 Google Inc. All rights reserved.
"
" Licensed under the Apache License, Version 2.0 (the "License");
" you may not use this file except in compliance with the License.
" You may obtain a copy of the License at
"
" http://www.apache.org/licenses/LICENSE-2.0
"
" Unless required by applicable law or agreed to in writing, software
" distributed under the License is distributed on an "AS IS" BASIS,
" WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
" See the License for the specific language governing permissions and
" limitations under the License.
"{{{ Init
let s:plugin = maktaba#plugin#Get('coverage')
let s:registry = s:plugin.GetExtensionRegistry()
if !exists('s:visible')
" Buffer paths for which the coverage is visible.
let s:visible = {}
endif
if !exists('s:cache')
" Cache of retrieved coverage data per path.
let s:cache = {}
endif
if !exists('s:coverage_states')
let s:coverage_states = ['covered', 'uncovered', 'partial']
endif
"}}}
"{{{ coverage utility functions
""
" Places the signs at given {lines} in given {state}.
function! s:ColorSigns(lines, state) abort
for l:num in a:lines
execute ':sign place ' . l:num . ' line=' . l:num .
\ ' name=sign_' . a:state . ' file=' . expand('%')
endfor
endfunction
""
" Defines highlighting rules for coverage colors and defines text signs for each
" coverage state, as defined via plugin flags. See |coverage-config|.
function! s:DefineHighlighting() abort
if !hlexists('coverage_covered')
for l:state in s:coverage_states
execute 'highlight coverage_' . l:state .
\ ' ctermbg=' . s:plugin.Flag(l:state . '_ctermbg') .
\ ' ctermfg=' . s:plugin.Flag(l:state . '_ctermfg') .
\ ' guibg=' . s:plugin.Flag(l:state . '_guibg') .
\ ' guifg=' . s:plugin.Flag(l:state . '_guifg')
execute 'sign define sign_' . l:state . ' text=' .
\ s:plugin.Flag(l:state . '_text') . ' texthl=coverage_' . l:state
endfor
endif
endfunction
""
" Renders the coverage for the required {filename}. Coverage needs to be in
" cache. This function does not get the coverage itself, only displays it.
" If [show_stats] is set, the coverage stats are shown, e.g. Coverage 70%(7/10).
" @default show_stats=1
function! s:RenderFromCache(filename, ...) abort
let l:show_stats = maktaba#ensure#IsBool(get(a:, 1, 1))
call s:DefineHighlighting()
if (has_key(s:cache, a:filename))
let l:data = s:cache[a:filename]
for l:state in s:coverage_states
call s:ColorSigns(l:data[l:state], l:state)
endfor
if l:show_stats
echomsg coverage#GetFormattedStats(a:filename)
endif
let s:visible[expand('%:p')] = 1
endif
endfunction
""
" Hides coverage layer.
function! s:CoverageHide() abort
if has_key(s:visible, expand('%:p'))
execute 'sign unplace * file=' . expand('%:p')
unlet s:visible[expand('%:p')]
endif
endfunction
""
" Toggles coverage layer.
function! s:CoverageToggle() abort
if has_key(s:visible, expand('%:p'))
call s:CoverageHide()
else
call s:CoverageShow()
endif
endfunction
""
" Shows coverage layer. If [explicit_provider] is set, it will be used for
" fetching the coverage data.
function! s:CoverageShow(...) abort
let l:filename = expand('%:p')
if !has_key(s:visible, l:filename)
if (has_key(s:cache, l:filename))
call s:RenderFromCache(l:filename)
else
if a:0 > 0
call coverage#ShowCoverage(maktaba#ensure#IsString(a:1))
else
call coverage#ShowCoverage()
endif
endif
endif
endfunction
""
" Shows coverage in vimdiff with the version coverage was known for.
function! s:CoverageShowDiff() abort
let l:filename = expand('%:p')
if has_key(s:cache, l:filename)
if has_key(s:cache, l:filename)
let l:data = s:cache[l:filename]
if has_key(l:data, 'diff_path')
" Current file has changed, so split into diff mode with the file at the
" point where the coverage is known, and render it there, in the split.
execute 'vertical' 'diffsplit' l:data.diff_path
call s:RenderFromCache(l:filename)
else
call maktaba#error#Warn('There is no diff.')
endif
endif
endif
endfunction
""
" Calculates coverage stats from @dict(s:cache), and returns the stats for the
" requested {filename}. Does not get the coverage stats.
function! coverage#GetFormattedStats(filename) abort
if has_key(s:cache, a:filename)
let l:data = s:cache[a:filename]
let l:stats = {'total': 0}
for l:state in s:coverage_states
let l:stats[l:state] = len(l:data[l:state])
let l:stats['total'] += len(l:data[l:state])
endfor
let l:percentage = 100.0 * l:stats.covered / l:stats.total
return printf('Coverage is %.2f%% (%d/%d lines).',
\ l:percentage, l:stats.covered, l:stats.total)
endif
endfunction
""
" @public
" Returns a coverage report compatible with |coverage|, for {covered},
" {uncovered} and {partial} lines. Optional param [extra_dict] can be passed in,
" which will be merged with the result.
function! coverage#CreateReport(covered, uncovered, partial, ...) abort
let l:extra_dict = {}
if a:0 > 0
let l:extra_dict = maktaba#ensure#IsDict(a:1)
endif
return extend(l:extra_dict,
\ {'covered': maktaba#ensure#IsList(a:covered),
\ 'uncovered': maktaba#ensure#IsList(a:uncovered),
\ 'partial': maktaba#ensure#IsList(a:partial)})
endfunction
""
" Renders coverage for the current file. A name of a registered [provider] can
" be passed as a parameter. If left blank, buffer b:coverage_provider variable
" will be first checked, and if it is not set, then the first registered
" provider will be used. If the provider defines a
" @function(provider#GetCoverageAsync), it is preferred. The callback must have
" function(coverage_data) prototype. Coverage data format is as described in
" the general help for this plugin.
" @default provider=b:coverage_provider or first registered provider
" @throws NotFound if requested provider is not found or non are registered, or
" the is not available for the current file.
function! coverage#ShowCoverage(...) abort
let l:filename = expand('%:p')
let l:providers = s:registry.GetExtensions()
if a:0 >= 1
let l:explicit_name = a:1
elseif !empty(get(b:, 'coverage_provider'))
let l:explicit_name = b:coverage_provider
elseif len(l:providers) > 0
for l:provider in l:providers
if l:provider.IsAvailable(l:filename)
let l:default_provider = l:provider
break
endif
endfor
if !exists('l:default_provider')
throw maktaba#error#NotFound('No available coverage providers.')
endif
let l:selected_provider = l:default_provider
else
throw maktaba#error#NotFound('No registered coverage providers.')
endif
if exists('l:explicit_name')
for l:provider in l:providers
if l:provider.name ==# l:explicit_name
let l:explicit_provider = l:provider
break
endif
endfor
if !exists('l:explicit_provider')
throw maktaba#error#NotFound('Coverage provider %s not found.',
\ l:explicit_name)
endif
let l:selected_provider = l:explicit_provider
endif
if !l:selected_provider.IsAvailable(l:filename)
throw maktaba#error#NotFound('Provider %s is not available for file %s',
\ l:explicit_name, l:filename)
endif
let l:callback = maktaba#function#Create('coverage#CacheAndShow',
\ [l:filename])
if has_key(l:selected_provider, 'GetCoverageAsync')
call l:selected_provider.GetCoverageAsync(l:filename, l:callback)
else
call maktaba#function#Apply(l:callback,
\ l:selected_provider.GetCoverage(l:filename))
endif
endfunction
""
" Caches the {coverage} for the {filename} and renders it. This can be used as a
" callback entry for asynchronous calls. {coverage} format is as described in
" the general help for this plugin.
function! coverage#CacheAndShow(filename, coverage) abort
if !maktaba#value#IsDict(a:coverage)
return
endif
let s:cache[a:filename] = a:coverage
if !has_key(a:coverage, 'diff_path')
call s:RenderFromCache(a:filename)
endif
endfunction
"}}}
"{{{ Misc
function! coverage#Toggle() abort
call s:CoverageToggle()
endfunction
function! coverage#Show(...) abort
try
if a:0 > 0
call s:CoverageShow(a:1)
else
call s:CoverageShow()
endif
catch /ERROR.*/
call maktaba#error#Shout('Error rendering coverage: %s', v:exception)
endtry
endfunction
function! coverage#ShowDiff() abort
try
call s:CoverageShowDiff()
catch /ERROR.*/
call maktaba#error#Shout('Error rendering coverage: %s', v:exception)
endtry
endfunction
function! coverage#Hide() abort
try
if has_key(s:visible, expand('%:p'))
call s:CoverageHide()
endif
catch /ERROR.*/
call maktaba#error#Shout('Error rendering coverage: %s', v:exception)
endtry
endfunction
""
" @private
" Completions for the available providers. Returns a list of providers that
" start with {arg}.
function! coverage#CompletionList(arg, line, pos) abort
let l:providers = []
for l:extension in s:registry.GetExtensions()
call add(l:providers, l:extension.name)
endfor
return filter(l:providers, "maktaba#string#StartsWith(v:val, a:arg)")
endfunction
""
" Makes sure the coverage {provider} is a valid provider for the plugin.
" @throws BadValue if the provider is not valid.
function! coverage#EnsureProvider(provider) abort
let l:required_fields = ['name', 'IsAvailable', 'GetCoverage']
" Throw BadValue if any required fields are missing.
let l:missing_fields =
\ filter(copy(l:required_fields), '!has_key(a:provider, v:val)')
if !empty(l:missing_fields)
throw maktaba#error#BadValue(
\ 'Provider is missing fields: %s. Got: %s',
\ join(l:missing_fields, ', '),
\ string(a:provider))
endif
endfunction
"}}}