@@ -140,6 +140,20 @@ impl FormatTrigger {
}
}
+#[derive(Debug)]
+pub struct DocumentDiagnosticsUpdate<'a, D> {
+ pub diagnostics: D,
+ pub result_id: Option<String>,
+ pub server_id: LanguageServerId,
+ pub disk_based_sources: Cow<'a, [String]>,
+}
+
+pub struct DocumentDiagnostics {
+ diagnostics: Vec<DiagnosticEntry<Unclipped<PointUtf16>>>,
+ document_abs_path: PathBuf,
+ version: Option<i32>,
+}
+
pub struct LocalLspStore {
weak: WeakEntity<LspStore>,
worktree_store: Entity<WorktreeStore>,
@@ -503,12 +517,16 @@ impl LocalLspStore {
adapter.process_diagnostics(&mut params, server_id, buffer);
}
- this.merge_diagnostics(
- server_id,
- params,
- None,
+ this.merge_lsp_diagnostics(
DiagnosticSourceKind::Pushed,
- &adapter.disk_based_diagnostic_sources,
+ vec![DocumentDiagnosticsUpdate {
+ server_id,
+ diagnostics: params,
+ result_id: None,
+ disk_based_sources: Cow::Borrowed(
+ &adapter.disk_based_diagnostic_sources,
+ ),
+ }],
|_, diagnostic, cx| match diagnostic.source_kind {
DiagnosticSourceKind::Other | DiagnosticSourceKind::Pushed => {
adapter.retain_old_diagnostic(diagnostic, cx)
@@ -3610,8 +3628,8 @@ pub enum LspStoreEvent {
RefreshInlayHints,
RefreshCodeLens,
DiagnosticsUpdated {
- language_server_id: LanguageServerId,
- path: ProjectPath,
+ server_id: LanguageServerId,
+ paths: Vec<ProjectPath>,
},
DiskBasedDiagnosticsStarted {
language_server_id: LanguageServerId,
@@ -4440,17 +4458,24 @@ impl LspStore {
pub(crate) fn send_diagnostic_summaries(&self, worktree: &mut Worktree) {
if let Some((client, downstream_project_id)) = self.downstream_client.clone() {
- if let Some(summaries) = self.diagnostic_summaries.get(&worktree.id()) {
- for (path, summaries) in summaries {
- for (&server_id, summary) in summaries {
- client
- .send(proto::UpdateDiagnosticSummary {
- project_id: downstream_project_id,
- worktree_id: worktree.id().to_proto(),
- summary: Some(summary.to_proto(server_id, path)),
- })
- .log_err();
- }
+ if let Some(diangostic_summaries) = self.diagnostic_summaries.get(&worktree.id()) {
+ let mut summaries =
+ diangostic_summaries
+ .into_iter()
+ .flat_map(|(path, summaries)| {
+ summaries
+ .into_iter()
+ .map(|(server_id, summary)| summary.to_proto(*server_id, path))
+ });
+ if let Some(summary) = summaries.next() {
+ client
+ .send(proto::UpdateDiagnosticSummary {
+ project_id: downstream_project_id,
+ worktree_id: worktree.id().to_proto(),
+ summary: Some(summary),
+ more_summaries: summaries.collect(),
+ })
+ .log_err();
}
}
}
@@ -6564,7 +6589,7 @@ impl LspStore {
&mut self,
buffer: Entity<Buffer>,
cx: &mut Context<Self>,
- ) -> Task<Result<Vec<LspPullDiagnostics>>> {
+ ) -> Task<Result<Option<Vec<LspPullDiagnostics>>>> {
let buffer_id = buffer.read(cx).remote_id();
if let Some((client, upstream_project_id)) = self.upstream_client() {
@@ -6575,7 +6600,7 @@ impl LspStore {
},
cx,
) {
- return Task::ready(Ok(Vec::new()));
+ return Task::ready(Ok(None));
}
let request_task = client.request(proto::MultiLspQuery {
buffer_id: buffer_id.to_proto(),
@@ -6593,7 +6618,7 @@ impl LspStore {
)),
});
cx.background_spawn(async move {
- Ok(request_task
+ let _proto_responses = request_task
.await?
.responses
.into_iter()
@@ -6606,8 +6631,11 @@ impl LspStore {
None
}
})
- .flat_map(GetDocumentDiagnostics::diagnostics_from_proto)
- .collect())
+ .collect::<Vec<_>>();
+ // Proto requests cause the diagnostics to be pulled from language server(s) on the local side
+ // and then, buffer state updated with the diagnostics received, which will be later propagated to the client.
+ // Do not attempt to further process the dummy responses here.
+ Ok(None)
})
} else {
let server_ids = buffer.update(cx, |buffer, cx| {
@@ -6635,7 +6663,7 @@ impl LspStore {
for diagnostics in join_all(pull_diagnostics).await {
responses.extend(diagnostics?);
}
- Ok(responses)
+ Ok(Some(responses))
})
}
}
@@ -6701,75 +6729,93 @@ impl LspStore {
buffer: Entity<Buffer>,
cx: &mut Context<Self>,
) -> Task<anyhow::Result<()>> {
- let buffer_id = buffer.read(cx).remote_id();
let diagnostics = self.pull_diagnostics(buffer, cx);
cx.spawn(async move |lsp_store, cx| {
- let diagnostics = diagnostics.await.context("pulling diagnostics")?;
+ let Some(diagnostics) = diagnostics.await.context("pulling diagnostics")? else {
+ return Ok(());
+ };
lsp_store.update(cx, |lsp_store, cx| {
if lsp_store.as_local().is_none() {
return;
}
- for diagnostics_set in diagnostics {
- let LspPullDiagnostics::Response {
- server_id,
- uri,
- diagnostics,
- } = diagnostics_set
- else {
- continue;
- };
-
- let adapter = lsp_store.language_server_adapter_for_id(server_id);
- let disk_based_sources = adapter
- .as_ref()
- .map(|adapter| adapter.disk_based_diagnostic_sources.as_slice())
- .unwrap_or(&[]);
- 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 {
+ let mut unchanged_buffers = HashSet::default();
+ let mut changed_buffers = HashSet::default();
+ let server_diagnostics_updates = diagnostics
+ .into_iter()
+ .filter_map(|diagnostics_set| match diagnostics_set {
+ LspPullDiagnostics::Response {
+ server_id,
+ uri,
diagnostics,
- result_id,
- } => {
- lsp_store
- .merge_diagnostics(
+ } => Some((server_id, uri, diagnostics)),
+ LspPullDiagnostics::Default => None,
+ })
+ .fold(
+ HashMap::default(),
+ |mut acc, (server_id, uri, diagnostics)| {
+ let (result_id, diagnostics) = match diagnostics {
+ PulledDiagnostics::Unchanged { result_id } => {
+ unchanged_buffers.insert(uri.clone());
+ (Some(result_id), Vec::new())
+ }
+ PulledDiagnostics::Changed {
+ result_id,
+ diagnostics,
+ } => {
+ changed_buffers.insert(uri.clone());
+ (result_id, diagnostics)
+ }
+ };
+ let disk_based_sources = Cow::Owned(
+ lsp_store
+ .language_server_adapter_for_id(server_id)
+ .as_ref()
+ .map(|adapter| adapter.disk_based_diagnostic_sources.as_slice())
+ .unwrap_or(&[])
+ .to_vec(),
+ );
+ acc.entry(server_id).or_insert_with(Vec::new).push(
+ DocumentDiagnosticsUpdate {
server_id,
- lsp::PublishDiagnosticsParams {
- uri: uri.clone(),
+ diagnostics: lsp::PublishDiagnosticsParams {
+ uri,
diagnostics,
version: None,
},
result_id,
- DiagnosticSourceKind::Pulled,
disk_based_sources,
- |buffer, old_diagnostic, _| match old_diagnostic.source_kind {
- DiagnosticSourceKind::Pulled => {
- buffer.remote_id() != buffer_id
- }
- DiagnosticSourceKind::Other
- | DiagnosticSourceKind::Pushed => true,
- },
- cx,
- )
- .log_err();
- }
- }
+ },
+ );
+ acc
+ },
+ );
+
+ for diagnostic_updates in server_diagnostics_updates.into_values() {
+ lsp_store
+ .merge_lsp_diagnostics(
+ DiagnosticSourceKind::Pulled,
+ diagnostic_updates,
+ |buffer, old_diagnostic, cx| {
+ File::from_dyn(buffer.file())
+ .and_then(|file| {
+ let abs_path = file.as_local()?.abs_path(cx);
+ lsp::Url::from_file_path(abs_path).ok()
+ })
+ .is_none_or(|buffer_uri| {
+ unchanged_buffers.contains(&buffer_uri)
+ || match old_diagnostic.source_kind {
+ DiagnosticSourceKind::Pulled => {
+ !changed_buffers.contains(&buffer_uri)
+ }
+ DiagnosticSourceKind::Other
+ | DiagnosticSourceKind::Pushed => true,
+ }
+ })
+ },
+ cx,
+ )
+ .log_err();
}
})
})
@@ -7791,88 +7837,135 @@ impl LspStore {
cx: &mut Context<Self>,
) -> anyhow::Result<()> {
self.merge_diagnostic_entries(
- server_id,
- abs_path,
- result_id,
- version,
- diagnostics,
+ vec![DocumentDiagnosticsUpdate {
+ diagnostics: DocumentDiagnostics {
+ diagnostics,
+ document_abs_path: abs_path,
+ version,
+ },
+ result_id,
+ server_id,
+ disk_based_sources: Cow::Borrowed(&[]),
+ }],
|_, _, _| false,
cx,
)?;
Ok(())
}
- pub fn merge_diagnostic_entries(
+ pub fn merge_diagnostic_entries<'a>(
&mut self,
- server_id: LanguageServerId,
- abs_path: PathBuf,
- result_id: Option<String>,
- version: Option<i32>,
- mut diagnostics: Vec<DiagnosticEntry<Unclipped<PointUtf16>>>,
- filter: impl Fn(&Buffer, &Diagnostic, &App) -> bool + Clone,
+ diagnostic_updates: Vec<DocumentDiagnosticsUpdate<'a, DocumentDiagnostics>>,
+ merge: impl Fn(&Buffer, &Diagnostic, &App) -> bool + Clone,
cx: &mut Context<Self>,
) -> anyhow::Result<()> {
- let Some((worktree, relative_path)) =
- self.worktree_store.read(cx).find_worktree(&abs_path, cx)
- else {
- log::warn!("skipping diagnostics update, no worktree found for path {abs_path:?}");
- return Ok(());
- };
+ let mut diagnostics_summary = None::<proto::UpdateDiagnosticSummary>;
+ let mut updated_diagnostics_paths = HashMap::default();
+ for mut update in diagnostic_updates {
+ let abs_path = &update.diagnostics.document_abs_path;
+ let server_id = update.server_id;
+ let Some((worktree, relative_path)) =
+ self.worktree_store.read(cx).find_worktree(abs_path, cx)
+ else {
+ log::warn!("skipping diagnostics update, no worktree found for path {abs_path:?}");
+ return Ok(());
+ };
- let project_path = ProjectPath {
- worktree_id: worktree.read(cx).id(),
- path: relative_path.into(),
- };
+ let worktree_id = worktree.read(cx).id();
+ let project_path = ProjectPath {
+ worktree_id,
+ path: relative_path.into(),
+ };
- if let Some(buffer_handle) = self.buffer_store.read(cx).get_by_path(&project_path) {
- let snapshot = buffer_handle.read(cx).snapshot();
- let buffer = buffer_handle.read(cx);
- let reused_diagnostics = buffer
- .get_diagnostics(server_id)
- .into_iter()
- .flat_map(|diag| {
- diag.iter()
- .filter(|v| filter(buffer, &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<_>>();
+ if let Some(buffer_handle) = self.buffer_store.read(cx).get_by_path(&project_path) {
+ let snapshot = buffer_handle.read(cx).snapshot();
+ let buffer = buffer_handle.read(cx);
+ let reused_diagnostics = buffer
+ .get_diagnostics(server_id)
+ .into_iter()
+ .flat_map(|diag| {
+ diag.iter()
+ .filter(|v| merge(buffer, &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()
- .context("cannot merge diagnostics on a remote LspStore")?
- .update_buffer_diagnostics(
- &buffer_handle,
+ self.as_local_mut()
+ .context("cannot merge diagnostics on a remote LspStore")?
+ .update_buffer_diagnostics(
+ &buffer_handle,
+ server_id,
+ update.result_id,
+ update.diagnostics.version,
+ update.diagnostics.diagnostics.clone(),
+ reused_diagnostics.clone(),
+ cx,
+ )?;
+
+ update.diagnostics.diagnostics.extend(reused_diagnostics);
+ }
+
+ let updated = worktree.update(cx, |worktree, cx| {
+ self.update_worktree_diagnostics(
+ worktree.id(),
server_id,
- result_id,
- version,
- diagnostics.clone(),
- reused_diagnostics.clone(),
+ project_path.path.clone(),
+ update.diagnostics.diagnostics,
cx,
- )?;
-
- diagnostics.extend(reused_diagnostics);
+ )
+ })?;
+ match updated {
+ ControlFlow::Continue(new_summary) => {
+ if let Some((project_id, new_summary)) = new_summary {
+ match &mut diagnostics_summary {
+ Some(diagnostics_summary) => {
+ diagnostics_summary
+ .more_summaries
+ .push(proto::DiagnosticSummary {
+ path: project_path.path.as_ref().to_proto(),
+ language_server_id: server_id.0 as u64,
+ error_count: new_summary.error_count,
+ warning_count: new_summary.warning_count,
+ })
+ }
+ None => {
+ diagnostics_summary = Some(proto::UpdateDiagnosticSummary {
+ project_id: project_id,
+ worktree_id: worktree_id.to_proto(),
+ summary: Some(proto::DiagnosticSummary {
+ path: project_path.path.as_ref().to_proto(),
+ language_server_id: server_id.0 as u64,
+ error_count: new_summary.error_count,
+ warning_count: new_summary.warning_count,
+ }),
+ more_summaries: Vec::new(),
+ })
+ }
+ }
+ }
+ updated_diagnostics_paths
+ .entry(server_id)
+ .or_insert_with(Vec::new)
+ .push(project_path);
+ }
+ ControlFlow::Break(()) => {}
+ }
}
- let updated = worktree.update(cx, |worktree, cx| {
- self.update_worktree_diagnostics(
- worktree.id(),
- server_id,
- project_path.path.clone(),
- diagnostics,
- cx,
- )
- })?;
- if updated {
- cx.emit(LspStoreEvent::DiagnosticsUpdated {
- language_server_id: server_id,
- path: project_path,
- })
+ if let Some((diagnostics_summary, (downstream_client, _))) =
+ diagnostics_summary.zip(self.downstream_client.as_ref())
+ {
+ downstream_client.send(diagnostics_summary).log_err();
+ }
+ for (server_id, paths) in updated_diagnostics_paths {
+ cx.emit(LspStoreEvent::DiagnosticsUpdated { server_id, paths });
}
Ok(())
}
@@ -7881,10 +7974,10 @@ impl LspStore {
&mut self,
worktree_id: WorktreeId,
server_id: LanguageServerId,
- worktree_path: Arc<Path>,
+ path_in_worktree: Arc<Path>,
diagnostics: Vec<DiagnosticEntry<Unclipped<PointUtf16>>>,
_: &mut Context<Worktree>,
- ) -> Result<bool> {
+ ) -> Result<ControlFlow<(), Option<(u64, proto::DiagnosticSummary)>>> {
let local = match &mut self.mode {
LspStoreMode::Local(local_lsp_store) => local_lsp_store,
_ => anyhow::bail!("update_worktree_diagnostics called on remote"),
@@ -7892,7 +7985,9 @@ impl LspStore {
let summaries_for_tree = self.diagnostic_summaries.entry(worktree_id).or_default();
let diagnostics_for_tree = local.diagnostics.entry(worktree_id).or_default();
- let summaries_by_server_id = summaries_for_tree.entry(worktree_path.clone()).or_default();
+ let summaries_by_server_id = summaries_for_tree
+ .entry(path_in_worktree.clone())
+ .or_default();
let old_summary = summaries_by_server_id
.remove(&server_id)
@@ -7900,18 +7995,19 @@ impl LspStore {
let new_summary = DiagnosticSummary::new(&diagnostics);
if new_summary.is_empty() {
- if let Some(diagnostics_by_server_id) = diagnostics_for_tree.get_mut(&worktree_path) {
+ if let Some(diagnostics_by_server_id) = diagnostics_for_tree.get_mut(&path_in_worktree)
+ {
if let Ok(ix) = diagnostics_by_server_id.binary_search_by_key(&server_id, |e| e.0) {
diagnostics_by_server_id.remove(ix);
}
if diagnostics_by_server_id.is_empty() {
- diagnostics_for_tree.remove(&worktree_path);
+ diagnostics_for_tree.remove(&path_in_worktree);
}
}
} else {
summaries_by_server_id.insert(server_id, new_summary);
let diagnostics_by_server_id = diagnostics_for_tree
- .entry(worktree_path.clone())
+ .entry(path_in_worktree.clone())
.or_default();
match diagnostics_by_server_id.binary_search_by_key(&server_id, |e| e.0) {
Ok(ix) => {
@@ -7924,23 +8020,22 @@ impl LspStore {
}
if !old_summary.is_empty() || !new_summary.is_empty() {
- if let Some((downstream_client, project_id)) = &self.downstream_client {
- downstream_client
- .send(proto::UpdateDiagnosticSummary {
- project_id: *project_id,
- worktree_id: worktree_id.to_proto(),
- summary: Some(proto::DiagnosticSummary {
- path: worktree_path.to_proto(),
- language_server_id: server_id.0 as u64,
- error_count: new_summary.error_count as u32,
- warning_count: new_summary.warning_count as u32,
- }),
- })
- .log_err();
+ if let Some((_, project_id)) = &self.downstream_client {
+ Ok(ControlFlow::Continue(Some((
+ *project_id,
+ proto::DiagnosticSummary {
+ path: path_in_worktree.to_proto(),
+ language_server_id: server_id.0 as u64,
+ error_count: new_summary.error_count as u32,
+ warning_count: new_summary.warning_count as u32,
+ },
+ ))))
+ } else {
+ Ok(ControlFlow::Continue(None))
}
+ } else {
+ Ok(ControlFlow::Break(()))
}
-
- Ok(!old_summary.is_empty() || !new_summary.is_empty())
}
pub fn open_buffer_for_symbol(
@@ -8793,23 +8888,30 @@ impl LspStore {
envelope: TypedEnvelope<proto::UpdateDiagnosticSummary>,
mut cx: AsyncApp,
) -> Result<()> {
- this.update(&mut cx, |this, cx| {
+ this.update(&mut cx, |lsp_store, cx| {
let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
- if let Some(message) = envelope.payload.summary {
+ let mut updated_diagnostics_paths = HashMap::default();
+ let mut diagnostics_summary = None::<proto::UpdateDiagnosticSummary>;
+ for message_summary in envelope
+ .payload
+ .summary
+ .into_iter()
+ .chain(envelope.payload.more_summaries)
+ {
let project_path = ProjectPath {
worktree_id,
- path: Arc::<Path>::from_proto(message.path),
+ path: Arc::<Path>::from_proto(message_summary.path),
};
let path = project_path.path.clone();
- let server_id = LanguageServerId(message.language_server_id as usize);
+ let server_id = LanguageServerId(message_summary.language_server_id as usize);
let summary = DiagnosticSummary {
- error_count: message.error_count as usize,
- warning_count: message.warning_count as usize,
+ error_count: message_summary.error_count as usize,
+ warning_count: message_summary.warning_count as usize,
};
if summary.is_empty() {
if let Some(worktree_summaries) =
- this.diagnostic_summaries.get_mut(&worktree_id)
+ lsp_store.diagnostic_summaries.get_mut(&worktree_id)
{
if let Some(summaries) = worktree_summaries.get_mut(&path) {
summaries.remove(&server_id);
@@ -8819,31 +8921,55 @@ impl LspStore {
}
}
} else {
- this.diagnostic_summaries
+ lsp_store
+ .diagnostic_summaries
.entry(worktree_id)
.or_default()
.entry(path)
.or_default()
.insert(server_id, summary);
}
- if let Some((downstream_client, project_id)) = &this.downstream_client {
- downstream_client
- .send(proto::UpdateDiagnosticSummary {
- project_id: *project_id,
- worktree_id: worktree_id.to_proto(),
- summary: Some(proto::DiagnosticSummary {
- path: project_path.path.as_ref().to_proto(),
- language_server_id: server_id.0 as u64,
- error_count: summary.error_count as u32,
- warning_count: summary.warning_count as u32,
- }),
- })
- .log_err();
+
+ if let Some((_, project_id)) = &lsp_store.downstream_client {
+ match &mut diagnostics_summary {
+ Some(diagnostics_summary) => {
+ diagnostics_summary
+ .more_summaries
+ .push(proto::DiagnosticSummary {
+ path: project_path.path.as_ref().to_proto(),
+ language_server_id: server_id.0 as u64,
+ error_count: summary.error_count as u32,
+ warning_count: summary.warning_count as u32,
+ })
+ }
+ None => {
+ diagnostics_summary = Some(proto::UpdateDiagnosticSummary {
+ project_id: *project_id,
+ worktree_id: worktree_id.to_proto(),
+ summary: Some(proto::DiagnosticSummary {
+ path: project_path.path.as_ref().to_proto(),
+ language_server_id: server_id.0 as u64,
+ error_count: summary.error_count as u32,
+ warning_count: summary.warning_count as u32,
+ }),
+ more_summaries: Vec::new(),
+ })
+ }
+ }
}
- cx.emit(LspStoreEvent::DiagnosticsUpdated {
- language_server_id: LanguageServerId(message.language_server_id as usize),
- path: project_path,
- });
+ updated_diagnostics_paths
+ .entry(server_id)
+ .or_insert_with(Vec::new)
+ .push(project_path);
+ }
+
+ if let Some((diagnostics_summary, (downstream_client, _))) =
+ diagnostics_summary.zip(lsp_store.downstream_client.as_ref())
+ {
+ downstream_client.send(diagnostics_summary).log_err();
+ }
+ for (server_id, paths) in updated_diagnostics_paths {
+ cx.emit(LspStoreEvent::DiagnosticsUpdated { server_id, paths });
}
Ok(())
})?
@@ -10361,6 +10487,7 @@ impl LspStore {
error_count: 0,
warning_count: 0,
}),
+ more_summaries: Vec::new(),
})
.log_err();
}
@@ -10649,52 +10776,80 @@ impl LspStore {
)
}
+ #[cfg(any(test, feature = "test-support"))]
pub fn update_diagnostics(
&mut self,
- language_server_id: LanguageServerId,
- params: lsp::PublishDiagnosticsParams,
+ server_id: LanguageServerId,
+ diagnostics: lsp::PublishDiagnosticsParams,
result_id: Option<String>,
source_kind: DiagnosticSourceKind,
disk_based_sources: &[String],
cx: &mut Context<Self>,
) -> Result<()> {
- self.merge_diagnostics(
- language_server_id,
- params,
- result_id,
+ self.merge_lsp_diagnostics(
source_kind,
- disk_based_sources,
+ vec![DocumentDiagnosticsUpdate {
+ diagnostics,
+ result_id,
+ server_id,
+ disk_based_sources: Cow::Borrowed(disk_based_sources),
+ }],
|_, _, _| false,
cx,
)
}
- pub fn merge_diagnostics(
+ pub fn merge_lsp_diagnostics(
&mut self,
- language_server_id: LanguageServerId,
- mut params: lsp::PublishDiagnosticsParams,
- result_id: Option<String>,
source_kind: DiagnosticSourceKind,
- disk_based_sources: &[String],
- filter: impl Fn(&Buffer, &Diagnostic, &App) -> bool + Clone,
+ lsp_diagnostics: Vec<DocumentDiagnosticsUpdate<lsp::PublishDiagnosticsParams>>,
+ merge: impl Fn(&Buffer, &Diagnostic, &App) -> bool + Clone,
cx: &mut Context<Self>,
) -> Result<()> {
anyhow::ensure!(self.mode.is_local(), "called update_diagnostics on remote");
- let abs_path = params
- .uri
- .to_file_path()
- .map_err(|()| anyhow!("URI is not a file"))?;
+ let updates = lsp_diagnostics
+ .into_iter()
+ .filter_map(|update| {
+ let abs_path = update.diagnostics.uri.to_file_path().ok()?;
+ Some(DocumentDiagnosticsUpdate {
+ diagnostics: self.lsp_to_document_diagnostics(
+ abs_path,
+ source_kind,
+ update.server_id,
+ update.diagnostics,
+ &update.disk_based_sources,
+ ),
+ result_id: update.result_id,
+ server_id: update.server_id,
+ disk_based_sources: update.disk_based_sources,
+ })
+ })
+ .collect();
+ self.merge_diagnostic_entries(updates, merge, cx)?;
+ Ok(())
+ }
+
+ fn lsp_to_document_diagnostics(
+ &mut self,
+ document_abs_path: PathBuf,
+ source_kind: DiagnosticSourceKind,
+ server_id: LanguageServerId,
+ mut lsp_diagnostics: lsp::PublishDiagnosticsParams,
+ disk_based_sources: &[String],
+ ) -> DocumentDiagnostics {
let mut diagnostics = Vec::default();
let mut primary_diagnostic_group_ids = HashMap::default();
let mut sources_by_group_id = HashMap::default();
let mut supporting_diagnostics = HashMap::default();
- let adapter = self.language_server_adapter_for_id(language_server_id);
+ let adapter = self.language_server_adapter_for_id(server_id);
// Ensure that primary diagnostics are always the most severe
- params.diagnostics.sort_by_key(|item| item.severity);
+ lsp_diagnostics
+ .diagnostics
+ .sort_by_key(|item| item.severity);
- for diagnostic in ¶ms.diagnostics {
+ for diagnostic in &lsp_diagnostics.diagnostics {
let source = diagnostic.source.as_ref();
let range = range_from_lsp(diagnostic.range);
let is_supporting = diagnostic
@@ -10716,7 +10871,7 @@ impl LspStore {
.map_or(false, |tags| tags.contains(&DiagnosticTag::UNNECESSARY));
let underline = self
- .language_server_adapter_for_id(language_server_id)
+ .language_server_adapter_for_id(server_id)
.map_or(true, |adapter| adapter.underline_diagnostic(diagnostic));
if is_supporting {
@@ -10758,7 +10913,7 @@ impl LspStore {
});
if let Some(infos) = &diagnostic.related_information {
for info in infos {
- if info.location.uri == params.uri && !info.message.is_empty() {
+ if info.location.uri == lsp_diagnostics.uri && !info.message.is_empty() {
let range = range_from_lsp(info.location.range);
diagnostics.push(DiagnosticEntry {
range,
@@ -10806,16 +10961,11 @@ impl LspStore {
}
}
- self.merge_diagnostic_entries(
- language_server_id,
- abs_path,
- result_id,
- params.version,
+ DocumentDiagnostics {
diagnostics,
- filter,
- cx,
- )?;
- Ok(())
+ document_abs_path,
+ version: lsp_diagnostics.version,
+ }
}
fn insert_newly_running_language_server(
@@ -11571,67 +11721,84 @@ impl LspStore {
) {
let workspace_diagnostics =
GetDocumentDiagnostics::deserialize_workspace_diagnostics_report(report, server_id);
- for workspace_diagnostics in workspace_diagnostics {
- let LspPullDiagnostics::Response {
- server_id,
- uri,
- diagnostics,
- } = workspace_diagnostics.diagnostics
- else {
- continue;
- };
-
- let adapter = self.language_server_adapter_for_id(server_id);
- let disk_based_sources = adapter
- .as_ref()
- .map(|adapter| adapter.disk_based_diagnostic_sources.as_slice())
- .unwrap_or(&[]);
-
- match diagnostics {
- PulledDiagnostics::Unchanged { result_id } => {
- self.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,
- } => {
- self.merge_diagnostics(
+ let mut unchanged_buffers = HashSet::default();
+ let mut changed_buffers = HashSet::default();
+ let workspace_diagnostics_updates = workspace_diagnostics
+ .into_iter()
+ .filter_map(
+ |workspace_diagnostics| match workspace_diagnostics.diagnostics {
+ LspPullDiagnostics::Response {
server_id,
- lsp::PublishDiagnosticsParams {
- uri: uri.clone(),
+ uri,
+ diagnostics,
+ } => Some((server_id, uri, diagnostics, workspace_diagnostics.version)),
+ LspPullDiagnostics::Default => None,
+ },
+ )
+ .fold(
+ HashMap::default(),
+ |mut acc, (server_id, uri, diagnostics, version)| {
+ let (result_id, diagnostics) = match diagnostics {
+ PulledDiagnostics::Unchanged { result_id } => {
+ unchanged_buffers.insert(uri.clone());
+ (Some(result_id), Vec::new())
+ }
+ PulledDiagnostics::Changed {
+ result_id,
diagnostics,
- version: workspace_diagnostics.version,
- },
- result_id,
- DiagnosticSourceKind::Pulled,
- disk_based_sources,
- |buffer, old_diagnostic, cx| match old_diagnostic.source_kind {
- DiagnosticSourceKind::Pulled => {
- let buffer_url = File::from_dyn(buffer.file())
- .map(|f| f.abs_path(cx))
- .and_then(|abs_path| file_path_to_lsp_url(&abs_path).ok());
- buffer_url.is_none_or(|buffer_url| buffer_url != uri)
- }
- DiagnosticSourceKind::Other | DiagnosticSourceKind::Pushed => true,
- },
- cx,
- )
- .log_err();
- }
- }
+ } => {
+ changed_buffers.insert(uri.clone());
+ (result_id, diagnostics)
+ }
+ };
+ let disk_based_sources = Cow::Owned(
+ self.language_server_adapter_for_id(server_id)
+ .as_ref()
+ .map(|adapter| adapter.disk_based_diagnostic_sources.as_slice())
+ .unwrap_or(&[])
+ .to_vec(),
+ );
+ acc.entry(server_id)
+ .or_insert_with(Vec::new)
+ .push(DocumentDiagnosticsUpdate {
+ server_id,
+ diagnostics: lsp::PublishDiagnosticsParams {
+ uri,
+ diagnostics,
+ version,
+ },
+ result_id,
+ disk_based_sources,
+ });
+ acc
+ },
+ );
+
+ for diagnostic_updates in workspace_diagnostics_updates.into_values() {
+ self.merge_lsp_diagnostics(
+ DiagnosticSourceKind::Pulled,
+ diagnostic_updates,
+ |buffer, old_diagnostic, cx| {
+ File::from_dyn(buffer.file())
+ .and_then(|file| {
+ let abs_path = file.as_local()?.abs_path(cx);
+ lsp::Url::from_file_path(abs_path).ok()
+ })
+ .is_none_or(|buffer_uri| {
+ unchanged_buffers.contains(&buffer_uri)
+ || match old_diagnostic.source_kind {
+ DiagnosticSourceKind::Pulled => {
+ !changed_buffers.contains(&buffer_uri)
+ }
+ DiagnosticSourceKind::Other | DiagnosticSourceKind::Pushed => {
+ true
+ }
+ }
+ })
+ },
+ cx,
+ )
+ .log_err();
}
}
}
@@ -74,9 +74,9 @@ use gpui::{
Task, WeakEntity, Window,
};
use language::{
- Buffer, BufferEvent, Capability, CodeLabel, CursorShape, DiagnosticSourceKind, Language,
- LanguageName, LanguageRegistry, PointUtf16, ToOffset, ToPointUtf16, Toolchain, ToolchainList,
- Transaction, Unclipped, language_settings::InlayHintKind, proto::split_operations,
+ Buffer, BufferEvent, Capability, CodeLabel, CursorShape, Language, LanguageName,
+ LanguageRegistry, PointUtf16, ToOffset, ToPointUtf16, Toolchain, ToolchainList, Transaction,
+ Unclipped, language_settings::InlayHintKind, proto::split_operations,
};
use lsp::{
CodeActionKind, CompletionContext, CompletionItemKind, DocumentHighlightKind, InsertTextMode,
@@ -305,7 +305,7 @@ pub enum Event {
language_server_id: LanguageServerId,
},
DiagnosticsUpdated {
- path: ProjectPath,
+ paths: Vec<ProjectPath>,
language_server_id: LanguageServerId,
},
RemoteIdChanged(Option<u64>),
@@ -2895,18 +2895,17 @@ impl Project {
cx: &mut Context<Self>,
) {
match event {
- LspStoreEvent::DiagnosticsUpdated {
- language_server_id,
- path,
- } => cx.emit(Event::DiagnosticsUpdated {
- path: path.clone(),
- language_server_id: *language_server_id,
- }),
- LspStoreEvent::LanguageServerAdded(language_server_id, name, worktree_id) => cx.emit(
- Event::LanguageServerAdded(*language_server_id, name.clone(), *worktree_id),
+ LspStoreEvent::DiagnosticsUpdated { server_id, paths } => {
+ cx.emit(Event::DiagnosticsUpdated {
+ paths: paths.clone(),
+ language_server_id: *server_id,
+ })
+ }
+ LspStoreEvent::LanguageServerAdded(server_id, name, worktree_id) => cx.emit(
+ Event::LanguageServerAdded(*server_id, name.clone(), *worktree_id),
),
- LspStoreEvent::LanguageServerRemoved(language_server_id) => {
- cx.emit(Event::LanguageServerRemoved(*language_server_id))
+ LspStoreEvent::LanguageServerRemoved(server_id) => {
+ cx.emit(Event::LanguageServerRemoved(*server_id))
}
LspStoreEvent::LanguageServerLog(server_id, log_type, string) => cx.emit(
Event::LanguageServerLog(*server_id, log_type.clone(), string.clone()),
@@ -3829,27 +3828,6 @@ impl Project {
})
}
- pub fn update_diagnostics(
- &mut self,
- language_server_id: LanguageServerId,
- source_kind: DiagnosticSourceKind,
- result_id: Option<String>,
- params: lsp::PublishDiagnosticsParams,
- disk_based_sources: &[String],
- cx: &mut Context<Self>,
- ) -> Result<(), anyhow::Error> {
- self.lsp_store.update(cx, |lsp_store, cx| {
- lsp_store.update_diagnostics(
- language_server_id,
- params,
- result_id,
- source_kind,
- disk_based_sources,
- cx,
- )
- })
- }
-
pub fn search(&mut self, query: SearchQuery, cx: &mut Context<Self>) -> Receiver<SearchResult> {
let (result_tx, result_rx) = smol::channel::unbounded();