path_coverage 0.0.1 → 0.0.2
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.
- checksums.yaml +4 -4
- data/LICENSE.md +22 -0
- data/README.md +316 -0
- data/exe/path_coverage_collector +26 -0
- data/lib/path_coverage/auto.rb +5 -0
- data/lib/path_coverage/collector.rb +46 -0
- data/lib/path_coverage/condition.rb +45 -0
- data/lib/path_coverage/configuration.rb +127 -0
- data/lib/path_coverage/gaps.rb +60 -0
- data/lib/path_coverage/html/file.html +33 -0
- data/lib/path_coverage/html/index.html +38 -0
- data/lib/path_coverage/html/side.js +531 -0
- data/lib/path_coverage/html/sort.js +28 -0
- data/lib/path_coverage/html/style.css +334 -0
- data/lib/path_coverage/html/toggle.js +12 -0
- data/lib/path_coverage/html_file.rb +109 -0
- data/lib/path_coverage/html_index.rb +43 -0
- data/lib/path_coverage/html_nav.rb +67 -0
- data/lib/path_coverage/html_paths.rb +138 -0
- data/lib/path_coverage/html_report.rb +103 -0
- data/lib/path_coverage/html_template.rb +20 -0
- data/lib/path_coverage/html_tree.rb +137 -0
- data/lib/path_coverage/json_writer.rb +21 -0
- data/lib/path_coverage/kernel.rb +179 -0
- data/lib/path_coverage/manifest.rb +177 -0
- data/lib/path_coverage/manifest_choices.rb +113 -0
- data/lib/path_coverage/merge.rb +35 -0
- data/lib/path_coverage/path_gaps.rb +41 -0
- data/lib/path_coverage/path_manifest.rb +110 -0
- data/lib/path_coverage/paths_report.rb +55 -0
- data/lib/path_coverage/post.rb +26 -0
- data/lib/path_coverage/recorder.rb +99 -0
- data/lib/path_coverage/render.rb +92 -0
- data/lib/path_coverage/result.rb +59 -0
- data/lib/path_coverage/rewriter.rb +120 -0
- data/lib/path_coverage/site.rb +11 -0
- data/lib/path_coverage/site_paths.rb +74 -0
- data/lib/path_coverage/site_tree.rb +94 -0
- data/lib/path_coverage/source_lines.rb +79 -0
- data/lib/path_coverage/summary.rb +33 -0
- data/lib/path_coverage/version.rb +3 -0
- data/lib/path_coverage.rb +2 -5
- metadata +68 -13
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8"/>
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
|
6
|
+
<title>{{path}} · Path coverage</title>
|
|
7
|
+
<style>
|
|
8
|
+
/*<![CDATA[*/
|
|
9
|
+
{{style}}
|
|
10
|
+
/*]]>*/
|
|
11
|
+
</style>
|
|
12
|
+
</head>
|
|
13
|
+
<body>
|
|
14
|
+
<main class="page">
|
|
15
|
+
<header class="head" data-head="">
|
|
16
|
+
<p class="back"><a data-index="" href="{{index}}">Path coverage</a></p>
|
|
17
|
+
<h1 class="path">{{path}}</h1>
|
|
18
|
+
{{jumps}}
|
|
19
|
+
</header>
|
|
20
|
+
<div class="box">
|
|
21
|
+
{{listing}}
|
|
22
|
+
</div>
|
|
23
|
+
<div class="pathgrids" hidden="hidden">
|
|
24
|
+
{{grids}}
|
|
25
|
+
</div>
|
|
26
|
+
</main>
|
|
27
|
+
<script>
|
|
28
|
+
//<![CDATA[
|
|
29
|
+
{{script}}
|
|
30
|
+
//]]>
|
|
31
|
+
</script>
|
|
32
|
+
</body>
|
|
33
|
+
</html>
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8"/>
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
|
6
|
+
<title>Path coverage</title>
|
|
7
|
+
<style>
|
|
8
|
+
/*<![CDATA[*/
|
|
9
|
+
{{style}}
|
|
10
|
+
/*]]>*/
|
|
11
|
+
</style>
|
|
12
|
+
</head>
|
|
13
|
+
<body>
|
|
14
|
+
<main class="page">
|
|
15
|
+
<h1>Path coverage</h1>
|
|
16
|
+
<div class="box">
|
|
17
|
+
<table id="files">
|
|
18
|
+
<thead>
|
|
19
|
+
<tr>
|
|
20
|
+
<th aria-sort="none"><button type="button" data-column="0" data-type="text">File</button></th>
|
|
21
|
+
<th class="num" aria-sort="none"><button type="button" data-column="1" data-type="number">Sites</button></th>
|
|
22
|
+
<th class="num" aria-sort="none"><button type="button" data-column="2" data-type="number">Outcomes seen</button></th>
|
|
23
|
+
<th class="num" aria-sort="none"><button type="button" data-column="3" data-type="number">Missing</button></th>
|
|
24
|
+
</tr>
|
|
25
|
+
</thead>
|
|
26
|
+
<tbody>
|
|
27
|
+
{{rows}}
|
|
28
|
+
</tbody>
|
|
29
|
+
</table>
|
|
30
|
+
</div>
|
|
31
|
+
</main>
|
|
32
|
+
<script>
|
|
33
|
+
//<![CDATA[
|
|
34
|
+
{{script}}
|
|
35
|
+
//]]>
|
|
36
|
+
</script>
|
|
37
|
+
</body>
|
|
38
|
+
</html>
|
|
@@ -0,0 +1,531 @@
|
|
|
1
|
+
// SPIKE: puts a file page's trees in a rail to the left of the
|
|
2
|
+
// code, each level with its site, and its path grids in a rail
|
|
3
|
+
// to the right. Each method whose paths are counted gets a bar
|
|
4
|
+
// on the code's right edge, from its def to its end, that opens
|
|
5
|
+
// its grid. One tree or grid is open at a time; opening one
|
|
6
|
+
// widens its rail, sliding the code away from it.
|
|
7
|
+
(function () {
|
|
8
|
+
var code = document.querySelector('.code');
|
|
9
|
+
if (!code) { return; }
|
|
10
|
+
var box = code.parentElement;
|
|
11
|
+
var trees = Array.prototype.slice.call(document.querySelectorAll('.tree'));
|
|
12
|
+
var grids = Array.prototype.slice.call(document.querySelectorAll('.pathgrid'));
|
|
13
|
+
var ccRail = document.createElement('div');
|
|
14
|
+
ccRail.className = 'rail cc';
|
|
15
|
+
var pcRail = document.createElement('div');
|
|
16
|
+
pcRail.className = 'rail pc';
|
|
17
|
+
var bars = grids.map(function (grid) {
|
|
18
|
+
var bar = document.createElement('button');
|
|
19
|
+
bar.type = 'button';
|
|
20
|
+
bar.className = 'pathbar';
|
|
21
|
+
bar.setAttribute('data-status', grid.getAttribute('data-status'));
|
|
22
|
+
bar.setAttribute('aria-controls', grid.id);
|
|
23
|
+
bar.setAttribute('aria-expanded', 'false');
|
|
24
|
+
bar.title = grid.querySelector('.calls').title;
|
|
25
|
+
return bar;
|
|
26
|
+
});
|
|
27
|
+
// Each method's def line, lit from its def to its bar, so the
|
|
28
|
+
// two read as one bracket around the method.
|
|
29
|
+
var bands = grids.map(function (grid) {
|
|
30
|
+
var band = document.createElement('div');
|
|
31
|
+
band.className = 'methodband';
|
|
32
|
+
band.setAttribute('data-status', grid.getAttribute('data-status'));
|
|
33
|
+
return band;
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
// The line-number button that opens tree.
|
|
37
|
+
function buttonOf(tree) {
|
|
38
|
+
return document.querySelector('button.lnum[aria-controls="' + tree.id + '"]');
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// The bar that opens grid.
|
|
42
|
+
function barOf(grid) {
|
|
43
|
+
return bars[grids.indexOf(grid)];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// The number span of line n.
|
|
47
|
+
function lineno(n) {
|
|
48
|
+
return document.getElementById('L' + n);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Element's top, middle and bottom relative to within's top.
|
|
52
|
+
function spot(element, within) {
|
|
53
|
+
var rect = element.getBoundingClientRect();
|
|
54
|
+
var top = rect.top - within.getBoundingClientRect().top;
|
|
55
|
+
return { top: top, middle: top + rect.height / 2, bottom: top + rect.height };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Opens panel, a tree or a grid, alone; null closes all. Any
|
|
59
|
+
// path shown or pinned goes with the grid it was in.
|
|
60
|
+
function openOnly(panel) {
|
|
61
|
+
pin(null);
|
|
62
|
+
clear();
|
|
63
|
+
trees.forEach(function (tree) {
|
|
64
|
+
tree.hidden = tree !== panel;
|
|
65
|
+
buttonOf(tree).setAttribute('aria-expanded', tree === panel ? 'true' : 'false');
|
|
66
|
+
});
|
|
67
|
+
grids.forEach(function (grid, i) {
|
|
68
|
+
grid.hidden = grid !== panel;
|
|
69
|
+
barOf(grid).setAttribute('aria-expanded', grid === panel ? 'true' : 'false');
|
|
70
|
+
bands[i].classList.toggle('open', grid === panel);
|
|
71
|
+
});
|
|
72
|
+
var side = !panel ? '' : panel.classList.contains('tree') ? 'cc' : 'pc';
|
|
73
|
+
document.body.setAttribute('data-open', side);
|
|
74
|
+
slide();
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// How long a rail takes to open or close, as style.css has it.
|
|
78
|
+
var SLIDE_MS = 250;
|
|
79
|
+
|
|
80
|
+
// Places everything on every frame while the rails slide, as
|
|
81
|
+
// the bars and bands follow the code across.
|
|
82
|
+
function slide() {
|
|
83
|
+
var end = performance.now() + SLIDE_MS + 50;
|
|
84
|
+
function frame(now) {
|
|
85
|
+
place();
|
|
86
|
+
if (now < end) { requestAnimationFrame(frame); }
|
|
87
|
+
}
|
|
88
|
+
requestAnimationFrame(frame);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Sets an open tree's top so its first site's row, naming its
|
|
92
|
+
// operator and path counts, is level with the first line of
|
|
93
|
+
// its site's code.
|
|
94
|
+
function placeTree(tree) {
|
|
95
|
+
var block = buttonOf(tree).closest('.block');
|
|
96
|
+
tree.style.top = '0px';
|
|
97
|
+
var line = spot(block.querySelector('.src'), ccRail).middle;
|
|
98
|
+
var row = spot(tree.querySelector('.site .row'), tree).middle;
|
|
99
|
+
tree.style.top = (line - row) + 'px';
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Sets an open grid's top level with its def, its calls row
|
|
103
|
+
// first and its decision rows straight after, with no gaps;
|
|
104
|
+
// each row's label names its decision's line.
|
|
105
|
+
// When the def has scrolled up under the sticky header, the
|
|
106
|
+
// grid slides down to stay just below it, but never past its
|
|
107
|
+
// method's last line.
|
|
108
|
+
function placeGrid(grid) {
|
|
109
|
+
var rows = Array.prototype.slice.call(grid.querySelectorAll('.prow'));
|
|
110
|
+
var first = spot(lineno(grid.getAttribute('data-first')), pcRail);
|
|
111
|
+
var last = spot(lineno(grid.getAttribute('data-last')), pcRail);
|
|
112
|
+
var height = rows[0].getBoundingClientRect().height + 2;
|
|
113
|
+
var tall = rows.length * height;
|
|
114
|
+
var head = document.querySelector('.head').getBoundingClientRect().bottom;
|
|
115
|
+
var below = head - pcRail.getBoundingClientRect().top + 8;
|
|
116
|
+
var top = first.middle - height / 2;
|
|
117
|
+
top = Math.max(top, Math.min(below, last.bottom - tall));
|
|
118
|
+
grid.style.top = top + 'px';
|
|
119
|
+
grid.style.height = tall + 'px';
|
|
120
|
+
rows.forEach(function (row, i) {
|
|
121
|
+
row.style.top = (i * height) + 'px';
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Places panel, a tree or a grid.
|
|
126
|
+
function placeOne(panel) {
|
|
127
|
+
if (panel.classList.contains('tree')) { placeTree(panel); } else { placeGrid(panel); }
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Sets each bar beside the lines from its def to its end, on
|
|
131
|
+
// the code pane's right edge, half into the gap before the
|
|
132
|
+
// rail.
|
|
133
|
+
// How many methods' lines enclose grid's: its bar sits one
|
|
134
|
+
// lane further in for each, like nested brackets.
|
|
135
|
+
function laneOf(grid) {
|
|
136
|
+
var first = Number(grid.getAttribute('data-first'));
|
|
137
|
+
var last = Number(grid.getAttribute('data-last'));
|
|
138
|
+
return grids.filter(function (other) {
|
|
139
|
+
var f = Number(other.getAttribute('data-first'));
|
|
140
|
+
var l = Number(other.getAttribute('data-last'));
|
|
141
|
+
return other !== grid && f <= first && last <= l;
|
|
142
|
+
}).length;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// The x of a bar's middle, from the code pane's right edge,
|
|
146
|
+
// for a method in lane.
|
|
147
|
+
function barInset(lane) {
|
|
148
|
+
return lane * 20;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Sets each method's band over its def line, from the def's
|
|
152
|
+
// column to its bar; it scrolls with the code, so is placed
|
|
153
|
+
// again whenever the code scrolls.
|
|
154
|
+
function placeBands() {
|
|
155
|
+
var pane = code.getBoundingClientRect();
|
|
156
|
+
bands.forEach(function (band, i) {
|
|
157
|
+
var grid = grids[i];
|
|
158
|
+
var src = srcOf(grid.getAttribute('data-first'));
|
|
159
|
+
var text = src.firstChild;
|
|
160
|
+
var col = Number(grid.getAttribute('data-col'));
|
|
161
|
+
var range = document.createRange();
|
|
162
|
+
range.setStart(text, Math.min(col, text.length));
|
|
163
|
+
range.collapse(true);
|
|
164
|
+
var left = range.getBoundingClientRect().left - pane.left + code.scrollLeft;
|
|
165
|
+
var right = code.scrollLeft + code.clientWidth - barInset(laneOf(grid));
|
|
166
|
+
var line = src.getBoundingClientRect();
|
|
167
|
+
band.style.left = (left - 4) + 'px';
|
|
168
|
+
band.style.width = Math.max(0, right - left + 4) + 'px';
|
|
169
|
+
band.style.top = (line.top - pane.top + code.scrollTop) + 'px';
|
|
170
|
+
band.style.height = line.height + 'px';
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function placeBars() {
|
|
175
|
+
var edge = code.getBoundingClientRect().right - box.getBoundingClientRect().left;
|
|
176
|
+
placeBands();
|
|
177
|
+
bars.forEach(function (bar, i) {
|
|
178
|
+
bar.style.left = (edge - 8 - barInset(laneOf(grids[i]))) + 'px';
|
|
179
|
+
var first = spot(lineno(grids[i].getAttribute('data-first')), box);
|
|
180
|
+
var last = spot(lineno(grids[i].getAttribute('data-last')), box);
|
|
181
|
+
bar.style.top = first.top + 'px';
|
|
182
|
+
bar.style.height = (last.bottom - first.top) + 'px';
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Places the bars and whichever tree or grid is open.
|
|
187
|
+
function place() {
|
|
188
|
+
placeBars();
|
|
189
|
+
trees.concat(grids).forEach(function (panel) {
|
|
190
|
+
if (!panel.hidden) { placeOne(panel); }
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// The trees in the left rail, the grids in the right, the bars
|
|
195
|
+
// on the code's right edge, nothing open.
|
|
196
|
+
function beside() {
|
|
197
|
+
box.insertBefore(ccRail, code);
|
|
198
|
+
box.appendChild(pcRail);
|
|
199
|
+
trees.forEach(function (tree) { ccRail.appendChild(tree); });
|
|
200
|
+
grids.forEach(function (grid) { pcRail.appendChild(grid); });
|
|
201
|
+
bars.forEach(function (bar) { box.appendChild(bar); });
|
|
202
|
+
bands.forEach(function (band) { code.appendChild(band); });
|
|
203
|
+
code.addEventListener('scroll', placeBands);
|
|
204
|
+
openOnly(null);
|
|
205
|
+
placeBars();
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// The code span of line n, whether a plain line or one of a
|
|
209
|
+
// site's lines.
|
|
210
|
+
function srcOf(n) {
|
|
211
|
+
var number = lineno(n);
|
|
212
|
+
if (!number) { return null; }
|
|
213
|
+
var button = number.closest('button.lnum');
|
|
214
|
+
if (!button) { return number.parentElement.querySelector('.src'); }
|
|
215
|
+
var index = Array.prototype.indexOf.call(button.querySelectorAll('.lineno'), number);
|
|
216
|
+
return button.closest('.block').querySelectorAll('.srcs > .src')[index];
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// [first, last] from a "first,last" attribute, null for "".
|
|
220
|
+
function span(text) {
|
|
221
|
+
return text ? text.split(',').map(Number) : null;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
var lit = [];
|
|
225
|
+
var chips = [];
|
|
226
|
+
var hot = [];
|
|
227
|
+
|
|
228
|
+
// Lights the code of every line in span, [first, last].
|
|
229
|
+
function light(lines) {
|
|
230
|
+
if (!lines) { return; }
|
|
231
|
+
for (var n = lines[0]; n <= lines[1]; n++) {
|
|
232
|
+
var src = srcOf(n);
|
|
233
|
+
if (src) { src.classList.add('lit'); lit.push(src); }
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Puts a T or F chip in the gutter of line n's row.
|
|
238
|
+
function chip(n, text) {
|
|
239
|
+
var number = lineno(n);
|
|
240
|
+
if (!number) { return; }
|
|
241
|
+
var nav = number.closest('.line, .block').querySelector('.nav');
|
|
242
|
+
nav.setAttribute('data-chip', text);
|
|
243
|
+
chips.push(nav);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// Undoes the lights and chips of the last path shown.
|
|
247
|
+
function clear() {
|
|
248
|
+
lit.forEach(function (src) { src.classList.remove('lit'); });
|
|
249
|
+
chips.forEach(function (nav) { nav.removeAttribute('data-chip'); });
|
|
250
|
+
hot.forEach(function (cell) { cell.classList.remove('hot'); });
|
|
251
|
+
lit = [];
|
|
252
|
+
chips = [];
|
|
253
|
+
hot = [];
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Shows in the code the path in column col of grid: for each
|
|
257
|
+
// decision it makes, its if lit with the arm taken and its
|
|
258
|
+
// end, T lighting the then arm, F the else (none without
|
|
259
|
+
// one), and a T or F chip in the gutter. Decisions after a
|
|
260
|
+
// path leaves early stay unlit.
|
|
261
|
+
function show(grid, col) {
|
|
262
|
+
clear();
|
|
263
|
+
grid.querySelectorAll('.prow').forEach(function (row) {
|
|
264
|
+
var cell = row.querySelectorAll('.pcell')[col];
|
|
265
|
+
cell.classList.add('hot');
|
|
266
|
+
hot.push(cell);
|
|
267
|
+
if (row.classList.contains('calls') || cell.classList.contains('ended')) { return; }
|
|
268
|
+
var taken = cell.classList.contains('true');
|
|
269
|
+
light(span(row.getAttribute('data-head')));
|
|
270
|
+
light(span(row.getAttribute(taken ? 'data-then' : 'data-else')));
|
|
271
|
+
light(span(row.getAttribute('data-end')));
|
|
272
|
+
chip(Number(row.getAttribute('data-line')), taken ? 'T' : 'F');
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// The pinned path, { grid, col }, shown whenever no other is
|
|
277
|
+
// hovered; null when none is pinned.
|
|
278
|
+
var pinned = null;
|
|
279
|
+
|
|
280
|
+
// The grid and column of a cell.
|
|
281
|
+
function columnOf(cell) {
|
|
282
|
+
var cells = Array.prototype.slice.call(cell.parentElement.querySelectorAll('.pcell'));
|
|
283
|
+
return { grid: cell.closest('.pathgrid'), col: cells.indexOf(cell) };
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// Pins column, or unpins with null, outlining the pinned
|
|
287
|
+
// column's cells.
|
|
288
|
+
function pin(column) {
|
|
289
|
+
document.querySelectorAll('.pcell.pinned').forEach(function (cell) {
|
|
290
|
+
cell.classList.remove('pinned');
|
|
291
|
+
});
|
|
292
|
+
pinned = column;
|
|
293
|
+
if (!column) { return; }
|
|
294
|
+
column.grid.querySelectorAll('.prow').forEach(function (row) {
|
|
295
|
+
row.querySelectorAll('.pcell')[column.col].classList.add('pinned');
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// Shows the pinned path, or nothing when none is pinned.
|
|
300
|
+
function showPinned() {
|
|
301
|
+
if (pinned) { show(pinned.grid, pinned.col); } else { clear(); }
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
pcRail.addEventListener('mouseover', function (event) {
|
|
305
|
+
var cell = event.target.closest('.pathgrid .pcell');
|
|
306
|
+
if (!cell) { return; }
|
|
307
|
+
var column = columnOf(cell);
|
|
308
|
+
show(column.grid, column.col);
|
|
309
|
+
});
|
|
310
|
+
pcRail.addEventListener('mouseleave', showPinned);
|
|
311
|
+
|
|
312
|
+
// The code spans whose characters are marked, with the text
|
|
313
|
+
// each had before.
|
|
314
|
+
var markedSrcs = [];
|
|
315
|
+
|
|
316
|
+
// The length of text's leading whitespace.
|
|
317
|
+
function indentOf(text) {
|
|
318
|
+
return text.match(/^\s*/)[0].length;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// Puts back every span marked as it was, site tints and all.
|
|
322
|
+
function unmarkChars() {
|
|
323
|
+
markedSrcs.forEach(function (entry) { entry.src.innerHTML = entry.html; });
|
|
324
|
+
markedSrcs = [];
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// Tints each site's own characters by its status, gaps when
|
|
328
|
+
// any of its paths was never taken, rather than its whole
|
|
329
|
+
// lines; where sites overlap, gaps wins.
|
|
330
|
+
function tintSites() {
|
|
331
|
+
var perLine = {};
|
|
332
|
+
document.querySelectorAll('.tree .site').forEach(function (site) {
|
|
333
|
+
var row = site.querySelector('.row[data-at]');
|
|
334
|
+
if (!row) { return; }
|
|
335
|
+
var at = row.getAttribute('data-at').split(',').map(Number);
|
|
336
|
+
var gaps = site.querySelector('.count.never') !== null;
|
|
337
|
+
for (var n = at[0]; n <= at[2]; n++) {
|
|
338
|
+
(perLine[n] = perLine[n] || []).push({
|
|
339
|
+
from: n === at[0] ? at[1] : -1, to: n === at[2] ? at[3] : Infinity,
|
|
340
|
+
status: gaps ? 'gaps' : 'covered'
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
});
|
|
344
|
+
Object.keys(perLine).forEach(function (n) {
|
|
345
|
+
var src = srcOf(n);
|
|
346
|
+
if (!src) { return; }
|
|
347
|
+
var text = src.textContent;
|
|
348
|
+
var marks = text.split('').map(function () { return null; });
|
|
349
|
+
perLine[n].forEach(function (span) {
|
|
350
|
+
// A continuation line (from -1) is tinted from its first
|
|
351
|
+
// character after the indent.
|
|
352
|
+
var from = span.from < 0 ? indentOf(text) : span.from;
|
|
353
|
+
for (var i = from; i < Math.min(span.to, text.length); i++) {
|
|
354
|
+
if (marks[i] !== 'gaps') { marks[i] = span.status; }
|
|
355
|
+
}
|
|
356
|
+
});
|
|
357
|
+
src.textContent = '';
|
|
358
|
+
var i = 0;
|
|
359
|
+
while (i < text.length) {
|
|
360
|
+
var j = i;
|
|
361
|
+
while (j < text.length && marks[j] === marks[i]) { j++; }
|
|
362
|
+
var part = text.slice(i, j);
|
|
363
|
+
if (marks[i]) {
|
|
364
|
+
var mark = document.createElement('mark');
|
|
365
|
+
mark.className = 'site';
|
|
366
|
+
mark.setAttribute('data-status', marks[i]);
|
|
367
|
+
mark.textContent = part;
|
|
368
|
+
src.appendChild(mark);
|
|
369
|
+
} else {
|
|
370
|
+
src.appendChild(document.createTextNode(part));
|
|
371
|
+
}
|
|
372
|
+
i = j;
|
|
373
|
+
}
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// Marks the characters from line l1, column c1 to line l2,
|
|
378
|
+
// column c2, each line's part in a mark of its own.
|
|
379
|
+
function markChars(l1, c1, l2, c2) {
|
|
380
|
+
unmarkChars();
|
|
381
|
+
for (var n = l1; n <= l2; n++) {
|
|
382
|
+
var src = srcOf(n);
|
|
383
|
+
if (!src) { continue; }
|
|
384
|
+
var text = src.textContent;
|
|
385
|
+
var from = n === l1 ? c1 : indentOf(text);
|
|
386
|
+
var to = n === l2 ? c2 : text.length;
|
|
387
|
+
markedSrcs.push({ src: src, html: src.innerHTML });
|
|
388
|
+
var mark = document.createElement('mark');
|
|
389
|
+
mark.className = 'chars';
|
|
390
|
+
mark.textContent = text.slice(from, to);
|
|
391
|
+
src.textContent = text.slice(0, from);
|
|
392
|
+
src.appendChild(mark);
|
|
393
|
+
src.appendChild(document.createTextNode(text.slice(to)));
|
|
394
|
+
}
|
|
395
|
+
reveal(document.querySelectorAll('mark.chars'));
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// The width of the code pane's left columns (gap arrows and
|
|
399
|
+
// line numbers), which code scrolls under.
|
|
400
|
+
var GUTTER = 108;
|
|
401
|
+
|
|
402
|
+
// Scrolls the code pane sideways so all of marks are in view,
|
|
403
|
+
// their start kept in view when they are wider than the pane.
|
|
404
|
+
function reveal(marks) {
|
|
405
|
+
if (marks.length === 0) { return; }
|
|
406
|
+
var pane = code.getBoundingClientRect();
|
|
407
|
+
var left = Infinity;
|
|
408
|
+
var right = -Infinity;
|
|
409
|
+
marks.forEach(function (mark) {
|
|
410
|
+
var rect = mark.getBoundingClientRect();
|
|
411
|
+
left = Math.min(left, rect.left);
|
|
412
|
+
right = Math.max(right, rect.right);
|
|
413
|
+
});
|
|
414
|
+
var shift = 0;
|
|
415
|
+
if (right > pane.right - 8) { shift = right - pane.right + 8; }
|
|
416
|
+
if (left - shift < pane.left + GUTTER) { shift = left - pane.left - GUTTER; }
|
|
417
|
+
if (shift !== 0) { code.scrollBy({ left: shift, behavior: 'smooth' }); }
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// Hovering a tree's row marks in the code the characters of
|
|
421
|
+
// its operand, or of its operator's whole condition.
|
|
422
|
+
var markedRow = null;
|
|
423
|
+
ccRail.addEventListener('mouseover', function (event) {
|
|
424
|
+
var row = event.target.closest('.tree .row[data-at]');
|
|
425
|
+
if (row === markedRow) { return; }
|
|
426
|
+
markedRow = row;
|
|
427
|
+
if (!row) { unmarkChars(); return; }
|
|
428
|
+
var at = row.getAttribute('data-at').split(',').map(Number);
|
|
429
|
+
markChars(at[0], at[1], at[2], at[3]);
|
|
430
|
+
});
|
|
431
|
+
ccRail.addEventListener('mouseleave', function () {
|
|
432
|
+
markedRow = null;
|
|
433
|
+
unmarkChars();
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
// A click on a cell pins its column, or unpins it when it is
|
|
437
|
+
// the one pinned.
|
|
438
|
+
pcRail.addEventListener('click', function (event) {
|
|
439
|
+
var cell = event.target.closest('.pathgrid .pcell');
|
|
440
|
+
if (!cell) { return; }
|
|
441
|
+
var column = columnOf(cell);
|
|
442
|
+
var same = pinned && pinned.grid === column.grid && pinned.col === column.col;
|
|
443
|
+
pin(same ? null : column);
|
|
444
|
+
show(column.grid, column.col);
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
document.addEventListener('keydown', function (event) {
|
|
448
|
+
if (event.key === 'Escape' && pinned) { pin(null); clear(); }
|
|
449
|
+
});
|
|
450
|
+
|
|
451
|
+
// Opening one tree or grid closes the others; a click on a
|
|
452
|
+
// site's code acts as one on its button; a gap arrow opens
|
|
453
|
+
// the tree of the line it jumps to.
|
|
454
|
+
document.addEventListener('click', function (event) {
|
|
455
|
+
var bar = event.target.closest('.pathbar');
|
|
456
|
+
if (bar) {
|
|
457
|
+
var grid = document.getElementById(bar.getAttribute('aria-controls'));
|
|
458
|
+
openOnly(grid.hidden ? grid : null);
|
|
459
|
+
return;
|
|
460
|
+
}
|
|
461
|
+
var line = event.target.closest('.line');
|
|
462
|
+
if (line && !event.target.closest('button, a')) {
|
|
463
|
+
// A click on a method's def line acts as one on its bar.
|
|
464
|
+
var number = line.querySelector('.lineno').getAttribute('data-line');
|
|
465
|
+
var method = grids.filter(function (g) { return g.getAttribute('data-first') === number; })[0];
|
|
466
|
+
if (method) { openOnly(method.hidden ? method : null); }
|
|
467
|
+
return;
|
|
468
|
+
}
|
|
469
|
+
var block = event.target.closest('.block');
|
|
470
|
+
if (block && !event.target.closest('button, a')) {
|
|
471
|
+
block.querySelector('button[aria-controls]').click();
|
|
472
|
+
return;
|
|
473
|
+
}
|
|
474
|
+
var button = event.target.closest('button.lnum');
|
|
475
|
+
if (button) {
|
|
476
|
+
var tree = document.getElementById(button.getAttribute('aria-controls'));
|
|
477
|
+
openOnly(tree.hidden ? null : tree);
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
480
|
+
var arrow = event.target.closest('.nav a, a.jump');
|
|
481
|
+
if (arrow) {
|
|
482
|
+
var target = document.getElementById('tree-' + arrow.hash.slice(2));
|
|
483
|
+
if (target) { openOnly(target); }
|
|
484
|
+
}
|
|
485
|
+
});
|
|
486
|
+
|
|
487
|
+
window.addEventListener('resize', place);
|
|
488
|
+
window.addEventListener('scroll', function () {
|
|
489
|
+
grids.forEach(function (grid) { if (!grid.hidden) { placeGrid(grid); } });
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
// Puts every site's jump in the header into one row, labelled
|
|
493
|
+
// Conditions, in line order; each chip's edge shows its status.
|
|
494
|
+
function oneJumpRow() {
|
|
495
|
+
var lists = document.querySelectorAll('.jump-list');
|
|
496
|
+
if (lists.length === 0) { return; }
|
|
497
|
+
var jumps = Array.prototype.slice.call(document.querySelectorAll('a.jump'));
|
|
498
|
+
jumps.sort(function (a, b) { return Number(a.textContent) - Number(b.textContent); });
|
|
499
|
+
var row = lists[0];
|
|
500
|
+
row.removeAttribute('data-jumps');
|
|
501
|
+
row.querySelector('.jump-label').textContent = 'Conditions';
|
|
502
|
+
jumps.forEach(function (jump) { row.appendChild(jump); });
|
|
503
|
+
for (var i = 1; i < lists.length; i++) { lists[i].remove(); }
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
// The cell kind that draws each of a tree's branch glyphs.
|
|
507
|
+
var BRANCH_CELLS = { '│': 'v', '├': 't', '└': 'l', '─': 'h' };
|
|
508
|
+
|
|
509
|
+
// Redraws each tree's branch glyphs as cells a whole row tall,
|
|
510
|
+
// whose lines run edge to edge, so the branches join from row
|
|
511
|
+
// to row as the glyphs, shorter than a row, cannot.
|
|
512
|
+
function drawBranches() {
|
|
513
|
+
document.querySelectorAll('.tree .branch').forEach(function (branch) {
|
|
514
|
+
var text = branch.textContent;
|
|
515
|
+
branch.setAttribute('aria-hidden', 'true');
|
|
516
|
+
branch.textContent = '';
|
|
517
|
+
text.split('').forEach(function (ch) {
|
|
518
|
+
var cell = document.createElement('span');
|
|
519
|
+
cell.className = 'bx';
|
|
520
|
+
cell.setAttribute('data-bx', BRANCH_CELLS[ch] || 's');
|
|
521
|
+
branch.appendChild(cell);
|
|
522
|
+
});
|
|
523
|
+
});
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
document.body.setAttribute('data-layout', 'beside');
|
|
527
|
+
drawBranches();
|
|
528
|
+
oneJumpRow();
|
|
529
|
+
tintSites();
|
|
530
|
+
beside();
|
|
531
|
+
})();
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// Sorts the files table by the clicked column's data-sort values;
|
|
2
|
+
// a second click on the same column reverses the order.
|
|
3
|
+
(function () {
|
|
4
|
+
var table = document.getElementById('files');
|
|
5
|
+
var body = table.tBodies[0];
|
|
6
|
+
var state = { column: -1, ascending: true };
|
|
7
|
+
|
|
8
|
+
table.querySelectorAll('thead button').forEach(function (button) {
|
|
9
|
+
button.addEventListener('click', function () {
|
|
10
|
+
var column = Number(button.dataset.column);
|
|
11
|
+
var numeric = button.dataset.type === 'number';
|
|
12
|
+
state.ascending = state.column === column ? !state.ascending : true;
|
|
13
|
+
state.column = column;
|
|
14
|
+
var rows = Array.from(body.rows);
|
|
15
|
+
rows.sort(function (a, b) {
|
|
16
|
+
var x = a.cells[column].dataset.sort;
|
|
17
|
+
var y = b.cells[column].dataset.sort;
|
|
18
|
+
var order = numeric ? Number(x) - Number(y) : x.localeCompare(y);
|
|
19
|
+
return state.ascending ? order : -order;
|
|
20
|
+
});
|
|
21
|
+
rows.forEach(function (row) { body.appendChild(row); });
|
|
22
|
+
table.querySelectorAll('thead th').forEach(function (th, i) {
|
|
23
|
+
var sort = state.ascending ? 'ascending' : 'descending';
|
|
24
|
+
th.setAttribute('aria-sort', i === column ? sort : 'none');
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
})();
|