Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 65 additions & 10 deletions crates/ruff_annotate_snippets/src/renderer/display_list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
//! styling.
//!
//! The above snippet has been built out of the following structure:
use crate::snippet;
use crate::{Id, snippet};
use std::cmp::{Reverse, max, min};
use std::collections::HashMap;
use std::fmt::Display;
Expand Down Expand Up @@ -189,6 +189,7 @@ impl DisplaySet<'_> {
}
Ok(())
}

fn format_annotation(
&self,
line_offset: usize,
Expand All @@ -199,11 +200,13 @@ impl DisplaySet<'_> {
) -> fmt::Result {
let hide_severity = annotation.annotation_type.is_none();
let color = get_annotation_style(&annotation.annotation_type, stylesheet);

let formatted_len = if let Some(id) = &annotation.id {
let id_len = id.id.len();
if hide_severity {
id.len()
id_len
} else {
2 + id.len() + annotation_type_len(&annotation.annotation_type)
2 + id_len + annotation_type_len(&annotation.annotation_type)
}
} else {
annotation_type_len(&annotation.annotation_type)
Expand Down Expand Up @@ -256,9 +259,20 @@ impl DisplaySet<'_> {
let annotation_type = annotation_type_str(&annotation.annotation_type);
if let Some(id) = annotation.id {
if hide_severity {
buffer.append(line_offset, &format!("{id} "), *stylesheet.error());
buffer.append(
line_offset,
&format!("{id} ", id = fmt_with_hyperlink(id.id, id.url, stylesheet)),
*stylesheet.error(),
);
} else {
buffer.append(line_offset, &format!("{annotation_type}[{id}]"), *color);
buffer.append(
line_offset,
&format!(
"{annotation_type}[{id}]",
id = fmt_with_hyperlink(id.id, id.url, stylesheet)
),
*color,
);
}
} else {
buffer.append(line_offset, annotation_type, *color);
Expand Down Expand Up @@ -707,7 +721,7 @@ impl DisplaySet<'_> {
let style =
get_annotation_style(&annotation.annotation_type, stylesheet);
let mut formatted_len = if let Some(id) = &annotation.annotation.id {
2 + id.len()
2 + id.id.len()
+ annotation_type_len(&annotation.annotation.annotation_type)
} else {
annotation_type_len(&annotation.annotation.annotation_type)
Expand All @@ -724,7 +738,10 @@ impl DisplaySet<'_> {
} else if formatted_len != 0 {
formatted_len += 2;
let id = match &annotation.annotation.id {
Some(id) => format!("[{id}]"),
Some(id) => format!(
"[{id}]",
id = fmt_with_hyperlink(&id.id, id.url, stylesheet)
),
None => String::new(),
};
buffer.puts(
Expand Down Expand Up @@ -827,7 +844,7 @@ impl DisplaySet<'_> {
#[derive(Clone, Debug, PartialEq)]
pub(crate) struct Annotation<'a> {
pub(crate) annotation_type: DisplayAnnotationType,
pub(crate) id: Option<&'a str>,
pub(crate) id: Option<Id<'a>>,
pub(crate) label: Vec<DisplayTextFragment<'a>>,
pub(crate) is_fixable: bool,
}
Expand Down Expand Up @@ -1140,7 +1157,7 @@ fn format_message<'m>(

fn format_title<'a>(
level: crate::Level,
id: Option<&'a str>,
id: Option<Id<'a>>,
label: &'a str,
is_fixable: bool,
) -> DisplayLine<'a> {
Expand All @@ -1158,7 +1175,7 @@ fn format_title<'a>(

fn format_footer<'a>(
level: crate::Level,
id: Option<&'a str>,
id: Option<Id<'a>>,
label: &'a str,
) -> Vec<DisplayLine<'a>> {
let mut result = vec![];
Expand Down Expand Up @@ -1706,6 +1723,7 @@ fn format_body<'m>(
annotation: Annotation {
annotation_type,
id: None,

label: format_label(annotation.label, None),
is_fixable: false,
},
Expand Down Expand Up @@ -1887,3 +1905,40 @@ fn char_width(c: char) -> Option<usize> {
unicode_width::UnicodeWidthChar::width(c)
}
}

pub(super) fn fmt_with_hyperlink<'a, T>(
content: T,
url: Option<&'a str>,
stylesheet: &Stylesheet,
) -> impl std::fmt::Display + 'a
where
T: std::fmt::Display + 'a,
{
struct FmtHyperlink<'a, T> {
content: T,
url: Option<&'a str>,
}

