@@ -32,7 +32,10 @@ use std::{
env, future, mem,
path::{Path, PathBuf},
rc::Rc,
- sync::Arc,
+ sync::{
+ atomic::{AtomicBool, Ordering::SeqCst},
+ Arc,
+ },
};
use unindent::Unindent as _;
use workspace::{
@@ -3636,6 +3639,141 @@ async fn test_collaborating_with_diagnostics(
});
}
+#[gpui::test(iterations = 10)]
+async fn test_collaborating_with_lsp_progress_updates_and_diagnostics_ordering(
+ deterministic: Arc<Deterministic>,
+ cx_a: &mut TestAppContext,
+ cx_b: &mut TestAppContext,
+) {
+ deterministic.forbid_parking();
+ let mut server = TestServer::start(&deterministic).await;
+ let client_a = server.create_client(cx_a, "user_a").await;
+ let client_b = server.create_client(cx_b, "user_b").await;
+ server
+ .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
+ .await;
+
+ // Set up a fake language server.
+ let mut language = Language::new(
+ LanguageConfig {
+ name: "Rust".into(),
+ path_suffixes: vec!["rs".to_string()],
+ ..Default::default()
+ },
+ Some(tree_sitter_rust::language()),
+ );
+ let mut fake_language_servers = language
+ .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
+ disk_based_diagnostics_progress_token: Some("the-disk-based-token".into()),
+ disk_based_diagnostics_sources: vec!["the-disk-based-diagnostics-source".into()],
+ ..Default::default()
+ }))
+ .await;
+ client_a.language_registry.add(Arc::new(language));
+
+ let file_names = &["one.rs", "two.rs", "three.rs", "four.rs", "five.rs"];
+ client_a
+ .fs
+ .insert_tree(
+ "/test",
+ json!({
+ "one.rs": "const ONE: usize = 1;",
+ "two.rs": "const TWO: usize = 2;",
+ "three.rs": "const THREE: usize = 3;",
+ "four.rs": "const FOUR: usize = 3;",
+ "five.rs": "const FIVE: usize = 3;",
+ }),
+ )
+ .await;
+
+ let (project_a, worktree_id) = client_a.build_local_project("/test", cx_a).await;
+
+ // Share a project as client A
+ let active_call_a = cx_a.read(ActiveCall::global);
+ let project_id = active_call_a
+ .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
+ .await
+ .unwrap();
+
+ // Join the project as client B and open all three files.
+ let project_b = client_b.build_remote_project(project_id, cx_b).await;
+ let guest_buffers = futures::future::try_join_all(file_names.iter().map(|file_name| {
+ project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, file_name), cx))
+ }))
+ .await
+ .unwrap();
+
+ // Simulate a language server reporting errors for a file.
+ let fake_language_server = fake_language_servers.next().await.unwrap();
+ fake_language_server
+ .request::<lsp::request::WorkDoneProgressCreate>(lsp::WorkDoneProgressCreateParams {
+ token: lsp::NumberOrString::String("the-disk-based-token".to_string()),
+ })
+ .await
+ .unwrap();
+ fake_language_server.notify::<lsp::notification::Progress>(lsp::ProgressParams {
+ token: lsp::NumberOrString::String("the-disk-based-token".to_string()),
+ value: lsp::ProgressParamsValue::WorkDone(lsp::WorkDoneProgress::Begin(
+ lsp::WorkDoneProgressBegin {
+ title: "Progress Began".into(),
+ ..Default::default()
+ },
+ )),
+ });
+ for file_name in file_names {
+ fake_language_server.notify::<lsp::notification::PublishDiagnostics>(
+ lsp::PublishDiagnosticsParams {
+ uri: lsp::Url::from_file_path(Path::new("/test").join(file_name)).unwrap(),
+ version: None,
+ diagnostics: vec![lsp::Diagnostic {
+ severity: Some(lsp::DiagnosticSeverity::WARNING),
+ source: Some("the-disk-based-diagnostics-source".into()),
+ range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
+ message: "message one".to_string(),
+ ..Default::default()
+ }],
+ },
+ );
+ }
+ fake_language_server.notify::<lsp::notification::Progress>(lsp::ProgressParams {
+ token: lsp::NumberOrString::String("the-disk-based-token".to_string()),
+ value: lsp::ProgressParamsValue::WorkDone(lsp::WorkDoneProgress::End(
+ lsp::WorkDoneProgressEnd { message: None },
+ )),
+ });
+
+ // When the "disk base diagnostics finished" message is received, the buffers'
+ // diagnostics are expected to be present.
+ let disk_based_diagnostics_finished = Arc::new(AtomicBool::new(false));
+ project_b.update(cx_b, {
+ let project_b = project_b.clone();
+ let disk_based_diagnostics_finished = disk_based_diagnostics_finished.clone();
+ move |_, cx| {
+ cx.subscribe(&project_b, move |_, _, event, cx| {
+ if let project::Event::DiskBasedDiagnosticsFinished { .. } = event {
+ disk_based_diagnostics_finished.store(true, SeqCst);
+ for buffer in &guest_buffers {
+ assert_eq!(
+ buffer
+ .read(cx)
+ .snapshot()
+ .diagnostics_in_range::<_, usize>(0..5, false)
+ .count(),
+ 1,
+ "expected a diagnostic for buffer {:?}",
+ buffer.read(cx).file().unwrap().path(),
+ );
+ }
+ }
+ })
+ .detach();
+ }
+ });
+
+ deterministic.run_until_parked();
+ assert!(disk_based_diagnostics_finished.load(SeqCst));
+}
+
#[gpui::test(iterations = 10)]
async fn test_collaborating_with_completion(
deterministic: Arc<Deterministic>,