Skip to content

Commit dd8d565

Browse files
committed
rustdoc: Include source files with documentation
All items have source links back to their actual code. Source files can be omitted with the doc(html_no_source) attribute on the crate. Currently there is no syntax highlighting, but that will come with syntax highlighting with all other snippets. Closes rust-lang#2072
1 parent b93678e commit dd8d565

File tree

6 files changed

+213
-12
lines changed

6 files changed

+213
-12
lines changed

src/librustdoc/clean.rs

+24-6
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ impl Clean<Crate> for visit_ast::RustdocVisitor {
8484
#[deriving(Clone, Encodable, Decodable)]
8585
pub struct Item {
8686
/// Stringified span
87-
source: ~str,
87+
source: Span,
8888
/// Not everything has a name. E.g., impls
8989
name: Option<~str>,
9090
attrs: ~[Attribute],
@@ -737,10 +737,28 @@ impl Clean<VariantKind> for ast::variant_kind {
737737
}
738738
}
739739

740-
impl Clean<~str> for syntax::codemap::Span {
741-
fn clean(&self) -> ~str {
742-
let cm = local_data::get(super::ctxtkey, |x| x.unwrap().clone()).sess.codemap;
743-
cm.span_to_str(*self)
740+
#[deriving(Clone, Encodable, Decodable)]
741+
pub struct Span {
742+
filename: ~str,
743+
loline: uint,
744+
locol: uint,
745+
hiline: uint,
746+
hicol: uint,
747+
}
748+
749+
impl Clean<Span> for syntax::codemap::Span {
750+
fn clean(&self) -> Span {
751+
let cm = local_data::get(super::ctxtkey, |x| *x.unwrap()).sess.codemap;
752+
let filename = cm.span_to_filename(*self);
753+
let lo = cm.lookup_char_pos(self.lo);
754+
let hi = cm.lookup_char_pos(self.hi);
755+
Span {
756+
filename: filename.to_owned(),
757+
loline: lo.line,
758+
locol: *lo.col,
759+
hiline: hi.line,
760+
hicol: *hi.col,
761+
}
744762
}
745763
}
746764

@@ -1034,7 +1052,7 @@ trait ToSource {
10341052

10351053
impl ToSource for syntax::codemap::Span {
10361054
fn to_src(&self) -> ~str {
1037-
debug!("converting span %s to snippet", self.clean());
1055+
debug!("converting span %? to snippet", self.clean());
10381056
let cm = local_data::get(super::ctxtkey, |x| x.unwrap().clone()).sess.codemap.clone();
10391057
let sn = match cm.span_to_snippet(*self) {
10401058
Some(x) => x,

src/librustdoc/html/escape.rs

+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
// Copyright 2013 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+
use std::fmt;
12+
13+
pub struct Escape<'self>(&'self str);
14+
15+
impl<'self> fmt::Default for Escape<'self> {
16+
fn fmt(s: &Escape<'self>, fmt: &mut fmt::Formatter) {
17+
// Because the internet is always right, turns out there's not that many
18+
// characters to escape: http://stackoverflow.com/questions/7381974
19+
let pile_o_bits = s.as_slice();
20+
let mut last = 0;
21+
for (i, ch) in s.byte_iter().enumerate() {
22+
match ch as char {
23+
'<' | '>' | '&' | '\'' | '"' => {
24+
fmt.buf.write(pile_o_bits.slice(last, i).as_bytes());
25+
let s = match ch as char {
26+
'>' => "&gt;",
27+
'<' => "&lt;",
28+
'&' => "&amp;",
29+
'\'' => "&#39;",
30+
'"' => "&quot;",
31+
_ => unreachable!()
32+
};
33+
fmt.buf.write(s.as_bytes());
34+
last = i + 1;
35+
}
36+
_ => {}
37+
}
38+
}
39+
40+
if last < s.len() {
41+
fmt.buf.write(pile_o_bits.slice_from(last).as_bytes());
42+
}
43+
}
44+
}

src/librustdoc/html/layout.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ pub fn render<T: fmt::Default, S: fmt::Default>(
6767
</nav>
6868
6969
<section id='main' class=\"content {ty}\">{content}</section>
70-
<section id='search' class=\"content hidden\">{content}</section>
70+
<section id='search' class=\"content hidden\"></section>
7171
7272
<section class=\"footer\"></section>
7373

src/librustdoc/html/render.rs

+138-3
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,14 @@ use std::cell::Cell;
1212
use std::comm::{SharedPort, SharedChan};
1313
use std::comm;
1414
use std::fmt;
15-
use std::hashmap::HashMap;
15+
use std::hashmap::{HashMap, HashSet};
1616
use std::local_data;
1717
use std::rt::io::buffered::BufferedWriter;
1818
use std::rt::io::file::{FileInfo, DirectoryInfo};
1919
use std::rt::io::file;
2020
use std::rt::io;
21+
use std::rt::io::Reader;
22+
use std::str;
2123
use std::task;
2224
use std::unstable::finally::Finally;
2325
use std::util;
@@ -33,6 +35,7 @@ use syntax::attr;
3335
use clean;
3436
use doctree;
3537
use fold::DocFolder;
38+
use html::escape::Escape;
3639
use html::format::{VisSpace, Method, PuritySpace};
3740
use html::layout;
3841
use html::markdown::Markdown;
@@ -44,6 +47,7 @@ pub struct Context {
4447
dst: Path,
4548
layout: layout::Layout,
4649
sidebar: HashMap<~str, ~[~str]>,
50+
include_sources: bool,
4751
}
4852

4953
enum Implementor {
@@ -68,6 +72,12 @@ struct Cache {
6872
priv search_index: ~[IndexItem],
6973
}
7074

75+
struct SourceCollector<'self> {
76+
seen: HashSet<~str>,
77+
dst: Path,
78+
cx: &'self Context,
79+
}
80+
7181
struct Item<'self> { cx: &'self Context, item: &'self clean::Item, }
7282
struct Sidebar<'self> { cx: &'self Context, item: &'self clean::Item, }
7383

@@ -79,6 +89,8 @@ struct IndexItem {
7989
parent: Option<ast::NodeId>,
8090
}
8191

92+
struct Source<'self>(&'self str);
93+
8294
local_data_key!(pub cache_key: RWArc<Cache>)
8395
local_data_key!(pub current_location_key: ~[~str])
8496

@@ -94,6 +106,7 @@ pub fn run(mut crate: clean::Crate, dst: Path) {
94106
favicon: ~"",
95107
crate: crate.name.clone(),
96108
},
109+
include_sources: true,
97110
};
98111
mkdir(&cx.dst);
99112

@@ -107,6 +120,9 @@ pub fn run(mut crate: clean::Crate, dst: Path) {
107120
clean::NameValue(~"html_logo_url", ref s) => {
108121
cx.layout.logo = s.to_owned();
109122
}
123+
clean::Word(~"html_no_source") => {
124+
cx.include_sources = false;
125+
}
110126
_ => {}
111127
}
112128
}
@@ -162,6 +178,19 @@ pub fn run(mut crate: clean::Crate, dst: Path) {
162178
w.flush();
163179
}
164180

181+
if cx.include_sources {
182+
let dst = cx.dst.push("src");
183+
mkdir(&dst);
184+
let dst = dst.push(crate.name);
185+
mkdir(&dst);
186+
let mut folder = SourceCollector {
187+
dst: dst,
188+
seen: HashSet::new(),
189+
cx: &cx,
190+
};
191+
crate = folder.fold_crate(crate);
192+
}
193+
165194
// Now render the whole crate.
166195
cx.crate(crate, cache);
167196
}
@@ -183,7 +212,80 @@ fn mkdir(path: &Path) {
183212
}
184213
}
185214

186-
impl<'self> DocFolder for Cache {
215+
fn clean_srcpath(src: &str, f: &fn(&str)) {
216+
let p = Path(src);
217+
for c in p.components.iter() {
218+
if "." == *c {
219+
loop
220+
}
221+
if ".." == *c {
222+
f("up");
223+
} else {
224+
f(c.as_slice())
225+
}
226+
}
227+
}
228+
229+
impl<'self> DocFolder for SourceCollector<'self> {
230+
fn fold_item(&mut self, item: clean::Item) -> Option<clean::Item> {
231+
if !self.seen.contains(&item.source.filename) {
232+
self.emit_source(item.source.filename);
233+
self.seen.insert(item.source.filename.clone());
234+
}
235+
self.fold_item_recur(item)
236+
}
237+
}
238+
239+
impl<'self> SourceCollector<'self> {
240+
fn emit_source(&self, filename: &str) {
241+
let p = Path(filename);
242+
243+
// Read the contents of the file
244+
let mut contents = ~[];
245+
{
246+
let mut buf = [0, ..1024];
247+
let r = do io::io_error::cond.trap(|_| {}).inside {
248+
p.open_reader(io::Open)
249+
};
250+
// If we couldn't open this file, then just returns because it
251+
// probably means that it's some standard library macro thing and we
252+
// can't have the source to it anyway.
253+
let mut r = match r { Some(r) => r, None => return };
254+
255+
// read everything
256+
loop {
257+
match r.read(buf) {
258+
Some(n) => contents.push_all(buf.slice_to(n)),
259+
None => break
260+
}
261+
}
262+
}
263+
let contents = str::from_utf8_owned(contents);
264+
265+
// Create the intermediate directories
266+
let mut cur = self.dst.clone();
267+
let mut root_path = ~"../../";
268+
do clean_srcpath(p.pop().to_str()) |component| {
269+
cur = cur.push(component);
270+
mkdir(&cur);
271+
root_path.push_str("../");
272+
}
273+
274+
let dst = cur.push(*p.components.last() + ".html");
275+
let mut w = dst.open_writer(io::CreateOrTruncate);
276+
277+
let title = format!("{} -- source", *dst.components.last());
278+
let page = layout::Page {
279+
title: title,
280+
ty: "source",
281+
root_path: root_path,
282+
};
283+
layout::render(&mut w as &mut io::Writer, &self.cx.layout,
284+
&page, &(""), &Source(contents.as_slice()));
285+
}
286+
}
287+
288+
impl DocFolder for Cache {
187289
fn fold_item(&mut self, item: clean::Item) -> Option<clean::Item> {
188290
// Register any generics to their corresponding string. This is used
189291
// when pretty-printing types
@@ -380,7 +482,6 @@ impl Context {
380482
return ret;
381483
}
382484

383-
/// Processes
384485
fn crate(self, mut crate: clean::Crate, cache: Cache) {
385486
enum Work {
386487
Die,
@@ -565,6 +666,20 @@ impl<'self> fmt::Default for Item<'self> {
565666
None => {}
566667
}
567668

669+
if it.cx.include_sources {
670+
let mut path = ~[];
671+
do clean_srcpath(it.item.source.filename) |component| {
672+
path.push(component.to_owned());
673+
}
674+
write!(fmt.buf,
675+
"<a class='source'
676+
href='{root}src/{crate}/{path}.html\\#{line}'>[src]</a>",
677+
root = it.cx.root_path,
678+
crate = it.cx.layout.crate,
679+
path = path.connect("/"),
680+
line = it.item.source.loline);
681+
}
682+
568683
// Write the breadcrumb trail header for the top
569684
write!(fmt.buf, "<h1 class='fqn'>");
570685
match it.item.inner {
@@ -1180,3 +1295,23 @@ fn build_sidebar(m: &clean::Module) -> HashMap<~str, ~[~str]> {
11801295
}
11811296
return map;
11821297
}
1298+
1299+
impl<'self> fmt::Default for Source<'self> {
1300+
fn fmt(s: &Source<'self>, fmt: &mut fmt::Formatter) {
1301+
let lines = s.line_iter().len();
1302+
let mut cols = 0;
1303+
let mut tmp = lines;
1304+
while tmp > 0 {
1305+
cols += 1;
1306+
tmp /= 10;
1307+
}
1308+
write!(fmt.buf, "<pre class='line-numbers'>");
1309+
for i in range(1, lines + 1) {
1310+
write!(fmt.buf, "<span id='{0}'>{0:1$u}</span>\n", i, cols);
1311+
}
1312+
write!(fmt.buf, "</pre>");
1313+
write!(fmt.buf, "<pre class='rust'>");
1314+
write!(fmt.buf, "{}", Escape(s.as_slice()));
1315+
write!(fmt.buf, "</pre>");
1316+
}
1317+
}

src/librustdoc/html/static/main.css

+3
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,9 @@ body {
119119
.content h1, .content h2 { margin-left: -20px; }
120120
.content pre { padding: 20px; }
121121

122+
.content pre.line-numbers { float: left; border: none; }
123+
.line-numbers span { color: #c67e2d; }
124+
122125
.content .highlighted {
123126
cursor: pointer;
124127
color: #000 !important;

src/librustdoc/rustdoc.rs

+3-2
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,11 @@ pub mod core;
3434
pub mod doctree;
3535
pub mod fold;
3636
pub mod html {
37-
pub mod render;
37+
pub mod escape;
38+
pub mod format;
3839
pub mod layout;
3940
pub mod markdown;
40-
pub mod format;
41+
pub mod render;
4142
}
4243
pub mod passes;
4344
pub mod plugins;

0 commit comments

Comments
 (0)