Skip to content

Commit 8786405

Browse files
committed
auto merge of #12416 : alexcrichton/rust/highlight, r=huonw
This adds simple syntax highlighting based off libsyntax's lexer to be sure to stay up to date with rust's grammar. Some of the highlighting is a bit ad-hoc, but it definitely seems to get the job done! This currently doesn't highlight rustdoc-rendered function signatures and structs that are emitted to each page because the colors already signify what's clickable and I think we'd have to figure out a different scheme before colorizing them. This does, however, colorize all code examples and source code. Closes #11393
2 parents 551da06 + ad9e26d commit 8786405

File tree

8 files changed

+239
-26
lines changed

8 files changed

+239
-26
lines changed

src/librustdoc/html/highlight.rs

+174
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
// Copyright 2014 The Rust Project Developers. See the COPYRIGHT
2+
// file at the top-level directory of this distribution and at
3+
// http://rust-lang.org/COPYRIGHT.
4+
//
5+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8+
// option. This file may not be copied, modified, or distributed
9+
// except according to those terms.
10+
11+
//! Basic html highlighting functionality
12+
//!
13+
//! This module uses libsyntax's lexer to provide token-based highlighting for
14+
//! the HTML documentation generated by rustdoc.
15+
16+
use std::str;
17+
use std::io;
18+
19+
use syntax::parse;
20+
use syntax::parse::lexer;
21+
use syntax::diagnostic;
22+
use syntax::codemap::{BytePos, Span};
23+
24+
use html::escape::Escape;
25+
26+
use t = syntax::parse::token;
27+
28+
/// Highlights some source code, returning the HTML output.
29+
pub fn highlight(src: &str) -> ~str {
30+
let sess = parse::new_parse_sess();
31+
let handler = diagnostic::mk_handler();
32+
let span_handler = diagnostic::mk_span_handler(handler, sess.cm);
33+
let fm = parse::string_to_filemap(sess, src.to_owned(), ~"<stdin>");
34+
35+
let mut out = io::MemWriter::new();
36+
doit(sess,
37+
lexer::new_string_reader(span_handler, fm),
38+
&mut out).unwrap();
39+
str::from_utf8_lossy(out.unwrap()).into_owned()
40+
}
41+
42+
/// Exhausts the `lexer` writing the output into `out`.
43+
///
44+
/// The general structure for this method is to iterate over each token,
45+
/// possibly giving it an HTML span with a class specifying what flavor of token
46+
/// it's used. All source code emission is done as slices from the source map,
47+
/// not from the tokens themselves, in order to stay true to the original
48+
/// source.
49+
fn doit(sess: @parse::ParseSess, lexer: lexer::StringReader,
50+
out: &mut Writer) -> io::IoResult<()> {
51+
use syntax::parse::lexer::Reader;
52+
53+
try!(write!(out, "<pre class='rust'>\n"));
54+
let mut last = BytePos(0);
55+
let mut is_attribute = false;
56+
let mut is_macro = false;
57+
loop {
58+
let next = lexer.next_token();
59+
let test = if next.tok == t::EOF {lexer.pos.get()} else {next.sp.lo};
60+
61+
// The lexer consumes all whitespace and non-doc-comments when iterating
62+
// between tokens. If this token isn't directly adjacent to our last
63+
// token, then we need to emit the whitespace/comment.
64+
//
65+
// If the gap has any '/' characters then we consider the whole thing a
66+
// comment. This will classify some whitespace as a comment, but that
67+
// doesn't matter too much for syntax highlighting purposes.
68+
if test > last {
69+
let snip = sess.cm.span_to_snippet(Span {
70+
lo: last,
71+
hi: test,
72+
expn_info: None,
73+
}).unwrap();
74+
if snip.contains("/") {
75+
try!(write!(out, "<span class='comment'>{}</span>",
76+
Escape(snip)));
77+
} else {
78+
try!(write!(out, "{}", Escape(snip)));
79+
}
80+
}
81+
last = next.sp.hi;
82+
if next.tok == t::EOF { break }
83+
84+
let klass = match next.tok {
85+
// If this '&' token is directly adjacent to another token, assume
86+
// that it's the address-of operator instead of the and-operator.
87+
// This allows us to give all pointers their own class (~ and @ are
88+
// below).
89+
t::BINOP(t::AND) if lexer.peek().sp.lo == next.sp.hi => "kw-2",
90+
t::AT | t::TILDE => "kw-2",
91+
92+
// consider this as part of a macro invocation if there was a
93+
// leading identifier
94+
t::NOT if is_macro => { is_macro = false; "macro" }
95+
96+
// operators
97+
t::EQ | t::LT | t::LE | t::EQEQ | t::NE | t::GE | t::GT |
98+
t::ANDAND | t::OROR | t::NOT | t::BINOP(..) | t::RARROW |
99+
t::BINOPEQ(..) | t::FAT_ARROW => "op",
100+
101+
// miscellaneous, no highlighting
102+
t::DOT | t::DOTDOT | t::DOTDOTDOT | t::COMMA | t::SEMI |
103+
t::COLON | t::MOD_SEP | t::LARROW | t::DARROW | t::LPAREN |
104+
t::RPAREN | t::LBRACKET | t::LBRACE | t::RBRACE |
105+
t::DOLLAR => "",
106+
107+
// This is the start of an attribute. We're going to want to
108+
// continue highlighting it as an attribute until the ending ']' is
109+
// seen, so skip out early. Down below we terminate the attribute
110+
// span when we see the ']'.
111+
t::POUND => {
112+
is_attribute = true;
113+
try!(write!(out, r"<span class='attribute'>\#"));
114+
continue
115+
}
116+
t::RBRACKET => {
117+
if is_attribute {
118+
is_attribute = false;
119+
try!(write!(out, "]</span>"));
120+
continue
121+
} else {
122+
""
123+
}
124+
}
125+
126+
// text literals
127+
t::LIT_CHAR(..) | t::LIT_STR(..) | t::LIT_STR_RAW(..) => "string",
128+
129+
// number literals
130+
t::LIT_INT(..) | t::LIT_UINT(..) | t::LIT_INT_UNSUFFIXED(..) |
131+
t::LIT_FLOAT(..) | t::LIT_FLOAT_UNSUFFIXED(..) => "number",
132+
133+
// keywords are also included in the identifier set
134+
t::IDENT(ident, _is_mod_sep) => {
135+
match t::get_ident(ident).get() {
136+
"ref" | "mut" => "kw-2",
137+
138+
"self" => "self",
139+
"false" | "true" => "boolval",
140+
141+
"Option" | "Result" => "prelude-ty",
142+
"Some" | "None" | "Ok" | "Err" => "prelude-val",
143+
144+
_ if t::is_any_keyword(&next.tok) => "kw",
145+
_ => {
146+
if lexer.peek().tok == t::NOT {
147+
is_macro = true;
148+
"macro"
149+
} else {
150+
"ident"
151+
}
152+
}
153+
}
154+
}
155+
156+
t::LIFETIME(..) => "lifetime",
157+
t::DOC_COMMENT(..) => "doccomment",
158+
t::UNDERSCORE | t::EOF | t::INTERPOLATED(..) => "",
159+
};
160+
161+
// as mentioned above, use the original source code instead of
162+
// stringifying this token
163+
let snip = sess.cm.span_to_snippet(next.sp).unwrap();
164+
if klass == "" {
165+
try!(write!(out, "{}", Escape(snip)));
166+
} else {
167+
try!(write!(out, "<span class='{}'>{}</span>", klass,
168+
Escape(snip)));
169+
}
170+
}
171+
172+
write!(out, "</pre>\n")
173+
}
174+

