From 124e63d07ceeea32a31218993a9b794ccb5e2f25 Mon Sep 17 00:00:00 2001 From: Thorsten Ball Date: Wed, 11 Dec 2024 17:13:22 +0100 Subject: [PATCH] Show inline completions when completion menu is visible (#21858) This changes the behavior of how we display inline completions and non-inline completions (i.e. completion menu). Previously we would never show inline completions if a completion menu was visible, meaning that we'd never show Copilot/Supermaven/... suggestions if the language server had a suggestion. With this change, we now display the inline completions even if there is a completion menu visible. In that case `` then accepts the inline completion and `` accepts the selected entry in the completion menu. Release Notes: - Changed how inline completions (Copilot, Supermaven, ...) and normal completions (from language servers) interact. Zed will now also show inline completions when the completion menu is visible. The user can accept the inline completion with `` and the active entry in the completion menu with ``. Previously, `` would also select the active entry in the completion menu. --------- Co-authored-by: Antonio --- assets/keymaps/default-linux.json | 12 +++- assets/keymaps/default-macos.json | 10 +++- .../src/copilot_completion_provider.rs | 60 +++---------------- crates/editor/src/editor.rs | 18 ++---- 4 files changed, 32 insertions(+), 68 deletions(-) diff --git a/assets/keymaps/default-linux.json b/assets/keymaps/default-linux.json index 3787f97a8d69bdd95d61e9488476852d63080f2b..f52ddfcc26000060dd10b2c54ef35a48cd9d64a2 100644 --- a/assets/keymaps/default-linux.json +++ b/assets/keymaps/default-linux.json @@ -468,13 +468,21 @@ }, { "context": "Editor && showing_completions", + "use_key_equivalents": true, + "bindings": { + "enter": "editor::ConfirmCompletion" + } + }, + { + "context": "Editor && !inline_completion && showing_completions", + "use_key_equivalents": true, "bindings": { - "enter": "editor::ConfirmCompletion", "tab": "editor::ComposeCompletion" } }, { - "context": "Editor && inline_completion && !showing_completions", + "context": "Editor && inline_completion", + "use_key_equivalents": true, "bindings": { "tab": "editor::AcceptInlineCompletion" } diff --git a/assets/keymaps/default-macos.json b/assets/keymaps/default-macos.json index 0e281079e97846e6211950decfdbfce27783e313..1ec898a84b81d6035bcc0d5a6b5360e025e2874b 100644 --- a/assets/keymaps/default-macos.json +++ b/assets/keymaps/default-macos.json @@ -541,12 +541,18 @@ "context": "Editor && showing_completions", "use_key_equivalents": true, "bindings": { - "enter": "editor::ConfirmCompletion", + "enter": "editor::ConfirmCompletion" + } + }, + { + "context": "Editor && !inline_completion && showing_completions", + "use_key_equivalents": true, + "bindings": { "tab": "editor::ComposeCompletion" } }, { - "context": "Editor && inline_completion && !showing_completions", + "context": "Editor && inline_completion", "use_key_equivalents": true, "bindings": { "tab": "editor::AcceptInlineCompletion" diff --git a/crates/copilot/src/copilot_completion_provider.rs b/crates/copilot/src/copilot_completion_provider.rs index 8d664e22899c493b3a694e802e30bc4e3b0714ce..949e2178a6f76dc87f482c4a847357b245aec970 100644 --- a/crates/copilot/src/copilot_completion_provider.rs +++ b/crates/copilot/src/copilot_completion_provider.rs @@ -296,7 +296,6 @@ mod tests { editor.set_inline_completion_provider(Some(copilot_provider), cx) }); - // When inserting, ensure autocompletion is favored over Copilot suggestions. cx.set_state(indoc! {" oneˇ two @@ -323,8 +322,9 @@ mod tests { ); executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT); cx.update_editor(|editor, cx| { + // We want to show both: the inline completion and the completion menu assert!(editor.context_menu_visible()); - assert!(!editor.has_active_inline_completion()); + assert!(editor.has_active_inline_completion()); // Confirming a completion inserts it and hides the context menu, without showing // the copilot suggestion afterwards. @@ -338,40 +338,7 @@ mod tests { assert_eq!(editor.display_text(cx), "one.completion_a\ntwo\nthree\n"); }); - // Ensure Copilot suggestions are shown right away if no autocompletion is available. - cx.set_state(indoc! {" - oneˇ - two - three - "}); - cx.simulate_keystroke("."); - drop(handle_completion_request( - &mut cx, - indoc! {" - one.|<> - two - three - "}, - vec![], - )); - handle_copilot_completion_request( - &copilot_lsp, - vec![crate::request::Completion { - text: "one.copilot1".into(), - range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 4)), - ..Default::default() - }], - vec![], - ); - executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT); - cx.update_editor(|editor, cx| { - assert!(!editor.context_menu_visible()); - assert!(editor.has_active_inline_completion()); - assert_eq!(editor.display_text(cx), "one.copilot1\ntwo\nthree\n"); - assert_eq!(editor.text(cx), "one.\ntwo\nthree\n"); - }); - - // Reset editor, and ensure autocompletion is still favored over Copilot suggestions. + // Reset editor and test that accepting completions works cx.set_state(indoc! {" oneˇ two @@ -399,17 +366,12 @@ mod tests { executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT); cx.update_editor(|editor, cx| { assert!(editor.context_menu_visible()); - assert!(!editor.has_active_inline_completion()); - - // When hiding the context menu, the Copilot suggestion becomes visible. - editor.cancel(&Default::default(), cx); - assert!(!editor.context_menu_visible()); assert!(editor.has_active_inline_completion()); assert_eq!(editor.display_text(cx), "one.copilot1\ntwo\nthree\n"); assert_eq!(editor.text(cx), "one.\ntwo\nthree\n"); }); - // Ensure existing completion is interpolated when inserting again. + // Ensure existing inline completion is interpolated when inserting again. cx.simulate_keystroke("c"); executor.run_until_parked(); cx.update_editor(|editor, cx| { @@ -880,7 +842,7 @@ mod tests { cx.update_editor(|editor, cx| editor.next_inline_completion(&Default::default(), cx)); executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT); cx.update_editor(|editor, cx| { - assert!(!editor.context_menu_visible(), "Even there are some completions available, those are not triggered when active copilot suggestion is present"); + assert!(!editor.context_menu_visible()); assert!(editor.has_active_inline_completion()); assert_eq!(editor.display_text(cx), "one\ntwo.foo()\nthree\n"); assert_eq!(editor.text(cx), "one\ntw\nthree\n"); @@ -934,15 +896,9 @@ mod tests { ); executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT); cx.update_editor(|editor, cx| { - assert!( - editor.context_menu_visible(), - "On completion trigger input, the completions should be fetched and visible" - ); - assert!( - !editor.has_active_inline_completion(), - "On completion trigger input, copilot suggestion should be dismissed" - ); - assert_eq!(editor.display_text(cx), "one\ntwo.\nthree\n"); + assert!(editor.context_menu_visible()); + assert!(editor.has_active_inline_completion(),); + assert_eq!(editor.display_text(cx), "one\ntwo.foo()\nthree\n"); assert_eq!(editor.text(cx), "one\ntwo.\nthree\n"); }); } diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 1336a76f22194518bdfb3065fc3038fdde6f44cc..97eb4ee6bffef65454ad9901a7b567681ded43d0 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -3687,16 +3687,13 @@ impl Editor { menu.resolve_visible_completions(editor.completion_provider.as_deref(), cx); *context_menu = Some(CodeContextMenu::Completions(menu)); drop(context_menu); - editor.discard_inline_completion(false, cx); cx.notify(); } else if editor.completion_tasks.len() <= 1 { // If there are no more completion tasks and the last menu was // empty, we should hide it. If it was already hidden, we should // also show the copilot completion when available. drop(context_menu); - if editor.hide_context_menu(cx).is_none() { - editor.update_visible_inline_completion(cx); - } + editor.hide_context_menu(cx); } })?; @@ -3732,6 +3729,7 @@ impl Editor { ) -> Option>> { use language::ToOffset as _; + self.discard_inline_completion(true, cx); let completions_menu = if let CodeContextMenu::Completions(menu) = self.hide_context_menu(cx)? { menu @@ -4475,6 +4473,8 @@ impl Editor { _: &AcceptInlineCompletion, cx: &mut ViewContext, ) { + self.hide_context_menu(cx); + let Some(active_inline_completion) = self.active_inline_completion.as_ref() else { return; }; @@ -4629,9 +4629,7 @@ impl Editor { let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer)); let excerpt_id = cursor.excerpt_id; - if self.context_menu.read().is_some() - || (!self.completion_tasks.is_empty() && !self.has_active_inline_completion()) - || !offset_selection.is_empty() + if !offset_selection.is_empty() || self .active_inline_completion .as_ref() @@ -4978,11 +4976,7 @@ impl Editor { fn hide_context_menu(&mut self, cx: &mut ViewContext) -> Option { cx.notify(); self.completion_tasks.clear(); - let context_menu = self.context_menu.write().take(); - if context_menu.is_some() { - self.update_visible_inline_completion(cx); - } - context_menu + self.context_menu.write().take() } fn show_snippet_choices(