about summary refs log tree commit diff
path: root/src/librustdoc/html/static
diff options
context:
space:
mode:
authorWill Crichton <wcrichto@cs.stanford.edu>2021-05-09 16:22:22 -0700
committerWill Crichton <wcrichto@cs.stanford.edu>2021-10-06 19:44:47 -0700
commit4b3f82ad0321b8f2e2630b74bbc526ffb8fa5bda (patch)
tree4d10d3906b55b93f95d813d1e859ef92592712a6 /src/librustdoc/html/static
parent0eabf25b90396dead0b2a1aaa275af18a1ae6008 (diff)
downloadrust-4b3f82ad0321b8f2e2630b74bbc526ffb8fa5bda.tar.gz
rust-4b3f82ad0321b8f2e2630b74bbc526ffb8fa5bda.zip
Add updated support for example-analyzer
Move rendering of examples into

Finalize design

Cleanup, rename found -> scraped

Softer yellow

Clean up dead code

Document scrape_examples

More simplification and documentation

Remove extra css

Test
Diffstat (limited to 'src/librustdoc/html/static')
-rw-r--r--src/librustdoc/html/static/css/rustdoc.css107
-rw-r--r--src/librustdoc/html/static/js/main.js196
2 files changed, 303 insertions, 0 deletions
diff --git a/src/librustdoc/html/static/css/rustdoc.css b/src/librustdoc/html/static/css/rustdoc.css
index 5d33681847a..ca8db4530f3 100644
--- a/src/librustdoc/html/static/css/rustdoc.css
+++ b/src/librustdoc/html/static/css/rustdoc.css
@@ -1970,3 +1970,110 @@ details.undocumented[open] > summary::before {
 		margin-left: 12px;
 	}
 }