src/librustdoc/html/markdown.rs

+26-2
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ use std::str;
3535
use std::intrinsics;
3636
use std::vec;
3737

38+
use html::highlight;
39+
3840
/// A unit struct which has the `fmt::Show` trait implemented. When
3941
/// formatted, this struct will emit the HTML corresponding to the rendered
4042
/// version of the contained markdown string.
@@ -95,6 +97,7 @@ extern {
9597
fn sd_markdown_free(md: *sd_markdown);
9698

9799
fn bufnew(unit: libc::size_t) -> *buf;
100+
fn bufputs(b: *buf, c: *libc::c_char);
98101
fn bufrelease(b: *buf);
99102

100103
}
@@ -127,7 +130,27 @@ pub fn render(w: &mut io::Writer, s: &str) -> fmt::Result {
127130
asize: text.len() as libc::size_t,
128131
unit: 0,
129132
};
130-
(my_opaque.dfltblk)(ob, &buf, lang, opaque);
133+
let rendered = if lang.is_null() {
134+
false
135+
} else {
136+
vec::raw::buf_as_slice((*lang).data,
137+
(*lang).size as uint, |rlang| {
138+
let rlang = str::from_utf8(rlang).unwrap();
139+
if rlang.contains("notrust") {
140+
(my_opaque.dfltblk)(ob, &buf, lang, opaque);
141+
true
142+
} else {
143+
false
144+
}
145+
})
146+
};
147+
148+
if !rendered {
149+
let output = highlight::highlight(text).to_c_str();
150+
output.with_ref(|r| {
151+
bufputs(ob, r)
152+
})
153+
}
131154
})
132155
}
133156
}
@@ -181,7 +204,8 @@ pub fn find_testable_code(doc: &str, tests: &mut ::test::Collector) {
181204
vec::raw::buf_as_slice((*lang).data,
182205
(*lang).size as uint, |lang| {
183206
let s = str::from_utf8(lang).unwrap();
184-
(s.contains("should_fail"), s.contains("ignore"))
207+
(s.contains("should_fail"), s.contains("ignore") ||
208+
s.contains("notrust"))
185209
})
186210
};
187211
if ignore { return }

src/librustdoc/html/render.rs

+8-9
Original file line numberDiff line numberDiff line change
@@ -50,10 +50,10 @@ use syntax::parse::token::InternedString;
5050
use clean;
5151
use doctree;
5252
use fold::DocFolder;
53-
use html::escape::Escape;
5453
use html::format::{VisSpace, Method, PuritySpace};
5554
use html::layout;
5655
use html::markdown::Markdown;
56+
use html::highlight;
5757