impl<T> std::fmt::Display for FmtHyperlink<'_, T>
where
T: std::fmt::Display,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if let Some(url) = self.url {
write!(f, "\x1B]8;;{url}\x1B\\")?;
}

self.content.fmt(f)?;

if self.url.is_some() {
f.write_str("\x1B]8;;\x1B\\")?;
}

Ok(())
}
}

let url = if stylesheet.hyperlink { url } else { None };

FmtHyperlink { content, url }
}
1 change: 1 addition & 0 deletions crates/ruff_annotate_snippets/src/renderer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ impl Renderer {
}
.effects(Effects::BOLD),
none: Style::new(),
hyperlink: true,
},
..Self::plain()
}
Expand Down
2 changes: 2 additions & 0 deletions crates/ruff_annotate_snippets/src/renderer/stylesheet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ pub(crate) struct Stylesheet {
pub(crate) line_no: Style,
pub(crate) emphasis: Style,
pub(crate) none: Style,
pub(crate) hyperlink: bool,
}

impl Default for Stylesheet {
Expand All @@ -29,6 +30,7 @@ impl Stylesheet {
line_no: Style::new(),
emphasis: Style::new(),
none: Style::new(),
hyperlink: false,
}
}
}
Expand Down
15 changes: 13 additions & 2 deletions crates/ruff_annotate_snippets/src/snippet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,19 @@

use std::ops::Range;

#[derive(Copy, Clone, Debug, Default, PartialEq)]
pub(crate) struct Id<'a> {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This type is inspired by the new annotate snippet that also has an Id type

pub(crate) id: &'a str,
pub(crate) url: Option<&'a str>,
}

/// Primary structure provided for formatting
///
/// See [`Level::title`] to create a [`Message`]
#[derive(Debug)]
pub struct Message<'a> {
pub(crate) level: Level,
pub(crate) id: Option<&'a str>,
pub(crate) id: Option<Id<'a>>,
pub(crate) title: &'a str,
pub(crate) snippets: Vec<Snippet<'a>>,
pub(crate) footer: Vec<Message<'a>>,
Expand All @@ -28,7 +34,12 @@ pub struct Message<'a> {

