Skip to content

Commit 93520d2

Browse files
Add source file sidebar
1 parent 4632cf2 commit 93520d2

File tree

8 files changed

+384
-63
lines changed

8 files changed

+384
-63
lines changed

src/librustdoc/html/layout.rs

+7-1
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}\"></script>",
198+
root_path=page.root_path,
199+
extra_script=e)
200+
}).collect::<String>(),
195201
)
196202
}
197203

src/librustdoc/html/render.rs

+80-5
Original file line numberDiff line numberDiff line change
@@ -859,6 +859,9 @@ 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+
write_minify(cx.dst.join(&format!("source-script{}.js", cx.shared.resource_suffix)),
863+
include_str!("static/source-script.js"),
864+
options.enable_minification)?;
862865

863866
{
864867
let mut data = format!("var resourcesSuffix = \"{}\";\n",
@@ -969,10 +972,82 @@ themePicker.onblur = handleThemeButtonsBlur;
969972
}
970973
}
971974

975+
use std::ffi::OsString;
976+
977+
#[derive(Debug)]
978+
struct Hierarchy {
979+
elem: OsString,
980+
children: FxHashMap<OsString, Hierarchy>,
981+
elems: FxHashSet<OsString>,
982+
}
983+
984+
impl Hierarchy {
985+
fn new(elem: OsString) -> Hierarchy {
986+
Hierarchy {
987+
elem,
988+
children: FxHashMap::default(),
989+
elems: FxHashSet::default(),
990+
}
991+
}
992+
993+
fn to_string(&self) -> String {
994+
let mut subs: Vec<&Hierarchy> = self.children.iter().map(|(_, v)| v).collect();
995+
subs.sort_unstable_by(|a, b| a.elem.cmp(&b.elem));
996+
let mut files = self.elems.iter()
997+
.map(|s| format!("\"{}\"",
998+
s.to_str()
999+
.expect("invalid osstring conversion")))
1000+
.collect::<Vec<_>>();
1001+
files.sort_unstable_by(|a, b| a.cmp(b));
1002+
// FIXME(imperio): we could avoid to generate "dirs" and "files" if they're empty.
1003+
format!("{{\"name\":\"{name}\",\"dirs\":[{subs}],\"files\":[{files}]}}",
1004+
name=self.elem.to_str().expect("invalid osstring conversion"),
1005+
subs=subs.iter().map(|s| s.to_string()).collect::<Vec<_>>().join(","),
1006+
files=files.join(","))
1007+
}
1008+
}
1009+
1010+
use std::path::Component;
1011+
1012+
let mut hierarchy = Hierarchy::new(OsString::new());
1013+
for source in cx.shared.local_sources.iter()
1014+
.filter_map(|p| p.0.strip_prefix(&cx.shared.src_root)
1015+
.ok()) {
1016+
let mut h = &mut hierarchy;
1017+
let mut elems = source.components()
1018+
.filter_map(|s| {
1019+
match s {
1020+
Component::Normal(s) => Some(s.to_owned()),
1021+
_ => None,
1022+
}
1023+
})
1024+
.peekable();
1025+
loop {
1026+
let cur_elem = elems.next().expect("empty file path");
1027+
if elems.peek().is_none() {
1028+
h.elems.insert(cur_elem);
1029+
break;
1030+
} else {
1031+
let e = cur_elem.clone();
1032+
h.children.entry(cur_elem.clone()).or_insert_with(|| Hierarchy::new(e));
1033+
h = h.children.get_mut(&cur_elem).expect("not found child");
1034+
}
1035+
}
1036+
}
1037+
1038+
let dst = cx.dst.join("source-files.js");
1039+
let (mut all_sources, _krates) = try_err!(collect(&dst, &krate.name, "sourcesIndex"), &dst);
1040+
all_sources.push(format!("sourcesIndex['{}'] = {};", &krate.name, hierarchy.to_string()));
1041+
all_sources.sort();
1042+
let mut w = try_err!(File::create(&dst), &dst);
1043+
try_err!(writeln!(&mut w, "var N = null;var sourcesIndex = {{}};\n{}", all_sources.join("\n")),
1044+
&dst);
1045+
9721046
// Update the search index
9731047
let dst = cx.dst.join("search-index.js");
9741048
let (mut all_indexes, mut krates) = try_err!(collect(&dst, &krate.name, "searchIndex"), &dst);
9751049
all_indexes.push(search_index);
1050+
9761051
// Sort the indexes by crate so the file will be generated identically even
9771052
// with rustdoc running in parallel.
9781053
all_indexes.sort();
@@ -1020,7 +1095,7 @@ themePicker.onblur = handleThemeButtonsBlur;
10201095
try_err!(layout::render(&mut w, &cx.shared.layout,
10211096
&page, &(""), &content,
10221097
cx.shared.css_file_extension.is_some(),
1023-
&cx.shared.themes), &dst);
1098+
&cx.shared.themes, &[]), &dst);
10241099
try_err!(w.flush(), &dst);
10251100
}
10261101
}
@@ -1292,7 +1367,7 @@ impl<'a> SourceCollector<'a> {
12921367
layout::render(&mut w, &self.scx.layout,
12931368
&page, &(""), &Source(contents),
12941369
self.scx.css_file_extension.is_some(),
1295-
&self.scx.themes)?;
1370+
&self.scx.themes, &["source-files.js", "source-script.js"])?;
12961371
w.flush()?;
12971372
self.scx.local_sources.insert(p.clone(), href);
12981373
Ok(())
@@ -1890,7 +1965,7 @@ impl Context {
18901965
try_err!(layout::render(&mut w, &self.shared.layout,
18911966
&page, &sidebar, &all,
18921967
self.shared.css_file_extension.is_some(),
1893-
&self.shared.themes),
1968+
&self.shared.themes, &[]),
18941969
&final_file);
18951970