5858
/// Major driving force in all rustdoc rendering. This contains information
5959
/// about where in the tree-like hierarchy rendering is occurring and controls
@@ -1091,7 +1091,8 @@ fn item_module(w: &mut Writer, cx: &Context,
10911091

10921092
fn item_function(w: &mut Writer, it: &clean::Item,
10931093
f: &clean::Function) -> fmt::Result {
1094-
try!(write!(w, "<pre class='fn'>{vis}{purity}fn {name}{generics}{decl}</pre>",
1094+
try!(write!(w, "<pre class='rust fn'>{vis}{purity}fn \
1095+
{name}{generics}{decl}</pre>",
10951096
vis = VisSpace(it.visibility),
10961097
purity = PuritySpace(f.purity),
10971098
name = it.name.get_ref().as_slice(),
@@ -1112,7 +1113,7 @@ fn item_trait(w: &mut Writer, it: &clean::Item,
11121113
}
11131114

11141115
// Output the trait definition
1115-
try!(write!(w, "<pre class='trait'>{}trait {}{}{} ",
1116+
try!(write!(w, "<pre class='rust trait'>{}trait {}{}{} ",
11161117
VisSpace(it.visibility),
11171118
it.name.get_ref().as_slice(),
11181119
t.generics,
@@ -1231,7 +1232,7 @@ fn render_method(w: &mut Writer, meth: &clean::Item) -> fmt::Result {
12311232

12321233
fn item_struct(w: &mut Writer, it: &clean::Item,
12331234
s: &clean::Struct) -> fmt::Result {
1234-
try!(write!(w, "<pre class='struct'>"));
1235+
try!(write!(w, "<pre class='rust struct'>"));
12351236
try!(render_struct(w, it, Some(&s.generics), s.struct_type, s.fields,
12361237
s.fields_stripped, "", true));
12371238
try!(write!(w, "</pre>"));
@@ -1255,7 +1256,7 @@ fn item_struct(w: &mut Writer, it: &clean::Item,
12551256
}
12561257

12571258
fn item_enum(w: &mut Writer, it: &clean::Item, e: &clean::Enum) -> fmt::Result {
1258-
try!(write!(w, "<pre class='enum'>{}enum {}{}",
1259+
try!(write!(w, "<pre class='rust enum'>{}enum {}{}",
12591260
VisSpace(it.visibility),
12601261
it.name.get_ref().as_slice(),
12611262
e.generics));
@@ -1532,7 +1533,7 @@ fn render_impl(w: &mut Writer, i: &clean::Impl,
15321533

15331534
fn item_typedef(w: &mut Writer, it: &clean::Item,
15341535
t: &clean::Typedef) -> fmt::Result {
1535-
try!(write!(w, "<pre class='typedef'>type {}{} = {};</pre>",
1536+
try!(write!(w, "<pre class='rust typedef'>type {}{} = {};</pre>",
15361537
it.name.get_ref().as_slice(),
15371538
t.generics,
15381539
t.type_));
@@ -1625,9 +1626,7 @@ impl<'a> fmt::Show for Source<'a> {
16251626
try!(write!(fmt.buf, "<span id='{0:u}'>{0:1$u}</span>\n", i, cols));
16261627
}
16271628
try!(write!(fmt.buf, "</pre>"));
1628-
try!(write!(fmt.buf, "<pre class='rust'>"));
1629-
try!(write!(fmt.buf, "{}", Escape(s.as_slice())));
1630-
try!(write!(fmt.buf, "</pre>"));
1629+
try!(write!(fmt.buf, "{}", highlight::highlight(s.as_slice())));
16311630
Ok(())
16321631
}
16331632
}

src/librustdoc/html/static/main.css

+15
Original file line numberDiff line numberDiff line change
@@ -303,3 +303,18 @@ a {
303303
.stability.Locked { border-color: #0084B6; color: #00668c; }
304304

305305
:target { background: #FDFFD3; }
306+
307+
pre.rust .kw { color: #cc782f; }
308+
pre.rust .kw-2 { color: #3bbb33; }
309+
pre.rust .prelude-ty { color: #3bbb33; }
310+
pre.rust .number { color: #c13928; }
311+
pre.rust .self { color: #c13928; }
312+
pre.rust .boolval { color: #c13928; }
313+
pre.rust .prelude-val { color: #c13928; }
314+
pre.rust .op { color: #cc782f; }
315+
pre.rust .comment { color: #533add; }
316+
pre.rust .doccomment { color: #d343d0; }
317+
pre.rust .macro { color: #d343d0; }
318+
pre.rust .string { color: #c13928; }
319+
pre.rust .lifetime { color: #d343d0; }
320+
pre.rust .attribute { color: #d343d0 !important; }

src/librustdoc/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ pub mod core;
3838
pub mod doctree;
3939
pub mod fold;
4040
pub mod html {
41+
pub mod highlight;
4142
pub mod escape;
4243
pub mod format;
4344
pub mod layout;

0 commit comments

Comments
 (0)