diff --git a/src/librustdoc/clean.rs b/src/librustdoc/clean.rs index 623ac82486d..75bc442bd54 100644 --- a/src/librustdoc/clean.rs +++ b/src/librustdoc/clean.rs @@ -84,7 +84,7 @@ impl Clean for visit_ast::RustdocVisitor { #[deriving(Clone, Encodable, Decodable)] pub struct Item { /// Stringified span - source: ~str, + source: Span, /// Not everything has a name. E.g., impls name: Option<~str>, attrs: ~[Attribute], @@ -737,10 +737,28 @@ impl Clean for ast::variant_kind { } } -impl Clean<~str> for syntax::codemap::Span { - fn clean(&self) -> ~str { - let cm = local_data::get(super::ctxtkey, |x| x.unwrap().clone()).sess.codemap; - cm.span_to_str(*self) +#[deriving(Clone, Encodable, Decodable)] +pub struct Span { + filename: ~str, + loline: uint, + locol: uint, + hiline: uint, + hicol: uint, +} + +impl Clean for syntax::codemap::Span { + fn clean(&self) -> Span { + let cm = local_data::get(super::ctxtkey, |x| *x.unwrap()).sess.codemap; + let filename = cm.span_to_filename(*self); + let lo = cm.lookup_char_pos(self.lo); + let hi = cm.lookup_char_pos(self.hi); + Span { + filename: filename.to_owned(), + loline: lo.line, + locol: *lo.col, + hiline: hi.line, + hicol: *hi.col, + } } } @@ -1034,7 +1052,7 @@ trait ToSource { impl ToSource for syntax::codemap::Span { fn to_src(&self) -> ~str { - debug!("converting span %s to snippet", self.clean()); + debug!("converting span %? to snippet", self.clean()); let cm = local_data::get(super::ctxtkey, |x| x.unwrap().clone()).sess.codemap.clone(); let sn = match cm.span_to_snippet(*self) { Some(x) => x, diff --git a/src/librustdoc/html/escape.rs b/src/librustdoc/html/escape.rs new file mode 100644 index 00000000000..076d43e2c12 --- /dev/null +++ b/src/librustdoc/html/escape.rs @@ -0,0 +1,44 @@ +// Copyright 2013 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 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use std::fmt; + +pub struct Escape<'self>(&'self str); + +impl<'self> fmt::Default for Escape<'self> { + fn fmt(s: &Escape<'self>, fmt: &mut fmt::Formatter) { + // Because the internet is always right, turns out there's not that many + // characters to escape: http://stackoverflow.com/questions/7381974 + let pile_o_bits = s.as_slice(); + let mut last = 0; + for (i, ch) in s.byte_iter().enumerate() { + match ch as char { + '<' | '>' | '&' | '\'' | '"' => { + fmt.buf.write(pile_o_bits.slice(last, i).as_bytes()); + let s = match ch as char { + '>' => ">", + '<' => "<", + '&' => "&", + '\'' => "'", + '"' => """, + _ => unreachable!() + }; + fmt.buf.write(s.as_bytes()); + last = i + 1; + } + _ => {} + } + } + + if last < s.len() { + fmt.buf.write(pile_o_bits.slice_from(last).as_bytes()); + } + } +} diff --git a/src/librustdoc/html/layout.rs b/src/librustdoc/html/layout.rs index ac3e8a07d46..d29bf5ed55b 100644 --- a/src/librustdoc/html/layout.rs +++ b/src/librustdoc/html/layout.rs @@ -67,7 +67,7 @@ pub fn render(
{content}
- +
diff --git a/src/librustdoc/html/render.rs b/src/librustdoc/html/render.rs index 24437c3a97f..4bb255bf5aa 100644 --- a/src/librustdoc/html/render.rs +++ b/src/librustdoc/html/render.rs @@ -12,12 +12,14 @@ use std::cell::Cell; use std::comm::{SharedPort, SharedChan}; use std::comm; use std::fmt; -use std::hashmap::HashMap; +use std::hashmap::{HashMap, HashSet}; use std::local_data; use std::rt::io::buffered::BufferedWriter; use std::rt::io::file::{FileInfo, DirectoryInfo}; use std::rt::io::file; use std::rt::io; +use std::rt::io::Reader; +use std::str; use std::task; use std::unstable::finally::Finally; use std::util; @@ -33,6 +35,7 @@ use syntax::attr; use clean; use doctree; use fold::DocFolder; +use html::escape::Escape; use html::format::{VisSpace, Method, PuritySpace}; use html::layout; use html::markdown::Markdown; @@ -44,6 +47,7 @@ pub struct Context { dst: Path, layout: layout::Layout, sidebar: HashMap<~str, ~[~str]>, + include_sources: bool, } enum Implementor { @@ -68,6 +72,12 @@ struct Cache { priv search_index: ~[IndexItem], } +struct SourceCollector<'self> { + seen: HashSet<~str>, + dst: Path, + cx: &'self Context, +} + struct Item<'self> { cx: &'self Context, item: &'self clean::Item, } struct Sidebar<'self> { cx: &'self Context, item: &'self clean::Item, } @@ -79,6 +89,8 @@ struct IndexItem { parent: Option, } +struct Source<'self>(&'self str); + local_data_key!(pub cache_key: RWArc) local_data_key!(pub current_location_key: ~[~str]) @@ -94,6 +106,7 @@ pub fn run(mut crate: clean::Crate, dst: Path) { favicon: ~"", crate: crate.name.clone(), }, + include_sources: true, }; mkdir(&cx.dst); @@ -107,6 +120,9 @@ pub fn run(mut crate: clean::Crate, dst: Path) { clean::NameValue(~"html_logo_url", ref s) => { cx.layout.logo = s.to_owned(); } + clean::Word(~"html_no_source") => { + cx.include_sources = false; + } _ => {} } } @@ -162,6 +178,19 @@ pub fn run(mut crate: clean::Crate, dst: Path) { w.flush(); } + if cx.include_sources { + let dst = cx.dst.push("src"); + mkdir(&dst); + let dst = dst.push(crate.name); + mkdir(&dst); + let mut folder = SourceCollector { + dst: dst, + seen: HashSet::new(), + cx: &cx, + }; + crate = folder.fold_crate(crate); + } + // Now render the whole crate. cx.crate(crate, cache); } @@ -183,7 +212,80 @@ fn mkdir(path: &Path) { } } -impl<'self> DocFolder for Cache { +fn clean_srcpath(src: &str, f: &fn(&str)) { + let p = Path(src); + for c in p.components.iter() { + if "." == *c { + loop + } + if ".." == *c { + f("up"); + } else { + f(c.as_slice()) + } + } +} + +impl<'self> DocFolder for SourceCollector<'self> { + fn fold_item(&mut self, item: clean::Item) -> Option { + if !self.seen.contains(&item.source.filename) { + self.emit_source(item.source.filename); + self.seen.insert(item.source.filename.clone()); + } + self.fold_item_recur(item) + } +} + +impl<'self> SourceCollector<'self> { + fn emit_source(&self, filename: &str) { + let p = Path(filename); + + // Read the contents of the file + let mut contents = ~[]; + { + let mut buf = [0, ..1024]; + let r = do io::io_error::cond.trap(|_| {}).inside { + p.open_reader(io::Open) + }; + // If we couldn't open this file, then just returns because it + // probably means that it's some standard library macro thing and we + // can't have the source to it anyway. + let mut r = match r { Some(r) => r, None => return }; + + // read everything + loop { + match r.read(buf) { + Some(n) => contents.push_all(buf.slice_to(n)), + None => break + } + } + } + let contents = str::from_utf8_owned(contents); + + // Create the intermediate directories + let mut cur = self.dst.clone(); + let mut root_path = ~"../../"; + do clean_srcpath(p.pop().to_str()) |component| { + cur = cur.push(component); + mkdir(&cur); + root_path.push_str("../"); + } + + let dst = cur.push(*p.components.last() + ".html"); + let mut w = dst.open_writer(io::CreateOrTruncate); + + let title = format!("{} -- source", *dst.components.last()); + let page = layout::Page { + title: title, + ty: "source", + root_path: root_path, + }; + layout::render(&mut w as &mut io::Writer, &self.cx.layout, + &page, &(""), &Source(contents.as_slice())); + } +} + +impl DocFolder for Cache { fn fold_item(&mut self, item: clean::Item) -> Option { // Register any generics to their corresponding string. This is used // when pretty-printing types @@ -380,7 +482,6 @@ impl Context { return ret; } - /// Processes fn crate(self, mut crate: clean::Crate, cache: Cache) { enum Work { Die, @@ -565,6 +666,20 @@ impl<'self> fmt::Default for Item<'self> { None => {} } + if it.cx.include_sources { + let mut path = ~[]; + do clean_srcpath(it.item.source.filename) |component| { + path.push(component.to_owned()); + } + write!(fmt.buf, + "[src]", + root = it.cx.root_path, + crate = it.cx.layout.crate, + path = path.connect("/"), + line = it.item.source.loline); + } + // Write the breadcrumb trail header for the top write!(fmt.buf, "

