diagnostics: Improve performance with large # of diagnostics (#20189)
Related to: https://github.com/zed-industries/zed/issues/19022 Release Notes: - Improve editor performance with large # of diagnostics. --------- Co-authored-by: Conrad <conrad@zed.dev> Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
This commit is contained in:
parent
77de20c23a
commit
dc5fad52a3
5 changed files with 65 additions and 70 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -3491,7 +3491,6 @@ dependencies = [
|
||||||
"ctor",
|
"ctor",
|
||||||
"editor",
|
"editor",
|
||||||
"env_logger 0.11.5",
|
"env_logger 0.11.5",
|
||||||
"futures 0.3.30",
|
|
||||||
"gpui",
|
"gpui",
|
||||||
"language",
|
"language",
|
||||||
"log",
|
"log",
|
||||||
|
|
|
@ -18,7 +18,6 @@ collections.workspace = true
|
||||||
ctor.workspace = true
|
ctor.workspace = true
|
||||||
editor.workspace = true
|
editor.workspace = true
|
||||||
env_logger.workspace = true
|
env_logger.workspace = true
|
||||||
futures.workspace = true
|
|
||||||
gpui.workspace = true
|
gpui.workspace = true
|
||||||
language.workspace = true
|
language.workspace = true
|
||||||
log.workspace = true
|
log.workspace = true
|
||||||
|
|
|
@ -14,10 +14,6 @@ use editor::{
|
||||||
scroll::Autoscroll,
|
scroll::Autoscroll,
|
||||||
Editor, EditorEvent, ExcerptId, ExcerptRange, MultiBuffer, ToOffset,
|
Editor, EditorEvent, ExcerptId, ExcerptRange, MultiBuffer, ToOffset,
|
||||||
};
|
};
|
||||||
use futures::{
|
|
||||||
channel::mpsc::{self, UnboundedSender},
|
|
||||||
StreamExt as _,
|
|
||||||
};
|
|
||||||
use gpui::{
|
use gpui::{
|
||||||
actions, div, svg, AnyElement, AnyView, AppContext, Context, EventEmitter, FocusHandle,
|
actions, div, svg, AnyElement, AnyView, AppContext, Context, EventEmitter, FocusHandle,
|
||||||
FocusableView, HighlightStyle, InteractiveElement, IntoElement, Model, ParentElement, Render,
|
FocusableView, HighlightStyle, InteractiveElement, IntoElement, Model, ParentElement, Render,
|
||||||
|
@ -62,11 +58,10 @@ struct ProjectDiagnosticsEditor {
|
||||||
summary: DiagnosticSummary,
|
summary: DiagnosticSummary,
|
||||||
excerpts: Model<MultiBuffer>,
|
excerpts: Model<MultiBuffer>,
|
||||||
path_states: Vec<PathState>,
|
path_states: Vec<PathState>,
|
||||||
paths_to_update: BTreeSet<(ProjectPath, LanguageServerId)>,
|
paths_to_update: BTreeSet<(ProjectPath, Option<LanguageServerId>)>,
|
||||||
include_warnings: bool,
|
include_warnings: bool,
|
||||||
context: u32,
|
context: u32,
|
||||||
update_paths_tx: UnboundedSender<(ProjectPath, Option<LanguageServerId>)>,
|
update_excerpts_task: Option<Task<Result<()>>>,
|
||||||
_update_excerpts_task: Task<Result<()>>,
|
|
||||||
_subscription: Subscription,
|
_subscription: Subscription,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -129,14 +124,14 @@ impl ProjectDiagnosticsEditor {
|
||||||
}
|
}
|
||||||
project::Event::DiskBasedDiagnosticsFinished { language_server_id } => {
|
project::Event::DiskBasedDiagnosticsFinished { language_server_id } => {
|
||||||
log::debug!("disk based diagnostics finished for server {language_server_id}");
|
log::debug!("disk based diagnostics finished for server {language_server_id}");
|
||||||
this.enqueue_update_stale_excerpts(Some(*language_server_id));
|
this.update_stale_excerpts(cx);
|
||||||
}
|
}
|
||||||
project::Event::DiagnosticsUpdated {
|
project::Event::DiagnosticsUpdated {
|
||||||
language_server_id,
|
language_server_id,
|
||||||
path,
|
path,
|
||||||
} => {
|
} => {
|
||||||
this.paths_to_update
|
this.paths_to_update
|
||||||
.insert((path.clone(), *language_server_id));
|
.insert((path.clone(), Some(*language_server_id)));
|
||||||
this.summary = project.read(cx).diagnostic_summary(false, cx);
|
this.summary = project.read(cx).diagnostic_summary(false, cx);
|
||||||
cx.emit(EditorEvent::TitleChanged);
|
cx.emit(EditorEvent::TitleChanged);
|
||||||
|
|
||||||
|
@ -144,7 +139,7 @@ impl ProjectDiagnosticsEditor {
|
||||||
log::debug!("diagnostics updated for server {language_server_id}, path {path:?}. recording change");
|
log::debug!("diagnostics updated for server {language_server_id}, path {path:?}. recording change");
|
||||||
} else {
|
} else {
|
||||||
log::debug!("diagnostics updated for server {language_server_id}, path {path:?}. updating excerpts");
|
log::debug!("diagnostics updated for server {language_server_id}, path {path:?}. updating excerpts");
|
||||||
this.enqueue_update_stale_excerpts(Some(*language_server_id));
|
this.update_stale_excerpts(cx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
|
@ -171,14 +166,12 @@ impl ProjectDiagnosticsEditor {
|
||||||
cx.focus(&this.focus_handle);
|
cx.focus(&this.focus_handle);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
EditorEvent::Blurred => this.enqueue_update_stale_excerpts(None),
|
EditorEvent::Blurred => this.update_stale_excerpts(cx),
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.detach();
|
.detach();
|
||||||
|
|
||||||
let (update_excerpts_tx, mut update_excerpts_rx) = mpsc::unbounded();
|
|
||||||
|
|
||||||
let project = project_handle.read(cx);
|
let project = project_handle.read(cx);
|
||||||
let mut this = Self {
|
let mut this = Self {
|
||||||
project: project_handle.clone(),
|
project: project_handle.clone(),
|
||||||
|
@ -191,27 +184,45 @@ impl ProjectDiagnosticsEditor {
|
||||||
path_states: Default::default(),
|
path_states: Default::default(),
|
||||||
paths_to_update: Default::default(),
|
paths_to_update: Default::default(),
|
||||||
include_warnings: ProjectDiagnosticsSettings::get_global(cx).include_warnings,
|
include_warnings: ProjectDiagnosticsSettings::get_global(cx).include_warnings,
|
||||||
update_paths_tx: update_excerpts_tx,
|
update_excerpts_task: None,
|
||||||
_update_excerpts_task: cx.spawn(move |this, mut cx| async move {
|
|
||||||
while let Some((path, language_server_id)) = update_excerpts_rx.next().await {
|
|
||||||
if let Some(buffer) = project_handle
|
|
||||||
.update(&mut cx, |project, cx| project.open_buffer(path.clone(), cx))?
|
|
||||||
.await
|
|
||||||
.log_err()
|
|
||||||
{
|
|
||||||
this.update(&mut cx, |this, cx| {
|
|
||||||
this.update_excerpts(path, language_server_id, buffer, cx);
|
|
||||||
})?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
anyhow::Ok(())
|
|
||||||
}),
|
|
||||||
_subscription: project_event_subscription,
|
_subscription: project_event_subscription,
|
||||||
};
|
};
|
||||||
this.enqueue_update_all_excerpts(cx);
|
this.update_all_excerpts(cx);
|
||||||
this
|
this
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn update_stale_excerpts(&mut self, cx: &mut ViewContext<Self>) {
|
||||||
|
if self.update_excerpts_task.is_some() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let project_handle = self.project.clone();
|
||||||
|
self.update_excerpts_task = Some(cx.spawn(|this, mut cx| async move {
|
||||||
|
loop {
|
||||||
|
let Some((path, language_server_id)) = this.update(&mut cx, |this, _| {
|
||||||
|
let Some((path, language_server_id)) = this.paths_to_update.pop_first() else {
|
||||||
|
this.update_excerpts_task.take();
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
Some((path, language_server_id))
|
||||||
|
})?
|
||||||
|
else {
|
||||||
|
break;
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(buffer) = project_handle
|
||||||
|
.update(&mut cx, |project, cx| project.open_buffer(path.clone(), cx))?
|
||||||
|
.await
|
||||||
|
.log_err()
|
||||||
|
{
|
||||||
|
this.update(&mut cx, |this, cx| {
|
||||||
|
this.update_excerpts(path, language_server_id, buffer, cx);
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
fn new(
|
fn new(
|
||||||
project_handle: Model<Project>,
|
project_handle: Model<Project>,
|
||||||
workspace: WeakView<Workspace>,
|
workspace: WeakView<Workspace>,
|
||||||
|
@ -239,7 +250,7 @@ impl ProjectDiagnosticsEditor {
|
||||||
|
|
||||||
fn toggle_warnings(&mut self, _: &ToggleWarnings, cx: &mut ViewContext<Self>) {
|
fn toggle_warnings(&mut self, _: &ToggleWarnings, cx: &mut ViewContext<Self>) {
|
||||||
self.include_warnings = !self.include_warnings;
|
self.include_warnings = !self.include_warnings;
|
||||||
self.enqueue_update_all_excerpts(cx);
|
self.update_all_excerpts(cx);
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -251,37 +262,28 @@ impl ProjectDiagnosticsEditor {
|
||||||
|
|
||||||
fn focus_out(&mut self, cx: &mut ViewContext<Self>) {
|
fn focus_out(&mut self, cx: &mut ViewContext<Self>) {
|
||||||
if !self.focus_handle.is_focused(cx) && !self.editor.focus_handle(cx).is_focused(cx) {
|
if !self.focus_handle.is_focused(cx) && !self.editor.focus_handle(cx).is_focused(cx) {
|
||||||
self.enqueue_update_stale_excerpts(None);
|
self.update_stale_excerpts(cx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Enqueue an update of all excerpts. Updates all paths that either
|
/// Enqueue an update of all excerpts. Updates all paths that either
|
||||||
/// currently have diagnostics or are currently present in this view.
|
/// currently have diagnostics or are currently present in this view.
|
||||||
fn enqueue_update_all_excerpts(&mut self, cx: &mut ViewContext<Self>) {
|
fn update_all_excerpts(&mut self, cx: &mut ViewContext<Self>) {
|
||||||
self.project.update(cx, |project, cx| {
|
self.project.update(cx, |project, cx| {
|
||||||
let mut paths = project
|
let mut paths = project
|
||||||
.diagnostic_summaries(false, cx)
|
.diagnostic_summaries(false, cx)
|
||||||
.map(|(path, _, _)| path)
|
.map(|(path, _, _)| (path, None))
|
||||||
.collect::<BTreeSet<_>>();
|
.collect::<BTreeSet<_>>();
|
||||||
paths.extend(self.path_states.iter().map(|state| state.path.clone()));
|
paths.extend(
|
||||||
for path in paths {
|
self.path_states
|
||||||
self.update_paths_tx.unbounded_send((path, None)).unwrap();
|
.iter()
|
||||||
}
|
.map(|state| (state.path.clone(), None)),
|
||||||
|
);
|
||||||
|
let paths_to_update = std::mem::take(&mut self.paths_to_update);
|
||||||
|
paths.extend(paths_to_update.into_iter().map(|(path, _)| (path, None)));
|
||||||
|
self.paths_to_update = paths;
|
||||||
});
|
});
|
||||||
}
|
self.update_stale_excerpts(cx);
|
||||||
|
|
||||||
/// Enqueue an update of the excerpts for any path whose diagnostics are known
|
|
||||||
/// to have changed. If a language server id is passed, then only the excerpts for
|
|
||||||
/// that language server's diagnostics will be updated. Otherwise, all stale excerpts
|
|
||||||
/// will be refreshed.
|
|
||||||
fn enqueue_update_stale_excerpts(&mut self, language_server_id: Option<LanguageServerId>) {
|
|
||||||
for (path, server_id) in &self.paths_to_update {
|
|
||||||
if language_server_id.map_or(true, |id| id == *server_id) {
|
|
||||||
self.update_paths_tx
|
|
||||||
.unbounded_send((path.clone(), Some(*server_id)))
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_excerpts(
|
fn update_excerpts(
|
||||||
|
@ -291,11 +293,6 @@ impl ProjectDiagnosticsEditor {
|
||||||
buffer: Model<Buffer>,
|
buffer: Model<Buffer>,
|
||||||
cx: &mut ViewContext<Self>,
|
cx: &mut ViewContext<Self>,
|
||||||
) {
|
) {
|
||||||
self.paths_to_update.retain(|(path, server_id)| {
|
|
||||||
*path != path_to_update
|
|
||||||
|| server_to_update.map_or(false, |to_update| *server_id != to_update)
|
|
||||||
});
|
|
||||||
|
|
||||||
let was_empty = self.path_states.is_empty();
|
let was_empty = self.path_states.is_empty();
|
||||||
let snapshot = buffer.read(cx).snapshot();
|
let snapshot = buffer.read(cx).snapshot();
|
||||||
let path_ix = match self
|
let path_ix = match self
|
||||||
|
|
|
@ -800,7 +800,7 @@ async fn test_random_diagnostics(cx: &mut TestAppContext, mut rng: StdRng) {
|
||||||
}
|
}
|
||||||
|
|
||||||
log::info!("updating mutated diagnostics view");
|
log::info!("updating mutated diagnostics view");
|
||||||
mutated_view.update(cx, |view, _| view.enqueue_update_stale_excerpts(None));
|
mutated_view.update(cx, |view, cx| view.update_stale_excerpts(cx));
|
||||||
cx.run_until_parked();
|
cx.run_until_parked();
|
||||||
|
|
||||||
log::info!("constructing reference diagnostics view");
|
log::info!("constructing reference diagnostics view");
|
||||||
|
|
|
@ -14,12 +14,12 @@ impl Render for ToolbarControls {
|
||||||
let mut has_stale_excerpts = false;
|
let mut has_stale_excerpts = false;
|
||||||
let mut is_updating = false;
|
let mut is_updating = false;
|
||||||
|
|
||||||
if let Some(editor) = self.editor() {
|
if let Some(editor) = self.diagnostics() {
|
||||||
let editor = editor.read(cx);
|
let diagnostics = editor.read(cx);
|
||||||
include_warnings = editor.include_warnings;
|
include_warnings = diagnostics.include_warnings;
|
||||||
has_stale_excerpts = !editor.paths_to_update.is_empty();
|
has_stale_excerpts = !diagnostics.paths_to_update.is_empty();
|
||||||
is_updating = !editor.update_paths_tx.is_empty()
|
is_updating = diagnostics.update_excerpts_task.is_some()
|
||||||
|| editor
|
|| diagnostics
|
||||||
.project
|
.project
|
||||||
.read(cx)
|
.read(cx)
|
||||||
.language_servers_running_disk_based_diagnostics(cx)
|
.language_servers_running_disk_based_diagnostics(cx)
|
||||||
|
@ -49,9 +49,9 @@ impl Render for ToolbarControls {
|
||||||
.disabled(is_updating)
|
.disabled(is_updating)
|
||||||
.tooltip(move |cx| Tooltip::text("Update excerpts", cx))
|
.tooltip(move |cx| Tooltip::text("Update excerpts", cx))
|
||||||
.on_click(cx.listener(|this, _, cx| {
|
.on_click(cx.listener(|this, _, cx| {
|
||||||
if let Some(editor) = this.editor() {
|
if let Some(diagnostics) = this.diagnostics() {
|
||||||
editor.update(cx, |editor, _| {
|
diagnostics.update(cx, |diagnostics, cx| {
|
||||||
editor.enqueue_update_stale_excerpts(None);
|
diagnostics.update_all_excerpts(cx);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
})),
|
})),
|
||||||
|
@ -63,7 +63,7 @@ impl Render for ToolbarControls {
|
||||||
.shape(IconButtonShape::Square)
|
.shape(IconButtonShape::Square)
|
||||||
.tooltip(move |cx| Tooltip::text(tooltip, cx))
|
.tooltip(move |cx| Tooltip::text(tooltip, cx))
|
||||||
.on_click(cx.listener(|this, _, cx| {
|
.on_click(cx.listener(|this, _, cx| {
|
||||||
if let Some(editor) = this.editor() {
|
if let Some(editor) = this.diagnostics() {
|
||||||
editor.update(cx, |editor, cx| {
|
editor.update(cx, |editor, cx| {
|
||||||
editor.toggle_warnings(&Default::default(), cx);
|
editor.toggle_warnings(&Default::default(), cx);
|
||||||
});
|
});
|
||||||
|
@ -105,7 +105,7 @@ impl ToolbarControls {
|
||||||
ToolbarControls { editor: None }
|
ToolbarControls { editor: None }
|
||||||
}
|
}
|
||||||
|
|
||||||
fn editor(&self) -> Option<View<ProjectDiagnosticsEditor>> {
|
fn diagnostics(&self) -> Option<View<ProjectDiagnosticsEditor>> {
|
||||||
self.editor.as_ref()?.upgrade()
|
self.editor.as_ref()?.upgrade()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue