diff --git a/assets/settings/default.json b/assets/settings/default.json index 37c06960555..adf2664339f 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -1508,7 +1508,10 @@ "diagnostics": { // Whether to show the project diagnostics button in the status bar. "button": true, - // Whether to show warnings or not by default. + // Whether to show warnings and info-level diagnostics. + // + // When true, errors, warnings, and info-level diagnostics are all shown. + // When false, only errors are shown. // // Default: true "include_warnings": true, diff --git a/crates/collab/migrations.sqlite/20221109000000_test_schema.sql b/crates/collab/migrations.sqlite/20221109000000_test_schema.sql index 9c39dd4c260..fe461e4d63a 100644 --- a/crates/collab/migrations.sqlite/20221109000000_test_schema.sql +++ b/crates/collab/migrations.sqlite/20221109000000_test_schema.sql @@ -160,6 +160,7 @@ CREATE TABLE "worktree_diagnostic_summaries" ( "language_server_id" INTEGER NOT NULL, "error_count" INTEGER NOT NULL, "warning_count" INTEGER NOT NULL, + "info_count" INTEGER NOT NULL DEFAULT 0, PRIMARY KEY (project_id, worktree_id, path), FOREIGN KEY (project_id, worktree_id) REFERENCES worktrees (project_id, id) ON DELETE CASCADE ); diff --git a/crates/collab/migrations/20251208000000_test_schema.sql b/crates/collab/migrations/20251208000000_test_schema.sql index 3a3329af776..41eda5ea646 100644 --- a/crates/collab/migrations/20251208000000_test_schema.sql +++ b/crates/collab/migrations/20251208000000_test_schema.sql @@ -446,7 +446,8 @@ CREATE TABLE public.worktree_diagnostic_summaries ( path character varying NOT NULL, language_server_id bigint NOT NULL, error_count integer NOT NULL, - warning_count integer NOT NULL + warning_count integer NOT NULL, + info_count integer NOT NULL DEFAULT 0 ); CREATE TABLE public.worktree_entries ( diff --git a/crates/collab/src/db/queries/projects.rs b/crates/collab/src/db/queries/projects.rs index cae1f238b1d..20d2c1af1b8 100644 --- a/crates/collab/src/db/queries/projects.rs +++ b/crates/collab/src/db/queries/projects.rs @@ -539,6 +539,7 @@ impl Database { language_server_id: ActiveValue::set(summary.language_server_id as i64), error_count: ActiveValue::set(summary.error_count as i32), warning_count: ActiveValue::set(summary.warning_count as i32), + info_count: ActiveValue::set(summary.info_count as i32), }) .on_conflict( OnConflict::columns([ @@ -550,6 +551,7 @@ impl Database { worktree_diagnostic_summary::Column::LanguageServerId, worktree_diagnostic_summary::Column::ErrorCount, worktree_diagnostic_summary::Column::WarningCount, + worktree_diagnostic_summary::Column::InfoCount, ]) .to_owned(), ) @@ -926,6 +928,7 @@ impl Database { language_server_id: db_summary.language_server_id as u64, error_count: db_summary.error_count as u32, warning_count: db_summary.warning_count as u32, + info_count: db_summary.info_count as u32, }); } } diff --git a/crates/collab/src/db/tables/worktree_diagnostic_summary.rs b/crates/collab/src/db/tables/worktree_diagnostic_summary.rs index 5620ed255f9..5bb21f81a6f 100644 --- a/crates/collab/src/db/tables/worktree_diagnostic_summary.rs +++ b/crates/collab/src/db/tables/worktree_diagnostic_summary.rs @@ -13,6 +13,7 @@ pub struct Model { pub language_server_id: i64, pub error_count: i32, pub warning_count: i32, + pub info_count: i32, } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] diff --git a/crates/collab/tests/integration/integration_tests.rs b/crates/collab/tests/integration/integration_tests.rs index 7fc56a3c86c..f7603bc468f 100644 --- a/crates/collab/tests/integration/integration_tests.rs +++ b/crates/collab/tests/integration/integration_tests.rs @@ -4125,6 +4125,7 @@ async fn test_collaborating_with_diagnostics( DiagnosticSummary { error_count: 1, warning_count: 0, + info_count: 0, }, )] ) @@ -4161,6 +4162,7 @@ async fn test_collaborating_with_diagnostics( DiagnosticSummary { error_count: 1, warning_count: 0, + info_count: 0, }, )] ); @@ -4202,6 +4204,7 @@ async fn test_collaborating_with_diagnostics( DiagnosticSummary { error_count: 1, warning_count: 1, + info_count: 0, }, )] ); @@ -4219,6 +4222,7 @@ async fn test_collaborating_with_diagnostics( DiagnosticSummary { error_count: 1, warning_count: 1, + info_count: 0, }, )] ); diff --git a/crates/diagnostics/src/buffer_diagnostics.rs b/crates/diagnostics/src/buffer_diagnostics.rs index 4558ce07076..478f6fb3dc1 100644 --- a/crates/diagnostics/src/buffer_diagnostics.rs +++ b/crates/diagnostics/src/buffer_diagnostics.rs @@ -318,7 +318,7 @@ impl BufferDiagnosticsEditor { let buffer_snapshot_max = buffer_snapshot.max_point(); let max_severity = Self::max_diagnostics_severity(self.include_warnings) .into_lsp() - .unwrap_or(lsp::DiagnosticSeverity::WARNING); + .unwrap_or(lsp::DiagnosticSeverity::INFORMATION); cx.spawn_in(window, async move |buffer_diagnostics_editor, mut cx| { // Fetch the diagnostics for the whole of the buffer @@ -658,7 +658,7 @@ impl BufferDiagnosticsEditor { fn max_diagnostics_severity(include_warnings: bool) -> DiagnosticSeverity { match include_warnings { - true => DiagnosticSeverity::Warning, + true => DiagnosticSeverity::Info, false => DiagnosticSeverity::Error, } } diff --git a/crates/diagnostics/src/diagnostics.rs b/crates/diagnostics/src/diagnostics.rs index 6b1e91b680e..06bd348912b 100644 --- a/crates/diagnostics/src/diagnostics.rs +++ b/crates/diagnostics/src/diagnostics.rs @@ -226,7 +226,7 @@ impl ProjectDiagnosticsEditor { editor.disable_inline_diagnostics(); editor.set_max_diagnostics_severity( if include_warnings { - DiagnosticSeverity::Warning + DiagnosticSeverity::Info } else { DiagnosticSeverity::Error }, @@ -262,7 +262,7 @@ impl ProjectDiagnosticsEditor { this.editor.update(cx, |editor, cx| { editor.set_max_diagnostics_severity( if include_warnings { - DiagnosticSeverity::Warning + DiagnosticSeverity::Info } else { DiagnosticSeverity::Error }, @@ -497,7 +497,7 @@ impl ProjectDiagnosticsEditor { let buffer_id = buffer_snapshot.remote_id(); let max_severity = if self.include_warnings { - lsp::DiagnosticSeverity::WARNING + lsp::DiagnosticSeverity::INFORMATION } else { lsp::DiagnosticSeverity::ERROR }; diff --git a/crates/diagnostics/src/diagnostics_tests.rs b/crates/diagnostics/src/diagnostics_tests.rs index 613b485c946..587ad5a9484 100644 --- a/crates/diagnostics/src/diagnostics_tests.rs +++ b/crates/diagnostics/src/diagnostics_tests.rs @@ -2023,12 +2023,221 @@ async fn test_buffer_diagnostics_multiple_servers(cx: &mut TestAppContext) { *buffer_diagnostics.summary(), DiagnosticSummary { warning_count: 2, - error_count: 0 + error_count: 0, + info_count: 0, } ); }) } +#[gpui::test] +async fn test_buffer_diagnostics_includes_info_with_warnings(cx: &mut TestAppContext) { + init_test(cx); + + let fs = FakeFs::new(cx.executor()); + fs.insert_tree( + path!("/test"), + json!({ + "main.rs": " + fn main() { + let x = 1; + let y = 2; + } + " + .unindent(), + }), + ) + .await; + + let project = Project::test(fs.clone(), [path!("/test").as_ref()], cx).await; + let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx)); + let cx = &mut VisualTestContext::from_window(window.into(), cx); + let project_path = project::ProjectPath { + worktree_id: project.read_with(cx, |project, cx| { + project.worktrees(cx).next().unwrap().read(cx).id() + }), + path: rel_path("main.rs").into(), + }; + let buffer = project + .update(cx, |project, cx| { + project.open_buffer(project_path.clone(), cx) + }) + .await + .ok(); + + let language_server_id = LanguageServerId(0); + let uri = lsp::Uri::from_file_path(path!("/test/main.rs")).unwrap(); + let lsp_store = project.read_with(cx, |project, _| project.lsp_store()); + + lsp_store.update(cx, |lsp_store, cx| { + lsp_store + .update_diagnostics( + language_server_id, + lsp::PublishDiagnosticsParams { + uri: uri.clone(), + diagnostics: vec![ + lsp::Diagnostic { + range: lsp::Range::new( + lsp::Position::new(1, 8), + lsp::Position::new(1, 9), + ), + severity: Some(lsp::DiagnosticSeverity::INFORMATION), + message: "unused variable: `x`".to_string(), + ..Default::default() + }, + lsp::Diagnostic { + range: lsp::Range::new( + lsp::Position::new(2, 8), + lsp::Position::new(2, 9), + ), + severity: Some(lsp::DiagnosticSeverity::ERROR), + message: "undefined function".to_string(), + ..Default::default() + }, + ], + version: None, + }, + None, + DiagnosticSourceKind::Pushed, + &[], + cx, + ) + .unwrap(); + }); + + let buffer_diagnostics = window.build_entity(cx, |window, cx| { + BufferDiagnosticsEditor::new( + project_path.clone(), + project.clone(), + buffer, + true, + window, + cx, + ) + }); + + let editor = buffer_diagnostics.update(cx, |buffer_diagnostics, _cx| { + buffer_diagnostics.editor().clone() + }); + + cx.executor() + .advance_clock(DIAGNOSTICS_UPDATE_DEBOUNCE + Duration::from_millis(10)); + + let content = editor_content_with_blocks(&editor, cx); + assert!( + content.contains("unused variable: `x`"), + "info diagnostic should be shown when include_warnings is true, got:\n{content}" + ); + assert!( + content.contains("undefined function"), + "error diagnostic should always be shown, got:\n{content}" + ); +} + +#[gpui::test] +async fn test_buffer_diagnostics_excludes_info_without_warnings(cx: &mut TestAppContext) { + init_test(cx); + + let fs = FakeFs::new(cx.executor()); + fs.insert_tree( + path!("/test"), + json!({ + "main.rs": " + fn main() { + let x = 1; + let y = 2; + } + " + .unindent(), + }), + ) + .await; + + let project = Project::test(fs.clone(), [path!("/test").as_ref()], cx).await; + let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx)); + let cx = &mut VisualTestContext::from_window(window.into(), cx); + let project_path = project::ProjectPath { + worktree_id: project.read_with(cx, |project, cx| { + project.worktrees(cx).next().unwrap().read(cx).id() + }), + path: rel_path("main.rs").into(), + }; + let buffer = project + .update(cx, |project, cx| { + project.open_buffer(project_path.clone(), cx) + }) + .await + .ok(); + + let language_server_id = LanguageServerId(0); + let uri = lsp::Uri::from_file_path(path!("/test/main.rs")).unwrap(); + let lsp_store = project.read_with(cx, |project, _| project.lsp_store()); + + lsp_store.update(cx, |lsp_store, cx| { + lsp_store + .update_diagnostics( + language_server_id, + lsp::PublishDiagnosticsParams { + uri: uri.clone(), + diagnostics: vec![ + lsp::Diagnostic { + range: lsp::Range::new( + lsp::Position::new(1, 8), + lsp::Position::new(1, 9), + ), + severity: Some(lsp::DiagnosticSeverity::INFORMATION), + message: "unused variable: `x`".to_string(), + ..Default::default() + }, + lsp::Diagnostic { + range: lsp::Range::new( + lsp::Position::new(2, 8), + lsp::Position::new(2, 9), + ), + severity: Some(lsp::DiagnosticSeverity::ERROR), + message: "undefined function".to_string(), + ..Default::default() + }, + ], + version: None, + }, + None, + DiagnosticSourceKind::Pushed, + &[], + cx, + ) + .unwrap(); + }); + + let buffer_diagnostics = window.build_entity(cx, |window, cx| { + BufferDiagnosticsEditor::new( + project_path.clone(), + project.clone(), + buffer, + false, + window, + cx, + ) + }); + + let editor = buffer_diagnostics.update(cx, |buffer_diagnostics, _cx| { + buffer_diagnostics.editor().clone() + }); + + cx.executor() + .advance_clock(DIAGNOSTICS_UPDATE_DEBOUNCE + Duration::from_millis(10)); + + let content = editor_content_with_blocks(&editor, cx); + assert!( + !content.contains("unused variable: `x`"), + "info diagnostic should be hidden when include_warnings is false, got:\n{content}" + ); + assert!( + content.contains("undefined function"), + "error diagnostic should always be shown, got:\n{content}" + ); +} + fn init_test(cx: &mut TestAppContext) { cx.update(|cx| { zlog::init_test(); diff --git a/crates/diagnostics/src/items.rs b/crates/diagnostics/src/items.rs index 9f243c78102..86583e4ca18 100644 --- a/crates/diagnostics/src/items.rs +++ b/crates/diagnostics/src/items.rs @@ -33,8 +33,9 @@ impl Render for DiagnosticIndicator { return indicator.hidden(); } + let include_warnings = ProjectSettings::get_global(cx).diagnostics.include_warnings; let diagnostic_indicator = match (self.summary.error_count, self.summary.warning_count) { - (0, 0) => h_flex().child( + (0, 0) if !include_warnings || self.summary.info_count == 0 => h_flex().child( Icon::new(IconName::Check) .size(IconSize::Small) .color(Color::Default), @@ -56,6 +57,14 @@ impl Render for DiagnosticIndicator { .color(Color::Warning), ) .child(Label::new(warning_count.to_string()).size(LabelSize::Small)) + }) + .when(self.summary.info_count > 0 && include_warnings, |this| { + this.child( + Icon::new(IconName::Info) + .size(IconSize::Small) + .color(Color::Info), + ) + .child(Label::new(self.summary.info_count.to_string()).size(LabelSize::Small)) }), }; diff --git a/crates/project/src/lsp_store.rs b/crates/project/src/lsp_store.rs index 811b9aebac6..896353d8561 100644 --- a/crates/project/src/lsp_store.rs +++ b/crates/project/src/lsp_store.rs @@ -8135,6 +8135,7 @@ impl LspStore { for (_, _, path_summary) in self.diagnostic_summaries(include_ignored, cx) { summary.error_count += path_summary.error_count; summary.warning_count += path_summary.warning_count; + summary.info_count += path_summary.info_count; } summary } @@ -8150,12 +8151,13 @@ impl LspStore { .get(&project_path.worktree_id) .and_then(|map| map.get(&project_path.path)) { - let (error_count, warning_count) = summaries.iter().fold( - (0, 0), - |(error_count, warning_count), (_language_server_id, summary)| { + let (error_count, warning_count, info_count) = summaries.iter().fold( + (0, 0, 0), + |(error_count, warning_count, info_count), (_language_server_id, summary)| { ( error_count + summary.error_count, warning_count + summary.warning_count, + info_count + summary.info_count, ) }, ); @@ -8163,6 +8165,7 @@ impl LspStore { DiagnosticSummary { error_count, warning_count, + info_count, } } else { DiagnosticSummary::default() @@ -8549,6 +8552,7 @@ impl LspStore { language_server_id: server_id.0 as u64, error_count: 0, warning_count: 0, + info_count: 0, }), more_summaries: Vec::new(), }) @@ -8798,6 +8802,7 @@ impl LspStore { language_server_id: server_id.0 as u64, error_count: new_summary.error_count, warning_count: new_summary.warning_count, + info_count: new_summary.info_count, }) } None => { @@ -8809,6 +8814,7 @@ impl LspStore { language_server_id: server_id.0 as u64, error_count: new_summary.error_count, warning_count: new_summary.warning_count, + info_count: new_summary.info_count, }), more_summaries: Vec::new(), }) @@ -8893,6 +8899,7 @@ impl LspStore { language_server_id: server_id.0 as u64, error_count: new_summary.error_count as u32, warning_count: new_summary.warning_count as u32, + info_count: new_summary.info_count as u32, }, )))) } else { @@ -9738,6 +9745,7 @@ impl LspStore { let summary = DiagnosticSummary { error_count: message_summary.error_count as usize, warning_count: message_summary.warning_count as usize, + info_count: message_summary.info_count as usize, }; if summary.is_empty() { @@ -9770,6 +9778,7 @@ impl LspStore { language_server_id: server_id.0 as u64, error_count: summary.error_count as u32, warning_count: summary.warning_count as u32, + info_count: summary.info_count as u32, }) } None => { @@ -9781,6 +9790,7 @@ impl LspStore { language_server_id: server_id.0 as u64, error_count: summary.error_count as u32, warning_count: summary.warning_count as u32, + info_count: summary.info_count as u32, }), more_summaries: Vec::new(), }) @@ -11241,6 +11251,7 @@ impl LspStore { language_server_id: server_id.0 as u64, error_count: 0, warning_count: 0, + info_count: 0, }), more_summaries: Vec::new(), }) @@ -14413,6 +14424,7 @@ pub struct LanguageServerProgress { pub struct DiagnosticSummary { pub error_count: usize, pub warning_count: usize, + pub info_count: usize, } impl DiagnosticSummary { @@ -14420,6 +14432,7 @@ impl DiagnosticSummary { let mut this = Self { error_count: 0, warning_count: 0, + info_count: 0, }; for entry in diagnostics { @@ -14427,6 +14440,7 @@ impl DiagnosticSummary { match entry.diagnostic.severity { DiagnosticSeverity::ERROR => this.error_count += 1, DiagnosticSeverity::WARNING => this.warning_count += 1, + DiagnosticSeverity::INFORMATION => this.info_count += 1, _ => {} } } @@ -14449,6 +14463,7 @@ impl DiagnosticSummary { language_server_id: language_server_id.0 as u64, error_count: self.error_count as u32, warning_count: self.warning_count as u32, + info_count: self.info_count as u32, } } } diff --git a/crates/project/tests/integration/project_tests.rs b/crates/project/tests/integration/project_tests.rs index daaaa0bd2c6..a67f22135c6 100644 --- a/crates/project/tests/integration/project_tests.rs +++ b/crates/project/tests/integration/project_tests.rs @@ -2798,6 +2798,7 @@ async fn test_omitted_diagnostics(cx: &mut gpui::TestAppContext) { DiagnosticSummary { error_count: 1, warning_count: 0, + info_count: 0, } )] ); @@ -3094,6 +3095,7 @@ async fn test_restarting_server_with_diagnostics_published(cx: &mut gpui::TestAp DiagnosticSummary { error_count: 1, warning_count: 0, + info_count: 0, } ); }); @@ -3120,6 +3122,7 @@ async fn test_restarting_server_with_diagnostics_published(cx: &mut gpui::TestAp DiagnosticSummary { error_count: 0, warning_count: 0, + info_count: 0, } ); }); @@ -3843,6 +3846,7 @@ async fn test_diagnostics_from_multiple_language_servers(cx: &mut gpui::TestAppC DiagnosticSummary { error_count: 2, warning_count: 0, + info_count: 0, } ); }); @@ -3906,6 +3910,7 @@ async fn test_diagnostic_summaries_cleared_on_worktree_entry_removal( DiagnosticSummary { error_count: 1, warning_count: 1, + info_count: 0, } ); }); @@ -3921,6 +3926,7 @@ async fn test_diagnostic_summaries_cleared_on_worktree_entry_removal( DiagnosticSummary { error_count: 0, warning_count: 1, + info_count: 0, }, ); }); @@ -3965,6 +3971,7 @@ async fn test_diagnostic_summaries_cleared_on_server_restart(cx: &mut gpui::Test DiagnosticSummary { error_count: 1, warning_count: 0, + info_count: 0, } ); }); @@ -3995,6 +4002,7 @@ async fn test_diagnostic_summaries_cleared_on_server_restart(cx: &mut gpui::Test DiagnosticSummary { error_count: 0, warning_count: 0, + info_count: 0, } ); }); @@ -4084,6 +4092,7 @@ async fn test_diagnostic_summaries_cleared_on_buffer_reload(cx: &mut gpui::TestA DiagnosticSummary { error_count: 1, warning_count: 0, + info_count: 0, } ); }); diff --git a/crates/proto/proto/lsp.proto b/crates/proto/proto/lsp.proto index ff9ec4d4e64..d1b8d455294 100644 --- a/crates/proto/proto/lsp.proto +++ b/crates/proto/proto/lsp.proto @@ -593,6 +593,7 @@ message DiagnosticSummary { uint64 language_server_id = 2; uint32 error_count = 3; uint32 warning_count = 4; + uint32 info_count = 5; } message UpdateLanguageServer { diff --git a/info_count_status_bar_plan.md b/info_count_status_bar_plan.md new file mode 100644 index 00000000000..9b83012a3a1 --- /dev/null +++ b/info_count_status_bar_plan.md @@ -0,0 +1,114 @@ +# Plan: Show `info_count` in the Status Bar + +The goal is consistency: the status bar should reflect what the diagnostics panel +shows. When `include_warnings` is true the panel now shows errors, warnings, and +info-level diagnostics, so the status bar counter should too. + +No new icons, toolbar buttons, or actions. The `include_warnings` setting name +stays the same. Changes touch six areas. + +--- + +## 1. Extend `DiagnosticSummary` — `crates/project/src/lsp_store.rs` + +**Struct** — add `pub info_count: usize`. + +**`DiagnosticSummary::new()`** — add an arm to the severity match: +```rust +DiagnosticSeverity::INFORMATION => this.info_count += 1, +``` + +**`is_empty()`** — no change. It gates the checkmark icon ("no errors or +warnings"). An info-only workspace still showing the checkmark is acceptable. + +**`diagnostic_summary()`** — add `summary.info_count += path_summary.info_count` +alongside the existing error/warning accumulation. + +**`diagnostic_summary_for_path()`** — expand the fold from a 2-tuple to a 3-tuple +`(error_count, warning_count, info_count)` and include it in the returned struct. + +**`to_proto()`** — add `info_count: self.info_count as u32`. + +--- + +## 2. Extend the proto message — `crates/proto/proto/lsp.proto` + +Add a new field to `message DiagnosticSummary`: +```proto +uint32 info_count = 5; +``` + +Protobuf field ordering means old clients silently ignore the new field, so this +is backward-compatible. No DB schema or migration change is needed — diagnostics +are not persisted in the collab database. + +After updating the schema, run the repo's normal proto regeneration/build path so +the Rust bindings for `proto::DiagnosticSummary` pick up the new field. Also keep +the proto change Buf-clean (`buf lint` / `buf format`) so CI passes. + +--- + +## 3. Propagate `info_count` through proto serialization — `crates/project/src/lsp_store.rs` + +Every site in `lsp_store.rs` that constructs `proto::DiagnosticSummary { ..., +error_count, warning_count }` needs `info_count` added as well. + +Every site in `lsp_store.rs` that deserializes a proto message back into +`DiagnosticSummary { error_count, warning_count }` needs +`info_count: message_summary.info_count as usize`. + +--- + +## 4. Show `info_count` in the status bar — `crates/diagnostics/src/items.rs` + +In `render()`, keep the outer checkmark-vs-counts decision based on +`error_count` and `warning_count` only, so an info-only workspace still renders +the checkmark. Within the existing non-checkmark branch, after the existing +`.when(warning_count > 0, ...)` child, add: + +```rust +let include_warnings = ProjectSettings::get_global(cx).diagnostics.include_warnings; +// ... +.when(self.summary.info_count > 0 && include_warnings, |this| { + this.child( + Icon::new(IconName::Info) + .size(IconSize::Small) + .color(Color::Info), + ) + .child(Label::new(self.summary.info_count.to_string()).size(LabelSize::Small)) +}) +``` + +`IconName::Info` and `Color::Info` already exist and are used elsewhere; no new +icons are introduced. + +The auto-enable logic in the `on_click` handler (which sets `include_warnings = +true` when the user clicks the status bar with errors = 0 and warnings > 0) does +not need to change — info-only scenarios are rare and can be handled in a follow-up. + +--- + +## 5. Fix the collab integration test — `crates/collab/tests/integration/integration_tests.rs` + +`test_collaborating_with_diagnostics` constructs `DiagnosticSummary` literals. +Add `info_count: 0` to each, or derive `Default` on the struct and use +`..Default::default()` struct-update syntax. + +--- + +## 6. Fix the diagnostics unit test — `crates/diagnostics/src/diagnostics_tests.rs` + +`test_buffer_diagnostics_multiple_servers` asserts on `*buffer_diagnostics.summary()` +using a `DiagnosticSummary` literal. Add `info_count: 0` there too. + +--- + +## Summary of files touched + +| File | Change | +|------|--------| +| `crates/project/src/lsp_store.rs` | Add `info_count` to struct, `new()`, `is_empty()` (no-op), `diagnostic_summary()`, `diagnostic_summary_for_path()`, `to_proto()`, and all proto construction/deserialization sites | +| `crates/proto/proto/lsp.proto` | Add `uint32 info_count = 5` to `DiagnosticSummary` message and regenerate/build the Rust bindings as required by the repo's proto pipeline | +| `crates/diagnostics/src/items.rs` | Render `info_count` in status bar when `include_warnings` is true | +| `crates/collab/tests/integration/integration_tests.rs` | Add `info_count: 0` to `DiagnosticSummary` literals | +| `crates/diagnostics/src/diagnostics_tests.rs` | Add `info_count: 0` to `DiagnosticSummary` literal | \ No newline at end of file