diff --git a/assets/settings/default.json b/assets/settings/default.json index 3cc556b9f1..66da692680 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -169,7 +169,13 @@ "show_type_hints": true, "show_parameter_hints": true, // Corresponds to null/None LSP hint type value. - "show_other_hints": true + "show_other_hints": true, + // Time to wait after editing the buffer, before requesting the hints, + // set to 0 to disable debouncing. + "edit_debounce_ms": 700, + // Time to wait after scrolling the buffer, before requesting the hints, + // set to 0 to disable debouncing. + "scroll_debounce_ms": 50 }, "project_panel": { // Default width of the project panel. diff --git a/crates/collab/src/tests/editor_tests.rs b/crates/collab/src/tests/editor_tests.rs index 458f347efb..a4de4cc432 100644 --- a/crates/collab/src/tests/editor_tests.rs +++ b/crates/collab/src/tests/editor_tests.rs @@ -1426,6 +1426,8 @@ async fn test_mutual_editor_inlay_hint_cache_update( store.update_user_settings::(cx, |settings| { settings.defaults.inlay_hints = Some(InlayHintSettings { enabled: true, + edit_debounce_ms: 0, + scroll_debounce_ms: 0, show_type_hints: true, show_parameter_hints: false, show_other_hints: true, @@ -1438,6 +1440,8 @@ async fn test_mutual_editor_inlay_hint_cache_update( store.update_user_settings::(cx, |settings| { settings.defaults.inlay_hints = Some(InlayHintSettings { enabled: true, + edit_debounce_ms: 0, + scroll_debounce_ms: 0, show_type_hints: true, show_parameter_hints: false, show_other_hints: true, @@ -1695,6 +1699,8 @@ async fn test_inlay_hint_refresh_is_forwarded( store.update_user_settings::(cx, |settings| { settings.defaults.inlay_hints = Some(InlayHintSettings { enabled: false, + edit_debounce_ms: 0, + scroll_debounce_ms: 0, show_type_hints: false, show_parameter_hints: false, show_other_hints: false, @@ -1707,6 +1713,8 @@ async fn test_inlay_hint_refresh_is_forwarded( store.update_user_settings::(cx, |settings| { settings.defaults.inlay_hints = Some(InlayHintSettings { enabled: true, + edit_debounce_ms: 0, + scroll_debounce_ms: 0, show_type_hints: true, show_parameter_hints: true, show_other_hints: true, diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 6fb25c5426..a35f300425 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -1360,6 +1360,7 @@ enum InlayHintRefreshReason { RefreshRequested, ExcerptsRemoved(Vec), } + impl InlayHintRefreshReason { fn description(&self) -> &'static str { match self { @@ -3029,6 +3030,12 @@ impl Editor { } let reason_description = reason.description(); + let ignore_debounce = matches!( + reason, + InlayHintRefreshReason::SettingsChange(_) + | InlayHintRefreshReason::Toggle(_) + | InlayHintRefreshReason::ExcerptsRemoved(_) + ); let (invalidate_cache, required_languages) = match reason { InlayHintRefreshReason::Toggle(enabled) => { self.inlay_hint_cache.enabled = enabled; @@ -3091,6 +3098,7 @@ impl Editor { reason_description, self.excerpts_for_inlay_hints_query(required_languages.as_ref(), cx), invalidate_cache, + ignore_debounce, cx, ) { self.splice_inlay_hints(to_remove, to_insert, cx); diff --git a/crates/editor/src/hover_links.rs b/crates/editor/src/hover_links.rs index 0c1fa02010..0c5e36dc68 100644 --- a/crates/editor/src/hover_links.rs +++ b/crates/editor/src/hover_links.rs @@ -984,6 +984,8 @@ mod tests { init_test(cx, |settings| { settings.defaults.inlay_hints = Some(InlayHintSettings { enabled: true, + edit_debounce_ms: 0, + scroll_debounce_ms: 0, show_type_hints: true, show_parameter_hints: true, show_other_hints: true, diff --git a/crates/editor/src/hover_popover.rs b/crates/editor/src/hover_popover.rs index fb65e35be7..a94b38df7b 100644 --- a/crates/editor/src/hover_popover.rs +++ b/crates/editor/src/hover_popover.rs @@ -1066,6 +1066,8 @@ mod tests { init_test(cx, |settings| { settings.defaults.inlay_hints = Some(InlayHintSettings { enabled: true, + edit_debounce_ms: 0, + scroll_debounce_ms: 0, show_type_hints: true, show_parameter_hints: true, show_other_hints: true, diff --git a/crates/editor/src/inlay_hint_cache.rs b/crates/editor/src/inlay_hint_cache.rs index dc2616c6ef..8b602263ff 100644 --- a/crates/editor/src/inlay_hint_cache.rs +++ b/crates/editor/src/inlay_hint_cache.rs @@ -37,6 +37,9 @@ pub struct InlayHintCache { version: usize, pub(super) enabled: bool, update_tasks: HashMap, + refresh_task: Option>, + invalidate_debounce: Option, + append_debounce: Option, lsp_request_limiter: Arc, } @@ -267,6 +270,9 @@ impl InlayHintCache { enabled: inlay_hint_settings.enabled, hints: HashMap::default(), update_tasks: HashMap::default(), + refresh_task: None, + invalidate_debounce: debounce_value(inlay_hint_settings.edit_debounce_ms), + append_debounce: debounce_value(inlay_hint_settings.scroll_debounce_ms), version: 0, lsp_request_limiter: Arc::new(Semaphore::new(MAX_CONCURRENT_LSP_REQUESTS)), } @@ -282,6 +288,8 @@ impl InlayHintCache { visible_hints: Vec, cx: &mut ViewContext, ) -> ControlFlow> { + self.invalidate_debounce = debounce_value(new_hint_settings.edit_debounce_ms); + self.append_debounce = debounce_value(new_hint_settings.scroll_debounce_ms); let new_allowed_hint_kinds = new_hint_settings.enabled_inlay_hint_kinds(); match (self.enabled, new_hint_settings.enabled) { (false, false) => { @@ -332,15 +340,15 @@ impl InlayHintCache { /// This way, concequent refresh invocations are less likely to trigger LSP queries for the invisible ranges. pub(super) fn spawn_hint_refresh( &mut self, - reason: &'static str, + reason_description: &'static str, excerpts_to_query: HashMap, Global, Range)>, invalidate: InvalidationStrategy, + ignore_debounce: bool, cx: &mut ViewContext, ) -> Option { if !self.enabled { return None; } - let mut invalidated_hints = Vec::new(); if invalidate.should_invalidate() { self.update_tasks @@ -358,12 +366,23 @@ impl InlayHintCache { } let cache_version = self.version + 1; - cx.spawn(|editor, mut cx| async move { + let debounce_duration = if ignore_debounce { + None + } else if invalidate.should_invalidate() { + self.invalidate_debounce + } else { + self.append_debounce + }; + self.refresh_task = Some(cx.spawn(|editor, mut cx| async move { + if let Some(debounce_duration) = debounce_duration { + cx.background_executor().timer(debounce_duration).await; + } + editor .update(&mut cx, |editor, cx| { spawn_new_update_tasks( editor, - reason, + reason_description, excerpts_to_query, invalidate, cache_version, @@ -371,8 +390,7 @@ impl InlayHintCache { ) }) .ok(); - }) - .detach(); + })); if invalidated_hints.is_empty() { None @@ -612,6 +630,14 @@ impl InlayHintCache { } } +fn debounce_value(debounce_ms: u64) -> Option { + if debounce_ms > 0 { + Some(Duration::from_millis(debounce_ms)) + } else { + None + } +} + fn spawn_new_update_tasks( editor: &mut Editor, reason: &'static str, @@ -1259,6 +1285,8 @@ pub mod tests { init_test(cx, |settings| { settings.defaults.inlay_hints = Some(InlayHintSettings { enabled: true, + edit_debounce_ms: 0, + scroll_debounce_ms: 0, show_type_hints: allowed_hint_kinds.contains(&Some(InlayHintKind::Type)), show_parameter_hints: allowed_hint_kinds.contains(&Some(InlayHintKind::Parameter)), show_other_hints: allowed_hint_kinds.contains(&None), @@ -1389,6 +1417,8 @@ pub mod tests { init_test(cx, |settings| { settings.defaults.inlay_hints = Some(InlayHintSettings { enabled: true, + edit_debounce_ms: 0, + scroll_debounce_ms: 0, show_type_hints: true, show_parameter_hints: true, show_other_hints: true, @@ -1506,6 +1536,8 @@ pub mod tests { init_test(cx, |settings| { settings.defaults.inlay_hints = Some(InlayHintSettings { enabled: true, + edit_debounce_ms: 0, + scroll_debounce_ms: 0, show_type_hints: true, show_parameter_hints: true, show_other_hints: true, @@ -1734,6 +1766,8 @@ pub mod tests { init_test(cx, |settings| { settings.defaults.inlay_hints = Some(InlayHintSettings { enabled: true, + edit_debounce_ms: 0, + scroll_debounce_ms: 0, show_type_hints: allowed_hint_kinds.contains(&Some(InlayHintKind::Type)), show_parameter_hints: allowed_hint_kinds.contains(&Some(InlayHintKind::Parameter)), show_other_hints: allowed_hint_kinds.contains(&None), @@ -1895,6 +1929,8 @@ pub mod tests { update_test_language_settings(cx, |settings| { settings.defaults.inlay_hints = Some(InlayHintSettings { enabled: true, + edit_debounce_ms: 0, + scroll_debounce_ms: 0, show_type_hints: new_allowed_hint_kinds.contains(&Some(InlayHintKind::Type)), show_parameter_hints: new_allowed_hint_kinds .contains(&Some(InlayHintKind::Parameter)), @@ -1939,6 +1975,8 @@ pub mod tests { update_test_language_settings(cx, |settings| { settings.defaults.inlay_hints = Some(InlayHintSettings { enabled: false, + edit_debounce_ms: 0, + scroll_debounce_ms: 0, show_type_hints: another_allowed_hint_kinds.contains(&Some(InlayHintKind::Type)), show_parameter_hints: another_allowed_hint_kinds .contains(&Some(InlayHintKind::Parameter)), @@ -1997,6 +2035,8 @@ pub mod tests { update_test_language_settings(cx, |settings| { settings.defaults.inlay_hints = Some(InlayHintSettings { enabled: true, + edit_debounce_ms: 0, + scroll_debounce_ms: 0, show_type_hints: final_allowed_hint_kinds.contains(&Some(InlayHintKind::Type)), show_parameter_hints: final_allowed_hint_kinds .contains(&Some(InlayHintKind::Parameter)), @@ -2071,6 +2111,8 @@ pub mod tests { init_test(cx, |settings| { settings.defaults.inlay_hints = Some(InlayHintSettings { enabled: true, + edit_debounce_ms: 0, + scroll_debounce_ms: 0, show_type_hints: true, show_parameter_hints: true, show_other_hints: true, @@ -2203,6 +2245,8 @@ pub mod tests { init_test(cx, |settings| { settings.defaults.inlay_hints = Some(InlayHintSettings { enabled: true, + edit_debounce_ms: 0, + scroll_debounce_ms: 0, show_type_hints: true, show_parameter_hints: true, show_other_hints: true, @@ -2361,6 +2405,11 @@ pub mod tests { editor .update(cx, |editor, cx| { editor.scroll_screen(&ScrollAmount::Page(1.0), cx); + }) + .unwrap(); + cx.executor().run_until_parked(); + editor + .update(cx, |editor, cx| { editor.scroll_screen(&ScrollAmount::Page(1.0), cx); }) .unwrap(); @@ -2497,6 +2546,8 @@ pub mod tests { init_test(cx, |settings| { settings.defaults.inlay_hints = Some(InlayHintSettings { enabled: true, + edit_debounce_ms: 0, + scroll_debounce_ms: 0, show_type_hints: true, show_parameter_hints: true, show_other_hints: true, @@ -2782,6 +2833,9 @@ pub mod tests { }); }) .unwrap(); + cx.executor().advance_clock(Duration::from_millis( + INVISIBLE_RANGES_HINTS_REQUEST_DELAY_MILLIS + 100, + )); cx.executor().run_until_parked(); editor.update(cx, |editor, cx| { let expected_hints = vec![ @@ -2816,12 +2870,12 @@ pub mod tests { cx.executor().run_until_parked(); editor.update(cx, |editor, cx| { let expected_hints = vec![ - "main hint(edited) #0".to_string(), - "main hint(edited) #1".to_string(), - "main hint(edited) #2".to_string(), - "main hint(edited) #3".to_string(), - "main hint(edited) #4".to_string(), - "main hint(edited) #5".to_string(), + "main hint #0".to_string(), + "main hint #1".to_string(), + "main hint #2".to_string(), + "main hint #3".to_string(), + "main hint #4".to_string(), + "main hint #5".to_string(), "other hint(edited) #0".to_string(), "other hint(edited) #1".to_string(), ]; @@ -2834,11 +2888,12 @@ pub mod tests { assert_eq!(expected_hints, visible_hint_labels(editor, cx)); let current_cache_version = editor.inlay_hint_cache().version; - let expected_version = last_scroll_update_version + expected_hints.len(); - assert!( - current_cache_version == expected_version || current_cache_version == expected_version + 1 , - // TODO we sometimes get an extra cache version bump, why? - "We should have updated cache N times == N of new hints arrived (separately from each excerpt), or hit a bug and do that one extra time" + // We expect two new hints for the excerpts from `other.rs`: + let expected_version = last_scroll_update_version + 2; + assert_eq!( + current_cache_version, + expected_version, + "We should have updated cache N times == N of new hints arrived (separately from each edited excerpt)" ); }).unwrap(); } @@ -2848,6 +2903,8 @@ pub mod tests { init_test(cx, |settings| { settings.defaults.inlay_hints = Some(InlayHintSettings { enabled: true, + edit_debounce_ms: 0, + scroll_debounce_ms: 0, show_type_hints: false, show_parameter_hints: false, show_other_hints: false, @@ -3049,6 +3106,8 @@ pub mod tests { update_test_language_settings(cx, |settings| { settings.defaults.inlay_hints = Some(InlayHintSettings { enabled: true, + edit_debounce_ms: 0, + scroll_debounce_ms: 0, show_type_hints: true, show_parameter_hints: true, show_other_hints: true, @@ -3082,6 +3141,8 @@ pub mod tests { init_test(cx, |settings| { settings.defaults.inlay_hints = Some(InlayHintSettings { enabled: true, + edit_debounce_ms: 0, + scroll_debounce_ms: 0, show_type_hints: true, show_parameter_hints: true, show_other_hints: true, @@ -3180,6 +3241,8 @@ pub mod tests { init_test(cx, |settings| { settings.defaults.inlay_hints = Some(InlayHintSettings { enabled: false, + edit_debounce_ms: 0, + scroll_debounce_ms: 0, show_type_hints: true, show_parameter_hints: true, show_other_hints: true, @@ -3258,6 +3321,8 @@ pub mod tests { update_test_language_settings(cx, |settings| { settings.defaults.inlay_hints = Some(InlayHintSettings { enabled: true, + edit_debounce_ms: 0, + scroll_debounce_ms: 0, show_type_hints: true, show_parameter_hints: true, show_other_hints: true, diff --git a/crates/language/src/language_settings.rs b/crates/language/src/language_settings.rs index 9de4c11aee..4babe6344c 100644 --- a/crates/language/src/language_settings.rs +++ b/crates/language/src/language_settings.rs @@ -327,12 +327,34 @@ pub struct InlayHintSettings { /// Default: true #[serde(default = "default_true")] pub show_other_hints: bool, + /// Whether or not to debounce inlay hints updates after buffer edits. + /// + /// Set to 0 to disable debouncing. + /// + /// Default: 700 + #[serde(default = "edit_debounce_ms")] + pub edit_debounce_ms: u64, + /// Whether or not to debounce inlay hints updates after buffer scrolls. + /// + /// Set to 0 to disable debouncing. + /// + /// Default: 50 + #[serde(default = "scroll_debounce_ms")] + pub scroll_debounce_ms: u64, } fn default_true() -> bool { true } +fn edit_debounce_ms() -> u64 { + 700 +} + +fn scroll_debounce_ms() -> u64 { + 50 +} + impl InlayHintSettings { /// Returns the kinds of inlay hints that are enabled based on the settings. pub fn enabled_inlay_hint_kinds(&self) -> HashSet> { diff --git a/docs/src/configuring_zed.md b/docs/src/configuring_zed.md index 03e844fa63..c4a6a6d015 100644 --- a/docs/src/configuring_zed.md +++ b/docs/src/configuring_zed.md @@ -383,7 +383,9 @@ To override settings for a language, add an entry for that language server's nam "enabled": false, "show_type_hints": true, "show_parameter_hints": true, - "show_other_hints": true + "show_other_hints": true, + "edit_debounce_ms": 700, + "scroll_debounce_ms": 50 } ``` @@ -402,6 +404,9 @@ The following languages have inlay hints preconfigured by Zed: Use the `lsp` section for the server configuration. Examples are provided in the corresponding language documentation. +Hints are not instantly queried in Zed, two kinds of debounces are used, either may be set to 0 to be disabled. +Settings-related hint updates are not debounced. + ## Journal - Description: Configuration for the journal.