Fix document colors issues with other inlays and multi buffers (#33598)
Closes https://github.com/zed-industries/zed/issues/33575 * Fixes inlay colors spoiled after document color displayed * Optimizes the query pattern for large multi buffers Release Notes: - Fixed document colors issues with other inlays and multi buffers
This commit is contained in:
parent
521a223681
commit
41583fb066
8 changed files with 288 additions and 250 deletions
|
@ -327,9 +327,9 @@ impl<'a> Iterator for InlayChunks<'a> {
|
|||
InlayId::DebuggerValue(_) => self.highlight_styles.inlay_hint,
|
||||
InlayId::Color(_) => match inlay.color {
|
||||
Some(color) => {
|
||||
let style = self.highlight_styles.inlay_hint.get_or_insert_default();
|
||||
let mut style = self.highlight_styles.inlay_hint.unwrap_or_default();
|
||||
style.color = Some(color);
|
||||
Some(*style)
|
||||
Some(style)
|
||||
}
|
||||
None => self.highlight_styles.inlay_hint,
|
||||
},
|
||||
|
|
|
@ -1845,13 +1845,13 @@ impl Editor {
|
|||
editor
|
||||
.refresh_inlay_hints(InlayHintRefreshReason::RefreshRequested, cx);
|
||||
}
|
||||
project::Event::LanguageServerAdded(server_id, ..)
|
||||
| project::Event::LanguageServerRemoved(server_id) => {
|
||||
project::Event::LanguageServerAdded(..)
|
||||
| project::Event::LanguageServerRemoved(..) => {
|
||||
if editor.tasks_update_task.is_none() {
|
||||
editor.tasks_update_task =
|
||||
Some(editor.refresh_runnables(window, cx));
|
||||
}
|
||||
editor.update_lsp_data(Some(*server_id), None, window, cx);
|
||||
editor.update_lsp_data(true, None, window, cx);
|
||||
}
|
||||
project::Event::SnippetEdit(id, snippet_edits) => {
|
||||
if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
|
||||
|
@ -2291,7 +2291,7 @@ impl Editor {
|
|||
editor.minimap =
|
||||
editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
|
||||
editor.colors = Some(LspColorData::new(cx));
|
||||
editor.update_lsp_data(None, None, window, cx);
|
||||
editor.update_lsp_data(false, None, window, cx);
|
||||
}
|
||||
|
||||
editor.report_editor_event("Editor Opened", None, cx);
|
||||
|
@ -5103,7 +5103,7 @@ impl Editor {
|
|||
to_insert,
|
||||
}) = self.inlay_hint_cache.spawn_hint_refresh(
|
||||
reason_description,
|
||||
self.excerpts_for_inlay_hints_query(required_languages.as_ref(), cx),
|
||||
self.visible_excerpts(required_languages.as_ref(), cx),
|
||||
invalidate_cache,
|
||||
ignore_debounce,
|
||||
cx,
|
||||
|
@ -5121,7 +5121,7 @@ impl Editor {
|
|||
.collect()
|
||||
}
|
||||
|
||||
pub fn excerpts_for_inlay_hints_query(
|
||||
pub fn visible_excerpts(
|
||||
&self,
|
||||
restrict_to_languages: Option<&HashSet<Arc<Language>>>,
|
||||
cx: &mut Context<Editor>,
|
||||
|
@ -19562,7 +19562,7 @@ impl Editor {
|
|||
cx.emit(SearchEvent::MatchesInvalidated);
|
||||
|
||||
if let Some(buffer) = edited_buffer {
|
||||
self.update_lsp_data(None, Some(buffer.read(cx).remote_id()), window, cx);
|
||||
self.update_lsp_data(false, Some(buffer.read(cx).remote_id()), window, cx);
|
||||
}
|
||||
|
||||
if *singleton_buffer_edited {
|
||||
|
@ -19627,7 +19627,7 @@ impl Editor {
|
|||
.detach();
|
||||
}
|
||||
}
|
||||
self.update_lsp_data(None, Some(buffer_id), window, cx);
|
||||
self.update_lsp_data(false, Some(buffer_id), window, cx);
|
||||
cx.emit(EditorEvent::ExcerptsAdded {
|
||||
buffer: buffer.clone(),
|
||||
predecessor: *predecessor,
|
||||
|
@ -19813,7 +19813,7 @@ impl Editor {
|
|||
if !inlay_splice.to_insert.is_empty() || !inlay_splice.to_remove.is_empty() {
|
||||
self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
|
||||
}
|
||||
self.refresh_colors(None, None, window, cx);
|
||||
self.refresh_colors(false, None, window, cx);
|
||||
}
|
||||
|
||||
cx.notify();
|
||||
|
@ -20714,13 +20714,13 @@ impl Editor {
|
|||
|
||||
fn update_lsp_data(
|
||||
&mut self,
|
||||
for_server_id: Option<LanguageServerId>,
|
||||
ignore_cache: bool,
|
||||
for_buffer: Option<BufferId>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<'_, Self>,
|
||||
) {
|
||||
self.pull_diagnostics(for_buffer, window, cx);
|
||||
self.refresh_colors(for_server_id, for_buffer, window, cx);
|
||||
self.refresh_colors(ignore_cache, for_buffer, window, cx);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -55,7 +55,8 @@ use util::{
|
|||
uri,
|
||||
};
|
||||
use workspace::{
|
||||
CloseActiveItem, CloseAllItems, CloseInactiveItems, NavigationEntry, OpenOptions, ViewId,
|
||||
CloseActiveItem, CloseAllItems, CloseInactiveItems, MoveItemToPaneInDirection, NavigationEntry,
|
||||
OpenOptions, ViewId,
|
||||
item::{FollowEvent, FollowableItem, Item, ItemHandle, SaveOptions},
|
||||
};
|
||||
|
||||
|
@ -22601,8 +22602,8 @@ async fn test_add_selection_after_moving_with_multiple_cursors(cx: &mut TestAppC
|
|||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_mtime_and_document_colors(cx: &mut TestAppContext) {
|
||||
#[gpui::test(iterations = 10)]
|
||||
async fn test_document_colors(cx: &mut TestAppContext) {
|
||||
let expected_color = Rgba {
|
||||
r: 0.33,
|
||||
g: 0.33,
|
||||
|
@ -22723,24 +22724,73 @@ async fn test_mtime_and_document_colors(cx: &mut TestAppContext) {
|
|||
.set_request_handler::<lsp::request::DocumentColor, _, _>(move |_, _| async move {
|
||||
panic!("Should not be called");
|
||||
});
|
||||
color_request_handle.next().await.unwrap();
|
||||
cx.run_until_parked();
|
||||
cx.executor().advance_clock(Duration::from_millis(100));
|
||||
color_request_handle.next().await.unwrap();
|
||||
cx.run_until_parked();
|
||||
assert_eq!(
|
||||
3,
|
||||
1,
|
||||
requests_made.load(atomic::Ordering::Acquire),
|
||||
"Should query for colors once per editor open (1) and once after the language server startup (2)"
|
||||
"Should query for colors once per editor open"
|
||||
);
|
||||
|
||||
cx.executor().advance_clock(Duration::from_millis(500));
|
||||
let save = editor.update_in(cx, |editor, window, cx| {
|
||||
editor.update_in(cx, |editor, _, cx| {
|
||||
assert_eq!(
|
||||
vec![expected_color],
|
||||
extract_color_inlays(editor, cx),
|
||||
"Should have an initial inlay"
|
||||
);
|
||||
});
|
||||
|
||||
// opening another file in a split should not influence the LSP query counter
|
||||
workspace
|
||||
.update(cx, |workspace, window, cx| {
|
||||
assert_eq!(
|
||||
workspace.panes().len(),
|
||||
1,
|
||||
"Should have one pane with one editor"
|
||||
);
|
||||
workspace.move_item_to_pane_in_direction(
|
||||
&MoveItemToPaneInDirection {
|
||||
direction: SplitDirection::Right,
|
||||
focus: false,
|
||||
clone: true,
|
||||
},
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
})
|
||||
.unwrap();
|
||||
cx.run_until_parked();
|
||||
workspace
|
||||
.update(cx, |workspace, _, cx| {
|
||||
let panes = workspace.panes();
|
||||
assert_eq!(panes.len(), 2, "Should have two panes after splitting");
|
||||
for pane in panes {
|
||||
let editor = pane
|
||||
.read(cx)
|
||||
.active_item()
|
||||
.and_then(|item| item.downcast::<Editor>())
|
||||
.expect("Should have opened an editor in each split");
|
||||
let editor_file = editor
|
||||
.read(cx)
|
||||
.buffer()
|
||||
.read(cx)
|
||||
.as_singleton()
|
||||
.expect("test deals with singleton buffers")
|
||||
.read(cx)
|
||||
.file()
|
||||
.expect("test buffese should have a file")
|
||||
.path();
|
||||
assert_eq!(
|
||||
editor_file.as_ref(),
|
||||
Path::new("first.rs"),
|
||||
"Both editors should be opened for the same file"
|
||||
)
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
cx.executor().advance_clock(Duration::from_millis(500));
|
||||
let save = editor.update_in(cx, |editor, window, cx| {
|
||||
editor.move_to_end(&MoveToEnd, window, cx);
|
||||
editor.handle_input("dirty", window, cx);
|
||||
editor.save(
|
||||
|
@ -22755,12 +22805,10 @@ async fn test_mtime_and_document_colors(cx: &mut TestAppContext) {
|
|||
});
|
||||
save.await.unwrap();
|
||||
|
||||
color_request_handle.next().await.unwrap();
|
||||
cx.run_until_parked();
|
||||
color_request_handle.next().await.unwrap();
|
||||
cx.run_until_parked();
|
||||
assert_eq!(
|
||||
5,
|
||||
3,
|
||||
requests_made.load(atomic::Ordering::Acquire),
|
||||
"Should query for colors once per save and once per formatting after save"
|
||||
);
|
||||
|
@ -22774,11 +22822,27 @@ async fn test_mtime_and_document_colors(cx: &mut TestAppContext) {
|
|||
})
|
||||
.unwrap();
|
||||
close.await.unwrap();
|
||||
let close = workspace
|
||||
.update(cx, |workspace, window, cx| {
|
||||
workspace.active_pane().update(cx, |pane, cx| {
|
||||
pane.close_active_item(&CloseActiveItem::default(), window, cx)
|
||||
})
|
||||
})
|
||||
.unwrap();
|
||||
close.await.unwrap();
|
||||
assert_eq!(
|
||||
5,
|
||||
3,
|
||||
requests_made.load(atomic::Ordering::Acquire),
|
||||
"After saving and closing the editor, no extra requests should be made"
|
||||
"After saving and closing all editors, no extra requests should be made"
|
||||
);
|
||||
workspace
|
||||
.update(cx, |workspace, _, cx| {
|
||||
assert!(
|
||||
workspace.active_item(cx).is_none(),
|
||||
"Should close all editors"
|
||||
)
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
workspace
|
||||
.update(cx, |workspace, window, cx| {
|
||||
|
@ -22788,13 +22852,7 @@ async fn test_mtime_and_document_colors(cx: &mut TestAppContext) {
|
|||
})
|
||||
.unwrap();
|
||||
cx.executor().advance_clock(Duration::from_millis(100));
|
||||
color_request_handle.next().await.unwrap();
|
||||
cx.run_until_parked();
|
||||
assert_eq!(
|
||||
6,
|
||||
requests_made.load(atomic::Ordering::Acquire),
|
||||
"After navigating back to an editor and reopening it, another color request should be made"
|
||||
);
|
||||
let editor = workspace
|
||||
.update(cx, |workspace, _, cx| {
|
||||
workspace
|
||||
|
@ -22804,6 +22862,12 @@ async fn test_mtime_and_document_colors(cx: &mut TestAppContext) {
|
|||
.expect("Should be an editor")
|
||||
})
|
||||
.unwrap();
|
||||
color_request_handle.next().await.unwrap();
|
||||
assert_eq!(
|
||||
3,
|
||||
requests_made.load(atomic::Ordering::Acquire),
|
||||
"Cache should be reused on buffer close and reopen"
|
||||
);
|
||||
editor.update(cx, |editor, cx| {
|
||||
assert_eq!(
|
||||
vec![expected_color],
|
||||
|
|
|
@ -956,7 +956,7 @@ fn fetch_and_update_hints(
|
|||
.update(cx, |editor, cx| {
|
||||
if got_throttled {
|
||||
let query_not_around_visible_range = match editor
|
||||
.excerpts_for_inlay_hints_query(None, cx)
|
||||
.visible_excerpts(None, cx)
|
||||
.remove(&query.excerpt_id)
|
||||
{
|
||||
Some((_, _, current_visible_range)) => {
|
||||
|
@ -2525,9 +2525,7 @@ pub mod tests {
|
|||
cx: &mut gpui::TestAppContext,
|
||||
) -> Range<Point> {
|
||||
let ranges = editor
|
||||
.update(cx, |editor, _window, cx| {
|
||||
editor.excerpts_for_inlay_hints_query(None, cx)
|
||||
})
|
||||
.update(cx, |editor, _window, cx| editor.visible_excerpts(None, cx))
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
ranges.len(),
|
||||
|
|
|
@ -3,10 +3,10 @@ use std::{cmp, ops::Range};
|
|||
use collections::HashMap;
|
||||
use futures::future::join_all;
|
||||
use gpui::{Hsla, Rgba};
|
||||
use itertools::Itertools;
|
||||
use language::point_from_lsp;
|
||||
use lsp::LanguageServerId;
|
||||
use multi_buffer::Anchor;
|
||||
use project::DocumentColor;
|
||||
use project::{DocumentColor, lsp_store::ColorFetchStrategy};
|
||||
use settings::Settings as _;
|
||||
use text::{Bias, BufferId, OffsetRangeExt as _};
|
||||
use ui::{App, Context, Window};
|
||||
|
@ -19,6 +19,7 @@ use crate::{
|
|||
|
||||
#[derive(Debug)]
|
||||
pub(super) struct LspColorData {
|
||||
cache_version_used: usize,
|
||||
colors: Vec<(Range<Anchor>, DocumentColor, InlayId)>,
|
||||
inlay_colors: HashMap<InlayId, usize>,
|
||||
render_mode: DocumentColorsRenderMode,
|
||||
|
@ -27,6 +28,7 @@ pub(super) struct LspColorData {
|
|||
impl LspColorData {
|
||||
pub fn new(cx: &App) -> Self {
|
||||
Self {
|
||||
cache_version_used: 0,
|
||||
colors: Vec::new(),
|
||||
inlay_colors: HashMap::default(),
|
||||
render_mode: EditorSettings::get_global(cx).lsp_document_colors,
|
||||
|
@ -122,7 +124,7 @@ impl LspColorData {
|
|||
impl Editor {
|
||||
pub(super) fn refresh_colors(
|
||||
&mut self,
|
||||
for_server_id: Option<LanguageServerId>,
|
||||
ignore_cache: bool,
|
||||
buffer_id: Option<BufferId>,
|
||||
_: &Window,
|
||||
cx: &mut Context<Self>,
|
||||
|
@ -141,29 +143,41 @@ impl Editor {
|
|||
return;
|
||||
}
|
||||
|
||||
let visible_buffers = self
|
||||
.visible_excerpts(None, cx)
|
||||
.into_values()
|
||||
.map(|(buffer, ..)| buffer)
|
||||
.filter(|editor_buffer| {
|
||||
buffer_id.is_none_or(|buffer_id| buffer_id == editor_buffer.read(cx).remote_id())
|
||||
})
|
||||
.unique_by(|buffer| buffer.read(cx).remote_id())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let all_colors_task = project.read(cx).lsp_store().update(cx, |lsp_store, cx| {
|
||||
self.buffer()
|
||||
.update(cx, |multi_buffer, cx| {
|
||||
multi_buffer
|
||||
.all_buffers()
|
||||
.into_iter()
|
||||
.filter(|editor_buffer| {
|
||||
buffer_id.is_none_or(|buffer_id| {
|
||||
buffer_id == editor_buffer.read(cx).remote_id()
|
||||
})
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
visible_buffers
|
||||
.into_iter()
|
||||
.filter_map(|buffer| {
|
||||
let buffer_id = buffer.read(cx).remote_id();
|
||||
let colors_task = lsp_store.document_colors(for_server_id, buffer, cx)?;
|
||||
let fetch_strategy = if ignore_cache {
|
||||
ColorFetchStrategy::IgnoreCache
|
||||
} else {
|
||||
ColorFetchStrategy::UseCache {
|
||||
known_cache_version: self
|
||||
.colors
|
||||
.as_ref()
|
||||
.map(|colors| colors.cache_version_used),
|
||||
}
|
||||
};
|
||||
let colors_task = lsp_store.document_colors(fetch_strategy, buffer, cx)?;
|
||||
Some(async move { (buffer_id, colors_task.await) })
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
});
|
||||
cx.spawn(async move |editor, cx| {
|
||||
let all_colors = join_all(all_colors_task).await;
|
||||
if all_colors.is_empty() {
|
||||
return;
|
||||
}
|
||||
let Ok((multi_buffer_snapshot, editor_excerpts)) = editor.update(cx, |editor, cx| {
|
||||
let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
|
||||
let editor_excerpts = multi_buffer_snapshot.excerpts().fold(
|
||||
|
@ -187,6 +201,7 @@ impl Editor {
|
|||
return;
|
||||
};
|
||||
|
||||
let mut cache_version = None;
|
||||
let mut new_editor_colors = Vec::<(Range<Anchor>, DocumentColor)>::new();
|
||||
for (buffer_id, colors) in all_colors {
|
||||
let Some(excerpts) = editor_excerpts.get(&buffer_id) else {
|
||||
|
@ -194,7 +209,8 @@ impl Editor {
|
|||
};
|
||||
match colors {
|
||||
Ok(colors) => {
|
||||
for color in colors {
|
||||
cache_version = colors.cache_version;
|
||||
for color in colors.colors {
|
||||
let color_start = point_from_lsp(color.lsp_range.start);
|
||||
let color_end = point_from_lsp(color.lsp_range.end);
|
||||
|
||||
|
@ -337,6 +353,9 @@ impl Editor {
|
|||
}
|
||||
|
||||
let mut updated = colors.set_colors(new_color_inlays);
|
||||
if let Some(cache_version) = cache_version {
|
||||
colors.cache_version_used = cache_version;
|
||||
}
|
||||
if colors.render_mode == DocumentColorsRenderMode::Inlay
|
||||
&& (!colors_splice.to_insert.is_empty()
|
||||
|| !colors_splice.to_remove.is_empty())
|
||||
|
|
|
@ -487,8 +487,9 @@ impl Editor {
|
|||
if opened_first_time {
|
||||
cx.spawn_in(window, async move |editor, cx| {
|
||||
editor
|
||||
.update(cx, |editor, cx| {
|
||||
editor.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx)
|
||||
.update_in(cx, |editor, window, cx| {
|
||||
editor.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
|
||||
editor.refresh_colors(false, None, window, cx);
|
||||
})
|
||||
.ok()
|
||||
})
|
||||
|
@ -599,6 +600,7 @@ impl Editor {
|
|||
);
|
||||
|
||||
self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
|
||||
self.refresh_colors(false, None, window, cx);
|
||||
}
|
||||
|
||||
pub fn scroll_position(&self, cx: &mut Context<Self>) -> gpui::Point<f32> {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue