Skip to content

Commit 596e10f

Browse files
committedDec 4, 2018
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
2 parents 91d5d56 + 82a7b6f commit 596e10f

File tree

10 files changed

+425
-63
lines changed

10 files changed

+425
-63
lines changed
 

‎src/librustdoc/html/layout.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ pub struct Page<'a> {
3333

3434
pub fn render<T: fmt::Display, S: fmt::Display>(
3535
dst: &mut dyn io::Write, layout: &Layout, page: &Page, sidebar: &S, t: &T,
36-
css_file_extension: bool, themes: &[PathBuf])
36+
css_file_extension: bool, themes: &[PathBuf], extra_scripts: &[&str])
3737
-> io::Result<()>
3838
{
3939
write!(dst,
@@ -149,6 +149,7 @@ pub fn render<T: fmt::Display, S: fmt::Display>(
149149
</script>\
150150
<script src=\"{root_path}aliases.js\"></script>\
151151
<script src=\"{root_path}main{suffix}.js\"></script>\
152+
{extra_scripts}\
152153
<script defer src=\"{root_path}search-index.js\"></script>\
153154
</body>\
154155
</html>",
@@ -192,6 +193,11 @@ pub fn render<T: fmt::Display, S: fmt::Display>(
192193
page.resource_suffix))
193194
.collect::<String>(),
194195
suffix=page.resource_suffix,
196+
extra_scripts=extra_scripts.iter().map(|e| {
197+
format!("<script src=\"{root_path}{extra_script}.js\"></script>",
198+
root_path=page.root_path,
199+
extra_script=e)
200+
}).collect::<String>(),
195201
)
196202
}
197203

‎src/librustdoc/html/render.rs

Lines changed: 89 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -859,6 +859,11 @@ themePicker.onblur = handleThemeButtonsBlur;
859859
write_minify(cx.dst.join(&format!("settings{}.js", cx.shared.resource_suffix)),
860860
static_files::SETTINGS_JS,
861861
options.enable_minification)?;
862+
if cx.shared.include_sources {
863+
write_minify(cx.dst.join(&format!("source-script{}.js", cx.shared.resource_suffix)),
864+
static_files::sidebar::SOURCE_SCRIPT,
865+
options.enable_minification)?;
866+
}
862867

863868
{
864869
let mut data = format!("var resourcesSuffix = \"{}\";\n",
@@ -969,10 +974,88 @@ themePicker.onblur = handleThemeButtonsBlur;
969974
}
970975
}
971976

