Skip to content

Commit 2153293

Browse files
committed
auto merge of #12687 : alexcrichton/rust/issue-12681, r=huonw
This commit adds a appear-on-over link to all section headers to generated documentation. Each header also receives an id now, even those generated through markdown. The purpose of this is to provide easy to link to sections. This modifies the default header markdown generation because the default id added looks like "toc_NN" which is difficult to reconcile among all sections (by default each section gets a "toc_0" id), and it's also not very descriptive of where you're going. This chooses to adopt the github-style anchors by taking the contents of the title and hyphen-separating them (after lower casing). Closes #12681
2 parents 253dbcb + 31e7e67 commit 2153293

File tree

4 files changed

+105
-24
lines changed

4 files changed

+105
-24
lines changed

Diff for: src/librustdoc/html/markdown.rs

+69-6
Original file line numberDiff line numberDiff line change
@@ -28,14 +28,17 @@
2828

2929
use std::cast;
3030
use std::fmt;
31+
use std::intrinsics;
3132
use std::io;
3233
use std::libc;
34+
use std::local_data;
3335
use std::mem;
3436
use std::str;
35-
use std::intrinsics;
3637
use std::vec;
38+
use collections::HashMap;
3739

3840
use html::highlight;
41+
use html::escape::Escape;
3942

4043
/// A unit struct which has the `fmt::Show` trait implemented. When
4144
/// formatted, this struct will emit the HTML corresponding to the rendered
@@ -52,8 +55,11 @@ static MKDEXT_STRIKETHROUGH: libc::c_uint = 1 << 4;
5255
type sd_markdown = libc::c_void; // this is opaque to us
5356

5457
struct sd_callbacks {
55-
blockcode: extern "C" fn(*buf, *buf, *buf, *libc::c_void),
56-
other: [libc::size_t, ..25],
58+
blockcode: Option<extern "C" fn(*buf, *buf, *buf, *libc::c_void)>,
59+
blockquote: Option<extern "C" fn(*buf, *buf, *libc::c_void)>,
60+
blockhtml: Option<extern "C" fn(*buf, *buf, *libc::c_void)>,
61+
header: Option<extern "C" fn(*buf, *buf, libc::c_int, *libc::c_void)>,
62+
other: [libc::size_t, ..22],
5763
}
5864

5965
struct html_toc_data {
@@ -115,6 +121,8 @@ fn stripped_filtered_line<'a>(s: &'a str) -> Option<&'a str> {
115121
}
116122
}
117123

