coverage 7.6.7__cp311-cp311-win_amd64.whl → 7.11.1__cp311-cp311-win_amd64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. coverage/__init__.py +2 -0
  2. coverage/__main__.py +2 -0
  3. coverage/annotate.py +1 -2
  4. coverage/bytecode.py +177 -3
  5. coverage/cmdline.py +329 -154
  6. coverage/collector.py +31 -42
  7. coverage/config.py +166 -62
  8. coverage/context.py +4 -5
  9. coverage/control.py +164 -85
  10. coverage/core.py +70 -33
  11. coverage/data.py +3 -4
  12. coverage/debug.py +112 -56
  13. coverage/disposition.py +1 -0
  14. coverage/env.py +65 -55
  15. coverage/exceptions.py +35 -7
  16. coverage/execfile.py +18 -13
  17. coverage/files.py +23 -18
  18. coverage/html.py +134 -88
  19. coverage/htmlfiles/style.css +42 -2
  20. coverage/htmlfiles/style.scss +65 -1
  21. coverage/inorout.py +61 -44
  22. coverage/jsonreport.py +17 -8
  23. coverage/lcovreport.py +16 -20
  24. coverage/misc.py +50 -46
  25. coverage/multiproc.py +12 -7
  26. coverage/numbits.py +3 -4
  27. coverage/parser.py +193 -269
  28. coverage/patch.py +166 -0
  29. coverage/phystokens.py +24 -25
  30. coverage/plugin.py +13 -13
  31. coverage/plugin_support.py +36 -35
  32. coverage/python.py +9 -13
  33. coverage/pytracer.py +40 -33
  34. coverage/regions.py +2 -1
  35. coverage/report.py +59 -43
  36. coverage/report_core.py +6 -9
  37. coverage/results.py +118 -66
  38. coverage/sqldata.py +260 -210
  39. coverage/sqlitedb.py +33 -25
  40. coverage/sysmon.py +195 -157
  41. coverage/templite.py +6 -6
  42. coverage/tomlconfig.py +12 -12
  43. coverage/tracer.cp311-win_amd64.pyd +0 -0
  44. coverage/tracer.pyi +2 -0
  45. coverage/types.py +25 -22
  46. coverage/version.py +3 -18
  47. coverage/xmlreport.py +16 -13
  48. {coverage-7.6.7.dist-info → coverage-7.11.1.dist-info}/METADATA +40 -18
  49. coverage-7.11.1.dist-info/RECORD +59 -0
  50. {coverage-7.6.7.dist-info → coverage-7.11.1.dist-info}/WHEEL +1 -1
  51. coverage-7.6.7.dist-info/RECORD +0 -58
  52. {coverage-7.6.7.dist-info → coverage-7.11.1.dist-info}/entry_points.txt +0 -0
  53. {coverage-7.6.7.dist-info → coverage-7.11.1.dist-info/licenses}/LICENSE.txt +0 -0
  54. {coverage-7.6.7.dist-info → coverage-7.11.1.dist-info}/top_level.txt +0 -0
coverage/html.py CHANGED
@@ -13,26 +13,30 @@ import json
13
13
  import os
14
14
  import re
15
15
  import string
16
-
17
- from dataclasses import dataclass, field
18
- from typing import Any, TYPE_CHECKING
19
16
  from collections.abc import Iterable
17
+ from dataclasses import dataclass, field
18
+ from typing import TYPE_CHECKING, Any
20
19
 
21
20
  import coverage
22
21
  from coverage.data import CoverageData, add_data_to_hash
23
22
  from coverage.exceptions import NoDataError
24
23
  from coverage.files import flat_rootname
25
24
  from coverage.misc import (
26
- ensure_dir, file_be_gone, Hasher, isolate_module, format_local_datetime,
27
- human_sorted, plural, stdout_link,
25
+ Hasher,
26
+ ensure_dir,
27
+ file_be_gone,
28
+ format_local_datetime,
29
+ human_sorted,
30
+ isolate_module,
31
+ plural,
32
+ stdout_link,
28
33
  )
29
34
  from coverage.report_core import get_analysis_to_report
30
- from coverage.results import Analysis, Numbers
35
+ from coverage.results import Analysis, AnalysisNarrower, Numbers
31
36
  from coverage.templite import Templite
32
37
  from coverage.types import TLineNo, TMorf
33
38
  from coverage.version import __url__
34
39
 
