use crate::{
Context, Entity, IntoElement, ParentElement, Pixels, Render, Styled, StyledExt, Window, div, px,
};
use super::diagnostics::{DiagnosticEntry, DiagnosticTag};
#[derive(Default)]
pub struct DiagnosticMarkersState {
pub diagnostics: Vec<DiagnosticEntry>,
pub enabled: bool,
}
impl DiagnosticMarkersState {
pub fn update(&mut self, diagnostics: Vec<DiagnosticEntry>) {
self.diagnostics = diagnostics;
}
pub fn clear(&mut self) {
self.diagnostics.clear();
}
pub fn diagnostics_for_line(&self, line: u32) -> Vec<&DiagnosticEntry> {
self.diagnostics
.iter()
.filter(|d| d.range.start.line <= line && d.range.end.line >= line)
.collect()
}
pub fn error_count(&self) -> usize {
self.diagnostics.iter().filter(|d| d.is_error()).count()
}
pub fn warning_count(&self) -> usize {
self.diagnostics.iter().filter(|d| d.is_warning()).count()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DiagnosticMarkerStyle {
Squiggly,
Straight,
Dotted,
Strikethrough,
}
impl DiagnosticMarkerStyle {
pub fn from_severity(severity: lsp_types::DiagnosticSeverity) -> Self {
match severity {
lsp_types::DiagnosticSeverity::ERROR => Self::Squiggly,
lsp_types::DiagnosticSeverity::WARNING => Self::Straight,
lsp_types::DiagnosticSeverity::INFORMATION => Self::Dotted,
lsp_types::DiagnosticSeverity::HINT => Self::Dotted,
_ => Self::Straight,
}
}
pub fn from_tags(tags: &[DiagnosticTag]) -> Option<Self> {
if tags.contains(&DiagnosticTag::Unnecessary) {
Some(Self::Strikethrough)
} else if tags.contains(&DiagnosticTag::Deprecated) {
Some(Self::Strikethrough)
} else {
None
}
}
}
pub struct DiagnosticColors {
pub error: crate::Hsla,
pub warning: crate::Hsla,
pub info: crate::Hsla,
pub hint: crate::Hsla,
pub unnecessary: crate::Hsla,
pub deprecated: crate::Hsla,
}
impl Default for DiagnosticColors {
fn default() -> Self {
Self {
error: crate::red_500(),
warning: crate::yellow_500(),
info: crate::blue_500(),
hint: crate::gray_500(),
unnecessary: crate::gray_500(),
deprecated: crate::gray_500(),
}
}
}
impl DiagnosticColors {
pub fn color_for_diagnostic(&self, diagnostic: &DiagnosticEntry) -> crate::Hsla {
if diagnostic.tags.contains(&DiagnosticTag::Unnecessary) {
self.unnecessary
} else if diagnostic.tags.contains(&DiagnosticTag::Deprecated) {
self.deprecated
} else {
match diagnostic.severity {
lsp_types::DiagnosticSeverity::ERROR => self.error,
lsp_types::DiagnosticSeverity::WARNING => self.warning,
lsp_types::DiagnosticSeverity::INFORMATION => self.info,
lsp_types::DiagnosticSeverity::HINT => self.hint,
_ => self.info,
}
}
}
}
pub struct DiagnosticMarkerConfig {
pub style: DiagnosticMarkerStyle,
pub colors: DiagnosticColors,
pub underline_offset: Pixels,
pub underline_height: Pixels,
pub show_source: bool,
pub show_code: bool,
}
impl Default for DiagnosticMarkerConfig {
fn default() -> Self {
Self {
style: DiagnosticMarkerStyle::Squiggly,
colors: DiagnosticColors::default(),
underline_offset: crate::px(2.),
underline_height: crate::px(2.),
show_source: true,
show_code: true,
}
}
}
pub struct DiagnosticMarkers {
state: Entity<DiagnosticMarkersState>,
config: DiagnosticMarkerConfig,
}
impl DiagnosticMarkers {
pub fn new(state: Entity<DiagnosticMarkersState>) -> Self {
Self {
state,
config: DiagnosticMarkerConfig::default(),
}
}
pub fn with_config(mut self, config: DiagnosticMarkerConfig) -> Self {
self.config = config;
self
}
}
impl Render for DiagnosticMarkers {
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let state = self.state.read(cx);
if !state.enabled || state.diagnostics.is_empty() {
return div().into_element();
}
div()
}
}
pub struct DiagnosticTooltip {
pub diagnostic: DiagnosticEntry,
pub config: DiagnosticMarkerConfig,
}
impl DiagnosticTooltip {
pub fn new(diagnostic: DiagnosticEntry) -> Self {
Self {
diagnostic,
config: DiagnosticMarkerConfig::default(),
}
}
pub fn severity_label(&self) -> &'static str {
match self.diagnostic.severity {
lsp_types::DiagnosticSeverity::ERROR => "Error",
lsp_types::DiagnosticSeverity::WARNING => "Warning",
lsp_types::DiagnosticSeverity::INFORMATION => "Info",
lsp_types::DiagnosticSeverity::HINT => "Hint",
_ => "Diagnostic",
}
}
pub fn severity_color(&self) -> crate::Hsla {
self.config.colors.color_for_diagnostic(&self.diagnostic)
}
}
impl Render for DiagnosticTooltip {
fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
let source_info = if self.config.show_source {
self.diagnostic.source.as_ref().map(|s| {
div()
.text_xs()
.text_color(crate::gray_400())
.child(format!("[{}]", s))
})
} else {
None
};
let code_info = if self.config.show_code {
self.diagnostic.code.as_ref().map(|c| {
div()
.text_xs()
.text_color(crate::gray_400())
.child(format!("({})", c))
})
} else {
None
};
div()
.max_w(px(400.))
.p_2()
.bg(crate::gray_900())
.border_1()
.border_color(crate::gray_700())
.rounded_md()
.shadow_lg()
.child(
div()
.flex()
.items_center()
.gap_2()
.mb_1()
.child(
div()
.text_xs()
.font_bold()
.text_color(self.severity_color())
.child(self.severity_label()),
)
.children(source_info)
.children(code_info),
)
.child(div().text_sm().child(self.diagnostic.message.clone()))
}
}