Tidy up signature help delays (#47762)

Kirill Bulatov created

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

Release Notes:

- N/A

Change summary

assets/settings/default.json          |   1 
crates/editor/src/editor.rs           |   2 
crates/editor/src/editor_tests.rs     | 179 +++++++++++++++++++++++++++++
crates/editor/src/signature_help.rs   |  19 ++
crates/settings_content/src/editor.rs |   1 
docs/src/reference/all-settings.md    |   2 
6 files changed, 201 insertions(+), 3 deletions(-)

Detailed changes

assets/settings/default.json πŸ”—

@@ -126,6 +126,7 @@
   // over symbols in the editor.
   "hover_popover_enabled": true,
   // Time to wait in milliseconds before showing the informational hover box.
+  // This delay also applies to auto signature help when `auto_signature_help` is enabled.
   "hover_popover_delay": 300,
   // Whether to confirm before quitting Zed.
   "confirm_quit": false,

crates/editor/src/editor.rs πŸ”—

@@ -3825,7 +3825,7 @@ impl Editor {
             self.selections_did_change(true, old_cursor_position, state.effects, window, cx);
 
             if self.should_open_signature_help_automatically(old_cursor_position, cx) {
-                self.show_signature_help(&ShowSignatureHelp, window, cx);
+                self.show_signature_help_auto(window, cx);
             }
         }
     }

crates/editor/src/editor_tests.rs πŸ”—

@@ -13677,6 +13677,185 @@ async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
     });
 }
 
