copilot2.rs

   1pub mod request;
   2mod sign_in;
   3
   4use anyhow::{anyhow, Context as _, Result};
   5use async_compression::futures::bufread::GzipDecoder;
   6use async_tar::Archive;
   7use collections::{HashMap, HashSet};
   8use futures::{channel::oneshot, future::Shared, Future, FutureExt};
   9use gpui2::{
  10    AppContext, AsyncAppContext, Context, EventEmitter, Handle, ModelContext, Task, WeakHandle,
  11};
  12use language2::{
  13    language_settings::{all_language_settings, language_settings},
  14    point_from_lsp, point_to_lsp, Anchor, Bias, Buffer, BufferSnapshot, Language,
  15    LanguageServerName, PointUtf16, ToPointUtf16,
  16};
  17use lsp2::{LanguageServer, LanguageServerBinary, LanguageServerId};
  18use node_runtime::NodeRuntime;
  19use request::StatusNotification;
  20use settings2::SettingsStore;
  21use smol::{fs, io::BufReader, stream::StreamExt};
  22use std::{
  23    ffi::OsString,
  24    mem,
  25    ops::Range,
  26    path::{Path, PathBuf},
  27    sync::Arc,
  28};
  29use util::{
  30    fs::remove_matching, github::latest_github_release, http::HttpClient, paths, ResultExt,
  31};
  32
  33// todo!()
  34// const COPILOT_AUTH_NAMESPACE: &'static str = "copilot_auth";
  35// actions!(copilot_auth, [SignIn, SignOut]);
  36
  37// todo!()
  38// const COPILOT_NAMESPACE: &'static str = "copilot";
  39// actions!(
  40//     copilot,
  41//     [Suggest, NextSuggestion, PreviousSuggestion, Reinstall]
  42// );
  43
  44pub fn init(
  45    new_server_id: LanguageServerId,
  46    http: Arc<dyn HttpClient>,
  47    node_runtime: Arc<dyn NodeRuntime>,
  48    cx: &mut AppContext,
  49) {
  50    let copilot = cx.entity({
  51        let node_runtime = node_runtime.clone();
  52        move |cx| Copilot::start(new_server_id, http, node_runtime, cx)
  53    });
  54    cx.set_global(copilot.clone());
  55
  56    // TODO
  57    // cx.observe(&copilot, |handle, cx| {
  58    //     let status = handle.read(cx).status();
  59    //     cx.update_default_global::<collections::CommandPaletteFilter, _, _>(move |filter, _cx| {
  60    //         match status {
  61    //             Status::Disabled => {
  62    //                 filter.filtered_namespaces.insert(COPILOT_NAMESPACE);
  63    //                 filter.filtered_namespaces.insert(COPILOT_AUTH_NAMESPACE);
  64    //             }
  65    //             Status::Authorized => {
  66    //                 filter.filtered_namespaces.remove(COPILOT_NAMESPACE);
  67    //                 filter.filtered_namespaces.remove(COPILOT_AUTH_NAMESPACE);
  68    //             }
  69    //             _ => {
  70    //                 filter.filtered_namespaces.insert(COPILOT_NAMESPACE);
  71    //                 filter.filtered_namespaces.remove(COPILOT_AUTH_NAMESPACE);
  72    //             }
  73    //         }
  74    //     });
  75    // })
  76    // .detach();
  77
  78    // sign_in::init(cx);
  79    // cx.add_global_action(|_: &SignIn, cx| {
  80    //     if let Some(copilot) = Copilot::global(cx) {
  81    //         copilot
  82    //             .update(cx, |copilot, cx| copilot.sign_in(cx))
  83    //             .detach_and_log_err(cx);
  84    //     }
  85    // });
  86    // cx.add_global_action(|_: &SignOut, cx| {
  87    //     if let Some(copilot) = Copilot::global(cx) {
  88    //         copilot
  89    //             .update(cx, |copilot, cx| copilot.sign_out(cx))
  90    //             .detach_and_log_err(cx);
  91    //     }
  92    // });
  93
  94    // cx.add_global_action(|_: &Reinstall, cx| {
  95    //     if let Some(copilot) = Copilot::global(cx) {
  96    //         copilot
  97    //             .update(cx, |copilot, cx| copilot.reinstall(cx))
  98    //             .detach();
  99    //     }
 100    // });
 101}
 102
 103enum CopilotServer {
 104    Disabled,
 105    Starting { task: Shared<Task<()>> },
 106    Error(Arc<str>),
 107    Running(RunningCopilotServer),
 108}
 109
 110impl CopilotServer {
 111    fn as_authenticated(&mut self) -> Result<&mut RunningCopilotServer> {
 112        let server = self.as_running()?;
 113        if matches!(server.sign_in_status, SignInStatus::Authorized { .. }) {
 114            Ok(server)
 115        } else {
 116            Err(anyhow!("must sign in before using copilot"))
 117        }
 118    }
 119
 120    fn as_running(&mut self) -> Result<&mut RunningCopilotServer> {
 121        match self {
 122            CopilotServer::Starting { .. } => Err(anyhow!("copilot is still starting")),
 123            CopilotServer::Disabled => Err(anyhow!("copilot is disabled")),
 124            CopilotServer::Error(error) => Err(anyhow!(
 125                "copilot was not started because of an error: {}",
 126                error
 127            )),
 128            CopilotServer::Running(server) => Ok(server),
 129        }
 130    }
 131}
 132
 133struct RunningCopilotServer {
 134    name: LanguageServerName,
 135    lsp: Arc<LanguageServer>,
 136    sign_in_status: SignInStatus,
 137    registered_buffers: HashMap<usize, RegisteredBuffer>,
 138}
 139
 140#[derive(Clone, Debug)]
 141enum SignInStatus {
 142    Authorized,
 143    Unauthorized,
 144    SigningIn {
 145        prompt: Option<request::PromptUserDeviceFlow>,
 146        task: Shared<Task<Result<(), Arc<anyhow::Error>>>>,
 147    },
 148    SignedOut,
 149}
 150
 151#[derive(Debug, Clone)]
 152pub enum Status {
 153    Starting {
 154        task: Shared<Task<()>>,
 155    },
 156    Error(Arc<str>),
 157    Disabled,
 158    SignedOut,
 159    SigningIn {
 160        prompt: Option<request::PromptUserDeviceFlow>,
 161    },
 162    Unauthorized,
 163    Authorized,
 164}
 165
 166impl Status {
 167    pub fn is_authorized(&self) -> bool {
 168        matches!(self, Status::Authorized)
 169    }
 170}
 171
 172struct RegisteredBuffer {
 173    uri: lsp2::Url,
 174    language_id: String,
 175    snapshot: BufferSnapshot,
 176    snapshot_version: i32,
 177    _subscriptions: [gpui2::Subscription; 2],
 178    pending_buffer_change: Task<Option<()>>,
 179}
 180
 181impl RegisteredBuffer {
 182    fn report_changes(
 183        &mut self,
 184        buffer: &Handle<Buffer>,
 185        cx: &mut ModelContext<Copilot>,
 186    ) -> oneshot::Receiver<(i32, BufferSnapshot)> {
 187        let (done_tx, done_rx) = oneshot::channel();
 188
 189        if buffer.read(cx).version() == self.snapshot.version {
 190            let _ = done_tx.send((self.snapshot_version, self.snapshot.clone()));
 191        } else {
 192            let buffer = buffer.downgrade();
 193            let id = buffer.id();
 194            let prev_pending_change =
 195                mem::replace(&mut self.pending_buffer_change, Task::ready(None));
 196            self.pending_buffer_change = cx.spawn_weak(|copilot, mut cx| async move {
 197                prev_pending_change.await;
 198
 199                let old_version = copilot.upgrade(&cx)?.update(&mut cx, |copilot, _| {
 200                    let server = copilot.server.as_authenticated().log_err()?;
 201                    let buffer = server.registered_buffers.get_mut(&id)?;
 202                    Some(buffer.snapshot.version.clone())
 203                })?;
 204                let new_snapshot = buffer
 205                    .upgrade()?
 206                    .read_with(&cx, |buffer, _| buffer.snapshot());
 207
 208                let content_changes = cx
 209                    .background()
 210                    .spawn({
 211                        let new_snapshot = new_snapshot.clone();
 212                        async move {
 213                            new_snapshot
 214                                .edits_since::<(PointUtf16, usize)>(&old_version)
 215                                .map(|edit| {
 216                                    let edit_start = edit.new.start.0;
 217                                    let edit_end = edit_start + (edit.old.end.0 - edit.old.start.0);
 218                                    let new_text = new_snapshot
 219                                        .text_for_range(edit.new.start.1..edit.new.end.1)
 220                                        .collect();
 221                                    lsp2::TextDocumentContentChangeEvent {
 222                                        range: Some(lsp2::Range::new(
 223                                            point_to_lsp(edit_start),
 224                                            point_to_lsp(edit_end),
 225                                        )),
 226                                        range_length: None,
 227                                        text: new_text,
 228                                    }
 229                                })
 230                                .collect::<Vec<_>>()
 231                        }
 232                    })
 233                    .await;
 234
 235                copilot.upgrade(&cx)?.update(&mut cx, |copilot, _| {
 236                    let server = copilot.server.as_authenticated().log_err()?;
 237                    let buffer = server.registered_buffers.get_mut(&id)?;
 238                    if !content_changes.is_empty() {
 239                        buffer.snapshot_version += 1;
 240                        buffer.snapshot = new_snapshot;
 241                        server
 242                            .lsp
 243                            .notify::<lsp2::notification::DidChangeTextDocument>(
 244                                lsp2::DidChangeTextDocumentParams {
 245                                    text_document: lsp2::VersionedTextDocumentIdentifier::new(
 246                                        buffer.uri.clone(),
 247                                        buffer.snapshot_version,
 248                                    ),
 249                                    content_changes,
 250                                },
 251                            )
 252                            .log_err();
 253                    }
 254                    let _ = done_tx.send((buffer.snapshot_version, buffer.snapshot.clone()));
 255                    Some(())
 256                })?;
 257
 258                Some(())
 259            });
 260        }
 261
 262        done_rx
 263    }
 264}
 265
 266#[derive(Debug)]
 267pub struct Completion {
 268    pub uuid: String,
 269    pub range: Range<Anchor>,
 270    pub text: String,
 271}
 272
 273pub struct Copilot {
 274    http: Arc<dyn HttpClient>,
 275    node_runtime: Arc<dyn NodeRuntime>,
 276    server: CopilotServer,
 277    buffers: HashSet<Handle<Buffer>>,
 278    server_id: LanguageServerId,
 279    _subscription: gpui2::Subscription,
 280}
 281
 282pub enum Event {
 283    CopilotLanguageServerStarted,
 284}
 285
 286impl EventEmitter for Copilot {
 287    type Event = Event;
 288}
 289
 290impl Copilot {
 291    pub fn global(cx: &AppContext) -> Option<Handle<Self>> {
 292        if cx.has_global::<Handle<Self>>() {
 293            Some(cx.global::<Handle<Self>>().clone())
 294        } else {
 295            None
 296        }
 297    }
 298
 299    fn start(
 300        new_server_id: LanguageServerId,
 301        http: Arc<dyn HttpClient>,
 302        node_runtime: Arc<dyn NodeRuntime>,
 303        cx: &mut ModelContext<Self>,
 304    ) -> Self {
 305        let mut this = Self {
 306            server_id: new_server_id,
 307            http,
 308            node_runtime,
 309            server: CopilotServer::Disabled,
 310            buffers: Default::default(),
 311            _subscription: cx.on_app_quit(Self::shutdown_language_server),
 312        };
 313        this.enable_or_disable_copilot(cx);
 314        cx.observe_global::<SettingsStore, _>(move |this, cx| this.enable_or_disable_copilot(cx))
 315            .detach();
 316        this
 317    }
 318
 319    fn shutdown_language_server(
 320        &mut self,
 321        cx: &mut ModelContext<Self>,
 322    ) -> impl Future<Output = ()> {
 323        let shutdown = match mem::replace(&mut self.server, CopilotServer::Disabled) {
 324            CopilotServer::Running(server) => Some(Box::pin(async move { server.lsp.shutdown() })),
 325            _ => None,
 326        };
 327
 328        async move {
 329            if let Some(shutdown) = shutdown {
 330                shutdown.await;
 331            }
 332        }
 333    }
 334
 335    fn enable_or_disable_copilot(&mut self, cx: &mut ModelContext<Self>) {
 336        let server_id = self.server_id;
 337        let http = self.http.clone();
 338        let node_runtime = self.node_runtime.clone();
 339        if all_language_settings(None, cx).copilot_enabled(None, None) {
 340            if matches!(self.server, CopilotServer::Disabled) {
 341                let start_task = cx
 342                    .spawn({
 343                        move |this, cx| {
 344                            Self::start_language_server(server_id, http, node_runtime, this, cx)
 345                        }
 346                    })
 347                    .shared();
 348                self.server = CopilotServer::Starting { task: start_task };
 349                cx.notify();
 350            }
 351        } else {
 352            self.server = CopilotServer::Disabled;
 353            cx.notify();
 354        }
 355    }
 356
 357    // #[cfg(any(test, feature = "test-support"))]
 358    // pub fn fake(cx: &mut gpui::TestAppContext) -> (ModelHandle<Self>, lsp::FakeLanguageServer) {
 359    //     use node_runtime::FakeNodeRuntime;
 360
 361    //     let (server, fake_server) =
 362    //         LanguageServer::fake("copilot".into(), Default::default(), cx.to_async());
 363    //     let http = util::http::FakeHttpClient::create(|_| async { unreachable!() });
 364    //     let node_runtime = FakeNodeRuntime::new();
 365    //     let this = cx.add_model(|_| Self {
 366    //         server_id: LanguageServerId(0),
 367    //         http: http.clone(),
 368    //         node_runtime,
 369    //         server: CopilotServer::Running(RunningCopilotServer {
 370    //             name: LanguageServerName(Arc::from("copilot")),
 371    //             lsp: Arc::new(server),
 372    //             sign_in_status: SignInStatus::Authorized,
 373    //             registered_buffers: Default::default(),
 374    //         }),
 375    //         buffers: Default::default(),
 376    //     });
 377    //     (this, fake_server)
 378    // }
 379
 380    fn start_language_server(
 381        new_server_id: LanguageServerId,
 382        http: Arc<dyn HttpClient>,
 383        node_runtime: Arc<dyn NodeRuntime>,
 384        this: Handle<Self>,
 385        mut cx: AsyncAppContext,
 386    ) -> impl Future<Output = ()> {
 387        async move {
 388            let start_language_server = async {
 389                let server_path = get_copilot_lsp(http).await?;
 390                let node_path = node_runtime.binary_path().await?;
 391                let arguments: Vec<OsString> = vec![server_path.into(), "--stdio".into()];
 392                let binary = LanguageServerBinary {
 393                    path: node_path,
 394                    arguments,
 395                };
 396                let server =
 397                    LanguageServer::new(new_server_id, binary, Path::new("/"), None, cx.clone())?;
 398
 399                server
 400                    .on_notification::<StatusNotification, _>(
 401                        |_, _| { /* Silence the notification */ },
 402                    )
 403                    .detach();
 404
 405                let server = server.initialize(Default::default()).await?;
 406
 407                let status = server
 408                    .request::<request::CheckStatus>(request::CheckStatusParams {
 409                        local_checks_only: false,
 410                    })
 411                    .await?;
 412
 413                server
 414                    .request::<request::SetEditorInfo>(request::SetEditorInfoParams {
 415                        editor_info: request::EditorInfo {
 416                            name: "zed".into(),
 417                            version: env!("CARGO_PKG_VERSION").into(),
 418                        },
 419                        editor_plugin_info: request::EditorPluginInfo {
 420                            name: "zed-copilot".into(),
 421                            version: "0.0.1".into(),
 422                        },
 423                    })
 424                    .await?;
 425
 426                anyhow::Ok((server, status))
 427            };
 428
 429            let server = start_language_server.await;
 430            this.update(&mut cx, |this, cx| {
 431                cx.notify();
 432                match server {
 433                    Ok((server, status)) => {
 434                        this.server = CopilotServer::Running(RunningCopilotServer {
 435                            name: LanguageServerName(Arc::from("copilot")),
 436                            lsp: server,
 437                            sign_in_status: SignInStatus::SignedOut,
 438                            registered_buffers: Default::default(),
 439                        });
 440                        cx.emit(Event::CopilotLanguageServerStarted);
 441                        this.update_sign_in_status(status, cx);
 442                    }
 443                    Err(error) => {
 444                        this.server = CopilotServer::Error(error.to_string().into());
 445                        cx.notify()
 446                    }
 447                }
 448            })
 449        }
 450    }
 451
 452    pub fn sign_in(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
 453        if let CopilotServer::Running(server) = &mut self.server {
 454            let task = match &server.sign_in_status {
 455                SignInStatus::Authorized { .. } => Task::ready(Ok(())).shared(),
 456                SignInStatus::SigningIn { task, .. } => {
 457                    cx.notify();
 458                    task.clone()
 459                }
 460                SignInStatus::SignedOut | SignInStatus::Unauthorized { .. } => {
 461                    let lsp = server.lsp.clone();
 462                    let task = cx
 463                        .spawn(|this, mut cx| async move {
 464                            let sign_in = async {
 465                                let sign_in = lsp
 466                                    .request::<request::SignInInitiate>(
 467                                        request::SignInInitiateParams {},
 468                                    )
 469                                    .await?;
 470                                match sign_in {
 471                                    request::SignInInitiateResult::AlreadySignedIn { user } => {
 472                                        Ok(request::SignInStatus::Ok { user })
 473                                    }
 474                                    request::SignInInitiateResult::PromptUserDeviceFlow(flow) => {
 475                                        this.update(&mut cx, |this, cx| {
 476                                            if let CopilotServer::Running(RunningCopilotServer {
 477                                                sign_in_status: status,
 478                                                ..
 479                                            }) = &mut this.server
 480                                            {
 481                                                if let SignInStatus::SigningIn {
 482                                                    prompt: prompt_flow,
 483                                                    ..
 484                                                } = status
 485                                                {
 486                                                    *prompt_flow = Some(flow.clone());
 487                                                    cx.notify();
 488                                                }
 489                                            }
 490                                        });
 491                                        let response = lsp
 492                                            .request::<request::SignInConfirm>(
 493                                                request::SignInConfirmParams {
 494                                                    user_code: flow.user_code,
 495                                                },
 496                                            )
 497                                            .await?;
 498                                        Ok(response)
 499                                    }
 500                                }
 501                            };
 502
 503                            let sign_in = sign_in.await;
 504                            this.update(&mut cx, |this, cx| match sign_in {
 505                                Ok(status) => {
 506                                    this.update_sign_in_status(status, cx);
 507                                    Ok(())
 508                                }
 509                                Err(error) => {
 510                                    this.update_sign_in_status(
 511                                        request::SignInStatus::NotSignedIn,
 512                                        cx,
 513                                    );
 514                                    Err(Arc::new(error))
 515                                }
 516                            })
 517                        })
 518                        .shared();
 519                    server.sign_in_status = SignInStatus::SigningIn {
 520                        prompt: None,
 521                        task: task.clone(),
 522                    };
 523                    cx.notify();
 524                    task
 525                }
 526            };
 527
 528            cx.foreground()
 529                .spawn(task.map_err(|err| anyhow!("{:?}", err)))
 530        } else {
 531            // If we're downloading, wait until download is finished
 532            // If we're in a stuck state, display to the user
 533            Task::ready(Err(anyhow!("copilot hasn't started yet")))
 534        }
 535    }
 536
 537    fn sign_out(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
 538        self.update_sign_in_status(request::SignInStatus::NotSignedIn, cx);
 539        if let CopilotServer::Running(RunningCopilotServer { lsp: server, .. }) = &self.server {
 540            let server = server.clone();
 541            cx.background().spawn(async move {
 542                server
 543                    .request::<request::SignOut>(request::SignOutParams {})
 544                    .await?;
 545                anyhow::Ok(())
 546            })
 547        } else {
 548            Task::ready(Err(anyhow!("copilot hasn't started yet")))
 549        }
 550    }
 551
 552    pub fn reinstall(&mut self, cx: &mut ModelContext<Self>) -> Task<()> {
 553        let start_task = cx
 554            .spawn({
 555                let http = self.http.clone();
 556                let node_runtime = self.node_runtime.clone();
 557                let server_id = self.server_id;
 558                move |this, cx| async move {
 559                    clear_copilot_dir().await;
 560                    Self::start_language_server(server_id, http, node_runtime, this, cx).await
 561                }
 562            })
 563            .shared();
 564
 565        self.server = CopilotServer::Starting {
 566            task: start_task.clone(),
 567        };
 568
 569        cx.notify();
 570
 571        cx.foreground().spawn(start_task)
 572    }
 573
 574    pub fn language_server(&self) -> Option<(&LanguageServerName, &Arc<LanguageServer>)> {
 575        if let CopilotServer::Running(server) = &self.server {
 576            Some((&server.name, &server.lsp))
 577        } else {
 578            None
 579        }
 580    }
 581
 582    pub fn register_buffer(&mut self, buffer: &Handle<Buffer>, cx: &mut ModelContext<Self>) {
 583        let weak_buffer = buffer.downgrade();
 584        self.buffers.insert(weak_buffer.clone());
 585
 586        if let CopilotServer::Running(RunningCopilotServer {
 587            lsp: server,
 588            sign_in_status: status,
 589            registered_buffers,
 590            ..
 591        }) = &mut self.server
 592        {
 593            if !matches!(status, SignInStatus::Authorized { .. }) {
 594                return;
 595            }
 596
 597            registered_buffers.entry(buffer.id()).or_insert_with(|| {
 598                let uri: lsp2::Url = uri_for_buffer(buffer, cx);
 599                let language_id = id_for_language(buffer.read(cx).language());
 600                let snapshot = buffer.read(cx).snapshot();
 601                server
 602                    .notify::<lsp2::notification::DidOpenTextDocument>(
 603                        lsp2::DidOpenTextDocumentParams {
 604                            text_document: lsp2::TextDocumentItem {
 605                                uri: uri.clone(),
 606                                language_id: language_id.clone(),
 607                                version: 0,
 608                                text: snapshot.text(),
 609                            },
 610                        },
 611                    )
 612                    .log_err();
 613
 614                RegisteredBuffer {
 615                    uri,
 616                    language_id,
 617                    snapshot,
 618                    snapshot_version: 0,
 619                    pending_buffer_change: Task::ready(Some(())),
 620                    _subscriptions: [
 621                        cx.subscribe(buffer, |this, buffer, event, cx| {
 622                            this.handle_buffer_event(buffer, event, cx).log_err();
 623                        }),
 624                        cx.observe_release(buffer, move |this, _buffer, _cx| {
 625                            this.buffers.remove(&weak_buffer);
 626                            this.unregister_buffer(&weak_buffer);
 627                        }),
 628                    ],
 629                }
 630            });
 631        }
 632    }
 633
 634    fn handle_buffer_event(
 635        &mut self,
 636        buffer: Handle<Buffer>,
 637        event: &language2::Event,
 638        cx: &mut ModelContext<Self>,
 639    ) -> Result<()> {
 640        if let Ok(server) = self.server.as_running() {
 641            if let Some(registered_buffer) = server.registered_buffers.get_mut(&buffer.id()) {
 642                match event {
 643                    language2::Event::Edited => {
 644                        let _ = registered_buffer.report_changes(&buffer, cx);
 645                    }
 646                    language2::Event::Saved => {
 647                        server
 648                            .lsp
 649                            .notify::<lsp2::notification::DidSaveTextDocument>(
 650                                lsp2::DidSaveTextDocumentParams {
 651                                    text_document: lsp2::TextDocumentIdentifier::new(
 652                                        registered_buffer.uri.clone(),
 653                                    ),
 654                                    text: None,
 655                                },
 656                            )?;
 657                    }
 658                    language2::Event::FileHandleChanged | language2::Event::LanguageChanged => {
 659                        let new_language_id = id_for_language(buffer.read(cx).language());
 660                        let new_uri = uri_for_buffer(&buffer, cx);
 661                        if new_uri != registered_buffer.uri
 662                            || new_language_id != registered_buffer.language_id
 663                        {
 664                            let old_uri = mem::replace(&mut registered_buffer.uri, new_uri);
 665                            registered_buffer.language_id = new_language_id;
 666                            server
 667                                .lsp
 668                                .notify::<lsp2::notification::DidCloseTextDocument>(
 669                                    lsp2::DidCloseTextDocumentParams {
 670                                        text_document: lsp2::TextDocumentIdentifier::new(old_uri),
 671                                    },
 672                                )?;
 673                            server
 674                                .lsp
 675                                .notify::<lsp2::notification::DidOpenTextDocument>(
 676                                    lsp2::DidOpenTextDocumentParams {
 677                                        text_document: lsp2::TextDocumentItem::new(
 678                                            registered_buffer.uri.clone(),
 679                                            registered_buffer.language_id.clone(),
 680                                            registered_buffer.snapshot_version,
 681                                            registered_buffer.snapshot.text(),
 682                                        ),
 683                                    },
 684                                )?;
 685                        }
 686                    }
 687                    _ => {}
 688                }
 689            }
 690        }
 691
 692        Ok(())
 693    }
 694
 695    fn unregister_buffer(&mut self, buffer: &WeakHandle<Buffer>) {
 696        if let Ok(server) = self.server.as_running() {
 697            if let Some(buffer) = server.registered_buffers.remove(&buffer.id()) {
 698                server
 699                    .lsp
 700                    .notify::<lsp2::notification::DidCloseTextDocument>(
 701                        lsp2::DidCloseTextDocumentParams {
 702                            text_document: lsp2::TextDocumentIdentifier::new(buffer.uri),
 703                        },
 704                    )
 705                    .log_err();
 706            }
 707        }
 708    }
 709
 710    pub fn completions<T>(
 711        &mut self,
 712        buffer: &Handle<Buffer>,
 713        position: T,
 714        cx: &mut ModelContext<Self>,
 715    ) -> Task<Result<Vec<Completion>>>
 716    where
 717        T: ToPointUtf16,
 718    {
 719        self.request_completions::<request::GetCompletions, _>(buffer, position, cx)
 720    }
 721
 722    pub fn completions_cycling<T>(
 723        &mut self,
 724        buffer: &Handle<Buffer>,
 725        position: T,
 726        cx: &mut ModelContext<Self>,
 727    ) -> Task<Result<Vec<Completion>>>
 728    where
 729        T: ToPointUtf16,
 730    {
 731        self.request_completions::<request::GetCompletionsCycling, _>(buffer, position, cx)
 732    }
 733
 734    pub fn accept_completion(
 735        &mut self,
 736        completion: &Completion,
 737        cx: &mut ModelContext<Self>,
 738    ) -> Task<Result<()>> {
 739        let server = match self.server.as_authenticated() {
 740            Ok(server) => server,
 741            Err(error) => return Task::ready(Err(error)),
 742        };
 743        let request =
 744            server
 745                .lsp
 746                .request::<request::NotifyAccepted>(request::NotifyAcceptedParams {
 747                    uuid: completion.uuid.clone(),
 748                });
 749        cx.background().spawn(async move {
 750            request.await?;
 751            Ok(())
 752        })
 753    }
 754
 755    pub fn discard_completions(
 756        &mut self,
 757        completions: &[Completion],
 758        cx: &mut ModelContext<Self>,
 759    ) -> Task<Result<()>> {
 760        let server = match self.server.as_authenticated() {
 761            Ok(server) => server,
 762            Err(error) => return Task::ready(Err(error)),
 763        };
 764        let request =
 765            server
 766                .lsp
 767                .request::<request::NotifyRejected>(request::NotifyRejectedParams {
 768                    uuids: completions
 769                        .iter()
 770                        .map(|completion| completion.uuid.clone())
 771                        .collect(),
 772                });
 773        cx.background().spawn(async move {
 774            request.await?;
 775            Ok(())
 776        })
 777    }
 778
 779    fn request_completions<R, T>(
 780        &mut self,
 781        buffer: &Handle<Buffer>,
 782        position: T,
 783        cx: &mut ModelContext<Self>,
 784    ) -> Task<Result<Vec<Completion>>>
 785    where
 786        R: 'static
 787            + lsp2::request::Request<
 788                Params = request::GetCompletionsParams,
 789                Result = request::GetCompletionsResult,
 790            >,
 791        T: ToPointUtf16,
 792    {
 793        self.register_buffer(buffer, cx);
 794
 795        let server = match self.server.as_authenticated() {
 796            Ok(server) => server,
 797            Err(error) => return Task::ready(Err(error)),
 798        };
 799        let lsp = server.lsp.clone();
 800        let registered_buffer = server.registered_buffers.get_mut(&buffer.id()).unwrap();
 801        let snapshot = registered_buffer.report_changes(buffer, cx);
 802        let buffer = buffer.read(cx);
 803        let uri = registered_buffer.uri.clone();
 804        let position = position.to_point_utf16(buffer);
 805        let settings = language_settings(buffer.language_at(position).as_ref(), buffer.file(), cx);
 806        let tab_size = settings.tab_size;
 807        let hard_tabs = settings.hard_tabs;
 808        let relative_path = buffer
 809            .file()
 810            .map(|file| file.path().to_path_buf())
 811            .unwrap_or_default();
 812
 813        cx.foreground().spawn(async move {
 814            let (version, snapshot) = snapshot.await?;
 815            let result = lsp
 816                .request::<R>(request::GetCompletionsParams {
 817                    doc: request::GetCompletionsDocument {
 818                        uri,
 819                        tab_size: tab_size.into(),
 820                        indent_size: 1,
 821                        insert_spaces: !hard_tabs,
 822                        relative_path: relative_path.to_string_lossy().into(),
 823                        position: point_to_lsp(position),
 824                        version: version.try_into().unwrap(),
 825                    },
 826                })
 827                .await?;
 828            let completions = result
 829                .completions
 830                .into_iter()
 831                .map(|completion| {
 832                    let start = snapshot
 833                        .clip_point_utf16(point_from_lsp(completion.range.start), Bias::Left);
 834                    let end =
 835                        snapshot.clip_point_utf16(point_from_lsp(completion.range.end), Bias::Left);
 836                    Completion {
 837                        uuid: completion.uuid,
 838                        range: snapshot.anchor_before(start)..snapshot.anchor_after(end),
 839                        text: completion.text,
 840                    }
 841                })
 842                .collect();
 843            anyhow::Ok(completions)
 844        })
 845    }
 846
 847    pub fn status(&self) -> Status {
 848        match &self.server {
 849            CopilotServer::Starting { task } => Status::Starting { task: task.clone() },
 850            CopilotServer::Disabled => Status::Disabled,
 851            CopilotServer::Error(error) => Status::Error(error.clone()),
 852            CopilotServer::Running(RunningCopilotServer { sign_in_status, .. }) => {
 853                match sign_in_status {
 854                    SignInStatus::Authorized { .. } => Status::Authorized,
 855                    SignInStatus::Unauthorized { .. } => Status::Unauthorized,
 856                    SignInStatus::SigningIn { prompt, .. } => Status::SigningIn {
 857                        prompt: prompt.clone(),
 858                    },
 859                    SignInStatus::SignedOut => Status::SignedOut,
 860                }
 861            }
 862        }
 863    }
 864
 865    fn update_sign_in_status(
 866        &mut self,
 867        lsp_status: request::SignInStatus,
 868        cx: &mut ModelContext<Self>,
 869    ) {
 870        self.buffers.retain(|buffer| buffer.is_upgradable(cx));
 871
 872        if let Ok(server) = self.server.as_running() {
 873            match lsp_status {
 874                request::SignInStatus::Ok { .. }
 875                | request::SignInStatus::MaybeOk { .. }
 876                | request::SignInStatus::AlreadySignedIn { .. } => {
 877                    server.sign_in_status = SignInStatus::Authorized;
 878                    for buffer in self.buffers.iter().cloned().collect::<Vec<_>>() {
 879                        if let Some(buffer) = buffer.upgrade(cx) {
 880                            self.register_buffer(&buffer, cx);
 881                        }
 882                    }
 883                }
 884                request::SignInStatus::NotAuthorized { .. } => {
 885                    server.sign_in_status = SignInStatus::Unauthorized;
 886                    for buffer in self.buffers.iter().copied().collect::<Vec<_>>() {
 887                        self.unregister_buffer(&buffer);
 888                    }
 889                }
 890                request::SignInStatus::NotSignedIn => {
 891                    server.sign_in_status = SignInStatus::SignedOut;
 892                    for buffer in self.buffers.iter().copied().collect::<Vec<_>>() {
 893                        self.unregister_buffer(&buffer);
 894                    }
 895                }
 896            }
 897
 898            cx.notify();
 899        }
 900    }
 901}
 902
 903fn id_for_language(language: Option<&Arc<Language>>) -> String {
 904    let language_name = language.map(|language| language.name());
 905    match language_name.as_deref() {
 906        Some("Plain Text") => "plaintext".to_string(),
 907        Some(language_name) => language_name.to_lowercase(),
 908        None => "plaintext".to_string(),
 909    }
 910}
 911
 912fn uri_for_buffer(buffer: &Handle<Buffer>, cx: &AppContext) -> lsp2::Url {
 913    if let Some(file) = buffer.read(cx).file().and_then(|file| file.as_local()) {
 914        lsp2::Url::from_file_path(file.abs_path(cx)).unwrap()
 915    } else {
 916        format!("buffer://{}", buffer.id()).parse().unwrap()
 917    }
 918}
 919
 920async fn clear_copilot_dir() {
 921    remove_matching(&paths::COPILOT_DIR, |_| true).await
 922}
 923
 924async fn get_copilot_lsp(http: Arc<dyn HttpClient>) -> anyhow::Result<PathBuf> {
 925    const SERVER_PATH: &'static str = "dist/agent.js";
 926
 927    ///Check for the latest copilot language server and download it if we haven't already
 928    async fn fetch_latest(http: Arc<dyn HttpClient>) -> anyhow::Result<PathBuf> {
 929        let release = latest_github_release("zed-industries/copilot", false, http.clone()).await?;
 930
 931        let version_dir = &*paths::COPILOT_DIR.join(format!("copilot-{}", release.name));
 932
 933        fs::create_dir_all(version_dir).await?;
 934        let server_path = version_dir.join(SERVER_PATH);
 935
 936        if fs::metadata(&server_path).await.is_err() {
 937            // Copilot LSP looks for this dist dir specifcially, so lets add it in.
 938            let dist_dir = version_dir.join("dist");
 939            fs::create_dir_all(dist_dir.as_path()).await?;
 940
 941            let url = &release
 942                .assets
 943                .get(0)
 944                .context("Github release for copilot contained no assets")?
 945                .browser_download_url;
 946
 947            let mut response = http
 948                .get(&url, Default::default(), true)
 949                .await
 950                .map_err(|err| anyhow!("error downloading copilot release: {}", err))?;
 951            let decompressed_bytes = GzipDecoder::new(BufReader::new(response.body_mut()));
 952            let archive = Archive::new(decompressed_bytes);
 953            archive.unpack(dist_dir).await?;
 954
 955            remove_matching(&paths::COPILOT_DIR, |entry| entry != version_dir).await;
 956        }
 957
 958        Ok(server_path)
 959    }
 960
 961    match fetch_latest(http).await {
 962        ok @ Result::Ok(..) => ok,
 963        e @ Err(..) => {
 964            e.log_err();
 965            // Fetch a cached binary, if it exists
 966            (|| async move {
 967                let mut last_version_dir = None;
 968                let mut entries = fs::read_dir(paths::COPILOT_DIR.as_path()).await?;
 969                while let Some(entry) = entries.next().await {
 970                    let entry = entry?;
 971                    if entry.file_type().await?.is_dir() {
 972                        last_version_dir = Some(entry.path());
 973                    }
 974                }
 975                let last_version_dir =
 976                    last_version_dir.ok_or_else(|| anyhow!("no cached binary"))?;
 977                let server_path = last_version_dir.join(SERVER_PATH);
 978                if server_path.exists() {
 979                    Ok(server_path)
 980                } else {
 981                    Err(anyhow!(
 982                        "missing executable in directory {:?}",
 983                        last_version_dir
 984                    ))
 985                }
 986            })()
 987            .await
 988        }
 989    }
 990}
 991
 992// #[cfg(test)]
 993// mod tests {
 994//     use super::*;
 995//     use gpui::{executor::Deterministic, TestAppContext};
 996
 997//     #[gpui::test(iterations = 10)]
 998//     async fn test_buffer_management(deterministic: Arc<Deterministic>, cx: &mut TestAppContext) {
 999//         deterministic.forbid_parking();
