1 // Coverage.py HTML report browser code. |
1 // Coverage.py HTML report browser code. |
|
2 /*jslint browser: true, sloppy: true, vars: true, plusplus: true, maxerr: 50, indent: 4 */ |
|
3 /*global coverage: true, document, window, $ */ |
|
4 |
|
5 coverage = {}; |
|
6 |
|
7 // Find all the elements with shortkey_* class, and use them to assign a shotrtcut key. |
|
8 coverage.assign_shortkeys = function () { |
|
9 $("*[class*='shortkey_']").each(function (i, e) { |
|
10 $.each($(e).attr("class").split(" "), function (i, c) { |
|
11 if (/^shortkey_/.test(c)) { |
|
12 $(document).bind('keydown', c.substr(9), function () { |
|
13 $(e).click(); |
|
14 }); |
|
15 } |
|
16 }); |
|
17 }); |
|
18 }; |
|
19 |
|
20 // Create the events for the help panel. |
|
21 coverage.wire_up_help_panel = function () { |
|
22 $("#keyboard_icon").click(function () { |
|
23 // Show the help panel, and position it so the keyboard icon in the |
|
24 // panel is in the same place as the keyboard icon in the header. |
|
25 $(".help_panel").show(); |
|
26 var koff = $("#keyboard_icon").offset(); |
|
27 var poff = $("#panel_icon").position(); |
|
28 $(".help_panel").offset({ |
|
29 top: koff.top-poff.top, |
|
30 left: koff.left-poff.left |
|
31 }); |
|
32 }); |
|
33 $("#panel_icon").click(function () { |
|
34 $(".help_panel").hide(); |
|
35 }); |
|
36 }; |
2 |
37 |
3 // Loaded on index.html |
38 // Loaded on index.html |
4 function index_page_ready($) { |
39 coverage.index_ready = function ($) { |
5 // Look for a cookie containing previous sort settings: |
40 // Look for a cookie containing previous sort settings: |
6 sort_list = []; |
41 var sort_list = []; |
7 cookie_name = "COVERAGE_INDEX_SORT"; |
42 var cookie_name = "COVERAGE_INDEX_SORT"; |
|
43 var i; |
8 |
44 |
9 // This almost makes it worth installing the jQuery cookie plugin: |
45 // This almost makes it worth installing the jQuery cookie plugin: |
10 if (document.cookie.indexOf(cookie_name) > -1) { |
46 if (document.cookie.indexOf(cookie_name) > -1) { |
11 cookies = document.cookie.split(";"); |
47 var cookies = document.cookie.split(";"); |
12 for (var i=0; i < cookies.length; i++) { |
48 for (i = 0; i < cookies.length; i++) { |
13 parts = cookies[i].split("=") |
49 var parts = cookies[i].split("="); |
14 |
50 |
15 if ($.trim(parts[0]) == cookie_name && parts[1]) { |
51 if ($.trim(parts[0]) === cookie_name && parts[1]) { |
16 sort_list = eval("[[" + parts[1] + "]]"); |
52 sort_list = eval("[[" + parts[1] + "]]"); |
17 break; |
53 break; |
18 } |
54 } |
19 } |
55 } |
20 } |
56 } |
23 // the sort order: |
59 // the sort order: |
24 $.tablesorter.addWidget({ |
60 $.tablesorter.addWidget({ |
25 id: "persistentSort", |
61 id: "persistentSort", |
26 |
62 |
27 // Format is called by the widget before displaying: |
63 // Format is called by the widget before displaying: |
28 format: function(table) { |
64 format: function (table) { |
29 if (table.config.sortList.length == 0 && sort_list.length > 0) { |
65 if (table.config.sortList.length === 0 && sort_list.length > 0) { |
30 // This table hasn't been sorted before - we'll use |
66 // This table hasn't been sorted before - we'll use |
31 // our stored settings: |
67 // our stored settings: |
32 jQuery(table).trigger('sorton', [sort_list]); |
68 $(table).trigger('sorton', [sort_list]); |
33 } |
69 } |
34 else { |
70 else { |
35 // This is not the first load - something has |
71 // This is not the first load - something has |
36 // already defined sorting so we'll just update |
72 // already defined sorting so we'll just update |
37 // our stored value to match: |
73 // our stored value to match: |
38 sort_list = table.config.sortList; |
74 sort_list = table.config.sortList; |
39 } |
75 } |
40 } |
76 } |
41 }); |
77 }); |
42 |
78 |
43 // Configure our tablesorter to handle the variable number of |
79 // Configure our tablesorter to handle the variable number of |
44 // columns produced depending on report options: |
80 // columns produced depending on report options: |
45 var headers = {}; |
81 var headers = []; |
46 var col_count = jQuery("table.index > thead > tr > th").length; |
82 var col_count = $("table.index > thead > tr > th").length; |
47 |
83 |
48 headers[0] = { sorter: 'text' }; |
84 headers[0] = { sorter: 'text' }; |
49 for (var i = 1; i < col_count-1; i++) { |
85 for (i = 1; i < col_count-1; i++) { |
50 headers[i] = { sorter: 'digit' }; |
86 headers[i] = { sorter: 'digit' }; |
51 } |
87 } |
52 headers[col_count-1] = { sorter: 'percent' }; |
88 headers[col_count-1] = { sorter: 'percent' }; |
53 |
89 |
54 // Enable the table sorter: |
90 // Enable the table sorter: |
55 $("table.index").tablesorter({ |
91 $("table.index").tablesorter({ |
56 widgets: ['persistentSort'], |
92 widgets: ['persistentSort'], |
57 headers: headers |
93 headers: headers |
58 }); |
94 }); |
59 |
95 |
|
96 coverage.assign_shortkeys(); |
|
97 coverage.wire_up_help_panel(); |
|
98 |
60 // Watch for page unload events so we can save the final sort settings: |
99 // Watch for page unload events so we can save the final sort settings: |
61 $(window).unload(function() { |
100 $(window).unload(function () { |
62 document.cookie = cookie_name + "=" + sort_list.toString() + "; path=/" |
101 document.cookie = cookie_name + "=" + sort_list.toString() + "; path=/"; |
63 }); |
102 }); |
64 } |
103 }; |
|
104 |
|
105 // -- pyfile stuff -- |
|
106 |
|
107 coverage.pyfile_ready = function ($) { |
|
108 // If we're directed to a particular line number, highlight the line. |
|
109 var frag = location.hash; |
|
110 if (frag.length > 2 && frag[1] === 'n') { |
|
111 $(frag).addClass('highlight'); |
|
112 coverage.set_sel(parseInt(frag.substr(2), 10)); |
|
113 } |
|
114 else { |
|
115 coverage.set_sel(0); |
|
116 } |
|
117 |
|
118 $(document) |
|
119 .bind('keydown', 'j', coverage.to_next_chunk_nicely) |
|
120 .bind('keydown', 'k', coverage.to_prev_chunk_nicely) |
|
121 .bind('keydown', '0', coverage.to_top) |
|
122 .bind('keydown', '1', coverage.to_first_chunk) |
|
123 ; |
|
124 |
|
125 $(".button_toggle_run").click(function (evt) {coverage.toggle_lines(evt.target, "run");}); |
|
126 $(".button_toggle_exc").click(function (evt) {coverage.toggle_lines(evt.target, "exc");}); |
|
127 $(".button_toggle_mis").click(function (evt) {coverage.toggle_lines(evt.target, "mis");}); |
|
128 $(".button_toggle_par").click(function (evt) {coverage.toggle_lines(evt.target, "par");}); |
|
129 |
|
130 coverage.assign_shortkeys(); |
|
131 coverage.wire_up_help_panel(); |
|
132 }; |
|
133 |
|
134 coverage.toggle_lines = function (btn, cls) { |
|
135 btn = $(btn); |
|
136 var hide = "hide_"+cls; |
|
137 if (btn.hasClass(hide)) { |
|
138 $("#source ."+cls).removeClass(hide); |
|
139 btn.removeClass(hide); |
|
140 } |
|
141 else { |
|
142 $("#source ."+cls).addClass(hide); |
|
143 btn.addClass(hide); |
|
144 } |
|
145 }; |
|
146 |
|
147 // Return the nth line div. |
|
148 coverage.line_elt = function (n) { |
|
149 return $("#t" + n); |
|
150 }; |
|
151 |
|
152 // Return the nth line number div. |
|
153 coverage.num_elt = function (n) { |
|
154 return $("#n" + n); |
|
155 }; |
|
156 |
|
157 // Return the container of all the code. |
|
158 coverage.code_container = function () { |
|
159 return $(".linenos"); |
|
160 }; |
|
161 |
|
162 // Set the selection. b and e are line numbers. |
|
163 coverage.set_sel = function (b, e) { |
|
164 // The first line selected. |
|
165 coverage.sel_begin = b; |
|
166 // The next line not selected. |
|
167 coverage.sel_end = (e === undefined) ? b+1 : e; |
|
168 }; |
|
169 |
|
170 coverage.to_top = function () { |
|
171 coverage.set_sel(0, 1); |
|
172 coverage.scroll_window(0); |
|
173 }; |
|
174 |
|
175 coverage.to_first_chunk = function () { |
|
176 coverage.set_sel(0, 1); |
|
177 coverage.to_next_chunk(); |
|
178 }; |
|
179 |
|
180 coverage.is_transparent = function (color) { |
|
181 // Different browsers return different colors for "none". |
|
182 return color === "transparent" || color === "rgba(0, 0, 0, 0)"; |
|
183 }; |
|
184 |
|
185 coverage.to_next_chunk = function () { |
|
186 var c = coverage; |
|
187 |
|
188 // Find the start of the next colored chunk. |
|
189 var probe = c.sel_end; |
|
190 while (true) { |
|
191 var probe_line = c.line_elt(probe); |
|
192 if (probe_line.length === 0) { |
|
193 return; |
|
194 } |
|
195 var color = probe_line.css("background-color"); |
|
196 if (!c.is_transparent(color)) { |
|
197 break; |
|
198 } |
|
199 probe++; |
|
200 } |
|
201 |
|
202 // There's a next chunk, `probe` points to it. |
|
203 var begin = probe; |
|
204 |
|
205 // Find the end of this chunk. |
|
206 var next_color = color; |
|
207 while (next_color === color) { |
|
208 probe++; |
|
209 probe_line = c.line_elt(probe); |
|
210 next_color = probe_line.css("background-color"); |
|
211 } |
|
212 c.set_sel(begin, probe); |
|
213 c.show_selection(); |
|
214 }; |
|
215 |
|
216 coverage.to_prev_chunk = function () { |
|
217 var c = coverage; |
|
218 |
|
219 // Find the end of the prev colored chunk. |
|
220 var probe = c.sel_begin-1; |
|
221 var probe_line = c.line_elt(probe); |
|
222 if (probe_line.length === 0) { |
|
223 return; |
|
224 } |
|
225 var color = probe_line.css("background-color"); |
|
226 while (probe > 0 && c.is_transparent(color)) { |
|
227 probe--; |
|
228 probe_line = c.line_elt(probe); |
|
229 if (probe_line.length === 0) { |
|
230 return; |
|
231 } |
|
232 color = probe_line.css("background-color"); |
|
233 } |
|
234 |
|
235 // There's a prev chunk, `probe` points to its last line. |
|
236 var end = probe+1; |
|
237 |
|
238 // Find the beginning of this chunk. |
|
239 var prev_color = color; |
|
240 while (prev_color === color) { |
|
241 probe--; |
|
242 probe_line = c.line_elt(probe); |
|
243 prev_color = probe_line.css("background-color"); |
|
244 } |
|
245 c.set_sel(probe+1, end); |
|
246 c.show_selection(); |
|
247 }; |
|
248 |
|
249 // Return the line number of the line nearest pixel position pos |
|
250 coverage.line_at_pos = function (pos) { |
|
251 var l1 = coverage.line_elt(1), |
|
252 l2 = coverage.line_elt(2), |
|
253 result; |
|
254 if (l1.length && l2.length) { |
|
255 var l1_top = l1.offset().top, |
|
256 line_height = l2.offset().top - l1_top, |
|
257 nlines = (pos - l1_top) / line_height; |
|
258 if (nlines < 1) { |
|
259 result = 1; |
|
260 } |
|
261 else { |
|
262 result = Math.ceil(nlines); |
|
263 } |
|
264 } |
|
265 else { |
|
266 result = 1; |
|
267 } |
|
268 return result; |
|
269 }; |
|
270 |
|
271 // Returns 0, 1, or 2: how many of the two ends of the selection are on |
|
272 // the screen right now? |
|
273 coverage.selection_ends_on_screen = function () { |
|
274 if (coverage.sel_begin === 0) { |
|
275 return 0; |
|
276 } |
|
277 |
|
278 var top = coverage.line_elt(coverage.sel_begin); |
|
279 var next = coverage.line_elt(coverage.sel_end-1); |
|
280 |
|
281 return ( |
|
282 (top.isOnScreen() ? 1 : 0) + |
|
283 (next.isOnScreen() ? 1 : 0) |
|
284 ); |
|
285 }; |
|
286 |
|
287 coverage.to_next_chunk_nicely = function () { |
|
288 coverage.finish_scrolling(); |
|
289 if (coverage.selection_ends_on_screen() === 0) { |
|
290 // The selection is entirely off the screen: select the top line on |
|
291 // the screen. |
|
292 var win = $(window); |
|
293 coverage.select_line_or_chunk(coverage.line_at_pos(win.scrollTop())); |
|
294 } |
|
295 coverage.to_next_chunk(); |
|
296 }; |
|
297 |
|
298 coverage.to_prev_chunk_nicely = function () { |
|
299 coverage.finish_scrolling(); |
|
300 if (coverage.selection_ends_on_screen() === 0) { |
|
301 var win = $(window); |
|
302 coverage.select_line_or_chunk(coverage.line_at_pos(win.scrollTop() + win.height())); |
|
303 } |
|
304 coverage.to_prev_chunk(); |
|
305 }; |
|
306 |
|
307 // Select line number lineno, or if it is in a colored chunk, select the |
|
308 // entire chunk |
|
309 coverage.select_line_or_chunk = function (lineno) { |
|
310 var c = coverage; |
|
311 var probe_line = c.line_elt(lineno); |
|
312 if (probe_line.length === 0) { |
|
313 return; |
|
314 } |
|
315 var the_color = probe_line.css("background-color"); |
|
316 if (!c.is_transparent(the_color)) { |
|
317 // The line is in a highlighted chunk. |
|
318 // Search backward for the first line. |
|
319 var probe = lineno; |
|
320 var color = the_color; |
|
321 while (probe > 0 && color === the_color) { |
|
322 probe--; |
|
323 probe_line = c.line_elt(probe); |
|
324 if (probe_line.length === 0) { |
|
325 break; |
|
326 } |
|
327 color = probe_line.css("background-color"); |
|
328 } |
|
329 var begin = probe + 1; |
|
330 |
|
331 // Search forward for the last line. |
|
332 probe = lineno; |
|
333 color = the_color; |
|
334 while (color === the_color) { |
|
335 probe++; |
|
336 probe_line = c.line_elt(probe); |
|
337 color = probe_line.css("background-color"); |
|
338 } |
|
339 |
|
340 coverage.set_sel(begin, probe); |
|
341 } |
|
342 else { |
|
343 coverage.set_sel(lineno); |
|
344 } |
|
345 }; |
|
346 |
|
347 coverage.show_selection = function () { |
|
348 var c = coverage; |
|
349 |
|
350 // Highlight the lines in the chunk |
|
351 c.code_container().find(".highlight").removeClass("highlight"); |
|
352 for (var probe = c.sel_begin; probe > 0 && probe < c.sel_end; probe++) { |
|
353 c.num_elt(probe).addClass("highlight"); |
|
354 } |
|
355 |
|
356 c.scroll_to_selection(); |
|
357 }; |
|
358 |
|
359 coverage.scroll_to_selection = function () { |
|
360 // Scroll the page if the chunk isn't fully visible. |
|
361 if (coverage.selection_ends_on_screen() < 2) { |
|
362 // Need to move the page. The html,body trick makes it scroll in all |
|
363 // browsers, got it from http://stackoverflow.com/questions/3042651 |
|
364 var top = coverage.line_elt(coverage.sel_begin); |
|
365 var top_pos = parseInt(top.offset().top, 10); |
|
366 coverage.scroll_window(top_pos - 30); |
|
367 } |
|
368 }; |
|
369 |
|
370 coverage.scroll_window = function (to_pos) { |
|
371 $("html,body").animate({scrollTop: to_pos}, 200); |
|
372 }; |
|
373 |
|
374 coverage.finish_scrolling = function () { |
|
375 $("html,body").stop(true, true); |
|
376 }; |