impl<'a> Message<'a> {
pub fn id(mut self, id: &'a str) -> Self {
self.id = Some(id);
self.id = Some(Id { id, url: None });
self
}

pub fn id_with_url(mut self, id: &'a str, url: Option<&'a str>) -> Self {
self.id = Some(Id { id, url });
self
}

Expand Down
10 changes: 10 additions & 0 deletions crates/ruff_db/src/diagnostic/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ impl Diagnostic {
severity,
message: message.into_diagnostic_message(),
custom_concise_message: None,
documentation_url: None,
annotations: vec![],
subs: vec![],
fix: None,
Expand Down Expand Up @@ -370,6 +371,14 @@ impl Diagnostic {
.is_some_and(|fix| fix.applies(config.fix_applicability))
}

pub fn documentation_url(&self) -> Option<&str> {
self.inner.documentation_url.as_deref()
}

pub fn set_documentation_url(&mut self, url: Option<String>) {
Arc::make_mut(&mut self.inner).documentation_url = url;
}

/// Returns the offset of the parent statement for this diagnostic if it exists.
///
/// This is primarily used for checking noqa/secondary code suppressions.
Expand Down Expand Up @@ -544,6 +553,7 @@ impl Diagnostic {
#[derive(Debug, Clone, Eq, PartialEq, Hash, get_size2::GetSize)]
struct DiagnosticInner {
id: DiagnosticId,
documentation_url: Option<String>,
severity: Severity,
message: DiagnosticMessage,
custom_concise_message: Option<DiagnosticMessage>,
Expand Down
15 changes: 10 additions & 5 deletions crates/ruff_db/src/diagnostic/render.rs
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,7 @@ impl<'a> Resolved<'a> {
struct ResolvedDiagnostic<'a> {
level: AnnotateLevel,
id: Option<String>,
documentation_url: Option<String>,
message: String,
annotations: Vec<ResolvedAnnotation<'a>>,
is_fixable: bool,
Expand Down Expand Up @@ -240,12 +241,12 @@ impl<'a> ResolvedDiagnostic<'a> {
// `DisplaySet::format_annotation` for both cases, but this is a small hack to improve
// the formatting of syntax errors for now. This should also be kept consistent with the
// concise formatting.
Some(diag.secondary_code().map_or_else(
diag.secondary_code().map_or_else(
|| format!("{id}:", id = diag.inner.id),
|code| code.to_string(),
))
)
} else {
Some(diag.inner.id.to_string())
diag.inner.id.to_string()
};

let level = if config.hide_severity {
Expand All @@ -256,7 +257,8 @@ impl<'a> ResolvedDiagnostic<'a> {

ResolvedDiagnostic {
level,
id,
id: Some(id),
documentation_url: diag.documentation_url().map(ToString::to_string),
message: diag.inner.message.as_str().to_string(),
annotations,
is_fixable: config.show_fix_status && diag.has_applicable_fix(config),
Expand Down Expand Up @@ -287,6 +289,7 @@ impl<'a> ResolvedDiagnostic<'a> {
ResolvedDiagnostic {
level: diag.inner.severity.to_annotate(),
id: None,
documentation_url: None,
message: diag.inner.message.as_str().to_string(),
annotations,
is_fixable: false,
Expand Down Expand Up @@ -385,6 +388,7 @@ impl<'a> ResolvedDiagnostic<'a> {
RenderableDiagnostic {
level: self.level,
id: self.id.as_deref(),
documentation_url: self.documentation_url.as_deref(),
message: &self.message,
snippets_by_input,
is_fixable: self.is_fixable,
Expand Down Expand Up @@ -485,6 +489,7 @@ struct RenderableDiagnostic<'r> {
/// An ID is always present for top-level diagnostics and always absent for
/// sub-diagnostics.
id: Option<&'r str>,
documentation_url: Option<&'r str>,
/// The message emitted with the diagnostic, before any snippets are
/// rendered.
message: &'r str,
Expand Down Expand Up @@ -519,7 +524,7 @@ impl RenderableDiagnostic<'_> {
.is_fixable(self.is_fixable)
.lineno_offset(self.header_offset);
if let Some(id) = self.id {
message = message.id(id);
message = message.id_with_url(id, self.documentation_url);
}
message.snippets(snippets)
}
Expand Down
22 changes: 18 additions & 4 deletions crates/ruff_db/src/diagnostic/render/concise.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::diagnostic::{
Diagnostic, DisplayDiagnosticConfig, Severity,
stylesheet::{DiagnosticStylesheet, fmt_styled},
stylesheet::{DiagnosticStylesheet, fmt_styled, fmt_with_hyperlink},
};

use super::FileResolver;
Expand Down Expand Up @@ -62,18 +62,29 @@ impl<'a> ConciseRenderer<'a> {
}
write!(f, "{sep} ")?;
}

if self.config.hide_severity {
if let Some(code) = diag.secondary_code() {
write!(
f,
"{code} ",
code = fmt_styled(code, stylesheet.secondary_code)
code = fmt_styled(
fmt_with_hyperlink(&code, diag.documentation_url(), &stylesheet),
stylesheet.secondary_code
)
)?;
} else {
write!(
f,
"{id}: ",
id = fmt_styled(diag.inner.id.as_str(), stylesheet.secondary_code)
id = fmt_styled(
fmt_with_hyperlink(
&diag.inner.id,
diag.documentation_url(),
&stylesheet
),
stylesheet.secondary_code
)
)?;
}
if self.config.show_fix_status {
Expand All @@ -93,7 +104,10 @@ impl<'a> ConciseRenderer<'a> {
f,
"{severity}[{id}] ",
severity = fmt_styled(severity, severity_style),
id = fmt_styled(diag.id(), stylesheet.emphasis)
id = fmt_styled(
fmt_with_hyperlink(&diag.id(), diag.documentation_url(), &stylesheet),
stylesheet.emphasis
)
)?;
}

Expand Down
Loading
Loading