about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--src/librustdoc/html/markdown.rs75
-rw-r--r--src/librustdoc/html/render.rs40
-rw-r--r--src/librustdoc/html/static/main.css7
-rw-r--r--src/librustdoc/html/static/main.js7
4 files changed, 105 insertions, 24 deletions
diff --git a/src/librustdoc/html/markdown.rs b/src/librustdoc/html/markdown.rs
index bc59f9e657a..1c692677590 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<extern "C" fn(*buf, *buf, *buf, *libc::c_void)>,
+    blockquote: Option<extern "C" fn(*buf, *buf, *libc::c_void)>,
+    blockhtml: Option<extern "C" fn(*buf, *buf, *libc::c_void)>,
+    header: Option<extern "C" fn(*buf, *buf, libc::c_int, *libc::c_void)>,
+    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#"<h{lvl} id="{id}">{}</h{lvl}>"#,
+                           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 0f5d01e1895..0c3e8354021 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, "</table>"));
             }
             curty = myty;
-            try!(write!(w, "<h2>{}</h2>\n<table>", 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, "<h2 id='{}'>{}</h2>\n<table>", short, name));
         }
 
         match myitem.inner {
diff --git a/src/librustdoc/html/static/main.css b/src/librustdoc/html/static/main.css
index 84c7d33652c..9a5cdaba33c 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 45a5819bf39..59970ac4508 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('<a href="#' + $(element).attr('id') + '" ' +
+                              'class="anchor"> § </a>');
+        }
+    });
 }());