about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2018-12-04 07:00:19 +0000
committerbors <bors@rust-lang.org>2018-12-04 07:00:19 +0000
commit596e10fd3221e50e6cdb182b3ad76cc6d660f170 (patch)
treefa59c224d3b1c52eba2964e3ed54fe23b84643f6 /src
parent91d5d56c00d8e2926ccf856f14a4e52ef480d039 (diff)
parent82a7b6fde819e7e402ea9be6af14fe2c1575950e (diff)
downloadrust-596e10fd3221e50e6cdb182b3ad76cc6d660f170.tar.gz
rust-596e10fd3221e50e6cdb182b3ad76cc6d660f170.zip
Auto merge of #55707 - GuillaumeGomez:file-sidebar, r=QuietMisdreavus
Add source file sidebar

This is just a start currently but that gives a good overview of what it'll look like:

<img width="1440" alt="screenshot 2018-11-06 at 01 39 15" src="https://user-images.githubusercontent.com/3050060/48035592-05336180-e165-11e8-82e1-5ead0c345eb9.png">

r? @QuietMisdreavus
Diffstat (limited to 'src')
-rw-r--r--src/librustdoc/html/layout.rs8
-rw-r--r--src/librustdoc/html/render.rs94
-rw-r--r--src/librustdoc/html/static/main.js66
-rw-r--r--src/librustdoc/html/static/rustdoc.css74
-rw-r--r--src/librustdoc/html/static/source-script.js147
-rw-r--r--src/librustdoc/html/static/storage.js40
-rw-r--r--src/librustdoc/html/static/themes/dark.css19
-rw-r--r--src/librustdoc/html/static/themes/light.css19
-rw-r--r--src/librustdoc/html/static_files.rs6
-rw-r--r--src/test/rustdoc/source-file.rs15
10 files changed, 425 insertions, 63 deletions
diff --git a/src/librustdoc/html/layout.rs b/src/librustdoc/html/layout.rs
index 6868c7707ad..5263cfe7b0e 100644
--- a/src/librustdoc/html/layout.rs
+++ b/src/librustdoc/html/layout.rs
@@ -33,7 +33,7 @@ pub struct Page<'a> {
 
 pub fn render<T: fmt::Display, S: fmt::Display>(
     dst: &mut dyn io::Write, layout: &Layout, page: &Page, sidebar: &S, t: &T,
-    css_file_extension: bool, themes: &[PathBuf])
+    css_file_extension: bool, themes: &[PathBuf], extra_scripts: &[&str])
     -> io::Result<()>
 {
     write!(dst,
@@ -149,6 +149,7 @@ pub fn render<T: fmt::Display, S: fmt::Display>(
     </script>\
     <script src=\"{root_path}aliases.js\"></script>\
     <script src=\"{root_path}main{suffix}.js\"></script>\
+    {extra_scripts}\
     <script defer src=\"{root_path}search-index.js\"></script>\
 </body>\
 </html>",
@@ -192,6 +193,11 @@ pub fn render<T: fmt::Display, S: fmt::Display>(
                                     page.resource_suffix))
                    .collect::<String>(),
     suffix=page.resource_suffix,
+    extra_scripts=extra_scripts.iter().map(|e| {
+        format!("<script src=\"{root_path}{extra_script}.js\"></script>",
+                root_path=page.root_path,
+                extra_script=e)
+    }).collect::<String>(),
     )
 }
 
diff --git a/src/librustdoc/html/render.rs b/src/librustdoc/html/render.rs
index bfd846797ab..2c4ddf38e98 100644
--- a/src/librustdoc/html/render.rs
+++ b/src/librustdoc/html/render.rs
@@ -859,6 +859,11 @@ themePicker.onblur = handleThemeButtonsBlur;
     write_minify(cx.dst.join(&format!("settings{}.js", cx.shared.resource_suffix)),
                  static_files::SETTINGS_JS,
                  options.enable_minification)?;
+    if cx.shared.include_sources {
+        write_minify(cx.dst.join(&format!("source-script{}.js", cx.shared.resource_suffix)),
+                     static_files::sidebar::SOURCE_SCRIPT,
+                     options.enable_minification)?;
+    }
 
     {
         let mut data = format!("var resourcesSuffix = \"{}\";\n",
@@ -969,10 +974,88 @@ themePicker.onblur = handleThemeButtonsBlur;
         }
     }
 
