summaryrefslogtreecommitdiffstats
path: root/src/librustdoc/html/highlight
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/librustdoc/html/highlight.rs368
-rw-r--r--src/librustdoc/html/highlight/fixtures/decorations.html6
-rw-r--r--src/librustdoc/html/highlight/fixtures/dos_line.html2
-rw-r--r--src/librustdoc/html/highlight/fixtures/highlight.html8
-rw-r--r--src/librustdoc/html/highlight/fixtures/sample.html39
-rw-r--r--src/librustdoc/html/highlight/fixtures/sample.rs1
-rw-r--r--src/librustdoc/html/highlight/fixtures/union.html10
-rw-r--r--src/librustdoc/html/highlight/tests.rs18
8 files changed, 342 insertions, 110 deletions
diff --git a/src/librustdoc/html/highlight.rs b/src/librustdoc/html/highlight.rs
index 05547ea15..5e28204b2 100644
--- a/src/librustdoc/html/highlight.rs
+++ b/src/librustdoc/html/highlight.rs
@@ -7,19 +7,18 @@
use crate::clean::PrimitiveType;
use crate::html::escape::Escape;
-use crate::html::render::Context;
+use crate::html::render::{Context, LinkFromSrc};
use std::collections::VecDeque;
use std::fmt::{Display, Write};
use rustc_data_structures::fx::FxHashMap;
-use rustc_lexer::{LiteralKind, TokenKind};
+use rustc_lexer::{Cursor, LiteralKind, TokenKind};
use rustc_span::edition::Edition;
use rustc_span::symbol::Symbol;
use rustc_span::{BytePos, Span, DUMMY_SP};
use super::format::{self, Buffer};
-use super::render::LinkFromSrc;
/// This type is needed in case we want to render links on items to allow to go to their definition.
pub(crate) struct HrefContext<'a, 'b, 'c> {
@@ -29,31 +28,74 @@ pub(crate) struct HrefContext<'a, 'b, 'c> {
/// This field is used to know "how far" from the top of the directory we are to link to either
/// documentation pages or other source pages.
pub(crate) root_path: &'c str,
+ /// This field is used to calculate precise local URLs.
+ pub(crate) current_href: &'c str,
}
/// Decorations are represented as a map from CSS class to vector of character ranges.
/// Each range will be wrapped in a span with that class.
+#[derive(Default)]
pub(crate) struct DecorationInfo(pub(crate) FxHashMap<&'static str, Vec<(u32, u32)>>);
-/// Highlights `src`, returning the HTML output.
-pub(crate) fn render_with_highlighting(
+#[derive(Eq, PartialEq, Clone, Copy)]
+pub(crate) enum Tooltip {
+ Ignore,
+ CompileFail,
+ ShouldPanic,
+ Edition(Edition),
+ None,
+}
+
+/// Highlights `src` as an inline example, returning the HTML output.
+pub(crate) fn render_example_with_highlighting(
src: &str,
out: &mut Buffer,
- class: Option<&str>,
+ tooltip: Tooltip,
playground_button: Option<&str>,
- tooltip: Option<(Option<Edition>, &str)>,
- edition: Edition,
- extra_content: Option<Buffer>,
- href_context: Option<HrefContext<'_, '_, '_>>,
- decoration_info: Option<DecorationInfo>,
) {
- debug!("highlighting: ================\n{}\n==============", src);
- if let Some((edition_info, class)) = tooltip {
+ write_header(out, "rust-example-rendered", None, tooltip);
+ write_code(out, src, None, None);
+ write_footer(out, playground_button);
+}
+
+/// Highlights `src` as a macro, returning the HTML output.
+pub(crate) fn render_macro_with_highlighting(src: &str, out: &mut Buffer) {
+ write_header(out, "macro", None, Tooltip::None);
+ write_code(out, src, None, None);
+ write_footer(out, None);
+}
+
+/// Highlights `src` as a source code page, returning the HTML output.
+pub(crate) fn render_source_with_highlighting(
+ src: &str,
+ out: &mut Buffer,
+ line_numbers: Buffer,
+ href_context: HrefContext<'_, '_, '_>,
+ decoration_info: DecorationInfo,
+) {
+ write_header(out, "", Some(line_numbers), Tooltip::None);
+ write_code(out, src, Some(href_context), Some(decoration_info));
+ write_footer(out, None);
+}
+
+fn write_header(out: &mut Buffer, class: &str, extra_content: Option<Buffer>, tooltip: Tooltip) {
+ write!(
+ out,
+ "<div class=\"example-wrap{}\">",
+ match tooltip {
+ Tooltip::Ignore => " ignore",
+ Tooltip::CompileFail => " compile_fail",
+ Tooltip::ShouldPanic => " should_panic",
+ Tooltip::Edition(_) => " edition",
+ Tooltip::None => "",
+ },
+ );
+
+ if tooltip != Tooltip::None {
write!(
out,
- "<div class='information'><div class='tooltip {}'{}>ⓘ</div></div>",
- class,
- if let Some(edition_info) = edition_info {
+ "<div class='tooltip'{}>ⓘ</div>",
+ if let Tooltip::Edition(edition_info) = tooltip {
format!(" data-edition=\"{}\"", edition_info)
} else {
String::new()
@@ -61,24 +103,115 @@ pub(crate) fn render_with_highlighting(
);
}
- write_header(out, class, extra_content);
- write_code(out, src, edition, href_context, decoration_info);
- write_footer(out, playground_button);
-}
-
-fn write_header(out: &mut Buffer, class: Option<&str>, extra_content: Option<Buffer>) {
- write!(out, "<div class=\"example-wrap\">");
if let Some(extra) = extra_content {
out.push_buffer(extra);
}
- if let Some(class) = class {
- write!(out, "<pre class=\"rust {}\">", class);
- } else {
+ if class.is_empty() {
write!(out, "<pre class=\"rust\">");
+ } else {
+ write!(out, "<pre class=\"rust {class}\">");
}
write!(out, "<code>");
}
+/// Check if two `Class` can be merged together. In the following rules, "unclassified" means `None`
+/// basically (since it's `Option<Class>`). The following rules apply:
+///
+/// * If two `Class` have the same variant, then they can be merged.
+/// * If the other `Class` is unclassified and only contains white characters (backline,
+/// whitespace, etc), it can be merged.
+/// * `Class::Ident` is considered the same as unclassified (because it doesn't have an associated
+/// CSS class).
+fn can_merge(class1: Option<Class>, class2: Option<Class>, text: &str) -> bool {
+ match (class1, class2) {
+ (Some(c1), Some(c2)) => c1.is_equal_to(c2),
+ (Some(Class::Ident(_)), None) | (None, Some(Class::Ident(_))) => true,
+ (Some(_), None) | (None, Some(_)) => text.trim().is_empty(),
+ (None, None) => true,
+ }
+}
+
+/// This type is used as a conveniency to prevent having to pass all its fields as arguments into
+/// the various functions (which became its methods).
+struct TokenHandler<'a, 'b, 'c, 'd, 'e> {
+ out: &'a mut Buffer,
+ /// It contains the closing tag and the associated `Class`.
+ closing_tags: Vec<(&'static str, Class)>,
+ /// This is used because we don't automatically generate the closing tag on `ExitSpan` in
+ /// case an `EnterSpan` event with the same class follows.
+ pending_exit_span: Option<Class>,
+ /// `current_class` and `pending_elems` are used to group HTML elements with same `class`
+ /// attributes to reduce the DOM size.
+ current_class: Option<Class>,
+ /// We need to keep the `Class` for each element because it could contain a `Span` which is
+ /// used to generate links.
+ pending_elems: Vec<(&'b str, Option<Class>)>,
+ href_context: Option<HrefContext<'c, 'd, 'e>>,
+}
+
+impl<'a, 'b, 'c, 'd, 'e> TokenHandler<'a, 'b, 'c, 'd, 'e> {
+ fn handle_exit_span(&mut self) {
+ // We can't get the last `closing_tags` element using `pop()` because `closing_tags` is
+ // being used in `write_pending_elems`.
+ let class = self.closing_tags.last().expect("ExitSpan without EnterSpan").1;
+ // We flush everything just in case...
+ self.write_pending_elems(Some(class));
+
+ exit_span(self.out, self.closing_tags.pop().expect("ExitSpan without EnterSpan").0);
+ self.pending_exit_span = None;
+ }
+
+ /// Write all the pending elements sharing a same (or at mergeable) `Class`.
+ ///
+ /// If there is a "parent" (if a `EnterSpan` event was encountered) and the parent can be merged
+ /// with the elements' class, then we simply write the elements since the `ExitSpan` event will
+ /// close the tag.
+ ///
+ /// Otherwise, if there is only one pending element, we let the `string` function handle both
+ /// opening and closing the tag, otherwise we do it into this function.
+ ///
+ /// It returns `true` if `current_class` must be set to `None` afterwards.
+ fn write_pending_elems(&mut self, current_class: Option<Class>) -> bool {
+ if self.pending_elems.is_empty() {
+ return false;
+ }
+ if let Some((_, parent_class)) = self.closing_tags.last() &&
+ can_merge(current_class, Some(*parent_class), "")
+ {
+ for (text, class) in self.pending_elems.iter() {
+ string(self.out, Escape(text), *class, &self.href_context, false);
+ }
+ } else {
+ // We only want to "open" the tag ourselves if we have more than one pending and if the
+ // current parent tag is not the same as our pending content.
+ let close_tag = if self.pending_elems.len() > 1 && current_class.is_some() {
+ Some(enter_span(self.out, current_class.unwrap(), &self.href_context))
+ } else {
+ None
+ };
+ for (text, class) in self.pending_elems.iter() {
+ string(self.out, Escape(text), *class, &self.href_context, close_tag.is_none());
+ }
+ if let Some(close_tag) = close_tag {
+ exit_span(self.out, close_tag);
+ }
+ }
+ self.pending_elems.clear();
+ true
+ }
+}
+
+impl<'a, 'b, 'c, 'd, 'e> Drop for TokenHandler<'a, 'b, 'c, 'd, 'e> {
+ /// When leaving, we need to flush all pending data to not have missing content.
+ fn drop(&mut self) {
+ if self.pending_exit_span.is_some() {
+ self.handle_exit_span();
+ } else {
+ self.write_pending_elems(self.current_class);
+ }
+ }
+}
+
/// Convert the given `src` source code into HTML by adding classes for highlighting.
///
/// This code is used to render code blocks (in the documentation) as well as the source code pages.
@@ -93,27 +226,74 @@ fn write_header(out: &mut Buffer, class: Option<&str>, extra_content: Option<Buf
fn write_code(
out: &mut Buffer,
src: &str,
- edition: Edition,
href_context: Option<HrefContext<'_, '_, '_>>,
decoration_info: Option<DecorationInfo>,
) {
// This replace allows to fix how the code source with DOS backline characters is displayed.
let src = src.replace("\r\n", "\n");
- let mut closing_tags: Vec<&'static str> = Vec::new();
+ let mut token_handler = TokenHandler {
+ out,
+ closing_tags: Vec::new(),
+ pending_exit_span: None,
+ current_class: None,
+ pending_elems: Vec::new(),
+ href_context,
+ };
+
Classifier::new(
&src,
- edition,
- href_context.as_ref().map(|c| c.file_span).unwrap_or(DUMMY_SP),
+ token_handler.href_context.as_ref().map(|c| c.file_span).unwrap_or(DUMMY_SP),
decoration_info,
)
.highlight(&mut |highlight| {
match highlight {
- Highlight::Token { text, class } => string(out, Escape(text), class, &href_context),
+ Highlight::Token { text, class } => {
+ // If we received a `ExitSpan` event and then have a non-compatible `Class`, we
+ // need to close the `<span>`.
+ let need_current_class_update = if let Some(pending) = token_handler.pending_exit_span &&
+ !can_merge(Some(pending), class, text) {
+ token_handler.handle_exit_span();
+ true
+ // If the two `Class` are different, time to flush the current content and start
+ // a new one.
+ } else if !can_merge(token_handler.current_class, class, text) {
+ token_handler.write_pending_elems(token_handler.current_class);
+ true
+ } else {
+ token_handler.current_class.is_none()
+ };
+
+ if need_current_class_update {
+ token_handler.current_class = class.map(Class::dummy);
+ }
+ token_handler.pending_elems.push((text, class));
+ }
Highlight::EnterSpan { class } => {
- closing_tags.push(enter_span(out, class, &href_context))
+ let mut should_add = true;
+ if let Some(pending_exit_span) = token_handler.pending_exit_span {
+ if class.is_equal_to(pending_exit_span) {
+ should_add = false;
+ } else {
+ token_handler.handle_exit_span();
+ }
+ } else {
+ // We flush everything just in case...
+ if token_handler.write_pending_elems(token_handler.current_class) {
+ token_handler.current_class = None;
+ }
+ }
+ if should_add {
+ let closing_tag = enter_span(token_handler.out, class, &token_handler.href_context);
+ token_handler.closing_tags.push((closing_tag, class));
+ }
+
+ token_handler.current_class = None;
+ token_handler.pending_exit_span = None;
}
Highlight::ExitSpan => {
- exit_span(out, closing_tags.pop().expect("ExitSpan without EnterSpan"))
+ token_handler.current_class = None;
+ token_handler.pending_exit_span =
+ Some(token_handler.closing_tags.last().as_ref().expect("ExitSpan without EnterSpan").1);
}
};
});
@@ -130,15 +310,15 @@ enum Class {
DocComment,
Attribute,
KeyWord,
- // Keywords that do pointer/reference stuff.
+ /// Keywords that do pointer/reference stuff.
RefKeyWord,
Self_(Span),
- Op,
Macro(Span),
MacroNonTerminal,
String,
Number,
Bool,
+ /// `Ident` isn't rendered in the HTML but we still need it for the `Span` it contains.
Ident(Span),
Lifetime,
PreludeTy,
@@ -148,6 +328,31 @@ enum Class {
}
impl Class {
+ /// It is only looking at the variant, not the variant content.
+ ///
+ /// It is used mostly to group multiple similar HTML elements into one `<span>` instead of
+ /// multiple ones.
+ fn is_equal_to(self, other: Self) -> bool {
+ match (self, other) {
+ (Self::Self_(_), Self::Self_(_))
+ | (Self::Macro(_), Self::Macro(_))
+ | (Self::Ident(_), Self::Ident(_)) => true,
+ (Self::Decoration(c1), Self::Decoration(c2)) => c1 == c2,
+ (x, y) => x == y,
+ }
+ }
+
+ /// If `self` contains a `Span`, it'll be replaced with `DUMMY_SP` to prevent creating links
+ /// on "empty content" (because of the attributes merge).
+ fn dummy(self) -> Self {
+ match self {
+ Self::Self_(_) => Self::Self_(DUMMY_SP),
+ Self::Macro(_) => Self::Macro(DUMMY_SP),
+ Self::Ident(_) => Self::Ident(DUMMY_SP),
+ s => s,
+ }
+ }
+
/// Returns the css class expected by rustdoc for each `Class`.
fn as_html(self) -> &'static str {
match self {
@@ -157,13 +362,12 @@ impl Class {
Class::KeyWord => "kw",
Class::RefKeyWord => "kw-2",
Class::Self_(_) => "self",
- Class::Op => "op",
Class::Macro(_) => "macro",
Class::MacroNonTerminal => "macro-nonterminal",
Class::String => "string",
Class::Number => "number",
Class::Bool => "bool-val",
- Class::Ident(_) => "ident",
+ Class::Ident(_) => "",
Class::Lifetime => "lifetime",
Class::PreludeTy => "prelude-ty",
Class::PreludeVal => "prelude-val",
@@ -182,7 +386,6 @@ impl Class {
| Self::Attribute
| Self::KeyWord
| Self::RefKeyWord
- | Self::Op
| Self::MacroNonTerminal
| Self::String
| Self::Number
@@ -204,15 +407,16 @@ enum Highlight<'a> {
struct TokenIter<'a> {
src: &'a str,
+ cursor: Cursor<'a>,
}
impl<'a> Iterator for TokenIter<'a> {
type Item = (TokenKind, &'a str);
fn next(&mut self) -> Option<(TokenKind, &'a str)> {
- if self.src.is_empty() {
+ let token = self.cursor.advance_token();
+ if token.kind == TokenKind::Eof {
return None;
}
- let token = rustc_lexer::first_token(self.src);
let (text, rest) = self.src.split_at(token.len as usize);
self.src = rest;
Some((token.kind, text))
@@ -220,7 +424,7 @@ impl<'a> Iterator for TokenIter<'a> {
}
/// Classifies into identifier class; returns `None` if this is a non-keyword identifier.
-fn get_real_ident_class(text: &str, edition: Edition, allow_path_keywords: bool) -> Option<Class> {
+fn get_real_ident_class(text: &str, allow_path_keywords: bool) -> Option<Class> {
let ignore: &[&str] =
if allow_path_keywords { &["self", "Self", "super", "crate"] } else { &["self", "Self"] };
if ignore.iter().any(|k| *k == text) {
@@ -229,7 +433,7 @@ fn get_real_ident_class(text: &str, edition: Edition, allow_path_keywords: bool)
Some(match text {
"ref" | "mut" => Class::RefKeyWord,
"false" | "true" => Class::Bool,
- _ if Symbol::intern(text).is_reserved(|| edition) => Class::KeyWord,
+ _ if Symbol::intern(text).is_reserved(|| Edition::Edition2021) => Class::KeyWord,
_ => return None,
})
}
@@ -250,7 +454,7 @@ impl<'a> PeekIter<'a> {
fn new(iter: TokenIter<'a>) -> Self {
Self { stored: VecDeque::new(), peek_pos: 0, iter }
}
- /// Returns the next item after the current one. It doesn't interfer with `peek_next` output.
+ /// Returns the next item after the current one. It doesn't interfere with `peek_next` output.
fn peek(&mut self) -> Option<&(TokenKind, &'a str)> {
if self.stored.is_empty() {
if let Some(next) = self.iter.next() {
@@ -259,7 +463,7 @@ impl<'a> PeekIter<'a> {
}
self.stored.front()
}
- /// Returns the next item after the last one peeked. It doesn't interfer with `peek` output.
+ /// Returns the next item after the last one peeked. It doesn't interfere with `peek` output.
fn peek_next(&mut self) -> Option<&(TokenKind, &'a str)> {
self.peek_pos += 1;
if self.peek_pos - 1 < self.stored.len() {
@@ -311,7 +515,6 @@ struct Classifier<'a> {
in_attribute: bool,
in_macro: bool,
in_macro_nonterminal: bool,
- edition: Edition,
byte_pos: u32,
file_span: Span,
src: &'a str,
@@ -321,20 +524,14 @@ struct Classifier<'a> {
impl<'a> Classifier<'a> {
/// Takes as argument the source code to HTML-ify, the rust edition to use and the source code
/// file span which will be used later on by the `span_correspondance_map`.
- fn new(
- src: &str,
- edition: Edition,
- file_span: Span,
- decoration_info: Option<DecorationInfo>,
- ) -> Classifier<'_> {
- let tokens = PeekIter::new(TokenIter { src });
+ fn new(src: &str, file_span: Span, decoration_info: Option<DecorationInfo>) -> Classifier<'_> {
+ let tokens = PeekIter::new(TokenIter { src, cursor: Cursor::new(src) });
let decorations = decoration_info.map(Decorations::new);
Classifier {
tokens,
in_attribute: false,
in_macro: false,
in_macro_nonterminal: false,
- edition,
byte_pos: 0,
file_span,
src,
@@ -354,7 +551,6 @@ impl<'a> Classifier<'a> {
let start = self.byte_pos as usize;
let mut pos = start;
let mut has_ident = false;
- let edition = self.edition;
loop {
let mut nb = 0;
@@ -376,7 +572,7 @@ impl<'a> Classifier<'a> {
if let Some((None, text)) = self.tokens.peek().map(|(token, text)| {
if *token == TokenKind::Ident {
- let class = get_real_ident_class(text, edition, true);
+ let class = get_real_ident_class(text, true);
(class, text)
} else {
// Doesn't matter which Class we put in here...
@@ -494,7 +690,7 @@ impl<'a> Classifier<'a> {
// or a reference or pointer type. Unless, of course, it looks like
// a logical and or a multiplication operator: `&&` or `* `.
TokenKind::Star => match self.tokens.peek() {
- Some((TokenKind::Whitespace, _)) => Class::Op,
+ Some((TokenKind::Whitespace, _)) => return no_highlight(sink),
Some((TokenKind::Ident, "mut")) => {
self.next();
sink(Highlight::Token { text: "*mut", class: Some(Class::RefKeyWord) });
@@ -510,15 +706,15 @@ impl<'a> Classifier<'a> {
TokenKind::And => match self.tokens.peek() {
Some((TokenKind::And, _)) => {
self.next();
- sink(Highlight::Token { text: "&&", class: Some(Class::Op) });
+ sink(Highlight::Token { text: "&&", class: None });
return;
}
Some((TokenKind::Eq, _)) => {
self.next();
- sink(Highlight::Token { text: "&=", class: Some(Class::Op) });
+ sink(Highlight::Token { text: "&=", class: None });
return;
}
- Some((TokenKind::Whitespace, _)) => Class::Op,
+ Some((TokenKind::Whitespace, _)) => return no_highlight(sink),
Some((TokenKind::Ident, "mut")) => {
self.next();
sink(Highlight::Token { text: "&mut", class: Some(Class::RefKeyWord) });
@@ -531,7 +727,7 @@ impl<'a> Classifier<'a> {
TokenKind::Eq => match lookahead {
Some(TokenKind::Eq) => {
self.next();
- sink(Highlight::Token { text: "==", class: Some(Class::Op) });
+ sink(Highlight::Token { text: "==", class: None });
return;
}
Some(TokenKind::Gt) => {
@@ -539,7 +735,7 @@ impl<'a> Classifier<'a> {
sink(Highlight::Token { text: "=>", class: None });
return;
}
- _ => Class::Op,
+ _ => return no_highlight(sink),
},
TokenKind::Minus if lookahead == Some(TokenKind::Gt) => {
self.next();
@@ -556,7 +752,7 @@ impl<'a> Classifier<'a> {
| TokenKind::Percent
| TokenKind::Bang
| TokenKind::Lt
- | TokenKind::Gt => Class::Op,
+ | TokenKind::Gt => return no_highlight(sink),
// Miscellaneous, no highlighting.
TokenKind::Dot
@@ -634,7 +830,7 @@ impl<'a> Classifier<'a> {
sink(Highlight::Token { text, class: None });
return;
}
- TokenKind::Ident => match get_real_ident_class(text, self.edition, false) {
+ TokenKind::Ident => match get_real_ident_class(text, false) {
None => match text {
"Option" | "Result" => Class::PreludeTy,
"Some" | "None" | "Ok" | "Err" => Class::PreludeVal,
@@ -654,6 +850,7 @@ impl<'a> Classifier<'a> {
Class::Ident(self.new_span(before, text))
}
TokenKind::Lifetime { .. } => Class::Lifetime,
+ TokenKind::Eof => panic!("Eof in advance"),
};
// Anything that didn't return above is the simple case where we the
// class just spans a single token, so we can use the `string` method.
@@ -682,7 +879,7 @@ fn enter_span(
klass: Class,
href_context: &Option<HrefContext<'_, '_, '_>>,
) -> &'static str {
- string_without_closing_tag(out, "", Some(klass), href_context).expect(
+ string_without_closing_tag(out, "", Some(klass), href_context, true).expect(
"internal error: enter_span was called with Some(klass) but did not return a \
closing HTML tag",
)
@@ -714,8 +911,10 @@ fn string<T: Display>(
text: T,
klass: Option<Class>,
href_context: &Option<HrefContext<'_, '_, '_>>,
+ open_tag: bool,
) {
- if let Some(closing_tag) = string_without_closing_tag(out, text, klass, href_context) {
+ if let Some(closing_tag) = string_without_closing_tag(out, text, klass, href_context, open_tag)
+ {
out.write_str(closing_tag);
}
}
@@ -734,6 +933,7 @@ fn string_without_closing_tag<T: Display>(
text: T,
klass: Option<Class>,
href_context: &Option<HrefContext<'_, '_, '_>>,
+ open_tag: bool,
) -> Option<&'static str> {
let Some(klass) = klass
else {
@@ -742,6 +942,10 @@ fn string_without_closing_tag<T: Display>(
};
let Some(def_span) = klass.get_span()
else {
+ if !open_tag {
+ write!(out, "{}", text);
+ return None;
+ }
write!(out, "<span class=\"{}\">{}", klass.as_html(), text);
return Some("</span>");
};
@@ -765,6 +969,7 @@ fn string_without_closing_tag<T: Display>(
path
});
}
+
if let Some(href_context) = href_context {
if let Some(href) =
href_context.context.shared.span_correspondance_map.get(&def_span).and_then(|href| {
@@ -775,9 +980,9 @@ fn string_without_closing_tag<T: Display>(
// a link to their definition can be generated using this:
// https://github.com/rust-lang/rust/blob/60f1a2fc4b535ead9c85ce085fdce49b1b097531/src/librustdoc/html/render/context.rs#L315-L338
match href {
- LinkFromSrc::Local(span) => context
- .href_from_span(*span, true)
- .map(|s| format!("{}{}", href_context.root_path, s)),
+ LinkFromSrc::Local(span) => {
+ context.href_from_span_relative(*span, href_context.current_href)
+ }
LinkFromSrc::External(def_id) => {
format::href_with_root_path(*def_id, context, Some(href_context.root_path))
.ok()
@@ -793,12 +998,33 @@ fn string_without_closing_tag<T: Display>(
}
})
{
- write!(out, "<a class=\"{}\" href=\"{}\">{}", klass.as_html(), href, text_s);
+ if !open_tag {
+ // We're already inside an element which has the same klass, no need to give it
+ // again.
+ write!(out, "<a href=\"{}\">{}", href, text_s);
+ } else {
+ let klass_s = klass.as_html();
+ if klass_s.is_empty() {
+ write!(out, "<a href=\"{}\">{}", href, text_s);
+ } else {
+ write!(out, "<a class=\"{}\" href=\"{}\">{}", klass_s, href, text_s);
+ }
+ }
return Some("</a>");
}
}
- write!(out, "<span class=\"{}\">{}", klass.as_html(), text_s);
- Some("</span>")
+ if !open_tag {
+ write!(out, "{}", text_s);
+ return None;
+ }
+ let klass_s = klass.as_html();
+ if klass_s.is_empty() {
+ write!(out, "{}", text_s);
+ Some("")
+ } else {
+ write!(out, "<span class=\"{}\">{}", klass_s, text_s);
+ Some("</span>")
+ }
}
#[cfg(test)]
diff --git a/src/librustdoc/html/highlight/fixtures/decorations.html b/src/librustdoc/html/highlight/fixtures/decorations.html
index 45f567880..ebf29f9cb 100644
--- a/src/librustdoc/html/highlight/fixtures/decorations.html
+++ b/src/librustdoc/html/highlight/fixtures/decorations.html
@@ -1,2 +1,4 @@
-<span class="example"><span class="kw">let</span> <span class="ident">x</span> <span class="op">=</span> <span class="number">1</span>;</span>
-<span class="kw">let</span> <span class="ident">y</span> <span class="op">=</span> <span class="number">2</span>; \ No newline at end of file
+<span class="example"><span class="kw">let </span>x = <span class="number">1</span>;
+<span class="kw">let </span>y = <span class="number">2</span>;
+</span><span class="example2"><span class="kw">let </span>z = <span class="number">3</span>;
+</span><span class="kw">let </span>a = <span class="number">4</span>; \ No newline at end of file
diff --git a/src/librustdoc/html/highlight/fixtures/dos_line.html b/src/librustdoc/html/highlight/fixtures/dos_line.html
index 1c8dbffe7..30b50ca7c 100644
--- a/src/librustdoc/html/highlight/fixtures/dos_line.html
+++ b/src/librustdoc/html/highlight/fixtures/dos_line.html
@@ -1,3 +1,3 @@
-<span class="kw">pub</span> <span class="kw">fn</span> <span class="ident">foo</span>() {
+<span class="kw">pub fn </span>foo() {
<span class="macro">println!</span>(<span class="string">&quot;foo&quot;</span>);
}
diff --git a/src/librustdoc/html/highlight/fixtures/highlight.html b/src/librustdoc/html/highlight/fixtures/highlight.html
index abc2db179..9f73e03f9 100644
--- a/src/librustdoc/html/highlight/fixtures/highlight.html
+++ b/src/librustdoc/html/highlight/fixtures/highlight.html
@@ -1,4 +1,4 @@
-<span class="kw">use</span> <span class="ident"><span class="kw">crate</span>::a::foo</span>;
-<span class="kw">use</span> <span class="ident"><span class="self">self</span>::whatever</span>;
-<span class="kw">let</span> <span class="ident">x</span> <span class="op">=</span> <span class="ident"><span class="kw">super</span>::b::foo</span>;
-<span class="kw">let</span> <span class="ident">y</span> <span class="op">=</span> <span class="ident"><span class="self">Self</span>::whatever</span>; \ No newline at end of file
+<span class="kw">use </span><span class="kw">crate</span>::a::foo;
+<span class="kw">use </span><span class="self">self</span>::whatever;
+<span class="kw">let </span>x = <span class="kw">super</span>::b::foo;
+<span class="kw">let </span>y = <span class="self">Self</span>::whatever; \ No newline at end of file
diff --git a/src/librustdoc/html/highlight/fixtures/sample.html b/src/librustdoc/html/highlight/fixtures/sample.html
index b117a12e3..4a5a3cf60 100644
--- a/src/librustdoc/html/highlight/fixtures/sample.html
+++ b/src/librustdoc/html/highlight/fixtures/sample.html
@@ -8,30 +8,31 @@
.lifetime { color: #B76514; }
.question-mark { color: #ff9011; }
</style>
-<pre><code><span class="attribute">#![<span class="ident">crate_type</span> <span class="op">=</span> <span class="string">&quot;lib&quot;</span>]</span>
+<pre><code><span class="attribute">#![crate_type = <span class="string">&quot;lib&quot;</span>]
-<span class="kw">use</span> <span class="ident">std::path</span>::{<span class="ident">Path</span>, <span class="ident">PathBuf</span>};
+</span><span class="kw">use </span>std::path::{Path, PathBuf};
-<span class="attribute">#[<span class="ident">cfg</span>(<span class="ident">target_os</span> <span class="op">=</span> <span class="string">&quot;linux&quot;</span>)]</span>
-<span class="kw">fn</span> <span class="ident">main</span>() -&gt; () {
- <span class="kw">let</span> <span class="ident">foo</span> <span class="op">=</span> <span class="bool-val">true</span> <span class="op">&amp;&amp;</span> <span class="bool-val">false</span> <span class="op">|</span><span class="op">|</span> <span class="bool-val">true</span>;
- <span class="kw">let</span> <span class="kw">_</span>: <span class="kw-2">*const</span> () <span class="op">=</span> <span class="number">0</span>;
- <span class="kw">let</span> <span class="kw">_</span> <span class="op">=</span> <span class="kw-2">&amp;</span><span class="ident">foo</span>;
- <span class="kw">let</span> <span class="kw">_</span> <span class="op">=</span> <span class="op">&amp;&amp;</span><span class="ident">foo</span>;
- <span class="kw">let</span> <span class="kw">_</span> <span class="op">=</span> <span class="kw-2">*</span><span class="ident">foo</span>;
- <span class="macro">mac!</span>(<span class="ident">foo</span>, <span class="kw-2">&amp;mut</span> <span class="ident">bar</span>);
- <span class="macro">assert!</span>(<span class="self">self</span>.<span class="ident">length</span> <span class="op">&lt;</span> <span class="ident">N</span> <span class="op">&amp;&amp;</span> <span class="ident">index</span> <span class="op">&lt;</span><span class="op">=</span> <span class="self">self</span>.<span class="ident">length</span>);
- <span class="ident">::std::env::var</span>(<span class="string">&quot;gateau&quot;</span>).<span class="ident">is_ok</span>();
- <span class="attribute">#[<span class="ident">rustfmt::skip</span>]</span>
- <span class="kw">let</span> <span class="ident">s</span>:<span class="ident">std::path::PathBuf</span> <span class="op">=</span> <span class="ident">std::path::PathBuf::new</span>();
- <span class="kw">let</span> <span class="kw-2">mut</span> <span class="ident">s</span> <span class="op">=</span> <span class="ident">String::new</span>();
+<span class="attribute">#[cfg(target_os = <span class="string">&quot;linux&quot;</span>)]
+#[cfg(target_os = <span class="string">&quot;windows&quot;</span>)]
+</span><span class="kw">fn </span>main() -&gt; () {
+ <span class="kw">let </span>foo = <span class="bool-val">true </span>&amp;&amp; <span class="bool-val">false </span>|| <span class="bool-val">true</span>;
+ <span class="kw">let _</span>: <span class="kw-2">*const </span>() = <span class="number">0</span>;
+ <span class="kw">let _ </span>= <span class="kw-2">&amp;</span>foo;
+ <span class="kw">let _ </span>= &amp;&amp;foo;
+ <span class="kw">let _ </span>= <span class="kw-2">*</span>foo;
+ <span class="macro">mac!</span>(foo, <span class="kw-2">&amp;mut </span>bar);
+ <span class="macro">assert!</span>(<span class="self">self</span>.length &lt; N &amp;&amp; index &lt;= <span class="self">self</span>.length);
+ ::std::env::var(<span class="string">&quot;gateau&quot;</span>).is_ok();
+ <span class="attribute">#[rustfmt::skip]
+ </span><span class="kw">let </span>s:std::path::PathBuf = std::path::PathBuf::new();
+ <span class="kw">let </span><span class="kw-2">mut </span>s = String::new();
- <span class="kw">match</span> <span class="kw-2">&amp;</span><span class="ident">s</span> {
- <span class="kw-2">ref</span> <span class="kw-2">mut</span> <span class="ident">x</span> =&gt; {}
+ <span class="kw">match </span><span class="kw-2">&amp;</span>s {
+ <span class="kw-2">ref mut </span>x =&gt; {}
}
}
-<span class="macro">macro_rules!</span> <span class="ident">bar</span> {
- (<span class="macro-nonterminal">$</span><span class="macro-nonterminal">foo</span>:<span class="ident">tt</span>) =&gt; {};
+<span class="macro">macro_rules! </span>bar {
+ (<span class="macro-nonterminal">$foo</span>:tt) =&gt; {};
}
</code></pre>
diff --git a/src/librustdoc/html/highlight/fixtures/sample.rs b/src/librustdoc/html/highlight/fixtures/sample.rs
index fbfdc6767..ef85b566c 100644
--- a/src/librustdoc/html/highlight/fixtures/sample.rs
+++ b/src/librustdoc/html/highlight/fixtures/sample.rs
@@ -3,6 +3,7 @@
use std::path::{Path, PathBuf};
#[cfg(target_os = "linux")]
+#[cfg(target_os = "windows")]
fn main() -> () {
let foo = true && false || true;
let _: *const () = 0;
diff --git a/src/librustdoc/html/highlight/fixtures/union.html b/src/librustdoc/html/highlight/fixtures/union.html
index c0acf31a0..9f8915282 100644
--- a/src/librustdoc/html/highlight/fixtures/union.html
+++ b/src/librustdoc/html/highlight/fixtures/union.html
@@ -1,8 +1,8 @@
-<span class="kw">union</span> <span class="ident">Foo</span> {
- <span class="ident">i</span>: <span class="ident">i8</span>,
- <span class="ident">u</span>: <span class="ident">i8</span>,
+<span class="kw">union </span>Foo {
+ i: i8,
+ u: i8,
}
-<span class="kw">fn</span> <span class="ident">main</span>() {
- <span class="kw">let</span> <span class="ident">union</span> <span class="op">=</span> <span class="number">0</span>;
+<span class="kw">fn </span>main() {
+ <span class="kw">let </span>union = <span class="number">0</span>;
}
diff --git a/src/librustdoc/html/highlight/tests.rs b/src/librustdoc/html/highlight/tests.rs
index 1fea7e983..a5e633df4 100644
--- a/src/librustdoc/html/highlight/tests.rs
+++ b/src/librustdoc/html/highlight/tests.rs
@@ -3,7 +3,6 @@ use crate::html::format::Buffer;
use expect_test::expect_file;
use rustc_data_structures::fx::FxHashMap;
use rustc_span::create_default_session_globals_then;
-use rustc_span::edition::Edition;
const STYLE: &str = r#"
<style>
@@ -23,7 +22,7 @@ fn test_html_highlighting() {
let src = include_str!("fixtures/sample.rs");
let html = {
let mut out = Buffer::new();
- write_code(&mut out, src, Edition::Edition2018, None, None);
+ write_code(&mut out, src, None, None);
format!("{}<pre><code>{}</code></pre>\n", STYLE, out.into_inner())
};
expect_file!["fixtures/sample.html"].assert_eq(&html);
@@ -37,7 +36,7 @@ fn test_dos_backline() {
println!(\"foo\");\r\n\
}\r\n";
let mut html = Buffer::new();
- write_code(&mut html, src, Edition::Edition2018, None, None);
+ write_code(&mut html, src, None, None);
expect_file!["fixtures/dos_line.html"].assert_eq(&html.into_inner());
});
}
@@ -51,7 +50,7 @@ let x = super::b::foo;
let y = Self::whatever;";
let mut html = Buffer::new();
- write_code(&mut html, src, Edition::Edition2018, None, None);
+ write_code(&mut html, src, None, None);
expect_file!["fixtures/highlight.html"].assert_eq(&html.into_inner());
});
}
@@ -61,7 +60,7 @@ fn test_union_highlighting() {
create_default_session_globals_then(|| {
let src = include_str!("fixtures/union.rs");
let mut html = Buffer::new();
- write_code(&mut html, src, Edition::Edition2018, None, None);
+ write_code(&mut html, src, None, None);
expect_file!["fixtures/union.html"].assert_eq(&html.into_inner());
});
}
@@ -70,12 +69,15 @@ fn test_union_highlighting() {
fn test_decorations() {
create_default_session_globals_then(|| {
let src = "let x = 1;
-let y = 2;";
+let y = 2;
+let z = 3;
+let a = 4;";
let mut decorations = FxHashMap::default();
- decorations.insert("example", vec![(0, 10)]);
+ decorations.insert("example", vec![(0, 10), (11, 21)]);
+ decorations.insert("example2", vec![(22, 32)]);
let mut html = Buffer::new();
- write_code(&mut html, src, Edition::Edition2018, None, Some(DecorationInfo(decorations)));
+ write_code(&mut html, src, None, Some(DecorationInfo(decorations)));
expect_file!["fixtures/decorations.html"].assert_eq(&html.into_inner());
});
}