From 3f3d894c8b0e9b33c2ff3b577083cb606830547e Mon Sep 17 00:00:00 2001 From: Remco Smits Date: Sun, 12 Oct 2025 20:59:12 +0200 Subject: [PATCH] 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 --- crates/collab/src/tests/editor_tests.rs | 3 ++- crates/editor/src/editor.rs | 3 +++ crates/editor/src/editor_tests.rs | 17 +++++++++-------- crates/editor/src/lsp_colors.rs | 14 +++++++++----- 4 files changed, 23 insertions(+), 14 deletions(-) diff --git a/crates/collab/src/tests/editor_tests.rs b/crates/collab/src/tests/editor_tests.rs index 0614d66928710aeeda4a4a492508b92c5b4d35e0..621e7e100ad9c6da8352d9ecad4061a1ebf7ae51 100644 --- a/crates/collab/src/tests/editor_tests.rs +++ b/crates/collab/src/tests/editor_tests.rs @@ -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!( diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 06256c35971e72b283fb99cb1fc40799e410b2d5..e1ec4d5f81bdfc4cb631b1779621dc5b45d4f0e8 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -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, + refresh_colors_task: Task<()>, folding_newlines: Task<()>, pub lookup_key: Option>, } @@ -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, diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index 8f7dac889d13b6ff1d80557811da555b1b7216a3..24fd007ac0ec068075210ce9aa59c8a760497736 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -25706,7 +25706,7 @@ async fn test_document_colors(cx: &mut TestAppContext) { .set_request_handler::(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" ); diff --git a/crates/editor/src/lsp_colors.rs b/crates/editor/src/lsp_colors.rs index 4d703d219f88cb10566c9e02faa76cc12408b677..f13ffd69206d3d3d89ba8dd29ff6f73fd93e0bec 100644 --- a/crates/editor/src/lsp_colors.rs +++ b/crates/editor/src/lsp_colors.rs @@ -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::>() }); - 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(); + }); } }