diff --git a/src/librustdoc/html/markdown.rs b/src/librustdoc/html/markdown.rs index bc59f9e657aeb..1c6926775908a 100644 --- a/src/librustdoc/html/markdown.rs +++ b/src/librustdoc/html/markdown.rs @@ -28,14 +28,17 @@ use std::cast; use std::fmt; +use std::intrinsics; use std::io; use std::libc; +use std::local_data; use std::mem; use std::str; -use std::intrinsics; use std::vec; +use collections::HashMap; use html::highlight; +use html::escape::Escape; /// A unit struct which has the `fmt::Show` trait implemented. When /// formatted, this struct will emit the HTML corresponding to the rendered @@ -52,8 +55,11 @@ static MKDEXT_STRIKETHROUGH: libc::c_uint = 1 << 4; type sd_markdown = libc::c_void; // this is opaque to us struct sd_callbacks { - blockcode: extern "C" fn(*buf, *buf, *buf, *libc::c_void), - other: [libc::size_t, ..25], + blockcode: Option, + blockquote: Option, + blockhtml: Option, + header: Option, + other: [libc::size_t, ..22], } struct html_toc_data { @@ -115,6 +121,8 @@ fn stripped_filtered_line<'a>(s: &'a str) -> Option<&'a str> { } } +local_data_key!(used_header_map: HashMap<~str, uint>) + pub fn render(w: &mut io::Writer, s: &str) -> fmt::Result { extern fn block(ob: *buf, text: *buf, lang: *buf, opaque: *libc::c_void) { unsafe { @@ -155,6 +163,45 @@ pub fn render(w: &mut io::Writer, s: &str) -> fmt::Result { } } + extern fn header(ob: *buf, text: *buf, level: libc::c_int, + _opaque: *libc::c_void) { + // sundown does this, we may as well too + "\n".with_c_str(|p| unsafe { bufputs(ob, p) }); + + // Extract the text provided + let s = if text.is_null() { + ~"" + } else { + unsafe { + str::raw::from_buf_len((*text).data, (*text).size as uint) + } + }; + + // Transform the contents of the header into a hyphenated string + let id = s.words().map(|s| { + match s.to_ascii_opt() { + Some(s) => s.to_lower().into_str(), + None => s.to_owned() + } + }).to_owned_vec().connect("-"); + + // Make sure our hyphenated ID is unique for this page + let id = local_data::get_mut(used_header_map, |map| { + let map = map.unwrap(); + match map.find_mut(&id) { + None => {} + Some(a) => { *a += 1; return format!("{}-{}", id, *a - 1) } + } + map.insert(id.clone(), 1); + id.clone() + }); + + // Render the HTML + let text = format!(r#"{}"#, + Escape(s.as_slice()), lvl = level, id = id); + text.with_c_str(|p| unsafe { bufputs(ob, p) }); + } + // This code is all lifted from examples/sundown.c in the sundown repo unsafe { let ob = bufnew(OUTPUT_UNIT); @@ -175,9 +222,10 @@ pub fn render(w: &mut io::Writer, s: &str) -> fmt::Result { sdhtml_renderer(&callbacks, &options, 0); let opaque = my_opaque { opt: options, - dfltblk: callbacks.blockcode, + dfltblk: callbacks.blockcode.unwrap(), }; - callbacks.blockcode = block; + callbacks.blockcode = Some(block); + callbacks.header = Some(header); let markdown = sd_markdown_new(extensions, 16, &callbacks, &opaque as *my_opaque as *libc::c_void); @@ -225,7 +273,10 @@ pub fn find_testable_code(doc: &str, tests: &mut ::test::Collector) { MKDEXT_FENCED_CODE | MKDEXT_AUTOLINK | MKDEXT_STRIKETHROUGH; let callbacks = sd_callbacks { - blockcode: block, + blockcode: Some(block), + blockquote: None, + blockhtml: None, + header: None, other: mem::init() }; @@ -239,6 +290,18 @@ pub fn find_testable_code(doc: &str, tests: &mut ::test::Collector) { } } +/// By default this markdown renderer generates anchors for each header in the +/// rendered document. The anchor name is the contents of the header spearated +/// by hyphens, and a task-local map is used to disambiguate among duplicate +/// headers (numbers are appended). +/// +/// This method will reset the local table for these headers. This is typically +/// used at the beginning of rendering an entire HTML page to reset from the +/// previous state (if any). +pub fn reset_headers() { + local_data::set(used_header_map, HashMap::new()) +} + impl<'a> fmt::Show for Markdown<'a> { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { let Markdown(md) = *self; diff --git a/src/librustdoc/html/render.rs b/src/librustdoc/html/render.rs index 0f5d01e18950e..0c3e835402136 100644 --- a/src/librustdoc/html/render.rs +++ b/src/librustdoc/html/render.rs @@ -52,6 +52,7 @@ use doctree; use fold::DocFolder; use html::format::{VisSpace, Method, PuritySpace}; use html::layout; +use html::markdown; use html::markdown::Markdown; use html::highlight; @@ -749,6 +750,8 @@ impl Context { title: title, }; + markdown::reset_headers(); + // We have a huge number of calls to write, so try to alleviate some // of the pain by using a buffered writer instead of invoking the // write sycall all the time. @@ -1001,24 +1004,25 @@ fn item_module(w: &mut Writer, cx: &Context, try!(write!(w, "")); } curty = myty; - try!(write!(w, "