35
-
36
40
  if TYPE_CHECKING:
37
41
  from coverage import Coverage
38
42
  from coverage.plugins import FileReporter
@@ -42,8 +46,7 @@ os = isolate_module(os)
42
46
 
43
47
 
44
48
  def data_filename(fname: str) -> str:
45
- """Return the path to an "htmlfiles" data file of ours.
46
- """
49
+ """Return the path to an "htmlfiles" data file of ours."""
47
50
  static_dir = os.path.join(os.path.dirname(__file__), "htmlfiles")
48
51
  static_filename = os.path.join(static_dir, fname)
49
52
  return static_filename
@@ -51,7 +54,7 @@ def data_filename(fname: str) -> str:
51
54
 
52
55
  def read_data(fname: str) -> str:
53
56
  """Return the contents of a data file of ours."""
54
- with open(data_filename(fname)) as data_file:
57
+ with open(data_filename(fname), encoding="utf-8") as data_file:
55
58
  return data_file.read()
56
59
 
57
60
 
@@ -65,6 +68,7 @@ def write_html(fname: str, html: str) -> None:
65
68
  @dataclass
66
69
  class LineData:
67
70
  """The data for each source line of HTML output."""
71
+
68
72
  tokens: list[tuple[str, str]]
69
73
  number: TLineNo
70
74
  category: str
@@ -83,6 +87,7 @@ class LineData:
83
87
  @dataclass
84
88
  class FileData:
85
89
  """The data for each source file of HTML output."""
90
+
86
91
  relative_filename: str
87
92
  nums: Numbers
88
93
  lines: list[LineData]
@@ -91,6 +96,7 @@ class FileData:
91
96
  @dataclass
92
97
  class IndexItem:
93
98
  """Information for each index entry, to render an index page."""
99
+
94
100
  url: str = ""
95
101
  file: str = ""
96
102
  description: str = ""
@@ -100,6 +106,7 @@ class IndexItem:
100
106
  @dataclass
101
107
  class IndexPage:
102
108
  """Data for each index page."""
109
+
103
110
  noun: str
104
111
  plural: str
105
112
  filename: str
@@ -137,10 +144,14 @@ class HtmlDataGeneration:
137
144
  contexts_by_lineno = self.data.contexts_by_lineno(analysis.filename)
138
145
 
139
146
  lines = []
147
+ branch_stats = analysis.branch_stats()
148
+ multiline_map = {}
149
+ if hasattr(fr, "multiline_map"):
150
+ multiline_map = fr.multiline_map()
140
151
 
141
152
  for lineno, tokens in enumerate(fr.source_token_lines(), start=1):
142
153
  # Figure out how to mark this line.
143
- category = ""
154
+ category = category2 = ""
144
155
  short_annotations = []
145
156
  long_annotations = []
146
157
 
@@ -150,14 +161,36 @@ class HtmlDataGeneration:
150
161
  category = "mis"
151
162
  elif self.has_arcs and lineno in missing_branch_arcs:
152
163
  category = "par"
153
- for b in missing_branch_arcs[lineno]:
154
- if b < 0:
155
- short_annotations.append("exit")
156
- else:
157
- short_annotations.append(str(b))
158
- long_annotations.append(fr.missing_arc_description(lineno, b, arcs_executed))
164
+ mba = missing_branch_arcs[lineno]
165
+ if len(mba) == branch_stats[lineno][0]:
166
+ # None of the branches were taken from this line.
167
+ short_annotations.append("anywhere")
168
+ long_annotations.append(
169
+ f"line {lineno} didn't jump anywhere: it always raised an exception."
170
+ )
171
+ else:
172
+ for b in missing_branch_arcs[lineno]:
173
+ if b < 0:
174
+ short_annotations.append("exit")
175
+ else:
176
+ short_annotations.append(str(b))
177
+ long_annotations.append(
178
+ fr.missing_arc_description(lineno, b, arcs_executed)
179
+ )
159
180
  elif lineno in analysis.statements:
160
181
  category = "run"
182
+ elif first_line := multiline_map.get(lineno):
183
+ if first_line in analysis.excluded:
184
+ category2 = "exc2"
185
+ elif first_line in analysis.missing:
186
+ category2 = "mis2"
187
+ elif self.has_arcs and first_line in missing_branch_arcs:
188
+ category2 = "par2"
189
+ # I don't understand why this last condition is marked as
190
+ # partial. If I add an else with an exception, the exception
191
+ # is raised.
192
+ elif first_line in analysis.statements: # pragma: part covered
193
+ category2 = "run2"
161
194
 