+    use std::ffi::OsString;
+
+    #[derive(Debug)]
+    struct Hierarchy {
+        elem: OsString,
+        children: FxHashMap<OsString, Hierarchy>,
+        elems: FxHashSet<OsString>,
+    }
+
+    impl Hierarchy {
+        fn new(elem: OsString) -> Hierarchy {
+            Hierarchy {
+                elem,
+                children: FxHashMap::default(),
+                elems: FxHashSet::default(),
+            }
+        }
+
+        fn to_json_string(&self) -> String {
+            let mut subs: Vec<&Hierarchy> = self.children.values().collect();
+            subs.sort_unstable_by(|a, b| a.elem.cmp(&b.elem));
+            let mut files = self.elems.iter()
+                                      .map(|s| format!("\"{}\"",
+                                                       s.to_str()
+                                                        .expect("invalid osstring conversion")))
+                                      .collect::<Vec<_>>();
+            files.sort_unstable_by(|a, b| a.cmp(b));
+            // FIXME(imperio): we could avoid to generate "dirs" and "files" if they're empty.
+            format!("{{\"name\":\"{name}\",\"dirs\":[{subs}],\"files\":[{files}]}}",
+                    name=self.elem.to_str().expect("invalid osstring conversion"),
+                    subs=subs.iter().map(|s| s.to_json_string()).collect::<Vec<_>>().join(","),
+                    files=files.join(","))
+        }
+    }
+
+    if cx.shared.include_sources {
+        use std::path::Component;
+
+        let mut hierarchy = Hierarchy::new(OsString::new());
+        for source in cx.shared.local_sources.iter()
+                                             .filter_map(|p| p.0.strip_prefix(&cx.shared.src_root)
+                                                                .ok()) {
+            let mut h = &mut hierarchy;
+            let mut elems = source.components()
+                                  .filter_map(|s| {
+                                      match s {
+                                          Component::Normal(s) => Some(s.to_owned()),
+                                          _ => None,
+                                      }
+                                  })
+                                  .peekable();
+            loop {
+                let cur_elem = elems.next().expect("empty file path");
+                if elems.peek().is_none() {
+                    h.elems.insert(cur_elem);
+                    break;
+                } else {
+                    let e = cur_elem.clone();
+                    h.children.entry(cur_elem.clone()).or_insert_with(|| Hierarchy::new(e));
+                    h = h.children.get_mut(&cur_elem).expect("not found child");
+                }
+            }
+        }
+
+        let dst = cx.dst.join("source-files.js");
+        let (mut all_sources, _krates) = try_err!(collect(&dst, &krate.name, "sourcesIndex"), &dst);
+        all_sources.push(format!("sourcesIndex['{}'] = {};",
+                                 &krate.name,
+                                 hierarchy.to_json_string()));
+        all_sources.sort();
+        let mut w = try_err!(File::create(&dst), &dst);
+        try_err!(writeln!(&mut w,
+                          "var N = null;var sourcesIndex = {{}};\n{}",
+                          all_sources.join("\n")),
+                 &dst);
+    }
+
     // Update the search index
     let dst = cx.dst.join("search-index.js");
     let (mut all_indexes, mut krates) = try_err!(collect(&dst, &krate.name, "searchIndex"), &dst);
     all_indexes.push(search_index);
+
     // Sort the indexes by crate so the file will be generated identically even
     // with rustdoc running in parallel.
     all_indexes.sort();
@@ -1020,7 +1103,7 @@ themePicker.onblur = handleThemeButtonsBlur;
             try_err!(layout::render(&mut w, &cx.shared.layout,
                                     &page, &(""), &content,
                                     cx.shared.css_file_extension.is_some(),
-                                    &cx.shared.themes), &dst);
+                                    &cx.shared.themes, &[]), &dst);
             try_err!(w.flush(), &dst);
         }
     }
