1use crate::{Completion, Copilot};
   2use anyhow::Result;
   3use edit_prediction::{Direction, EditPrediction, EditPredictionProvider};
   4use gpui::{App, Context, Entity, EntityId, Task};
   5use language::{Buffer, OffsetRangeExt, ToOffset, language_settings::AllLanguageSettings};
   6use settings::Settings;
   7use std::{path::Path, time::Duration};
   8
   9pub const COPILOT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(75);
  10
  11pub struct CopilotCompletionProvider {
  12    cycled: bool,
  13    buffer_id: Option<EntityId>,
  14    completions: Vec<Completion>,
  15    active_completion_index: usize,
  16    file_extension: Option<String>,
  17    pending_refresh: Option<Task<Result<()>>>,
  18    pending_cycling_refresh: Option<Task<Result<()>>>,
  19    copilot: Entity<Copilot>,
  20}
  21
  22impl CopilotCompletionProvider {
  23    pub fn new(copilot: Entity<Copilot>) -> Self {
  24        Self {
  25            cycled: false,
  26            buffer_id: None,
  27            completions: Vec::new(),
  28            active_completion_index: 0,
  29            file_extension: None,
  30            pending_refresh: None,
  31            pending_cycling_refresh: None,
  32            copilot,
  33        }
  34    }
  35
  36    fn active_completion(&self) -> Option<&Completion> {
  37        self.completions.get(self.active_completion_index)
  38    }
  39
  40    fn push_completion(&mut self, new_completion: Completion) {
  41        for completion in &self.completions {
  42            if completion.text == new_completion.text && completion.range == new_completion.range {
  43                return;
  44            }
  45        }
  46        self.completions.push(new_completion);
  47    }
  48}
  49
  50impl EditPredictionProvider for CopilotCompletionProvider {
  51    fn name() -> &'static str {
  52        "copilot"
  53    }
  54
  55    fn display_name() -> &'static str {
  56        "Copilot"
  57    }
  58
  59    fn show_completions_in_menu() -> bool {
  60        true
  61    }
  62
  63    fn show_tab_accept_marker() -> bool {
  64        true
  65    }
  66
  67    fn supports_jump_to_edit() -> bool {
  68        false
  69    }
  70
  71    fn is_refreshing(&self) -> bool {
  72        self.pending_refresh.is_some() && self.completions.is_empty()
  73    }
  74
  75    fn is_enabled(
  76        &self,
  77        _buffer: &Entity<Buffer>,
  78        _cursor_position: language::Anchor,
  79        cx: &App,
  80    ) -> bool {
  81        self.copilot.read(cx).status().is_authorized()
  82    }
  83
  84    fn refresh(
  85        &mut self,
  86        buffer: Entity<Buffer>,
  87        cursor_position: language::Anchor,
  88        debounce: bool,
  89        cx: &mut Context<Self>,
  90    ) {
  91        let copilot = self.copilot.clone();
  92        self.pending_refresh = Some(cx.spawn(async move |this, cx| {
  93            if debounce {
  94                cx.background_executor()
  95                    .timer(COPILOT_DEBOUNCE_TIMEOUT)
  96                    .await;
  97            }
  98
  99            let completions = copilot
 100                .update(cx, |copilot, cx| {
 101                    copilot.completions(&buffer, cursor_position, cx)
 102                })?
 103                .await?;
 104
 105            this.update(cx, |this, cx| {
 106                if !completions.is_empty() {
 107                    this.cycled = false;
 108                    this.pending_refresh = None;
 109                    this.pending_cycling_refresh = None;
 110                    this.completions.clear();
 111                    this.active_completion_index = 0;
 112                    this.buffer_id = Some(buffer.entity_id());
 113                    this.file_extension = buffer.read(cx).file().and_then(|file| {
 114                        Some(
 115                            Path::new(file.file_name(cx))
 116                                .extension()?
 117                                .to_str()?
 118                                .to_string(),
 119                        )
 120                    });
 121
 122                    for completion in completions {
 123                        this.push_completion(completion);
 124                    }
 125                    cx.notify();
 126                }
 127            })?;
 128
 129            Ok(())
 130        }));
 131    }
 132
 133    fn cycle(
 134        &mut self,
 135        buffer: Entity<Buffer>,
 136        cursor_position: language::Anchor,
 137        direction: Direction,
 138        cx: &mut Context<Self>,
 139    ) {
 140        if self.cycled {
 141            match direction {
 142                Direction::Prev => {
 143                    self.active_completion_index = if self.active_completion_index == 0 {
 144                        self.completions.len().saturating_sub(1)
 145                    } else {
 146                        self.active_completion_index - 1
 147                    };
 148                }
 149                Direction::Next => {
 150                    if self.completions.is_empty() {
 151                        self.active_completion_index = 0
 152                    } else {
 153                        self.active_completion_index =
 154                            (self.active_completion_index + 1) % self.completions.len();
 155                    }
 156                }
 157            }
 158
 159            cx.notify();
 160        } else {
 161            let copilot = self.copilot.clone();
 162            self.pending_cycling_refresh = Some(cx.spawn(async move |this, cx| {
 163                let completions = copilot
 164                    .update(cx, |copilot, cx| {
 165                        copilot.completions_cycling(&buffer, cursor_position, cx)
 166                    })?
 167                    .await?;
 168
 169                this.update(cx, |this, cx| {
 170                    this.cycled = true;
 171                    this.file_extension = buffer.read(cx).file().and_then(|file| {
 172                        Some(
 173                            Path::new(file.file_name(cx))
 174                                .extension()?
 175                                .to_str()?
 176                                .to_string(),
 177                        )
 178                    });
 179                    for completion in completions {
 180                        this.push_completion(completion);
 181                    }
 182                    this.cycle(buffer, cursor_position, direction, cx);
 183                })?;
 184
 185                Ok(())
 186            }));
 187        }
 188    }
 189
 190    fn accept(&mut self, cx: &mut Context<Self>) {
 191        if let Some(completion) = self.active_completion() {
 192            self.copilot
 193                .update(cx, |copilot, cx| copilot.accept_completion(completion, cx))
 194                .detach_and_log_err(cx);
 195        }
 196    }
 197
 198    fn discard(&mut self, cx: &mut Context<Self>) {
 199        let settings = AllLanguageSettings::get_global(cx);
 200
 201        let copilot_enabled = settings.show_edit_predictions(None, cx);
 202
 203        if !copilot_enabled {
 204            return;
 205        }
 206
 207        self.copilot
 208            .update(cx, |copilot, cx| {
 209                copilot.discard_completions(&self.completions, cx)
 210            })
 211            .detach_and_log_err(cx);
 212    }
 213
 214    fn suggest(
 215        &mut self,
 216        buffer: &Entity<Buffer>,
 217        cursor_position: language::Anchor,
 218        cx: &mut Context<Self>,
 219    ) -> Option<EditPrediction> {
 220        let buffer_id = buffer.entity_id();
 221        let buffer = buffer.read(cx);
 222        let completion = self.active_completion()?;
 223        if Some(buffer_id) != self.buffer_id
 224            || !completion.range.start.is_valid(buffer)
 225            || !completion.range.end.is_valid(buffer)
 226        {
 227            return None;
 228        }
 229
 230        let mut completion_range = completion.range.to_offset(buffer);
 231        let prefix_len = common_prefix(
 232            buffer.chars_for_range(completion_range.clone()),
 233            completion.text.chars(),
 234        );
 235        completion_range.start += prefix_len;
 236        let suffix_len = common_prefix(
 237            buffer.reversed_chars_for_range(completion_range.clone()),
 238            completion.text[prefix_len..].chars().rev(),
 239        );
 240        completion_range.end = completion_range.end.saturating_sub(suffix_len);
 241
 242        if completion_range.is_empty()
 243            && completion_range.start == cursor_position.to_offset(buffer)
 244        {
 245            let completion_text = &completion.text[prefix_len..completion.text.len() - suffix_len];
 246            if completion_text.trim().is_empty() {
 247                None
 248            } else {
 249                let position = cursor_position.bias_right(buffer);
 250                Some(EditPrediction::Local {
 251                    id: None,
 252                    edits: vec![(position..position, completion_text.into())],
 253                    edit_preview: None,
 254                })
 255            }
 256        } else {
 257            None
 258        }
 259    }
 260}
 261
 262fn common_prefix<T1: Iterator<Item = char>, T2: Iterator<Item = char>>(a: T1, b: T2) -> usize {
 263    a.zip(b)
 264        .take_while(|(a, b)| a == b)
 265        .map(|(a, _)| a.len_utf8())
 266        .sum()
 267}
 268
 269#[cfg(test)]
 270mod tests {
 271    use super::*;
 272    use editor::{
 273        Editor, ExcerptRange, MultiBuffer, SelectionEffects,
 274        test::editor_lsp_test_context::EditorLspTestContext,
 275    };
 276    use fs::FakeFs;
 277    use futures::StreamExt;
 278    use gpui::{AppContext as _, BackgroundExecutor, TestAppContext, UpdateGlobal};
 279    use indoc::indoc;
 280    use language::{
 281        Point,
 282        language_settings::{CompletionSettingsContent, LspInsertMode, WordsCompletionMode},
 283    };
 284    use project::Project;
 285    use serde_json::json;
 286    use settings::{AllLanguageSettingsContent, SettingsStore};
 287    use std::future::Future;
 288    use util::{
 289        path,
 290        test::{TextRangeMarker, marked_text_ranges_by},
 291    };
 292
 293    #[gpui::test(iterations = 10)]
 294    async fn test_copilot(executor: BackgroundExecutor, cx: &mut TestAppContext) {
 295        // flaky
 296        init_test(cx, |settings| {
 297            settings.defaults.completions = Some(CompletionSettingsContent {
 298                words: Some(WordsCompletionMode::Disabled),
 299                words_min_length: Some(0),
 300                lsp_insert_mode: Some(LspInsertMode::Insert),
 301                ..Default::default()
 302            });
 303        });
 304
 305        let (copilot, copilot_lsp) = Copilot::fake(cx);
 306        let mut cx = EditorLspTestContext::new_rust(
 307            lsp::ServerCapabilities {
 308                completion_provider: Some(lsp::CompletionOptions {
 309                    trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
 310                    ..Default::default()
 311                }),
 312                ..Default::default()
 313            },
 314            cx,
 315        )
 316        .await;
 317        let copilot_provider = cx.new(|_| CopilotCompletionProvider::new(copilot));
 318        cx.update_editor(|editor, window, cx| {
 319            editor.set_edit_prediction_provider(Some(copilot_provider), window, cx)
 320        });
 321
 322        cx.set_state(indoc! {"
 323            oneˇ
 324            two
 325            three
 326        "});
 327        cx.simulate_keystroke(".");
 328        drop(handle_completion_request(
 329            &mut cx,
 330            indoc! {"
 331                one.|<>
 332                two
 333                three
 334            "},
 335            vec!["completion_a", "completion_b"],
 336        ));
 337        handle_copilot_completion_request(
 338            &copilot_lsp,
 339            vec![crate::request::Completion {
 340                text: "one.copilot1".into(),
 341                range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 4)),
 342                ..Default::default()
 343            }],
 344            vec![],
 345        );
 346        executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
 347        cx.update_editor(|editor, window, cx| {
 348            assert!(editor.context_menu_visible());
 349            assert!(editor.has_active_edit_prediction());
 350            // Since we have both, the copilot suggestion is existing but does not show up as ghost text
 351            assert_eq!(editor.text(cx), "one.\ntwo\nthree\n");
 352            assert_eq!(editor.display_text(cx), "one.\ntwo\nthree\n");
 353
 354            // Confirming a non-copilot completion inserts it and hides the context menu, without showing
 355            // the copilot suggestion afterwards.
 356            editor
 357                .confirm_completion(&Default::default(), window, cx)
 358                .unwrap()
 359                .detach();
 360            assert!(!editor.context_menu_visible());
 361            assert!(!editor.has_active_edit_prediction());
 362            assert_eq!(editor.text(cx), "one.completion_a\ntwo\nthree\n");
 363            assert_eq!(editor.display_text(cx), "one.completion_a\ntwo\nthree\n");
 364        });
 365
 366        // Reset editor and only return copilot suggestions
 367        cx.set_state(indoc! {"
 368            oneˇ
 369            two
 370            three
 371        "});
 372        cx.simulate_keystroke(".");
 373
 374        drop(handle_completion_request(
 375            &mut cx,
 376            indoc! {"
 377                one.|<>
 378                two
 379                three
 380            "},
 381            vec![],
 382        ));
 383        handle_copilot_completion_request(
 384            &copilot_lsp,
 385            vec![crate::request::Completion {
 386                text: "one.copilot1".into(),
 387                range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 4)),
 388                ..Default::default()
 389            }],
 390            vec![],
 391        );
 392        executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
 393        cx.update_editor(|editor, _, cx| {
 394            assert!(!editor.context_menu_visible());
 395            assert!(editor.has_active_edit_prediction());
 396            // Since only the copilot is available, it's shown inline
 397            assert_eq!(editor.display_text(cx), "one.copilot1\ntwo\nthree\n");
 398            assert_eq!(editor.text(cx), "one.\ntwo\nthree\n");
 399        });
 400
 401        // Ensure existing edit prediction is interpolated when inserting again.
 402        cx.simulate_keystroke("c");
 403        executor.run_until_parked();
 404        cx.update_editor(|editor, _, cx| {
 405            assert!(!editor.context_menu_visible());
 406            assert!(editor.has_active_edit_prediction());
 407            assert_eq!(editor.display_text(cx), "one.copilot1\ntwo\nthree\n");
 408            assert_eq!(editor.text(cx), "one.c\ntwo\nthree\n");
 409        });
 410
 411        // After debouncing, new Copilot completions should be requested.
 412        handle_copilot_completion_request(
 413            &copilot_lsp,
 414            vec![crate::request::Completion {
 415                text: "one.copilot2".into(),
 416                range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 5)),
 417                ..Default::default()
 418            }],
 419            vec![],
 420        );
 421        executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
 422        cx.update_editor(|editor, window, cx| {
 423            assert!(!editor.context_menu_visible());
 424            assert!(editor.has_active_edit_prediction());
 425            assert_eq!(editor.display_text(cx), "one.copilot2\ntwo\nthree\n");
 426            assert_eq!(editor.text(cx), "one.c\ntwo\nthree\n");
 427
 428            // Canceling should remove the active Copilot suggestion.
 429            editor.cancel(&Default::default(), window, cx);
 430            assert!(!editor.has_active_edit_prediction());
 431            assert_eq!(editor.display_text(cx), "one.c\ntwo\nthree\n");
 432            assert_eq!(editor.text(cx), "one.c\ntwo\nthree\n");
 433
 434            // After canceling, tabbing shouldn't insert the previously shown suggestion.
 435            editor.tab(&Default::default(), window, cx);
 436            assert!(!editor.has_active_edit_prediction());
 437            assert_eq!(editor.display_text(cx), "one.c   \ntwo\nthree\n");
 438            assert_eq!(editor.text(cx), "one.c   \ntwo\nthree\n");
 439
 440            // When undoing the previously active suggestion is shown again.
 441            editor.undo(&Default::default(), window, cx);
 442            assert!(editor.has_active_edit_prediction());
 443            assert_eq!(editor.display_text(cx), "one.copilot2\ntwo\nthree\n");
 444            assert_eq!(editor.text(cx), "one.c\ntwo\nthree\n");
 445        });
 446
 447        // If an edit occurs outside of this editor, the suggestion is still correctly interpolated.
 448        cx.update_buffer(|buffer, cx| buffer.edit([(5..5, "o")], None, cx));
 449        cx.update_editor(|editor, window, cx| {
 450            assert!(editor.has_active_edit_prediction());
 451            assert_eq!(editor.display_text(cx), "one.copilot2\ntwo\nthree\n");
 452            assert_eq!(editor.text(cx), "one.co\ntwo\nthree\n");
 453
 454            // AcceptEditPrediction when there is an active suggestion inserts it.
 455            editor.accept_edit_prediction(&Default::default(), window, cx);
 456            assert!(!editor.has_active_edit_prediction());
 457            assert_eq!(editor.display_text(cx), "one.copilot2\ntwo\nthree\n");
 458            assert_eq!(editor.text(cx), "one.copilot2\ntwo\nthree\n");
 459
 460            // When undoing the previously active suggestion is shown again.
 461            editor.undo(&Default::default(), window, cx);
 462            assert!(editor.has_active_edit_prediction());
 463            assert_eq!(editor.display_text(cx), "one.copilot2\ntwo\nthree\n");
 464            assert_eq!(editor.text(cx), "one.co\ntwo\nthree\n");
 465
 466            // Hide suggestion.
 467            editor.cancel(&Default::default(), window, cx);
 468            assert!(!editor.has_active_edit_prediction());
 469            assert_eq!(editor.display_text(cx), "one.co\ntwo\nthree\n");
 470            assert_eq!(editor.text(cx), "one.co\ntwo\nthree\n");
 471        });
 472
 473        // If an edit occurs outside of this editor but no suggestion is being shown,
 474        // we won't make it visible.
 475        cx.update_buffer(|buffer, cx| buffer.edit([(6..6, "p")], None, cx));
 476        cx.update_editor(|editor, _, cx| {
 477            assert!(!editor.has_active_edit_prediction());
 478            assert_eq!(editor.display_text(cx), "one.cop\ntwo\nthree\n");
 479            assert_eq!(editor.text(cx), "one.cop\ntwo\nthree\n");
 480        });
 481
 482        // Reset the editor to verify how suggestions behave when tabbing on leading indentation.
 483        cx.update_editor(|editor, window, cx| {
 484            editor.set_text("fn foo() {\n  \n}", window, cx);
 485            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 486                s.select_ranges([Point::new(1, 2)..Point::new(1, 2)])
 487            });
 488        });
 489        handle_copilot_completion_request(
 490            &copilot_lsp,
 491            vec![crate::request::Completion {
 492                text: "    let x = 4;".into(),
 493                range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 2)),
 494                ..Default::default()
 495            }],
 496            vec![],
 497        );
 498
 499        cx.update_editor(|editor, window, cx| {
 500            editor.next_edit_prediction(&Default::default(), window, cx)
 501        });
 502        executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
 503        cx.update_editor(|editor, window, cx| {
 504            assert!(editor.has_active_edit_prediction());
 505            assert_eq!(editor.display_text(cx), "fn foo() {\n    let x = 4;\n}");
 506            assert_eq!(editor.text(cx), "fn foo() {\n  \n}");
 507
 508            // Tabbing inside of leading whitespace inserts indentation without accepting the suggestion.
 509            editor.tab(&Default::default(), window, cx);
 510            assert!(editor.has_active_edit_prediction());
 511            assert_eq!(editor.text(cx), "fn foo() {\n    \n}");
 512            assert_eq!(editor.display_text(cx), "fn foo() {\n    let x = 4;\n}");
 513
 514            // Using AcceptEditPrediction again accepts the suggestion.
 515            editor.accept_edit_prediction(&Default::default(), window, cx);
 516            assert!(!editor.has_active_edit_prediction());
 517            assert_eq!(editor.text(cx), "fn foo() {\n    let x = 4;\n}");
 518            assert_eq!(editor.display_text(cx), "fn foo() {\n    let x = 4;\n}");
 519        });
 520    }
 521
 522    #[gpui::test(iterations = 10)]
 523    async fn test_accept_partial_copilot_suggestion(
 524        executor: BackgroundExecutor,
 525        cx: &mut TestAppContext,
 526    ) {
 527        // flaky
 528        init_test(cx, |settings| {
 529            settings.defaults.completions = Some(CompletionSettingsContent {
 530                words: Some(WordsCompletionMode::Disabled),
 531                words_min_length: Some(0),
 532                lsp_insert_mode: Some(LspInsertMode::Insert),
 533                ..Default::default()
 534            });
 535        });
 536
 537        let (copilot, copilot_lsp) = Copilot::fake(cx);
 538        let mut cx = EditorLspTestContext::new_rust(
 539            lsp::ServerCapabilities {
 540                completion_provider: Some(lsp::CompletionOptions {
 541                    trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
 542                    ..Default::default()
 543                }),
 544                ..Default::default()
 545            },
 546            cx,
 547        )
 548        .await;
 549        let copilot_provider = cx.new(|_| CopilotCompletionProvider::new(copilot));
 550        cx.update_editor(|editor, window, cx| {
 551            editor.set_edit_prediction_provider(Some(copilot_provider), window, cx)
 552        });
 553
 554        // Setup the editor with a completion request.
 555        cx.set_state(indoc! {"
 556            oneˇ
 557            two
 558            three
 559        "});
 560        cx.simulate_keystroke(".");
 561        drop(handle_completion_request(
 562            &mut cx,
 563            indoc! {"
 564                one.|<>
 565                two
 566                three
 567            "},
 568            vec![],
 569        ));
 570        handle_copilot_completion_request(
 571            &copilot_lsp,
 572            vec![crate::request::Completion {
 573                text: "one.copilot1".into(),
 574                range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 4)),
 575                ..Default::default()
 576            }],
 577            vec![],
 578        );
 579        executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
 580        cx.update_editor(|editor, window, cx| {
 581            assert!(editor.has_active_edit_prediction());
 582
 583            // Accepting the first word of the suggestion should only accept the first word and still show the rest.
 584            editor.accept_partial_edit_prediction(&Default::default(), window, cx);
 585            assert!(editor.has_active_edit_prediction());
 586            assert_eq!(editor.text(cx), "one.copilot\ntwo\nthree\n");
 587            assert_eq!(editor.display_text(cx), "one.copilot1\ntwo\nthree\n");
 588
 589            // Accepting next word should accept the non-word and copilot suggestion should be gone
 590            editor.accept_partial_edit_prediction(&Default::default(), window, cx);
 591            assert!(!editor.has_active_edit_prediction());
 592            assert_eq!(editor.text(cx), "one.copilot1\ntwo\nthree\n");
 593            assert_eq!(editor.display_text(cx), "one.copilot1\ntwo\nthree\n");
 594        });
 595
 596        // Reset the editor and check non-word and whitespace completion
 597        cx.set_state(indoc! {"
 598            oneˇ
 599            two
 600            three
 601        "});
 602        cx.simulate_keystroke(".");
 603        drop(handle_completion_request(
 604            &mut cx,
 605            indoc! {"
 606                one.|<>
 607                two
 608                three
 609            "},
 610            vec![],
 611        ));
 612        handle_copilot_completion_request(
 613            &copilot_lsp,
 614            vec![crate::request::Completion {
 615                text: "one.123. copilot\n 456".into(),
 616                range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 4)),
 617                ..Default::default()
 618            }],
 619            vec![],
 620        );
 621        executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
 622        cx.update_editor(|editor, window, cx| {
 623            assert!(editor.has_active_edit_prediction());
 624
 625            // Accepting the first word (non-word) of the suggestion should only accept the first word and still show the rest.
 626            editor.accept_partial_edit_prediction(&Default::default(), window, cx);
 627            assert!(editor.has_active_edit_prediction());
 628            assert_eq!(editor.text(cx), "one.123. \ntwo\nthree\n");
 629            assert_eq!(
 630                editor.display_text(cx),
 631                "one.123. copilot\n 456\ntwo\nthree\n"
 632            );
 633
 634            // Accepting next word should accept the next word and copilot suggestion should still exist
 635            editor.accept_partial_edit_prediction(&Default::default(), window, cx);
 636            assert!(editor.has_active_edit_prediction());
 637            assert_eq!(editor.text(cx), "one.123. copilot\ntwo\nthree\n");
 638            assert_eq!(
 639                editor.display_text(cx),
 640                "one.123. copilot\n 456\ntwo\nthree\n"
 641            );
 642
 643            // Accepting the whitespace should accept the non-word/whitespaces with newline and copilot suggestion should be gone
 644            editor.accept_partial_edit_prediction(&Default::default(), window, cx);
 645            assert!(!editor.has_active_edit_prediction());
 646            assert_eq!(editor.text(cx), "one.123. copilot\n 456\ntwo\nthree\n");
 647            assert_eq!(
 648                editor.display_text(cx),
 649                "one.123. copilot\n 456\ntwo\nthree\n"
 650            );
 651        });
 652    }
 653
 654    #[gpui::test]
 655    async fn test_copilot_completion_invalidation(
 656        executor: BackgroundExecutor,
 657        cx: &mut TestAppContext,
 658    ) {
 659        init_test(cx, |_| {});
 660
 661        let (copilot, copilot_lsp) = Copilot::fake(cx);
 662        let mut cx = EditorLspTestContext::new_rust(
 663            lsp::ServerCapabilities {
 664                completion_provider: Some(lsp::CompletionOptions {
 665                    trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
 666                    ..Default::default()
 667                }),
 668                ..Default::default()
 669            },
 670            cx,
 671        )
 672        .await;
 673        let copilot_provider = cx.new(|_| CopilotCompletionProvider::new(copilot));
 674        cx.update_editor(|editor, window, cx| {
 675            editor.set_edit_prediction_provider(Some(copilot_provider), window, cx)
 676        });
 677
 678        cx.set_state(indoc! {"
 679            one
 680            twˇ
 681            three
 682        "});
 683
 684        handle_copilot_completion_request(
 685            &copilot_lsp,
 686            vec![crate::request::Completion {
 687                text: "two.foo()".into(),
 688                range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 2)),
 689                ..Default::default()
 690            }],
 691            vec![],
 692        );
 693        cx.update_editor(|editor, window, cx| {
 694            editor.next_edit_prediction(&Default::default(), window, cx)
 695        });
 696        executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
 697        cx.update_editor(|editor, window, cx| {
 698            assert!(editor.has_active_edit_prediction());
 699            assert_eq!(editor.display_text(cx), "one\ntwo.foo()\nthree\n");
 700            assert_eq!(editor.text(cx), "one\ntw\nthree\n");
 701
 702            editor.backspace(&Default::default(), window, cx);
 703            assert!(editor.has_active_edit_prediction());
 704            assert_eq!(editor.display_text(cx), "one\ntwo.foo()\nthree\n");
 705            assert_eq!(editor.text(cx), "one\nt\nthree\n");
 706
 707            editor.backspace(&Default::default(), window, cx);
 708            assert!(editor.has_active_edit_prediction());
 709            assert_eq!(editor.display_text(cx), "one\ntwo.foo()\nthree\n");
 710            assert_eq!(editor.text(cx), "one\n\nthree\n");
 711
 712            // Deleting across the original suggestion range invalidates it.
 713            editor.backspace(&Default::default(), window, cx);
 714            assert!(!editor.has_active_edit_prediction());
 715            assert_eq!(editor.display_text(cx), "one\nthree\n");
 716            assert_eq!(editor.text(cx), "one\nthree\n");
 717
 718            // Undoing the deletion restores the suggestion.
 719            editor.undo(&Default::default(), window, cx);
 720            assert!(editor.has_active_edit_prediction());
 721            assert_eq!(editor.display_text(cx), "one\ntwo.foo()\nthree\n");
 722            assert_eq!(editor.text(cx), "one\n\nthree\n");
 723        });
 724    }
 725
 726    #[gpui::test]
 727    async fn test_copilot_multibuffer(executor: BackgroundExecutor, cx: &mut TestAppContext) {
 728        init_test(cx, |_| {});
 729
 730        let (copilot, copilot_lsp) = Copilot::fake(cx);
 731
 732        let buffer_1 = cx.new(|cx| Buffer::local("a = 1\nb = 2\n", cx));
 733        let buffer_2 = cx.new(|cx| Buffer::local("c = 3\nd = 4\n", cx));
 734        let multibuffer = cx.new(|cx| {
 735            let mut multibuffer = MultiBuffer::new(language::Capability::ReadWrite);
 736            multibuffer.push_excerpts(
 737                buffer_1.clone(),
 738                [ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0))],
 739                cx,
 740            );
 741            multibuffer.push_excerpts(
 742                buffer_2.clone(),
 743                [ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0))],
 744                cx,
 745            );
 746            multibuffer
 747        });
 748        let editor =
 749            cx.add_window(|window, cx| Editor::for_multibuffer(multibuffer, None, window, cx));
 750        editor
 751            .update(cx, |editor, window, cx| {
 752                use gpui::Focusable;
 753                window.focus(&editor.focus_handle(cx));
 754            })
 755            .unwrap();
 756        let copilot_provider = cx.new(|_| CopilotCompletionProvider::new(copilot));
 757        editor
 758            .update(cx, |editor, window, cx| {
 759                editor.set_edit_prediction_provider(Some(copilot_provider), window, cx)
 760            })
 761            .unwrap();
 762
 763        handle_copilot_completion_request(
 764            &copilot_lsp,
 765            vec![crate::request::Completion {
 766                text: "b = 2 + a".into(),
 767                range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 5)),
 768                ..Default::default()
 769            }],
 770            vec![],
 771        );
 772        _ = editor.update(cx, |editor, window, cx| {
 773            // Ensure copilot suggestions are shown for the first excerpt.
 774            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 775                s.select_ranges([Point::new(1, 5)..Point::new(1, 5)])
 776            });
 777            editor.next_edit_prediction(&Default::default(), window, cx);
 778        });
 779        executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
 780        _ = editor.update(cx, |editor, _, cx| {
 781            assert!(editor.has_active_edit_prediction());
 782            assert_eq!(
 783                editor.display_text(cx),
 784                "\n\na = 1\nb = 2 + a\n\n\n\nc = 3\nd = 4\n"
 785            );
 786            assert_eq!(editor.text(cx), "a = 1\nb = 2\n\nc = 3\nd = 4\n");
 787        });
 788
 789        handle_copilot_completion_request(
 790            &copilot_lsp,
 791            vec![crate::request::Completion {
 792                text: "d = 4 + c".into(),
 793                range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 6)),
 794                ..Default::default()
 795            }],
 796            vec![],
 797        );
 798        _ = editor.update(cx, |editor, window, cx| {
 799            // Move to another excerpt, ensuring the suggestion gets cleared.
 800            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 801                s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
 802            });
 803            assert!(!editor.has_active_edit_prediction());
 804            assert_eq!(
 805                editor.display_text(cx),
 806                "\n\na = 1\nb = 2\n\n\n\nc = 3\nd = 4\n"
 807            );
 808            assert_eq!(editor.text(cx), "a = 1\nb = 2\n\nc = 3\nd = 4\n");
 809
 810            // Type a character, ensuring we don't even try to interpolate the previous suggestion.
 811            editor.handle_input(" ", window, cx);
 812            assert!(!editor.has_active_edit_prediction());
 813            assert_eq!(
 814                editor.display_text(cx),
 815                "\n\na = 1\nb = 2\n\n\n\nc = 3\nd = 4 \n"
 816            );
 817            assert_eq!(editor.text(cx), "a = 1\nb = 2\n\nc = 3\nd = 4 \n");
 818        });
 819
 820        // Ensure the new suggestion is displayed when the debounce timeout expires.
 821        executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
 822        _ = editor.update(cx, |editor, _, cx| {
 823            assert!(editor.has_active_edit_prediction());
 824            assert_eq!(
 825                editor.display_text(cx),
 826                "\n\na = 1\nb = 2\n\n\n\nc = 3\nd = 4 + c\n"
 827            );
 828            assert_eq!(editor.text(cx), "a = 1\nb = 2\n\nc = 3\nd = 4 \n");
 829        });
 830    }
 831
 832    #[gpui::test]
 833    async fn test_copilot_does_not_prevent_completion_triggers(
 834        executor: BackgroundExecutor,
 835        cx: &mut TestAppContext,
 836    ) {
 837        init_test(cx, |_| {});
 838
 839        let (copilot, copilot_lsp) = Copilot::fake(cx);
 840        let mut cx = EditorLspTestContext::new_rust(
 841            lsp::ServerCapabilities {
 842                completion_provider: Some(lsp::CompletionOptions {
 843                    trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
 844                    ..lsp::CompletionOptions::default()
 845                }),
 846                ..lsp::ServerCapabilities::default()
 847            },
 848            cx,
 849        )
 850        .await;
 851        let copilot_provider = cx.new(|_| CopilotCompletionProvider::new(copilot));
 852        cx.update_editor(|editor, window, cx| {
 853            editor.set_edit_prediction_provider(Some(copilot_provider), window, cx)
 854        });
 855
 856        cx.set_state(indoc! {"
 857                one
 858                twˇ
 859                three
 860            "});
 861
 862        drop(handle_completion_request(
 863            &mut cx,
 864            indoc! {"
 865                one
 866                tw|<>
 867                three
 868            "},
 869            vec!["completion_a", "completion_b"],
 870        ));
 871        handle_copilot_completion_request(
 872            &copilot_lsp,
 873            vec![crate::request::Completion {
 874                text: "two.foo()".into(),
 875                range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 2)),
 876                ..Default::default()
 877            }],
 878            vec![],
 879        );
 880        cx.update_editor(|editor, window, cx| {
 881            editor.next_edit_prediction(&Default::default(), window, cx)
 882        });
 883        executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
 884        cx.update_editor(|editor, _, cx| {
 885            assert!(!editor.context_menu_visible());
 886            assert!(editor.has_active_edit_prediction());
 887            assert_eq!(editor.display_text(cx), "one\ntwo.foo()\nthree\n");
 888            assert_eq!(editor.text(cx), "one\ntw\nthree\n");
 889        });
 890
 891        cx.simulate_keystroke("o");
 892        drop(handle_completion_request(
 893            &mut cx,
 894            indoc! {"
 895                one
 896                two|<>
 897                three
 898            "},
 899            vec!["completion_a_2", "completion_b_2"],
 900        ));
 901        handle_copilot_completion_request(
 902            &copilot_lsp,
 903            vec![crate::request::Completion {
 904                text: "two.foo()".into(),
 905                range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 3)),
 906                ..Default::default()
 907            }],
 908            vec![],
 909        );
 910        executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
 911        cx.update_editor(|editor, _, cx| {
 912            assert!(!editor.context_menu_visible());
 913            assert!(editor.has_active_edit_prediction());
 914            assert_eq!(editor.display_text(cx), "one\ntwo.foo()\nthree\n");
 915            assert_eq!(editor.text(cx), "one\ntwo\nthree\n");
 916        });
 917
 918        cx.simulate_keystroke(".");
 919        drop(handle_completion_request(
 920            &mut cx,
 921            indoc! {"
 922                one
 923                two.|<>
 924                three
 925            "},
 926            vec!["something_else()"],
 927        ));
 928        handle_copilot_completion_request(
 929            &copilot_lsp,
 930            vec![crate::request::Completion {
 931                text: "two.foo()".into(),
 932                range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 4)),
 933                ..Default::default()
 934            }],
 935            vec![],
 936        );
 937        executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
 938        cx.update_editor(|editor, _, cx| {
 939            assert!(editor.context_menu_visible());
 940            assert!(editor.has_active_edit_prediction());
 941            assert_eq!(editor.text(cx), "one\ntwo.\nthree\n");
 942            assert_eq!(editor.display_text(cx), "one\ntwo.\nthree\n");
 943        });
 944    }
 945
 946    #[gpui::test]
 947    async fn test_copilot_disabled_globs(executor: BackgroundExecutor, cx: &mut TestAppContext) {
 948        init_test(cx, |settings| {
 949            settings
 950                .edit_predictions
 951                .get_or_insert(Default::default())
 952                .disabled_globs = Some(vec![".env*".to_string()]);
 953        });
 954
 955        let (copilot, copilot_lsp) = Copilot::fake(cx);
 956
 957        let fs = FakeFs::new(cx.executor());
 958        fs.insert_tree(
 959            path!("/test"),
 960            json!({
 961                ".env": "SECRET=something\n",
 962                "README.md": "hello\nworld\nhow\nare\nyou\ntoday"
 963            }),
 964        )
 965        .await;
 966        let project = Project::test(fs, [path!("/test").as_ref()], cx).await;
 967
 968        let private_buffer = project
 969            .update(cx, |project, cx| {
 970                project.open_local_buffer(path!("/test/.env"), cx)
 971            })
 972            .await
 973            .unwrap();
 974        let public_buffer = project
 975            .update(cx, |project, cx| {
 976                project.open_local_buffer(path!("/test/README.md"), cx)
 977            })
 978            .await
 979            .unwrap();
 980
 981        let multibuffer = cx.new(|cx| {
 982            let mut multibuffer = MultiBuffer::new(language::Capability::ReadWrite);
 983            multibuffer.push_excerpts(
 984                private_buffer.clone(),
 985                [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
 986                cx,
 987            );
 988            multibuffer.push_excerpts(
 989                public_buffer.clone(),
 990                [ExcerptRange::new(Point::new(0, 0)..Point::new(6, 0))],
 991                cx,
 992            );
 993            multibuffer
 994        });
 995        let editor =
 996            cx.add_window(|window, cx| Editor::for_multibuffer(multibuffer, None, window, cx));
 997        editor
 998            .update(cx, |editor, window, cx| {
 999                use gpui::Focusable;
1000                window.focus(&editor.focus_handle(cx))
1001            })
1002            .unwrap();
1003        let copilot_provider = cx.new(|_| CopilotCompletionProvider::new(copilot));
1004        editor
1005            .update(cx, |editor, window, cx| {
1006                editor.set_edit_prediction_provider(Some(copilot_provider), window, cx)
1007            })
1008            .unwrap();
1009
1010        let mut copilot_requests = copilot_lsp
1011            .set_request_handler::<crate::request::GetCompletions, _, _>(
1012                move |_params, _cx| async move {
1013                    Ok(crate::request::GetCompletionsResult {
1014                        completions: vec![crate::request::Completion {
1015                            text: "next line".into(),
1016                            range: lsp::Range::new(
1017                                lsp::Position::new(1, 0),
1018                                lsp::Position::new(1, 0),
1019                            ),
1020                            ..Default::default()
1021                        }],
1022                    })
1023                },
1024            );
1025
1026        _ = editor.update(cx, |editor, window, cx| {
1027            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
1028                selections.select_ranges([Point::new(0, 0)..Point::new(0, 0)])
1029            });
1030            editor.refresh_edit_prediction(true, false, window, cx);
1031        });
1032
1033        executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
1034        assert!(copilot_requests.try_next().is_err());
1035
1036        _ = editor.update(cx, |editor, window, cx| {
1037            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
1038                s.select_ranges([Point::new(5, 0)..Point::new(5, 0)])
1039            });
1040            editor.refresh_edit_prediction(true, false, window, cx);
1041        });
1042
1043        executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
1044        assert!(copilot_requests.try_next().is_ok());
1045    }
1046
1047    fn handle_copilot_completion_request(
1048        lsp: &lsp::FakeLanguageServer,
1049        completions: Vec<crate::request::Completion>,
1050        completions_cycling: Vec<crate::request::Completion>,
1051    ) {
1052        lsp.set_request_handler::<crate::request::GetCompletions, _, _>(move |_params, _cx| {
1053            let completions = completions.clone();
1054            async move {
1055                Ok(crate::request::GetCompletionsResult {
1056                    completions: completions.clone(),
1057                })
1058            }
1059        });
1060        lsp.set_request_handler::<crate::request::GetCompletionsCycling, _, _>(
1061            move |_params, _cx| {
1062                let completions_cycling = completions_cycling.clone();
1063                async move {
1064                    Ok(crate::request::GetCompletionsResult {
1065                        completions: completions_cycling.clone(),
1066                    })
1067                }
1068            },
1069        );
1070    }
1071
1072    fn handle_completion_request(
1073        cx: &mut EditorLspTestContext,
1074        marked_string: &str,
1075        completions: Vec<&'static str>,
1076    ) -> impl Future<Output = ()> {
1077        let complete_from_marker: TextRangeMarker = '|'.into();
1078        let replace_range_marker: TextRangeMarker = ('<', '>').into();
1079        let (_, mut marked_ranges) = marked_text_ranges_by(
1080            marked_string,
1081            vec![complete_from_marker, replace_range_marker.clone()],
1082        );
1083
1084        let replace_range =
1085            cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
1086
1087        let mut request =
1088            cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
1089                let completions = completions.clone();
1090                async move {
1091                    assert_eq!(params.text_document_position.text_document.uri, url.clone());
1092                    Ok(Some(lsp::CompletionResponse::Array(
1093                        completions
1094                            .iter()
1095                            .map(|completion_text| lsp::CompletionItem {
1096                                label: completion_text.to_string(),
1097                                text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
1098                                    range: replace_range,
1099                                    new_text: completion_text.to_string(),
1100                                })),
1101                                ..Default::default()
1102                            })
1103                            .collect(),
1104                    )))
1105                }
1106            });
1107
1108        async move {
1109            request.next().await;
1110        }
1111    }
1112
1113    fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
1114        cx.update(|cx| {
1115            let store = SettingsStore::test(cx);
1116            cx.set_global(store);
1117            theme::init(theme::LoadThemes::JustBase, cx);
1118            client::init_settings(cx);
1119            language::init(cx);
1120            editor::init_settings(cx);
1121            Project::init_settings(cx);
1122            workspace::init_settings(cx);
1123            SettingsStore::update_global(cx, |store: &mut SettingsStore, cx| {
1124                store.update_user_settings(cx, |settings| f(&mut settings.project.all_languages));
1125            });
1126        });
1127    }
1128}