977+
use std::ffi::OsString;
978+
979+
#[derive(Debug)]
980+
struct Hierarchy {
981+
elem: OsString,
982+
children: FxHashMap<OsString, Hierarchy>,
983+
elems: FxHashSet<OsString>,
984+
}
985+
986+
impl Hierarchy {
987+
fn new(elem: OsString) -> Hierarchy {
988+
Hierarchy {
989+
elem,
990+
children: FxHashMap::default(),
991+
elems: FxHashSet::default(),
992+
}
993+
}
994+
995+
fn to_json_string(&self) -> String {
996+
let mut subs: Vec<&Hierarchy> = self.children.values().collect();
997+
subs.sort_unstable_by(|a, b| a.elem.cmp(&b.elem));
998+
let mut files = self.elems.iter()
999+
.map(|s| format!("\"{}\"",
1000+
s.to_str()
1001+
.expect("invalid osstring conversion")))
1002+
.collect::<Vec<_>>();
1003+
files.sort_unstable_by(|a, b| a.cmp(b));
1004+
// FIXME(imperio): we could avoid to generate "dirs" and "files" if they're empty.
1005+
format!("{{\"name\":\"{name}\",\"dirs\":[{subs}],\"files\":[{files}]}}",
1006+
name=self.elem.to_str().expect("invalid osstring conversion"),
1007+
subs=subs.iter().map(|s| s.to_json_string()).collect::<Vec<_>>().join(","),
1008+
files=files.join(","))
1009+
}
1010+
}
1011+
1012+
if cx.shared.include_sources {
1013+
use std::path::Component;
1014+
1015+
let mut hierarchy = Hierarchy::new(OsString::new());
1016+
for source in cx.shared.local_sources.iter()
1017+
.filter_map(|p| p.0.strip_prefix(&cx.shared.src_root)
1018+
.ok()) {
1019+
let mut h = &mut hierarchy;
1020+
let mut elems = source.components()
1021+
.filter_map(|s| {
1022+
match s {
1023+
Component::Normal(s) => Some(s.to_owned()),
1024+
_ => None,
1025+
}
1026+
})
1027+
.peekable();
1028+
loop {
1029+
let cur_elem = elems.next().expect("empty file path");
1030+
if elems.peek().is_none() {
1031+
h.elems.insert(cur_elem);
1032+
break;
1033+
} else {
1034+
let e = cur_elem.clone();
1035+
h.children.entry(cur_elem.clone()).or_insert_with(|| Hierarchy::new(e));
1036+
h = h.children.get_mut(&cur_elem).expect("not found child");
1037+
}
1038+
}
1039+
}
1040+
1041+
let dst = cx.dst.join("source-files.js");
1042+
let (mut all_sources, _krates) = try_err!(collect(&dst, &krate.name, "sourcesIndex"), &dst);
1043+
all_sources.push(format!("sourcesIndex['{}'] = {};",
1044+
&krate.name,
1045+
hierarchy.to_json_string()));
1046+
all_sources.sort();
1047+
let mut w = try_err!(File::create(&dst), &dst);
1048+
try_err!(writeln!(&mut w,
1049+
"var N = null;var sourcesIndex = {{}};\n{}",
1050+
all_sources.join("\n")),
1051+
&dst);
1052+
}
1053+
9721054
// Update the search index
9731055
let dst = cx.dst.join("search-index.js");
9741056
let (mut all_indexes, mut krates) = try_err!(collect(&dst, &krate.name, "searchIndex"), &dst);
9751057
all_indexes.push(search_index);
1058+
9761059
// Sort the indexes by crate so the file will be generated identically even
9771060
// with rustdoc running in parallel.
9781061
all_indexes.sort();
@@ -1020,7 +1103,7 @@ themePicker.onblur = handleThemeButtonsBlur;
10201103
try_err!(layout::render(&mut w, &cx.shared.layout,
10211104
&page, &(""), &content,
10221105
cx.shared.css_file_extension.is_some(),
1023-
&cx.shared.themes), &dst);
1106+
&cx.shared.themes, &[]), &dst);
10241107
try_err!(w.flush(), &dst);
10251108
}
10261109
}
@@ -1292,7 +1375,8 @@ impl<'a> SourceCollector<'a> {
12921375
layout::render(&mut w, &self.scx.layout,
12931376
&page, &(""), &Source(contents),
12941377
self.scx.css_file_extension.is_some(),
1295-
&self.scx.themes)?;
1378+
&self.scx.themes, &["source-files",
1379+
&format!("source-script{}", page.resource_suffix)])?;
12961380
w.flush()?;
12971381
self.scx.local_sources.insert(p.clone(), href);
12981382
Ok(())
@@ -1890,7 +1974,7 @@ impl Context {
18901974
try_err!(layout::render(&mut w, &self.shared.layout,
18911975
&page, &sidebar, &all,
18921976
self.shared.css_file_extension.is_some(),
1893-
&self.shared.themes),
1977+
&self.shared.themes, &[]),
18941978
&final_file);
18951979

18961980
// Generating settings page.
@@ -1910,7 +1994,7 @@ impl Context {
19101994
try_err!(layout::render(&mut w, &layout,
19111995
&page, &sidebar, &settings,
19121996
self.shared.css_file_extension.is_some(),
1913-
&themes),
1997+
&themes, &[]),
19141998
&settings_file);
19151999