162
195
  contexts = []
163
196
  contexts_label = ""
@@ -170,16 +203,18 @@ class HtmlDataGeneration:
170
203
  contexts_label = f"{len(contexts)} ctx"
171
204
  context_list = contexts
172
205
 
173
- lines.append(LineData(
174
- tokens=tokens,
175
- number=lineno,
176
- category=category,
177
- contexts=contexts,
178
- contexts_label=contexts_label,
179
- context_list=context_list,
180
- short_annotations=short_annotations,
181
- long_annotations=long_annotations,
182
- ))
206
+ lines.append(
207
+ LineData(
208
+ tokens=tokens,
209
+ number=lineno,
210
+ category=category or category2,
211
+ contexts=contexts,
212
+ contexts_label=contexts_label,
213
+ context_list=context_list,
214
+ short_annotations=short_annotations,
215
+ long_annotations=long_annotations,
216
+ )
217
+ )
183
218
 
184
219
  file_data = FileData(
185
220
  relative_filename=fr.relative_filename(),
@@ -192,6 +227,7 @@ class HtmlDataGeneration:
192
227
 
193
228
  class FileToReport:
194
229
  """A file we're considering reporting."""
230
+
195
231
  def __init__(self, fr: FileReporter, analysis: Analysis) -> None:
196
232
  self.fr = fr
197
233
  self.analysis = analysis
@@ -202,6 +238,7 @@ class FileToReport:
202
238
 
203
239
  HTML_SAFE = string.ascii_letters + string.digits + "!#$%'()*+,-./:;=?@[]^_`{|}~"
204
240
 
241
+
205
242
  @functools.cache
206
243
  def encode_int(n: int) -> str:
207
244
  """Create a short HTML-safe string from an integer, using HTML_SAFE."""
@@ -277,7 +314,6 @@ class HtmlReporter:
277
314
  "escape": escape,
278
315
  "pair": pair,
279
316
  "len": len,
280
-
281
317
  # Constants for this report.
282
318
  "__url__": __url__,
283
319
  "__version__": coverage.__version__,
@@ -287,7 +323,6 @@ class HtmlReporter:
287
323
  "has_arcs": self.has_arcs,
288
324
  "show_contexts": self.config.show_contexts,
289
325
  "statics": {},
290
-
291
326
  # Constants for all reports.
292
327
  # These css classes determine which lines are highlighted by default.
293
328
  "category": {
@@ -295,6 +330,10 @@ class HtmlReporter:
295
330
  "mis": "mis show_mis",
296
331
  "par": "par run show_par",
297
332
  "run": "run",
333
+ "exc2": "exc exc2 show_exc",
334
+ "mis2": "mis mis2 show_mis",
335
+ "par2": "par par2 ru2 show_par",
336
+ "run2": "run run2",
298
337
  },
299
338
  }
300
339
  self.index_tmpl = Templite(read_data("index.html"), self.template_globals)
@@ -383,7 +422,7 @@ class HtmlReporter:
383
422
  dest = copy_with_cache_bust(src, self.directory)
384
423
  if not slug:
385
424
  slug = os.path.basename(src).replace(".", "_")
386
- self.template_globals["statics"][slug] = dest # type: ignore
425
+ self.template_globals["statics"][slug] = dest # type: ignore
387
426
 
388
427
  def make_local_static_report_files(self) -> None:
389
428
  """Make local instances of static files for HTML report."""
@@ -401,7 +440,7 @@ class HtmlReporter:
401
440
  # .gitignore can't be copied from the source tree because if it was in
402
441
  # the source tree, it would stop the static files from being checked in.
403
442
  if self.directory_was_empty:
404
- with open(os.path.join(self.directory, ".gitignore"), "w") as fgi:
443
+ with open(os.path.join(self.directory, ".gitignore"), "w", encoding="utf-8") as fgi:
405
444
  fgi.write("# Created by coverage.py\n*\n")
406
445
 
407
446
  def should_report(self, analysis: Analysis, index_page: IndexPage) -> bool:
@@ -412,8 +451,8 @@ class HtmlReporter:
412
451
 
413
452
  if self.skip_covered:
414
453
  # Don't report on 100% files.
