diff --git a/assets/settings/default.json b/assets/settings/default.json index fbcde696c3..426ccd983c 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -1033,9 +1033,14 @@ "button": true, // Whether to show warnings or not by default. "include_warnings": true, - // Minimum time to wait before pulling diagnostics from the language server(s). - // 0 turns the debounce off, `null` disables the feature. - "lsp_pull_diagnostics_debounce_ms": 50, + // Settings for using LSP pull diagnostics mechanism in Zed. + "lsp_pull_diagnostics": { + // Whether to pull for diagnostics or not. + "enabled": true, + // Minimum time to wait before pulling diagnostics from the language server(s). + // 0 turns the debounce off. + "debounce_ms": 50 + }, // Settings for inline diagnostics "inline": { // Whether to show diagnostics inline or not diff --git a/crates/collab/src/rpc.rs b/crates/collab/src/rpc.rs index e768e4c3d0..a9f0f2cdf3 100644 --- a/crates/collab/src/rpc.rs +++ b/crates/collab/src/rpc.rs @@ -356,7 +356,7 @@ impl Server { .add_message_handler(broadcast_project_message_from_host::) .add_message_handler(broadcast_project_message_from_host::) .add_message_handler( - broadcast_project_message_from_host::, + broadcast_project_message_from_host::, ) .add_request_handler(get_users) .add_request_handler(fuzzy_search_users) diff --git a/crates/diagnostics/src/diagnostics_tests.rs b/crates/diagnostics/src/diagnostics_tests.rs index 1050c0ecf9..83505d21b3 100644 --- a/crates/diagnostics/src/diagnostics_tests.rs +++ b/crates/diagnostics/src/diagnostics_tests.rs @@ -105,7 +105,7 @@ async fn test_diagnostics(cx: &mut TestAppContext) { } ], version: None - }, DiagnosticSourceKind::Pushed, &[], cx).unwrap(); + }, None, DiagnosticSourceKind::Pushed, &[], cx).unwrap(); }); // Open the project diagnostics view while there are already diagnostics. @@ -176,6 +176,7 @@ async fn test_diagnostics(cx: &mut TestAppContext) { }], version: None, }, + None, DiagnosticSourceKind::Pushed, &[], cx, @@ -262,6 +263,7 @@ async fn test_diagnostics(cx: &mut TestAppContext) { ], version: None, }, + None, DiagnosticSourceKind::Pushed, &[], cx, @@ -370,6 +372,7 @@ async fn test_diagnostics_with_folds(cx: &mut TestAppContext) { }], version: None, }, + None, DiagnosticSourceKind::Pushed, &[], cx, @@ -468,6 +471,7 @@ async fn test_diagnostics_multiple_servers(cx: &mut TestAppContext) { }], version: None, }, + None, DiagnosticSourceKind::Pushed, &[], cx, @@ -511,6 +515,7 @@ async fn test_diagnostics_multiple_servers(cx: &mut TestAppContext) { }], version: None, }, + None, DiagnosticSourceKind::Pushed, &[], cx, @@ -553,6 +558,7 @@ async fn test_diagnostics_multiple_servers(cx: &mut TestAppContext) { }], version: None, }, + None, DiagnosticSourceKind::Pushed, &[], cx, @@ -566,6 +572,7 @@ async fn test_diagnostics_multiple_servers(cx: &mut TestAppContext) { diagnostics: vec![], version: None, }, + None, DiagnosticSourceKind::Pushed, &[], cx, @@ -607,6 +614,7 @@ async fn test_diagnostics_multiple_servers(cx: &mut TestAppContext) { }], version: None, }, + None, DiagnosticSourceKind::Pushed, &[], cx, @@ -740,6 +748,7 @@ async fn test_random_diagnostics_blocks(cx: &mut TestAppContext, mut rng: StdRng diagnostics: diagnostics.clone(), version: None, }, + None, DiagnosticSourceKind::Pushed, &[], cx, @@ -928,6 +937,7 @@ async fn test_random_diagnostics_with_inlays(cx: &mut TestAppContext, mut rng: S diagnostics: diagnostics.clone(), version: None, }, + None, DiagnosticSourceKind::Pushed, &[], cx, @@ -984,6 +994,7 @@ async fn active_diagnostics_dismiss_after_invalidation(cx: &mut TestAppContext) ..Default::default() }], }, + None, DiagnosticSourceKind::Pushed, &[], cx, @@ -1018,6 +1029,7 @@ async fn active_diagnostics_dismiss_after_invalidation(cx: &mut TestAppContext) version: None, diagnostics: Vec::new(), }, + None, DiagnosticSourceKind::Pushed, &[], cx, @@ -1100,6 +1112,7 @@ async fn cycle_through_same_place_diagnostics(cx: &mut TestAppContext) { }, ], }, + None, DiagnosticSourceKind::Pushed, &[], cx, @@ -1239,6 +1252,7 @@ async fn test_diagnostics_with_links(cx: &mut TestAppContext) { ..Default::default() }], }, + None, DiagnosticSourceKind::Pushed, &[], cx, @@ -1291,6 +1305,7 @@ async fn test_hover_diagnostic_and_info_popovers(cx: &mut gpui::TestAppContext) ..Default::default() }], }, + None, DiagnosticSourceKind::Pushed, &[], cx, @@ -1393,6 +1408,7 @@ async fn test_diagnostics_with_code(cx: &mut TestAppContext) { ], version: None, }, + None, DiagnosticSourceKind::Pushed, &[], cx, diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index ed8e0c9cc8..4e3baf69ff 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -125,7 +125,7 @@ use markdown::Markdown; use mouse_context_menu::MouseContextMenu; use persistence::DB; use project::{ - BreakpointWithPosition, CompletionResponse, LspPullDiagnostics, ProjectPath, + BreakpointWithPosition, CompletionResponse, LspPullDiagnostics, ProjectPath, PulledDiagnostics, debugger::{ breakpoint_store::{ BreakpointEditAction, BreakpointSessionState, BreakpointState, BreakpointStore, @@ -1700,7 +1700,7 @@ impl Editor { } editor.pull_diagnostics(window, cx); } - project::Event::RefreshDocumentsDiagnostics => { + project::Event::PullWorkspaceDiagnostics => { editor.pull_diagnostics(window, cx); } project::Event::SnippetEdit(id, snippet_edits) => { @@ -15966,11 +15966,13 @@ impl Editor { fn pull_diagnostics(&mut self, window: &Window, cx: &mut Context) -> Option<()> { let project = self.project.as_ref()?.downgrade(); - let debounce = Duration::from_millis( - ProjectSettings::get_global(cx) - .diagnostics - .lsp_pull_diagnostics_debounce_ms?, - ); + let pull_diagnostics_settings = ProjectSettings::get_global(cx) + .diagnostics + .lsp_pull_diagnostics; + if !pull_diagnostics_settings.enabled { + return None; + } + let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms); let buffers = self.buffer.read(cx).all_buffers(); self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| { @@ -18733,13 +18735,16 @@ impl Editor { } if let Some(project) = self.project.as_ref() { project.update(cx, |project, cx| { - // Diagnostics are not local: an edit within one file (`pub mod foo()` -> `pub mod bar()`), may cause errors in another files with `foo()`. - // Hence, emit a project-wide event to pull for every buffer's diagnostics that has an open editor. if edited_buffer .as_ref() .is_some_and(|buffer| buffer.read(cx).file().is_some()) { - cx.emit(project::Event::RefreshDocumentsDiagnostics); + // Diagnostics are not local: an edit within one file (`pub mod foo()` -> `pub mod bar()`), may cause errors in another files with `foo()`. + // Hence, emit a project-wide event to pull for every buffer's diagnostics that has an open editor. + // TODO: https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#diagnostic_refresh explains the flow how + // diagnostics should be pulled: instead of pulling every open editor's buffer's diagnostics (which happens effectively due to emitting this event), + // we should only pull for the current buffer's diagnostics and get the rest via the workspace diagnostics LSP request — this is not implemented yet. + cx.emit(project::Event::PullWorkspaceDiagnostics); } if let Some(buffer) = edited_buffer { @@ -20990,7 +20995,7 @@ impl SemanticsProvider for Entity { let LspPullDiagnostics::Response { server_id, uri, - diagnostics: project::PulledDiagnostics::Changed { diagnostics, .. }, + diagnostics, } = diagnostics_set else { continue; @@ -21001,25 +21006,49 @@ impl SemanticsProvider for Entity { .as_ref() .map(|adapter| adapter.disk_based_diagnostic_sources.as_slice()) .unwrap_or(&[]); - lsp_store - .merge_diagnostics( - server_id, - lsp::PublishDiagnosticsParams { - uri: uri.clone(), - diagnostics, - version: None, - }, - DiagnosticSourceKind::Pulled, - disk_based_sources, - |old_diagnostic, _| match old_diagnostic.source_kind { - DiagnosticSourceKind::Pulled => false, - DiagnosticSourceKind::Other | DiagnosticSourceKind::Pushed => { - true - } - }, - cx, - ) - .log_err(); + match diagnostics { + PulledDiagnostics::Unchanged { result_id } => { + lsp_store + .merge_diagnostics( + server_id, + lsp::PublishDiagnosticsParams { + uri: uri.clone(), + diagnostics: Vec::new(), + version: None, + }, + Some(result_id), + DiagnosticSourceKind::Pulled, + disk_based_sources, + |_, _| true, + cx, + ) + .log_err(); + } + PulledDiagnostics::Changed { + diagnostics, + result_id, + } => { + lsp_store + .merge_diagnostics( + server_id, + lsp::PublishDiagnosticsParams { + uri: uri.clone(), + diagnostics, + version: None, + }, + result_id, + DiagnosticSourceKind::Pulled, + disk_based_sources, + |old_diagnostic, _| match old_diagnostic.source_kind { + DiagnosticSourceKind::Pulled => false, + DiagnosticSourceKind::Other + | DiagnosticSourceKind::Pushed => true, + }, + cx, + ) + .log_err(); + } + } } }) }) diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index c884ebaffb..dc09e1b71a 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -13940,6 +13940,7 @@ async fn go_to_prev_overlapping_diagnostic(executor: BackgroundExecutor, cx: &mu }, ], }, + None, DiagnosticSourceKind::Pushed, &[], cx, @@ -21854,7 +21855,7 @@ fn assert_hunk_revert( assert_eq!(actual_hunk_statuses_before, expected_hunk_statuses_before); } -#[gpui::test] +#[gpui::test(iterations = 10)] async fn test_pulling_diagnostics(cx: &mut TestAppContext) { init_test(cx, |_| {}); @@ -21912,7 +21913,8 @@ async fn test_pulling_diagnostics(cx: &mut TestAppContext) { let fake_server = fake_servers.next().await.unwrap(); let mut first_request = fake_server .set_request_handler::(move |params, _| { - counter.fetch_add(1, atomic::Ordering::Release); + let new_result_id = counter.fetch_add(1, atomic::Ordering::Release) + 1; + let result_id = Some(new_result_id.to_string()); assert_eq!( params.text_document.uri, lsp::Url::from_file_path(path!("/a/first.rs")).unwrap() @@ -21923,13 +21925,27 @@ async fn test_pulling_diagnostics(cx: &mut TestAppContext) { related_documents: None, full_document_diagnostic_report: lsp::FullDocumentDiagnosticReport { items: Vec::new(), - result_id: None, + result_id, }, }), )) } }); + let ensure_result_id = |expected: Option, cx: &mut TestAppContext| { + editor.update(cx, |editor, cx| { + let buffer_result_id = editor + .buffer() + .read(cx) + .as_singleton() + .expect("created a singleton buffer") + .read(cx) + .result_id(); + assert_eq!(expected, buffer_result_id); + }); + }; + + ensure_result_id(None, cx); cx.executor().advance_clock(Duration::from_millis(60)); cx.executor().run_until_parked(); assert_eq!( @@ -21941,6 +21957,7 @@ async fn test_pulling_diagnostics(cx: &mut TestAppContext) { .next() .await .expect("should have sent the first diagnostics pull request"); + ensure_result_id(Some("1".to_string()), cx); // Editing should trigger diagnostics editor.update_in(cx, |editor, window, cx| { @@ -21953,6 +21970,7 @@ async fn test_pulling_diagnostics(cx: &mut TestAppContext) { 2, "Editing should trigger diagnostic request" ); + ensure_result_id(Some("2".to_string()), cx); // Moving cursor should not trigger diagnostic request editor.update_in(cx, |editor, window, cx| { @@ -21967,6 +21985,7 @@ async fn test_pulling_diagnostics(cx: &mut TestAppContext) { 2, "Cursor movement should not trigger diagnostic request" ); + ensure_result_id(Some("2".to_string()), cx); // Multiple rapid edits should be debounced for _ in 0..5 { @@ -21980,7 +21999,7 @@ async fn test_pulling_diagnostics(cx: &mut TestAppContext) { let final_requests = diagnostic_requests.load(atomic::Ordering::Acquire); assert!( final_requests <= 4, - "Multiple rapid edits should be debounced (got {} requests)", - final_requests + "Multiple rapid edits should be debounced (got {final_requests} requests)", ); + ensure_result_id(Some(final_requests.to_string()), cx); } diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index ae82f3aa63..656ef5bfd5 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -127,6 +127,8 @@ pub struct Buffer { has_unsaved_edits: Cell<(clock::Global, bool)>, change_bits: Vec>>, _subscriptions: Vec, + /// The result id received last time when pulling diagnostics for this buffer. + pull_diagnostics_result_id: Option, } #[derive(Copy, Clone, Debug, PartialEq, Eq)] @@ -955,6 +957,7 @@ impl Buffer { completion_triggers_timestamp: Default::default(), deferred_ops: OperationQueue::new(), has_conflict: false, + pull_diagnostics_result_id: None, change_bits: Default::default(), _subscriptions: Vec::new(), } @@ -2740,6 +2743,14 @@ impl Buffer { pub fn preserve_preview(&self) -> bool { !self.has_edits_since(&self.preview_version) } + + pub fn result_id(&self) -> Option { + self.pull_diagnostics_result_id.clone() + } + + pub fn set_result_id(&mut self, result_id: Option) { + self.pull_diagnostics_result_id = result_id; + } } #[doc(hidden)] diff --git a/crates/project/src/lsp_command.rs b/crates/project/src/lsp_command.rs index 97cc35c209..10706d49a0 100644 --- a/crates/project/src/lsp_command.rs +++ b/crates/project/src/lsp_command.rs @@ -3832,7 +3832,7 @@ impl LspCommand for GetDocumentDiagnostics { fn to_lsp( &self, path: &Path, - _: &Buffer, + buffer: &Buffer, language_server: &Arc, _: &App, ) -> Result { @@ -3849,7 +3849,7 @@ impl LspCommand for GetDocumentDiagnostics { uri: file_path_to_lsp_url(path)?, }, identifier, - previous_result_id: None, + previous_result_id: buffer.result_id(), partial_result_params: Default::default(), work_done_progress_params: Default::default(), }) diff --git a/crates/project/src/lsp_store.rs b/crates/project/src/lsp_store.rs index 15cf954ef4..523d577466 100644 --- a/crates/project/src/lsp_store.rs +++ b/crates/project/src/lsp_store.rs @@ -255,8 +255,8 @@ impl LocalLspStore { let fs = self.fs.clone(); let pull_diagnostics = ProjectSettings::get_global(cx) .diagnostics - .lsp_pull_diagnostics_debounce_ms - .is_some(); + .lsp_pull_diagnostics + .enabled; cx.spawn(async move |cx| { let result = async { let toolchains = this.update(cx, |this, cx| this.toolchain_store(cx))?; @@ -480,6 +480,7 @@ impl LocalLspStore { this.merge_diagnostics( server_id, params, + None, DiagnosticSourceKind::Pushed, &adapter.disk_based_diagnostic_sources, |diagnostic, cx| match diagnostic.source_kind { @@ -871,9 +872,9 @@ impl LocalLspStore { let mut cx = cx.clone(); async move { this.update(&mut cx, |this, cx| { - cx.emit(LspStoreEvent::RefreshDocumentsDiagnostics); + cx.emit(LspStoreEvent::PullWorkspaceDiagnostics); this.downstream_client.as_ref().map(|(client, project_id)| { - client.send(proto::RefreshDocumentsDiagnostics { + client.send(proto::PullWorkspaceDiagnostics { project_id: *project_id, }) }) @@ -2138,8 +2139,16 @@ impl LocalLspStore { for (server_id, diagnostics) in diagnostics.get(file.path()).cloned().unwrap_or_default() { - self.update_buffer_diagnostics(buffer_handle, server_id, None, diagnostics, cx) - .log_err(); + self.update_buffer_diagnostics( + buffer_handle, + server_id, + None, + None, + diagnostics, + Vec::new(), + cx, + ) + .log_err(); } } let Some(language) = language else { @@ -2208,8 +2217,10 @@ impl LocalLspStore { &mut self, buffer: &Entity, server_id: LanguageServerId, + result_id: Option, version: Option, - mut diagnostics: Vec>>, + new_diagnostics: Vec>>, + reused_diagnostics: Vec>>, cx: &mut Context, ) -> Result<()> { fn compare_diagnostics(a: &Diagnostic, b: &Diagnostic) -> Ordering { @@ -2220,7 +2231,11 @@ impl LocalLspStore { .then_with(|| a.message.cmp(&b.message)) } - diagnostics.sort_unstable_by(|a, b| { + let mut diagnostics = Vec::with_capacity(new_diagnostics.len() + reused_diagnostics.len()); + diagnostics.extend(new_diagnostics.into_iter().map(|d| (true, d))); + diagnostics.extend(reused_diagnostics.into_iter().map(|d| (false, d))); + + diagnostics.sort_unstable_by(|(_, a), (_, b)| { Ordering::Equal .then_with(|| a.range.start.cmp(&b.range.start)) .then_with(|| b.range.end.cmp(&a.range.end)) @@ -2236,13 +2251,15 @@ impl LocalLspStore { let mut sanitized_diagnostics = Vec::with_capacity(diagnostics.len()); - for entry in diagnostics { + for (new_diagnostic, entry) in diagnostics { let start; let end; - if entry.diagnostic.is_disk_based { + if new_diagnostic && entry.diagnostic.is_disk_based { // Some diagnostics are based on files on disk instead of buffers' // current contents. Adjust these diagnostics' ranges to reflect // any unsaved edits. + // Do not alter the reused ones though, as their coordinates were stored as anchors + // and were properly adjusted on reuse. start = Unclipped((*edits_since_save).old_to_new(entry.range.start.0)); end = Unclipped((*edits_since_save).old_to_new(entry.range.end.0)); } else { @@ -2273,6 +2290,7 @@ impl LocalLspStore { let set = DiagnosticSet::new(sanitized_diagnostics, &snapshot); buffer.update(cx, |buffer, cx| { + buffer.set_result_id(result_id); buffer.update_diagnostics(server_id, set, cx) }); Ok(()) @@ -3479,7 +3497,7 @@ pub enum LspStoreEvent { edits: Vec<(lsp::Range, Snippet)>, most_recent_edit: clock::Lamport, }, - RefreshDocumentsDiagnostics, + PullWorkspaceDiagnostics, } #[derive(Clone, Debug, Serialize)] @@ -3527,7 +3545,7 @@ impl LspStore { client.add_entity_request_handler(Self::handle_register_buffer_with_language_servers); client.add_entity_request_handler(Self::handle_rename_project_entry); client.add_entity_request_handler(Self::handle_language_server_id_for_name); - client.add_entity_request_handler(Self::handle_refresh_documents_diagnostics); + client.add_entity_request_handler(Self::handle_pull_workspace_diagnostics); client.add_entity_request_handler(Self::handle_lsp_command::); client.add_entity_request_handler(Self::handle_lsp_command::); client.add_entity_request_handler(Self::handle_lsp_command::); @@ -6594,21 +6612,32 @@ impl LspStore { .insert(language_server_id); } + #[cfg(test)] pub fn update_diagnostic_entries( &mut self, server_id: LanguageServerId, abs_path: PathBuf, + result_id: Option, version: Option, diagnostics: Vec>>, cx: &mut Context, ) -> anyhow::Result<()> { - self.merge_diagnostic_entries(server_id, abs_path, version, diagnostics, |_, _| false, cx) + self.merge_diagnostic_entries( + server_id, + abs_path, + result_id, + version, + diagnostics, + |_, _| false, + cx, + ) } pub fn merge_diagnostic_entries bool + Clone>( &mut self, server_id: LanguageServerId, abs_path: PathBuf, + result_id: Option, version: Option, mut diagnostics: Vec>>, filter: F, @@ -6633,29 +6662,32 @@ impl LspStore { .buffer_snapshot_for_lsp_version(&buffer_handle, server_id, version, cx)?; let buffer = buffer_handle.read(cx); - diagnostics.extend( - buffer - .get_diagnostics(server_id) - .into_iter() - .flat_map(|diag| { - diag.iter().filter(|v| filter(&v.diagnostic, cx)).map(|v| { - let start = Unclipped(v.range.start.to_point_utf16(&snapshot)); - let end = Unclipped(v.range.end.to_point_utf16(&snapshot)); - DiagnosticEntry { - range: start..end, - diagnostic: v.diagnostic.clone(), - } - }) - }), - ); + let reused_diagnostics = buffer + .get_diagnostics(server_id) + .into_iter() + .flat_map(|diag| { + diag.iter().filter(|v| filter(&v.diagnostic, cx)).map(|v| { + let start = Unclipped(v.range.start.to_point_utf16(&snapshot)); + let end = Unclipped(v.range.end.to_point_utf16(&snapshot)); + DiagnosticEntry { + range: start..end, + diagnostic: v.diagnostic.clone(), + } + }) + }) + .collect::>(); self.as_local_mut().unwrap().update_buffer_diagnostics( &buffer_handle, server_id, + result_id, version, diagnostics.clone(), + reused_diagnostics.clone(), cx, )?; + + diagnostics.extend(reused_diagnostics); } let updated = worktree.update(cx, |worktree, cx| { @@ -8139,13 +8171,13 @@ impl LspStore { Ok(proto::Ack {}) } - async fn handle_refresh_documents_diagnostics( + async fn handle_pull_workspace_diagnostics( this: Entity, - _: TypedEnvelope, + _: TypedEnvelope, mut cx: AsyncApp, ) -> Result { this.update(&mut cx, |_, cx| { - cx.emit(LspStoreEvent::RefreshDocumentsDiagnostics); + cx.emit(LspStoreEvent::PullWorkspaceDiagnostics); })?; Ok(proto::Ack {}) } @@ -8872,6 +8904,7 @@ impl LspStore { &mut self, language_server_id: LanguageServerId, params: lsp::PublishDiagnosticsParams, + result_id: Option, source_kind: DiagnosticSourceKind, disk_based_sources: &[String], cx: &mut Context, @@ -8879,6 +8912,7 @@ impl LspStore { self.merge_diagnostics( language_server_id, params, + result_id, source_kind, disk_based_sources, |_, _| false, @@ -8890,6 +8924,7 @@ impl LspStore { &mut self, language_server_id: LanguageServerId, mut params: lsp::PublishDiagnosticsParams, + result_id: Option, source_kind: DiagnosticSourceKind, disk_based_sources: &[String], filter: F, @@ -9027,6 +9062,7 @@ impl LspStore { self.merge_diagnostic_entries( language_server_id, abs_path, + result_id, params.version, diagnostics, filter, diff --git a/crates/project/src/lsp_store/clangd_ext.rs b/crates/project/src/lsp_store/clangd_ext.rs index 9f2b044ed1..8076374dcc 100644 --- a/crates/project/src/lsp_store/clangd_ext.rs +++ b/crates/project/src/lsp_store/clangd_ext.rs @@ -84,6 +84,7 @@ pub fn register_notifications( this.merge_diagnostics( server_id, mapped_diagnostics, + None, DiagnosticSourceKind::Pushed, &adapter.disk_based_diagnostic_sources, |diag, _| !is_inactive_region(diag), diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 41be601456..e4b4c7a82e 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -317,7 +317,7 @@ pub enum Event { SnippetEdit(BufferId, Vec<(lsp::Range, Snippet)>), ExpandedAllForEntry(WorktreeId, ProjectEntryId), AgentLocationChanged, - RefreshDocumentsDiagnostics, + PullWorkspaceDiagnostics, } pub struct AgentLocationChanged; @@ -2814,9 +2814,7 @@ impl Project { } LspStoreEvent::RefreshInlayHints => cx.emit(Event::RefreshInlayHints), LspStoreEvent::RefreshCodeLens => cx.emit(Event::RefreshCodeLens), - LspStoreEvent::RefreshDocumentsDiagnostics => { - cx.emit(Event::RefreshDocumentsDiagnostics) - } + LspStoreEvent::PullWorkspaceDiagnostics => cx.emit(Event::PullWorkspaceDiagnostics), LspStoreEvent::LanguageServerPrompt(prompt) => { cx.emit(Event::LanguageServerPrompt(prompt.clone())) } @@ -3732,6 +3730,7 @@ impl Project { &mut self, language_server_id: LanguageServerId, source_kind: DiagnosticSourceKind, + result_id: Option, params: lsp::PublishDiagnosticsParams, disk_based_sources: &[String], cx: &mut Context, @@ -3740,6 +3739,7 @@ impl Project { lsp_store.update_diagnostics( language_server_id, params, + result_id, source_kind, disk_based_sources, cx, diff --git a/crates/project/src/project_settings.rs b/crates/project/src/project_settings.rs index f32ce0c546..8e84f3c73b 100644 --- a/crates/project/src/project_settings.rs +++ b/crates/project/src/project_settings.rs @@ -127,9 +127,8 @@ pub struct DiagnosticsSettings { /// Whether or not to include warning diagnostics. pub include_warnings: bool, - /// Minimum time to wait before pulling diagnostics from the language server(s). - /// 0 turns the debounce off, None disables the feature. - pub lsp_pull_diagnostics_debounce_ms: Option, + /// Settings for using LSP pull diagnostics mechanism in Zed. + pub lsp_pull_diagnostics: LspPullDiagnosticsSettings, /// Settings for showing inline diagnostics. pub inline: InlineDiagnosticsSettings, @@ -146,6 +145,26 @@ impl DiagnosticsSettings { } } +#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema)] +#[serde(default)] +pub struct LspPullDiagnosticsSettings { + /// Whether to pull for diagnostics or not. + /// + /// Default: true + #[serde(default = "default_true")] + pub enabled: bool, + /// Minimum time to wait before pulling diagnostics from the language server(s). + /// 0 turns the debounce off. + /// + /// Default: 50 + #[serde(default = "default_lsp_diagnostics_pull_debounce_ms")] + pub debounce_ms: u64, +} + +fn default_lsp_diagnostics_pull_debounce_ms() -> u64 { + 50 +} + #[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema)] #[serde(default)] pub struct InlineDiagnosticsSettings { @@ -157,11 +176,13 @@ pub struct InlineDiagnosticsSettings { /// last editor event. /// /// Default: 150 + #[serde(default = "default_inline_diagnostics_update_debounce_ms")] pub update_debounce_ms: u64, /// The amount of padding between the end of the source line and the start /// of the inline diagnostic in units of columns. /// /// Default: 4 + #[serde(default = "default_inline_diagnostics_padding")] pub padding: u32, /// The minimum column to display inline diagnostics. This setting can be /// used to horizontally align inline diagnostics at some position. Lines @@ -173,6 +194,47 @@ pub struct InlineDiagnosticsSettings { pub max_severity: Option, } +fn default_inline_diagnostics_update_debounce_ms() -> u64 { + 150 +} + +fn default_inline_diagnostics_padding() -> u32 { + 4 +} + +impl Default for DiagnosticsSettings { + fn default() -> Self { + Self { + button: true, + include_warnings: true, + lsp_pull_diagnostics: LspPullDiagnosticsSettings::default(), + inline: InlineDiagnosticsSettings::default(), + cargo: None, + } + } +} + +impl Default for LspPullDiagnosticsSettings { + fn default() -> Self { + Self { + enabled: true, + debounce_ms: default_lsp_diagnostics_pull_debounce_ms(), + } + } +} + +impl Default for InlineDiagnosticsSettings { + fn default() -> Self { + Self { + enabled: false, + update_debounce_ms: default_inline_diagnostics_update_debounce_ms(), + padding: default_inline_diagnostics_padding(), + min_column: 0, + max_severity: None, + } + } +} + #[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)] pub struct CargoDiagnosticsSettings { /// When enabled, Zed disables rust-analyzer's check on save and starts to query @@ -208,30 +270,6 @@ impl DiagnosticSeverity { } } -impl Default for DiagnosticsSettings { - fn default() -> Self { - Self { - button: true, - include_warnings: true, - lsp_pull_diagnostics_debounce_ms: Some(30), - inline: InlineDiagnosticsSettings::default(), - cargo: None, - } - } -} - -impl Default for InlineDiagnosticsSettings { - fn default() -> Self { - Self { - enabled: false, - update_debounce_ms: 150, - padding: 4, - min_column: 0, - max_severity: None, - } - } -} - #[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, JsonSchema)] pub struct GitSettings { /// Whether or not to show the git gutter. diff --git a/crates/project/src/project_tests.rs b/crates/project/src/project_tests.rs index c369a2245b..23bda8bf65 100644 --- a/crates/project/src/project_tests.rs +++ b/crates/project/src/project_tests.rs @@ -1332,6 +1332,7 @@ async fn test_single_file_worktrees_diagnostics(cx: &mut gpui::TestAppContext) { ..Default::default() }], }, + None, DiagnosticSourceKind::Pushed, &[], cx, @@ -1350,6 +1351,7 @@ async fn test_single_file_worktrees_diagnostics(cx: &mut gpui::TestAppContext) { ..Default::default() }], }, + None, DiagnosticSourceKind::Pushed, &[], cx, @@ -1441,6 +1443,7 @@ async fn test_omitted_diagnostics(cx: &mut gpui::TestAppContext) { ..Default::default() }], }, + None, DiagnosticSourceKind::Pushed, &[], cx, @@ -1459,6 +1462,7 @@ async fn test_omitted_diagnostics(cx: &mut gpui::TestAppContext) { ..Default::default() }], }, + None, DiagnosticSourceKind::Pushed, &[], cx, @@ -2376,6 +2380,7 @@ async fn test_empty_diagnostic_ranges(cx: &mut gpui::TestAppContext) { LanguageServerId(0), PathBuf::from("/dir/a.rs"), None, + None, vec![ DiagnosticEntry { range: Unclipped(PointUtf16::new(0, 10)) @@ -2442,6 +2447,7 @@ async fn test_diagnostics_from_multiple_language_servers(cx: &mut gpui::TestAppC LanguageServerId(0), Path::new("/dir/a.rs").to_owned(), None, + None, vec![DiagnosticEntry { range: Unclipped(PointUtf16::new(0, 0))..Unclipped(PointUtf16::new(0, 3)), diagnostic: Diagnostic { @@ -2460,6 +2466,7 @@ async fn test_diagnostics_from_multiple_language_servers(cx: &mut gpui::TestAppC LanguageServerId(1), Path::new("/dir/a.rs").to_owned(), None, + None, vec![DiagnosticEntry { range: Unclipped(PointUtf16::new(0, 0))..Unclipped(PointUtf16::new(0, 3)), diagnostic: Diagnostic { @@ -4596,6 +4603,7 @@ async fn test_grouped_diagnostics(cx: &mut gpui::TestAppContext) { lsp_store.update_diagnostics( LanguageServerId(0), message, + None, DiagnosticSourceKind::Pushed, &[], cx, diff --git a/crates/proto/proto/lsp.proto b/crates/proto/proto/lsp.proto index b04009d622..b4c90b17d3 100644 --- a/crates/proto/proto/lsp.proto +++ b/crates/proto/proto/lsp.proto @@ -804,6 +804,6 @@ message PulledDiagnostics { repeated LspDiagnostic diagnostics = 5; } -message RefreshDocumentsDiagnostics { +message PullWorkspaceDiagnostics { uint64 project_id = 1; } diff --git a/crates/proto/proto/zed.proto b/crates/proto/proto/zed.proto index 0b5be48308..a23381508a 100644 --- a/crates/proto/proto/zed.proto +++ b/crates/proto/proto/zed.proto @@ -391,7 +391,7 @@ message Envelope { GetDocumentDiagnostics get_document_diagnostics = 350; GetDocumentDiagnosticsResponse get_document_diagnostics_response = 351; - RefreshDocumentsDiagnostics refresh_documents_diagnostics = 352; // current max + PullWorkspaceDiagnostics pull_workspace_diagnostics = 352; // current max } diff --git a/crates/proto/src/proto.rs b/crates/proto/src/proto.rs index e166685f10..3f72415dd7 100644 --- a/crates/proto/src/proto.rs +++ b/crates/proto/src/proto.rs @@ -309,7 +309,7 @@ messages!( (LogToDebugConsole, Background), (GetDocumentDiagnostics, Background), (GetDocumentDiagnosticsResponse, Background), - (RefreshDocumentsDiagnostics, Background) + (PullWorkspaceDiagnostics, Background) ); request_messages!( @@ -473,7 +473,7 @@ request_messages!( (GetDebugAdapterBinary, DebugAdapterBinary), (RunDebugLocators, DebugRequest), (GetDocumentDiagnostics, GetDocumentDiagnosticsResponse), - (RefreshDocumentsDiagnostics, Ack) + (PullWorkspaceDiagnostics, Ack) ); entity_messages!( @@ -601,7 +601,7 @@ entity_messages!( GetDebugAdapterBinary, LogToDebugConsole, GetDocumentDiagnostics, - RefreshDocumentsDiagnostics + PullWorkspaceDiagnostics ); entity_messages!(