]> git.lizzy.rs Git - rust.git/blob - src/librustdoc/html/highlight.rs
Sync core::simd up to rust-lang/portable-simd@2e081db92aa3ee0a4563bc28ce01bdad5b1b2efd
[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 pub(crate) struct DecorationInfo(pub(crate) FxHashMap<&'static str, Vec<(u32, u32)>>);
37
38 /// Highlights `src`, returning the HTML output.
39 pub(crate) fn render_with_highlighting(
40     src: &str,
41     out: &mut Buffer,
42     class: Option<&str>,
43     playground_button: Option<&str>,
44     tooltip: Option<(Option<Edition>, &str)>,
45     edition: Edition,
46     extra_content: Option<Buffer>,
47     href_context: Option<HrefContext<'_, '_, '_>>,
48     decoration_info: Option<DecorationInfo>,
49 ) {
50     debug!("highlighting: ================\n{}\n==============", src);
51     if let Some((edition_info, class)) = tooltip {
52         write!(
53             out,
54             "<div class='information'><div class='tooltip {}'{}>ⓘ</div></div>",
55             class,
56             if let Some(edition_info) = edition_info {
57                 format!(" data-edition=\"{}\"", edition_info)
58             } else {
59                 String::new()
60             },
61         );
62     }
63
64     write_header(out, class, extra_content);
65     write_code(out, src, edition, href_context, decoration_info);
66     write_footer(out, playground_button);
67 }
68
69 fn write_header(out: &mut Buffer, class: Option<&str>, extra_content: Option<Buffer>) {
70     write!(out, "<div class=\"example-wrap\">");
71     if let Some(extra) = extra_content {
72         out.push_buffer(extra);
73     }
74     if let Some(class) = class {
75         write!(out, "<pre class=\"rust {}\">", class);
76     } else {
77         write!(out, "<pre class=\"rust\">");
78     }
79     write!(out, "<code>");
80 }
81
82 /// Convert the given `src` source code into HTML by adding classes for highlighting.
83 ///
84 /// This code is used to render code blocks (in the documentation) as well as the source code pages.
85 ///
86 /// Some explanations on the last arguments:
87 ///
88 /// In case we are rendering a code block and not a source code file, `href_context` will be `None`.
89 /// To put it more simply: if `href_context` is `None`, the code won't try to generate links to an
90 /// item definition.
91 ///
92 /// More explanations about spans and how we use them here are provided in the
93 fn write_code(
94     out: &mut Buffer,
95     src: &str,
96     edition: Edition,
97     href_context: Option<HrefContext<'_, '_, '_>>,
98     decoration_info: Option<DecorationInfo>,
99 ) {
100     // This replace allows to fix how the code source with DOS backline characters is displayed.
101     let src = src.replace("\r\n", "\n");
102     let mut closing_tags: Vec<&'static str> = Vec::new();
103     Classifier::new(
104         &src,
105         edition,
106         href_context.as_ref().map(|c| c.file_span).unwrap_or(DUMMY_SP),
107         decoration_info,
108     )
109     .highlight(&mut |highlight| {
110         match highlight {
111             Highlight::Token { text, class } => string(out, Escape(text), class, &href_context),
112             Highlight::EnterSpan { class } => {
113                 closing_tags.push(enter_span(out, class, &href_context))
114             }
115             Highlight::ExitSpan => {
116                 exit_span(out, closing_tags.pop().expect("ExitSpan without EnterSpan"))
117             }
118         };
119     });
120 }
121
122 fn write_footer(out: &mut Buffer, playground_button: Option<&str>) {
123     writeln!(out, "</code></pre>{}</div>", playground_button.unwrap_or_default());
124 }
125
126 /// How a span of text is classified. Mostly corresponds to token kinds.
127 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
128 enum Class {
129     Comment,
130     DocComment,
131     Attribute,
132     KeyWord,
133     // Keywords that do pointer/reference stuff.
134     RefKeyWord,
135     Self_(Span),
136     Op,
137     Macro(Span),
138     MacroNonTerminal,
139     String,
140     Number,
141     Bool,
142     Ident(Span),
143     Lifetime,
144     PreludeTy,
145     PreludeVal,
146     QuestionMark,
147     Decoration(&'static str),
148 }
149
150 impl Class {
151     /// Returns the css class expected by rustdoc for each `Class`.
152     fn as_html(self) -> &'static str {
153         match self {
154             Class::Comment => "comment",
155             Class::DocComment => "doccomment",
156             Class::Attribute => "attribute",
157             Class::KeyWord => "kw",
158             Class::RefKeyWord => "kw-2",
159             Class::Self_(_) => "self",
160             Class::Op => "op",
161             Class::Macro(_) => "macro",
162             Class::MacroNonTerminal => "macro-nonterminal",
163             Class::String => "string",
164             Class::Number => "number",
165             Class::Bool => "bool-val",
166             Class::Ident(_) => "ident",
167             Class::Lifetime => "lifetime",
168             Class::PreludeTy => "prelude-ty",
169             Class::PreludeVal => "prelude-val",
170             Class::QuestionMark => "question-mark",
171             Class::Decoration(kind) => kind,
172         }
173     }
174
175     /// In case this is an item which can be converted into a link to a definition, it'll contain
176     /// a "span" (a tuple representing `(lo, hi)` equivalent of `Span`).
177     fn get_span(self) -> Option<Span> {
178         match self {
179             Self::Ident(sp) | Self::Self_(sp) | Self::Macro(sp) => Some(sp),
180             Self::Comment
181             | Self::DocComment
182             | Self::Attribute
183             | Self::KeyWord
184             | Self::RefKeyWord
185             | Self::Op
186             | Self::MacroNonTerminal
187             | Self::String
188             | Self::Number
189             | Self::Bool
190             | Self::Lifetime
191             | Self::PreludeTy
192             | Self::PreludeVal
193             | Self::QuestionMark
194             | Self::Decoration(_) => None,
195         }
196     }
197 }
198
199 enum Highlight<'a> {
200     Token { text: &'a str, class: Option<Class> },
201     EnterSpan { class: Class },
202     ExitSpan,
203 }
204
205 struct TokenIter<'a> {
206     src: &'a str,
207 }
208
209 impl<'a> Iterator for TokenIter<'a> {
210     type Item = (TokenKind, &'a str);
211     fn next(&mut self) -> Option<(TokenKind, &'a str)> {
212         if self.src.is_empty() {
213             return None;
214         }
215         let token = rustc_lexer::first_token(self.src);
216         let (text, rest) = self.src.split_at(token.len);
217         self.src = rest;
218         Some((token.kind, text))
219     }
220 }
221
222 /// Classifies into identifier class; returns `None` if this is a non-keyword identifier.
223 fn get_real_ident_class(text: &str, edition: Edition, allow_path_keywords: bool) -> Option<Class> {
224     let ignore: &[&str] =
225         if allow_path_keywords { &["self", "Self", "super", "crate"] } else { &["self", "Self"] };
226     if ignore.iter().any(|k| *k == text) {
227         return None;
228     }
229     Some(match text {
230         "ref" | "mut" => Class::RefKeyWord,
231         "false" | "true" => Class::Bool,
232         _ if Symbol::intern(text).is_reserved(|| edition) => Class::KeyWord,
233         _ => return None,
234     })
235 }
236
237 /// This iterator comes from the same idea than "Peekable" except that it allows to "peek" more than
238 /// just the next item by using `peek_next`. The `peek` method always returns the next item after
239 /// the current one whereas `peek_next` will return the next item after the last one peeked.
240 ///
241 /// You can use both `peek` and `peek_next` at the same time without problem.
242 struct PeekIter<'a> {
243     stored: VecDeque<(TokenKind, &'a str)>,
244     /// This position is reinitialized when using `next`. It is used in `peek_next`.
245     peek_pos: usize,
246     iter: TokenIter<'a>,
247 }
248
249 impl<'a> PeekIter<'a> {
250     fn new(iter: TokenIter<'a>) -> Self {
251         Self { stored: VecDeque::new(), peek_pos: 0, iter }
252     }
253     /// Returns the next item after the current one. It doesn't interfer with `peek_next` output.
254     fn peek(&mut self) -> Option<&(TokenKind, &'a str)> {
255         if self.stored.is_empty() {
256             if let Some(next) = self.iter.next() {
257                 self.stored.push_back(next);
258             }
259         }
260         self.stored.front()
261     }
262     /// Returns the next item after the last one peeked. It doesn't interfer with `peek` output.
263     fn peek_next(&mut self) -> Option<&(TokenKind, &'a str)> {
264         self.peek_pos += 1;
265         if self.peek_pos - 1 < self.stored.len() {
266             self.stored.get(self.peek_pos - 1)
267         } else if let Some(next) = self.iter.next() {
268             self.stored.push_back(next);
269             self.stored.back()
270         } else {
271             None
272         }
273     }
274 }
275
276 impl<'a> Iterator for PeekIter<'a> {
277     type Item = (TokenKind, &'a str);
278     fn next(&mut self) -> Option<Self::Item> {
279         self.peek_pos = 0;
280         if let Some(first) = self.stored.pop_front() { Some(first) } else { self.iter.next() }
281     }
282 }
283
284 /// Custom spans inserted into the source. Eg --scrape-examples uses this to highlight function calls
285 struct Decorations {
286     starts: Vec<(u32, &'static str)>,
287     ends: Vec<u32>,
288 }
289
290 impl Decorations {
291     fn new(info: DecorationInfo) -> Self {
292         // Extract tuples (start, end, kind) into separate sequences of (start, kind) and (end).
293         let (mut starts, mut ends): (Vec<_>, Vec<_>) = info
294             .0
295             .into_iter()
296             .flat_map(|(kind, ranges)| ranges.into_iter().map(move |(lo, hi)| ((lo, kind), hi)))
297             .unzip();
298
299         // Sort the sequences in document order.
300         starts.sort_by_key(|(lo, _)| *lo);
301         ends.sort();
302
303         Decorations { starts, ends }
304     }
305 }
306
307 /// Processes program tokens, classifying strings of text by highlighting
308 /// category (`Class`).
309 struct Classifier<'a> {
310     tokens: PeekIter<'a>,
311     in_attribute: bool,
312     in_macro: bool,
313     in_macro_nonterminal: bool,
314     edition: Edition,
315     byte_pos: u32,
316     file_span: Span,
317     src: &'a str,
318     decorations: Option<Decorations>,
319 }
320
321 impl<'a> Classifier<'a> {
322     /// Takes as argument the source code to HTML-ify, the rust edition to use and the source code
323     /// file span which will be used later on by the `span_correspondance_map`.
324     fn new(
325         src: &str,
326         edition: Edition,
327         file_span: Span,
328         decoration_info: Option<DecorationInfo>,
329     ) -> Classifier<'_> {
330         let tokens = PeekIter::new(TokenIter { src });
331         let decorations = decoration_info.map(Decorations::new);
332         Classifier {
333             tokens,
334             in_attribute: false,
335             in_macro: false,
336             in_macro_nonterminal: false,
337             edition,
338             byte_pos: 0,
339             file_span,
340             src,
341             decorations,
342         }
343     }
344
345     /// Convenient wrapper to create a [`Span`] from a position in the file.
346     fn new_span(&self, lo: u32, text: &str) -> Span {
347         let hi = lo + text.len() as u32;
348         let file_lo = self.file_span.lo();
349         self.file_span.with_lo(file_lo + BytePos(lo)).with_hi(file_lo + BytePos(hi))
350     }
351
352     /// Concatenate colons and idents as one when possible.
353     fn get_full_ident_path(&mut self) -> Vec<(TokenKind, usize, usize)> {
354         let start = self.byte_pos as usize;
355         let mut pos = start;
356         let mut has_ident = false;
357         let edition = self.edition;
358
359         loop {
360             let mut nb = 0;
361             while let Some((TokenKind::Colon, _)) = self.tokens.peek() {
362                 self.tokens.next();
363                 nb += 1;
364             }
365             // Ident path can start with "::" but if we already have content in the ident path,
366             // the "::" is mandatory.
367             if has_ident && nb == 0 {
368                 return vec![(TokenKind::Ident, start, pos)];
369             } else if nb != 0 && nb != 2 {
370                 if has_ident {
371                     return vec![(TokenKind::Ident, start, pos), (TokenKind::Colon, pos, pos + nb)];
372                 } else {
373                     return vec![(TokenKind::Colon, start, pos + nb)];
374                 }
375             }
376
377             if let Some((None, text)) = self.tokens.peek().map(|(token, text)| {
378                 if *token == TokenKind::Ident {
379                     let class = get_real_ident_class(text, edition, true);
380                     (class, text)
381                 } else {
382                     // Doesn't matter which Class we put in here...
383                     (Some(Class::Comment), text)
384                 }
385             }) {
386                 // We only "add" the colon if there is an ident behind.
387                 pos += text.len() + nb;
388                 has_ident = true;
389                 self.tokens.next();
390             } else if nb > 0 && has_ident {
391                 return vec![(TokenKind::Ident, start, pos), (TokenKind::Colon, pos, pos + nb)];
392             } else if nb > 0 {
393                 return vec![(TokenKind::Colon, start, start + nb)];
394             } else if has_ident {
395                 return vec![(TokenKind::Ident, start, pos)];
396             } else {
397                 return Vec::new();
398             }
399         }
400     }
401
402     /// Wraps the tokens iteration to ensure that the `byte_pos` is always correct.
403     ///
404     /// It returns the token's kind, the token as a string and its byte position in the source
405     /// string.
406     fn next(&mut self) -> Option<(TokenKind, &'a str, u32)> {
407         if let Some((kind, text)) = self.tokens.next() {
408             let before = self.byte_pos;
409             self.byte_pos += text.len() as u32;
410             Some((kind, text, before))
411         } else {
412             None
413         }
414     }
415
416     /// Exhausts the `Classifier` writing the output into `sink`.
417     ///
418     /// The general structure for this method is to iterate over each token,
419     /// possibly giving it an HTML span with a class specifying what flavor of
420     /// token is used.
421     fn highlight(mut self, sink: &mut dyn FnMut(Highlight<'a>)) {
422         loop {
423             if let Some(decs) = self.decorations.as_mut() {
424                 let byte_pos = self.byte_pos;
425                 let n_starts = decs.starts.iter().filter(|(i, _)| byte_pos >= *i).count();
426                 for (_, kind) in decs.starts.drain(0..n_starts) {
427                     sink(Highlight::EnterSpan { class: Class::Decoration(kind) });
428                 }
429
430                 let n_ends = decs.ends.iter().filter(|i| byte_pos >= **i).count();
431                 for _ in decs.ends.drain(0..n_ends) {
432                     sink(Highlight::ExitSpan);
433                 }
434             }
435
436             if self
437                 .tokens
438                 .peek()
439                 .map(|t| matches!(t.0, TokenKind::Colon | TokenKind::Ident))
440                 .unwrap_or(false)
441             {
442                 let tokens = self.get_full_ident_path();
443                 for (token, start, end) in &tokens {
444                     let text = &self.src[*start..*end];
445                     self.advance(*token, text, sink, *start as u32);
446                     self.byte_pos += text.len() as u32;
447                 }
448                 if !tokens.is_empty() {
449                     continue;
450                 }
451             }
452             if let Some((token, text, before)) = self.next() {
453                 self.advance(token, text, sink, before);
454             } else {
455                 break;
456             }
457         }
458     }
459
460     /// Single step of highlighting. This will classify `token`, but maybe also a couple of
461     /// following ones as well.
462     ///
463     /// `before` is the position of the given token in the `source` string and is used as "lo" byte
464     /// in case we want to try to generate a link for this token using the
465     /// `span_correspondance_map`.
466     fn advance(
467         &mut self,
468         token: TokenKind,
469         text: &'a str,
470         sink: &mut dyn FnMut(Highlight<'a>),
471         before: u32,
472     ) {
473         let lookahead = self.peek();
474         let no_highlight = |sink: &mut dyn FnMut(_)| sink(Highlight::Token { text, class: None });
475         let class = match token {
476             TokenKind::Whitespace => return no_highlight(sink),
477             TokenKind::LineComment { doc_style } | TokenKind::BlockComment { doc_style, .. } => {
478                 if doc_style.is_some() {
479                     Class::DocComment
480                 } else {
481                     Class::Comment
482                 }
483             }
484             // Consider this as part of a macro invocation if there was a
485             // leading identifier.
486             TokenKind::Bang if self.in_macro => {
487                 self.in_macro = false;
488                 sink(Highlight::Token { text, class: None });
489                 sink(Highlight::ExitSpan);
490                 return;
491             }
492
493             // Assume that '&' or '*' is the reference or dereference operator
494             // or a reference or pointer type. Unless, of course, it looks like
495             // a logical and or a multiplication operator: `&&` or `* `.
496             TokenKind::Star => match self.tokens.peek() {
497                 Some((TokenKind::Whitespace, _)) => Class::Op,
498                 Some((TokenKind::Ident, "mut")) => {
499                     self.next();
500                     sink(Highlight::Token { text: "*mut", class: Some(Class::RefKeyWord) });
501                     return;
502                 }
503                 Some((TokenKind::Ident, "const")) => {
504                     self.next();
505                     sink(Highlight::Token { text: "*const", class: Some(Class::RefKeyWord) });
506                     return;
507                 }
508                 _ => Class::RefKeyWord,
509             },
510             TokenKind::And => match self.tokens.peek() {
511                 Some((TokenKind::And, _)) => {
512                     self.next();
513                     sink(Highlight::Token { text: "&&", class: Some(Class::Op) });
514                     return;
515                 }
516                 Some((TokenKind::Eq, _)) => {
517                     self.next();
518                     sink(Highlight::Token { text: "&=", class: Some(Class::Op) });
519                     return;
520                 }
521                 Some((TokenKind::Whitespace, _)) => Class::Op,
522                 Some((TokenKind::Ident, "mut")) => {
523                     self.next();
524                     sink(Highlight::Token { text: "&mut", class: Some(Class::RefKeyWord) });
525                     return;
526                 }
527                 _ => Class::RefKeyWord,
528             },
529
530             // These can either be operators, or arrows.
531             TokenKind::Eq => match lookahead {
532                 Some(TokenKind::Eq) => {
533                     self.next();
534                     sink(Highlight::Token { text: "==", class: Some(Class::Op) });
535                     return;
536                 }
537                 Some(TokenKind::Gt) => {
538                     self.next();
539                     sink(Highlight::Token { text: "=>", class: None });
540                     return;
541                 }
542                 _ => Class::Op,
543             },
544             TokenKind::Minus if lookahead == Some(TokenKind::Gt) => {
545                 self.next();
546                 sink(Highlight::Token { text: "->", class: None });
547                 return;
548             }
549
550             // Other operators.
551             TokenKind::Minus
552             | TokenKind::Plus
553             | TokenKind::Or
554             | TokenKind::Slash
555             | TokenKind::Caret
556             | TokenKind::Percent
557             | TokenKind::Bang
558             | TokenKind::Lt
559             | TokenKind::Gt => Class::Op,
560
561             // Miscellaneous, no highlighting.
562             TokenKind::Dot
563             | TokenKind::Semi
564             | TokenKind::Comma
565             | TokenKind::OpenParen
566             | TokenKind::CloseParen
567             | TokenKind::OpenBrace
568             | TokenKind::CloseBrace
569             | TokenKind::OpenBracket
570             | TokenKind::At
571             | TokenKind::Tilde
572             | TokenKind::Colon
573             | TokenKind::Unknown => return no_highlight(sink),
574
575             TokenKind::Question => Class::QuestionMark,
576
577             TokenKind::Dollar => match lookahead {
578                 Some(TokenKind::Ident) => {
579                     self.in_macro_nonterminal = true;
580                     Class::MacroNonTerminal
581                 }
582                 _ => return no_highlight(sink),
583             },
584
585             // This might be the start of an attribute. We're going to want to
586             // continue highlighting it as an attribute until the ending ']' is
587             // seen, so skip out early. Down below we terminate the attribute
588             // span when we see the ']'.
589             TokenKind::Pound => {
590                 match lookahead {
591                     // Case 1: #![inner_attribute]
592                     Some(TokenKind::Bang) => {
593                         self.next();
594                         if let Some(TokenKind::OpenBracket) = self.peek() {
595                             self.in_attribute = true;
596                             sink(Highlight::EnterSpan { class: Class::Attribute });
597                         }
598                         sink(Highlight::Token { text: "#", class: None });
599                         sink(Highlight::Token { text: "!", class: None });
600                         return;
601                     }
602                     // Case 2: #[outer_attribute]
603                     Some(TokenKind::OpenBracket) => {
604                         self.in_attribute = true;
605                         sink(Highlight::EnterSpan { class: Class::Attribute });
606                     }
607                     _ => (),
608                 }
609                 return no_highlight(sink);
610             }
611             TokenKind::CloseBracket => {
612                 if self.in_attribute {
613                     self.in_attribute = false;
614                     sink(Highlight::Token { text: "]", class: None });
615                     sink(Highlight::ExitSpan);
616                     return;
617                 }
618                 return no_highlight(sink);
619             }
620             TokenKind::Literal { kind, .. } => match kind {
621                 // Text literals.
622                 LiteralKind::Byte { .. }
623                 | LiteralKind::Char { .. }
624                 | LiteralKind::Str { .. }
625                 | LiteralKind::ByteStr { .. }
626                 | LiteralKind::RawStr { .. }
627                 | LiteralKind::RawByteStr { .. } => Class::String,
628                 // Number literals.
629                 LiteralKind::Float { .. } | LiteralKind::Int { .. } => Class::Number,
630             },
631             TokenKind::Ident | TokenKind::RawIdent if lookahead == Some(TokenKind::Bang) => {
632                 self.in_macro = true;
633                 sink(Highlight::EnterSpan { class: Class::Macro(self.new_span(before, text)) });
634                 sink(Highlight::Token { text, class: None });
635                 return;
636             }
637             TokenKind::Ident => match get_real_ident_class(text, self.edition, false) {
638                 None => match text {
639                     "Option" | "Result" => Class::PreludeTy,
640                     "Some" | "None" | "Ok" | "Err" => Class::PreludeVal,
641                     // "union" is a weak keyword and is only considered as a keyword when declaring
642                     // a union type.
643                     "union" if self.check_if_is_union_keyword() => Class::KeyWord,
644                     _ if self.in_macro_nonterminal => {
645                         self.in_macro_nonterminal = false;
646                         Class::MacroNonTerminal
647                     }
648                     "self" | "Self" => Class::Self_(self.new_span(before, text)),
649                     _ => Class::Ident(self.new_span(before, text)),
650                 },
651                 Some(c) => c,
652             },
653             TokenKind::RawIdent | TokenKind::UnknownPrefix | TokenKind::InvalidIdent => {
654                 Class::Ident(self.new_span(before, text))
655             }
656             TokenKind::Lifetime { .. } => Class::Lifetime,
657         };
658         // Anything that didn't return above is the simple case where we the
659         // class just spans a single token, so we can use the `string` method.
660         sink(Highlight::Token { text, class: Some(class) });
661     }
662
663     fn peek(&mut self) -> Option<TokenKind> {
664         self.tokens.peek().map(|(token_kind, _text)| *token_kind)
665     }
666
667     fn check_if_is_union_keyword(&mut self) -> bool {
668         while let Some(kind) = self.tokens.peek_next().map(|(token_kind, _text)| token_kind) {
669             if *kind == TokenKind::Whitespace {
670                 continue;
671             }
672             return *kind == TokenKind::Ident;
673         }
674         false
675     }
676 }
677
678 /// Called when we start processing a span of text that should be highlighted.
679 /// The `Class` argument specifies how it should be highlighted.
680 fn enter_span(
681     out: &mut Buffer,
682     klass: Class,
683     href_context: &Option<HrefContext<'_, '_, '_>>,
684 ) -> &'static str {
685     string_without_closing_tag(out, "", Some(klass), href_context).expect(
686         "internal error: enter_span was called with Some(klass) but did not return a \
687             closing HTML tag",
688     )
689 }
690
691 /// Called at the end of a span of highlighted text.
692 fn exit_span(out: &mut Buffer, closing_tag: &str) {
693     out.write_str(closing_tag);
694 }
695
696 /// Called for a span of text. If the text should be highlighted differently
697 /// from the surrounding text, then the `Class` argument will be a value other
698 /// than `None`.
699 ///
700 /// The following sequences of callbacks are equivalent:
701 /// ```plain
702 ///     enter_span(Foo), string("text", None), exit_span()
703 ///     string("text", Foo)
704 /// ```
705 ///
706 /// The latter can be thought of as a shorthand for the former, which is more
707 /// flexible.
708 ///
709 /// Note that if `context` is not `None` and that the given `klass` contains a `Span`, the function
710 /// will then try to find this `span` in the `span_correspondance_map`. If found, it'll then
711 /// generate a link for this element (which corresponds to where its definition is located).
712 fn string<T: Display>(
713     out: &mut Buffer,
714     text: T,
715     klass: Option<Class>,
716     href_context: &Option<HrefContext<'_, '_, '_>>,
717 ) {
718     if let Some(closing_tag) = string_without_closing_tag(out, text, klass, href_context) {
719         out.write_str(closing_tag);
720     }
721 }
722
723 /// This function writes `text` into `out` with some modifications depending on `klass`:
724 ///
725 /// * If `klass` is `None`, `text` is written into `out` with no modification.
726 /// * If `klass` is `Some` but `klass.get_span()` is `None`, it writes the text wrapped in a
727 ///   `<span>` with the provided `klass`.
728 /// * If `klass` is `Some` and has a [`rustc_span::Span`], it then tries to generate a link (`<a>`
729 ///   element) by retrieving the link information from the `span_correspondance_map` that was filled
730 ///   in `span_map.rs::collect_spans_and_sources`. If it cannot retrieve the information, then it's
731 ///   the same as the second point (`klass` is `Some` but doesn't have a [`rustc_span::Span`]).
732 fn string_without_closing_tag<T: Display>(
733     out: &mut Buffer,
734     text: T,
735     klass: Option<Class>,
736     href_context: &Option<HrefContext<'_, '_, '_>>,
737 ) -> Option<&'static str> {
738     let Some(klass) = klass
739     else {
740         write!(out, "{}", text);
741         return None;
742     };
743     let Some(def_span) = klass.get_span()
744     else {
745         write!(out, "<span class=\"{}\">{}", klass.as_html(), text);
746         return Some("</span>");
747     };
748
749     let mut text_s = text.to_string();
750     if text_s.contains("::") {
751         text_s = text_s.split("::").intersperse("::").fold(String::new(), |mut path, t| {
752             match t {
753                 "self" | "Self" => write!(
754                     &mut path,
755                     "<span class=\"{}\">{}</span>",
756                     Class::Self_(DUMMY_SP).as_html(),
757                     t
758                 ),
759                 "crate" | "super" => {
760                     write!(&mut path, "<span class=\"{}\">{}</span>", Class::KeyWord.as_html(), t)
761                 }
762                 t => write!(&mut path, "{}", t),
763             }
764             .expect("Failed to build source HTML path");
765             path
766         });
767     }
768     if let Some(href_context) = href_context {
769         if let Some(href) =
770             href_context.context.shared.span_correspondance_map.get(&def_span).and_then(|href| {
771                 let context = href_context.context;
772                 // FIXME: later on, it'd be nice to provide two links (if possible) for all items:
773                 // one to the documentation page and one to the source definition.
774                 // FIXME: currently, external items only generate a link to their documentation,
775                 // a link to their definition can be generated using this:
776                 // https://github.com/rust-lang/rust/blob/60f1a2fc4b535ead9c85ce085fdce49b1b097531/src/librustdoc/html/render/context.rs#L315-L338
777                 match href {
778                     LinkFromSrc::Local(span) => context
779                         .href_from_span(*span, true)
780                         .map(|s| format!("{}{}", href_context.root_path, s)),
781                     LinkFromSrc::External(def_id) => {
782                         format::href_with_root_path(*def_id, context, Some(href_context.root_path))
783                             .ok()
784                             .map(|(url, _, _)| url)
785                     }
786                     LinkFromSrc::Primitive(prim) => format::href_with_root_path(
787                         PrimitiveType::primitive_locations(context.tcx())[prim],
788                         context,
789                         Some(href_context.root_path),
790                     )
791                     .ok()
792                     .map(|(url, _, _)| url),
793                 }
794             })
795         {
796             write!(out, "<a class=\"{}\" href=\"{}\">{}", klass.as_html(), href, text_s);
797             return Some("</a>");
798         }
799     }
800     write!(out, "<span class=\"{}\">{}", klass.as_html(), text_s);
801     Some("</span>")
802 }
803
804 #[cfg(test)]
805 mod tests;