1271 lines · 45.7 KB · Rust
RawDownload
pub mod copilot_chat;mod copilot_completion_provider;pub mod request;mod sign_in; use ::fs::Fs;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, ActionTypeId, AppContext, AsyncAppContext, Context, Entity, EntityId, EventEmitter,    Global, Model, ModelContext, Task, WeakModel,};use http_client::github::latest_github_release;use http_client::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,    env,    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,    fs: Arc<dyn Fs>,    http: Arc<dyn HttpClient>,    node_runtime: Arc<dyn NodeRuntime>,    cx: &mut AppContext,) {    copilot_chat::init(fs, http.clone(), cx);     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 = [            ActionTypeId::of::<Suggest>(),            ActionTypeId::of::<NextSuggestion>(),            ActionTypeId::of::<PreviousSuggestion>(),            ActionTypeId::of::<Reinstall>(),        ];        let copilot_auth_action_types = [ActionTypeId::of::<SignOut>()];        let copilot_no_auth_action_types = [ActionTypeId::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)    }     pub fn is_disabled(&self) -> bool {        matches!(self, Status::Disabled)    }} 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,    CopilotAuthSignedIn,    CopilotAuthSignedOut,} 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_client::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)    }     async fn start_language_server(        new_server_id: LanguageServerId,        http: Arc<dyn HttpClient>,        node_runtime: Arc<dyn NodeRuntime>,        this: WeakModel<Self>,        mut cx: AsyncAppContext,    ) {        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()

Lines 1–500 of 1271.