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(' § ');
+ }
+ });
}());