blob: f6f5de20771ce0e4bdc1b6c826e25c7c47808599 [file] [log] [blame]
// Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
// For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt
// Coverage.py HTML report browser code.
/*jslint browser: true, sloppy: true, vars: true, plusplus: true, maxerr: 50, indent: 4 */
/*global coverage: true, document, window, $ */
coverage = {};
// Find all the elements with shortkey_* class, and use them to assign a shortcut key.
coverage.assign_shortkeys = function () {
$("*[class*='shortkey_']").each(function (i, e) {
$.each($(e).attr("class").split(" "), function (i, c) {
if (/^shortkey_/.test(c)) {
$(document).bind('keydown', c.substr(9), function () {
$(e).click();
});
}
});
});
};
// Create the events for the help panel.
coverage.wire_up_help_panel = function () {
$("#keyboard_icon").click(function () {
// Show the help panel, and position it so the keyboard icon in the
// panel is in the same place as the keyboard icon in the header.
$(".help_panel").show();
var koff = $("#keyboard_icon").offset();
var poff = $("#panel_icon").position();
$(".help_panel").offset({
top: koff.top-poff.top,
left: koff.left-poff.left
});
});
$("#panel_icon").click(function () {
$(".help_panel").hide();
});
};
// Create the events for the filter box.
coverage.wire_up_filter = function () {
// Cache elements.
var table = $("table.index");
var table_rows = table.find("tbody tr");
var table_row_names = table_rows.find("td.name a");
var no_rows = $("#no_rows");
// Create a duplicate table footer that we can modify with dynamic summed values.
var table_footer = $("table.index tfoot tr");
var table_dynamic_footer = table_footer.clone();
table_dynamic_footer.attr('class', 'total_dynamic hidden');
table_footer.after(table_dynamic_footer);
// Observe filter keyevents.
$("#filter").on("keyup change", $.debounce(150, function (event) {
var filter_value = $(this).val();
if (filter_value === "") {
// Filter box is empty, remove all filtering.
table_rows.removeClass("hidden");
// Show standard footer, hide dynamic footer.
table_footer.removeClass("hidden");
table_dynamic_footer.addClass("hidden");
// Hide placeholder, show table.
if (no_rows.length > 0) {
no_rows.hide();
}
table.show();
}
else {
// Filter table items by value.
var hidden = 0;
var shown = 0;
// Hide / show elements.
$.each(table_row_names, function () {
var element = $(this).parents("tr");
if ($(this).text().indexOf(filter_value) === -1) {
// hide
element.addClass("hidden");
hidden++;
}
else {
// show
element.removeClass("hidden");
shown++;
}
});
// Show placeholder if no rows will be displayed.
if (no_rows.length > 0) {
if (shown === 0) {
// Show placeholder, hide table.
no_rows.show();
table.hide();
}
else {
// Hide placeholder, show table.
no_rows.hide();
table.show();
}
}
// Manage dynamic header:
if (hidden > 0) {
// Calculate new dynamic sum values based on visible rows.
for (var column = 2; column < 20; column++) {
// Calculate summed value.
var cells = table_rows.find('td:nth-child(' + column + ')');
if (!cells.length) {
// No more columns...!
break;
}
var sum = 0, numer = 0, denom = 0;
$.each(cells.filter(':visible'), function () {
var ratio = $(this).data("ratio");
if (ratio) {
var splitted = ratio.split(" ");
numer += parseInt(splitted[0], 10);
denom += parseInt(splitted[1], 10);
}
else {
sum += parseInt(this.innerHTML, 10);
}
});
// Get footer cell element.
var footer_cell = table_dynamic_footer.find('td:nth-child(' + column + ')');
// Set value into dynamic footer cell element.
if (cells[0].innerHTML.indexOf('%') > -1) {
// Percentage columns use the numerator and denominator,
// and adapt to the number of decimal places.
var match = /\.([0-9]+)/.exec(cells[0].innerHTML);
var places = 0;
if (match) {
places = match[1].length;
}
var pct = numer * 100 / denom;
footer_cell.text(pct.toFixed(places) + '%');
}
else {
footer_cell.text(sum);
}
}
// Hide standard footer, show dynamic footer.
table_footer.addClass("hidden");
table_dynamic_footer.removeClass("hidden");
}
else {
// Show standard footer, hide dynamic footer.
table_footer.removeClass("hidden");
table_dynamic_footer.addClass("hidden");
}
}
}));
// Trigger change event on setup, to force filter on page refresh
// (filter value may still be present).
$("#filter").trigger("change");
};
// Loaded on index.html
coverage.index_ready = function ($) {
// Look for a cookie containing previous sort settings:
var sort_list = [];
var cookie_name = "COVERAGE_INDEX_SORT";
var i;
// This almost makes it worth installing the jQuery cookie plugin:
if (document.cookie.indexOf(cookie_name) > -1) {
var cookies = document.cookie.split(";");
for (i = 0; i < cookies.length; i++) {
var parts = cookies[i].split("=");
if ($.trim(parts[0]) === cookie_name && parts[1]) {
sort_list = eval("[[" + parts[1] + "]]");
break;
}
}
}
// Create a new widget which exists only to save and restore
// the sort order:
$.tablesorter.addWidget({
id: "persistentSort",
// Format is called by the widget before displaying:
format: function (table) {
if (table.config.sortList.length === 0 && sort_list.length > 0) {
// This table hasn't been sorted before - we'll use
// our stored settings:
$(table).trigger('sorton', [sort_list]);
}
else {
// This is not the first load - something has
// already defined sorting so we'll just update
// our stored value to match:
sort_list = table.config.sortList;
}
}
});
// Configure our tablesorter to handle the variable number of
// columns produced depending on report options:
var headers = [];
var col_count = $("table.index > thead > tr > th").length;
headers[0] = { sorter: 'text' };
for (i = 1; i < col_count-1; i++) {
headers[i] = { sorter: 'digit' };
}
headers[col_count-1] = { sorter: 'percent' };
// Enable the table sorter:
$("table.index").tablesorter({
widgets: ['persistentSort'],
headers: headers
});
coverage.assign_shortkeys();
coverage.wire_up_help_panel();
coverage.wire_up_filter();
// Watch for page unload events so we can save the final sort settings:
$(window).unload(function () {
document.cookie = cookie_name + "=" + sort_list.toString() + "; path=/";
});
};
// -- pyfile stuff --
coverage.pyfile_ready = function ($) {
// If we're directed to a particular line number, highlight the line.
var frag = location.hash;
if (frag.length > 2 && frag[1] === 'n') {
$(frag).addClass('highlight');
coverage.set_sel(parseInt(frag.substr(2), 10));
}
else {
coverage.set_sel(0);
}
$(document)
.bind('keydown', 'j', coverage.to_next_chunk_nicely)
.bind('keydown', 'k', coverage.to_prev_chunk_nicely)
.bind('keydown', '0', coverage.to_top)
.bind('keydown', '1', coverage.to_first_chunk)
;
$(".button_toggle_run").click(function (evt) {coverage.toggle_lines(evt.target, "run");});
$(".button_toggle_exc").click(function (evt) {coverage.toggle_lines(evt.target, "exc");});
$(".button_toggle_mis").click(function (evt) {coverage.toggle_lines(evt.target, "mis");});
$(".button_toggle_par").click(function (evt) {coverage.toggle_lines(evt.target, "par");});
coverage.assign_shortkeys();
coverage.wire_up_help_panel();
coverage.init_scroll_markers();
// Rebuild scroll markers after window high changing
$(window).resize(coverage.resize_scroll_markers);
};
coverage.toggle_lines = function (btn, cls) {
btn = $(btn);
var hide = "hide_"+cls;
if (btn.hasClass(hide)) {
$("#source ."+cls).removeClass(hide);
btn.removeClass(hide);
}
else {
$("#source ."+cls).addClass(hide);
btn.addClass(hide);
}
};
// Return the nth line div.
coverage.line_elt = function (n) {
return $("#t" + n);
};
// Return the nth line number div.
coverage.num_elt = function (n) {
return $("#n" + n);
};
// Return the container of all the code.
coverage.code_container = function () {
return $(".linenos");
};
// Set the selection. b and e are line numbers.
coverage.set_sel = function (b, e) {
// The first line selected.
coverage.sel_begin = b;
// The next line not selected.
coverage.sel_end = (e === undefined) ? b+1 : e;
};
coverage.to_top = function () {
coverage.set_sel(0, 1);
coverage.scroll_window(0);
};
coverage.to_first_chunk = function () {
coverage.set_sel(0, 1);
coverage.to_next_chunk();
};
coverage.is_transparent = function (color) {
// Different browsers return different colors for "none".
return color === "transparent" || color === "rgba(0, 0, 0, 0)";
};
coverage.to_next_chunk = function () {
var c = coverage;
// Find the start of the next colored chunk.
var probe = c.sel_end;
var color, probe_line;
while (true) {
probe_line = c.line_elt(probe);
if (probe_line.length === 0) {
return;
}
color = probe_line.css("background-color");
if (!c.is_transparent(color)) {
break;
}
probe++;
}
// There's a next chunk, `probe` points to it.
var begin = probe;
// Find the end of this chunk.
var next_color = color;
while (next_color === color) {
probe++;
probe_line = c.line_elt(probe);
next_color = probe_line.css("background-color");
}
c.set_sel(begin, probe);
c.show_selection();
};
coverage.to_prev_chunk = function () {
var c = coverage;
// Find the end of the prev colored chunk.
var probe = c.sel_begin-1;
var probe_line = c.line_elt(probe);
if (probe_line.length === 0) {
return;
}
var color = probe_line.css("background-color");
while (probe > 0 && c.is_transparent(color)) {
probe--;
probe_line = c.line_elt(probe);
if (probe_line.length === 0) {
return;
}
color = probe_line.css("background-color");
}
// There's a prev chunk, `probe` points to its last line.
var end = probe+1;
// Find the beginning of this chunk.
var prev_color = color;
while (prev_color === color) {
probe--;
probe_line = c.line_elt(probe);
prev_color = probe_line.css("background-color");
}
c.set_sel(probe+1, end);
c.show_selection();
};
// Return the line number of the line nearest pixel position pos
coverage.line_at_pos = function (pos) {
var l1 = coverage.line_elt(1),
l2 = coverage.line_elt(2),
result;
if (l1.length && l2.length) {
var l1_top = l1.offset().top,
line_height = l2.offset().top - l1_top,
nlines = (pos - l1_top) / line_height;
if (nlines < 1) {
result = 1;
}
else {
result = Math.ceil(nlines);
}
}
else {
result = 1;
}
return result;
};
// Returns 0, 1, or 2: how many of the two ends of the selection are on
// the screen right now?
coverage.selection_ends_on_screen = function () {
if (coverage.sel_begin === 0) {
return 0;
}
var top = coverage.line_elt(coverage.sel_begin);
var next = coverage.line_elt(coverage.sel_end-1);
return (
(top.isOnScreen() ? 1 : 0) +
(next.isOnScreen() ? 1 : 0)
);
};
coverage.to_next_chunk_nicely = function () {
coverage.finish_scrolling();
if (coverage.selection_ends_on_screen() === 0) {
// The selection is entirely off the screen: select the top line on
// the screen.
var win = $(window);
coverage.select_line_or_chunk(coverage.line_at_pos(win.scrollTop()));
}
coverage.to_next_chunk();
};
coverage.to_prev_chunk_nicely = function () {
coverage.finish_scrolling();
if (coverage.selection_ends_on_screen() === 0) {
var win = $(window);
coverage.select_line_or_chunk(coverage.line_at_pos(win.scrollTop() + win.height()));
}
coverage.to_prev_chunk();
};
// Select line number lineno, or if it is in a colored chunk, select the
// entire chunk
coverage.select_line_or_chunk = function (lineno) {
var c = coverage;
var probe_line = c.line_elt(lineno);
if (probe_line.length === 0) {
return;
}
var the_color = probe_line.css("background-color");
if (!c.is_transparent(the_color)) {
// The line is in a highlighted chunk.
// Search backward for the first line.
var probe = lineno;
var color = the_color;
while (probe > 0 && color === the_color) {
probe--;
probe_line = c.line_elt(probe);
if (probe_line.length === 0) {
break;
}
color = probe_line.css("background-color");
}
var begin = probe + 1;
// Search forward for the last line.
probe = lineno;
color = the_color;
while (color === the_color) {
probe++;
probe_line = c.line_elt(probe);
color = probe_line.css("background-color");
}
coverage.set_sel(begin, probe);
}
else {
coverage.set_sel(lineno);
}
};
coverage.show_selection = function () {
var c = coverage;
// Highlight the lines in the chunk
c.code_container().find(".highlight").removeClass("highlight");
for (var probe = c.sel_begin; probe > 0 && probe < c.sel_end; probe++) {
c.num_elt(probe).addClass("highlight");
}
c.scroll_to_selection();
};
coverage.scroll_to_selection = function () {
// Scroll the page if the chunk isn't fully visible.
if (coverage.selection_ends_on_screen() < 2) {
// Need to move the page. The html,body trick makes it scroll in all
// browsers, got it from http://stackoverflow.com/questions/3042651
var top = coverage.line_elt(coverage.sel_begin);
var top_pos = parseInt(top.offset().top, 10);
coverage.scroll_window(top_pos - 30);
}
};
coverage.scroll_window = function (to_pos) {
$("html,body").animate({scrollTop: to_pos}, 200);
};
coverage.finish_scrolling = function () {
$("html,body").stop(true, true);
};
coverage.init_scroll_markers = function () {
var c = coverage;
// Init some variables
c.lines_len = $('td.text p').length;
c.body_h = $('body').height();
c.header_h = $('div#header').height();
c.missed_lines = $('td.text p.mis, td.text p.par');
// Build html
c.resize_scroll_markers();
};
coverage.resize_scroll_markers = function () {
var c = coverage,
min_line_height = 3,
max_line_height = 10,
visible_window_h = $(window).height();
$('#scroll_marker').remove();
// Don't build markers if the window has no scroll bar.
if (c.body_h <= visible_window_h) {
return;
}
$("body").append("<div id='scroll_marker'>&nbsp;</div>");
var scroll_marker = $('#scroll_marker'),
marker_scale = scroll_marker.height() / c.body_h,
line_height = scroll_marker.height() / c.lines_len;
// Line height must be between the extremes.
if (line_height > min_line_height) {
if (line_height > max_line_height) {
line_height = max_line_height;
}
}
else {
line_height = min_line_height;
}
var previous_line = -99,
last_mark,
last_top;
c.missed_lines.each(function () {
var line_top = Math.round($(this).offset().top * marker_scale),
id_name = $(this).attr('id'),
line_number = parseInt(id_name.substring(1, id_name.length));
if (line_number === previous_line + 1) {
// If this solid missed block just make previous mark higher.
last_mark.css({
'height': line_top + line_height - last_top
});
}
else {
// Add colored line in scroll_marker block.
scroll_marker.append('<div id="m' + line_number + '" class="marker"></div>');
last_mark = $('#m' + line_number);
last_mark.css({
'height': line_height,
'top': line_top
});
last_top = line_top;
}
previous_line = line_number;
});
};