project_panel: Add diagnostic count badges (#49802)
Discussed in #6668 specifically this comment from @zackangelo: > The biggest thing keeping me from using Zed as a daily driver is error indication in the project panel. When I'm making big project-wide changes I can't clearly see which files have errors (in editors like VSCode the filenames turn red). > VSCode seems to use a letter on the right gutter to indicate git status and a number next to it to indicate diagnostic status. The color indicates either. This PR implements that, I added an opt-in `diagnostic_badges` setting (default is false) that shows error and warning counts as colored labels on the right side of each project panel entry. Counts bubble up to parent directories. When `diagnostic_badges` is enabled, diagnostic severity takes priority over git status for entry text color. Since warnings and git-modified share the same yellow, git status with this option on is readable through the file icon decoration and the absence of a number badge on the right. Example: <img width="522" height="785" alt="image" src="https://github.com/user-attachments/assets/2da62580-86fe-480b-9b57-ff137ea42285" /> <img width="884" height="580" alt="image" src="https://github.com/user-attachments/assets/198e9a45-dacd-4f1e-a66c-f2b84fd4db63" /> Release Notes: - Added diagnostic count badges to the project panel, displaying error and warning counts next to file names. You can modify this setting using the `diagnostic_badges` option, which is enabled by default. --------- Co-authored-by: Smit Barmase <heysmitbarmase@gmail.com>
This commit is contained in:
1 parent
f8ef6c8c20
commit
f765a0efcb
6 files changed
+136
-20
No files matched your search
@@ -799,6 +799,8 @@
|
||||
// 3. Show files first, then directories:
|
||||
// "files_first"
|
||||
"sort_mode": "directories_first",
|
||||
// Whether to show error and warning count badges next to file names in the project panel.
|
||||
"diagnostic_badges": true,
|
||||
// Whether to enable drag-and-drop operations in the project panel.
|
||||
"drag_and_drop": true,
|
||||
// Whether to hide the root entry when only one folder is open in the window;
|
||||
|
||||
@@ -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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -801,6 +801,7 @@ impl VsCodeSettings {
|
||||
starts_open: None,
|
||||
sticky_scroll: None,
|
||||
auto_open: None,
|
||||
diagnostic_badges: None,
|
||||
};
|
||||
|
||||
if let (Some(false), Some(false)) = (
|
||||
|
||||
@@ -739,6 +739,10 @@ pub struct ProjectPanelSettingsContent {
|
||||
///
|
||||
/// Default: directories_first
|
||||
pub sort_mode: Option<ProjectPanelSortMode>,
|
||||
/// Whether to show error and warning count badges next to file names in the project panel.
|
||||
///
|
||||
/// Default: true
|
||||
pub diagnostic_badges: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(
|
||||
|
||||
@@ -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.",
|
||||
|
||||
Reference in new issue
Block a user