Pull diagnostics fixes (#32242)
Follow-up of https://github.com/zed-industries/zed/pull/19230 * starts to send `result_id` in pull requests to allow servers to reply with non-full results * fixes a bug where disk-based diagnostics were offset after pulling the diagnostics * fixes a bug due to which pull diagnostics could not be disabled * uses better names and comments for the workspace pull diagnostics part Release Notes: - N/A
This commit is contained in:
parent
508b604b67
commit
380d8c5662
15 changed files with 272 additions and 109 deletions
|
@ -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
|
||||
|
|
|
@ -356,7 +356,7 @@ impl Server {
|
|||
.add_message_handler(broadcast_project_message_from_host::<proto::BufferSaved>)
|
||||
.add_message_handler(broadcast_project_message_from_host::<proto::UpdateDiffBases>)
|
||||
.add_message_handler(
|
||||
broadcast_project_message_from_host::<proto::RefreshDocumentsDiagnostics>,
|
||||
broadcast_project_message_from_host::<proto::PullWorkspaceDiagnostics>,
|
||||
)
|
||||
.add_request_handler(get_users)
|
||||
.add_request_handler(fuzzy_search_users)
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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<Self>) -> 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<Project> {
|
|||
let LspPullDiagnostics::Response {
|
||||
server_id,
|
||||
uri,
|
||||
diagnostics: project::PulledDiagnostics::Changed { diagnostics, .. },
|
||||
diagnostics,
|
||||
} = diagnostics_set
|
||||
else {
|
||||
continue;
|
||||
|
@ -21001,25 +21006,49 @@ impl SemanticsProvider for Entity<Project> {
|
|||
.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();
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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::<lsp::request::DocumentDiagnosticRequest, _, _>(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<String>, 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);
|
||||
}
|
||||
|
|
|
@ -127,6 +127,8 @@ pub struct Buffer {
|
|||
has_unsaved_edits: Cell<(clock::Global, bool)>,
|
||||
change_bits: Vec<rc::Weak<Cell<bool>>>,
|
||||
_subscriptions: Vec<gpui::Subscription>,
|
||||
/// The result id received last time when pulling diagnostics for this buffer.
|
||||
pull_diagnostics_result_id: Option<String>,
|
||||
}
|
||||
|
||||
#[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<String> {
|
||||
self.pull_diagnostics_result_id.clone()
|
||||
}
|
||||
|
||||
pub fn set_result_id(&mut self, result_id: Option<String>) {
|
||||
self.pull_diagnostics_result_id = result_id;
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
|
|
|
@ -3832,7 +3832,7 @@ impl LspCommand for GetDocumentDiagnostics {
|
|||
fn to_lsp(
|
||||
&self,
|
||||
path: &Path,
|
||||
_: &Buffer,
|
||||
buffer: &Buffer,
|
||||
language_server: &Arc<LanguageServer>,
|
||||
_: &App,
|
||||
) -> Result<lsp::DocumentDiagnosticParams> {
|
||||
|
@ -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(),
|
||||
})
|
||||
|
|
|
@ -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<Buffer>,
|
||||
server_id: LanguageServerId,
|
||||
result_id: Option<String>,
|
||||
version: Option<i32>,
|
||||
mut diagnostics: Vec<DiagnosticEntry<Unclipped<PointUtf16>>>,
|
||||
new_diagnostics: Vec<DiagnosticEntry<Unclipped<PointUtf16>>>,
|
||||
reused_diagnostics: Vec<DiagnosticEntry<Unclipped<PointUtf16>>>,
|
||||
cx: &mut Context<LspStore>,
|
||||
) -> 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::<GetCodeActions>);
|
||||
client.add_entity_request_handler(Self::handle_lsp_command::<GetCompletions>);
|
||||
client.add_entity_request_handler(Self::handle_lsp_command::<GetHover>);
|
||||
|
@ -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<String>,
|
||||
version: Option<i32>,
|
||||
diagnostics: Vec<DiagnosticEntry<Unclipped<PointUtf16>>>,
|
||||
cx: &mut Context<Self>,
|
||||
) -> 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<F: Fn(&Diagnostic, &App) -> bool + Clone>(
|
||||
&mut self,
|
||||
server_id: LanguageServerId,
|
||||
abs_path: PathBuf,
|
||||
result_id: Option<String>,
|
||||
version: Option<i32>,
|
||||
mut diagnostics: Vec<DiagnosticEntry<Unclipped<PointUtf16>>>,
|
||||
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::<Vec<_>>();
|
||||
|
||||
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<Self>,
|
||||
_: TypedEnvelope<proto::RefreshDocumentsDiagnostics>,
|
||||
_: TypedEnvelope<proto::PullWorkspaceDiagnostics>,
|
||||
mut cx: AsyncApp,
|
||||
) -> Result<proto::Ack> {
|
||||
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<String>,
|
||||
source_kind: DiagnosticSourceKind,
|
||||
disk_based_sources: &[String],
|
||||
cx: &mut Context<Self>,
|
||||
|
@ -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<String>,
|
||||
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,
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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<String>,
|
||||
params: lsp::PublishDiagnosticsParams,
|
||||
disk_based_sources: &[String],
|
||||
cx: &mut Context<Self>,
|
||||
|
@ -3740,6 +3739,7 @@ impl Project {
|
|||
lsp_store.update_diagnostics(
|
||||
language_server_id,
|
||||
params,
|
||||
result_id,
|
||||
source_kind,
|
||||
disk_based_sources,
|
||||
cx,
|
||||
|
|
|
@ -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<u64>,
|
||||
/// 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<DiagnosticSeverity>,
|
||||
}
|
||||
|
||||
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.
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -804,6 +804,6 @@ message PulledDiagnostics {
|
|||
repeated LspDiagnostic diagnostics = 5;
|
||||
}
|
||||
|
||||
message RefreshDocumentsDiagnostics {
|
||||
message PullWorkspaceDiagnostics {
|
||||
uint64 project_id = 1;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -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!(
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue