]> git.lizzy.rs Git - rust.git/blob - src/librustdoc/html/highlight.rs
Rollup merge of #101631 - rust-lang:notriddle/duplicate-module, r=GuillaumeGomez
[rust.git] / src / librustdoc / html / highlight.rs
1 //! Basic syntax highlighting functionality.
2 //!
3 //! This module uses librustc_ast'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::clean::PrimitiveType;
9 use crate::html::escape::Escape;
10 use crate::html::render::Context;
11
12 use std::collections::VecDeque;
13 use std::fmt::{Display, Write};
14
15 use rustc_data_structures::fx::FxHashMap;
16 use rustc_lexer::{LiteralKind, TokenKind};
17 use rustc_span::edition::Edition;
18 use rustc_span::symbol::Symbol;
19 use rustc_span::{BytePos, Span, DUMMY_SP};
20
21 use super::format::{self, Buffer};
22 use super::render::LinkFromSrc;
23
24 /// This type is needed in case we want to render links on items to allow to go to their definition.
25 pub(crate) struct HrefContext<'a, 'b, 'c> {
26     pub(crate) context: &'a Context<'b>,
27     /// This span contains the current file we're going through.
28     pub(crate) file_span: Span,
29     /// This field is used to know "how far" from the top of the directory we are to link to either
30     /// documentation pages or other source pages.
31     pub(crate) root_path: &'c str,
32 }
33
34 /// Decorations are represented as a map from CSS class to vector of character ranges.
35 /// Each range will be wrapped in a span with that class.
36 #[derive(Default)]
37 pub(crate) struct DecorationInfo(pub(crate) FxHashMap<&'static str, Vec<(u32, u32)>>);
38
39 #[derive(Eq, PartialEq, Clone, Copy)]
40 pub(crate) enum Tooltip {
41     Ignore,
42     CompileFail,
43     ShouldPanic,
44     Edition(Edition),
45     None,
46 }
47
48 /// Highlights `src` as an inline example, returning the HTML output.
49 pub(crate) fn render_example_with_highlighting(
50     src: &str,
51     out: &mut Buffer,
52     tooltip: Tooltip,
53     playground_button: Option<&str>,
54 ) {
55     write_header(out, "rust-example-rendered", None, tooltip);
56     write_code(out, src, None, None);
57     write_footer(out, playground_button);
58 }
59
60 /// Highlights `src` as a macro, returning the HTML output.
61 pub(crate) fn render_macro_with_highlighting(src: &str, out: &mut Buffer) {
62     write_header(out, "macro", None, Tooltip::None);
63     write_code(out, src, None, None);
64     write_footer(out, None);
65 }
66
67 /// Highlights `src` as a source code page, returning the HTML output.
68 pub(crate) fn render_source_with_highlighting(
69     src: &str,
70     out: &mut Buffer,
71     line_numbers: Buffer,
72     href_context: HrefContext<'_, '_, '_>,
73     decoration_info: DecorationInfo,
74 ) {
75     write_header(out, "", Some(line_numbers), Tooltip::None);
76     write_code(out, src, Some(href_context), Some(decoration_info));
77     write_footer(out, None);
78 }
79
80 fn write_header(out: &mut Buffer, class: &str, extra_content: Option<Buffer>, tooltip: Tooltip) {
81     write!(out, "<div class=\"example-wrap\">");
82
83     let tooltip_class = match tooltip {
84         Tooltip::Ignore => " ignore",
85         Tooltip::CompileFail => " compile_fail",
86         Tooltip::ShouldPanic => " should_panic",
87         Tooltip::Edition(_) => " edition",
88         Tooltip::None => "",
89     };
90
91     if tooltip != Tooltip::None {
92         write!(
93             out,
94             "<div class='information'><div class='tooltip{}'{}>ⓘ</div></div>",
95             tooltip_class,
96             if let Tooltip::Edition(edition_info) = tooltip {
97                 format!(" data-edition=\"{}\"", edition_info)
98             } else {
99                 String::new()
100             },
101         );
102     }
103
104     if let Some(extra) = extra_content {
105         out.push_buffer(extra);
106     }
107     if class.is_empty() && tooltip_class.is_empty() {
108         write!(out, "<pre class=\"rust\">");
109     } else {
110         write!(out, "<pre class=\"rust {class}{tooltip_class}\">");
111     }
112     write!(out, "<code>");
113 }
114
115 /// Check if two `Class` can be merged together. In the following rules, "unclassified" means `None`
116 /// basically (since it's `Option<Class>`). The following rules apply:
117 ///
118 /// * If two `Class` have the same variant, then they can be merged.
119 /// * If the other `Class` is unclassified and only contains white characters (backline,
120 ///   whitespace, etc), it can be merged.
121 /// * `Class::Ident` is considered the same as unclassified (because it doesn't have an associated
122 ///    CSS class).
123 fn can_merge(class1: Option<Class>, class2: Option<Class>, text: &str) -> bool {
124     match (class1, class2) {
125         (Some(c1), Some(c2)) => c1.is_equal_to(c2),
126         (Some(Class::Ident(_)), None) | (None, Some(Class::Ident(_))) => true,
127         (Some(_), None) | (None, Some(_)) => text.trim().is_empty(),
128         (None, None) => true,
129     }
130 }
131
132 /// This type is used as a conveniency to prevent having to pass all its fields as arguments into
133 /// the various functions (which became its methods).
134 struct TokenHandler<'a, 'b, 'c, 'd, 'e> {
135     out: &'a mut Buffer,
136     /// It contains the closing tag and the associated `Class`.
137     closing_tags: Vec<(&'static str, Class)>,
138     /// This is used because we don't automatically generate the closing tag on `ExitSpan` in
139     /// case an `EnterSpan` event with the same class follows.
140     pending_exit_span: Option<Class>,
141     /// `current_class` and `pending_elems` are used to group HTML elements with same `class`
142     /// attributes to reduce the DOM size.
143     current_class: Option<Class>,
144     /// We need to keep the `Class` for each element because it could contain a `Span` which is
145     /// used to generate links.
146     pending_elems: Vec<(&'b str, Option<Class>)>,
147     href_context: Option<HrefContext<'c, 'd, 'e>>,
148 }
149
150 impl<'a, 'b, 'c, 'd, 'e> TokenHandler<'a, 'b, 'c, 'd, 'e> {
151     fn handle_exit_span(&mut self) {
152         // We can't get the last `closing_tags` element using `pop()` because `closing_tags` is
153         // being used in `write_pending_elems`.
154         let class = self.closing_tags.last().expect("ExitSpan without EnterSpan").1;
155         // We flush everything just in case...
156         self.write_pending_elems(Some(class));
157
158         exit_span(self.out, self.closing_tags.pop().expect("ExitSpan without EnterSpan").0);
159         self.pending_exit_span = None;
160     }
161
162     /// Write all the pending elements sharing a same (or at mergeable) `Class`.
163     ///
164     /// If there is a "parent" (if a `EnterSpan` event was encountered) and the parent can be merged
165     /// with the elements' class, then we simply write the elements since the `ExitSpan` event will
166     /// close the tag.
167     ///
168     /// Otherwise, if there is only one pending element, we let the `string` function handle both
169     /// opening and closing the tag, otherwise we do it into this function.
170     ///
171     /// It returns `true` if `current_class` must be set to `None` afterwards.
172     fn write_pending_elems(&mut self, current_class: Option<Class>) -> bool {
173         if self.pending_elems.is_empty() {
174             return false;
175         }
176         if let Some((_, parent_class)) = self.closing_tags.last() &&
177             can_merge(current_class, Some(*parent_class), "")
178         {
179             for (text, class) in self.pending_elems.iter() {
180                 string(self.out, Escape(text), *class, &self.href_context, false);
181             }
182         } else {
183             // We only want to "open" the tag ourselves if we have more than one pending and if the
184             // current parent tag is not the same as our pending content.
185             let close_tag = if self.pending_elems.len() > 1 && current_class.is_some() {
186                 Some(enter_span(self.out, current_class.unwrap(), &self.href_context))
187             } else {
188                 None
189             };
190             for (text, class) in self.pending_elems.iter() {
191                 string(self.out, Escape(text), *class, &self.href_context, close_tag.is_none());
192             }
193             if let Some(close_tag) = close_tag {
194                 exit_span(self.out, close_tag);
195             }
196         }
197         self.pending_elems.clear();
198         true
199     }
200 }
201
202 impl<'a, 'b, 'c, 'd, 'e> Drop for TokenHandler<'a, 'b, 'c, 'd, 'e> {
203     /// When leaving, we need to flush all pending data to not have missing content.
204     fn drop(&mut self) {
205         if self.pending_exit_span.is_some() {
206             self.handle_exit_span();
207         } else {
208             self.write_pending_elems(self.current_class);
209         }
210     }
211 }
212
213 /// Convert the given `src` source code into HTML by adding classes for highlighting.
214 ///
215 /// This code is used to render code blocks (in the documentation) as well as the source code pages.
216 ///
217 /// Some explanations on the last arguments:
218 ///
219 /// In case we are rendering a code block and not a source code file, `href_context` will be `None`.
220 /// To put it more simply: if `href_context` is `None`, the code won't try to generate links to an
221 /// item definition.
222 ///
223 /// More explanations about spans and how we use them here are provided in the
224 fn write_code(
225     out: &mut Buffer,
226     src: &str,
227     href_context: Option<HrefContext<'_, '_, '_>>,
228     decoration_info: Option<DecorationInfo>,
229 ) {
230     // This replace allows to fix how the code source with DOS backline characters is displayed.
231     let src = src.replace("\r\n", "\n");
232     let mut token_handler = TokenHandler {
233         out,
234         closing_tags: Vec::new(),
235         pending_exit_span: None,
236         current_class: None,
237         pending_elems: Vec::new(),
238         href_context,
239     };
240
241     Classifier::new(
242         &src,
243         token_handler.href_context.as_ref().map(|c| c.file_span).unwrap_or(DUMMY_SP),
244         decoration_info,
245     )
246     .highlight(&mut |highlight| {
247         match highlight {
248             Highlight::Token { text, class } => {
249                 // If we received a `ExitSpan` event and then have a non-compatible `Class`, we
250                 // need to close the `<span>`.
251                 let need_current_class_update = if let Some(pending) = token_handler.pending_exit_span &&
252                     !can_merge(Some(pending), class, text) {
253                         token_handler.handle_exit_span();
254                         true
255                 // If the two `Class` are different, time to flush the current content and start
256                 // a new one.
257                 } else if !can_merge(token_handler.current_class, class, text) {
258                     token_handler.write_pending_elems(token_handler.current_class);
259                     true
260                 } else {
261                     token_handler.current_class.is_none()
262                 };
263
264                 if need_current_class_update {
265                     token_handler.current_class = class.map(Class::dummy);
266                 }
267                 token_handler.pending_elems.push((text, class));
268             }
269             Highlight::EnterSpan { class } => {
270                 let mut should_add = true;
271                 if let Some(pending_exit_span) = token_handler.pending_exit_span {
272                     if class.is_equal_to(pending_exit_span) {
273                         should_add = false;
274                     } else {
275                         token_handler.handle_exit_span();
276                     }
277                 } else {
278                     // We flush everything just in case...
279                     if token_handler.write_pending_elems(token_handler.current_class) {
280                         token_handler.current_class = None;
281                     }
282                 }
283                 if should_add {
284                     let closing_tag = enter_span(token_handler.out, class, &token_handler.href_context);
285                     token_handler.closing_tags.push((closing_tag, class));
286                 }
287
288                 token_handler.current_class = None;
289                 token_handler.pending_exit_span = None;
290             }
291             Highlight::ExitSpan => {
292                 token_handler.current_class = None;
293                 token_handler.pending_exit_span =
294                     Some(token_handler.closing_tags.last().as_ref().expect("ExitSpan without EnterSpan").1);
295             }
296         };
297     });
298 }
299
300 fn write_footer(out: &mut Buffer, playground_button: Option<&str>) {
301     writeln!(out, "</code></pre>{}</div>", playground_button.unwrap_or_default());
302 }
303
304 /// How a span of text is classified. Mostly corresponds to token kinds.
305 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
306 enum Class {
307     Comment,
308     DocComment,
309     Attribute,
310     KeyWord,
311     /// Keywords that do pointer/reference stuff.
312     RefKeyWord,
313     Self_(Span),
314     Macro(Span),
315     MacroNonTerminal,
316     String,
317     Number,
318     Bool,
319     /// `Ident` isn't rendered in the HTML but we still need it for the `Span` it contains.
320     Ident(Span),
321     Lifetime,
322     PreludeTy,
323     PreludeVal,
324     QuestionMark,
325     Decoration(&'static str),
326 }
327
328 impl Class {
329     /// It is only looking at the variant, not the variant content.
330     ///
331     /// It is used mostly to group multiple similar HTML elements into one `<span>` instead of
332     /// multiple ones.
333     fn is_equal_to(self, other: Self) -> bool {
334         match (self, other) {
335             (Self::Self_(_), Self::Self_(_))
336             | (Self::Macro(_), Self::Macro(_))
337             | (Self::Ident(_), Self::Ident(_)) => true,
338             (Self::Decoration(c1), Self::Decoration(c2)) => c1 == c2,
339             (x, y) => x == y,
340         }
341     }
342
343     /// If `self` contains a `Span`, it'll be replaced with `DUMMY_SP` to prevent creating links
344     /// on "empty content" (because of the attributes merge).
345     fn dummy(self) -> Self {
346         match self {
347             Self::Self_(_) => Self::Self_(DUMMY_SP),
348             Self::Macro(_) => Self::Macro(DUMMY_SP),
349             Self::Ident(_) => Self::Ident(DUMMY_SP),
350             s => s,
351         }
352     }
353
354     /// Returns the css class expected by rustdoc for each `Class`.
355     fn as_html(self) -> &'static str {
356         match self {
357             Class::Comment => "comment",
358             Class::DocComment => "doccomment",
359             Class::Attribute => "attribute",
360             Class::KeyWord => "kw",
361             Class::RefKeyWord => "kw-2",
362             Class::Self_(_) => "self",
363             Class::Macro(_) => "macro",
364             Class::MacroNonTerminal => "macro-nonterminal",
365             Class::String => "string",
366             Class::Number => "number",
367             Class::Bool => "bool-val",
368             Class::Ident(_) => "",
369             Class::Lifetime => "lifetime",
370             Class::PreludeTy => "prelude-ty",
371             Class::PreludeVal => "prelude-val",
372             Class::QuestionMark => "question-mark",
373             Class::Decoration(kind) => kind,
374         }
375     }
376
377     /// In case this is an item which can be converted into a link to a definition, it'll contain
378     /// a "span" (a tuple representing `(lo, hi)` equivalent of `Span`).
379     fn get_span(self) -> Option<Span> {
380         match self {
381             Self::Ident(sp) | Self::Self_(sp) | Self::Macro(sp) => Some(sp),
382             Self::Comment
383             | Self::DocComment
384             | Self::Attribute
385             | Self::KeyWord
386             | Self::RefKeyWord
387             | Self::MacroNonTerminal
388             | Self::String
389             | Self::Number
390             | Self::Bool
391             | Self::Lifetime
392             | Self::PreludeTy
393             | Self::PreludeVal
394             | Self::QuestionMark
395             | Self::Decoration(_) => None,
396         }
397     }
398 }
399
400 enum Highlight<'a> {
401     Token { text: &'a str, class: Option<Class> },
402     EnterSpan { class: Class },
403     ExitSpan,
404 }
405
406 struct TokenIter<'a> {
407     src: &'a str,
408 }
409
410 impl<'a> Iterator for TokenIter<'a> {
411     type Item = (TokenKind, &'a str);
412     fn next(&mut self) -> Option<(TokenKind, &'a str)> {
413         if self.src.is_empty() {
414             return None;
415         }
416         let token = rustc_lexer::first_token(self.src);
417         let (text, rest) = self.src.split_at(token.len as usize);
418         self.src = rest;
419         Some((token.kind, text))
420     }
421 }
422
423 /// Classifies into identifier class; returns `None` if this is a non-keyword identifier.
424 fn get_real_ident_class(text: &str, allow_path_keywords: bool) -> Option<Class> {
425     let ignore: &[&str] =
426         if allow_path_keywords { &["self", "Self", "super", "crate"] } else { &["self", "Self"] };
427     if ignore.iter().any(|k| *k == text) {
428         return None;
429     }
430     Some(match text {
431         "ref" | "mut" => Class::RefKeyWord,
432         "false" | "true" => Class::Bool,
433         _ if Symbol::intern(text).is_reserved(|| Edition::Edition2021) => Class::KeyWord,
434         _ => return None,
435     })
436 }
437
438 /// This iterator comes from the same idea than "Peekable" except that it allows to "peek" more than
439 /// just the next item by using `peek_next`. The `peek` method always returns the next item after
440 /// the current one whereas `peek_next` will return the next item after the last one peeked.
441 ///
442 /// You can use both `peek` and `peek_next` at the same time without problem.
443 struct PeekIter<'a> {
444     stored: VecDeque<(TokenKind, &'a str)>,
445     /// This position is reinitialized when using `next`. It is used in `peek_next`.
446     peek_pos: usize,
447     iter: TokenIter<'a>,
448 }
449
450 impl<'a> PeekIter<'a> {
451     fn new(iter: TokenIter<'a>) -> Self {
452         Self { stored: VecDeque::new(), peek_pos: 0, iter }
453     }
454     /// Returns the next item after the current one. It doesn't interfere with `peek_next` output.
455     fn peek(&mut self) -> Option<&(TokenKind, &'a str)> {
456         if self.stored.is_empty() {
457             if let Some(next) = self.iter.next() {
458                 self.stored.push_back(next);
459             }
460         }
461         self.stored.front()
462     }
463     /// Returns the next item after the last one peeked. It doesn't interfere with `peek` output.
464     fn peek_next(&mut self) -> Option<&(TokenKind, &'a str)> {
465         self.peek_pos += 1;
466         if self.peek_pos - 1 < self.stored.len() {
467             self.stored.get(self.peek_pos - 1)
468         } else if let Some(next) = self.iter.next() {
469             self.stored.push_back(next);
470             self.stored.back()
471         } else {
472             None
473         }
474     }
475 }
476
477 impl<'a> Iterator for PeekIter<'a> {
478     type Item = (TokenKind, &'a str);
479     fn next(&mut self) -> Option<Self::Item> {
480         self.peek_pos = 0;
481         if let Some(first) = self.stored.pop_front() { Some(first) } else { self.iter.next() }
482     }
483 }
484
485 /// Custom spans inserted into the source. Eg --scrape-examples uses this to highlight function calls
486 struct Decorations {
487     starts: Vec<(u32, &'static str)>,
488     ends: Vec<u32>,
489 }
490
491 impl Decorations {
492     fn new(info: DecorationInfo) -> Self {
493         // Extract tuples (start, end, kind) into separate sequences of (start, kind) and (end).
494         let (mut starts, mut ends): (Vec<_>, Vec<_>) = info
495             .0
496             .into_iter()
497             .flat_map(|(kind, ranges)| ranges.into_iter().map(move |(lo, hi)| ((lo, kind), hi)))
498             .unzip();
499
500         // Sort the sequences in document order.
501         starts.sort_by_key(|(lo, _)| *lo);
502         ends.sort();
503
504         Decorations { starts, ends }
505     }
506 }
507
508 /// Processes program tokens, classifying strings of text by highlighting
509 /// category (`Class`).
510 struct Classifier<'a> {
511     tokens: PeekIter<'a>,
512     in_attribute: bool,
513     in_macro: bool,
514     in_macro_nonterminal: bool,
515     byte_pos: u32,
516     file_span: Span,
517     src: &'a str,
518     decorations: Option<Decorations>,
519 }
520
521 impl<'a> Classifier<'a> {
522     /// Takes as argument the source code to HTML-ify, the rust edition to use and the source code
523     /// file span which will be used later on by the `span_correspondance_map`.
524     fn new(src: &str, file_span: Span, decoration_info: Option<DecorationInfo>) -> Classifier<'_> {
525         let tokens = PeekIter::new(TokenIter { src });
526         let decorations = decoration_info.map(Decorations::new);
527         Classifier {
528             tokens,
529             in_attribute: false,
530             in_macro: false,
531             in_macro_nonterminal: false,
532             byte_pos: 0,
533             file_span,
534             src,
535             decorations,
536         }
537     }
538
539     /// Convenient wrapper to create a [`Span`] from a position in the file.
540     fn new_span(&self, lo: u32, text: &str) -> Span {
541         let hi = lo + text.len() as u32;
542         let file_lo = self.file_span.lo();
543         self.file_span.with_lo(file_lo + BytePos(lo)).with_hi(file_lo + BytePos(hi))
544     }
545
546     /// Concatenate colons and idents as one when possible.
547     fn get_full_ident_path(&mut self) -> Vec<(TokenKind, usize, usize)> {
548         let start = self.byte_pos as usize;
549         let mut pos = start;
550         let mut has_ident = false;
551
552         loop {
553             let mut nb = 0;
554             while let Some((TokenKind::Colon, _)) = self.tokens.peek() {
555                 self.tokens.next();
556                 nb += 1;
557             }
558             // Ident path can start with "::" but if we already have content in the ident path,
559             // the "::" is mandatory.
560             if has_ident && nb == 0 {
561                 return vec![(TokenKind::Ident, start, pos)];
562             } else if nb != 0 && nb != 2 {
563                 if has_ident {
564                     return vec![(TokenKind::Ident, start, pos), (TokenKind::Colon, pos, pos + nb)];
565                 } else {
566                     return vec![(TokenKind::Colon, start, pos + nb)];
567                 }
568             }
569
570             if let Some((None, text)) = self.tokens.peek().map(|(token, text)| {
571                 if *token == TokenKind::Ident {
572                     let class = get_real_ident_class(text, true);
573                     (class, text)
574                 } else {
575                     // Doesn't matter which Class we put in here...
576                     (Some(Class::Comment), text)
577                 }
578             }) {
579                 // We only "add" the colon if there is an ident behind.
580                 pos += text.len() + nb;
581                 has_ident = true;
582                 self.tokens.next();
583             } else if nb > 0 && has_ident {
584                 return vec![(TokenKind::Ident, start, pos), (TokenKind::Colon, pos, pos + nb)];
585             } else if nb > 0 {
586                 return vec![(TokenKind::Colon, start, start + nb)];
587             } else if has_ident {
588                 return vec![(TokenKind::Ident, start, pos)];
589             } else {
590                 return Vec::new();
591             }
592         }
593     }
594
595     /// Wraps the tokens iteration to ensure that the `byte_pos` is always correct.
596     ///
597     /// It returns the token's kind, the token as a string and its byte position in the source
598     /// string.
599     fn next(&mut self) -> Option<(TokenKind, &'a str, u32)> {
600         if let Some((kind, text)) = self.tokens.next() {
601             let before = self.byte_pos;
602             self.byte_pos += text.len() as u32;
603             Some((kind, text, before))
604         } else {
605             None
606         }
607     }
608
609     /// Exhausts the `Classifier` writing the output into `sink`.
610     ///
611     /// The general structure for this method is to iterate over each token,
612     /// possibly giving it an HTML span with a class specifying what flavor of
613     /// token is used.
614     fn highlight(mut self, sink: &mut dyn FnMut(Highlight<'a>)) {
615         loop {
616             if let Some(decs) = self.decorations.as_mut() {
617                 let byte_pos = self.byte_pos;
618                 let n_starts = decs.starts.iter().filter(|(i, _)| byte_pos >= *i).count();
619                 for (_, kind) in decs.starts.drain(0..n_starts) {
620                     sink(Highlight::EnterSpan { class: Class::Decoration(kind) });
621                 }
622
623                 let n_ends = decs.ends.iter().filter(|i| byte_pos >= **i).count();
624                 for _ in decs.ends.drain(0..n_ends) {
625                     sink(Highlight::ExitSpan);
626                 }
627             }
628
629             if self
630                 .tokens
631                 .peek()
632                 .map(|t| matches!(t.0, TokenKind::Colon | TokenKind::Ident))
633                 .unwrap_or(false)
634             {
635                 let tokens = self.get_full_ident_path();
636                 for (token, start, end) in &tokens {
637                     let text = &self.src[*start..*end];
638                     self.advance(*token, text, sink, *start as u32);
639                     self.byte_pos += text.len() as u32;
640                 }
641                 if !tokens.is_empty() {
642                     continue;
643                 }
644             }
645             if let Some((token, text, before)) = self.next() {
646                 self.advance(token, text, sink, before);
647             } else {
648                 break;
649             }
650         }
651     }
652
653     /// Single step of highlighting. This will classify `token`, but maybe also a couple of
654     /// following ones as well.
655     ///
656     /// `before` is the position of the given token in the `source` string and is used as "lo" byte
657     /// in case we want to try to generate a link for this token using the
658     /// `span_correspondance_map`.
659     fn advance(
660         &mut self,
661         token: TokenKind,
662         text: &'a str,
663         sink: &mut dyn FnMut(Highlight<'a>),
664         before: u32,
665     ) {
666         let lookahead = self.peek();
667         let no_highlight = |sink: &mut dyn FnMut(_)| sink(Highlight::Token { text, class: None });
668         let class = match token {
669             TokenKind::Whitespace => return no_highlight(sink),
670             TokenKind::LineComment { doc_style } | TokenKind::BlockComment { doc_style, .. } => {
671                 if doc_style.is_some() {
672                     Class::DocComment
673                 } else {
674                     Class::Comment
675                 }
676             }
677             // Consider this as part of a macro invocation if there was a
678             // leading identifier.
679             TokenKind::Bang if self.in_macro => {
680                 self.in_macro = false;
681                 sink(Highlight::Token { text, class: None });
682                 sink(Highlight::ExitSpan);
683                 return;
684             }
685
686             // Assume that '&' or '*' is the reference or dereference operator
687             // or a reference or pointer type. Unless, of course, it looks like
688             // a logical and or a multiplication operator: `&&` or `* `.
689             TokenKind::Star => match self.tokens.peek() {
690                 Some((TokenKind::Whitespace, _)) => return no_highlight(sink),
691                 Some((TokenKind::Ident, "mut")) => {
692                     self.next();
693                     sink(Highlight::Token { text: "*mut", class: Some(Class::RefKeyWord) });
694                     return;
695                 }
696                 Some((TokenKind::Ident, "const")) => {
697                     self.next();
698                     sink(Highlight::Token { text: "*const", class: Some(Class::RefKeyWord) });
699                     return;
700                 }
701                 _ => Class::RefKeyWord,
702             },
703             TokenKind::And => match self.tokens.peek() {
704                 Some((TokenKind::And, _)) => {
705                     self.next();
706                     sink(Highlight::Token { text: "&&", class: None });
707                     return;
708                 }
709                 Some((TokenKind::Eq, _)) => {
710                     self.next();
711                     sink(Highlight::Token { text: "&=", class: None });
712                     return;
713                 }
714                 Some((TokenKind::Whitespace, _)) => return no_highlight(sink),
715                 Some((TokenKind::Ident, "mut")) => {
716                     self.next();
717                     sink(Highlight::Token { text: "&mut", class: Some(Class::RefKeyWord) });
718                     return;
719                 }
720                 _ => Class::RefKeyWord,
721             },
722
723             // These can either be operators, or arrows.
724             TokenKind::Eq => match lookahead {
725                 Some(TokenKind::Eq) => {
726                     self.next();
727                     sink(Highlight::Token { text: "==", class: None });
728                     return;
729                 }
730                 Some(TokenKind::Gt) => {
731                     self.next();
732                     sink(Highlight::Token { text: "=>", class: None });
733                     return;
734                 }
735                 _ => return no_highlight(sink),
736             },
737             TokenKind::Minus if lookahead == Some(TokenKind::Gt) => {
738                 self.next();
739                 sink(Highlight::Token { text: "->", class: None });
740                 return;
741             }
742
743             // Other operators.
744             TokenKind::Minus
745             | TokenKind::Plus
746             | TokenKind::Or
747             | TokenKind::Slash
748             | TokenKind::Caret
749             | TokenKind::Percent
750             | TokenKind::Bang
751             | TokenKind::Lt
752             | TokenKind::Gt => return no_highlight(sink),
753
754             // Miscellaneous, no highlighting.
755             TokenKind::Dot
756             | TokenKind::Semi
757             | TokenKind::Comma
758             | TokenKind::OpenParen
759             | TokenKind::CloseParen
760             | TokenKind::OpenBrace
761             | TokenKind::CloseBrace
762             | TokenKind::OpenBracket
763             | TokenKind::At
764             | TokenKind::Tilde
765             | TokenKind::Colon
766             | TokenKind::Unknown => return no_highlight(sink),
767
768             TokenKind::Question => Class::QuestionMark,
769
770             TokenKind::Dollar => match lookahead {
771                 Some(TokenKind::Ident) => {
772                     self.in_macro_nonterminal = true;
773                     Class::MacroNonTerminal
774                 }
775                 _ => return no_highlight(sink),
776             },
777
778             // This might be the start of an attribute. We're going to want to
779             // continue highlighting it as an attribute until the ending ']' is
780             // seen, so skip out early. Down below we terminate the attribute
781             // span when we see the ']'.
782             TokenKind::Pound => {
783                 match lookahead {
784                     // Case 1: #![inner_attribute]
785                     Some(TokenKind::Bang) => {
786                         self.next();
787                         if let Some(TokenKind::OpenBracket) = self.peek() {
788                             self.in_attribute = true;
789                             sink(Highlight::EnterSpan { class: Class::Attribute });
790                         }
791                         sink(Highlight::Token { text: "#", class: None });
792                         sink(Highlight::Token { text: "!", class: None });
793                         return;
794                     }
795                     // Case 2: #[outer_attribute]
796                     Some(TokenKind::OpenBracket) => {
797                         self.in_attribute = true;
798                         sink(Highlight::EnterSpan { class: Class::Attribute });
799                     }
800                     _ => (),
801                 }
802                 return no_highlight(sink);
803             }
804             TokenKind::CloseBracket => {
805                 if self.in_attribute {
806                     self.in_attribute = false;
807                     sink(Highlight::Token { text: "]", class: None });
808                     sink(Highlight::ExitSpan);
809                     return;
810                 }
811                 return no_highlight(sink);
812             }
813             TokenKind::Literal { kind, .. } => match kind {
814                 // Text literals.
815                 LiteralKind::Byte { .. }
816                 | LiteralKind::Char { .. }
817                 | LiteralKind::Str { .. }
818                 | LiteralKind::ByteStr { .. }
819                 | LiteralKind::RawStr { .. }
820                 | LiteralKind::RawByteStr { .. } => Class::String,
821                 // Number literals.
822                 LiteralKind::Float { .. } | LiteralKind::Int { .. } => Class::Number,
823             },
824             TokenKind::Ident | TokenKind::RawIdent if lookahead == Some(TokenKind::Bang) => {
825                 self.in_macro = true;
826                 sink(Highlight::EnterSpan { class: Class::Macro(self.new_span(before, text)) });
827                 sink(Highlight::Token { text, class: None });
828                 return;
829             }
830             TokenKind::Ident => match get_real_ident_class(text, false) {
831                 None => match text {
832                     "Option" | "Result" => Class::PreludeTy,
833                     "Some" | "None" | "Ok" | "Err" => Class::PreludeVal,
834                     // "union" is a weak keyword and is only considered as a keyword when declaring
835                     // a union type.
836                     "union" if self.check_if_is_union_keyword() => Class::KeyWord,
837                     _ if self.in_macro_nonterminal => {
838                         self.in_macro_nonterminal = false;
839                         Class::MacroNonTerminal
840                     }
841                     "self" | "Self" => Class::Self_(self.new_span(before, text)),
842                     _ => Class::Ident(self.new_span(before, text)),
843                 },
844                 Some(c) => c,
845             },
846             TokenKind::RawIdent | TokenKind::UnknownPrefix | TokenKind::InvalidIdent => {
847                 Class::Ident(self.new_span(before, text))
848             }
849             TokenKind::Lifetime { .. } => Class::Lifetime,
850         };
851         // Anything that didn't return above is the simple case where we the
852         // class just spans a single token, so we can use the `string` method.
853         sink(Highlight::Token { text, class: Some(class) });
854     }
855
856     fn peek(&mut self) -> Option<TokenKind> {
857         self.tokens.peek().map(|(token_kind, _text)| *token_kind)
858     }
859
860     fn check_if_is_union_keyword(&mut self) -> bool {
861         while let Some(kind) = self.tokens.peek_next().map(|(token_kind, _text)| token_kind) {
862             if *kind == TokenKind::Whitespace {
863                 continue;
864             }
865             return *kind == TokenKind::Ident;
866         }
867         false
868     }
869 }
870
871 /// Called when we start processing a span of text that should be highlighted.
872 /// The `Class` argument specifies how it should be highlighted.
873 fn enter_span(
874     out: &mut Buffer,
875     klass: Class,
876     href_context: &Option<HrefContext<'_, '_, '_>>,
877 ) -> &'static str {
878     string_without_closing_tag(out, "", Some(klass), href_context, true).expect(
879         "internal error: enter_span was called with Some(klass) but did not return a \
880             closing HTML tag",
881     )
882 }
883
884 /// Called at the end of a span of highlighted text.
885 fn exit_span(out: &mut Buffer, closing_tag: &str) {
886     out.write_str(closing_tag);
887 }
888
889 /// Called for a span of text. If the text should be highlighted differently
890 /// from the surrounding text, then the `Class` argument will be a value other
891 /// than `None`.
892 ///
893 /// The following sequences of callbacks are equivalent:
894 /// ```plain
895 ///     enter_span(Foo), string("text", None), exit_span()
896 ///     string("text", Foo)
897 /// ```
898 ///
899 /// The latter can be thought of as a shorthand for the former, which is more
900 /// flexible.
901 ///
902 /// Note that if `context` is not `None` and that the given `klass` contains a `Span`, the function
903 /// will then try to find this `span` in the `span_correspondance_map`. If found, it'll then
904 /// generate a link for this element (which corresponds to where its definition is located).
905 fn string<T: Display>(
906     out: &mut Buffer,
907     text: T,
908     klass: Option<Class>,
909     href_context: &Option<HrefContext<'_, '_, '_>>,
910     open_tag: bool,
911 ) {
912     if let Some(closing_tag) = string_without_closing_tag(out, text, klass, href_context, open_tag)
913     {
914         out.write_str(closing_tag);
915     }
916 }
917
918 /// This function writes `text` into `out` with some modifications depending on `klass`:
919 ///
920 /// * If `klass` is `None`, `text` is written into `out` with no modification.
921 /// * If `klass` is `Some` but `klass.get_span()` is `None`, it writes the text wrapped in a
922 ///   `<span>` with the provided `klass`.
923 /// * If `klass` is `Some` and has a [`rustc_span::Span`], it then tries to generate a link (`<a>`
924 ///   element) by retrieving the link information from the `span_correspondance_map` that was filled
925 ///   in `span_map.rs::collect_spans_and_sources`. If it cannot retrieve the information, then it's
926 ///   the same as the second point (`klass` is `Some` but doesn't have a [`rustc_span::Span`]).
927 fn string_without_closing_tag<T: Display>(
928     out: &mut Buffer,
929     text: T,
930     klass: Option<Class>,
931     href_context: &Option<HrefContext<'_, '_, '_>>,
932     open_tag: bool,
933 ) -> Option<&'static str> {
934     let Some(klass) = klass
935     else {
936         write!(out, "{}", text);
937         return None;
938     };
939     let Some(def_span) = klass.get_span()
940     else {
941         if !open_tag {
942             write!(out, "{}", text);
943             return None;
944         }
945         write!(out, "<span class=\"{}\">{}", klass.as_html(), text);
946         return Some("</span>");
947     };
948
949     let mut text_s = text.to_string();
950     if text_s.contains("::") {
951         text_s = text_s.split("::").intersperse("::").fold(String::new(), |mut path, t| {
952             match t {
953                 "self" | "Self" => write!(
954                     &mut path,
955                     "<span class=\"{}\">{}</span>",
956                     Class::Self_(DUMMY_SP).as_html(),
957                     t
958                 ),
959                 "crate" | "super" => {
960                     write!(&mut path, "<span class=\"{}\">{}</span>", Class::KeyWord.as_html(), t)
961                 }
962                 t => write!(&mut path, "{}", t),
963             }
964             .expect("Failed to build source HTML path");
965             path
966         });
967     }
968
969     if let Some(href_context) = href_context {
970         if let Some(href) =
971             href_context.context.shared.span_correspondance_map.get(&def_span).and_then(|href| {
972                 let context = href_context.context;
973                 // FIXME: later on, it'd be nice to provide two links (if possible) for all items:
974                 // one to the documentation page and one to the source definition.
975                 // FIXME: currently, external items only generate a link to their documentation,
976                 // a link to their definition can be generated using this:
977                 // https://github.com/rust-lang/rust/blob/60f1a2fc4b535ead9c85ce085fdce49b1b097531/src/librustdoc/html/render/context.rs#L315-L338
978                 match href {
979                     LinkFromSrc::Local(span) => context
980                         .href_from_span(*span, true)
981                         .map(|s| format!("{}{}", href_context.root_path, s)),
982                     LinkFromSrc::External(def_id) => {
983                         format::href_with_root_path(*def_id, context, Some(href_context.root_path))
984                             .ok()
985                             .map(|(url, _, _)| url)
986                     }
987                     LinkFromSrc::Primitive(prim) => format::href_with_root_path(
988                         PrimitiveType::primitive_locations(context.tcx())[prim],
989                         context,
990                         Some(href_context.root_path),
991                     )
992                     .ok()
993                     .map(|(url, _, _)| url),
994                 }
995             })
996         {
997             if !open_tag {
998                 // We're already inside an element which has the same klass, no need to give it
999                 // again.
1000                 write!(out, "<a href=\"{}\">{}", href, text_s);
1001             } else {
1002                 let klass_s = klass.as_html();
1003                 if klass_s.is_empty() {
1004                     write!(out, "<a href=\"{}\">{}", href, text_s);
1005                 } else {
1006                     write!(out, "<a class=\"{}\" href=\"{}\">{}", klass_s, href, text_s);
1007                 }
1008             }
1009             return Some("</a>");
1010         }
1011     }
1012     if !open_tag {
1013         write!(out, "{}", text_s);
1014         return None;
1015     }
1016     let klass_s = klass.as_html();
1017     if klass_s.is_empty() {
1018         write!(out, "{}", text_s);
1019         Some("")
1020     } else {
1021         write!(out, "<span class=\"{}\">{}", klass_s, text_s);
1022         Some("</span>")
1023     }
1024 }
1025
1026 #[cfg(test)]
1027 mod tests;