19162000
Ok(())
@@ -1968,7 +2052,7 @@ impl Context {
19682052
&Sidebar{ cx: self, item: it },
19692053
&Item{ cx: self, item: it },
19702054
self.shared.css_file_extension.is_some(),
1971-
&self.shared.themes)?;
2055+
&self.shared.themes, &[])?;
19722056
} else {
19732057
let mut url = self.root_path();
19742058
if let Some(&(ref names, ty)) = cache().paths.get(&it.def_id) {

‎src/librustdoc/html/static/main.js

Lines changed: 13 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,19 @@
1313
/*jslint browser: true, es5: true */
1414
/*globals $: true, rootPath: true */
1515

16+
if (!String.prototype.startsWith) {
17+
String.prototype.startsWith = function(searchString, position) {
18+
position = position || 0;
19+
return this.indexOf(searchString, position) === position;
20+
};
21+
}
22+
if (!String.prototype.endsWith) {
23+
String.prototype.endsWith = function(suffix, length) {
24+
var l = length || this.length;
25+
return this.indexOf(suffix, l - suffix.length) !== -1;
26+
};
27+
}
28+
1629
(function() {
1730
"use strict";
1831

@@ -57,19 +70,6 @@
5770

5871
var titleBeforeSearch = document.title;
5972

60-
if (!String.prototype.startsWith) {
61-
String.prototype.startsWith = function(searchString, position) {
62-
position = position || 0;
63-
return this.indexOf(searchString, position) === position;
64-
};
65-
}
66-
if (!String.prototype.endsWith) {
67-
String.prototype.endsWith = function(suffix, length) {
68-
var l = length || this.length;
69-
return this.indexOf(suffix, l - suffix.length) !== -1;
70-
};
71-
}
72-
7373
function getPageId() {
7474
var id = document.location.href.split('#')[1];
7575
if (id) {
@@ -78,46 +78,6 @@
7878
return null;
7979
}
8080

81-
function hasClass(elem, className) {
82-
if (elem && className && elem.className) {
83-
var elemClass = elem.className;
84-
var start = elemClass.indexOf(className);
85-
if (start === -1) {
86-
return false;
87-
} else if (elemClass.length === className.length) {
88-
return true;
89-
} else {
90-
if (start > 0 && elemClass[start - 1] !== ' ') {
91-
return false;
92-
}
93-
var end = start + className.length;
94-
return !(end < elemClass.length && elemClass[end] !== ' ');
95-
}
96-
}
97-
return false;
98-
}
99-
100-
function addClass(elem, className) {
101-
if (elem && className && !hasClass(elem, className)) {
102-
if (elem.className && elem.className.length > 0) {
103-
elem.className += ' ' + className;
104-
} else {
105-
elem.className = className;
106-
}
107-
}
108-
}
109-
110-
function removeClass(elem, className) {
111-
if (elem && className && elem.className) {
112-
elem.className = (" " + elem.className + " ").replace(" " + className + " ", " ")
113-
.trim();
114-
}
115-
}
116-
117-
function isHidden(elem) {
118-
return (elem.offsetParent === null)
119-
}
120-
12181
function showSidebar() {
12282
var elems = document.getElementsByClassName("sidebar-elems")[0];
12383
if (elems) {

‎src/librustdoc/html/static/rustdoc.css

Lines changed: 70 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,8 @@ h3.impl, h3.method, h3.type {
113113

114114
h1, h2, h3, h4,
115115
.sidebar, a.source, .search-input, .content table :not(code)>a,
116-
.collapse-toggle, div.item-list .out-of-band {
116+
.collapse-toggle, div.item-list .out-of-band,
117+
#source-sidebar, #sidebar-toggle {
117118
font-family: "Fira Sans", sans-serif;
118119
}
119120

@@ -668,9 +669,9 @@ a {
668669
padding-right: 10px;
669670
}
670671
.content .search-results td:first-child a:after {
671-
clear: both;
672-
content: "";
673-
display: block;
672+
clear: both;
673+
content: "";
674+
display: block;
674675
}
675676
.content .search-results td:first-child a span {
676677
float: left;
@@ -1459,3 +1460,68 @@ kbd {
14591460
.non-exhaustive {
14601461
margin-bottom: 1em;
14611462
}
1463+
1464+
#sidebar-toggle {
1465+
position: fixed;
1466+
top: 30px;
1467+
left: 300px;
1468+
z-index: 10;
1469+
padding: 3px;
1470+
border-top-right-radius: 3px;
1471+
border-bottom-right-radius: 3px;
1472+
cursor: pointer;
1473+
font-weight: bold;
1474+
transition: left .5s;
1475+
font-size: 1.2em;
1476+
border: 1px solid;
1477+
border-left: 0;
1478+
}
1479+
#source-sidebar {
1480+
position: fixed;
1481+
top: 0;
1482+
bottom: 0;
1483+
left: 0;
1484+
width: 300px;
1485+
z-index: 1;
1486+
overflow: auto;
1487+
transition: left .5s;
1488+
border-right: 1px solid;
1489+
}
1490+
#source-sidebar > .title {
1491+
font-size: 1.5em;
1492+
text-align: center;
1493+
border-bottom: 1px solid;
1494+
margin-bottom: 6px;
1495+
}
1496+
1497+
div.children {
1498+
padding-left: 27px;
1499+
display: none;
1500+
}
1501+
div.name {
1502+
cursor: pointer;
1503+
position: relative;
1504+
margin-left: 16px;
1505+
}
1506+
div.files > a {
1507+
display: block;
1508+
padding: 0 3px;
1509+
}
1510+
div.files > a:hover, div.name:hover {
1511+
background-color: #a14b4b;
1512+
}
1513+
div.name.expand + .children {
1514+
display: block;
1515+
}
1516+
div.name::before {
1517+
content: "\25B6";
1518+
padding-left: 4px;
1519+
font-size: 0.7em;
1520+
position: absolute;
1521+
left: -16px;
1522+
top: 4px;
1523+
}
1524+
div.name.expand::before {
1525+
transform: rotate(90deg);
1526+
left: -14px;
1527+
}
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
/*!
2+
* Copyright 2018 The Rust Project Developers. See the COPYRIGHT
3+
* file at the top-level directory of this distribution and at
4+
* http://rust-lang.org/COPYRIGHT.
5+
*
6+
* Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
7+
* http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
8+
* <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
9+
* option. This file may not be copied, modified, or distributed
10+
* except according to those terms.
11+
*/
12+
13+
function getCurrentFilePath() {
14+
var parts = window.location.pathname.split("/");
15+
var rootPathParts = window.rootPath.split("/");
16+
17+
for (var i = 0; i < rootPathParts.length; ++i) {
18+
if (rootPathParts[i] === "..") {
19+
parts.pop();
20+
}
21+
}
22+
var file = window.location.pathname.substring(parts.join("/").length);
23+
if (file.startsWith("/")) {
24+
file = file.substring(1);
25+
}
26+
return file.substring(0, file.length - 5);
27+
}
28+
29+
function createDirEntry(elem, parent, fullPath, currentFile, hasFoundFile) {
30+
var name = document.createElement("div");
31+
name.className = "name";
32+
33+
fullPath += elem["name"] + "/";
34+
35+
name.onclick = function() {
36+
if (hasClass(this, "expand")) {
37+
removeClass(this, "expand");
38+
} else {
39+
addClass(this, "expand");
40+
}
41+
};
42+
name.innerText = elem["name"];
43+
44+
var children = document.createElement("div");
45+
children.className = "children";
46+
var folders = document.createElement("div");
47+
folders.className = "folders";
48+
for (var i = 0; i < elem.dirs.length; ++i) {
49+
if (createDirEntry(elem.dirs[i], folders, fullPath, currentFile,
50+
hasFoundFile) === true) {
51+
addClass(name, "expand");
52+
hasFoundFile = true;
53+
}
54+
}
55+
children.appendChild(folders);
56+
57+
var files = document.createElement("div");
58+
files.className = "files";
59+
for (i = 0; i < elem.files.length; ++i) {
60+
var file = document.createElement("a");
61+
file.innerText = elem.files[i];
62+
file.href = window.rootPath + "src/" + fullPath + elem.files[i] + ".html";
63+
if (hasFoundFile === false &&
64+
currentFile === fullPath + elem.files[i]) {
65+
file.className = "selected";
66+
addClass(name, "expand");
67+
hasFoundFile = true;
68+
}
69+
files.appendChild(file);
70+
}
71+
search.fullPath = fullPath;
72+
children.appendChild(files);
73+
parent.appendChild(name);
74+
parent.appendChild(children);
75+
return hasFoundFile === true && currentFile.startsWith(fullPath);
76+
}
77+
78+
function toggleSidebar() {
79+
var sidebar = document.getElementById("source-sidebar");
80+
var child = this.children[0].children[0];
81+
if (child.innerText === ">") {
82+
sidebar.style.left = "";
83+
this.style.left = "";
84+
child.innerText = "<";
85+
updateLocalStorage("rustdoc-source-sidebar-show", "true");
86+
} else {
87+
sidebar.style.left = "-300px";
88+
this.style.left = "0";
89+
child.innerText = ">";
90+
updateLocalStorage("rustdoc-source-sidebar-show", "false");
91+
}
92+
}
93+
94+
function createSidebarToggle() {
95+
var sidebarToggle = document.createElement("div");
96+
sidebarToggle.id = "sidebar-toggle";
97+
sidebarToggle.onclick = toggleSidebar;
98+
99+
var inner1 = document.createElement("div");
100+
inner1.style.position = "relative";
101+
102+
var inner2 = document.createElement("div");
103+
inner2.style.marginTop = "-2px";
104+
if (getCurrentValue("rustdoc-source-sidebar-show") === "true") {
105+
inner2.innerText = "<";
106+
} else {
107+
inner2.innerText = ">";
108+
sidebarToggle.style.left = "0";
109+
}
110+
111+
inner1.appendChild(inner2);
112+
sidebarToggle.appendChild(inner1);
113+
return sidebarToggle;
114+
}
115+
116+
function createSourceSidebar() {
117+
if (window.rootPath.endsWith("/") === false) {
118+
window.rootPath += "/";
119+
}
120+
var main = document.getElementById("main");
121+
122+
var sidebarToggle = createSidebarToggle();
123+
main.insertBefore(sidebarToggle, main.firstChild);
124+
125+
var sidebar = document.createElement("div");
126+
sidebar.id = "source-sidebar";
127+
if (getCurrentValue("rustdoc-source-sidebar-show") !== "true") {
128+
sidebar.style.left = "-300px";
129+
}
130+
131+
var currentFile = getCurrentFilePath();
132+
var hasFoundFile = false;
133+
134+
var title = document.createElement("div");
135+
title.className = "title";
136+
title.innerText = "Files";
137+
sidebar.appendChild(title);
138+
Object.keys(sourcesIndex).forEach(function(key) {
139+
sourcesIndex[key].name = key;
140+
hasFoundFile = createDirEntry(sourcesIndex[key], sidebar, "",
141+
currentFile, hasFoundFile);
142+
});
143+
144+
main.insertBefore(sidebar, main.firstChild);
145+
}
146+
147+
createSourceSidebar();

‎src/librustdoc/html/static/storage.js

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,46 @@ var mainTheme = document.getElementById("mainThemeStyle");
1515

1616
var savedHref = [];
1717

18+
function hasClass(elem, className) {
19+
if (elem && className && elem.className) {
20+
var elemClass = elem.className;
21+
var start = elemClass.indexOf(className);
22+
if (start === -1) {
23+
return false;
24+
} else if (elemClass.length === className.length) {
25+
return true;
26+
} else {
27+
if (start > 0 && elemClass[start - 1] !== ' ') {
28+
return false;
29+
}
30+
var end = start + className.length;
31+
return !(end < elemClass.length && elemClass[end] !== ' ');
32+
}
33+
}
34+
return false;
35+
}
36+
37+
function addClass(elem, className) {
38+
if (elem && className && !hasClass(elem, className)) {
39+
if (elem.className && elem.className.length > 0) {
40+
elem.className += ' ' + className;
41+
} else {
42+
elem.className = className;
43+
}
44+
}
45+
}
46+
47+
function removeClass(elem, className) {
48+
if (elem && className && elem.className) {
49+
elem.className = (" " + elem.className + " ").replace(" " + className + " ", " ")
50+
.trim();
51+
}
52+
}
53+
54+
function isHidden(elem) {
55+
return (elem.offsetParent === null)
56+
}
57+
1858
function onEach(arr, func, reversed) {
1959
if (arr && arr.length > 0 && func) {
2060
if (reversed !== true) {

‎src/librustdoc/html/static/themes/dark.css

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -416,3 +416,22 @@ kbd {
416416
.impl-items code {
417417
background-color: rgba(0, 0, 0, 0);
418418
}
419+
420+
#sidebar-toggle {
421+
background-color: #565656;
422+
}
423+
#sidebar-toggle:hover {
424+
background-color: #676767;
425+
}
426+
#source-sidebar {
427+
background-color: #565656;
428+
}
429+
#source-sidebar > .title {
430+
border-bottom-color: #ccc;
431+
}
432+
div.files > a:hover, div.name:hover {
433+
background-color: #444;
434+
}
435+
div.files > .selected {
436+
background-color: #333;
437+
}

‎src/librustdoc/html/static/themes/light.css

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -410,3 +410,22 @@ kbd {
410410
.impl-items code {
411411
background-color: rgba(0, 0, 0, 0);
412412
}
413+
414+
#sidebar-toggle {
415+
background-color: #F1F1F1;
416+
}
417+
#sidebar-toggle:hover {
418+
background-color: #E0E0E0;
419+
}
420+
#source-sidebar {
421+
background-color: #F1F1F1;
422+
}
423+
#source-sidebar > .title {
424+
border-bottom-color: #ccc;
425+
}
426+
div.files > a:hover, div.name:hover {
427+
background-color: #E0E0E0;
428+
}
429+
div.files > .selected {
430+
background-color: #fff;
431+
}

‎src/librustdoc/html/static_files.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,3 +109,9 @@ pub mod source_code_pro {
109109
/// The file `SourceCodePro-LICENSE.txt`, the license text of the Source Code Pro font.
110110
pub static LICENSE: &'static [u8] = include_bytes!("static/SourceCodePro-LICENSE.txt");
111111
}
112+
113+
/// Files related to the sidebar in rustdoc sources.
114+
pub mod sidebar {
115+
/// File script to handle sidebar.
116+
pub static SOURCE_SCRIPT: &'static str = include_str!("static/source-script.js");
117+
}

‎src/test/rustdoc/source-file.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// Copyright 2018 The Rust Project Developers. See the COPYRIGHT
2+
// file at the top-level directory of this distribution and at
3+
// http://rust-lang.org/COPYRIGHT.
4+
//
5+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8+
// option. This file may not be copied, modified, or distributed
9+
// except according to those terms.
10+
11+
#![crate_name = "foo"]
12+
13+
// @has source-files.js source-file.rs
14+
15+
pub struct Foo;

0 commit comments

Comments
 (0)
Please sign in to comment.