415
- no_missing_lines = (nums.n_missing == 0)
416
- no_missing_branches = (nums.n_partial_branches == 0)
454
+ no_missing_lines = (nums.n_missing == 0) # fmt: skip
455
+ no_missing_branches = (nums.n_partial_branches == 0) # fmt: skip
417
456
  if no_missing_lines and no_missing_branches:
418
457
  index_page.skipped_covered_count += 1
419
458
  return False
@@ -467,9 +506,8 @@ class HtmlReporter:
467
506
  encode_int(context_codes[c_context]) for c_context in ldata.context_list
468
507
  ]
469
508
  code_width = max(len(ec) for ec in encoded_contexts)
470
- ldata.context_str = (
471
- str(code_width)
472
- + "".join(ec.ljust(code_width) for ec in encoded_contexts)
509
+ ldata.context_str = str(code_width) + "".join(
510
+ ec.ljust(code_width) for ec in encoded_contexts
473
511
  )
474
512
  else:
475
513
  ldata.context_str = ""
@@ -478,48 +516,45 @@ class HtmlReporter:
478
516
  # 202F is NARROW NO-BREAK SPACE.
479
517
  # 219B is RIGHTWARDS ARROW WITH STROKE.
480
518
  ldata.annotate = ",&nbsp;&nbsp; ".join(
481
- f"{ldata.number}&#x202F;&#x219B;&#x202F;{d}"
482
- for d in ldata.short_annotations
519
+ f"{ldata.number}&#x202F;&#x219B;&#x202F;{d}" for d in ldata.short_annotations
483
520
  )
484
521
  else:
485
522
  ldata.annotate = None
486
523
 
487
524
  if ldata.long_annotations:
488
525
  longs = ldata.long_annotations
489
- if len(longs) == 1:
490
- ldata.annotate_long = longs[0]
491
- else:
492
- ldata.annotate_long = "{:d} missed branches: {}".format(
493
- len(longs),
494
- ", ".join(
495
- f"{num:d}) {ann_long}"
496
- for num, ann_long in enumerate(longs, start=1)
497
- ),
498
- )
526
+ # A line can only have two branch destinations. If there were
527
+ # two missing, we would have written one as "always raised."
528
+ assert len(longs) == 1, (
529
+ f"Had long annotations in {ftr.fr.relative_filename()}: {longs}"
530
+ )
531
+ ldata.annotate_long = longs[0]
499
532
  else:
500
533
  ldata.annotate_long = None
501
534
 
502
535
  css_classes = []
503
536
  if ldata.category:
504
537
  css_classes.append(
505
- self.template_globals["category"][ldata.category], # type: ignore[index]
538
+ self.template_globals["category"][ldata.category], # type: ignore[index]
506
539
  )
507
540
  ldata.css_class = " ".join(css_classes) or "pln"
508
541
 
509
542
  html_path = os.path.join(self.directory, ftr.html_filename)
510
- html = self.source_tmpl.render({
511
- **file_data.__dict__,
512
- "contexts_json": contexts_json,
513
- "prev_html": ftr.prev_html,
514
- "next_html": ftr.next_html,
515
- })
543
+ html = self.source_tmpl.render(
544
+ {
545
+ **file_data.__dict__,
546
+ "contexts_json": contexts_json,
547
+ "prev_html": ftr.prev_html,
548
+ "next_html": ftr.next_html,
549
+ }
550
+ )
516
551
  write_html(html_path, html)
517
552
 
518
553
  # Save this file's information for the index page.
519
554
  index_info = IndexItem(
520
- url = ftr.html_filename,
521
- file = escape(ftr.fr.relative_filename()),
522
- nums = ftr.analysis.numbers,
555
+ url=ftr.html_filename,
556
+ file=escape(ftr.fr.relative_filename()),
557
+ nums=ftr.analysis.numbers,
523
558
  )
524
559
  self.index_pages["file"].summaries.append(index_info)
525
560
  self.incr.set_index_info(ftr.rootname, index_info)
@@ -547,39 +582,51 @@ class HtmlReporter:
547
582
 
548
583
  for noun in region_nouns:
549
584
  page_data = self.index_pages[noun]
550
- outside_lines = set(range(1, num_lines + 1))
551
585
 
586
+ outside_lines = set(range(1, num_lines + 1))
552
587
  for region in regions:
553
588
  if region.kind != noun:
554
589
  continue
555
590
  outside_lines -= region.lines
556
- analysis = ftr.analysis.narrow(region.lines)
591
+
592
+ narrower = AnalysisNarrower(ftr.analysis)
593
+ narrower.add_regions(r.lines for r in regions if r.kind == noun)
594
+ narrower.add_regions([outside_lines])
595
+
596
+ for region in regions:
597
+ if region.kind != noun:
598
+ continue
599
+ analysis = narrower.narrow(region.lines)
557
600
  if not self.should_report(analysis, page_data):
558
601
  continue
559
602
  sorting_name = region.name.rpartition(".")[-1].lstrip("_")
560
- page_data.summaries.append(IndexItem(
561
- url=f"{ftr.html_filename}#t{region.start}",
562
- file=escape(ftr.fr.relative_filename()),
563
- description=(
564
- f"<data value='{escape(sorting_name)}'>"
565
- + escape(region.name)
566
- + "</data>"
567
- ),
568
- nums=analysis.numbers,
569
- ))
570
-
571
- analysis = ftr.analysis.narrow(outside_lines)
603
+ page_data.summaries.append(
604
+ IndexItem(
605
+ url=f"{ftr.html_filename}#t{region.start}",
606
+ file=escape(ftr.fr.relative_filename()),
607
+ description=(
608
+ f"<data value='{escape(sorting_name)}'>"
609
+ + escape(region.name)
610
+ + "</data>"
611
+ ),
612
+ nums=analysis.numbers,
613
+ )
614
+ )
615
+
616
+ analysis = narrower.narrow(outside_lines)
572
617
  if self.should_report(analysis, page_data):
573
- page_data.summaries.append(IndexItem(
574
- url=ftr.html_filename,
575
- file=escape(ftr.fr.relative_filename()),
576
- description=(
577
- "<data value=''>"
578
- + f"<span class='no-noun'>(no {escape(noun)})</span>"
579
- + "</data>"
580
- ),
581
- nums=analysis.numbers,
582
- ))
618
+ page_data.summaries.append(
619
+ IndexItem(
620
+ url=ftr.html_filename,
621
+ file=escape(ftr.fr.relative_filename()),
622
+ description=(
623
+ "<data value=''>"
624
+ + f"<span class='no-noun'>(no {escape(noun)})</span>"
625
+ + "</data>"
626
+ ),
627
+ nums=analysis.numbers,
628
+ )
629
+ )
583
630
 
584
631
  for noun, index_page in self.index_pages.items():
585
632
  if noun != "file":
@@ -629,6 +676,7 @@ class HtmlReporter:
629
676
  @dataclass
630
677
  class FileInfo:
631
678
  """Summary of the information from last rendering, to avoid duplicate work."""
679
+
632
680
  hash: str = ""
633
681
  index: IndexItem = field(default_factory=IndexItem)
634
682
 
@@ -699,7 +747,7 @@ class IncrementalChecker:
699
747
  """Read the information we stored last time."""
700
748
  try:
701
749
  status_file = os.path.join(self.directory, self.STATUS_FILE)
702
- with open(status_file) as fstatus:
750
+ with open(status_file, encoding="utf-8") as fstatus:
703
751
  status = json.load(fstatus)
704
752
  except (OSError, ValueError):
705
753
  # Status file is missing or malformed.
@@ -735,12 +783,9 @@ class IncrementalChecker:
735
783
  "format": self.STATUS_FORMAT,
736
784
  "version": coverage.__version__,
737
785
  "globals": self.globals,
738
- "files": {
739
- fname: dataclasses.asdict(finfo)
740
- for fname, finfo in self.files.items()
741
- },
786
+ "files": {fname: dataclasses.asdict(finfo) for fname, finfo in self.files.items()},
742
787
  }