"); match it.item.inner { @@ -1180,3 +1295,23 @@ fn build_sidebar(m: &clean::Module) -> HashMap<~str, ~[~str]> { } return map; } + +impl<'self> fmt::Default for Source<'self> { + fn fmt(s: &Source<'self>, fmt: &mut fmt::Formatter) { + let lines = s.line_iter().len(); + let mut cols = 0; + let mut tmp = lines; + while tmp > 0 { + cols += 1; + tmp /= 10; + } + write!(fmt.buf, "
");
+        for i in range(1, lines + 1) {
+            write!(fmt.buf, "{0:1$u}\n", i, cols);
+        }
+        write!(fmt.buf, "
"); + write!(fmt.buf, "
");
+        write!(fmt.buf, "{}", Escape(s.as_slice()));
+        write!(fmt.buf, "
"); + } +} diff --git a/src/librustdoc/html/static/main.css b/src/librustdoc/html/static/main.css index 11ca7a09311..2366a530a87 100644 --- a/src/librustdoc/html/static/main.css +++ b/src/librustdoc/html/static/main.css @@ -119,6 +119,9 @@ body { .content h1, .content h2 { margin-left: -20px; } .content pre { padding: 20px; } +.content pre.line-numbers { float: left; border: none; } +.line-numbers span { color: #c67e2d; } + .content .highlighted { cursor: pointer; color: #000 !important; diff --git a/src/librustdoc/rustdoc.rs b/src/librustdoc/rustdoc.rs index ccab6dad835..3cb37f5e15c 100644 --- a/src/librustdoc/rustdoc.rs +++ b/src/librustdoc/rustdoc.rs @@ -34,10 +34,11 @@ pub mod core; pub mod doctree; pub mod fold; pub mod html { - pub mod render; + pub mod escape; + pub mod format; pub mod layout; pub mod markdown; - pub mod format; + pub mod render; } pub mod passes; pub mod plugins;