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 std
::io
::prelude
::*;
20 use syntax
::parse
::lexer
;
21 use syntax
::parse
::token
;
24 /// Highlights some source code, returning the HTML output.
25 pub fn highlight(src
: &str, class
: Option
<&str>, id
: Option
<&str>) -> String
{
26 debug
!("highlighting: ================\n{}\n==============", src
);
27 let sess
= parse
::new_parse_sess();
28 let fm
= parse
::string_to_filemap(&sess
,
30 "<stdin>".to_string());
32 let mut out
= Vec
::new();
34 lexer
::StringReader
::new(&sess
.span_diagnostic
, fm
),
38 String
::from_utf8_lossy(&out
[..]).into_owned()
41 /// Exhausts the `lexer` writing the output into `out`.
43 /// The general structure for this method is to iterate over each token,
44 /// possibly giving it an HTML span with a class specifying what flavor of token
45 /// it's used. All source code emission is done as slices from the source map,
46 /// not from the tokens themselves, in order to stay true to the original
48 fn doit(sess
: &parse
::ParseSess
, mut lexer
: lexer
::StringReader
,
49 class
: Option
<&str>, id
: Option
<&str>,
50 out
: &mut Write
) -> io
::Result
<()> {
51 use syntax
::parse
::lexer
::Reader
;
53 try
!(write
!(out
, "<pre "));
55 Some(id
) => try
!(write
!(out
, "id='{}' ", id
)),
58 try
!(write
!(out
, "class='rust {}'>\n", class
.unwrap_or("")));
59 let mut is_attribute
= false;
60 let mut is_macro
= false;
61 let mut is_macro_nonterminal
= false;
63 let next
= lexer
.next_token();
65 let snip
= |sp
| sess
.span_diagnostic
.cm
.span_to_snippet(sp
).unwrap();
67 if next
.tok
== token
::Eof { break }
69 let klass
= match next
.tok
{
70 token
::Whitespace
=> {
71 try
!(write
!(out
, "{}", Escape(&snip(next
.sp
))));
75 try
!(write
!(out
, "<span class='comment'>{}</span>",
76 Escape(&snip(next
.sp
))));
79 token
::Shebang(s
) => {
80 try
!(write
!(out
, "{}", Escape(s
.as_str())));
83 // If this '&' token is directly adjacent to another token, assume
84 // that it's the address-of operator instead of the and-operator.
85 // This allows us to give all pointers their own class (`Box` and
87 token
::BinOp(token
::And
) if lexer
.peek().sp
.lo
== next
.sp
.hi
=> "kw-2",
88 token
::At
| token
::Tilde
=> "kw-2",
90 // consider this as part of a macro invocation if there was a
92 token
::Not
if is_macro
=> { is_macro = false; "macro" }
95 token
::Eq
| token
::Lt
| token
::Le
| token
::EqEq
| token
::Ne
| token
::Ge
| token
::Gt
|
96 token
::AndAnd
| token
::OrOr
| token
::Not
| token
::BinOp(..) | token
::RArrow
|
97 token
::BinOpEq(..) | token
::FatArrow
=> "op",
99 // miscellaneous, no highlighting
100 token
::Dot
| token
::DotDot
| token
::DotDotDot
| token
::Comma
| token
::Semi
|
101 token
::Colon
| token
::ModSep
| token
::LArrow
| token
::OpenDelim(_
) |
102 token
::CloseDelim(token
::Brace
) | token
::CloseDelim(token
::Paren
) |
103 token
::Question
=> "",
105 if lexer
.peek().tok
.is_ident() {
106 is_macro_nonterminal
= true;
113 // This is the start of an attribute. We're going to want to
114 // continue highlighting it as an attribute until the ending ']' is
115 // seen, so skip out early. Down below we terminate the attribute
116 // span when we see the ']'.
119 try
!(write
!(out
, r
"<span class='attribute'>#"));
122 token
::CloseDelim(token
::Bracket
) => {
124 is_attribute
= false;
125 try
!(write
!(out
, "]</span>"));
132 token
::Literal(lit
, _suf
) => {
135 token
::Byte(..) | token
::Char(..) |
136 token
::Binary(..) | token
::BinaryRaw(..) |
137 token
::Str_(..) | token
::StrRaw(..) => "string",
140 token
::Integer(..) | token
::Float(..) => "number",
144 // keywords are also included in the identifier set
145 token
::Ident(ident
, _is_mod_sep
) => {
146 match &token
::get_ident(ident
)[..] {
147 "ref" | "mut" => "kw-2",
150 "false" | "true" => "boolval",
152 "Option" | "Result" => "prelude-ty",
153 "Some" | "None" | "Ok" | "Err" => "prelude-val",
155 _
if next
.tok
.is_any_keyword() => "kw",
157 if is_macro_nonterminal
{
158 is_macro_nonterminal
= false;
160 } else if lexer
.peek().tok
== token
::Not
{
170 // Special macro vars are like keywords
171 token
::SpecialVarNt(_
) => "kw-2",
173 token
::Lifetime(..) => "lifetime",
174 token
::DocComment(..) => "doccomment",
175 token
::Underscore
| token
::Eof
| token
::Interpolated(..) |
176 token
::MatchNt(..) | token
::SubstNt(..) => "",
179 // as mentioned above, use the original source code instead of
180 // stringifying this token
181 let snip
= sess
.span_diagnostic
.cm
.span_to_snippet(next
.sp
).unwrap();
183 try
!(write
!(out
, "{}", Escape(&snip
)));
185 try
!(write
!(out
, "<span class='{}'>{}</span>", klass
,
190 write
!(out
, "</pre>\n")