]> git.lizzy.rs Git - rust.git/blob - src/librustdoc/html/highlight.rs
Remove leftover AwaitOrigin
[rust.git] / src / librustdoc / html / highlight.rs
1 //! Basic syntax highlighting functionality.
2 //!
3 //! This module uses libsyntax's lexer to provide token-based highlighting for
4 //! the HTML documentation generated by rustdoc.
5 //!
6 //! Use the `render_with_highlighting` to highlight some rust code.
7
8 use crate::html::escape::Escape;
9
10 use std::fmt::Display;
11 use std::io;
12 use std::io::prelude::*;
13
14 use syntax::source_map::{SourceMap, FilePathMapping};
15 use syntax::parse::lexer;
16 use syntax::parse::token::{self, Token};
17 use syntax::parse;
18 use syntax::symbol::{kw, sym};
19 use syntax_pos::{Span, FileName};
20
21 /// Highlights `src`, returning the HTML output.
22 pub fn render_with_highlighting(
23     src: &str,
24     class: Option<&str>,
25     extension: Option<&str>,
26     tooltip: Option<(&str, &str)>,
27 ) -> String {
28     debug!("highlighting: ================\n{}\n==============", src);
29     let mut out = Vec::new();
30     if let Some((tooltip, class)) = tooltip {
31         write!(out, "<div class='information'><div class='tooltip {}'>ⓘ<span \
32                      class='tooltiptext'>{}</span></div></div>",
33                class, tooltip).unwrap();
34     }
35
36     let sess = parse::ParseSess::new(FilePathMapping::empty());
37     let fm = sess.source_map().new_source_file(
38         FileName::Custom(String::from("rustdoc-highlighting")),
39         src.to_owned(),
40     );
41     let highlight_result = {
42         let lexer = lexer::StringReader::new(&sess, fm, None);
43         let mut classifier = Classifier::new(lexer, sess.source_map());
44
45         let mut highlighted_source = vec![];
46         if classifier.write_source(&mut highlighted_source).is_err() {
47             Err(classifier.lexer.buffer_fatal_errors())
48         } else {
49             Ok(String::from_utf8_lossy(&highlighted_source).into_owned())
50         }
51     };
52
53     match highlight_result {
54         Ok(highlighted_source) => {
55             write_header(class, &mut out).unwrap();
56             write!(out, "{}", highlighted_source).unwrap();
57             if let Some(extension) = extension {
58                 write!(out, "{}", extension).unwrap();
59             }
60             write_footer(&mut out).unwrap();
61         }
62         Err(errors) => {
63             // If errors are encountered while trying to highlight, cancel the errors and just emit
64             // the unhighlighted source. The errors will have already been reported in the
65             // `check-code-block-syntax` pass.
66             for mut error in errors {
67                 error.cancel();
68             }
69
70             write!(out, "<pre><code>{}</code></pre>", src).unwrap();
71         }
72     }
73
74     String::from_utf8_lossy(&out[..]).into_owned()
75 }
76
77 /// Processes a program (nested in the internal `lexer`), classifying strings of
78 /// text by highlighting category (`Class`). Calls out to a `Writer` to write
79 /// each span of text in sequence.
80 struct Classifier<'a> {
81     lexer: lexer::StringReader<'a>,
82     peek_token: Option<Token>,
83     source_map: &'a SourceMap,
84
85     // State of the classifier.
86     in_attribute: bool,
87     in_macro: bool,
88     in_macro_nonterminal: bool,
89 }
90
91 /// How a span of text is classified. Mostly corresponds to token kinds.
92 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
93 enum Class {
94     None,
95     Comment,
96     DocComment,
97     Attribute,
98     KeyWord,
99     // Keywords that do pointer/reference stuff.
100     RefKeyWord,
101     Self_,
102     Op,
103     Macro,
104     MacroNonTerminal,
105     String,
106     Number,
107     Bool,
108     Ident,
109     Lifetime,
110     PreludeTy,
111     PreludeVal,
112     QuestionMark,
113 }
114
115 /// Trait that controls writing the output of syntax highlighting. Users should
116 /// implement this trait to customize writing output.
117 ///
118 /// The classifier will call into the `Writer` implementation as it finds spans
119 /// of text to highlight. Exactly how that text should be highlighted is up to
120 /// the implementation.
121 trait Writer {
122     /// Called when we start processing a span of text that should be highlighted.
123     /// The `Class` argument specifies how it should be highlighted.
124     fn enter_span(&mut self, _: Class) -> io::Result<()>;
125
126     /// Called at the end of a span of highlighted text.
127     fn exit_span(&mut self) -> io::Result<()>;
128
129     /// Called for a span of text. If the text should be highlighted differently from the
130     /// surrounding text, then the `Class` argument will be a value other than `None`.
131     ///
132     /// The following sequences of callbacks are equivalent:
133     /// ```plain
134     ///     enter_span(Foo), string("text", None), exit_span()
135     ///     string("text", Foo)
136     /// ```
137     /// The latter can be thought of as a shorthand for the former, which is
138     /// more flexible.
139     fn string<T: Display>(&mut self,
140                           text: T,
141                           klass: Class)
142                           -> io::Result<()>;
143 }
144
145 // Implement `Writer` for anthing that can be written to, this just implements
146 // the default rustdoc behaviour.
147 impl<U: Write> Writer for U {
148     fn string<T: Display>(&mut self,
149                           text: T,
150                           klass: Class)
151                           -> io::Result<()> {
152         match klass {
153             Class::None => write!(self, "{}", text),
154             klass => write!(self, "<span class=\"{}\">{}</span>", klass.rustdoc_class(), text),
155         }
156     }
157
158     fn enter_span(&mut self, klass: Class) -> io::Result<()> {
159         write!(self, "<span class=\"{}\">", klass.rustdoc_class())
160     }
161
162     fn exit_span(&mut self) -> io::Result<()> {
163         write!(self, "</span>")
164     }
165 }
166
167 enum HighlightError {
168     LexError,
169     IoError(io::Error),
170 }
171
172 impl From<io::Error> for HighlightError {
173     fn from(err: io::Error) -> Self {
174         HighlightError::IoError(err)
175     }
176 }
177
178 impl<'a> Classifier<'a> {
179     fn new(lexer: lexer::StringReader<'a>, source_map: &'a SourceMap) -> Classifier<'a> {
180         Classifier {
181             lexer,
182             peek_token: None,
183             source_map,
184             in_attribute: false,
185             in_macro: false,
186             in_macro_nonterminal: false,
187         }
188     }
189
190     /// Gets the next token out of the lexer.
191     fn try_next_token(&mut self) -> Result<Token, HighlightError> {
192         if let Some(token) = self.peek_token.take() {
193             return Ok(token);
194         }
195         self.lexer.try_next_token().map_err(|()| HighlightError::LexError)
196     }
197
198     fn peek(&mut self) -> Result<&Token, HighlightError> {
199         if self.peek_token.is_none() {
200             self.peek_token = Some(
201                 self.lexer.try_next_token().map_err(|()| HighlightError::LexError)?
202             );
203         }
204         Ok(self.peek_token.as_ref().unwrap())
205     }
206
207     /// Exhausts the `lexer` writing the output into `out`.
208     ///
209     /// The general structure for this method is to iterate over each token,
210     /// possibly giving it an HTML span with a class specifying what flavor of token
211     /// is used. All source code emission is done as slices from the source map,
212     /// not from the tokens themselves, in order to stay true to the original
213     /// source.
214     fn write_source<W: Writer>(&mut self,
215                                    out: &mut W)
216                                    -> Result<(), HighlightError> {
217         loop {
218             let next = self.try_next_token()?;
219             if next == token::Eof {
220                 break;
221             }
222
223             self.write_token(out, next)?;
224         }
225
226         Ok(())
227     }
228
229     // Handles an individual token from the lexer.
230     fn write_token<W: Writer>(&mut self,
231                               out: &mut W,
232                               token: Token)
233                               -> Result<(), HighlightError> {
234         let klass = match token.kind {
235             token::Shebang(s) => {
236                 out.string(Escape(&s.as_str()), Class::None)?;
237                 return Ok(());
238             },
239
240             token::Whitespace => Class::None,
241             token::Comment => Class::Comment,
242             token::DocComment(..) => Class::DocComment,
243
244             // If this '&' or '*' token is followed by a non-whitespace token, assume that it's the
245             // reference or dereference operator or a reference or pointer type, instead of the
246             // bit-and or multiplication operator.
247             token::BinOp(token::And) | token::BinOp(token::Star)
248                 if self.peek()? != &token::Whitespace => Class::RefKeyWord,
249
250             // Consider this as part of a macro invocation if there was a
251             // leading identifier.
252             token::Not if self.in_macro => {
253                 self.in_macro = false;
254                 Class::Macro
255             }
256
257             // Operators.
258             token::Eq | token::Lt | token::Le | token::EqEq | token::Ne | token::Ge | token::Gt |
259                 token::AndAnd | token::OrOr | token::Not | token::BinOp(..) | token::RArrow |
260                 token::BinOpEq(..) | token::FatArrow => Class::Op,
261
262             // Miscellaneous, no highlighting.
263             token::Dot | token::DotDot | token::DotDotDot | token::DotDotEq | token::Comma |
264                 token::Semi | token::Colon | token::ModSep | token::LArrow | token::OpenDelim(_) |
265                 token::CloseDelim(token::Brace) | token::CloseDelim(token::Paren) |
266                 token::CloseDelim(token::NoDelim) => Class::None,
267
268             token::Question => Class::QuestionMark,
269
270             token::Dollar => {
271                 if self.peek()?.is_ident() {
272                     self.in_macro_nonterminal = true;
273                     Class::MacroNonTerminal
274                 } else {
275                     Class::None
276                 }
277             }
278
279             // This might be the start of an attribute. We're going to want to
280             // continue highlighting it as an attribute until the ending ']' is
281             // seen, so skip out early. Down below we terminate the attribute
282             // span when we see the ']'.
283             token::Pound => {
284                 // We can't be sure that our # begins an attribute (it could
285                 // just be appearing in a macro) until we read either `#![` or
286                 // `#[` from the input stream.
287                 //
288                 // We don't want to start highlighting as an attribute until
289                 // we're confident there is going to be a ] coming up, as
290                 // otherwise # tokens in macros highlight the rest of the input
291                 // as an attribute.
292
293                 // Case 1: #![inner_attribute]
294                 if self.peek()? == &token::Not {
295                     self.try_next_token()?; // NOTE: consumes `!` token!
296                     if self.peek()? == &token::OpenDelim(token::Bracket) {
297                         self.in_attribute = true;
298                         out.enter_span(Class::Attribute)?;
299                     }
300                     out.string("#", Class::None)?;
301                     out.string("!", Class::None)?;
302                     return Ok(());
303                 }
304
305                 // Case 2: #[outer_attribute]
306                 if self.peek()? == &token::OpenDelim(token::Bracket) {
307                     self.in_attribute = true;
308                     out.enter_span(Class::Attribute)?;
309                 }
310                 out.string("#", Class::None)?;
311                 return Ok(());
312             }
313             token::CloseDelim(token::Bracket) => {
314                 if self.in_attribute {
315                     self.in_attribute = false;
316                     out.string("]", Class::None)?;
317                     out.exit_span()?;
318                     return Ok(());
319                 } else {
320                     Class::None
321                 }
322             }
323
324             token::Literal(lit) => {
325                 match lit.kind {
326                     // Text literals.
327                     token::Byte | token::Char | token::Err |
328                     token::ByteStr | token::ByteStrRaw(..) |
329                     token::Str | token::StrRaw(..) => Class::String,
330
331                     // Number literals.
332                     token::Integer | token::Float => Class::Number,
333
334                     token::Bool => panic!("literal token contains `Lit::Bool`"),
335                 }
336             }
337
338             // Keywords are also included in the identifier set.
339             token::Ident(name, is_raw) => {
340                 match name {
341                     kw::Ref | kw::Mut if !is_raw => Class::RefKeyWord,
342
343                     kw::SelfLower | kw::SelfUpper => Class::Self_,
344                     kw::False | kw::True if !is_raw => Class::Bool,
345
346                     sym::Option | sym::Result => Class::PreludeTy,
347                     sym::Some | sym::None | sym::Ok | sym::Err => Class::PreludeVal,
348
349                     _ if token.is_reserved_ident() => Class::KeyWord,
350
351                     _ => {
352                         if self.in_macro_nonterminal {
353                             self.in_macro_nonterminal = false;
354                             Class::MacroNonTerminal
355                         } else if self.peek()? == &token::Not {
356                             self.in_macro = true;
357                             Class::Macro
358                         } else {
359                             Class::Ident
360                         }
361                     }
362                 }
363             }
364
365             token::Lifetime(..) => Class::Lifetime,
366
367             token::Eof | token::Interpolated(..) |
368             token::Tilde | token::At| token::SingleQuote => Class::None,
369         };
370
371         // Anything that didn't return above is the simple case where we the
372         // class just spans a single token, so we can use the `string` method.
373         out.string(Escape(&self.snip(token.span)), klass)?;
374
375         Ok(())
376     }
377
378     // Helper function to get a snippet from the source_map.
379     fn snip(&self, sp: Span) -> String {
380         self.source_map.span_to_snippet(sp).unwrap()
381     }
382 }
383
384 impl Class {
385     /// Returns the css class expected by rustdoc for each `Class`.
386     fn rustdoc_class(self) -> &'static str {
387         match self {
388             Class::None => "",
389             Class::Comment => "comment",
390             Class::DocComment => "doccomment",
391             Class::Attribute => "attribute",
392             Class::KeyWord => "kw",
393             Class::RefKeyWord => "kw-2",
394             Class::Self_ => "self",
395             Class::Op => "op",
396             Class::Macro => "macro",
397             Class::MacroNonTerminal => "macro-nonterminal",
398             Class::String => "string",
399             Class::Number => "number",
400             Class::Bool => "bool-val",
401             Class::Ident => "ident",
402             Class::Lifetime => "lifetime",
403             Class::PreludeTy => "prelude-ty",
404             Class::PreludeVal => "prelude-val",
405             Class::QuestionMark => "question-mark"
406         }
407     }
408 }
409
410 fn write_header(class: Option<&str>, out: &mut dyn Write) -> io::Result<()> {
411     write!(out, "<div class=\"example-wrap\"><pre class=\"rust {}\">\n", class.unwrap_or(""))
412 }
413
414 fn write_footer(out: &mut dyn Write) -> io::Result<()> {
415     write!(out, "</pre></div>\n")
416 }