diff --git a/coverage/html.py b/coverage/html.py
index d1a90ecd6..570760604 100644
--- a/coverage/html.py
+++ b/coverage/html.py
@@ -7,10 +7,12 @@
import collections
import datetime
+import functools
import json
import os
import re
import shutil
+import string # pylint: disable=deprecated-module
from dataclasses import dataclass
from typing import Any, Dict, Iterable, List, Optional, Tuple, TYPE_CHECKING, cast
@@ -187,6 +189,21 @@ def __init__(self, fr: FileReporter, analysis: Analysis) -> None:
self.html_filename = self.rootname + ".html"
+HTML_SAFE = string.ascii_letters + string.digits + "!#$%'()*+,-./:;=?@[]^_`{|}~"
+
+@functools.lru_cache(maxsize=None)
+def encode_int(n: int) -> str:
+ """Create a short HTML-safe string from an integer, using HTML_SAFE."""
+ if n == 0:
+ return HTML_SAFE[0]
+
+ r = []
+ while n:
+ n, t = divmod(n, len(HTML_SAFE))
+ r.append(HTML_SAFE[t])
+ return "".join(r)
+
+
class HtmlReporter:
"""HTML reporting."""
@@ -373,7 +390,10 @@ def write_html_file(self, ftr: FileToReport, prev_html: str, next_html: str) ->
contexts = collections.Counter(c for cline in file_data.lines for c in cline.contexts)
context_codes = {y: i for (i, y) in enumerate(x[0] for x in contexts.most_common())}
if context_codes:
- contexts_json = json.dumps({v: k for (k, v) in context_codes.items()}, indent=2)
+ contexts_json = json.dumps(
+ {encode_int(v): k for (k, v) in context_codes.items()},
+ indent=2,
+ )
else:
contexts_json = None
@@ -387,9 +407,17 @@ def write_html_file(self, ftr: FileToReport, prev_html: str, next_html: str) ->
tok_html = escape(tok_text) or " "
html_parts.append(f'{tok_html}')
ldata.html = "".join(html_parts)
- ldata.context_str = ",".join(
- str(context_codes[c_context]) for c_context in ldata.context_list
- )
+ if ldata.context_list:
+ encoded_contexts = [
+ encode_int(context_codes[c_context]) for c_context in ldata.context_list
+ ]
+ code_width = max(len(ec) for ec in encoded_contexts)
+ ldata.context_str = (
+ str(code_width)
+ + "".join(ec.ljust(code_width) for ec in encoded_contexts)
+ )
+ else:
+ ldata.context_str = ""
if ldata.short_annotations:
# 202F is NARROW NO-BREAK SPACE.
diff --git a/coverage/htmlfiles/coverage_html.js b/coverage/htmlfiles/coverage_html.js
index 691a56c38..4c321182c 100644
--- a/coverage/htmlfiles/coverage_html.js
+++ b/coverage/htmlfiles/coverage_html.js
@@ -212,11 +212,6 @@ coverage.index_ready = function () {
coverage.LINE_FILTERS_STORAGE = "COVERAGE_LINE_FILTERS";
coverage.pyfile_ready = function () {
- cboxes = document.querySelectorAll('[id^=ctxs]')
- cboxes.forEach(function(cbox) {
- cbox.addEventListener("click", coverage.showContexts)
- });
-
// If we're directed to a particular line number, highlight the line.
var frag = location.hash;
if (frag.length > 2 && frag[1] === "t") {
@@ -262,6 +257,10 @@ coverage.pyfile_ready = function () {
coverage.init_scroll_markers();
coverage.wire_up_sticky_header();
+ document.querySelectorAll("[id^=ctxs]").forEach(
+ cbox => cbox.addEventListener("click", coverage.expand_contexts)
+ );
+
// Rebuild scroll markers when the window height changes.
window.addEventListener("resize", coverage.build_scroll_markers);
};
@@ -600,17 +599,19 @@ coverage.wire_up_sticky_header = function () {
updateHeader();
};
-coverage.showContexts = function (e) {
- span = e.target.nextElementSibling.nextElementSibling;
- span_text = span.textContent;
-
- if (/^[0-9,]+$/.test(span_text)) {
- span.textContent = "";
- span_text.split(",").forEach(function(s) {
- ctx = contexts[s];
- span.appendChild(document.createTextNode(ctx));
- span.appendChild(document.createElement("br"));
- })
+coverage.expand_contexts = function (e) {
+ var ctxs = e.target.parentNode.querySelector(".ctxs");
+
+ if (!ctxs.classList.contains("expanded")) {
+ var ctxs_text = ctxs.textContent;
+ var width = Number(ctxs_text[0]);
+ ctxs.textContent = "";
+ for (var i = 1; i < ctxs_text.length; i += width) {
+ key = ctxs_text.substring(i, i + width).trim();
+ ctxs.appendChild(document.createTextNode(contexts[key]));
+ ctxs.appendChild(document.createElement("br"));
+ }
+ ctxs.classList.add("expanded");
}
};
@@ -620,5 +621,4 @@ document.addEventListener("DOMContentLoaded", () => {
} else {
coverage.pyfile_ready();
}
-
});
diff --git a/tests/gold/html/contexts/two_tests_py.html b/tests/gold/html/contexts/two_tests_py.html
index cbb6e5739..aadc79767 100644
--- a/tests/gold/html/contexts/two_tests_py.html
+++ b/tests/gold/html/contexts/two_tests_py.html
@@ -7,9 +7,9 @@
@@ -72,7 +72,7 @@
» next
coverage.py v7.2.3a0.dev1,
- created at 2023-03-22 13:16 -0400
+ created at 2023-04-01 08:30 -0400