Detailed changes
@@ -135,10 +135,21 @@
"focus": true
}
],
- "alt-\\": "copilot::Suggest",
+ "ctrl->": "assistant::QuoteSelection"
+ }
+ },
+ {
+ "context": "Editor && mode == full && copilot_suggestion",
+ "bindings": {
"alt-]": "copilot::NextSuggestion",
"alt-[": "copilot::PreviousSuggestion",
- "ctrl->": "assistant::QuoteSelection"
+ "alt-right": "editor::AcceptPartialCopilotSuggestion"
+ }
+ },
+ {
+ "context": "Editor && !copilot_suggestion",
+ "bindings": {
+ "alt-\\": "copilot::Suggest"
}
},
{
@@ -176,10 +176,21 @@
"focus": false
}
],
- "alt-\\": "copilot::Suggest",
+ "cmd->": "assistant::QuoteSelection"
+ }
+ },
+ {
+ "context": "Editor && mode == full && copilot_suggestion",
+ "bindings": {
"alt-]": "copilot::NextSuggestion",
"alt-[": "copilot::PreviousSuggestion",
- "cmd->": "assistant::QuoteSelection"
+ "alt-right": "editor::AcceptPartialCopilotSuggestion"
+ }
+ },
+ {
+ "context": "Editor && !copilot_suggestion",
+ "bindings": {
+ "alt-\\": "copilot::Suggest"
}
},
{
@@ -119,6 +119,7 @@ impl_actions!(
gpui::actions!(
editor,
[
+ AcceptPartialCopilotSuggestion,
AddSelectionAbove,
AddSelectionBelow,
Backspace,
@@ -1627,6 +1627,10 @@ impl Editor {
key_context.set("extension", extension.to_string());
}
+ if self.has_active_copilot_suggestion(cx) {
+ key_context.add("copilot_suggestion");
+ }
+
key_context
}
@@ -3965,6 +3969,39 @@ impl Editor {
}
}
+ fn accept_partial_copilot_suggestion(
+ &mut self,
+ _: &AcceptPartialCopilotSuggestion,
+ cx: &mut ViewContext<Self>,
+ ) {
+ if self.selections.count() == 1 && self.has_active_copilot_suggestion(cx) {
+ if let Some(suggestion) = self.take_active_copilot_suggestion(cx) {
+ let mut partial_suggestion = suggestion
+ .text
+ .chars()
+ .by_ref()
+ .take_while(|c| c.is_alphabetic())
+ .collect::<String>();
+ if partial_suggestion.is_empty() {
+ partial_suggestion = suggestion
+ .text
+ .chars()
+ .by_ref()
+ .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
+ .collect::<String>();
+ }
+
+ cx.emit(EditorEvent::InputHandled {
+ utf16_range_to_replace: None,
+ text: partial_suggestion.clone().into(),
+ });
+ self.insert_with_autoindent_mode(&partial_suggestion, None, cx);
+ self.refresh_copilot_suggestions(true, cx);
+ cx.notify();
+ }
+ }
+ }
+
fn discard_copilot_suggestion(&mut self, cx: &mut ViewContext<Self>) -> bool {
if let Some(suggestion) = self.take_active_copilot_suggestion(cx) {
if let Some(copilot) = Copilot::global(cx) {
@@ -7623,6 +7623,128 @@ async fn test_copilot(executor: BackgroundExecutor, cx: &mut gpui::TestAppContex
});
}
+#[gpui::test(iterations = 10)]
+async fn test_accept_partial_copilot_suggestion(
+ executor: BackgroundExecutor,
+ cx: &mut gpui::TestAppContext,
+) {
+ // flaky
+ init_test(cx, |_| {});
+
+ let (copilot, copilot_lsp) = Copilot::fake(cx);
+ _ = cx.update(|cx| Copilot::set_global(copilot, cx));
+ let mut cx = EditorLspTestContext::new_rust(
+ lsp::ServerCapabilities {
+ completion_provider: Some(lsp::CompletionOptions {
+ trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
+ ..Default::default()
+ }),
+ ..Default::default()
+ },
+ cx,
+ )
+ .await;
+
+ // Setup the editor with a completion request.
+ cx.set_state(indoc! {"
+ oneΛ
+ two
+ three
+ "});
+ cx.simulate_keystroke(".");
+ let _ = handle_completion_request(
+ &mut cx,
+ indoc! {"
+ one.|<>
+ two
+ three
+ "},
+ vec![],
+ );
+ handle_copilot_completion_request(
+ &copilot_lsp,
+ vec![copilot::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.has_active_copilot_suggestion(cx));
+
+ // Accepting the first word of the suggestion should only accept the first word and still show the rest.
+ editor.accept_partial_copilot_suggestion(&Default::default(), cx);
+ assert!(editor.has_active_copilot_suggestion(cx));
+ assert_eq!(editor.text(cx), "one.copilot\ntwo\nthree\n");
+ assert_eq!(editor.display_text(cx), "one.copilot1\ntwo\nthree\n");
+
+ // Accepting next word should accept the non-word and copilot suggestion should be gone
+ editor.accept_partial_copilot_suggestion(&Default::default(), cx);
+ assert!(!editor.has_active_copilot_suggestion(cx));
+ assert_eq!(editor.text(cx), "one.copilot1\ntwo\nthree\n");
+ assert_eq!(editor.display_text(cx), "one.copilot1\ntwo\nthree\n");
+ });
+
+ // Reset the editor and check non-word and whitespace completion
+ cx.set_state(indoc! {"
+ oneΛ
+ two
+ three
+ "});
+ cx.simulate_keystroke(".");
+ let _ = handle_completion_request(
+ &mut cx,
+ indoc! {"
+ one.|<>
+ two
+ three
+ "},
+ vec![],
+ );
+ handle_copilot_completion_request(
+ &copilot_lsp,
+ vec![copilot::request::Completion {
+ text: "one.123. copilot\n 456".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.has_active_copilot_suggestion(cx));
+
+ // Accepting the first word (non-word) of the suggestion should only accept the first word and still show the rest.
+ editor.accept_partial_copilot_suggestion(&Default::default(), cx);
+ assert!(editor.has_active_copilot_suggestion(cx));
+ assert_eq!(editor.text(cx), "one.123. \ntwo\nthree\n");
+ assert_eq!(
+ editor.display_text(cx),
+ "one.123. copilot\n 456\ntwo\nthree\n"
+ );
+
+ // Accepting next word should accept the next word and copilot suggestion should still exist
+ editor.accept_partial_copilot_suggestion(&Default::default(), cx);
+ assert!(editor.has_active_copilot_suggestion(cx));
+ assert_eq!(editor.text(cx), "one.123. copilot\ntwo\nthree\n");
+ assert_eq!(
+ editor.display_text(cx),
+ "one.123. copilot\n 456\ntwo\nthree\n"
+ );
+
+ // Accepting the whitespace should accept the non-word/whitespaces with newline and copilot suggestion should be gone
+ editor.accept_partial_copilot_suggestion(&Default::default(), cx);
+ assert!(!editor.has_active_copilot_suggestion(cx));
+ assert_eq!(editor.text(cx), "one.123. copilot\n 456\ntwo\nthree\n");
+ assert_eq!(
+ editor.display_text(cx),
+ "one.123. copilot\n 456\ntwo\nthree\n"
+ );
+ });
+}
+
#[gpui::test]
async fn test_copilot_completion_invalidation(
executor: BackgroundExecutor,
@@ -338,6 +338,7 @@ impl EditorElement {
register_action(view, cx, Editor::display_cursor_names);
register_action(view, cx, Editor::unique_lines_case_insensitive);
register_action(view, cx, Editor::unique_lines_case_sensitive);
+ register_action(view, cx, Editor::accept_partial_copilot_suggestion);
}
fn register_key_listeners(