@@ -1292,7 +1375,8 @@ impl<'a> SourceCollector<'a> {
         layout::render(&mut w, &self.scx.layout,
                        &page, &(""), &Source(contents),
                        self.scx.css_file_extension.is_some(),
-                       &self.scx.themes)?;
+                       &self.scx.themes, &["source-files",
+                                           &format!("source-script{}", page.resource_suffix)])?;
         w.flush()?;
         self.scx.local_sources.insert(p.clone(), href);
         Ok(())
@@ -1890,7 +1974,7 @@ impl Context {
         try_err!(layout::render(&mut w, &self.shared.layout,
                                 &page, &sidebar, &all,
                                 self.shared.css_file_extension.is_some(),
-                                &self.shared.themes),
+                                &self.shared.themes, &[]),
                  &final_file);
 
         // Generating settings page.
@@ -1910,7 +1994,7 @@ impl Context {
         try_err!(layout::render(&mut w, &layout,
                                 &page, &sidebar, &settings,
                                 self.shared.css_file_extension.is_some(),
-                                &themes),
+                                &themes, &[]),
                  &settings_file);
 
         Ok(())
@@ -1968,7 +2052,7 @@ impl Context {
                            &Sidebar{ cx: self, item: it },
                            &Item{ cx: self, item: it },
                            self.shared.css_file_extension.is_some(),
-                           &self.shared.themes)?;
+                           &self.shared.themes, &[])?;
         } else {
             let mut url = self.root_path();
             if let Some(&(ref names, ty)) = cache().paths.get(&it.def_id) {
diff --git a/src/librustdoc/html/static/main.js b/src/librustdoc/html/static/main.js
index 55415e973c5..781f99cd693 100644
--- a/src/librustdoc/html/static/main.js
+++ b/src/librustdoc/html/static/main.js
@@ -13,6 +13,19 @@
 /*jslint browser: true, es5: true */
 /*globals $: true, rootPath: true */
 
+if (!String.prototype.startsWith) {
+    String.prototype.startsWith = function(searchString, position) {
+        position = position || 0;
+        return this.indexOf(searchString, position) === position;
+    };
+}
+if (!String.prototype.endsWith) {
+    String.prototype.endsWith = function(suffix, length) {
+        var l = length || this.length;
+        return this.indexOf(suffix, l - suffix.length) !== -1;
+    };
+}
+
 (function() {
     "use strict";
 
@@ -57,19 +70,6 @@
 
     var titleBeforeSearch = document.title;
 
-    if (!String.prototype.startsWith) {
-        String.prototype.startsWith = function(searchString, position) {
-            position = position || 0;
-            return this.indexOf(searchString, position) === position;
-        };
-    }
-    if (!String.prototype.endsWith) {
-        String.prototype.endsWith = function(suffix, length) {
-            var l = length || this.length;
-            return this.indexOf(suffix, l - suffix.length) !== -1;
-        };
-    }
-
     function getPageId() {
         var id = document.location.href.split('#')[1];
         if (id) {
@@ -78,46 +78,6 @@
         return null;
     }
 
-    function hasClass(elem, className) {
-        if (elem && className && elem.className) {
-            var elemClass = elem.className;
-            var start = elemClass.indexOf(className);
-            if (start === -1) {
-                return false;
-            } else if (elemClass.length === className.length) {
-                return true;
-            } else {
-                if (start > 0 && elemClass[start - 1] !== ' ') {
-                    return false;
-                }
-                var end = start + className.length;
-                return !(end < elemClass.length && elemClass[end] !== ' ');
-            }
-        }
-        return false;
-    }
-
-    function addClass(elem, className) {
-        if (elem && className && !hasClass(elem, className)) {
-            if (elem.className && elem.className.length > 0) {
-                elem.className += ' ' + className;
-            } else {
-                elem.className = className;
-            }
-        }
-    }
-
-    function removeClass(elem, className) {
-        if (elem && className && elem.className) {
-            elem.className = (" " + elem.className + " ").replace(" " + className + " ", " ")
-                                                         .trim();
-        }
-    }
-
-    function isHidden(elem) {
-        return (elem.offsetParent === null)
-    }
-
     function showSidebar() {
         var elems = document.getElementsByClassName("sidebar-elems")[0];
         if (elems) {
diff --git a/src/librustdoc/html/static/rustdoc.css b/src/librustdoc/html/static/rustdoc.css
index c50c968f360..b6242457ae2 100644
--- a/src/librustdoc/html/static/rustdoc.css
+++ b/src/librustdoc/html/static/rustdoc.css
@@ -113,7 +113,8 @@ h3.impl, h3.method, h3.type {
 
 h1, h2, h3, h4,
 .sidebar, a.source, .search-input, .content table :not(code)>a,
-.collapse-toggle, div.item-list .out-of-band {
+.collapse-toggle, div.item-list .out-of-band,
+#source-sidebar, #sidebar-toggle {
 	font-family: "Fira Sans", sans-serif;
 }
 
@@ -668,9 +669,9 @@ a {
 	padding-right: 10px;
 }
 .content .search-results td:first-child a:after {
-    clear: both;
-    content: "";
-    display: block;
+	clear: both;
+	content: "";
+	display: block;
 }
 .content .search-results td:first-child a span {
 	float: left;
@@ -1459,3 +1460,68 @@ kbd {
 .non-exhaustive {
 	margin-bottom: 1em;
 }
+
+#sidebar-toggle {
+	position: fixed;
+	top: 30px;
+	left: 300px;
+	z-index: 10;
+	padding: 3px;
+	border-top-right-radius: 3px;
+	border-bottom-right-radius: 3px;
+	cursor: pointer;
+	font-weight: bold;
+	transition: left .5s;
+	font-size: 1.2em;
+	border: 1px solid;
+	border-left: 0;
+}
+#source-sidebar {
+	position: fixed;
+	top: 0;
+	bottom: 0;
+	left: 0;
+	width: 300px;
+	z-index: 1;
+	overflow: auto;
+	transition: left .5s;
+	border-right: 1px solid;
+}
+#source-sidebar > .title {
+	font-size: 1.5em;
+	text-align: center;
+	border-bottom: 1px solid;
+	margin-bottom: 6px;
+}
+
+div.children {
+	padding-left: 27px;
+	display: none;
+}
+div.name {
+	cursor: pointer;
+	position: relative;
+	margin-left: 16px;
+}
+div.files > a {
+	display: block;
+	padding: 0 3px;
+}
+div.files > a:hover, div.name:hover {
+	background-color: #a14b4b;
+}
+div.name.expand + .children {
+	display: block;
+}
+div.name::before {
+	content: "\25B6";
+	padding-left: 4px;
+	font-size: 0.7em;
+	position: absolute;
+	left: -16px;
+	top: 4px;
+}
+div.name.expand::before {
+	transform: rotate(90deg);
+	left: -14px;
+}
diff --git a/src/librustdoc/html/static/source-script.js b/src/librustdoc/html/static/source-script.js
new file mode 100644
index 00000000000..1db8218dae6
--- /dev/null
+++ b/src/librustdoc/html/static/source-script.js
@@ -0,0 +1,147 @@
+/*!
+ * Copyright 2018 The Rust Project Developers. See the COPYRIGHT
+ * file at the top-level directory of this distribution and at
+ * http://rust-lang.org/COPYRIGHT.
+ *
+ * Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+ * http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+ * <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+ * option. This file may not be copied, modified, or distributed
+ * except according to those terms.
+ */
+
+function getCurrentFilePath() {
+    var parts = window.location.pathname.split("/");
+    var rootPathParts = window.rootPath.split("/");
+
+    for (var i = 0; i < rootPathParts.length; ++i) {
+        if (rootPathParts[i] === "..") {
+            parts.pop();
+        }
+    }
+    var file = window.location.pathname.substring(parts.join("/").length);
+    if (file.startsWith("/")) {
+        file = file.substring(1);
+    }
+    return file.substring(0, file.length - 5);
+}
+
+function createDirEntry(elem, parent, fullPath, currentFile, hasFoundFile) {
+    var name = document.createElement("div");
+    name.className = "name";
+
+    fullPath += elem["name"] + "/";
+
+    name.onclick = function() {
+        if (hasClass(this, "expand")) {
+            removeClass(this, "expand");
+        } else {
+            addClass(this, "expand");
+        }
+    };
+    name.innerText = elem["name"];
+
+    var children = document.createElement("div");
+    children.className = "children";
+    var folders = document.createElement("div");
+    folders.className = "folders";
+    for (var i = 0; i < elem.dirs.length; ++i) {
+        if (createDirEntry(elem.dirs[i], folders, fullPath, currentFile,
+                           hasFoundFile) === true) {
+            addClass(name, "expand");
+            hasFoundFile = true;
+        }
+    }
+    children.appendChild(folders);
+
+    var files = document.createElement("div");
+    files.className = "files";
+    for (i = 0; i < elem.files.length; ++i) {
+        var file = document.createElement("a");
+        file.innerText = elem.files[i];
+        file.href = window.rootPath + "src/" + fullPath + elem.files[i] + ".html";
+        if (hasFoundFile === false &&
+                currentFile === fullPath + elem.files[i]) {
+            file.className = "selected";
+            addClass(name, "expand");
+            hasFoundFile = true;
+        }
+        files.appendChild(file);
+    }
+    search.fullPath = fullPath;
+    children.appendChild(files);
+    parent.appendChild(name);
+    parent.appendChild(children);
+    return hasFoundFile === true && currentFile.startsWith(fullPath);
+}
+
+function toggleSidebar() {
+    var sidebar = document.getElementById("source-sidebar");
+    var child = this.children[0].children[0];
+    if (child.innerText === ">") {
+        sidebar.style.left = "";
+        this.style.left = "";
+        child.innerText = "<";
+        updateLocalStorage("rustdoc-source-sidebar-show", "true");
+    } else {
+        sidebar.style.left = "-300px";
+        this.style.left = "0";
+        child.innerText = ">";
+        updateLocalStorage("rustdoc-source-sidebar-show", "false");
+    }
+}
+
+function createSidebarToggle() {
+    var sidebarToggle = document.createElement("div");
+    sidebarToggle.id = "sidebar-toggle";
+    sidebarToggle.onclick = toggleSidebar;
+
+    var inner1 = document.createElement("div");
+    inner1.style.position = "relative";
+
+    var inner2 = document.createElement("div");
+    inner2.style.marginTop = "-2px";
+    if (getCurrentValue("rustdoc-source-sidebar-show") === "true") {
+        inner2.innerText = "<";
+    } else {
+        inner2.innerText = ">";
+        sidebarToggle.style.left = "0";
+    }
+
+    inner1.appendChild(inner2);
+    sidebarToggle.appendChild(inner1);
+    return sidebarToggle;
+}
+
+function createSourceSidebar() {
+    if (window.rootPath.endsWith("/") === false) {
+        window.rootPath += "/";
+    }
+    var main = document.getElementById("main");
+
+    var sidebarToggle = createSidebarToggle();
+    main.insertBefore(sidebarToggle, main.firstChild);
+
+    var sidebar = document.createElement("div");
+    sidebar.id = "source-sidebar";
+    if (getCurrentValue("rustdoc-source-sidebar-show") !== "true") {
+        sidebar.style.left = "-300px";
+    }
+
+    var currentFile = getCurrentFilePath();
+    var hasFoundFile = false;
+
+    var title = document.createElement("div");
+    title.className = "title";
+    title.innerText = "Files";
+    sidebar.appendChild(title);
+    Object.keys(sourcesIndex).forEach(function(key) {
+        sourcesIndex[key].name = key;
+        hasFoundFile = createDirEntry(sourcesIndex[key], sidebar, "",
+                                      currentFile, hasFoundFile);
+    });
+
+    main.insertBefore(sidebar, main.firstChild);
+}
+
+createSourceSidebar();
diff --git a/src/librustdoc/html/static/storage.js b/src/librustdoc/html/static/storage.js
index 5f7a8c75d3c..150001a7514 100644
--- a/src/librustdoc/html/static/storage.js
+++ b/src/librustdoc/html/static/storage.js
@@ -15,6 +15,46 @@ var mainTheme = document.getElementById("mainThemeStyle");
 
 var savedHref = [];
 
+function hasClass(elem, className) {
+    if (elem && className && elem.className) {
+        var elemClass = elem.className;
+        var start = elemClass.indexOf(className);
+        if (start === -1) {
+            return false;
+        } else if (elemClass.length === className.length) {
+            return true;
+        } else {
+            if (start > 0 && elemClass[start - 1] !== ' ') {
+                return false;
+            }
+            var end = start + className.length;
+            return !(end < elemClass.length && elemClass[end] !== ' ');
+        }
+    }
+    return false;
+}
+
+function addClass(elem, className) {
+    if (elem && className && !hasClass(elem, className)) {
+        if (elem.className && elem.className.length > 0) {
+            elem.className += ' ' + className;
+        } else {
+            elem.className = className;
+        }
+    }
+}
+
+function removeClass(elem, className) {
+    if (elem && className && elem.className) {
+        elem.className = (" " + elem.className + " ").replace(" " + className + " ", " ")
+                                                     .trim();
+    }
+}
+
+function isHidden(elem) {
+    return (elem.offsetParent === null)
+}
+
 function onEach(arr, func, reversed) {
     if (arr && arr.length > 0 && func) {
         if (reversed !== true) {
diff --git a/src/librustdoc/html/static/themes/dark.css b/src/librustdoc/html/static/themes/dark.css
index 4a8950b236c..acf9a8cca64 100644
--- a/src/librustdoc/html/static/themes/dark.css
+++ b/src/librustdoc/html/static/themes/dark.css
@@ -416,3 +416,22 @@ kbd {
 .impl-items code {
 	background-color: rgba(0, 0, 0, 0);
 }
+
+#sidebar-toggle {
+	background-color: #565656;
+}
+#sidebar-toggle:hover {
+	background-color: #676767;
+}
+#source-sidebar {
+	background-color: #565656;
+}
+#source-sidebar > .title {
+	border-bottom-color: #ccc;
+}
+div.files > a:hover, div.name:hover {
+	background-color: #444;
+}
+div.files > .selected {
+	background-color: #333;
+}
diff --git a/src/librustdoc/html/static/themes/light.css b/src/librustdoc/html/static/themes/light.css
index b3b0b6b2ea9..d98f1718a6a 100644
--- a/src/librustdoc/html/static/themes/light.css
+++ b/src/librustdoc/html/static/themes/light.css
@@ -410,3 +410,22 @@ kbd {
 .impl-items code {
 	background-color: rgba(0, 0, 0, 0);
 }
+
+#sidebar-toggle {
+	background-color: #F1F1F1;
+}
+#sidebar-toggle:hover {
+	background-color: #E0E0E0;
+}
+#source-sidebar {
+	background-color: #F1F1F1;
+}
+#source-sidebar > .title {
+	border-bottom-color: #ccc;
+}
+div.files > a:hover, div.name:hover {
+	background-color: #E0E0E0;
+}
+div.files > .selected {
+	background-color: #fff;
+}
diff --git a/src/librustdoc/html/static_files.rs b/src/librustdoc/html/static_files.rs
index 3baa082bd0e..ee29f15d686 100644
--- a/src/librustdoc/html/static_files.rs
+++ b/src/librustdoc/html/static_files.rs
@@ -109,3 +109,9 @@ pub mod source_code_pro {
     /// The file `SourceCodePro-LICENSE.txt`, the license text of the Source Code Pro font.
     pub static LICENSE: &'static [u8] = include_bytes!("static/SourceCodePro-LICENSE.txt");
 }
+
+/// Files related to the sidebar in rustdoc sources.
+pub mod sidebar {
+    /// File script to handle sidebar.
+    pub static SOURCE_SCRIPT: &'static str = include_str!("static/source-script.js");
+}
diff --git a/src/test/rustdoc/source-file.rs b/src/test/rustdoc/source-file.rs
new file mode 100644
index 00000000000..077817bcf5b
--- /dev/null
+++ b/src/test/rustdoc/source-file.rs
@@ -0,0 +1,15 @@
+// Copyright 2018 The Rust Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution and at
+// http://rust-lang.org/COPYRIGHT.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+#![crate_name = "foo"]
+
+// @has source-files.js source-file.rs
+
+pub struct Foo;