copilot.rs

   1pub mod copilot_chat;
   2mod copilot_edit_prediction_delegate;
   3pub mod copilot_responses;
   4pub mod request;
   5mod sign_in;
   6
   7use crate::sign_in::initiate_sign_out;
   8use ::fs::Fs;
   9use anyhow::{Context as _, Result, anyhow};
  10use collections::{HashMap, HashSet};
  11use command_palette_hooks::CommandPaletteFilter;
  12use futures::{Future, FutureExt, TryFutureExt, channel::oneshot, future::Shared};
  13use gpui::{
  14    App, AppContext as _, AsyncApp, Context, Entity, EntityId, EventEmitter, Global, Task,
  15    WeakEntity, actions,
  16};
  17use http_client::HttpClient;
  18use language::language_settings::CopilotSettings;
  19use language::{
  20    Anchor, Bias, Buffer, BufferSnapshot, Language, PointUtf16, ToPointUtf16,
  21    language_settings::{EditPredictionProvider, all_language_settings, language_settings},
  22    point_from_lsp, point_to_lsp,
  23};
  24use lsp::{LanguageServer, LanguageServerBinary, LanguageServerId, LanguageServerName};
  25use node_runtime::{NodeRuntime, VersionStrategy};
  26use parking_lot::Mutex;
  27use project::DisableAiSettings;
  28use request::StatusNotification;
  29use semver::Version;
  30use serde_json::json;
  31use settings::{Settings, SettingsStore};
  32use std::{
  33    any::TypeId,
  34    collections::hash_map::Entry,
  35    env,
  36    ffi::OsString,
  37    mem,
  38    ops::Range,
  39    path::{Path, PathBuf},
  40    sync::Arc,
  41};
  42use sum_tree::Dimensions;
  43use util::{ResultExt, fs::remove_matching, rel_path::RelPath};
  44use workspace::Workspace;
  45
  46pub use crate::copilot_edit_prediction_delegate::CopilotEditPredictionDelegate;
  47pub use crate::sign_in::{
  48    ConfigurationMode, ConfigurationView, CopilotCodeVerification, initiate_sign_in,
  49    reinstall_and_sign_in,
  50};
  51
  52actions!(
  53    copilot,
  54    [
  55        /// Requests a code completion suggestion from Copilot.
  56        Suggest,
  57        /// Cycles to the next Copilot suggestion.
  58        NextSuggestion,
  59        /// Cycles to the previous Copilot suggestion.
  60        PreviousSuggestion,
  61        /// Reinstalls the Copilot language server.
  62        Reinstall,
  63        /// Signs in to GitHub Copilot.
  64        SignIn,
  65        /// Signs out of GitHub Copilot.
  66        SignOut
  67    ]
  68);
  69
  70pub fn init(
  71    new_server_id: LanguageServerId,
  72    fs: Arc<dyn Fs>,
  73    http: Arc<dyn HttpClient>,
  74    node_runtime: NodeRuntime,
  75    cx: &mut App,
  76) {
  77    let language_settings = all_language_settings(None, cx);
  78    let configuration = copilot_chat::CopilotChatConfiguration {
  79        enterprise_uri: language_settings
  80            .edit_predictions
  81            .copilot
  82            .enterprise_uri
  83            .clone(),
  84    };
  85    copilot_chat::init(fs.clone(), http.clone(), configuration, cx);
  86
  87    let copilot = cx.new(move |cx| Copilot::start(new_server_id, fs, node_runtime, cx));
  88    Copilot::set_global(copilot.clone(), cx);
  89    cx.observe(&copilot, |copilot, cx| {
  90        copilot.update(cx, |copilot, cx| copilot.update_action_visibilities(cx));
  91    })
  92    .detach();
  93    cx.observe_global::<SettingsStore>(|cx| {
  94        if let Some(copilot) = Copilot::global(cx) {
  95            copilot.update(cx, |copilot, cx| copilot.update_action_visibilities(cx));
  96        }
  97    })
  98    .detach();
  99
 100    cx.observe_new(|workspace: &mut Workspace, _window, _cx| {
 101        workspace.register_action(|_, _: &SignIn, window, cx| {
 102            initiate_sign_in(window, cx);
 103        });
 104        workspace.register_action(|_, _: &Reinstall, window, cx| {
 105            reinstall_and_sign_in(window, cx);
 106        });
 107        workspace.register_action(|_, _: &SignOut, window, cx| {
 108            initiate_sign_out(window, cx);
 109        });
 110    })
 111    .detach();
 112}
 113
 114enum CopilotServer {
 115    Disabled,
 116    Starting { task: Shared<Task<()>> },
 117    Error(Arc<str>),
 118    Running(RunningCopilotServer),
 119}
 120
 121impl CopilotServer {
 122    fn as_authenticated(&mut self) -> Result<&mut RunningCopilotServer> {
 123        let server = self.as_running()?;
 124        anyhow::ensure!(
 125            matches!(server.sign_in_status, SignInStatus::Authorized),
 126            "must sign in before using copilot"
 127        );
 128        Ok(server)
 129    }
 130
 131    fn as_running(&mut self) -> Result<&mut RunningCopilotServer> {
 132        match self {
 133            CopilotServer::Starting { .. } => anyhow::bail!("copilot is still starting"),
 134            CopilotServer::Disabled => anyhow::bail!("copilot is disabled"),
 135            CopilotServer::Error(error) => {
 136                anyhow::bail!("copilot was not started because of an error: {error}")
 137            }
 138            CopilotServer::Running(server) => Ok(server),
 139        }
 140    }
 141}
 142
 143struct RunningCopilotServer {
 144    lsp: Arc<LanguageServer>,
 145    sign_in_status: SignInStatus,
 146    registered_buffers: HashMap<EntityId, RegisteredBuffer>,
 147}
 148
 149#[derive(Clone, Debug)]
 150enum SignInStatus {
 151    Authorized,
 152    Unauthorized,
 153    SigningIn {
 154        prompt: Option<request::PromptUserDeviceFlow>,
 155        task: Shared<Task<Result<(), Arc<anyhow::Error>>>>,
 156    },
 157    SignedOut {
 158        awaiting_signing_in: bool,
 159    },
 160}
 161
 162#[derive(Debug, Clone)]
 163pub enum Status {
 164    Starting {
 165        task: Shared<Task<()>>,
 166    },
 167    Error(Arc<str>),
 168    Disabled,
 169    SignedOut {
 170        awaiting_signing_in: bool,
 171    },
 172    SigningIn {
 173        prompt: Option<request::PromptUserDeviceFlow>,
 174    },
 175    Unauthorized,
 176    Authorized,
 177}
 178
 179impl Status {
 180    pub fn is_authorized(&self) -> bool {
 181        matches!(self, Status::Authorized)
 182    }
 183
 184    pub fn is_configured(&self) -> bool {
 185        matches!(
 186            self,
 187            Status::Starting { .. }
 188                | Status::Error(_)
 189                | Status::SigningIn { .. }
 190                | Status::Authorized
 191        )
 192    }
 193}
 194
 195struct RegisteredBuffer {
 196    uri: lsp::Uri,
 197    language_id: String,
 198    snapshot: BufferSnapshot,
 199    snapshot_version: i32,
 200    _subscriptions: [gpui::Subscription; 2],
 201    pending_buffer_change: Task<Option<()>>,
 202}
 203
 204impl RegisteredBuffer {
 205    fn report_changes(
 206        &mut self,
 207        buffer: &Entity<Buffer>,
 208        cx: &mut Context<Copilot>,
 209    ) -> oneshot::Receiver<(i32, BufferSnapshot)> {
 210        let (done_tx, done_rx) = oneshot::channel();
 211
 212        if buffer.read(cx).version() == self.snapshot.version {
 213            let _ = done_tx.send((self.snapshot_version, self.snapshot.clone()));
 214        } else {
 215            let buffer = buffer.downgrade();
 216            let id = buffer.entity_id();
 217            let prev_pending_change =
 218                mem::replace(&mut self.pending_buffer_change, Task::ready(None));
 219            self.pending_buffer_change = cx.spawn(async move |copilot, cx| {
 220                prev_pending_change.await;
 221
 222                let old_version = copilot
 223                    .update(cx, |copilot, _| {
 224                        let server = copilot.server.as_authenticated().log_err()?;
 225                        let buffer = server.registered_buffers.get_mut(&id)?;
 226                        Some(buffer.snapshot.version.clone())
 227                    })
 228                    .ok()??;
 229                let new_snapshot = buffer.read_with(cx, |buffer, _| buffer.snapshot()).ok()?;
 230
 231                let content_changes = cx
 232                    .background_spawn({
 233                        let new_snapshot = new_snapshot.clone();
 234                        async move {
 235                            new_snapshot
 236                                .edits_since::<Dimensions<PointUtf16, usize>>(&old_version)
 237                                .map(|edit| {
 238                                    let edit_start = edit.new.start.0;
 239                                    let edit_end = edit_start + (edit.old.end.0 - edit.old.start.0);
 240                                    let new_text = new_snapshot
 241                                        .text_for_range(edit.new.start.1..edit.new.end.1)
 242                                        .collect();
 243                                    lsp::TextDocumentContentChangeEvent {
 244                                        range: Some(lsp::Range::new(
 245                                            point_to_lsp(edit_start),
 246                                            point_to_lsp(edit_end),
 247                                        )),
 248                                        range_length: None,
 249                                        text: new_text,
 250                                    }
 251                                })
 252                                .collect::<Vec<_>>()
 253                        }
 254                    })
 255                    .await;
 256
 257                copilot
 258                    .update(cx, |copilot, _| {
 259                        let server = copilot.server.as_authenticated().log_err()?;
 260                        let buffer = server.registered_buffers.get_mut(&id)?;
 261                        if !content_changes.is_empty() {
 262                            buffer.snapshot_version += 1;
 263                            buffer.snapshot = new_snapshot;
 264                            server
 265                                .lsp
 266                                .notify::<lsp::notification::DidChangeTextDocument>(
 267                                    lsp::DidChangeTextDocumentParams {
 268                                        text_document: lsp::VersionedTextDocumentIdentifier::new(
 269                                            buffer.uri.clone(),
 270                                            buffer.snapshot_version,
 271                                        ),
 272                                        content_changes,
 273                                    },
 274                                )
 275                                .ok();
 276                        }
 277                        let _ = done_tx.send((buffer.snapshot_version, buffer.snapshot.clone()));
 278                        Some(())
 279                    })
 280                    .ok()?;
 281
 282                Some(())
 283            });
 284        }
 285
 286        done_rx
 287    }
 288}
 289
 290#[derive(Debug)]
 291pub struct Completion {
 292    pub uuid: String,
 293    pub range: Range<Anchor>,
 294    pub text: String,
 295}
 296
 297pub struct Copilot {
 298    fs: Arc<dyn Fs>,
 299    node_runtime: NodeRuntime,
 300    server: CopilotServer,
 301    buffers: HashSet<WeakEntity<Buffer>>,
 302    server_id: LanguageServerId,
 303    _subscription: gpui::Subscription,
 304}
 305
 306pub enum Event {
 307    CopilotLanguageServerStarted,
 308    CopilotAuthSignedIn,
 309    CopilotAuthSignedOut,
 310}
 311
 312impl EventEmitter<Event> for Copilot {}
 313
 314struct GlobalCopilot(Entity<Copilot>);
 315
 316impl Global for GlobalCopilot {}
 317
 318impl Copilot {
 319    pub fn global(cx: &App) -> Option<Entity<Self>> {
 320        cx.try_global::<GlobalCopilot>()
 321            .map(|model| model.0.clone())
 322    }
 323
 324    pub fn set_global(copilot: Entity<Self>, cx: &mut App) {
 325        cx.set_global(GlobalCopilot(copilot));
 326    }
 327
 328    fn start(
 329        new_server_id: LanguageServerId,
 330        fs: Arc<dyn Fs>,
 331        node_runtime: NodeRuntime,
 332        cx: &mut Context<Self>,
 333    ) -> Self {
 334        let mut this = Self {
 335            server_id: new_server_id,
 336            fs,
 337            node_runtime,
 338            server: CopilotServer::Disabled,
 339            buffers: Default::default(),
 340            _subscription: cx.on_app_quit(Self::shutdown_language_server),
 341        };
 342        this.start_copilot(true, false, cx);
 343        cx.observe_global::<SettingsStore>(move |this, cx| {
 344            this.start_copilot(true, false, cx);
 345            if let Ok(server) = this.server.as_running() {
 346                notify_did_change_config_to_server(&server.lsp, cx)
 347                    .context("copilot setting change: did change configuration")
 348                    .log_err();
 349            }
 350        })
 351        .detach();
 352        this
 353    }
 354
 355    fn shutdown_language_server(
 356        &mut self,
 357        _cx: &mut Context<Self>,
 358    ) -> impl Future<Output = ()> + use<> {
 359        let shutdown = match mem::replace(&mut self.server, CopilotServer::Disabled) {
 360            CopilotServer::Running(server) => Some(Box::pin(async move { server.lsp.shutdown() })),
 361            _ => None,
 362        };
 363
 364        async move {
 365            if let Some(shutdown) = shutdown {
 366                shutdown.await;
 367            }
 368        }
 369    }
 370
 371    pub fn start_copilot(
 372        &mut self,
 373        check_edit_prediction_provider: bool,
 374        awaiting_sign_in_after_start: bool,
 375        cx: &mut Context<Self>,
 376    ) {
 377        if !matches!(self.server, CopilotServer::Disabled) {
 378            return;
 379        }
 380        let language_settings = all_language_settings(None, cx);
 381        if check_edit_prediction_provider
 382            && language_settings.edit_predictions.provider != EditPredictionProvider::Copilot
 383        {
 384            return;
 385        }
 386        let server_id = self.server_id;
 387        let fs = self.fs.clone();
 388        let node_runtime = self.node_runtime.clone();
 389        let env = self.build_env(&language_settings.edit_predictions.copilot);
 390        let start_task = cx
 391            .spawn(async move |this, cx| {
 392                Self::start_language_server(
 393                    server_id,
 394                    fs,
 395                    node_runtime,
 396                    env,
 397                    this,
 398                    awaiting_sign_in_after_start,
 399                    cx,
 400                )
 401                .await
 402            })
 403            .shared();
 404        self.server = CopilotServer::Starting { task: start_task };
 405        cx.notify();
 406    }
 407
 408    fn build_env(&self, copilot_settings: &CopilotSettings) -> Option<HashMap<String, String>> {
 409        let proxy_url = copilot_settings.proxy.clone()?;
 410        let no_verify = copilot_settings.proxy_no_verify;
 411        let http_or_https_proxy = if proxy_url.starts_with("http:") {
 412            Some("HTTP_PROXY")
 413        } else if proxy_url.starts_with("https:") {
 414            Some("HTTPS_PROXY")
 415        } else {
 416            log::error!(
 417                "Unsupported protocol scheme for language server proxy (must be http or https)"
 418            );
 419            None
 420        };
 421
 422        let mut env = HashMap::default();
 423
 424        if let Some(proxy_type) = http_or_https_proxy {
 425            env.insert(proxy_type.to_string(), proxy_url);
 426            if let Some(true) = no_verify {
 427                env.insert("NODE_TLS_REJECT_UNAUTHORIZED".to_string(), "0".to_string());
 428            };
 429        }
 430
 431        if let Ok(oauth_token) = env::var(copilot_chat::COPILOT_OAUTH_ENV_VAR) {
 432            env.insert(copilot_chat::COPILOT_OAUTH_ENV_VAR.to_string(), oauth_token);
 433        }
 434
 435        if env.is_empty() { None } else { Some(env) }
 436    }
 437
 438    #[cfg(any(test, feature = "test-support"))]
 439    pub fn fake(cx: &mut gpui::TestAppContext) -> (Entity<Self>, lsp::FakeLanguageServer) {
 440        use fs::FakeFs;
 441        use lsp::FakeLanguageServer;
 442        use node_runtime::NodeRuntime;
 443
 444        let (server, fake_server) = FakeLanguageServer::new(
 445            LanguageServerId(0),
 446            LanguageServerBinary {
 447                path: "path/to/copilot".into(),
 448                arguments: vec![],
 449                env: None,
 450            },
 451            "copilot".into(),
 452            Default::default(),
 453            &mut cx.to_async(),
 454        );
 455        let node_runtime = NodeRuntime::unavailable();
 456        let this = cx.new(|cx| Self {
 457            server_id: LanguageServerId(0),
 458            fs: FakeFs::new(cx.background_executor().clone()),
 459            node_runtime,
 460            server: CopilotServer::Running(RunningCopilotServer {
 461                lsp: Arc::new(server),
 462                sign_in_status: SignInStatus::Authorized,
 463                registered_buffers: Default::default(),
 464            }),
 465            _subscription: cx.on_app_quit(Self::shutdown_language_server),
 466            buffers: Default::default(),
 467        });
 468        (this, fake_server)
 469    }
 470
 471    async fn start_language_server(
 472        new_server_id: LanguageServerId,
 473        fs: Arc<dyn Fs>,
 474        node_runtime: NodeRuntime,
 475        env: Option<HashMap<String, String>>,
 476        this: WeakEntity<Self>,
 477        awaiting_sign_in_after_start: bool,
 478        cx: &mut AsyncApp,
 479    ) {
 480        let start_language_server = async {
 481            let server_path = get_copilot_lsp(fs, node_runtime.clone()).await?;
 482            let node_path = node_runtime.binary_path().await?;
 483            ensure_node_version_for_copilot(&node_path).await?;
 484
 485            let arguments: Vec<OsString> = vec![
 486                "--experimental-sqlite".into(),
 487                server_path.into(),
 488                "--stdio".into(),
 489            ];
 490            let binary = LanguageServerBinary {
 491                path: node_path,
 492                arguments,
 493                env,
 494            };
 495
 496            let root_path = if cfg!(target_os = "windows") {
 497                Path::new("C:/")
 498            } else {
 499                Path::new("/")
 500            };
 501
 502            let server_name = LanguageServerName("copilot".into());
 503            let server = LanguageServer::new(
 504                Arc::new(Mutex::new(None)),
 505                new_server_id,
 506                server_name,
 507                binary,
 508                root_path,
 509                None,
 510                Default::default(),
 511                cx,
 512            )?;
 513
 514            server
 515                .on_notification::<StatusNotification, _>(|_, _| { /* Silence the notification */ })
 516                .detach();
 517
 518            let configuration = lsp::DidChangeConfigurationParams {
 519                settings: Default::default(),
 520            };
 521
 522            let editor_info = request::SetEditorInfoParams {
 523                editor_info: request::EditorInfo {
 524                    name: "zed".into(),
 525                    version: env!("CARGO_PKG_VERSION").into(),
 526                },
 527                editor_plugin_info: request::EditorPluginInfo {
 528                    name: "zed-copilot".into(),
 529                    version: "0.0.1".into(),
 530                },
 531            };
 532            let editor_info_json = serde_json::to_value(&editor_info)?;
 533
 534            let server = cx
 535                .update(|cx| {
 536                    let mut params = server.default_initialize_params(false, cx);
 537                    params.initialization_options = Some(editor_info_json);
 538                    server.initialize(params, configuration.into(), cx)
 539                })?
 540                .await?;
 541
 542            this.update(cx, |_, cx| notify_did_change_config_to_server(&server, cx))?
 543                .context("copilot: did change configuration")?;
 544
 545            let status = server
 546                .request::<request::CheckStatus>(request::CheckStatusParams {
 547                    local_checks_only: false,
 548                })
 549                .await
 550                .into_response()
 551                .context("copilot: check status")?;
 552
 553            anyhow::Ok((server, status))
 554        };
 555
 556        let server = start_language_server.await;
 557        this.update(cx, |this, cx| {
 558            cx.notify();
 559
 560            if env::var("ZED_FORCE_COPILOT_ERROR").is_ok() {
 561                this.server = CopilotServer::Error(
 562                    "Forced error for testing (ZED_FORCE_COPILOT_ERROR)".into(),
 563                );
 564                return;
 565            }
 566
 567            match server {
 568                Ok((server, status)) => {
 569                    this.server = CopilotServer::Running(RunningCopilotServer {
 570                        lsp: server,
 571                        sign_in_status: SignInStatus::SignedOut {
 572                            awaiting_signing_in: awaiting_sign_in_after_start,
 573                        },
 574                        registered_buffers: Default::default(),
 575                    });
 576                    cx.emit(Event::CopilotLanguageServerStarted);
 577                    this.update_sign_in_status(status, cx);
 578                }
 579                Err(error) => {
 580                    this.server = CopilotServer::Error(error.to_string().into());
 581                    cx.notify()
 582                }
 583            }
 584        })
 585        .ok();
 586    }
 587
 588    pub fn is_authenticated(&self) -> bool {
 589        return matches!(
 590            self.server,
 591            CopilotServer::Running(RunningCopilotServer {
 592                sign_in_status: SignInStatus::Authorized,
 593                ..
 594            })
 595        );
 596    }
 597
 598    pub fn sign_in(&mut self, cx: &mut Context<Self>) -> Task<Result<()>> {
 599        if let CopilotServer::Running(server) = &mut self.server {
 600            let task = match &server.sign_in_status {
 601                SignInStatus::Authorized => Task::ready(Ok(())).shared(),
 602                SignInStatus::SigningIn { task, .. } => {
 603                    cx.notify();
 604                    task.clone()
 605                }
 606                SignInStatus::SignedOut { .. } | SignInStatus::Unauthorized => {
 607                    let lsp = server.lsp.clone();
 608                    let task = cx
 609                        .spawn(async move |this, cx| {
 610                            let sign_in = async {
 611                                let sign_in = lsp
 612                                    .request::<request::SignInInitiate>(
 613                                        request::SignInInitiateParams {},
 614                                    )
 615                                    .await
 616                                    .into_response()
 617                                    .context("copilot sign-in")?;
 618                                match sign_in {
 619                                    request::SignInInitiateResult::AlreadySignedIn { user } => {
 620                                        Ok(request::SignInStatus::Ok { user: Some(user) })
 621                                    }
 622                                    request::SignInInitiateResult::PromptUserDeviceFlow(flow) => {
 623                                        this.update(cx, |this, cx| {
 624                                            if let CopilotServer::Running(RunningCopilotServer {
 625                                                sign_in_status: status,
 626                                                ..
 627                                            }) = &mut this.server
 628                                                && let SignInStatus::SigningIn {
 629                                                    prompt: prompt_flow,
 630                                                    ..
 631                                                } = status
 632                                            {
 633                                                *prompt_flow = Some(flow.clone());
 634                                                cx.notify();
 635                                            }
 636                                        })?;
 637                                        let response = lsp
 638                                            .request::<request::SignInConfirm>(
 639                                                request::SignInConfirmParams {
 640                                                    user_code: flow.user_code,
 641                                                },
 642                                            )
 643                                            .await
 644                                            .into_response()
 645                                            .context("copilot: sign in confirm")?;
 646                                        Ok(response)
 647                                    }
 648                                }
 649                            };
 650
 651                            let sign_in = sign_in.await;
 652                            this.update(cx, |this, cx| match sign_in {
 653                                Ok(status) => {
 654                                    this.update_sign_in_status(status, cx);
 655                                    Ok(())
 656                                }
 657                                Err(error) => {
 658                                    this.update_sign_in_status(
 659                                        request::SignInStatus::NotSignedIn,
 660                                        cx,
 661                                    );
 662                                    Err(Arc::new(error))
 663                                }
 664                            })?
 665                        })
 666                        .shared();
 667                    server.sign_in_status = SignInStatus::SigningIn {
 668                        prompt: None,
 669                        task: task.clone(),
 670                    };
 671                    cx.notify();
 672                    task
 673                }
 674            };
 675
 676            cx.background_spawn(task.map_err(|err| anyhow!("{err:?}")))
 677        } else {
 678            // If we're downloading, wait until download is finished
 679            // If we're in a stuck state, display to the user
 680            Task::ready(Err(anyhow!("copilot hasn't started yet")))
 681        }
 682    }
 683
 684    pub(crate) fn sign_out(&mut self, cx: &mut Context<Self>) -> Task<Result<()>> {
 685        self.update_sign_in_status(request::SignInStatus::NotSignedIn, cx);
 686        match &self.server {
 687            CopilotServer::Running(RunningCopilotServer { lsp: server, .. }) => {
 688                let server = server.clone();
 689                cx.background_spawn(async move {
 690                    server
 691                        .request::<request::SignOut>(request::SignOutParams {})
 692                        .await
 693                        .into_response()
 694                        .context("copilot: sign in confirm")?;
 695                    anyhow::Ok(())
 696                })
 697            }
 698            CopilotServer::Disabled => cx.background_spawn(async {
 699                clear_copilot_config_dir().await;
 700                anyhow::Ok(())
 701            }),
 702            _ => Task::ready(Err(anyhow!("copilot hasn't started yet"))),
 703        }
 704    }
 705
 706    pub(crate) fn reinstall(&mut self, cx: &mut Context<Self>) -> Shared<Task<()>> {
 707        let language_settings = all_language_settings(None, cx);
 708        let env = self.build_env(&language_settings.edit_predictions.copilot);
 709        let start_task = cx
 710            .spawn({
 711                let fs = self.fs.clone();
 712                let node_runtime = self.node_runtime.clone();
 713                let server_id = self.server_id;
 714                async move |this, cx| {
 715                    clear_copilot_dir().await;
 716                    Self::start_language_server(server_id, fs, node_runtime, env, this, false, cx)
 717                        .await
 718                }
 719            })
 720            .shared();
 721
 722        self.server = CopilotServer::Starting {
 723            task: start_task.clone(),
 724        };
 725
 726        cx.notify();
 727
 728        start_task
 729    }
 730
 731    pub fn language_server(&self) -> Option<&Arc<LanguageServer>> {
 732        if let CopilotServer::Running(server) = &self.server {
 733            Some(&server.lsp)
 734        } else {
 735            None
 736        }
 737    }
 738
 739    pub fn register_buffer(&mut self, buffer: &Entity<Buffer>, cx: &mut Context<Self>) {
 740        let weak_buffer = buffer.downgrade();
 741        self.buffers.insert(weak_buffer.clone());
 742
 743        if let CopilotServer::Running(RunningCopilotServer {
 744            lsp: server,
 745            sign_in_status: status,
 746            registered_buffers,
 747            ..
 748        }) = &mut self.server
 749        {
 750            if !matches!(status, SignInStatus::Authorized) {
 751                return;
 752            }
 753
 754            let entry = registered_buffers.entry(buffer.entity_id());
 755            if let Entry::Vacant(e) = entry {
 756                let Ok(uri) = uri_for_buffer(buffer, cx) else {
 757                    return;
 758                };
 759                let language_id = id_for_language(buffer.read(cx).language());
 760                let snapshot = buffer.read(cx).snapshot();
 761                server
 762                    .notify::<lsp::notification::DidOpenTextDocument>(
 763                        lsp::DidOpenTextDocumentParams {
 764                            text_document: lsp::TextDocumentItem {
 765                                uri: uri.clone(),
 766                                language_id: language_id.clone(),
 767                                version: 0,
 768                                text: snapshot.text(),
 769                            },
 770                        },
 771                    )
 772                    .ok();
 773
 774                e.insert(RegisteredBuffer {
 775                    uri,
 776                    language_id,
 777                    snapshot,
 778                    snapshot_version: 0,
 779                    pending_buffer_change: Task::ready(Some(())),
 780                    _subscriptions: [
 781                        cx.subscribe(buffer, |this, buffer, event, cx| {
 782                            this.handle_buffer_event(buffer, event, cx).log_err();
 783                        }),
 784                        cx.observe_release(buffer, move |this, _buffer, _cx| {
 785                            this.buffers.remove(&weak_buffer);
 786                            this.unregister_buffer(&weak_buffer);
 787                        }),
 788                    ],
 789                });
 790            }
 791        }
 792    }
 793
 794    fn handle_buffer_event(
 795        &mut self,
 796        buffer: Entity<Buffer>,
 797        event: &language::BufferEvent,
 798        cx: &mut Context<Self>,
 799    ) -> Result<()> {
 800        if let Ok(server) = self.server.as_running()
 801            && let Some(registered_buffer) = server.registered_buffers.get_mut(&buffer.entity_id())
 802        {
 803            match event {
 804                language::BufferEvent::Edited => {
 805                    drop(registered_buffer.report_changes(&buffer, cx));
 806                }
 807                language::BufferEvent::Saved => {
 808                    server
 809                        .lsp
 810                        .notify::<lsp::notification::DidSaveTextDocument>(
 811                            lsp::DidSaveTextDocumentParams {
 812                                text_document: lsp::TextDocumentIdentifier::new(
 813                                    registered_buffer.uri.clone(),
 814                                ),
 815                                text: None,
 816                            },
 817                        )
 818                        .ok();
 819                }
 820                language::BufferEvent::FileHandleChanged
 821                | language::BufferEvent::LanguageChanged(_) => {
 822                    let new_language_id = id_for_language(buffer.read(cx).language());
 823                    let Ok(new_uri) = uri_for_buffer(&buffer, cx) else {
 824                        return Ok(());
 825                    };
 826                    if new_uri != registered_buffer.uri
 827                        || new_language_id != registered_buffer.language_id
 828                    {
 829                        let old_uri = mem::replace(&mut registered_buffer.uri, new_uri);
 830                        registered_buffer.language_id = new_language_id;
 831                        server
 832                            .lsp
 833                            .notify::<lsp::notification::DidCloseTextDocument>(
 834                                lsp::DidCloseTextDocumentParams {
 835                                    text_document: lsp::TextDocumentIdentifier::new(old_uri),
 836                                },
 837                            )
 838                            .ok();
 839                        server
 840                            .lsp
 841                            .notify::<lsp::notification::DidOpenTextDocument>(
 842                                lsp::DidOpenTextDocumentParams {
 843                                    text_document: lsp::TextDocumentItem::new(
 844                                        registered_buffer.uri.clone(),
 845                                        registered_buffer.language_id.clone(),
 846                                        registered_buffer.snapshot_version,
 847                                        registered_buffer.snapshot.text(),
 848                                    ),
 849                                },
 850                            )
 851                            .ok();
 852                    }
 853                }
 854                _ => {}
 855            }
 856        }
 857
 858        Ok(())
 859    }
 860
 861    fn unregister_buffer(&mut self, buffer: &WeakEntity<Buffer>) {
 862        if let Ok(server) = self.server.as_running()
 863            && let Some(buffer) = server.registered_buffers.remove(&buffer.entity_id())
 864        {
 865            server
 866                .lsp
 867                .notify::<lsp::notification::DidCloseTextDocument>(
 868                    lsp::DidCloseTextDocumentParams {
 869                        text_document: lsp::TextDocumentIdentifier::new(buffer.uri),
 870                    },
 871                )
 872                .ok();
 873        }
 874    }
 875
 876    pub fn completions<T>(
 877        &mut self,
 878        buffer: &Entity<Buffer>,
 879        position: T,
 880        cx: &mut Context<Self>,
 881    ) -> Task<Result<Vec<Completion>>>
 882    where
 883        T: ToPointUtf16,
 884    {
 885        self.request_completions::<request::GetCompletions, _>(buffer, position, cx)
 886    }
 887
 888    pub fn completions_cycling<T>(
 889        &mut self,
 890        buffer: &Entity<Buffer>,
 891        position: T,
 892        cx: &mut Context<Self>,
 893    ) -> Task<Result<Vec<Completion>>>
 894    where
 895        T: ToPointUtf16,
 896    {
 897        self.request_completions::<request::GetCompletionsCycling, _>(buffer, position, cx)
 898    }
 899
 900    pub fn accept_completion(
 901        &mut self,
 902        completion: &Completion,
 903        cx: &mut Context<Self>,
 904    ) -> Task<Result<()>> {
 905        let server = match self.server.as_authenticated() {
 906            Ok(server) => server,
 907            Err(error) => return Task::ready(Err(error)),
 908        };
 909        let request =
 910            server
 911                .lsp
 912                .request::<request::NotifyAccepted>(request::NotifyAcceptedParams {
 913                    uuid: completion.uuid.clone(),
 914                });
 915        cx.background_spawn(async move {
 916            request
 917                .await
 918                .into_response()
 919                .context("copilot: notify accepted")?;
 920            Ok(())
 921        })
 922    }
 923
 924    pub fn discard_completions(
 925        &mut self,
 926        completions: &[Completion],
 927        cx: &mut Context<Self>,
 928    ) -> Task<Result<()>> {
 929        let server = match self.server.as_authenticated() {
 930            Ok(server) => server,
 931            Err(_) => return Task::ready(Ok(())),
 932        };
 933        let request =
 934            server
 935                .lsp
 936                .request::<request::NotifyRejected>(request::NotifyRejectedParams {
 937                    uuids: completions
 938                        .iter()
 939                        .map(|completion| completion.uuid.clone())
 940                        .collect(),
 941                });
 942        cx.background_spawn(async move {
 943            request
 944                .await
 945                .into_response()
 946                .context("copilot: notify rejected")?;
 947            Ok(())
 948        })
 949    }
 950
 951    fn request_completions<R, T>(
 952        &mut self,
 953        buffer: &Entity<Buffer>,
 954        position: T,
 955        cx: &mut Context<Self>,
 956    ) -> Task<Result<Vec<Completion>>>
 957    where
 958        R: 'static
 959            + lsp::request::Request<
 960                Params = request::GetCompletionsParams,
 961                Result = request::GetCompletionsResult,
 962            >,
 963        T: ToPointUtf16,
 964    {
 965        self.register_buffer(buffer, cx);
 966
 967        let server = match self.server.as_authenticated() {
 968            Ok(server) => server,
 969            Err(error) => return Task::ready(Err(error)),
 970        };
 971        let lsp = server.lsp.clone();
 972        let registered_buffer = server
 973            .registered_buffers
 974            .get_mut(&buffer.entity_id())
 975            .unwrap();
 976        let snapshot = registered_buffer.report_changes(buffer, cx);
 977        let buffer = buffer.read(cx);
 978        let uri = registered_buffer.uri.clone();
 979        let position = position.to_point_utf16(buffer);
 980        let settings = language_settings(
 981            buffer.language_at(position).map(|l| l.name()),
 982            buffer.file(),
 983            cx,
 984        );
 985        let tab_size = settings.tab_size;
 986        let hard_tabs = settings.hard_tabs;
 987        let relative_path = buffer
 988            .file()
 989            .map_or(RelPath::empty().into(), |file| file.path().clone());
 990
 991        cx.background_spawn(async move {
 992            let (version, snapshot) = snapshot.await?;
 993            let result = lsp
 994                .request::<R>(request::GetCompletionsParams {
 995                    doc: request::GetCompletionsDocument {
 996                        uri,
 997                        tab_size: tab_size.into(),
 998                        indent_size: 1,
 999                        insert_spaces: !hard_tabs,
1000                        relative_path: relative_path.to_proto(),
1001                        position: point_to_lsp(position),
1002                        version: version.try_into().unwrap(),
1003                    },
1004                })
1005                .await
1006                .into_response()
1007                .context("copilot: get completions")?;
1008            let completions = result
1009                .completions
1010                .into_iter()
1011                .map(|completion| {
1012                    let start = snapshot
1013                        .clip_point_utf16(point_from_lsp(completion.range.start), Bias::Left);
1014                    let end =
1015                        snapshot.clip_point_utf16(point_from_lsp(completion.range.end), Bias::Left);
1016                    Completion {
1017                        uuid: completion.uuid,
1018                        range: snapshot.anchor_before(start)..snapshot.anchor_after(end),
1019                        text: completion.text,
1020                    }
1021                })
1022                .collect();
1023            anyhow::Ok(completions)
1024        })
1025    }
1026
1027    pub fn status(&self) -> Status {
1028        match &self.server {
1029            CopilotServer::Starting { task } => Status::Starting { task: task.clone() },
1030            CopilotServer::Disabled => Status::Disabled,
1031            CopilotServer::Error(error) => Status::Error(error.clone()),
1032            CopilotServer::Running(RunningCopilotServer { sign_in_status, .. }) => {
1033                match sign_in_status {
1034                    SignInStatus::Authorized => Status::Authorized,
1035                    SignInStatus::Unauthorized => Status::Unauthorized,
1036                    SignInStatus::SigningIn { prompt, .. } => Status::SigningIn {
1037                        prompt: prompt.clone(),
1038                    },
1039                    SignInStatus::SignedOut {
1040                        awaiting_signing_in,
1041                    } => Status::SignedOut {
1042                        awaiting_signing_in: *awaiting_signing_in,
1043                    },
1044                }
1045            }
1046        }
1047    }
1048
1049    fn update_sign_in_status(&mut self, lsp_status: request::SignInStatus, cx: &mut Context<Self>) {
1050        self.buffers.retain(|buffer| buffer.is_upgradable());
1051
1052        if let Ok(server) = self.server.as_running() {
1053            match lsp_status {
1054                request::SignInStatus::Ok { user: Some(_) }
1055                | request::SignInStatus::MaybeOk { .. }
1056                | request::SignInStatus::AlreadySignedIn { .. } => {
1057                    server.sign_in_status = SignInStatus::Authorized;
1058                    cx.emit(Event::CopilotAuthSignedIn);
1059                    for buffer in self.buffers.iter().cloned().collect::<Vec<_>>() {
1060                        if let Some(buffer) = buffer.upgrade() {
1061                            self.register_buffer(&buffer, cx);
1062                        }
1063                    }
1064                }
1065                request::SignInStatus::NotAuthorized { .. } => {
1066                    server.sign_in_status = SignInStatus::Unauthorized;
1067                    for buffer in self.buffers.iter().cloned().collect::<Vec<_>>() {
1068                        self.unregister_buffer(&buffer);
1069                    }
1070                }
1071                request::SignInStatus::Ok { user: None } | request::SignInStatus::NotSignedIn => {
1072                    if !matches!(server.sign_in_status, SignInStatus::SignedOut { .. }) {
1073                        server.sign_in_status = SignInStatus::SignedOut {
1074                            awaiting_signing_in: false,
1075                        };
1076                    }
1077                    cx.emit(Event::CopilotAuthSignedOut);
1078                    for buffer in self.buffers.iter().cloned().collect::<Vec<_>>() {
1079                        self.unregister_buffer(&buffer);
1080                    }
1081                }
1082            }
1083
1084            cx.notify();
1085        }
1086    }
1087
1088    fn update_action_visibilities(&self, cx: &mut App) {
1089        let signed_in_actions = [
1090            TypeId::of::<Suggest>(),
1091            TypeId::of::<NextSuggestion>(),
1092            TypeId::of::<PreviousSuggestion>(),
1093            TypeId::of::<Reinstall>(),
1094        ];
1095        let auth_actions = [TypeId::of::<SignOut>()];
1096        let no_auth_actions = [TypeId::of::<SignIn>()];
1097        let status = self.status();
1098
1099        let is_ai_disabled = DisableAiSettings::get_global(cx).disable_ai;
1100        let filter = CommandPaletteFilter::global_mut(cx);
1101
1102        if is_ai_disabled {
1103            filter.hide_action_types(&signed_in_actions);
1104            filter.hide_action_types(&auth_actions);
1105            filter.hide_action_types(&no_auth_actions);
1106        } else {
1107            match status {
1108                Status::Disabled => {
1109                    filter.hide_action_types(&signed_in_actions);
1110                    filter.hide_action_types(&auth_actions);
1111                    filter.hide_action_types(&no_auth_actions);
1112                }
1113                Status::Authorized => {
1114                    filter.hide_action_types(&no_auth_actions);
1115                    filter.show_action_types(signed_in_actions.iter().chain(&auth_actions));
1116                }
1117                _ => {
1118                    filter.hide_action_types(&signed_in_actions);
1119                    filter.hide_action_types(&auth_actions);
1120                    filter.show_action_types(&no_auth_actions);
1121                }
1122            }
1123        }
1124    }
1125}
1126
1127fn id_for_language(language: Option<&Arc<Language>>) -> String {
1128    language
1129        .map(|language| language.lsp_id())
1130        .unwrap_or_else(|| "plaintext".to_string())
1131}
1132
1133fn uri_for_buffer(buffer: &Entity<Buffer>, cx: &App) -> Result<lsp::Uri, ()> {
1134    if let Some(file) = buffer.read(cx).file().and_then(|file| file.as_local()) {
1135        lsp::Uri::from_file_path(file.abs_path(cx))
1136    } else {
1137        format!("buffer://{}", buffer.entity_id())
1138            .parse()
1139            .map_err(|_| ())
1140    }
1141}
1142
1143fn notify_did_change_config_to_server(
1144    server: &Arc<LanguageServer>,
1145    cx: &mut Context<Copilot>,
1146) -> std::result::Result<(), anyhow::Error> {
1147    let copilot_settings = all_language_settings(None, cx)
1148        .edit_predictions
1149        .copilot
1150        .clone();
1151
1152    if let Some(copilot_chat) = copilot_chat::CopilotChat::global(cx) {
1153        copilot_chat.update(cx, |chat, cx| {
1154            chat.set_configuration(
1155                copilot_chat::CopilotChatConfiguration {
1156                    enterprise_uri: copilot_settings.enterprise_uri.clone(),
1157                },
1158                cx,
1159            );
1160        });
1161    }
1162
1163    let settings = json!({
1164        "http": {
1165            "proxy": copilot_settings.proxy,
1166            "proxyStrictSSL": !copilot_settings.proxy_no_verify.unwrap_or(false)
1167        },
1168        "github-enterprise": {
1169            "uri": copilot_settings.enterprise_uri
1170        }
1171    });
1172
1173    server
1174        .notify::<lsp::notification::DidChangeConfiguration>(lsp::DidChangeConfigurationParams {
1175            settings,
1176        })
1177        .ok();
1178    Ok(())
1179}
1180
1181async fn clear_copilot_dir() {
1182    remove_matching(paths::copilot_dir(), |_| true).await
1183}
1184
1185async fn clear_copilot_config_dir() {
1186    remove_matching(copilot_chat::copilot_chat_config_dir(), |_| true).await
1187}
1188
1189async fn ensure_node_version_for_copilot(node_path: &Path) -> anyhow::Result<()> {
1190    const MIN_COPILOT_NODE_VERSION: Version = Version::new(20, 8, 0);
1191
1192    log::info!("Checking Node.js version for Copilot at: {:?}", node_path);
1193
1194    let output = util::command::new_smol_command(node_path)
1195        .arg("--version")
1196        .output()
1197        .await
1198        .with_context(|| format!("checking Node.js version at {:?}", node_path))?;
1199
1200    if !output.status.success() {
1201        anyhow::bail!(
1202            "failed to run node --version for Copilot. stdout: {}, stderr: {}",
1203            String::from_utf8_lossy(&output.stdout),
1204            String::from_utf8_lossy(&output.stderr),
1205        );
1206    }
1207
1208    let version_str = String::from_utf8_lossy(&output.stdout);
1209    let version = Version::parse(version_str.trim().trim_start_matches('v'))
1210        .with_context(|| format!("parsing Node.js version from '{}'", version_str.trim()))?;
1211
1212    if version < MIN_COPILOT_NODE_VERSION {
1213        anyhow::bail!(
1214            "GitHub Copilot language server requires Node.js {MIN_COPILOT_NODE_VERSION} or later, but found {version}. \
1215            Please update your Node.js version or configure a different Node.js path in settings."
1216        );
1217    }
1218
1219    log::info!(
1220        "Node.js version {} meets Copilot requirements (>= {})",
1221        version,
1222        MIN_COPILOT_NODE_VERSION
1223    );
1224    Ok(())
1225}
1226
1227async fn get_copilot_lsp(fs: Arc<dyn Fs>, node_runtime: NodeRuntime) -> anyhow::Result<PathBuf> {
1228    const PACKAGE_NAME: &str = "@github/copilot-language-server";
1229    const SERVER_PATH: &str =
1230        "node_modules/@github/copilot-language-server/dist/language-server.js";
1231
1232    let latest_version = node_runtime
1233        .npm_package_latest_version(PACKAGE_NAME)
1234        .await?;
1235    let server_path = paths::copilot_dir().join(SERVER_PATH);
1236
1237    fs.create_dir(paths::copilot_dir()).await?;
1238
1239    let should_install = node_runtime
1240        .should_install_npm_package(
1241            PACKAGE_NAME,
1242            &server_path,
1243            paths::copilot_dir(),
1244            VersionStrategy::Latest(&latest_version),
1245        )
1246        .await;
1247    if should_install {
1248        node_runtime
1249            .npm_install_packages(paths::copilot_dir(), &[(PACKAGE_NAME, &latest_version)])
1250            .await?;
1251    }
1252
1253    Ok(server_path)
1254}
1255
1256#[cfg(test)]
1257mod tests {
1258    use super::*;
1259    use gpui::TestAppContext;
1260    use util::{path, paths::PathStyle, rel_path::rel_path};
1261
1262    #[gpui::test(iterations = 10)]
1263    async fn test_buffer_management(cx: &mut TestAppContext) {
1264        let (copilot, mut lsp) = Copilot::fake(cx);
1265
1266        let buffer_1 = cx.new(|cx| Buffer::local("Hello", cx));
1267        let buffer_1_uri: lsp::Uri = format!("buffer://{}", buffer_1.entity_id().as_u64())
1268            .parse()
1269            .unwrap();
1270        copilot.update(cx, |copilot, cx| copilot.register_buffer(&buffer_1, cx));
1271        assert_eq!(
1272            lsp.receive_notification::<lsp::notification::DidOpenTextDocument>()
1273                .await,
1274            lsp::DidOpenTextDocumentParams {
1275                text_document: lsp::TextDocumentItem::new(
1276                    buffer_1_uri.clone(),
1277                    "plaintext".into(),
1278                    0,
1279                    "Hello".into()
1280                ),
1281            }
1282        );
1283
1284        let buffer_2 = cx.new(|cx| Buffer::local("Goodbye", cx));
1285        let buffer_2_uri: lsp::Uri = format!("buffer://{}", buffer_2.entity_id().as_u64())
1286            .parse()
1287            .unwrap();
1288        copilot.update(cx, |copilot, cx| copilot.register_buffer(&buffer_2, cx));
1289        assert_eq!(
1290            lsp.receive_notification::<lsp::notification::DidOpenTextDocument>()
1291                .await,
1292            lsp::DidOpenTextDocumentParams {
1293                text_document: lsp::TextDocumentItem::new(
1294                    buffer_2_uri.clone(),
1295                    "plaintext".into(),
1296                    0,
1297                    "Goodbye".into()
1298                ),
1299            }
1300        );
1301
1302        buffer_1.update(cx, |buffer, cx| buffer.edit([(5..5, " world")], None, cx));
1303        assert_eq!(
1304            lsp.receive_notification::<lsp::notification::DidChangeTextDocument>()
1305                .await,
1306            lsp::DidChangeTextDocumentParams {
1307                text_document: lsp::VersionedTextDocumentIdentifier::new(buffer_1_uri.clone(), 1),
1308                content_changes: vec![lsp::TextDocumentContentChangeEvent {
1309                    range: Some(lsp::Range::new(
1310                        lsp::Position::new(0, 5),
1311                        lsp::Position::new(0, 5)
1312                    )),
1313                    range_length: None,
1314                    text: " world".into(),
1315                }],
1316            }
1317        );
1318
1319        // Ensure updates to the file are reflected in the LSP.
1320        buffer_1.update(cx, |buffer, cx| {
1321            buffer.file_updated(
1322                Arc::new(File {
1323                    abs_path: path!("/root/child/buffer-1").into(),
1324                    path: rel_path("child/buffer-1").into(),
1325                }),
1326                cx,
1327            )
1328        });
1329        assert_eq!(
1330            lsp.receive_notification::<lsp::notification::DidCloseTextDocument>()
1331                .await,
1332            lsp::DidCloseTextDocumentParams {
1333                text_document: lsp::TextDocumentIdentifier::new(buffer_1_uri),
1334            }
1335        );
1336        let buffer_1_uri = lsp::Uri::from_file_path(path!("/root/child/buffer-1")).unwrap();
1337        assert_eq!(
1338            lsp.receive_notification::<lsp::notification::DidOpenTextDocument>()
1339                .await,
1340            lsp::DidOpenTextDocumentParams {
1341                text_document: lsp::TextDocumentItem::new(
1342                    buffer_1_uri.clone(),
1343                    "plaintext".into(),
1344                    1,
1345                    "Hello world".into()
1346                ),
1347            }
1348        );
1349
1350        // Ensure all previously-registered buffers are closed when signing out.
1351        lsp.set_request_handler::<request::SignOut, _, _>(|_, _| async {
1352            Ok(request::SignOutResult {})
1353        });
1354        copilot
1355            .update(cx, |copilot, cx| copilot.sign_out(cx))
1356            .await
1357            .unwrap();
1358        assert_eq!(
1359            lsp.receive_notification::<lsp::notification::DidCloseTextDocument>()
1360                .await,
1361            lsp::DidCloseTextDocumentParams {
1362                text_document: lsp::TextDocumentIdentifier::new(buffer_1_uri.clone()),
1363            }
1364        );
1365        assert_eq!(
1366            lsp.receive_notification::<lsp::notification::DidCloseTextDocument>()
1367                .await,
1368            lsp::DidCloseTextDocumentParams {
1369                text_document: lsp::TextDocumentIdentifier::new(buffer_2_uri.clone()),
1370            }
1371        );
1372
1373        // Ensure all previously-registered buffers are re-opened when signing in.
1374        lsp.set_request_handler::<request::SignInInitiate, _, _>(|_, _| async {
1375            Ok(request::SignInInitiateResult::AlreadySignedIn {
1376                user: "user-1".into(),
1377            })
1378        });
1379        copilot
1380            .update(cx, |copilot, cx| copilot.sign_in(cx))
1381            .await
1382            .unwrap();
1383
1384        assert_eq!(
1385            lsp.receive_notification::<lsp::notification::DidOpenTextDocument>()
1386                .await,
1387            lsp::DidOpenTextDocumentParams {
1388                text_document: lsp::TextDocumentItem::new(
1389                    buffer_1_uri.clone(),
1390                    "plaintext".into(),
1391                    0,
1392                    "Hello world".into()
1393                ),
1394            }
1395        );
1396        assert_eq!(
1397            lsp.receive_notification::<lsp::notification::DidOpenTextDocument>()
1398                .await,
1399            lsp::DidOpenTextDocumentParams {
1400                text_document: lsp::TextDocumentItem::new(
1401                    buffer_2_uri.clone(),
1402                    "plaintext".into(),
1403                    0,
1404                    "Goodbye".into()
1405                ),
1406            }
1407        );
1408        // Dropping a buffer causes it to be closed on the LSP side as well.
1409        cx.update(|_| drop(buffer_2));
1410        assert_eq!(
1411            lsp.receive_notification::<lsp::notification::DidCloseTextDocument>()
1412                .await,
1413            lsp::DidCloseTextDocumentParams {
1414                text_document: lsp::TextDocumentIdentifier::new(buffer_2_uri),
1415            }
1416        );
1417    }
1418
1419    struct File {
1420        abs_path: PathBuf,
1421        path: Arc<RelPath>,
1422    }
1423
1424    impl language::File for File {
1425        fn as_local(&self) -> Option<&dyn language::LocalFile> {
1426            Some(self)
1427        }
1428
1429        fn disk_state(&self) -> language::DiskState {
1430            language::DiskState::Present {
1431                mtime: ::fs::MTime::from_seconds_and_nanos(100, 42),
1432            }
1433        }
1434
1435        fn path(&self) -> &Arc<RelPath> {
1436            &self.path
1437        }
1438
1439        fn path_style(&self, _: &App) -> PathStyle {
1440            PathStyle::local()
1441        }
1442
1443        fn full_path(&self, _: &App) -> PathBuf {
1444            unimplemented!()
1445        }
1446
1447        fn file_name<'a>(&'a self, _: &'a App) -> &'a str {
1448            unimplemented!()
1449        }
1450
1451        fn to_proto(&self, _: &App) -> rpc::proto::File {
1452            unimplemented!()
1453        }
1454
1455        fn worktree_id(&self, _: &App) -> settings::WorktreeId {
1456            settings::WorktreeId::from_usize(0)
1457        }
1458
1459        fn is_private(&self) -> bool {
1460            false
1461        }
1462    }
1463
1464    impl language::LocalFile for File {
1465        fn abs_path(&self, _: &App) -> PathBuf {
1466            self.abs_path.clone()
1467        }
1468
1469        fn load(&self, _: &App) -> Task<Result<String>> {
1470            unimplemented!()
1471        }
1472
1473        fn load_bytes(&self, _cx: &App) -> Task<Result<Vec<u8>>> {
1474            unimplemented!()
1475        }
1476    }
1477}
1478
1479#[cfg(test)]
1480#[ctor::ctor]
1481fn init_logger() {
1482    zlog::init_test();
1483}