diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index c19c98e448..248e6fed95 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -3230,10 +3230,6 @@ impl Editor { } pub fn tab(&mut self, _: &Tab, cx: &mut ViewContext) { - if self.accept_copilot_suggestion(cx) { - return; - } - if self.move_to_next_snippet_tabstop(cx) { return; } @@ -3263,8 +3259,8 @@ impl Editor { // If the selection is empty and the cursor is in the leading whitespace before the // suggested indentation, then auto-indent the line. let cursor = selection.head(); + let current_indent = snapshot.indent_size_for_line(cursor.row); if let Some(suggested_indent) = suggested_indents.get(&cursor.row).copied() { - let current_indent = snapshot.indent_size_for_line(cursor.row); if cursor.column < suggested_indent.len && cursor.column <= current_indent.len && current_indent.len <= suggested_indent.len @@ -3283,6 +3279,16 @@ impl Editor { } } + // Accept copilot suggestion if there is only one selection and the cursor is + // in the leading whitespace. + if self.selections.count() == 1 + && selection.start.column >= current_indent.len + && self.has_active_copilot_suggestion(cx) + { + self.accept_copilot_suggestion(cx); + return; + } + // Otherwise, insert a hard or soft tab. let settings = cx.global::(); let language_name = buffer.language_at(cursor, cx).map(|l| l.name()); @@ -3306,7 +3312,8 @@ impl Editor { self.transact(cx, |this, cx| { this.buffer.update(cx, |b, cx| b.edit(edits, None, cx)); - this.change_selections(Some(Autoscroll::fit()), cx, |s| s.select(selections)) + this.change_selections(Some(Autoscroll::fit()), cx, |s| s.select(selections)); + this.refresh_copilot_suggestions(cx); }); } diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index 8c882f3ea8..6220ad3354 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -5881,7 +5881,7 @@ async fn test_move_to_enclosing_bracket(cx: &mut gpui::TestAppContext) { ); } -#[gpui::test] +#[gpui::test(iterations = 10)] async fn test_copilot(deterministic: Arc, cx: &mut gpui::TestAppContext) { let (copilot, copilot_lsp) = Copilot::fake(cx); cx.update(|cx| cx.set_global(copilot)); @@ -5918,7 +5918,6 @@ async fn test_copilot(deterministic: Arc, cx: &mut gpui::TestAppC &copilot_lsp, vec![copilot::request::Completion { text: "copilot1".into(), - position: lsp::Position::new(0, 5), range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 5)), ..Default::default() }], @@ -5962,7 +5961,6 @@ async fn test_copilot(deterministic: Arc, cx: &mut gpui::TestAppC &copilot_lsp, vec![copilot::request::Completion { text: "one.copilot1".into(), - position: lsp::Position::new(0, 4), range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 4)), ..Default::default() }], @@ -5996,7 +5994,6 @@ async fn test_copilot(deterministic: Arc, cx: &mut gpui::TestAppC &copilot_lsp, vec![copilot::request::Completion { text: "one.copilot2".into(), - position: lsp::Position::new(0, 5), range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 5)), ..Default::default() }], @@ -6062,6 +6059,43 @@ async fn test_copilot(deterministic: Arc, cx: &mut gpui::TestAppC assert_eq!(editor.display_text(cx), "one.cop\ntwo\nthree\n"); assert_eq!(editor.text(cx), "one.cop\ntwo\nthree\n"); }); + + // Reset the editor to verify how suggestions behave when tabbing on leading indentation. + cx.update_editor(|editor, cx| { + editor.set_text("fn foo() {\n \n}", cx); + editor.change_selections(None, cx, |s| { + s.select_ranges([Point::new(1, 2)..Point::new(1, 2)]) + }); + }); + handle_copilot_completion_request( + &copilot_lsp, + vec![copilot::request::Completion { + text: " let x = 4;".into(), + range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 2)), + ..Default::default() + }], + vec![], + ); + + cx.update_editor(|editor, cx| editor.next_copilot_suggestion(&Default::default(), cx)); + deterministic.advance_clock(COPILOT_DEBOUNCE_TIMEOUT); + cx.update_editor(|editor, cx| { + assert!(editor.has_active_copilot_suggestion(cx)); + assert_eq!(editor.display_text(cx), "fn foo() {\n let x = 4;\n}"); + assert_eq!(editor.text(cx), "fn foo() {\n \n}"); + + // Tabbing inside of leading whitespace inserts indentation without accepting the suggestion. + editor.tab(&Default::default(), cx); + assert!(editor.has_active_copilot_suggestion(cx)); + assert_eq!(editor.text(cx), "fn foo() {\n \n}"); + assert_eq!(editor.display_text(cx), "fn foo() {\n let x = 4;\n}"); + + // Tabbing again accepts the suggestion. + editor.tab(&Default::default(), cx); + assert!(!editor.has_active_copilot_suggestion(cx)); + assert_eq!(editor.text(cx), "fn foo() {\n let x = 4;\n}"); + assert_eq!(editor.display_text(cx), "fn foo() {\n let x = 4;\n}"); + }); } fn empty_range(row: usize, column: usize) -> Range {