@@ -146,6 +146,7 @@ pub struct ProjectPanel {
width: Option<Pixels>,
pending_serialization: Task<Option<()>>,
diagnostics: HashMap<(WorktreeId, Arc<RelPath>), DiagnosticSeverity>,
+ diagnostic_counts: HashMap<(WorktreeId, Arc<RelPath>), DiagnosticCount>,
diagnostic_summary_update: Task<()>,
// We keep track of the mouse down state on entries so we don't flash the UI
// in case a user clicks to open a file.
@@ -232,6 +233,30 @@ enum ClipboardEntry {
Cut(BTreeSet<SelectedEntry>),
}
+#[derive(Debug, Default, PartialEq, Eq, Clone, Copy)]
+struct DiagnosticCount {
+ error_count: usize,
+ warning_count: usize,
+}
+
+impl DiagnosticCount {
+ fn capped_error_count(&self) -> String {
+ Self::capped_count(self.error_count)
+ }
+
+ fn capped_warning_count(&self) -> String {
+ Self::capped_count(self.warning_count)
+ }
+
+ fn capped_count(count: usize) -> String {
+ if count > 99 {
+ "99+".to_string()
+ } else {
+ count.to_string()
+ }
+ }
+}
+
#[derive(Debug, PartialEq, Eq, Clone)]
struct EntryDetails {
filename: String,
@@ -249,6 +274,7 @@ struct EntryDetails {
sticky: Option<StickyDetails>,
filename_text_color: Color,
diagnostic_severity: Option<DiagnosticSeverity>,
+ diagnostic_count: Option<DiagnosticCount>,
git_status: GitSummary,
is_private: bool,
worktree_id: WorktreeId,
@@ -847,6 +873,7 @@ impl ProjectPanel {
width: None,
pending_serialization: Task::ready(None),
diagnostics: Default::default(),
+ diagnostic_counts: Default::default(),
diagnostic_summary_update: Task::ready(()),
scroll_handle,
mouse_down: false,
@@ -1029,6 +1056,26 @@ impl ProjectPanel {
});
}
self.diagnostics = diagnostics;
+
+ let diagnostic_badges = ProjectPanelSettings::get_global(cx).diagnostic_badges;
+ self.diagnostic_counts =
+ if diagnostic_badges && show_diagnostics_setting != ShowDiagnostics::Off {
+ self.project.read(cx).diagnostic_summaries(false, cx).fold(
+ HashMap::default(),
+ |mut counts, (project_path, _, summary)| {
+ let entry = counts
+ .entry((project_path.worktree_id, project_path.path))
+ .or_default();
+ entry.error_count += summary.error_count;
+ if show_diagnostics_setting == ShowDiagnostics::All {
+ entry.warning_count += summary.warning_count;
+ }
+ counts
+ },
+ )
+ } else {
+ Default::default()
+ };
}
fn update_strongest_diagnostic_severity(
@@ -5044,6 +5091,7 @@ impl ProjectPanel {
let filename_text_color = details.filename_text_color;
let diagnostic_severity = details.diagnostic_severity;
+ let diagnostic_count = details.diagnostic_count;
let item_colors = get_item_color(is_sticky, cx);
let canonical_path = details
@@ -5482,22 +5530,55 @@ impl ProjectPanel {
ProjectPanelEntrySpacing::Standard => ListItemSpacing::ExtraDense,
})
.selectable(false)
- .when_some(canonical_path, |this, path| {
- this.end_slot::<AnyElement>(
- div()
- .id("symlink_icon")
- .pr_3()
- .tooltip(move |_window, cx| {
- Tooltip::with_meta(path.to_string(), None, "Symbolic Link", cx)
- })
- .child(
- Icon::new(IconName::ArrowUpRight)
- .size(IconSize::Indicator)
- .color(filename_text_color),
- )
- .into_any_element(),
- )
- })
+ .when(
+ canonical_path.is_some() || diagnostic_count.is_some(),
+ |this| {
+ let symlink_element = canonical_path.map(|path| {
+ div()
+ .id("symlink_icon")
+ .tooltip(move |_window, cx| {
+ Tooltip::with_meta(
+ path.to_string(),
+ None,
+ "Symbolic Link",
+ cx,
+ )
+ })
+ .child(
+ Icon::new(IconName::ArrowUpRight)
+ .size(IconSize::Indicator)
+ .color(filename_text_color),
+ )
+ });
+ this.end_slot::<AnyElement>(
+ h_flex()
+ .gap_1()
+ .flex_none()
+ .pr_3()
+ .when_some(diagnostic_count, |this, count| {
+ this.when(count.error_count > 0, |this| {
+ this.child(
+ Label::new(count.capped_error_count())
+ .size(LabelSize::Small)
+ .color(Color::Error),
+ )
+ })
+ .when(
+ count.warning_count > 0,
+ |this| {
+ this.child(
+ Label::new(count.capped_warning_count())
+ .size(LabelSize::Small)
+ .color(Color::Warning),
+ )
+ },
+ )
+ })
+ .when_some(symlink_element, |this, el| this.child(el))
+ .into_any_element(),
+ )
+ },
+ )
.child(if let Some(icon) = &icon {
if let Some((_, decoration_color)) =
entry_diagnostic_aware_icon_decoration_and_color(diagnostic_severity)
@@ -5907,6 +5988,11 @@ impl ProjectPanel {
.get(&(worktree_id, entry.path.clone()))
.cloned();
+ let diagnostic_count = self
+ .diagnostic_counts
+ .get(&(worktree_id, entry.path.clone()))
+ .copied();
+
let filename_text_color =
entry_git_aware_label_color(git_status, entry.is_ignored, is_marked);
@@ -5931,6 +6017,7 @@ impl ProjectPanel {
sticky,
filename_text_color,
diagnostic_severity,
+ diagnostic_count,
git_status,
is_private: entry.is_private,
worktree_id,
@@ -35,6 +35,7 @@ pub struct ProjectPanelSettings {
pub drag_and_drop: bool,
pub auto_open: AutoOpenSettings,
pub sort_mode: ProjectPanelSortMode,
+ pub diagnostic_badges: bool,
}
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
@@ -125,9 +126,8 @@ impl Settings for ProjectPanelSettings {
on_drop: auto_open.on_drop.unwrap(),
}
},
- sort_mode: project_panel
- .sort_mode
- .unwrap_or(ProjectPanelSortMode::DirectoriesFirst),
+ sort_mode: project_panel.sort_mode.unwrap(),
+ diagnostic_badges: project_panel.diagnostic_badges.unwrap(),
}
}
}
@@ -4256,7 +4256,7 @@ fn window_and_layout_page() -> SettingsPage {
}
fn panels_page() -> SettingsPage {
- fn project_panel_section() -> [SettingsPageItem; 21] {
+ fn project_panel_section() -> [SettingsPageItem; 22] {
[
SettingsPageItem::SectionHeader("Project Panel"),
SettingsPageItem::SettingItem(SettingItem {
@@ -4556,6 +4556,28 @@ fn panels_page() -> SettingsPage {
metadata: None,
files: USER,
}),
+ SettingsPageItem::SettingItem(SettingItem {
+ title: "Diagnostic Badges",
+ description: "Show error and warning count badges next to file names in the project panel.",
+ field: Box::new(SettingField {
+ json_path: Some("project_panel.diagnostic_badges"),
+ pick: |settings_content| {
+ settings_content
+ .project_panel
+ .as_ref()?
+ .diagnostic_badges
+ .as_ref()
+ },
+ write: |settings_content, value| {
+ settings_content
+ .project_panel
+ .get_or_insert_default()
+ .diagnostic_badges = value;
+ },
+ }),
+ metadata: None,
+ files: USER,
+ }),
SettingsPageItem::SettingItem(SettingItem {
title: "Sticky Scroll",
description: "Whether to stick parent directories at top of the project panel.",