lsp colors: Reduce flickering while typing (#40055)

Closes #40019

Follow-up https://github.com/zed-industries/zed/pull/40025

This PR reduces/removes the flickering of inlay colors. This is done by
adding a debounce, and not detaching the task that fetches the new
colors.

**Result**


https://github.com/user-attachments/assets/5dae278b-b821-4e64-8adb-c4d8376ba1df

Release Notes:

- Lsp colors: Reduce flickering while typing.

---------

Co-authored-by: Kirill Bulatov <kirill@zed.dev>
This commit is contained in:
Remco Smits
2025-10-12 18:59:12 +00:00
committed by GitHub
co-authored by Kirill Bulatov
parent 83f0a36733
commit 3f3d894c8b
4 changed files with 23 additions and 14 deletions
+2 -1
View File
@@ -4,7 +4,7 @@ use crate::{
};
use call::ActiveCall;
use editor::{
DocumentColorsRenderMode, Editor, RowInfo, SelectionEffects,
DocumentColorsRenderMode, Editor, FETCH_COLORS_DEBOUNCE_TIMEOUT, RowInfo, SelectionEffects,
actions::{
ConfirmCodeAction, ConfirmCompletion, ConfirmRename, ContextMenuFirst,
ExpandMacroRecursively, MoveToEnd, Redo, Rename, SelectAll, ToggleCodeActions, Undo,
@@ -2409,6 +2409,7 @@ async fn test_lsp_document_color(cx_a: &mut TestAppContext, cx_b: &mut TestAppCo
.unwrap();
color_request_handle.next().await.unwrap();
executor.advance_clock(FETCH_COLORS_DEBOUNCE_TIMEOUT);
executor.run_until_parked();
assert_eq!(
+3
View File
@@ -226,6 +226,7 @@ pub const SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis
pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
pub const FETCH_COLORS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(150);
pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
pub(crate) const EDIT_PREDICTION_CONFLICT_KEY_CONTEXT: &str = "edit_prediction_conflict";
@@ -1189,6 +1190,7 @@ pub struct Editor {
inline_value_cache: InlineValueCache,
selection_drag_state: SelectionDragState,
colors: Option<LspColorData>,
refresh_colors_task: Task<()>,
folding_newlines: Task<()>,
pub lookup_key: Option<Box<dyn Any + Send + Sync>>,
}
@@ -2244,6 +2246,7 @@ impl Editor {
tasks_update_task: None,
pull_diagnostics_task: Task::ready(()),
colors: None,
refresh_colors_task: Task::ready(()),
next_color_inlay_id: 0,
linked_edit_ranges: Default::default(),
in_project_search: false,
+9 -8
View File
@@ -25706,7 +25706,7 @@ async fn test_document_colors(cx: &mut TestAppContext) {
.set_request_handler::<lsp::request::DocumentColor, _, _>(move |_, _| async move {
panic!("Should not be called");
});
cx.executor().advance_clock(Duration::from_millis(100));
cx.executor().advance_clock(FETCH_COLORS_DEBOUNCE_TIMEOUT);
color_request_handle.next().await.unwrap();
cx.run_until_parked();
assert_eq!(
@@ -25790,9 +25790,9 @@ async fn test_document_colors(cx: &mut TestAppContext) {
color_request_handle.next().await.unwrap();
cx.run_until_parked();
assert_eq!(
3,
2,
requests_made.load(atomic::Ordering::Acquire),
"Should query for colors once per save and once per formatting after save"
"Should query for colors once per save (deduplicated) and once per formatting after save"
);
drop(editor);
@@ -25813,7 +25813,7 @@ async fn test_document_colors(cx: &mut TestAppContext) {
.unwrap();
close.await.unwrap();
assert_eq!(
3,
2,
requests_made.load(atomic::Ordering::Acquire),
"After saving and closing all editors, no extra requests should be made"
);
@@ -25833,7 +25833,7 @@ async fn test_document_colors(cx: &mut TestAppContext) {
})
})
.unwrap();
cx.executor().advance_clock(Duration::from_millis(100));
cx.executor().advance_clock(FETCH_COLORS_DEBOUNCE_TIMEOUT);
cx.run_until_parked();
let editor = workspace
.update(cx, |workspace, _, cx| {
@@ -25844,9 +25844,9 @@ async fn test_document_colors(cx: &mut TestAppContext) {
.expect("Should be an editor")
})
.unwrap();
color_request_handle.next().await.unwrap();
assert_eq!(
3,
2,
requests_made.load(atomic::Ordering::Acquire),
"Cache should be reused on buffer close and reopen"
);
@@ -25887,10 +25887,11 @@ async fn test_document_colors(cx: &mut TestAppContext) {
});
save.await.unwrap();
cx.executor().advance_clock(FETCH_COLORS_DEBOUNCE_TIMEOUT);
empty_color_request_handle.next().await.unwrap();
cx.run_until_parked();
assert_eq!(
4,
3,
requests_made.load(atomic::Ordering::Acquire),
"Should query for colors once per save only, as formatting was not requested"
);
+9 -5
View File
@@ -13,8 +13,8 @@ use ui::{App, Context, Window};
use util::post_inc;
use crate::{
DisplayPoint, Editor, EditorSettings, EditorSnapshot, InlayId, InlaySplice, RangeToAnchorExt,
display_map::Inlay, editor_settings::DocumentColorsRenderMode,
DisplayPoint, Editor, EditorSettings, EditorSnapshot, FETCH_COLORS_DEBOUNCE_TIMEOUT, InlayId,
InlaySplice, RangeToAnchorExt, display_map::Inlay, editor_settings::DocumentColorsRenderMode,
};
#[derive(Debug)]
@@ -193,7 +193,12 @@ impl Editor {
})
.collect::<Vec<_>>()
});
cx.spawn(async move |editor, cx| {
self.refresh_colors_task = cx.spawn(async move |editor, cx| {
cx.background_executor()
.timer(FETCH_COLORS_DEBOUNCE_TIMEOUT)
.await;
let all_colors = join_all(all_colors_task).await;
if all_colors.is_empty() {
return;
@@ -420,7 +425,6 @@ impl Editor {
}
})
.ok();
})
.detach();
});
}
}