124+
local_data_key!(used_header_map: HashMap<~str, uint>)
125+
118126
pub fn render(w: &mut io::Writer, s: &str) -> fmt::Result {
119127
extern fn block(ob: *buf, text: *buf, lang: *buf, opaque: *libc::c_void) {
120128
unsafe {
@@ -155,6 +163,45 @@ pub fn render(w: &mut io::Writer, s: &str) -> fmt::Result {
155163
}
156164
}
157165

166+
extern fn header(ob: *buf, text: *buf, level: libc::c_int,
167+
_opaque: *libc::c_void) {
168+
// sundown does this, we may as well too
169+
"\n".with_c_str(|p| unsafe { bufputs(ob, p) });
170+
171+
// Extract the text provided
172+
let s = if text.is_null() {
173+
~""
174+
} else {
175+
unsafe {
176+
str::raw::from_buf_len((*text).data, (*text).size as uint)
177+
}
178+
};
179+
180+
// Transform the contents of the header into a hyphenated string
181+
let id = s.words().map(|s| {
182+
match s.to_ascii_opt() {
183+
Some(s) => s.to_lower().into_str(),
184+
None => s.to_owned()
185+
}
186+
}).to_owned_vec().connect("-");
187+
188+
// Make sure our hyphenated ID is unique for this page
189+
let id = local_data::get_mut(used_header_map, |map| {
190+
let map = map.unwrap();
191+
match map.find_mut(&id) {
192+
None => {}
193+
Some(a) => { *a += 1; return format!("{}-{}", id, *a - 1) }
194+
}
195+
map.insert(id.clone(), 1);
196+
id.clone()
197+
});
198+
199+
// Render the HTML
200+
let text = format!(r#"<h{lvl} id="{id}">{}</h{lvl}>"#,
201+
Escape(s.as_slice()), lvl = level, id = id);
202+
text.with_c_str(|p| unsafe { bufputs(ob, p) });
203+
}
204+
158205
// This code is all lifted from examples/sundown.c in the sundown repo
159206
unsafe {
160207
let ob = bufnew(OUTPUT_UNIT);
@@ -175,9 +222,10 @@ pub fn render(w: &mut io::Writer, s: &str) -> fmt::Result {
175222
sdhtml_renderer(&callbacks, &options, 0);
176223
let opaque = my_opaque {
177224
opt: options,
178-
dfltblk: callbacks.blockcode,
225+
dfltblk: callbacks.blockcode.unwrap(),
179226
};
180-
callbacks.blockcode = block;
227+
callbacks.blockcode = Some(block);
228+
callbacks.header = Some(header);
181229
let markdown = sd_markdown_new(extensions, 16, &callbacks,
182230
&opaque as *my_opaque as *libc::c_void);
183231

@@ -225,7 +273,10 @@ pub fn find_testable_code(doc: &str, tests: &mut ::test::Collector) {
225273
MKDEXT_FENCED_CODE | MKDEXT_AUTOLINK |
226274
MKDEXT_STRIKETHROUGH;
227275
let callbacks = sd_callbacks {
228-
blockcode: block,
276+
blockcode: Some(block),
277+
blockquote: None,
278+
blockhtml: None,
279+
header: None,
229280
other: mem::init()
230281
};
231282

@@ -239,6 +290,18 @@ pub fn find_testable_code(doc: &str, tests: &mut ::test::Collector) {
239290
}
240291
}
241292

293+
/// By default this markdown renderer generates anchors for each header in the
294+
/// rendered document. The anchor name is the contents of the header spearated
295+
/// by hyphens, and a task-local map is used to disambiguate among duplicate
296+
/// headers (numbers are appended).
297+
///
298+
/// This method will reset the local table for these headers. This is typically
299+
/// used at the beginning of rendering an entire HTML page to reset from the
300+
/// previous state (if any).
301+
pub fn reset_headers() {
302+
local_data::set(used_header_map, HashMap::new())
303+
}
304+
242305
impl<'a> fmt::Show for Markdown<'a> {
243306
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
244307
let Markdown(md) = *self;

Diff for: src/librustdoc/html/render.rs

+22-18
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ use doctree;
5252
use fold::DocFolder;
5353
use html::format::{VisSpace, Method, PuritySpace};
5454
use html::layout;
55+
use html::markdown;
5556
use html::markdown::Markdown;
5657
use html::highlight;
5758

@@ -749,6 +750,8 @@ impl Context {
749750
title: title,
750751
};
751752

753+
markdown::reset_headers();
754+
752755
// We have a huge number of calls to write, so try to alleviate some
753756
// of the pain by using a buffered writer instead of invoking the
754757
// write sycall all the time.
@@ -1001,24 +1004,25 @@ fn item_module(w: &mut Writer, cx: &Context,
10011004
try!(write!(w, "</table>"));
10021005
}
10031006
curty = myty;
1004-
try!(write!(w, "<h2>{}</h2>\n<table>", match myitem.inner {
1005-
clean::ModuleItem(..) => "Modules",
1006-
clean::StructItem(..) => "Structs",
1007-
clean::EnumItem(..) => "Enums",
1008-
clean::FunctionItem(..) => "Functions",
1009-
clean::TypedefItem(..) => "Type Definitions",
1010-
clean::StaticItem(..) => "Statics",
1011-
clean::TraitItem(..) => "Traits",
1012-
clean::ImplItem(..) => "Implementations",
1013-
clean::ViewItemItem(..) => "Reexports",
1014-
clean::TyMethodItem(..) => "Type Methods",
1015-
clean::MethodItem(..) => "Methods",
1016-
clean::StructFieldItem(..) => "Struct Fields",
1017-
clean::VariantItem(..) => "Variants",
1018-
clean::ForeignFunctionItem(..) => "Foreign Functions",
1019-
clean::ForeignStaticItem(..) => "Foreign Statics",
1020-
clean::MacroItem(..) => "Macros",
1021-
}));
1007+
let (short, name) = match myitem.inner {
1008+
clean::ModuleItem(..) => ("modules", "Modules"),
1009+
clean::StructItem(..) => ("structs", "Structs"),
1010+
clean::EnumItem(..) => ("enums", "Enums"),
1011+
clean::FunctionItem(..) => ("functions", "Functions"),
1012+
clean::TypedefItem(..) => ("types", "Type Definitions"),
1013+
clean::StaticItem(..) => ("statics", "Statics"),
1014+
clean::TraitItem(..) => ("traits", "Traits"),
1015+
clean::ImplItem(..) => ("impls", "Implementations"),
1016+
clean::ViewItemItem(..) => ("reexports", "Reexports"),
1017+
clean::TyMethodItem(..) => ("tymethods", "Type Methods"),
1018+
clean::MethodItem(..) => ("methods", "Methods"),
1019+
clean::StructFieldItem(..) => ("fields", "Struct Fields"),
1020+
clean::VariantItem(..) => ("variants", "Variants"),
1021+
clean::ForeignFunctionItem(..) => ("ffi-fns", "Foreign Functions"),
1022+
clean::ForeignStaticItem(..) => ("ffi-statics", "Foreign Statics"),
1023+
clean::MacroItem(..) => ("macros", "Macros"),
1024+
};
1025+
try!(write!(w, "<h2 id='{}'>{}</h2>\n<table>", short, name));
10221026
}
10231027

10241028
match myitem.inner {

Diff for: src/librustdoc/html/static/main.css

+7
Original file line numberDiff line numberDiff line change
@@ -319,3 +319,10 @@ pre.rust .macro, pre.rust .macro-nonterminal { color: #3E999f; }
319319
pre.rust .string { color: #718C00; }
320320
pre.rust .lifetime { color: #C13928; }
321321
pre.rust .attribute, pre.rust .attribute .ident { color: #C82829; }
322+
323+
h1 a.anchor,
324+
h2 a.anchor,
325+
h3 a.anchor { display: none; }
326+
h1:hover a.anchor,
327+
h2:hover a.anchor,
328+
h3:hover a.anchor { display: inline-block; }

Diff for: src/librustdoc/html/static/main.js

+7
Original file line numberDiff line numberDiff line change
@@ -599,4 +599,11 @@
599599
}
600600

601601
initSearch(searchIndex);
602+
603+
$.each($('h1, h2, h3'), function(idx, element) {
604+
if ($(element).attr('id') != undefined) {
605+
$(element).append('<a href="#' + $(element).attr('id') + '" ' +
606+
'class="anchor"> § </a>');
607+
}
608+
});
602609
}());

0 commit comments

Comments
 (0)