743
- with open(status_file, "w") as fout:
788
+ with open(status_file, "w", encoding="utf-8") as fout:
744
789
  json.dump(status_data, fout, separators=(",", ":"))
745
790
 
746
791
  def check_global_data(self, *data: Any) -> None:
@@ -795,6 +840,7 @@ class IncrementalChecker:
795
840
 
796
841
  # Helpers for templates and generating HTML
797
842
 
843
+
798
844
  def escape(t: str) -> str:
799
845
  """HTML-escape the text in `t`.
800
846
 
@@ -200,9 +200,9 @@ kbd { border: 1px solid black; border-color: #888 #333 #333 #888; padding: .1em
200
200
 
201
201
  #source p .t .key { font-weight: bold; line-height: 1px; }
202
202
 
203
- #source p .t .str { color: #0451a5; }
203
+ #source p .t .str, #source p .t .fst { color: #0451a5; }
204
204
 
205
- @media (prefers-color-scheme: dark) { #source p .t .str { color: #9cdcfe; } }
205
+ @media (prefers-color-scheme: dark) { #source p .t .str, #source p .t .fst { color: #9cdcfe; } }
206
206
 
207
207
  #source p.mis .t { border-left: 0.2em solid #ff0000; }
208
208
 
@@ -214,6 +214,16 @@ kbd { border: 1px solid black; border-color: #888 #333 #333 #888; padding: .1em
214
214
 
215
215
  @media (prefers-color-scheme: dark) { #source p.mis.show_mis .t:hover { background: #532323; } }
216
216
 
217
+ #source p.mis.mis2 .t { border-left: 0.2em dotted #ff0000; }
218
+
219
+ #source p.mis.mis2.show_mis .t { background: #ffeeee; }
220
+
221
+ @media (prefers-color-scheme: dark) { #source p.mis.mis2.show_mis .t { background: #351b1b; } }
222
+
223
+ #source p.mis.mis2.show_mis .t:hover { background: #f2d2d2; }
224
+
225
+ @media (prefers-color-scheme: dark) { #source p.mis.mis2.show_mis .t:hover { background: #532323; } }
226
+
217
227
  #source p.run .t { border-left: 0.2em solid #00dd00; }
218
228
 
219
229
  #source p.run.show_run .t { background: #dfd; }
@@ -224,6 +234,16 @@ kbd { border: 1px solid black; border-color: #888 #333 #333 #888; padding: .1em
224
234
 
225
235
  @media (prefers-color-scheme: dark) { #source p.run.show_run .t:hover { background: #404633; } }
226
236
 
237
+ #source p.run.run2 .t { border-left: 0.2em dotted #00dd00; }
238
+
239
+ #source p.run.run2.show_run .t { background: #eeffee; }
240
+
241
+ @media (prefers-color-scheme: dark) { #source p.run.run2.show_run .t { background: #2b2e24; } }
242
+
243
+ #source p.run.run2.show_run .t:hover { background: #d2f2d2; }
244
+
245
+ @media (prefers-color-scheme: dark) { #source p.run.run2.show_run .t:hover { background: #404633; } }
246
+
227
247
  #source p.exc .t { border-left: 0.2em solid #808080; }
228
248
 
229
249
  #source p.exc.show_exc .t { background: #eee; }
@@ -234,6 +254,16 @@ kbd { border: 1px solid black; border-color: #888 #333 #333 #888; padding: .1em
234
254
 
235
255
  @media (prefers-color-scheme: dark) { #source p.exc.show_exc .t:hover { background: #3c3c3c; } }
236
256
 
257
+ #source p.exc.exc2 .t { border-left: 0.2em dotted #808080; }
258
+
259
+ #source p.exc.exc2.show_exc .t { background: #f7f7f7; }
260
+
261
+ @media (prefers-color-scheme: dark) { #source p.exc.exc2.show_exc .t { background: #292929; } }
262
+
263
+ #source p.exc.exc2.show_exc .t:hover { background: #e2e2e2; }
264
+
265
+ @media (prefers-color-scheme: dark) { #source p.exc.exc2.show_exc .t:hover { background: #3c3c3c; } }
266
+
237
267
  #source p.par .t { border-left: 0.2em solid #bbbb00; }
238
268
 
239
269
  #source p.par.show_par .t { background: #ffa; }
@@ -244,6 +274,16 @@ kbd { border: 1px solid black; border-color: #888 #333 #333 #888; padding: .1em
244
274
 
245
275
  @media (prefers-color-scheme: dark) { #source p.par.show_par .t:hover { background: #6d5d0c; } }
246
276
 
277
+ #source p.par.par2 .t { border-left: 0.2em dotted #bbbb00; }
278
+
279
+ #source p.par.par2.show_par .t { background: #ffffd5; }
280
+
281
+ @media (prefers-color-scheme: dark) { #source p.par.par2.show_par .t { background: #423a0f; } }
282
+
283
+ #source p.par.par2.show_par .t:hover { background: #f2f2a2; }
284
+
285
+ @media (prefers-color-scheme: dark) { #source p.par.par2.show_par .t:hover { background: #6d5d0c; } }
286
+
247
287
  #source p .r { position: absolute; top: 0; right: 2.5em; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; }
248
288
 
249
289
  #source p .annotate { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; color: #666; padding-right: .5em; }
@@ -27,6 +27,7 @@ $font-code: SFMono-Regular, Menlo, Monaco, Consolas, monospace;
27
27
 
28
28
  $off-button-lighten: 50%;
29
29
  $hover-dark-amt: 95%;
30
+ $multi-dim-amt: 50%;
30
31
 
31
32
  $focus-color: #007acc;
32
33
 
@@ -501,7 +502,7 @@ $border-indicator-width: .2em;
501
502
  font-weight: bold;
502
503
  line-height: 1px;
503
504
  }
504
- .str {
505
+ .str, .fst {
505
506
  color: $light-token-str;
506
507
  @include color-dark($dark-token-str);
507
508
  }
@@ -521,6 +522,22 @@ $border-indicator-width: .2em;
521
522
  @include background-dark(mix($dark-mis-bg, $dark-fg, $hover-dark-amt));
522
523
  }
523
524
  }
525
+
526
+ &.mis2 {
527
+ .t {
528
+ border-left: $border-indicator-width dotted $mis-color;
529
+ }
530
+
531
+ &.show_mis .t {
532
+ background: mix($light-mis-bg, $light-bg, $multi-dim-amt);
533
+ @include background-dark(mix($dark-mis-bg, $dark-bg, $multi-dim-amt));
534
+
535
+ &:hover {
536
+ background: mix($light-mis-bg, $light-fg, $hover-dark-amt);
537
+ @include background-dark(mix($dark-mis-bg, $dark-fg, $hover-dark-amt));
538
+ }
539
+ }
540
+ }
524
541
  }
525
542
 
526
543
  &.run {
@@ -537,6 +554,22 @@ $border-indicator-width: .2em;
537
554
  @include background-dark(mix($dark-run-bg, $dark-fg, $hover-dark-amt));
538
555
  }
539
556
  }
557
+
558
+ &.run2 {
559
+ .t {
560
+ border-left: $border-indicator-width dotted $run-color;
561
+ }
562
+
563
+ &.show_run .t {
564
+ background: mix($light-run-bg, $light-bg, $multi-dim-amt);
565
+ @include background-dark(mix($dark-run-bg, $dark-bg, $multi-dim-amt));
566
+
567
+ &:hover {
568
+ background: mix($light-run-bg, $light-fg, $hover-dark-amt);
569
+ @include background-dark(mix($dark-run-bg, $dark-fg, $hover-dark-amt));
570
+ }
571
+ }
572
+ }
540
573
  }
541
574
 
542
575
  &.exc {
@@ -553,6 +586,22 @@ $border-indicator-width: .2em;
553
586
  @include background-dark(mix($dark-exc-bg, $dark-fg, $hover-dark-amt));
554
587
  }
555
588
  }
589
+
590
+ &.exc2 {
591
+ .t {
592
+ border-left: $border-indicator-width dotted $exc-color;
593
+ }
594
+
595
+ &.show_exc .t {
596
+ background: mix($light-exc-bg, $light-bg, $multi-dim-amt);
597
+ @include background-dark(mix($dark-exc-bg, $dark-bg, $multi-dim-amt));
598
+
599
+ &:hover {
600
+ background: mix($light-exc-bg, $light-fg, $hover-dark-amt);
601
+ @include background-dark(mix($dark-exc-bg, $dark-fg, $hover-dark-amt));
602
+ }
603
+ }
604
+ }
556
605
  }
557
606
 
558
607
  &.par {
@@ -570,6 +619,21 @@ $border-indicator-width: .2em;
570
619
  }
571
620
  }
572
621
 
622
+ &.par2 {
623
+ .t {
624
+ border-left: $border-indicator-width dotted $par-color;
625
+ }
626
+
627
+ &.show_par .t {
628
+ background: mix($light-par-bg, $light-bg, $multi-dim-amt);
629
+ @include background-dark(mix($dark-par-bg, $dark-bg, $multi-dim-amt));
630
+
631
+ &:hover {
632
+ background: mix($light-par-bg, $light-fg, $hover-dark-amt);
633
+ @include background-dark(mix($dark-par-bg, $dark-fg, $hover-dark-amt));
634
+ }
635
+ }
636
+ }
573
637
  }
574
638
 
575
639
  .r {