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.
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.
11 //! Basic html highlighting functionality
13 //! This module uses libsyntax's lexer to provide token-based highlighting for
14 //! the HTML documentation generated by rustdoc.
16 use html
::escape
::Escape
;
19 use syntax
::parse
::lexer
;
20 use syntax
::parse
::token
;
23 /// Highlights some source code, returning the HTML output.
24 pub fn highlight(src
: &str, class
: Option
<&str>, id
: Option
<&str>) -> String
{
25 debug
!("highlighting: ================\n{}\n==============", src
);
26 let sess
= parse
::new_parse_sess();
27 let fm
= parse
::string_to_filemap(&sess
,
29 "<stdin>".to_string());
31 let mut out
= Vec
::new();
33 lexer
::StringReader
::new(&sess
.span_diagnostic
, fm
),
37 String
::from_utf8_lossy(&out
[]).into_owned()
40 /// Exhausts the `lexer` writing the output into `out`.
42 /// The general structure for this method is to iterate over each token,
43 /// possibly giving it an HTML span with a class specifying what flavor of token
44 /// it's used. All source code emission is done as slices from the source map,
45 /// not from the tokens themselves, in order to stay true to the original
47 fn doit(sess
: &parse
::ParseSess
, mut lexer
: lexer
::StringReader
,
48 class
: Option
<&str>, id
: Option
<&str>,
49 out
: &mut Writer
) -> io
::IoResult
<()> {
50 use syntax
::parse
::lexer
::Reader
;
52 try
!(write
!(out
, "<pre "));
54 Some(id
) => try
!(write
!(out
, "id='{}' ", id
)),
57 try
!(write
!(out
, "class='rust {}'>\n", class
.unwrap_or("")));
58 let mut is_attribute
= false;
59 let mut is_macro
= false;
60 let mut is_macro_nonterminal
= false;
62 let next
= lexer
.next_token();
64 let snip
= |&: sp
| sess
.span_diagnostic
.cm
.span_to_snippet(sp
).unwrap();
66 if next
.tok
== token
::Eof { break }
68 let klass
= match next
.tok
{
69 token
::Whitespace
=> {
70 try
!(write
!(out
, "{}", Escape(snip(next
.sp
).as_slice())));
74 try
!(write
!(out
, "<span class='comment'>{}</span>",
75 Escape(snip(next
.sp
).as_slice())));
78 token
::Shebang(s
) => {
79 try
!(write
!(out
, "{}", Escape(s
.as_str())));
82 // If this '&' token is directly adjacent to another token, assume
83 // that it's the address-of operator instead of the and-operator.
84 // This allows us to give all pointers their own class (`Box` and
86 token
::BinOp(token
::And
) if lexer
.peek().sp
.lo
== next
.sp
.hi
=> "kw-2",
87 token
::At
| token
::Tilde
=> "kw-2",
89 // consider this as part of a macro invocation if there was a
91 token
::Not
if is_macro
=> { is_macro = false; "macro" }
94 token
::Eq
| token
::Lt
| token
::Le
| token
::EqEq
| token
::Ne
| token
::Ge
| token
::Gt
|
95 token
::AndAnd
| token
::OrOr
| token
::Not
| token
::BinOp(..) | token
::RArrow
|
96 token
::BinOpEq(..) | token
::FatArrow
=> "op",
98 // miscellaneous, no highlighting
99 token
::Dot
| token
::DotDot
| token
::DotDotDot
| token
::Comma
| token
::Semi
|
100 token
::Colon
| token
::ModSep
| token
::LArrow
| token
::OpenDelim(_
) |
101 token
::CloseDelim(token
::Brace
) | token
::CloseDelim(token
::Paren
) |
102 token
::Question
=> "",
104 if lexer
.peek().tok
.is_ident() {
105 is_macro_nonterminal
= true;
112 // This is the start of an attribute. We're going to want to
113 // continue highlighting it as an attribute until the ending ']' is
114 // seen, so skip out early. Down below we terminate the attribute
115 // span when we see the ']'.
118 try
!(write
!(out
, r
"<span class='attribute'>#"));
121 token
::CloseDelim(token
::Bracket
) => {
123 is_attribute
= false;
124 try
!(write
!(out
, "]</span>"));
131 token
::Literal(lit
, _suf
) => {
134 token
::Byte(..) | token
::Char(..) |
135 token
::Binary(..) | token
::BinaryRaw(..) |
136 token
::Str_(..) | token
::StrRaw(..) => "string",
139 token
::Integer(..) | token
::Float(..) => "number",
143 // keywords are also included in the identifier set
144 token
::Ident(ident
, _is_mod_sep
) => {
145 match token
::get_ident(ident
).get() {
146 "ref" | "mut" => "kw-2",
149 "false" | "true" => "boolval",
151 "Option" | "Result" => "prelude-ty",
152 "Some" | "None" | "Ok" | "Err" => "prelude-val",
154 _
if next
.tok
.is_any_keyword() => "kw",
156 if is_macro_nonterminal
{
157 is_macro_nonterminal
= false;
159 } else if lexer
.peek().tok
== token
::Not
{
169 // Special macro vars are like keywords
170 token
::SpecialVarNt(_
) => "kw-2",
172 token
::Lifetime(..) => "lifetime",
173 token
::DocComment(..) => "doccomment",
174 token
::Underscore
| token
::Eof
| token
::Interpolated(..) |
175 token
::MatchNt(..) | token
::SubstNt(..) => "",
178 // as mentioned above, use the original source code instead of
179 // stringifying this token
180 let snip
= sess
.span_diagnostic
.cm
.span_to_snippet(next
.sp
).unwrap();
182 try
!(write
!(out
, "{}", Escape(snip
.as_slice())));
184 try
!(write
!(out
, "<span class='{}'>{}</span>", klass
,
185 Escape(snip
.as_slice())));
189 write
!(out
, "</pre>\n")