]>
git.proxmox.com Git - rustc.git/blob - src/librustdoc/html/highlight.rs
1 //! Basic syntax highlighting functionality.
3 //! This module uses libsyntax's lexer to provide token-based highlighting for
4 //! the HTML documentation generated by rustdoc.
6 //! Use the `render_with_highlighting` to highlight some rust code.
8 use crate::html
::escape
::Escape
;
10 use std
::fmt
::Display
;
12 use std
::io
::prelude
::*;
14 use rustc_parse
::lexer
;
15 use syntax
::token
::{self, Token}
;
16 use syntax
::sess
::ParseSess
;
17 use syntax
::source_map
::SourceMap
;
18 use syntax
::symbol
::{kw, sym}
;
19 use syntax_pos
::{Span, FileName}
;
21 /// Highlights `src`, returning the HTML output.
22 pub fn render_with_highlighting(
25 extension
: Option
<&str>,
26 tooltip
: Option
<(&str, &str)>,
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();
36 let sess
= ParseSess
::with_silent_emitter();
37 let fm
= sess
.source_map().new_source_file(
38 FileName
::Custom(String
::from("rustdoc-highlighting")),
41 let highlight_result
= {
42 let lexer
= lexer
::StringReader
::new(&sess
, fm
, None
);
43 let mut classifier
= Classifier
::new(lexer
, sess
.source_map());
45 let mut highlighted_source
= vec
![];
46 if classifier
.write_source(&mut highlighted_source
).is_err() {
49 Ok(String
::from_utf8_lossy(&highlighted_source
).into_owned())
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();
60 write_footer(&mut out
).unwrap();
63 // If errors are encountered while trying to highlight, just emit
64 // the unhighlighted source.
65 write
!(out
, "<pre><code>{}</code></pre>", src
).unwrap();
69 String
::from_utf8_lossy(&out
[..]).into_owned()
72 /// Processes a program (nested in the internal `lexer`), classifying strings of
73 /// text by highlighting category (`Class`). Calls out to a `Writer` to write
74 /// each span of text in sequence.
75 struct Classifier
<'a
> {
76 lexer
: lexer
::StringReader
<'a
>,
77 peek_token
: Option
<Token
>,
78 source_map
: &'a SourceMap
,
80 // State of the classifier.
83 in_macro_nonterminal
: bool
,
86 /// How a span of text is classified. Mostly corresponds to token kinds.
87 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
94 // Keywords that do pointer/reference stuff.
110 /// Trait that controls writing the output of syntax highlighting. Users should
111 /// implement this trait to customize writing output.
113 /// The classifier will call into the `Writer` implementation as it finds spans
114 /// of text to highlight. Exactly how that text should be highlighted is up to
115 /// the implementation.
117 /// Called when we start processing a span of text that should be highlighted.
118 /// The `Class` argument specifies how it should be highlighted.
119 fn enter_span(&mut self, _
: Class
) -> io
::Result
<()>;
121 /// Called at the end of a span of highlighted text.
122 fn exit_span(&mut self) -> io
::Result
<()>;
124 /// Called for a span of text. If the text should be highlighted differently from the
125 /// surrounding text, then the `Class` argument will be a value other than `None`.
127 /// The following sequences of callbacks are equivalent:
129 /// enter_span(Foo), string("text", None), exit_span()
130 /// string("text", Foo)
132 /// The latter can be thought of as a shorthand for the former, which is
134 fn string
<T
: Display
>(&mut self,
140 // Implement `Writer` for anthing that can be written to, this just implements
141 // the default rustdoc behaviour.
142 impl<U
: Write
> Writer
for U
{
143 fn string
<T
: Display
>(&mut self,
148 Class
::None
=> write
!(self, "{}", text
),
149 klass
=> write
!(self, "<span class=\"{}\">{}</span>", klass
.rustdoc_class(), text
),
153 fn enter_span(&mut self, klass
: Class
) -> io
::Result
<()> {
154 write
!(self, "<span class=\"{}\">", klass
.rustdoc_class())
157 fn exit_span(&mut self) -> io
::Result
<()> {
158 write
!(self, "</span>")
162 enum HighlightError
{
167 impl From
<io
::Error
> for HighlightError
{
168 fn from(err
: io
::Error
) -> Self {
169 HighlightError
::IoError(err
)
173 impl<'a
> Classifier
<'a
> {
174 fn new(lexer
: lexer
::StringReader
<'a
>, source_map
: &'a SourceMap
) -> Classifier
<'a
> {
181 in_macro_nonterminal
: false,
185 /// Gets the next token out of the lexer.
186 fn try_next_token(&mut self) -> Result
<Token
, HighlightError
> {
187 if let Some(token
) = self.peek_token
.take() {
190 let token
= self.lexer
.next_token();
191 if let token
::Unknown(..) = &token
.kind
{
192 return Err(HighlightError
::LexError
);
197 fn peek(&mut self) -> Result
<&Token
, HighlightError
> {
198 if self.peek_token
.is_none() {
199 let token
= self.lexer
.next_token();
200 if let token
::Unknown(..) = &token
.kind
{
201 return Err(HighlightError
::LexError
);
203 self.peek_token
= Some(token
);
205 Ok(self.peek_token
.as_ref().unwrap())
208 /// Exhausts the `lexer` writing the output into `out`.
210 /// The general structure for this method is to iterate over each token,
211 /// possibly giving it an HTML span with a class specifying what flavor of token
212 /// is used. All source code emission is done as slices from the source map,
213 /// not from the tokens themselves, in order to stay true to the original
215 fn write_source
<W
: Writer
>(&mut self,
217 -> Result
<(), HighlightError
> {
219 let next
= self.try_next_token()?
;
220 if next
== token
::Eof
{
224 self.write_token(out
, next
)?
;
230 // Handles an individual token from the lexer.
231 fn write_token
<W
: Writer
>(&mut self,
234 -> Result
<(), HighlightError
> {
235 let klass
= match token
.kind
{
236 token
::Shebang(s
) => {
237 out
.string(Escape(&s
.as_str()), Class
::None
)?
;
241 token
::Whitespace
| token
::Unknown(..) => Class
::None
,
242 token
::Comment
=> Class
::Comment
,
243 token
::DocComment(..) => Class
::DocComment
,
245 // If this '&' or '*' token is followed by a non-whitespace token, assume that it's the
246 // reference or dereference operator or a reference or pointer type, instead of the
247 // bit-and or multiplication operator.
248 token
::BinOp(token
::And
) | token
::BinOp(token
::Star
)
249 if self.peek()?
!= &token
::Whitespace
=> Class
::RefKeyWord
,
251 // Consider this as part of a macro invocation if there was a
252 // leading identifier.
253 token
::Not
if self.in_macro
=> {
254 self.in_macro
= false;
259 token
::Eq
| token
::Lt
| token
::Le
| token
::EqEq
| token
::Ne
| token
::Ge
| token
::Gt
|
260 token
::AndAnd
| token
::OrOr
| token
::Not
| token
::BinOp(..) | token
::RArrow
|
261 token
::BinOpEq(..) | token
::FatArrow
=> Class
::Op
,
263 // Miscellaneous, no highlighting.
264 token
::Dot
| token
::DotDot
| token
::DotDotDot
| token
::DotDotEq
| token
::Comma
|
265 token
::Semi
| token
::Colon
| token
::ModSep
| token
::LArrow
| token
::OpenDelim(_
) |
266 token
::CloseDelim(token
::Brace
) | token
::CloseDelim(token
::Paren
) |
267 token
::CloseDelim(token
::NoDelim
) => Class
::None
,
269 token
::Question
=> Class
::QuestionMark
,
272 if self.peek()?
.is_ident() {
273 self.in_macro_nonterminal
= true;
274 Class
::MacroNonTerminal
280 // This might be the start of an attribute. We're going to want to
281 // continue highlighting it as an attribute until the ending ']' is
282 // seen, so skip out early. Down below we terminate the attribute
283 // span when we see the ']'.
285 // We can't be sure that our # begins an attribute (it could
286 // just be appearing in a macro) until we read either `#![` or
287 // `#[` from the input stream.
289 // We don't want to start highlighting as an attribute until
290 // we're confident there is going to be a ] coming up, as
291 // otherwise # tokens in macros highlight the rest of the input
294 // Case 1: #![inner_attribute]
295 if self.peek()?
== &token
::Not
{
296 self.try_next_token()?
; // NOTE: consumes `!` token!
297 if self.peek()?
== &token
::OpenDelim(token
::Bracket
) {
298 self.in_attribute
= true;
299 out
.enter_span(Class
::Attribute
)?
;
301 out
.string("#", Class
::None
)?
;
302 out
.string("!", Class
::None
)?
;
306 // Case 2: #[outer_attribute]
307 if self.peek()?
== &token
::OpenDelim(token
::Bracket
) {
308 self.in_attribute
= true;
309 out
.enter_span(Class
::Attribute
)?
;
311 out
.string("#", Class
::None
)?
;
314 token
::CloseDelim(token
::Bracket
) => {
315 if self.in_attribute
{
316 self.in_attribute
= false;
317 out
.string("]", Class
::None
)?
;
325 token
::Literal(lit
) => {
328 token
::Byte
| token
::Char
| token
::Err
|
329 token
::ByteStr
| token
::ByteStrRaw(..) |
330 token
::Str
| token
::StrRaw(..) => Class
::String
,
333 token
::Integer
| token
::Float
=> Class
::Number
,
335 token
::Bool
=> panic
!("literal token contains `Lit::Bool`"),
339 // Keywords are also included in the identifier set.
340 token
::Ident(name
, is_raw
) => {
342 kw
::Ref
| kw
::Mut
if !is_raw
=> Class
::RefKeyWord
,
344 kw
::SelfLower
| kw
::SelfUpper
=> Class
::Self_
,
345 kw
::False
| kw
::True
if !is_raw
=> Class
::Bool
,
347 sym
::Option
| sym
::Result
=> Class
::PreludeTy
,
348 sym
::Some
| sym
::None
| sym
::Ok
| sym
::Err
=> Class
::PreludeVal
,
350 _
if token
.is_reserved_ident() => Class
::KeyWord
,
353 if self.in_macro_nonterminal
{
354 self.in_macro_nonterminal
= false;
355 Class
::MacroNonTerminal
356 } else if self.peek()?
== &token
::Not
{
357 self.in_macro
= true;
366 token
::Lifetime(..) => Class
::Lifetime
,
368 token
::Eof
| token
::Interpolated(..) |
369 token
::Tilde
| token
::At
| token
::SingleQuote
=> Class
::None
,
372 // Anything that didn't return above is the simple case where we the
373 // class just spans a single token, so we can use the `string` method.
374 out
.string(Escape(&self.snip(token
.span
)), klass
)?
;
379 // Helper function to get a snippet from the source_map.
380 fn snip(&self, sp
: Span
) -> String
{
381 self.source_map
.span_to_snippet(sp
).unwrap()
386 /// Returns the css class expected by rustdoc for each `Class`.
387 fn rustdoc_class(self) -> &'
static str {
390 Class
::Comment
=> "comment",
391 Class
::DocComment
=> "doccomment",
392 Class
::Attribute
=> "attribute",
393 Class
::KeyWord
=> "kw",
394 Class
::RefKeyWord
=> "kw-2",
395 Class
::Self_
=> "self",
397 Class
::Macro
=> "macro",
398 Class
::MacroNonTerminal
=> "macro-nonterminal",
399 Class
::String
=> "string",
400 Class
::Number
=> "number",
401 Class
::Bool
=> "bool-val",
402 Class
::Ident
=> "ident",
403 Class
::Lifetime
=> "lifetime",
404 Class
::PreludeTy
=> "prelude-ty",
405 Class
::PreludeVal
=> "prelude-val",
406 Class
::QuestionMark
=> "question-mark"
411 fn write_header(class
: Option
<&str>, out
: &mut dyn Write
) -> io
::Result
<()> {
412 write
!(out
, "<div class=\"example-wrap\"><pre class=\"rust {}\">\n", class
.unwrap_or(""))
415 fn write_footer(out
: &mut dyn Write
) -> io
::Result
<()> {
416 write
!(out
, "</pre></div>\n")