18961971
// Generating settings page.
@@ -1910,7 +1985,7 @@ impl Context {
19101985
try_err!(layout::render(&mut w, &layout,
19111986
&page, &sidebar, &settings,
19121987
self.shared.css_file_extension.is_some(),
1913-
&themes),
1988+
&themes, &[]),
19141989
&settings_file);
19151990

19161991
Ok(())
@@ -1968,7 +2043,7 @@ impl Context {
19682043
&Sidebar{ cx: self, item: it },
19692044
&Item{ cx: self, item: it },
19702045
self.shared.css_file_extension.is_some(),
1971-
&self.shared.themes)?;
2046+
&self.shared.themes, &[])?;
19722047
} else {
19732048
let mut url = self.root_path();
19742049
if let Some(&(ref names, ty)) = cache().paths.get(&it.def_id) {

src/librustdoc/html/static/main.js

+13-53
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

+59-4
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,57 @@ kbd {
14591460
.non-exhaustive {
14601461
margin-bottom: 1em;
14611462
}
1463+
1464+
#sidebar-toggle {
1465+
position: fixed;
1466+
top: 30px;
1467+
right: 300px;
1468+
z-index: 10;
1469+
padding: 3px;
1470+
border-top-left-radius: 3px;
1471+
border-bottom-left-radius: 3px;
1472+
cursor: pointer;
1473+
font-weight: bold;
1474+
transition: right 1s;
1475+
font-size: 1.2em;
1476+
}
1477+
#source-sidebar {
1478+
position: fixed;
1479+
top: 0;
1480+
bottom: 0;
1481+
right: 0;
1482+
width: 300px;
1483+
z-index: 1;
1484+
overflow: auto;
1485+
transition: right 1s;
1486+
}
1487+
#source-sidebar > .title {
1488+
font-size: 1.5em;
1489+
text-align: center;
1490+
border-bottom: 1px solid;
1491+
margin-bottom: 6px;
1492+
}
1493+
1494+
div.children {
1495+
padding-left: 27px;
1496+
display: none;
1497+
}
1498+
div.files > a {
1499+
display: block;
1500+
padding: 0 3px;
1501+
}
1502+
div.files > a:hover, div.name:hover {
1503+
background-color: #a14b4b;
1504+
}
1505+
div.name.expand + .children {
1506+
display: block;
1507+
}
1508+
div.name::before {
1509+
content: "\25B6";
1510+
display: inline-block;
1511+
padding-right: 4px;
1512+
font-size: 0.7em;
1513+
}
1514+
div.name.expand::before {
1515+
transform: rotate(90deg);
1516+
}

0 commit comments

Comments
 (0)