1000//         let (copilot, mut lsp) = Copilot::fake(cx);
1001
1002//         let buffer_1 = cx.add_model(|cx| Buffer::new(0, cx.model_id() as u64, "Hello"));
1003//         let buffer_1_uri: lsp::Url = format!("buffer://{}", buffer_1.id()).parse().unwrap();
1004//         copilot.update(cx, |copilot, cx| copilot.register_buffer(&buffer_1, cx));
1005//         assert_eq!(
1006//             lsp.receive_notification::<lsp::notification::DidOpenTextDocument>()
1007//                 .await,
1008//             lsp::DidOpenTextDocumentParams {
1009//                 text_document: lsp::TextDocumentItem::new(
1010//                     buffer_1_uri.clone(),
1011//                     "plaintext".into(),
1012//                     0,
1013//                     "Hello".into()
1014//                 ),
1015//             }
1016//         );
1017
1018//         let buffer_2 = cx.add_model(|cx| Buffer::new(0, cx.model_id() as u64, "Goodbye"));
1019//         let buffer_2_uri: lsp::Url = format!("buffer://{}", buffer_2.id()).parse().unwrap();
1020//         copilot.update(cx, |copilot, cx| copilot.register_buffer(&buffer_2, cx));
1021//         assert_eq!(
1022//             lsp.receive_notification::<lsp::notification::DidOpenTextDocument>()
1023//                 .await,
1024//             lsp::DidOpenTextDocumentParams {
1025//                 text_document: lsp::TextDocumentItem::new(
1026//                     buffer_2_uri.clone(),
1027//                     "plaintext".into(),
1028//                     0,
1029//                     "Goodbye".into()
1030//                 ),
1031//             }
1032//         );
1033
1034//         buffer_1.update(cx, |buffer, cx| buffer.edit([(5..5, " world")], None, cx));
1035//         assert_eq!(
1036//             lsp.receive_notification::<lsp::notification::DidChangeTextDocument>()
1037//                 .await,
1038//             lsp::DidChangeTextDocumentParams {
1039//                 text_document: lsp::VersionedTextDocumentIdentifier::new(buffer_1_uri.clone(), 1),
1040//                 content_changes: vec![lsp::TextDocumentContentChangeEvent {
1041//                     range: Some(lsp::Range::new(
1042//                         lsp::Position::new(0, 5),
1043//                         lsp::Position::new(0, 5)
1044//                     )),
1045//                     range_length: None,
1046//                     text: " world".into(),
1047//                 }],
1048//             }
1049//         );
1050
1051//         // Ensure updates to the file are reflected in the LSP.
1052//         buffer_1
1053//             .update(cx, |buffer, cx| {
1054//                 buffer.file_updated(
1055//                     Arc::new(File {
1056//                         abs_path: "/root/child/buffer-1".into(),
1057//                         path: Path::new("child/buffer-1").into(),
1058//                     }),
1059//                     cx,
1060//                 )
1061//             })
1062//             .await;
1063//         assert_eq!(
1064//             lsp.receive_notification::<lsp::notification::DidCloseTextDocument>()
1065//                 .await,
1066//             lsp::DidCloseTextDocumentParams {
1067//                 text_document: lsp::TextDocumentIdentifier::new(buffer_1_uri),
1068//             }
1069//         );
1070//         let buffer_1_uri = lsp::Url::from_file_path("/root/child/buffer-1").unwrap();
1071//         assert_eq!(
1072//             lsp.receive_notification::<lsp::notification::DidOpenTextDocument>()
1073//                 .await,
1074//             lsp::DidOpenTextDocumentParams {
1075//                 text_document: lsp::TextDocumentItem::new(
1076//                     buffer_1_uri.clone(),
1077//                     "plaintext".into(),
1078//                     1,
1079//                     "Hello world".into()
1080//                 ),
1081//             }
1082//         );
1083
1084//         // Ensure all previously-registered buffers are closed when signing out.
1085//         lsp.handle_request::<request::SignOut, _, _>(|_, _| async {
1086//             Ok(request::SignOutResult {})
1087//         });
1088//         copilot
1089//             .update(cx, |copilot, cx| copilot.sign_out(cx))
1090//             .await
1091//             .unwrap();
1092//         assert_eq!(
1093//             lsp.receive_notification::<lsp::notification::DidCloseTextDocument>()
1094//                 .await,
1095//             lsp::DidCloseTextDocumentParams {
1096//                 text_document: lsp::TextDocumentIdentifier::new(buffer_2_uri.clone()),
1097//             }
1098//         );
1099//         assert_eq!(
1100//             lsp.receive_notification::<lsp::notification::DidCloseTextDocument>()
1101//                 .await,
1102//             lsp::DidCloseTextDocumentParams {
1103//                 text_document: lsp::TextDocumentIdentifier::new(buffer_1_uri.clone()),
1104//             }
1105//         );
1106
1107//         // Ensure all previously-registered buffers are re-opened when signing in.
1108//         lsp.handle_request::<request::SignInInitiate, _, _>(|_, _| async {
1109//             Ok(request::SignInInitiateResult::AlreadySignedIn {
1110//                 user: "user-1".into(),
1111//             })
1112//         });
1113//         copilot
1114//             .update(cx, |copilot, cx| copilot.sign_in(cx))
1115//             .await
1116//             .unwrap();
1117//         assert_eq!(
1118//             lsp.receive_notification::<lsp::notification::DidOpenTextDocument>()
1119//                 .await,
1120//             lsp::DidOpenTextDocumentParams {
1121//                 text_document: lsp::TextDocumentItem::new(
1122//                     buffer_2_uri.clone(),
1123//                     "plaintext".into(),
1124//                     0,
1125//                     "Goodbye".into()
1126//                 ),
1127//             }
1128//         );
1129//         assert_eq!(
1130//             lsp.receive_notification::<lsp::notification::DidOpenTextDocument>()
1131//                 .await,
1132//             lsp::DidOpenTextDocumentParams {
1133//                 text_document: lsp::TextDocumentItem::new(
1134//                     buffer_1_uri.clone(),
1135//                     "plaintext".into(),
1136//                     0,
1137//                     "Hello world".into()
1138//                 ),
1139//             }
1140//         );
1141
1142//         // Dropping a buffer causes it to be closed on the LSP side as well.
1143//         cx.update(|_| drop(buffer_2));
1144//         assert_eq!(
1145//             lsp.receive_notification::<lsp::notification::DidCloseTextDocument>()
1146//                 .await,
1147//             lsp::DidCloseTextDocumentParams {
1148//                 text_document: lsp::TextDocumentIdentifier::new(buffer_2_uri),
1149//             }
1150//         );
1151//     }
1152
1153//     struct File {
1154//         abs_path: PathBuf,
1155//         path: Arc<Path>,
1156//     }
1157
1158//     impl language2::File for File {
1159//         fn as_local(&self) -> Option<&dyn language2::LocalFile> {
1160//             Some(self)
1161//         }
1162
1163//         fn mtime(&self) -> std::time::SystemTime {
1164//             unimplemented!()
1165//         }
1166
1167//         fn path(&self) -> &Arc<Path> {
1168//             &self.path
1169//         }
1170
1171//         fn full_path(&self, _: &AppContext) -> PathBuf {
1172//             unimplemented!()
1173//         }
1174
1175//         fn file_name<'a>(&'a self, _: &'a AppContext) -> &'a std::ffi::OsStr {
1176//             unimplemented!()
1177//         }
1178
1179//         fn is_deleted(&self) -> bool {
1180//             unimplemented!()
1181//         }
1182
1183//         fn as_any(&self) -> &dyn std::any::Any {
1184//             unimplemented!()
1185//         }
1186
1187//         fn to_proto(&self) -> rpc::proto::File {
1188//             unimplemented!()
1189//         }
1190
1191//         fn worktree_id(&self) -> usize {
1192//             0
1193//         }
1194//     }
1195
1196//     impl language::LocalFile for File {
1197//         fn abs_path(&self, _: &AppContext) -> PathBuf {
1198//             self.abs_path.clone()
1199//         }
1200
1201//         fn load(&self, _: &AppContext) -> Task<Result<String>> {
1202//             unimplemented!()
1203//         }
1204
1205//         fn buffer_reloaded(
1206//             &self,
1207//             _: u64,
1208//             _: &clock::Global,
1209//             _: language::RopeFingerprint,
1210//             _: language::LineEnding,
1211//             _: std::time::SystemTime,
1212//             _: &mut AppContext,
1213//         ) {
1214//             unimplemented!()
1215//         }
1216//     }
1217// }