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