+
+/* This part is for the new "examples" components */
+
+.scraped-example:not(.expanded) .code-wrapper pre.line-numbers, .scraped-example:not(.expanded) .code-wrapper .example-wrap pre.rust {
+	overflow: hidden;
+	height: 240px;
+}
+
+.scraped-example .code-wrapper .prev {
+	position: absolute;
+	top: 0.25em;
+	right: 2.25em;
+	z-index: 100;
+	cursor: pointer;
+}
+
+.scraped-example .code-wrapper .next {
+	position: absolute;
+	top: 0.25em;
+	right: 1.25em;
+	z-index: 100;
+	cursor: pointer;
+}
+
+.scraped-example .code-wrapper .expand {
+	position: absolute;
+	top: 0.25em;
+	right: 0.25em;
+	z-index: 100;
+	cursor: pointer;
+}
+
+.scraped-example .code-wrapper {
+	position: relative;
+	display: flex;
+	flex-direction: row;
+	flex-wrap: wrap;
+	width: 100%;
+}
+
+.scraped-example:not(.expanded) .code-wrapper:before {
+	content: " ";
+	width: 100%;
+	height: 20px;
+	position: absolute;
+	z-index: 100;
+	top: 0;
+	background: linear-gradient(to bottom, rgba(255, 255, 255, 1), rgba(255, 255, 255, 0));
+}
+
+.scraped-example:not(.expanded) .code-wrapper:after {
+	content: " ";
+	width: 100%;
+	height: 20px;
+	position: absolute;
+	z-index: 100;
+	bottom: 0;
+	background: linear-gradient(to top, rgba(255, 255, 255, 1), rgba(255, 255, 255, 0));
+}
+
+.scraped-example:not(.expanded) .code-wrapper {
+	overflow: hidden;
+	height: 240px;
+}
+
+.scraped-example .code-wrapper .line-numbers {
+	margin: 0;
+	padding: 14px 0;
+}
+
+.scraped-example .code-wrapper .line-numbers span {
+	padding: 0 14px;
+}
+
+.scraped-example .code-wrapper .example-wrap {
+	flex: 1;
+	overflow-x: auto;
+	overflow-y: hidden;
+	margin-bottom: 0;
+}
+
+.scraped-example .code-wrapper .example-wrap pre.rust {
+	overflow-x: inherit;
+	width: inherit;
+	overflow-y: hidden;
+}
+
+.scraped-example .line-numbers span.highlight {
+	background: #f6fdb0;
+}
+
+.scraped-example .example-wrap .rust span.highlight {
+	background: #f6fdb0;
+}
+
+.more-scraped-examples {
+	padding-left: 10px;
+	border-left: 1px solid #ccc;
+}
+
+.toggle-examples .collapse-toggle {
+	position: relative;
+}
+
+.toggle-examples a {
+	color: #999 !important; // FIXME(wcrichto): why is important needed
+}
diff --git a/src/librustdoc/html/static/js/main.js b/src/librustdoc/html/static/js/main.js
index e396fd9d288..5ac00ff244a 100644
--- a/src/librustdoc/html/static/js/main.js
+++ b/src/librustdoc/html/static/js/main.js
@@ -979,6 +979,202 @@ function hideThemeButtonState() {
     onHashChange(null);
     window.addEventListener("hashchange", onHashChange);
     searchState.setup();
+
+    /////// EXAMPLE ANALYZER
+
+    // Merge the full set of [from, to] offsets into a minimal set of non-overlapping
+    // [from, to] offsets.
+    // NB: This is such a archetypal software engineering interview question that
+    // I can't believe I actually had to write it. Yes, it's O(N) in the input length --
+    // but it does assume a sorted input!
+    function distinctRegions(locs) {
+        var start = -1;
+        var end = -1;
+        var output = [];
+        for (var i = 0; i < locs.length; i++) {
+            var loc = locs[i];
+            if (loc[0] > end) {
+                if (end > 0) {
+                    output.push([start, end]);
+                }
+                start = loc[0];
+                end = loc[1];
+            } else {
+                end = Math.max(end, loc[1]);
+            }
+        }
+        if (end > 0) {
+            output.push([start, end]);
+        }
+        return output;
+    }
+
+    function convertLocsStartsToLineOffsets(code, locs) {
+        locs = distinctRegions(locs.slice(0).sort(function (a, b) {
+            return a[0] === b[0] ? a[1] - b[1] : a[0] - b[0];
+        })); // sort by start; use end if start is equal.
+        var codeLines = code.split("\n");
+        var lineIndex = 0;
+        var totalOffset = 0;
+        var output = [];
+
+        while (locs.length > 0 && lineIndex < codeLines.length) {
+            var lineLength = codeLines[lineIndex].length + 1; // +1 here and later is due to omitted \n
+            while (locs.length > 0 && totalOffset + lineLength > locs[0][0]) {
+                var endIndex = lineIndex;
+                var charsRemaining = locs[0][1] - totalOffset;
+                while (endIndex < codeLines.length && charsRemaining > codeLines[endIndex].length + 1) {
+                    charsRemaining -= codeLines[endIndex].length + 1;
+                    endIndex += 1;
+                }
+                output.push({
+                    from: [lineIndex, locs[0][0] - totalOffset],
+                    to: [endIndex, charsRemaining]
+                });
+                locs.shift();
+            }
+            lineIndex++;
+            totalOffset += lineLength;
+        }
+        return output;
+    }
+
+    // inserts str into html, *but* calculates idx by eliding anything in html that's not in raw.
+    // ideally this would work by walking the element tree...but this is good enough for now.
+    function insertStrAtRawIndex(raw, html, idx, str) {
+        if (idx > raw.length) {
+            return html;
+        }
+        if (idx == raw.length) {
+            return html + str;
+        }
+        var rawIdx = 0;
+        var htmlIdx = 0;
+        while (rawIdx < idx && rawIdx < raw.length) {
+            while (raw[rawIdx] !== html[htmlIdx] && htmlIdx < html.length) {
+                htmlIdx++;
+            }
+            rawIdx++;
+            htmlIdx++;
+        }
+        return html.substring(0, htmlIdx) + str + html.substr(htmlIdx);
+    }
+
+    // Scroll code block to put the given code location in the middle of the viewer
+    function scrollToLoc(elt, loc) {
+        var wrapper = elt.querySelector(".code-wrapper");
+        var halfHeight = wrapper.offsetHeight / 2;
+        var lines = elt.querySelector('.line-numbers');
+        var offsetMid = (lines.children[loc.from[0]].offsetTop + lines.children[loc.to[0]].offsetTop) / 2;
+        var scrollOffset = offsetMid - halfHeight;
+        lines.scrollTo(0, scrollOffset);
+        elt.querySelector(".rust").scrollTo(0, scrollOffset);
+    }
+
+    function updateScrapedExample(example) {
+        var code = example.attributes.getNamedItem("data-code").textContent;
+        var codeLines = code.split("\n");
+        var locs = JSON.parse(example.attributes.getNamedItem("data-locs").textContent);
+        locs = convertLocsStartsToLineOffsets(code, locs);
+
+        // Add call-site highlights to code listings
+        var litParent = example.querySelector('.example-wrap pre.rust');
+        var litHtml = litParent.innerHTML.split("\n");
+        onEach(locs, function (loc) {
+            for (var i = loc.from[0]; i < loc.to[0] + 1; i++) {
+                addClass(example.querySelector('.line-numbers').children[i], "highlight");
+            }
+            litHtml[loc.to[0]] = insertStrAtRawIndex(
+                codeLines[loc.to[0]],
+                litHtml[loc.to[0]],
+                loc.to[1],
+                "</span>");
+            litHtml[loc.from[0]] = insertStrAtRawIndex(
+                codeLines[loc.from[0]],
+                litHtml[loc.from[0]],
+                loc.from[1],
+                '<span class="highlight" data-loc="' + JSON.stringify(loc).replace(/"/g, "&quot;") + '">');
+        }, true); // do this backwards to avoid shifting later offsets
+        litParent.innerHTML = litHtml.join('\n');
+
+        // Toggle through list of examples in a given file
+        var locIndex = 0;
+        if (locs.length > 1) {
+            example.querySelector('.prev')
+                .addEventListener('click', function () {
+                    locIndex = (locIndex - 1 + locs.length) % locs.length;
+                    scrollToLoc(example, locs[locIndex]);
+                });
+            example.querySelector('.next')
+                .addEventListener('click', function () {
+                    locIndex = (locIndex + 1) % locs.length;
+                    scrollToLoc(example, locs[locIndex]);
+                });
+        } else {
+            example.querySelector('.prev').remove();
+            example.querySelector('.next').remove();
+        }
+
+        // Show full code on expansion
+        example.querySelector('.expand').addEventListener('click', function () {
+            if (hasClass(example, "expanded")) {
+                removeClass(example, "expanded");
+                scrollToLoc(example, locs[0]);
+            } else {
+                addClass(example, "expanded");
+            }
+        });
+
+        // Start with the first example in view
+        scrollToLoc(example, locs[0]);
+    }
+
+    function updateScrapedExamples() {
+        onEach(document.getElementsByClassName('scraped-example-list'), function (exampleSet) {
+            updateScrapedExample(exampleSet.querySelector(".small-section-header + .scraped-example"));
+        });
+
+        onEach(document.getElementsByClassName("more-scraped-examples"), function (more) {
+            var toggle = createSimpleToggle(true);
+            var label = "More examples";
+            var wrapper = createToggle(toggle, label, 14, "toggle-examples", false);
+            more.parentNode.insertBefore(wrapper, more);
+            var examples_init = false;
+
+            // Show additional examples on click
+            wrapper.onclick = function () {
+                if (hasClass(this, "collapsed")) {
+                    removeClass(this, "collapsed");
+                    onEachLazy(this.parentNode.getElementsByClassName("hidden"), function (x) {
+                        if (hasClass(x, "content") === false) {
+                            removeClass(x, "hidden");
+                            addClass(x, "x")
+                        }
+                    }, true);
+                    this.querySelector('.toggle-label').innerHTML = "Hide examples";
+                    this.querySelector('.inner').innerHTML = labelForToggleButton(false);
+                    if (!examples_init) {
+                        examples_init = true;
+                        onEach(more.getElementsByClassName('scraped-example'), updateScrapedExample);
+                    }
+                } else {
+                    addClass(this, "collapsed");
+                    onEachLazy(this.parentNode.getElementsByClassName("x"), function (x) {
+                        if (hasClass(x, "content") === false) {
+                            addClass(x, "hidden");
+                            removeClass(x, "x")
+                        }
+                    }, true);
+                    this.querySelector('.toggle-label').innerHTML = label;
+                    this.querySelector('.inner').innerHTML = labelForToggleButton(true);
+                }
+            };
+        });
+    }
+
+    var start = Date.now();
+    updateScrapedExamples();
+    console.log("updated examples took", Date.now() - start, "ms");
 }());
 
 (function () {