1261 lines · 45.5 KB · Rust
RawDownload
mod copilot_completion_provider;pub mod request;mod sign_in; use anyhow::{anyhow, Context as _, Result};use async_compression::futures::bufread::GzipDecoder;use async_tar::Archive;use collections::{HashMap, HashSet};use command_palette_hooks::CommandPaletteFilter;use futures::{channel::oneshot, future::Shared, Future, FutureExt, TryFutureExt};use gpui::{    actions, AppContext, AsyncAppContext, Context, Entity, EntityId, EventEmitter, Global, Model,    ModelContext, Task, WeakModel,};use http::github::latest_github_release;use http::HttpClient;use language::{    language_settings::{all_language_settings, language_settings, InlineCompletionProvider},    point_from_lsp, point_to_lsp, Anchor, Bias, Buffer, BufferSnapshot, Language, PointUtf16,    ToPointUtf16,};use lsp::{LanguageServer, LanguageServerBinary, LanguageServerId};use node_runtime::NodeRuntime;use parking_lot::Mutex;use request::StatusNotification;use settings::SettingsStore;use smol::{fs, io::BufReader, stream::StreamExt};use std::{    any::TypeId,    ffi::OsString,    mem,    ops::Range,    path::{Path, PathBuf},    sync::Arc,};use util::{fs::remove_matching, maybe, ResultExt}; pub use copilot_completion_provider::CopilotCompletionProvider;pub use sign_in::CopilotCodeVerification; actions!(    copilot,    [        Suggest,        NextSuggestion,        PreviousSuggestion,        Reinstall,        SignIn,        SignOut    ]); pub fn init(    new_server_id: LanguageServerId,    http: Arc<dyn HttpClient>,    node_runtime: Arc<dyn NodeRuntime>,    cx: &mut AppContext,) {    let copilot = cx.new_model({        let node_runtime = node_runtime.clone();        move |cx| Copilot::start(new_server_id, http, node_runtime, cx)    });    Copilot::set_global(copilot.clone(), cx);    cx.observe(&copilot, |handle, cx| {        let copilot_action_types = [            TypeId::of::<Suggest>(),            TypeId::of::<NextSuggestion>(),            TypeId::of::<PreviousSuggestion>(),            TypeId::of::<Reinstall>(),        ];        let copilot_auth_action_types = [TypeId::of::<SignOut>()];        let copilot_no_auth_action_types = [TypeId::of::<SignIn>()];        let status = handle.read(cx).status();        let filter = CommandPaletteFilter::global_mut(cx);         match status {            Status::Disabled => {                filter.hide_action_types(&copilot_action_types);                filter.hide_action_types(&copilot_auth_action_types);                filter.hide_action_types(&copilot_no_auth_action_types);            }            Status::Authorized => {                filter.hide_action_types(&copilot_no_auth_action_types);                filter.show_action_types(                    copilot_action_types                        .iter()                        .chain(&copilot_auth_action_types),                );            }            _ => {                filter.hide_action_types(&copilot_action_types);                filter.hide_action_types(&copilot_auth_action_types);                filter.show_action_types(copilot_no_auth_action_types.iter());            }        }    })    .detach();     cx.on_action(|_: &SignIn, cx| {        if let Some(copilot) = Copilot::global(cx) {            copilot                .update(cx, |copilot, cx| copilot.sign_in(cx))                .detach_and_log_err(cx);        }    });    cx.on_action(|_: &SignOut, cx| {        if let Some(copilot) = Copilot::global(cx) {            copilot                .update(cx, |copilot, cx| copilot.sign_out(cx))                .detach_and_log_err(cx);        }    });    cx.on_action(|_: &Reinstall, cx| {        if let Some(copilot) = Copilot::global(cx) {            copilot                .update(cx, |copilot, cx| copilot.reinstall(cx))                .detach();        }    });} enum CopilotServer {    Disabled,    Starting { task: Shared<Task<()>> },    Error(Arc<str>),    Running(RunningCopilotServer),} impl CopilotServer {    fn as_authenticated(&mut self) -> Result<&mut RunningCopilotServer> {        let server = self.as_running()?;        if matches!(server.sign_in_status, SignInStatus::Authorized { .. }) {            Ok(server)        } else {            Err(anyhow!("must sign in before using copilot"))        }    }     fn as_running(&mut self) -> Result<&mut RunningCopilotServer> {        match self {            CopilotServer::Starting { .. } => Err(anyhow!("copilot is still starting")),            CopilotServer::Disabled => Err(anyhow!("copilot is disabled")),            CopilotServer::Error(error) => Err(anyhow!(                "copilot was not started because of an error: {}",                error            )),            CopilotServer::Running(server) => Ok(server),        }    }} struct RunningCopilotServer {    lsp: Arc<LanguageServer>,    sign_in_status: SignInStatus,    registered_buffers: HashMap<EntityId, RegisteredBuffer>,} #[derive(Clone, Debug)]enum SignInStatus {    Authorized,    Unauthorized,    SigningIn {        prompt: Option<request::PromptUserDeviceFlow>,        task: Shared<Task<Result<(), Arc<anyhow::Error>>>>,    },    SignedOut,} #[derive(Debug, Clone)]pub enum Status {    Starting {        task: Shared<Task<()>>,    },    Error(Arc<str>),    Disabled,    SignedOut,    SigningIn {        prompt: Option<request::PromptUserDeviceFlow>,    },    Unauthorized,    Authorized,} impl Status {    pub fn is_authorized(&self) -> bool {        matches!(self, Status::Authorized)    }} struct RegisteredBuffer {    uri: lsp::Url,    language_id: String,    snapshot: BufferSnapshot,    snapshot_version: i32,    _subscriptions: [gpui::Subscription; 2],    pending_buffer_change: Task<Option<()>>,} impl RegisteredBuffer {    fn report_changes(        &mut self,        buffer: &Model<Buffer>,        cx: &mut ModelContext<Copilot>,    ) -> oneshot::Receiver<(i32, BufferSnapshot)> {        let (done_tx, done_rx) = oneshot::channel();         if buffer.read(cx).version() == self.snapshot.version {            let _ = done_tx.send((self.snapshot_version, self.snapshot.clone()));        } else {            let buffer = buffer.downgrade();            let id = buffer.entity_id();            let prev_pending_change =                mem::replace(&mut self.pending_buffer_change, Task::ready(None));            self.pending_buffer_change = cx.spawn(move |copilot, mut cx| async move {                prev_pending_change.await;                 let old_version = copilot                    .update(&mut cx, |copilot, _| {                        let server = copilot.server.as_authenticated().log_err()?;                        let buffer = server.registered_buffers.get_mut(&id)?;                        Some(buffer.snapshot.version.clone())                    })                    .ok()??;                let new_snapshot = buffer.update(&mut cx, |buffer, _| buffer.snapshot()).ok()?;                 let content_changes = cx                    .background_executor()                    .spawn({                        let new_snapshot = new_snapshot.clone();                        async move {                            new_snapshot                                .edits_since::<(PointUtf16, usize)>(&old_version)                                .map(|edit| {                                    let edit_start = edit.new.start.0;                                    let edit_end = edit_start + (edit.old.end.0 - edit.old.start.0);                                    let new_text = new_snapshot                                        .text_for_range(edit.new.start.1..edit.new.end.1)                                        .collect();                                    lsp::TextDocumentContentChangeEvent {                                        range: Some(lsp::Range::new(                                            point_to_lsp(edit_start),                                            point_to_lsp(edit_end),                                        )),                                        range_length: None,                                        text: new_text,                                    }                                })                                .collect::<Vec<_>>()                        }                    })                    .await;                 copilot                    .update(&mut cx, |copilot, _| {                        let server = copilot.server.as_authenticated().log_err()?;                        let buffer = server.registered_buffers.get_mut(&id)?;                        if !content_changes.is_empty() {                            buffer.snapshot_version += 1;                            buffer.snapshot = new_snapshot;                            server                                .lsp                                .notify::<lsp::notification::DidChangeTextDocument>(                                    lsp::DidChangeTextDocumentParams {                                        text_document: lsp::VersionedTextDocumentIdentifier::new(                                            buffer.uri.clone(),                                            buffer.snapshot_version,                                        ),                                        content_changes,                                    },                                )                                .log_err();                        }                        let _ = done_tx.send((buffer.snapshot_version, buffer.snapshot.clone()));                        Some(())                    })                    .ok()?;                 Some(())            });        }         done_rx    }} #[derive(Debug)]pub struct Completion {    pub uuid: String,    pub range: Range<Anchor>,    pub text: String,} pub struct Copilot {    http: Arc<dyn HttpClient>,    node_runtime: Arc<dyn NodeRuntime>,    server: CopilotServer,    buffers: HashSet<WeakModel<Buffer>>,    server_id: LanguageServerId,    _subscription: gpui::Subscription,} pub enum Event {    CopilotLanguageServerStarted,} impl EventEmitter<Event> for Copilot {} struct GlobalCopilot(Model<Copilot>); impl Global for GlobalCopilot {} impl Copilot {    pub fn global(cx: &AppContext) -> Option<Model<Self>> {        cx.try_global::<GlobalCopilot>()            .map(|model| model.0.clone())    }     pub fn set_global(copilot: Model<Self>, cx: &mut AppContext) {        cx.set_global(GlobalCopilot(copilot));    }     fn start(        new_server_id: LanguageServerId,        http: Arc<dyn HttpClient>,        node_runtime: Arc<dyn NodeRuntime>,        cx: &mut ModelContext<Self>,    ) -> Self {        let mut this = Self {            server_id: new_server_id,            http,            node_runtime,            server: CopilotServer::Disabled,            buffers: Default::default(),            _subscription: cx.on_app_quit(Self::shutdown_language_server),        };        this.enable_or_disable_copilot(cx);        cx.observe_global::<SettingsStore>(move |this, cx| this.enable_or_disable_copilot(cx))            .detach();        this    }     fn shutdown_language_server(        &mut self,        _cx: &mut ModelContext<Self>,    ) -> impl Future<Output = ()> {        let shutdown = match mem::replace(&mut self.server, CopilotServer::Disabled) {            CopilotServer::Running(server) => Some(Box::pin(async move { server.lsp.shutdown() })),            _ => None,        };         async move {            if let Some(shutdown) = shutdown {                shutdown.await;            }        }    }     fn enable_or_disable_copilot(&mut self, cx: &mut ModelContext<Self>) {        let server_id = self.server_id;        let http = self.http.clone();        let node_runtime = self.node_runtime.clone();        if all_language_settings(None, cx).inline_completions.provider            == InlineCompletionProvider::Copilot        {            if matches!(self.server, CopilotServer::Disabled) {                let start_task = cx                    .spawn(move |this, cx| {                        Self::start_language_server(server_id, http, node_runtime, this, cx)                    })                    .shared();                self.server = CopilotServer::Starting { task: start_task };                cx.notify();            }        } else {            self.server = CopilotServer::Disabled;            cx.notify();        }    }     #[cfg(any(test, feature = "test-support"))]    pub fn fake(cx: &mut gpui::TestAppContext) -> (Model<Self>, lsp::FakeLanguageServer) {        use lsp::FakeLanguageServer;        use node_runtime::FakeNodeRuntime;         let (server, fake_server) = FakeLanguageServer::new(            LanguageServerId(0),            LanguageServerBinary {                path: "path/to/copilot".into(),                arguments: vec![],                env: None,            },            "copilot".into(),            Default::default(),            cx.to_async(),        );        let http = http::FakeHttpClient::create(|_| async { unreachable!() });        let node_runtime = FakeNodeRuntime::new();        let this = cx.new_model(|cx| Self {            server_id: LanguageServerId(0),            http: http.clone(),            node_runtime,            server: CopilotServer::Running(RunningCopilotServer {                lsp: Arc::new(server),                sign_in_status: SignInStatus::Authorized,                registered_buffers: Default::default(),            }),            _subscription: cx.on_app_quit(Self::shutdown_language_server),            buffers: Default::default(),        });        (this, fake_server)    }     fn start_language_server(        new_server_id: LanguageServerId,        http: Arc<dyn HttpClient>,        node_runtime: Arc<dyn NodeRuntime>,        this: WeakModel<Self>,        mut cx: AsyncAppContext,    ) -> impl Future<Output = ()> {        async move {            let start_language_server = async {                let server_path = get_copilot_lsp(http).await?;                let node_path = node_runtime.binary_path().await?;                let arguments: Vec<OsString> = vec![server_path.into(), "--stdio".into()];                let binary = LanguageServerBinary {                    path: node_path,                    arguments,                    // TODO: We could set HTTP_PROXY etc here and fix the copilot issue.                    env: None,                };                 let root_path = if cfg!(target_os = "windows") {                    Path::new("C:/")                } else {                    Path::new("/")                };                 let server = LanguageServer::new(                    Arc::new(Mutex::new(None)),                    new_server_id,                    binary,                    root_path,                    None,                    cx.clone(),                )?;                 server                    .on_notification::<StatusNotification, _>(                        |_, _| { /* Silence the notification */ },                    )                    .detach();                let server = cx.update(|cx| server.initialize(None, cx))?.await?;                 let status = server                    .request::<request::CheckStatus>(request::CheckStatusParams {                        local_checks_only: false,                    })                    .await?;                 server                    .request::<request::SetEditorInfo>(request::SetEditorInfoParams {                        editor_info: request::EditorInfo {                            name: "zed".into(),                            version: env!("CARGO_PKG_VERSION").into(),                        },                        editor_plugin_info: request::EditorPluginInfo {                            name: "zed-copilot".into(),                            version: "0.0.1".into(),                        },                    })                    .await?;                 anyhow::Ok((server, status))            };             let server = start_language_server.await;            this.update(&mut cx, |this, cx| {                cx.notify();                match server {                    Ok((server, status)) => {                        this.server = CopilotServer::Running(RunningCopilotServer {                            lsp: server,                            sign_in_status: SignInStatus::SignedOut,                            registered_buffers: Default::default(),                        });                        cx.emit(Event::CopilotLanguageServerStarted);                        this.update_sign_in_status(status, cx);                    }                    Err(error) => {                        this.server = CopilotServer::Error(error.to_string().into());                        cx.notify()                    }                }            })            .ok();        }    }     pub fn sign_in(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {        if let CopilotServer::Running(server) = &mut self.server {

Lines 1–500 of 1261.