+#[gpui::test]
+async fn test_signature_help_delay_only_for_auto(cx: &mut TestAppContext) {
+    init_test(cx, |_| {});
+
+    let delay_ms = 500;
+    cx.update(|cx| {
+        cx.update_global::<SettingsStore, _>(|settings, cx| {
+            settings.update_user_settings(cx, |settings| {
+                settings.editor.auto_signature_help = Some(true);
+                settings.editor.show_signature_help_after_edits = Some(false);
+                settings.editor.hover_popover_delay = Some(DelayMs(delay_ms));
+            });
+        });
+    });
+
+    let mut cx = EditorLspTestContext::new_rust(
+        lsp::ServerCapabilities {
+            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
+            ..lsp::ServerCapabilities::default()
+        },
+        cx,
+    )
+    .await;
+
+    let mocked_response = lsp::SignatureHelp {
+        signatures: vec![lsp::SignatureInformation {
+            label: "fn sample(param1: u8)".to_string(),
+            documentation: None,
+            parameters: Some(vec![lsp::ParameterInformation {
+                label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
+                documentation: None,
+            }]),
+            active_parameter: None,
+        }],
+        active_signature: Some(0),
+        active_parameter: Some(0),
+    };
+
+    cx.set_state(indoc! {"
+        fn main() {
+            sample(Λ‡);
+        }
+
+        fn sample(param1: u8) {}
+    "});
+
+    // Manual trigger should show immediately without delay
+    cx.update_editor(|editor, window, cx| {
+        editor.show_signature_help(&ShowSignatureHelp, window, cx);
+    });
+    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
+    cx.run_until_parked();
+    cx.editor(|editor, _, _| {
+        assert!(
+            editor.signature_help_state.is_shown(),
+            "Manual trigger should show signature help without delay"
+        );
+    });
+
+    cx.update_editor(|editor, _, cx| {
+        editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
+    });
+    cx.run_until_parked();
+    cx.editor(|editor, _, _| {
+        assert!(!editor.signature_help_state.is_shown());
+    });
+
+    // Auto trigger (cursor movement into brackets) should respect delay
+    cx.set_state(indoc! {"
+        fn main() {
+            sampleˇ();
+        }
+
+        fn sample(param1: u8) {}
+    "});
+    cx.update_editor(|editor, window, cx| {
+        editor.move_right(&MoveRight, window, cx);
+    });
+    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
+    cx.run_until_parked();
+    cx.editor(|editor, _, _| {
+        assert!(
+            !editor.signature_help_state.is_shown(),
+            "Auto trigger should wait for delay before showing signature help"
+        );
+    });
+
+    cx.executor()
+        .advance_clock(Duration::from_millis(delay_ms + 50));
+    cx.run_until_parked();
+    cx.editor(|editor, _, _| {
+        assert!(
+            editor.signature_help_state.is_shown(),
+            "Auto trigger should show signature help after delay elapsed"
+        );
+    });
+}
+
+#[gpui::test]
+async fn test_signature_help_after_edits_no_delay(cx: &mut TestAppContext) {
+    init_test(cx, |_| {});
+
+    let delay_ms = 500;
+    cx.update(|cx| {
+        cx.update_global::<SettingsStore, _>(|settings, cx| {
+            settings.update_user_settings(cx, |settings| {
+                settings.editor.auto_signature_help = Some(false);
+                settings.editor.show_signature_help_after_edits = Some(true);
+                settings.editor.hover_popover_delay = Some(DelayMs(delay_ms));
+            });
+        });
+    });
+
+    let mut cx = EditorLspTestContext::new_rust(
+        lsp::ServerCapabilities {
+            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
+            ..lsp::ServerCapabilities::default()
+        },
+        cx,
+    )
+    .await;
+
+    let language = Arc::new(Language::new(
+        LanguageConfig {
+            name: "Rust".into(),
+            brackets: BracketPairConfig {
+                pairs: vec![BracketPair {
+                    start: "(".to_string(),
+                    end: ")".to_string(),
+                    close: true,
+                    surround: true,
+                    newline: true,
+                }],
+                ..BracketPairConfig::default()
+            },
+            autoclose_before: "})".to_string(),
+            ..LanguageConfig::default()
+        },
+        Some(tree_sitter_rust::LANGUAGE.into()),
+    ));
+    cx.language_registry().add(language.clone());
+    cx.update_buffer(|buffer, cx| {
+        buffer.set_language(Some(language), cx);
+    });
+
+    let mocked_response = lsp::SignatureHelp {
+        signatures: vec![lsp::SignatureInformation {
+            label: "fn sample(param1: u8)".to_string(),
+            documentation: None,
+            parameters: Some(vec![lsp::ParameterInformation {
+                label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
+                documentation: None,
+            }]),
+            active_parameter: None,
+        }],
+        active_signature: Some(0),
+        active_parameter: Some(0),
+    };
+
+    cx.set_state(indoc! {"
+        fn main() {
+            sampleˇ
+        }
+    "});
+
+    // Typing bracket should show signature help immediately without delay
+    cx.update_editor(|editor, window, cx| {
+        editor.handle_input("(", window, cx);
+    });
+    handle_signature_help_request(&mut cx, mocked_response).await;
+    cx.run_until_parked();
+    cx.editor(|editor, _, _| {
+        assert!(
+            editor.signature_help_state.is_shown(),
+            "show_signature_help_after_edits should show signature help without delay"
+        );
+    });
+}
+
 #[gpui::test]
 async fn test_handle_input_with_different_show_signature_settings(cx: &mut TestAppContext) {
     init_test(cx, |_| {});

crates/editor/src/signature_help.rs πŸ”—

@@ -166,6 +166,19 @@ impl Editor {
         _: &ShowSignatureHelp,
         window: &mut Window,
         cx: &mut Context<Self>,
+    ) {
+        self.show_signature_help_impl(false, window, cx);
+    }
+
+    pub(super) fn show_signature_help_auto(&mut self, window: &mut Window, cx: &mut Context<Self>) {
+        self.show_signature_help_impl(true, window, cx);
+    }
+
+    fn show_signature_help_impl(
+        &mut self,
+        use_delay: bool,
+        window: &mut Window,
+        cx: &mut Context<Self>,
     ) {
         if self.pending_rename.is_some() || self.has_visible_completions_menu() {
             return;
@@ -189,7 +202,11 @@ impl Editor {
         });
         let language = self.language_at(position, cx);
 
-        let signature_help_delay_ms = EditorSettings::get_global(cx).hover_popover_delay.0;
+        let signature_help_delay_ms = if use_delay {
+            EditorSettings::get_global(cx).hover_popover_delay.0
+        } else {
+            0
+        };
 
         self.signature_help_state
             .set_task(cx.spawn_in(window, async move |editor, cx| {

crates/settings_content/src/editor.rs πŸ”—

@@ -53,6 +53,7 @@ pub struct EditorSettingsContent {
     /// Default: true
     pub hover_popover_enabled: Option<bool>,
     /// Time to wait in milliseconds before showing the informational hover box.
+    /// This delay also applies to auto signature help when `auto_signature_help` is enabled.
     ///
     /// Default: 300
     pub hover_popover_delay: Option<DelayMs>,

docs/src/reference/all-settings.md πŸ”—

@@ -2317,7 +2317,7 @@ Example:
 
 ## Hover Popover Delay
 
-- Description: Time to wait in milliseconds before showing the informational hover box.
+- Description: Time to wait in milliseconds before showing the informational hover box. This delay also applies to auto signature help when `auto_signature_help` is enabled.
 - Setting: `hover_popover_delay`
 - Default: `300`