{}

\n", match myitem.inner { - clean::ModuleItem(..) => "Modules", - clean::StructItem(..) => "Structs", - clean::EnumItem(..) => "Enums", - clean::FunctionItem(..) => "Functions", - clean::TypedefItem(..) => "Type Definitions", - clean::StaticItem(..) => "Statics", - clean::TraitItem(..) => "Traits", - clean::ImplItem(..) => "Implementations", - clean::ViewItemItem(..) => "Reexports", - clean::TyMethodItem(..) => "Type Methods", - clean::MethodItem(..) => "Methods", - clean::StructFieldItem(..) => "Struct Fields", - clean::VariantItem(..) => "Variants", - clean::ForeignFunctionItem(..) => "Foreign Functions", - clean::ForeignStaticItem(..) => "Foreign Statics", - clean::MacroItem(..) => "Macros", - })); + let (short, name) = match myitem.inner { + clean::ModuleItem(..) => ("modules", "Modules"), + clean::StructItem(..) => ("structs", "Structs"), + clean::EnumItem(..) => ("enums", "Enums"), + clean::FunctionItem(..) => ("functions", "Functions"), + clean::TypedefItem(..) => ("types", "Type Definitions"), + clean::StaticItem(..) => ("statics", "Statics"), + clean::TraitItem(..) => ("traits", "Traits"), + clean::ImplItem(..) => ("impls", "Implementations"), + clean::ViewItemItem(..) => ("reexports", "Reexports"), + clean::TyMethodItem(..) => ("tymethods", "Type Methods"), + clean::MethodItem(..) => ("methods", "Methods"), + clean::StructFieldItem(..) => ("fields", "Struct Fields"), + clean::VariantItem(..) => ("variants", "Variants"), + clean::ForeignFunctionItem(..) => ("ffi-fns", "Foreign Functions"), + clean::ForeignStaticItem(..) => ("ffi-statics", "Foreign Statics"), + clean::MacroItem(..) => ("macros", "Macros"), + }; + try!(write!(w, "

{}

\n
", short, name)); } match myitem.inner { diff --git a/src/librustdoc/html/static/main.css b/src/librustdoc/html/static/main.css index 84c7d33652c6c..9a5cdaba33c80 100644 --- a/src/librustdoc/html/static/main.css +++ b/src/librustdoc/html/static/main.css @@ -319,3 +319,10 @@ pre.rust .macro, pre.rust .macro-nonterminal { color: #3E999f; } pre.rust .string { color: #718C00; } pre.rust .lifetime { color: #C13928; } pre.rust .attribute, pre.rust .attribute .ident { color: #C82829; } + +h1 a.anchor, +h2 a.anchor, +h3 a.anchor { display: none; } +h1:hover a.anchor, +h2:hover a.anchor, +h3:hover a.anchor { display: inline-block; } diff --git a/src/librustdoc/html/static/main.js b/src/librustdoc/html/static/main.js index 45a5819bf3978..59970ac4508db 100644 --- a/src/librustdoc/html/static/main.js +++ b/src/librustdoc/html/static/main.js @@ -599,4 +599,11 @@ } initSearch(searchIndex); + + $.each($('h1, h2, h3'), function(idx, element) { + if ($(element).attr('id') != undefined) { + $(element).append(' § '); + } + }); }());