use super::*;use collections::HashMap;use editor::{ display_map::{Block, BlockContext, DisplayRow}, DisplayPoint, GutterDimensions,};use gpui::{px, AvailableSpace, Stateful, TestAppContext, VisualTestContext};use language::{ Diagnostic, DiagnosticEntry, DiagnosticSeverity, OffsetRangeExt, PointUtf16, Rope, Unclipped,};use pretty_assertions::assert_eq;use project::FakeFs;use rand::{rngs::StdRng, seq::IteratorRandom as _, Rng};use serde_json::json;use settings::SettingsStore;use std::{ env, path::{Path, PathBuf},};use unindent::Unindent as _;use util::{post_inc, RandomCharIter}; #[ctor::ctor]fn init_logger() { if env::var("RUST_LOG").is_ok() { env_logger::init(); }} #[gpui::test]async fn test_diagnostics(cx: &mut TestAppContext) { init_test(cx); let fs = FakeFs::new(cx.executor()); fs.insert_tree( "/test", json!({ "consts.rs": " const a: i32 = 'a'; const b: i32 = c; " .unindent(), "main.rs": " fn main() { let x = vec![]; let y = vec![]; a(x); b(y); // comment 1 // comment 2 c(y); d(x); } " .unindent(), }), ) .await; let language_server_id = LanguageServerId(0); let project = Project::test(fs.clone(), ["/test".as_ref()], cx).await; let lsp_store = project.read_with(cx, |project, _| project.lsp_store()); let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); let cx = &mut VisualTestContext::from_window(*window, cx); let workspace = window.root(cx).unwrap(); // Create some diagnostics lsp_store.update(cx, |lsp_store, cx| { lsp_store .update_diagnostic_entries( language_server_id, PathBuf::from("/test/main.rs"), None, vec![ DiagnosticEntry { range: Unclipped(PointUtf16::new(1, 8))..Unclipped(PointUtf16::new(1, 9)), diagnostic: Diagnostic { message: "move occurs because `x` has type `Vec<char>`, which does not implement the `Copy` trait" .to_string(), severity: DiagnosticSeverity::INFORMATION, is_primary: false, is_disk_based: true, group_id: 1, ..Default::default() }, }, DiagnosticEntry { range: Unclipped(PointUtf16::new(2, 8))..Unclipped(PointUtf16::new(2, 9)), diagnostic: Diagnostic { message: "move occurs because `y` has type `Vec<char>`, which does not implement the `Copy` trait" .to_string(), severity: DiagnosticSeverity::INFORMATION, is_primary: false, is_disk_based: true, group_id: 0, ..Default::default() }, }, DiagnosticEntry { range: Unclipped(PointUtf16::new(3, 6))..Unclipped(PointUtf16::new(3, 7)), diagnostic: Diagnostic { message: "value moved here".to_string(), severity: DiagnosticSeverity::INFORMATION, is_primary: false, is_disk_based: true, group_id: 1, ..Default::default() }, }, DiagnosticEntry { range: Unclipped(PointUtf16::new(4, 6))..Unclipped(PointUtf16::new(4, 7)), diagnostic: Diagnostic { message: "value moved here".to_string(), severity: DiagnosticSeverity::INFORMATION, is_primary: false, is_disk_based: true, group_id: 0, ..Default::default() }, }, DiagnosticEntry { range: Unclipped(PointUtf16::new(7, 6))..Unclipped(PointUtf16::new(7, 7)), diagnostic: Diagnostic { message: "use of moved value\nvalue used here after move".to_string(), severity: DiagnosticSeverity::ERROR, is_primary: true, is_disk_based: true, group_id: 0, ..Default::default() }, }, DiagnosticEntry { range: Unclipped(PointUtf16::new(8, 6))..Unclipped(PointUtf16::new(8, 7)), diagnostic: Diagnostic { message: "use of moved value\nvalue used here after move".to_string(), severity: DiagnosticSeverity::ERROR, is_primary: true, is_disk_based: true, group_id: 1, ..Default::default() }, }, ], cx, ) .unwrap(); }); // Open the project diagnostics view while there are already diagnostics. let view = window.build_view(cx, |cx| { ProjectDiagnosticsEditor::new_with_context( 1, true, project.clone(), workspace.downgrade(), cx, ) }); let editor = view.update(cx, |view, _| view.editor.clone()); view.next_notification(DIAGNOSTICS_UPDATE_DEBOUNCE + Duration::from_millis(10), cx) .await; assert_eq!( editor_blocks(&editor, cx), [ (DisplayRow(0), FILE_HEADER.into()), (DisplayRow(2), DIAGNOSTIC_HEADER.into()), (DisplayRow(15), EXCERPT_HEADER.into()), (DisplayRow(16), DIAGNOSTIC_HEADER.into()), (DisplayRow(25), EXCERPT_HEADER.into()), ] ); assert_eq!( editor.update(cx, |editor, cx| editor.display_text(cx)), concat!( // // main.rs // "\n", // filename "\n", // padding // diagnostic group 1 "\n", // primary message "\n", // padding " let x = vec![];\n", " let y = vec![];\n", "\n", // supporting diagnostic " a(x);\n", " b(y);\n", "\n", // supporting diagnostic " // comment 1\n", " // comment 2\n", " c(y);\n", "\n", // supporting diagnostic " d(x);\n", "\n", // context ellipsis // diagnostic group 2 "\n", // primary message "\n", // padding "fn main() {\n", " let x = vec![];\n", "\n", // supporting diagnostic " let y = vec![];\n", " a(x);\n", "\n", // supporting diagnostic " b(y);\n", "\n", // context ellipsis " c(y);\n", " d(x);\n", "\n", // supporting diagnostic "}" ) ); // Cursor is at the first diagnostic editor.update(cx, |editor, cx| { assert_eq!( editor.selections.display_ranges(cx), [DisplayPoint::new(DisplayRow(12), 6)..DisplayPoint::new(DisplayRow(12), 6)] ); }); // Diagnostics are added for another earlier path. lsp_store.update(cx, |lsp_store, cx| { lsp_store.disk_based_diagnostics_started(language_server_id, cx); lsp_store .update_diagnostic_entries( language_server_id, PathBuf::from("/test/consts.rs"), None, vec![DiagnosticEntry { range: Unclipped(PointUtf16::new(0, 15))..Unclipped(PointUtf16::new(0, 15)), diagnostic: Diagnostic { message: "mismatched types\nexpected `usize`, found `char`".to_string(), severity: DiagnosticSeverity::ERROR, is_primary: true, is_disk_based: true, group_id: 0, ..Default::default() }, }], cx, ) .unwrap(); lsp_store.disk_based_diagnostics_finished(language_server_id, cx); }); view.next_notification(DIAGNOSTICS_UPDATE_DEBOUNCE + Duration::from_millis(10), cx) .await; assert_eq!( editor_blocks(&editor, cx), [ (DisplayRow(0), FILE_HEADER.into()), (DisplayRow(2), DIAGNOSTIC_HEADER.into()), (DisplayRow(7), FILE_HEADER.into()), (DisplayRow(9), DIAGNOSTIC_HEADER.into()), (DisplayRow(22), EXCERPT_HEADER.into()), (DisplayRow(23), DIAGNOSTIC_HEADER.into()), (DisplayRow(32), EXCERPT_HEADER.into()), ] ); assert_eq!( editor.update(cx, |editor, cx| editor.display_text(cx)), concat!( // // consts.rs // "\n", // filename "\n", // padding // diagnostic group 1 "\n", // primary message "\n", // padding "const a: i32 = 'a';\n", "\n", // supporting diagnostic "const b: i32 = c;\n", // // main.rs // "\n", // filename "\n", // padding // diagnostic group 1 "\n", // primary message "\n", // padding " let x = vec![];\n", " let y = vec![];\n", "\n", // supporting diagnostic " a(x);\n", " b(y);\n", "\n", // supporting diagnostic " // comment 1\n", " // comment 2\n", " c(y);\n", "\n", // supporting diagnostic " d(x);\n", "\n", // collapsed context // diagnostic group 2 "\n", // primary message "\n", // filename "fn main() {\n", " let x = vec![];\n", "\n", // supporting diagnostic " let y = vec![];\n", " a(x);\n", "\n", // supporting diagnostic " b(y);\n", "\n", // context ellipsis " c(y);\n", " d(x);\n", "\n", // supporting diagnostic "}" ) ); // Cursor keeps its position. editor.update(cx, |editor, cx| { assert_eq!( editor.selections.display_ranges(cx), [DisplayPoint::new(DisplayRow(19), 6)..DisplayPoint::new(DisplayRow(19), 6)] ); }); // Diagnostics are added to the first path lsp_store.update(cx, |lsp_store, cx| { lsp_store.disk_based_diagnostics_started(language_server_id, cx); lsp_store .update_diagnostic_entries( language_server_id, PathBuf::from("/test/consts.rs"), None, vec![ DiagnosticEntry { range: Unclipped(PointUtf16::new(0, 15))..Unclipped(PointUtf16::new(0, 15)), diagnostic: Diagnostic { message: "mismatched types\nexpected `usize`, found `char`".to_string(), severity: DiagnosticSeverity::ERROR, is_primary: true, is_disk_based: true, group_id: 0, ..Default::default() }, }, DiagnosticEntry { range: Unclipped(PointUtf16::new(1, 15))..Unclipped(PointUtf16::new(1, 15)), diagnostic: Diagnostic { message: "unresolved name `c`".to_string(), severity: DiagnosticSeverity::ERROR, is_primary: true, is_disk_based: true, group_id: 1, ..Default::default() }, }, ], cx, ) .unwrap(); lsp_store.disk_based_diagnostics_finished(language_server_id, cx); }); view.next_notification(DIAGNOSTICS_UPDATE_DEBOUNCE + Duration::from_millis(10), cx) .await; assert_eq!( editor_blocks(&editor, cx), [ (DisplayRow(0), FILE_HEADER.into()), (DisplayRow(2), DIAGNOSTIC_HEADER.into()), (DisplayRow(7), EXCERPT_HEADER.into()), (DisplayRow(8), DIAGNOSTIC_HEADER.into()), (DisplayRow(13), FILE_HEADER.into()), (DisplayRow(15), DIAGNOSTIC_HEADER.into()), (DisplayRow(28), EXCERPT_HEADER.into()), (DisplayRow(29), DIAGNOSTIC_HEADER.into()), (DisplayRow(38), EXCERPT_HEADER.into()), ] ); assert_eq!( editor.update(cx, |editor, cx| editor.display_text(cx)), concat!( // // consts.rs // "\n", // filename "\n", // padding // diagnostic group 1 "\n", // primary message "\n", // padding "const a: i32 = 'a';\n", "\n", // supporting diagnostic "const b: i32 = c;\n", "\n", // context ellipsis // diagnostic group 2 "\n", // primary message "\n", // padding "const a: i32 = 'a';\n", "const b: i32 = c;\n", "\n", // supporting diagnostic // // main.rs // "\n", // filename "\n", // padding // diagnostic group 1 "\n", // primary message "\n", // padding " let x = vec![];\n", " let y = vec![];\n", "\n", // supporting diagnostic " a(x);\n", " b(y);\n", "\n", // supporting diagnostic " // comment 1\n", " // comment 2\n", " c(y);\n", "\n", // supporting diagnostic " d(x);\n", "\n", // context ellipsis // diagnostic group 2 "\n", // primary message "\n", // filename "fn main() {\n", " let x = vec![];\n", "\n", // supporting diagnostic " let y = vec![];\n", " a(x);\n", "\n", // supporting diagnostic " b(y);\n", "\n", // context ellipsis " c(y);\n", " d(x);\n", "\n", // supporting diagnostic "}" ) );} #[gpui::test]async fn test_diagnostics_multiple_servers(cx: &mut TestAppContext) { init_test(cx); let fs = FakeFs::new(cx.executor()); fs.insert_tree( "/test", json!({ "main.js": " a(); b(); c(); d(); e(); ".unindent() }), ) .await; let server_id_1 = LanguageServerId(100); let server_id_2 = LanguageServerId(101); let project = Project::test(fs.clone(), ["/test".as_ref()], cx).await; let lsp_store = project.read_with(cx, |project, _| project.lsp_store()); let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); let cx = &mut VisualTestContext::from_window(*window, cx); let workspace = window.root(cx).unwrap(); let view = window.build_view(cx, |cx| { ProjectDiagnosticsEditor::new_with_context( 1, true, project.clone(), workspace.downgrade(), cx, ) }); let editor = view.update(cx, |view, _| view.editor.clone()); // Two language servers start updating diagnostics lsp_store.update(cx, |lsp_store, cx| { lsp_store.disk_based_diagnostics_started(server_id_1, cx); lsp_store.disk_based_diagnostics_started(server_id_2, cx); lsp_store .update_diagnostic_entries( server_id_1, PathBuf::from("/test/main.js"), None, vec![DiagnosticEntry { range: Unclipped(PointUtf16::new(0, 0))..Unclipped(PointUtf16::new(0, 1)), diagnostic: Diagnostic { message: "error 1".to_string(), severity: DiagnosticSeverity::WARNING, is_primary: true, is_disk_based: true, group_id: 1, ..Default::default() }, }], cx, ) .unwrap();Lines 1–500 of 1072. Load more
use super::*; use collections::HashMap; use editor::{ display_map::{Block, BlockContext, DisplayRow}, DisplayPoint, GutterDimensions, }; use gpui::{px, AvailableSpace, Stateful, TestAppContext, VisualTestContext}; use language::{ Diagnostic, DiagnosticEntry, DiagnosticSeverity, OffsetRangeExt, PointUtf16, Rope, Unclipped, }; use pretty_assertions::assert_eq; use project::FakeFs; use rand::{rngs::StdRng, seq::IteratorRandom as _, Rng}; use serde_json::json; use settings::SettingsStore; use std::{ env, path::{Path, PathBuf}, }; use unindent::Unindent as _; use util::{post_inc, RandomCharIter}; #[ctor::ctor] fn init_logger() { if env::var("RUST_LOG").is_ok() { env_logger::init(); } } #[gpui::test] async fn test_diagnostics(cx: &mut TestAppContext) { init_test(cx); let fs = FakeFs::new(cx.executor()); fs.insert_tree( "/test", json!({ "consts.rs": " const a: i32 = 'a'; const b: i32 = c; " .unindent(), "main.rs": " fn main() { let x = vec![]; let y = vec![]; a(x); b(y); // comment 1 // comment 2 c(y); d(x); } " .unindent(), }), ) .await; let language_server_id = LanguageServerId(0); let project = Project::test(fs.clone(), ["/test".as_ref()], cx).await; let lsp_store = project.read_with(cx, |project, _| project.lsp_store()); let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); let cx = &mut VisualTestContext::from_window(*window, cx); let workspace = window.root(cx).unwrap(); // Create some diagnostics lsp_store.update(cx, |lsp_store, cx| { lsp_store .update_diagnostic_entries( language_server_id, PathBuf::from("/test/main.rs"), None, vec![ DiagnosticEntry { range: Unclipped(PointUtf16::new(1, 8))..Unclipped(PointUtf16::new(1, 9)), diagnostic: Diagnostic { message: "move occurs because `x` has type `Vec<char>`, which does not implement the `Copy` trait" .to_string(), severity: DiagnosticSeverity::INFORMATION, is_primary: false, is_disk_based: true, group_id: 1, ..Default::default() }, }, DiagnosticEntry { range: Unclipped(PointUtf16::new(2, 8))..Unclipped(PointUtf16::new(2, 9)), diagnostic: Diagnostic { message: "move occurs because `y` has type `Vec<char>`, which does not implement the `Copy` trait" .to_string(), severity: DiagnosticSeverity::INFORMATION, is_primary: false, is_disk_based: true, group_id: 0, ..Default::default() }, }, DiagnosticEntry { range: Unclipped(PointUtf16::new(3, 6))..Unclipped(PointUtf16::new(3, 7)), diagnostic: Diagnostic { message: "value moved here".to_string(), severity: DiagnosticSeverity::INFORMATION, is_primary: false, is_disk_based: true, group_id: 1, ..Default::default() }, }, DiagnosticEntry { range: Unclipped(PointUtf16::new(4, 6))..Unclipped(PointUtf16::new(4, 7)), diagnostic: Diagnostic { message: "value moved here".to_string(), severity: DiagnosticSeverity::INFORMATION, is_primary: false, is_disk_based: true, group_id: 0, ..Default::default() }, }, DiagnosticEntry { range: Unclipped(PointUtf16::new(7, 6))..Unclipped(PointUtf16::new(7, 7)), diagnostic: Diagnostic { message: "use of moved value\nvalue used here after move".to_string(), severity: DiagnosticSeverity::ERROR, is_primary: true, is_disk_based: true, group_id: 0, ..Default::default() }, }, DiagnosticEntry { range: Unclipped(PointUtf16::new(8, 6))..Unclipped(PointUtf16::new(8, 7)), diagnostic: Diagnostic { message: "use of moved value\nvalue used here after move".to_string(), severity: DiagnosticSeverity::ERROR, is_primary: true, is_disk_based: true, group_id: 1, ..Default::default() }, }, ], cx, ) .unwrap(); }); // Open the project diagnostics view while there are already diagnostics. let view = window.build_view(cx, |cx| { ProjectDiagnosticsEditor::new_with_context( 1, true, project.clone(), workspace.downgrade(), cx, ) }); let editor = view.update(cx, |view, _| view.editor.clone()); view.next_notification(DIAGNOSTICS_UPDATE_DEBOUNCE + Duration::from_millis(10), cx) .await; assert_eq!( editor_blocks(&editor, cx), [ (DisplayRow(0), FILE_HEADER.into()), (DisplayRow(2), DIAGNOSTIC_HEADER.into()), (DisplayRow(15), EXCERPT_HEADER.into()), (DisplayRow(16), DIAGNOSTIC_HEADER.into()), (DisplayRow(25), EXCERPT_HEADER.into()), ] ); assert_eq!( editor.update(cx, |editor, cx| editor.display_text(cx)), concat!( // // main.rs // "\n", // filename "\n", // padding // diagnostic group 1 "\n", // primary message "\n", // padding " let x = vec![];\n", " let y = vec![];\n", "\n", // supporting diagnostic " a(x);\n", " b(y);\n", "\n", // supporting diagnostic " // comment 1\n", " // comment 2\n", " c(y);\n", "\n", // supporting diagnostic " d(x);\n", "\n", // context ellipsis // diagnostic group 2 "\n", // primary message "\n", // padding "fn main() {\n", " let x = vec![];\n", "\n", // supporting diagnostic " let y = vec![];\n", " a(x);\n", "\n", // supporting diagnostic " b(y);\n", "\n", // context ellipsis " c(y);\n", " d(x);\n", "\n", // supporting diagnostic "}" ) ); // Cursor is at the first diagnostic editor.update(cx, |editor, cx| { assert_eq!( editor.selections.display_ranges(cx), [DisplayPoint::new(DisplayRow(12), 6)..DisplayPoint::new(DisplayRow(12), 6)] ); }); // Diagnostics are added for another earlier path. lsp_store.update(cx, |lsp_store, cx| { lsp_store.disk_based_diagnostics_started(language_server_id, cx); lsp_store .update_diagnostic_entries( language_server_id, PathBuf::from("/test/consts.rs"), None, vec![DiagnosticEntry { range: Unclipped(PointUtf16::new(0, 15))..Unclipped(PointUtf16::new(0, 15)), diagnostic: Diagnostic { message: "mismatched types\nexpected `usize`, found `char`".to_string(), severity: DiagnosticSeverity::ERROR, is_primary: true, is_disk_based: true, group_id: 0, ..Default::default() }, }], cx, ) .unwrap(); lsp_store.disk_based_diagnostics_finished(language_server_id, cx); }); view.next_notification(DIAGNOSTICS_UPDATE_DEBOUNCE + Duration::from_millis(10), cx) .await; assert_eq!( editor_blocks(&editor, cx), [ (DisplayRow(0), FILE_HEADER.into()), (DisplayRow(2), DIAGNOSTIC_HEADER.into()), (DisplayRow(7), FILE_HEADER.into()), (DisplayRow(9), DIAGNOSTIC_HEADER.into()), (DisplayRow(22), EXCERPT_HEADER.into()), (DisplayRow(23), DIAGNOSTIC_HEADER.into()), (DisplayRow(32), EXCERPT_HEADER.into()), ] ); assert_eq!( editor.update(cx, |editor, cx| editor.display_text(cx)), concat!( // // consts.rs // "\n", // filename "\n", // padding // diagnostic group 1 "\n", // primary message "\n", // padding "const a: i32 = 'a';\n", "\n", // supporting diagnostic "const b: i32 = c;\n", // // main.rs // "\n", // filename "\n", // padding // diagnostic group 1 "\n", // primary message "\n", // padding " let x = vec![];\n", " let y = vec![];\n", "\n", // supporting diagnostic " a(x);\n", " b(y);\n", "\n", // supporting diagnostic " // comment 1\n", " // comment 2\n", " c(y);\n", "\n", // supporting diagnostic " d(x);\n", "\n", // collapsed context // diagnostic group 2 "\n", // primary message "\n", // filename "fn main() {\n", " let x = vec![];\n", "\n", // supporting diagnostic " let y = vec![];\n", " a(x);\n", "\n", // supporting diagnostic " b(y);\n", "\n", // context ellipsis " c(y);\n", " d(x);\n", "\n", // supporting diagnostic "}" ) ); // Cursor keeps its position. editor.update(cx, |editor, cx| { assert_eq!( editor.selections.display_ranges(cx), [DisplayPoint::new(DisplayRow(19), 6)..DisplayPoint::new(DisplayRow(19), 6)] ); }); // Diagnostics are added to the first path lsp_store.update(cx, |lsp_store, cx| { lsp_store.disk_based_diagnostics_started(language_server_id, cx); lsp_store .update_diagnostic_entries( language_server_id, PathBuf::from("/test/consts.rs"), None, vec![ DiagnosticEntry { range: Unclipped(PointUtf16::new(0, 15))..Unclipped(PointUtf16::new(0, 15)), diagnostic: Diagnostic { message: "mismatched types\nexpected `usize`, found `char`".to_string(), severity: DiagnosticSeverity::ERROR, is_primary: true, is_disk_based: true, group_id: 0, ..Default::default() }, }, DiagnosticEntry { range: Unclipped(PointUtf16::new(1, 15))..Unclipped(PointUtf16::new(1, 15)), diagnostic: Diagnostic { message: "unresolved name `c`".to_string(), severity: DiagnosticSeverity::ERROR, is_primary: true, is_disk_based: true, group_id: 1, ..Default::default() }, }, ], cx, ) .unwrap(); lsp_store.disk_based_diagnostics_finished(language_server_id, cx); }); view.next_notification(DIAGNOSTICS_UPDATE_DEBOUNCE + Duration::from_millis(10), cx) .await; assert_eq!( editor_blocks(&editor, cx), [ (DisplayRow(0), FILE_HEADER.into()), (DisplayRow(2), DIAGNOSTIC_HEADER.into()), (DisplayRow(7), EXCERPT_HEADER.into()), (DisplayRow(8), DIAGNOSTIC_HEADER.into()), (DisplayRow(13), FILE_HEADER.into()), (DisplayRow(15), DIAGNOSTIC_HEADER.into()), (DisplayRow(28), EXCERPT_HEADER.into()), (DisplayRow(29), DIAGNOSTIC_HEADER.into()), (DisplayRow(38), EXCERPT_HEADER.into()), ] ); assert_eq!( editor.update(cx, |editor, cx| editor.display_text(cx)), concat!( // // consts.rs // "\n", // filename "\n", // padding // diagnostic group 1 "\n", // primary message "\n", // padding "const a: i32 = 'a';\n", "\n", // supporting diagnostic "const b: i32 = c;\n", "\n", // context ellipsis // diagnostic group 2 "\n", // primary message "\n", // padding "const a: i32 = 'a';\n", "const b: i32 = c;\n", "\n", // supporting diagnostic // // main.rs // "\n", // filename "\n", // padding // diagnostic group 1 "\n", // primary message "\n", // padding " let x = vec![];\n", " let y = vec![];\n", "\n", // supporting diagnostic " a(x);\n", " b(y);\n", "\n", // supporting diagnostic " // comment 1\n", " // comment 2\n", " c(y);\n", "\n", // supporting diagnostic " d(x);\n", "\n", // context ellipsis // diagnostic group 2 "\n", // primary message "\n", // filename "fn main() {\n", " let x = vec![];\n", "\n", // supporting diagnostic " let y = vec![];\n", " a(x);\n", "\n", // supporting diagnostic " b(y);\n", "\n", // context ellipsis " c(y);\n", " d(x);\n", "\n", // supporting diagnostic "}" ) ); } #[gpui::test] async fn test_diagnostics_multiple_servers(cx: &mut TestAppContext) { init_test(cx); let fs = FakeFs::new(cx.executor()); fs.insert_tree( "/test", json!({ "main.js": " a(); b(); c(); d(); e(); ".unindent() }), ) .await; let server_id_1 = LanguageServerId(100); let server_id_2 = LanguageServerId(101); let project = Project::test(fs.clone(), ["/test".as_ref()], cx).await; let lsp_store = project.read_with(cx, |project, _| project.lsp_store()); let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); let cx = &mut VisualTestContext::from_window(*window, cx); let workspace = window.root(cx).unwrap(); let view = window.build_view(cx, |cx| { ProjectDiagnosticsEditor::new_with_context( 1, true, project.clone(), workspace.downgrade(), cx, ) }); let editor = view.update(cx, |view, _| view.editor.clone()); // Two language servers start updating diagnostics lsp_store.update(cx, |lsp_store, cx| { lsp_store.disk_based_diagnostics_started(server_id_1, cx); lsp_store.disk_based_diagnostics_started(server_id_2, cx); lsp_store .update_diagnostic_entries( server_id_1, PathBuf::from("/test/main.js"), None, vec![DiagnosticEntry { range: Unclipped(PointUtf16::new(0, 0))..Unclipped(PointUtf16::new(0, 1)), diagnostic: Diagnostic { message: "error 1".to_string(), severity: DiagnosticSeverity::WARNING, is_primary: true, is_disk_based: true, group_id: 1, ..Default::default() }, }], cx, ) .unwrap();