diff --git a/coverage/config.py b/coverage/config.py
index 7c7dbe19f..bc0684f7f 100644
--- a/coverage/config.py
+++ b/coverage/config.py
@@ -238,6 +238,7 @@ def __init__(self) -> None:
self.html_skip_empty: Optional[bool] = None
self.html_title = "Coverage report"
self.show_contexts = False
+ self.theme = None
# Defaults for [xml]
self.xml_output = "coverage.xml"
@@ -411,6 +412,7 @@ def copy(self) -> CoverageConfig:
# [html]
("extra_css", "html:extra_css"),
+ ("theme", "html:theme"),
("html_dir", "html:directory"),
("html_skip_covered", "html:skip_covered", "boolean"),
("html_skip_empty", "html:skip_empty", "boolean"),
diff --git a/coverage/html.py b/coverage/html.py
index 7b827a794..cfce881c5 100644
--- a/coverage/html.py
+++ b/coverage/html.py
@@ -217,6 +217,10 @@ class HtmlReporter:
"favicon_32.png",
]
+ THEMES = {
+ "github_dark_dimmed": "github_dark_dimmed.css"
+ }
+
def __init__(self, cov: Coverage) -> None:
self.coverage = cov
self.config = self.coverage.config
@@ -239,6 +243,15 @@ def __init__(self, cov: Coverage) -> None:
else:
self.extra_css = None
+ self.theme = None
+
+ if self.config.theme in self.THEMES:
+ self.theme = self.THEMES[self.config.theme]
+
+ elif self.config.theme:
+ self.coverage._message(f"Unknown theme '{self.config.theme}'")
+ self.coverage._message(f"Available themes: {list(self.THEMES.keys())}")
+
self.data = self.coverage.get_data()
self.has_arcs = self.data.has_arcs()
@@ -263,6 +276,7 @@ def __init__(self, cov: Coverage) -> None:
"title": title,
"time_stamp": format_local_datetime(datetime.datetime.now()),
"extra_css": self.extra_css,
+ "theme": self.theme,
"has_arcs": self.has_arcs,
"show_contexts": self.config.show_contexts,
@@ -340,6 +354,12 @@ def make_local_static_report_files(self) -> None:
for static in self.STATIC_FILES:
shutil.copyfile(data_filename(static), os.path.join(self.directory, static))
+ # If a theme is set, copy the corresponding css file
+ if self.theme:
+ shutil.copyfile(
+ data_filename(os.path.join("themes", self.theme)),
+ os.path.join(self.directory, self.theme))
+
# Only write the .gitignore file if the directory was originally empty.
# .gitignore can't be copied from the source tree because it would
# prevent the static files from being checked in.
diff --git a/coverage/htmlfiles/index.html b/coverage/htmlfiles/index.html
index bde46eafe..c66048cb9 100644
--- a/coverage/htmlfiles/index.html
+++ b/coverage/htmlfiles/index.html
@@ -8,6 +8,9 @@
{{ title|escape }}
+ {% if theme %}
+
+ {% endif %}
{% if extra_css %}
{% endif %}
diff --git a/coverage/htmlfiles/pyfile.html b/coverage/htmlfiles/pyfile.html
index bc8fa697d..10c22621b 100644
--- a/coverage/htmlfiles/pyfile.html
+++ b/coverage/htmlfiles/pyfile.html
@@ -8,6 +8,9 @@
Coverage for {{relative_filename|escape}}: {{nums.pc_covered_str}}%
+ {% if theme %}
+
+ {% endif %}
{% if extra_css %}
{% endif %}
diff --git a/coverage/htmlfiles/themes/github_dark_dimmed.css b/coverage/htmlfiles/themes/github_dark_dimmed.css
new file mode 100644
index 000000000..e2ce33e94
--- /dev/null
+++ b/coverage/htmlfiles/themes/github_dark_dimmed.css
@@ -0,0 +1,87 @@
+/* Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 */
+/* For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt */
+/* Don't edit this .css file. Edit the .scss file instead! */
+:root { color-scheme: dark; }
+
+body { background-color: #22272e; color: #adbac7; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; }
+
+html > body { font-size: 12px; }
+
+html > body.indexfile { font-size: 16px; }
+
+p { font-size: inherit; line-height: 20px; }
+
+a:active, a:focus { outline: 2px solid #539bf5; outline-offset: -2px; }
+
+body.indexfile header h1 { flex-grow: 1; }
+body.indexfile header .content { display: flex; align-items: center; padding: 16px; gap: 16px; flex-wrap: wrap; }
+body.indexfile header .content .text { margin: 0; order: 2; flex-basis: 100%; }
+
+header { background-color: #2d333b; border-bottom-color: #444c56; }
+header p.text { color: #adbac7; }
+header.sticky { height: unset; }
+header.sticky .text { display: none; }
+
+footer .content { color: #768390; }
+
+body.indexfile #filter_container { float: initial; margin: 0; }
+
+#filter_container input { width: 10em; font-size: 14px; line-height: 20px; padding: 5px 12px; color: #adbac7; border: 2px solid #444c56; background-color: #22272e; border-radius: 6px; vertical-align: middle; transition: 80ms cubic-bezier(0.33, 1, 0.68, 1); transition-property: color,background-color,box-shadow,border-color; }
+#filter_container input:focus:not(:focus-visible) { outline: none; box-shadow: inset 0 0 0 1px transparent; }
+#filter_container input:focus-visible { outline: none; box-shadow: inset 0 0 0 1px #539bf5; }
+
+header button { padding: 5px 16px; font-size: 14px; font-weight: 500; line-height: 20px; vertical-align: middle; border: 1px solid; border-radius: 6px; border-color: rgba(205, 217, 229, 0.1); appearance: none; }
+header button:active, header button:focus { outline: 2px solid #539bf5; outline-offset: -2px; }
+header button.run { background-color: #57ab5a; color: #ffffff; }
+header button.run.show_run { background-color: #8ddb8c; border-color: rgba(205, 217, 229, 0.1); }
+header button.mis { background-color: #c93c37; color: #222; }
+header button.mis.show_mis { background-color: #f47067; border-color: rgba(205, 217, 229, 0.1); }
+header button.exc { background-color: #373e47; color: #adbac7; }
+header button.exc.show_exc { background-color: #545d68; border-color: rgba(205, 217, 229, 0.1); }
+header button.par { background-color: #c69026; color: #444; }
+header button.par.show_par { background-color: #eac55f; border-color: rgba(205, 217, 229, 0.1); }
+
+#help_panel { background-color: #2d333b; border-radius: 6px; border-color: #444c56; color: #adbac7; padding: 8px 16px; }
+
+body.indexfile #help_panel_wrapper { float: initial; order: 1; }
+
+#help_panel_wrapper label { cursor: pointer; }
+
+#help_panel .legend { font-size: 12px; font-weight: 600; }
+
+kbd { color: #adbac7; background-color: #2d333b; display: inline-block; padding: 3px 5px; font: 11px ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace; line-height: 10px; vertical-align: middle; border: 1px solid rgba(99, 110, 123, 0.4); border-radius: 6px; box-shadow: inset 0 -1px 0 rgba(99, 110, 123, 0.4); }
+
+#source { font-family: SFMono-Regular, SF Mono, Menlo, Consolas, "Liberation Mono", monospace; }
+#source p .n.highlight { background-color: rgba(255, 213, 79, 0.3); }
+#source p .t { border-left-color: #444c56; }
+#source p .t:hover { background-color: rgba(174, 124, 20, 0.15); }
+#source p .t .com { color: #768390; }
+#source p .t .key { color: #f47067; font-weight: normal; }
+#source p .t .str { color: #96d0ff; }
+#source p.mis .t { border-left-color: rgba(229, 83, 75, 0.3); }
+#source p.mis.show_mis .t { background-color: rgba(229, 83, 75, 0.15); }
+#source p.mis.show_mis .t:hover { background-color: rgba(229, 83, 75, 0.6); }
+#source p.run .t { border-left-color: rgba(87, 171, 90, 0.3); }
+#source p.run.show_run .t { background-color: rgba(87, 171, 90, 0.15); }
+#source p.run.show_run .t:hover { background-color: rgba(87, 171, 90, 0.6); }
+#source p.exc .t { border-left-color: rgba(118, 131, 144, 0.3); }
+#source p.exc.show_exc .t { background-color: rgba(118, 131, 144, 0.15); }
+#source p.exc.show_exc .t:hover { background-color: rgba(118, 131, 144, 0.6); }
+#source p.par .t { border-left-color: rgba(174, 124, 20, 0.3); }
+#source p.par.show_par .t { background-color: rgba(174, 124, 20, 0.15); }
+#source p.par.show_par .t:hover { background-color: rgba(174, 124, 20, 0.6); }
+#source p .annotate { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; color: #adbac7; }
+#source p input ~ .r label.ctx:hover { background-color: rgba(65, 132, 228, 0.15); color: #adbac7; }
+#source p input:checked ~ .r label.ctx { background-color: rgba(65, 132, 228, 0.15); color: #adbac7; }
+#source p label.ctxt { color: #adbac7; }
+#source p .ctxs { background-color: rgba(65, 132, 228, 0.15); color: #adbac7; }
+
+#index { font-family: SFMono-Regular, SF Mono, Menlo, Consolas, "Liberation Mono", monospace; }
+#index td, #index th { border-bottom-color: #444c56; padding: 8px; }
+#index th { color: #adbac7; }
+#index th:hover, #index th[aria-sort="ascending"], #index th[aria-sort="descending"] { background-color: #2d333b; }
+#index tr.total td, #index tr.total_dynamic td { border-top-color: #444c56; }
+#index tr.file:hover { background-color: #2d333b; }
+
+#scroll_marker { background-color: #22272e; border-color: #444c56; }
+#scroll_marker .marker { background-color: #444c56; }
diff --git a/coverage/htmlfiles/themes/github_dark_dimmed.scss b/coverage/htmlfiles/themes/github_dark_dimmed.scss
new file mode 100644
index 000000000..ba53f900f
--- /dev/null
+++ b/coverage/htmlfiles/themes/github_dark_dimmed.scss
@@ -0,0 +1,450 @@
+/* Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 */
+/* For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt */
+
+// CSS styles for coverage.py HTML reports.
+// GitHub Dark dimmed theme (implemented by Lord Baryhobal)
+
+// When you edit this file, you need to run "make css" to get the CSS file
+// generated, and then check in both the .scss and the .css files.
+
+// When working on the file, this command is useful:
+// sass --watch --style=compact --sourcemap=none --no-cache coverage/htmlfiles/themes/github_dark_dimmed.scss:coverage/htmfiles/themes/github_dark_dimmed.css
+//
+// OR you can process sass purely in python with `pip install pysass`, then:
+// pysassc --style=compact coverage/htmlfiles/themes/github_dark_dimmed.scss coverage/htmlfiles/themes/github_dark_dimmed.css
+
+// Ignore this comment, it's for the CSS output file:
+/* Don't edit this .css file. Edit the .scss file instead! */
+
+//
+// Declare colors and variables
+//
+
+$font-normal: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji";
+$font-code: SFMono-Regular, SF Mono, Menlo, Consolas, "Liberation Mono", monospace;
+$font-kbd: 11px ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace;
+
+$bg: #22272e;
+$bg2: #2d333b;
+$border: #444c56;
+$color: #adbac7;
+$comment: #768390;
+$string: #96d0ff;
+$keyword: #f47067;
+
+$green: #57ab5a;
+$yellow: #c69026;
+$red: #c93c37;
+$grey: #373e47;
+
+$green-active: #8ddb8c;
+$yellow-active: #eac55f;
+$red-active: #f47067;
+$grey-active: #545d68;
+
+$btn-border: rgba(#cdd9e5, 0.1);
+
+$green-txt: #ffffff;
+$yellow-txt: #444;
+$red-txt: #222;
+$grey-txt: #adbac7;
+
+$green2: $green;
+$yellow2: #ae7c14;
+$red2: #e5534b;
+$grey2: $comment;
+
+$footer: #768390;
+$line-hover: rgba($yellow2, 0.15);
+$line-green: rgba($green2, 0.15);
+$line-green-border: rgba($green2, 0.30);
+$line-green-hover: rgba($green2, 0.60);
+
+$line-yellow: rgba($yellow2, 0.15);
+$line-yellow-border: rgba($yellow2, 0.30);
+$line-yellow-hover: rgba($yellow2, 0.60);
+
+$line-red: rgba($red2, 0.15);
+$line-red-border: rgba($red2, 0.30);
+$line-red-hover: rgba($red2, 0.60);
+
+$line-grey: rgba($grey2, 0.15);
+$line-grey-border: rgba($grey2, 0.30);
+$line-grey-hover: rgba($grey2, 0.60);
+
+$line-blue: rgba(#4184e4, 0.15);
+
+$outline: #539bf5;
+
+$overlay: #2d333b;
+$border-kbd: rgba(#636e7b, 0.4);
+
+$highlight: rgba(#ffd54f, 0.3);
+
+//
+// Mixins and utilities
+//
+
+// Add visual outline to navigable elements on focus improve accessibility.
+@mixin focus-border {
+ &:active, &:focus {
+ outline: 2px solid $outline;
+ outline-offset: -2px;
+ }
+}
+
+// Page-wide styles
+:root {
+ color-scheme: dark;
+}
+
+body {
+ background-color: $bg;
+ color: $color;
+ font-family: $font-normal;
+}
+html > body {
+ font-size: 12px;
+}
+
+html > body.indexfile {
+ font-size: 16px;
+}
+
+p {
+ font-size: inherit;
+ line-height: 20px;
+}
+
+a {
+ @include focus-border;
+}
+
+// Page structure
+body.indexfile header {
+ h1 {
+ flex-grow: 1;
+ }
+
+ .content {
+ display: flex;
+ align-items: center;
+ padding: 16px;
+ gap: 16px;
+ flex-wrap: wrap;
+
+ .text {
+ margin: 0;
+ order: 2;
+ flex-basis: 100%;
+ }
+ }
+}
+
+header {
+ background-color: $bg2;
+ border-bottom-color: $border;
+
+ p.text {
+ color: $color;
+ }
+
+ &.sticky {
+ height: unset;
+
+ .text {
+ display: none;
+ }
+ }
+}
+
+footer {
+ .content {
+ color: $footer;
+ }
+}
+
+// Header styles
+body.indexfile #filter_container {
+ float: initial;
+ margin: 0;
+}
+
+
+#filter_container {
+ input {
+ width: 10em;
+ font-size: 14px;
+ line-height: 20px;
+ padding: 5px 12px;
+ color: $color;
+ border: 2px solid $border;
+ background-color: $bg;
+ border-radius: 6px;
+ vertical-align: middle;
+ transition: 80ms cubic-bezier(0.33, 1, 0.68, 1);
+ transition-property: color,background-color,box-shadow,border-color;
+
+ &:focus:not(:focus-visible) {
+ outline: none;
+ box-shadow: inset 0 0 0 1px transparent;
+ }
+
+ &:focus-visible {
+ outline: none;
+ box-shadow: inset 0 0 0 1px $outline;
+ }
+ }
+}
+
+
+header button {
+ padding: 5px 16px;
+ font-size: 14px;
+ font-weight: 500;
+ line-height: 20px;
+ vertical-align: middle;
+ border: 1px solid;
+ border-radius: 6px;
+ border-color: $btn-border;
+ appearance: none;
+
+ @include focus-border;
+
+ &.run {
+ background-color: $green;
+ color: $green-txt;
+ &.show_run {
+ background-color: $green-active;
+ border-color: $btn-border;
+ }
+ }
+ &.mis {
+ background-color: $red;
+ color: $red-txt;
+ &.show_mis {
+ background-color: $red-active;
+ border-color: $btn-border;
+ }
+ }
+ &.exc {
+ background-color: $grey;
+ color: $grey-txt;
+ &.show_exc {
+ background-color: $grey-active;
+ border-color: $btn-border;
+ }
+ }
+ &.par {
+ background-color: $yellow;
+ color: $yellow-txt;
+ &.show_par {
+ background-color: $yellow-active;
+ border-color: $btn-border;
+ }
+ }
+}
+
+// Yellow post-it things.
+%popup {
+ background-color: $overlay;
+ border-radius: 6px;
+ border-color: $border;
+ color: $color;
+ padding: 8px 16px;
+}
+
+// Help panel
+body.indexfile #help_panel_wrapper {
+ float: initial;
+ order: 1;
+}
+
+#help_panel_wrapper {
+ label {
+ cursor: pointer;
+ }
+}
+
+#help_panel {
+ @extend %popup;
+
+ .legend {
+ font-size: 12px;
+ font-weight: 600;
+ }
+}
+
+kbd {
+ color: $color;
+ background-color: $bg2;
+ display: inline-block;
+ padding: 3px 5px;
+ font: $font-kbd;
+ line-height: 10px;
+ vertical-align: middle;
+ border: 1px solid $border-kbd;
+ border-radius: 6px;
+ box-shadow: inset 0 -1px 0 $border-kbd;
+}
+
+
+// Source file styles
+
+// The slim bar at the left edge of the source lines, colored by coverage.
+$border-indicator-width: .2em;
+
+#source {
+ font-family: $font-code;
+
+ p {
+ // position relative makes position:absolute pop-ups appear in the right place.
+ .n {
+ &.highlight {
+ background-color: $highlight;
+ }
+ }
+
+ .t {
+ border-left-color: $border;
+
+ &:hover {
+ background-color: $line-hover;
+ }
+
+ // Syntax coloring
+ .com {
+ color: $comment;
+ }
+ .key {
+ color: $keyword;
+ font-weight: normal;
+ }
+ .str {
+ color: $string;
+ }
+ }
+
+ &.mis {
+ .t {
+ border-left-color: $line-red-border;
+ }
+
+ &.show_mis .t {
+ background-color: $line-red;
+
+ &:hover {
+ background-color: $line-red-hover;
+ }
+ }
+ }
+
+ &.run {
+ .t {
+ border-left-color: $line-green-border;
+ }
+
+ &.show_run .t {
+ background-color: $line-green;
+
+ &:hover {
+ background-color: $line-green-hover;
+ }
+ }
+ }
+
+ &.exc {
+ .t {
+ border-left-color: $line-grey-border;
+ }
+
+ &.show_exc .t {
+ background-color: $line-grey;
+
+ &:hover {
+ background-color: $line-grey-hover;
+ }
+ }
+ }
+
+ &.par {
+ .t {
+ border-left-color: $line-yellow-border;
+ }
+
+ &.show_par .t {
+ background-color: $line-yellow;
+
+ &:hover {
+ background-color: $line-yellow-hover;
+ }
+ }
+
+ }
+
+ .annotate {
+ font-family: $font-normal;
+ color: $color;
+ }
+
+ input {
+ & ~ .r label.ctx {
+ &:hover {
+ background-color: $line-blue;
+ color: $color;
+ }
+ }
+
+ &:checked ~ .r label.ctx {
+ background-color: $line-blue;
+ color: $color;
+ }
+ }
+
+ label.ctxt {
+ color: $color;
+ }
+
+ .ctxs {
+ background-color: $line-blue;
+ color: $color;
+ }
+ }
+}
+
+
+// index styles
+#index {
+ font-family: $font-code;
+
+ td, th {
+ border-bottom-color: $border;
+ padding: 8px;
+ }
+ th {
+ color: $color;
+
+ &:hover,
+ &[aria-sort="ascending"], &[aria-sort="descending"] {
+ background-color: $bg2;
+ }
+ }
+
+ tr.total td,
+ tr.total_dynamic td {
+ border-top-color: $border;
+ }
+ tr.file:hover {
+ background-color: $bg2;
+ }
+}
+
+// scroll marker styles
+#scroll_marker {
+ background-color: $bg;
+ border-color: $border;
+
+ .marker {
+ background-color: $border;
+ }
+}
diff --git a/setup.py b/setup.py
index 536b64ac9..dd22a7fd7 100644
--- a/setup.py
+++ b/setup.py
@@ -86,7 +86,7 @@
package_data={
'coverage': [
- 'htmlfiles/*.*',
+ 'htmlfiles/**/*.*',
'py.typed',
]
},