@@ -0,0 +1,1217 @@
+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 futures::{channel::oneshot, future::Shared, Future, FutureExt};
+use gpui2::{
+ AppContext, AsyncAppContext, Context, EventEmitter, Handle, ModelContext, Task, WeakHandle,
+};
+use language2::{
+ language_settings::{all_language_settings, language_settings},
+ point_from_lsp, point_to_lsp, Anchor, Bias, Buffer, BufferSnapshot, Language,
+ LanguageServerName, PointUtf16, ToPointUtf16,
+};
+use lsp2::{LanguageServer, LanguageServerBinary, LanguageServerId};
+use node_runtime::NodeRuntime;
+use request::StatusNotification;
+use settings2::SettingsStore;
+use smol::{fs, io::BufReader, stream::StreamExt};
+use std::{
+ ffi::OsString,
+ mem,
+ ops::Range,
+ path::{Path, PathBuf},
+ sync::Arc,
+};
+use util::{
+ fs::remove_matching, github::latest_github_release, http::HttpClient, paths, ResultExt,
+};
+
+// todo!()
+// const COPILOT_AUTH_NAMESPACE: &'static str = "copilot_auth";
+// actions!(copilot_auth, [SignIn, SignOut]);
+
+// todo!()
+// const COPILOT_NAMESPACE: &'static str = "copilot";
+// actions!(
+// copilot,
+// [Suggest, NextSuggestion, PreviousSuggestion, Reinstall]
+// );
+
+pub fn init(
+ new_server_id: LanguageServerId,
+ http: Arc<dyn HttpClient>,
+ node_runtime: Arc<dyn NodeRuntime>,
+ cx: &mut AppContext,
+) {
+ let copilot = cx.entity({
+ let node_runtime = node_runtime.clone();
+ move |cx| Copilot::start(new_server_id, http, node_runtime, cx)
+ });
+ cx.set_global(copilot.clone());
+
+ // TODO
+ // cx.observe(&copilot, |handle, cx| {
+ // let status = handle.read(cx).status();
+ // cx.update_default_global::<collections::CommandPaletteFilter, _, _>(move |filter, _cx| {
+ // match status {
+ // Status::Disabled => {
+ // filter.filtered_namespaces.insert(COPILOT_NAMESPACE);
+ // filter.filtered_namespaces.insert(COPILOT_AUTH_NAMESPACE);
+ // }
+ // Status::Authorized => {
+ // filter.filtered_namespaces.remove(COPILOT_NAMESPACE);
+ // filter.filtered_namespaces.remove(COPILOT_AUTH_NAMESPACE);
+ // }
+ // _ => {
+ // filter.filtered_namespaces.insert(COPILOT_NAMESPACE);
+ // filter.filtered_namespaces.remove(COPILOT_AUTH_NAMESPACE);
+ // }
+ // }
+ // });
+ // })
+ // .detach();
+
+ // sign_in::init(cx);
+ // cx.add_global_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.add_global_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.add_global_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 {
+ name: LanguageServerName,
+ lsp: Arc<LanguageServer>,
+ sign_in_status: SignInStatus,
+ registered_buffers: HashMap<usize, 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: lsp2::Url,
+ language_id: String,
+ snapshot: BufferSnapshot,
+ snapshot_version: i32,
+ _subscriptions: [gpui2::Subscription; 2],
+ pending_buffer_change: Task<Option<()>>,
+}
+
+impl RegisteredBuffer {
+ fn report_changes(
+ &mut self,
+ buffer: &Handle<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.id();
+ let prev_pending_change =
+ mem::replace(&mut self.pending_buffer_change, Task::ready(None));
+ self.pending_buffer_change = cx.spawn_weak(|copilot, mut cx| async move {
+ prev_pending_change.await;
+
+ let old_version = copilot.upgrade(&cx)?.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())
+ })?;
+ let new_snapshot = buffer
+ .upgrade()?
+ .read_with(&cx, |buffer, _| buffer.snapshot());
+
+ let content_changes = cx
+ .background()
+ .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();
+ lsp2::TextDocumentContentChangeEvent {
+ range: Some(lsp2::Range::new(
+ point_to_lsp(edit_start),
+ point_to_lsp(edit_end),
+ )),
+ range_length: None,
+ text: new_text,
+ }
+ })
+ .collect::<Vec<_>>()
+ }
+ })
+ .await;
+
+ copilot.upgrade(&cx)?.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::<lsp2::notification::DidChangeTextDocument>(
+ lsp2::DidChangeTextDocumentParams {
+ text_document: lsp2::VersionedTextDocumentIdentifier::new(
+ buffer.uri.clone(),
+ buffer.snapshot_version,
+ ),
+ content_changes,
+ },
+ )
+ .log_err();
+ }
+ let _ = done_tx.send((buffer.snapshot_version, buffer.snapshot.clone()));
+ Some(())
+ })?;
+
+ 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<Handle<Buffer>>,
+ server_id: LanguageServerId,
+ _subscription: gpui2::Subscription,
+}
+
+pub enum Event {
+ CopilotLanguageServerStarted,
+}
+
+impl EventEmitter for Copilot {
+ type Event = Event;
+}
+
+impl Copilot {
+ pub fn global(cx: &AppContext) -> Option<Handle<Self>> {
+ if cx.has_global::<Handle<Self>>() {
+ Some(cx.global::<Handle<Self>>().clone())
+ } else {
+ None
+ }
+ }
+
+ 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).copilot_enabled(None, None) {
+ 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) -> (ModelHandle<Self>, lsp::FakeLanguageServer) {
+ // use node_runtime::FakeNodeRuntime;
+
+ // let (server, fake_server) =
+ // LanguageServer::fake("copilot".into(), Default::default(), cx.to_async());
+ // let http = util::http::FakeHttpClient::create(|_| async { unreachable!() });
+ // let node_runtime = FakeNodeRuntime::new();
+ // let this = cx.add_model(|_| Self {
+ // server_id: LanguageServerId(0),
+ // http: http.clone(),
+ // node_runtime,
+ // server: CopilotServer::Running(RunningCopilotServer {
+ // name: LanguageServerName(Arc::from("copilot")),
+ // lsp: Arc::new(server),
+ // sign_in_status: SignInStatus::Authorized,
+ // registered_buffers: Default::default(),
+ // }),
+ // buffers: Default::default(),
+ // });
+ // (this, fake_server)
+ // }
+
+ fn start_language_server(
+ new_server_id: LanguageServerId,
+ http: Arc<dyn HttpClient>,
+ node_runtime: Arc<dyn NodeRuntime>,
+ this: Handle<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,
+ };
+ let server =
+ LanguageServer::new(new_server_id, binary, Path::new("/"), None, cx.clone())?;
+
+ server
+ .on_notification::<StatusNotification, _>(
+ |_, _| { /* Silence the notification */ },
+ )
+ .detach();
+
+ let server = server.initialize(Default::default()).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 {
+ name: LanguageServerName(Arc::from("copilot")),
+ 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()
+ }
+ }
+ })
+ }
+ }
+
+ pub fn sign_in(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
+ if let CopilotServer::Running(server) = &mut self.server {
+ let task = match &server.sign_in_status {
+ SignInStatus::Authorized { .. } => Task::ready(Ok(())).shared(),
+ SignInStatus::SigningIn { task, .. } => {
+ cx.notify();
+ task.clone()
+ }
+ SignInStatus::SignedOut | SignInStatus::Unauthorized { .. } => {
+ let lsp = server.lsp.clone();
+ let task = cx
+ .spawn(|this, mut cx| async move {
+ let sign_in = async {
+ let sign_in = lsp
+ .request::<request::SignInInitiate>(
+ request::SignInInitiateParams {},
+ )
+ .await?;
+ match sign_in {
+ request::SignInInitiateResult::AlreadySignedIn { user } => {
+ Ok(request::SignInStatus::Ok { user })
+ }
+ request::SignInInitiateResult::PromptUserDeviceFlow(flow) => {
+ this.update(&mut cx, |this, cx| {
+ if let CopilotServer::Running(RunningCopilotServer {
+ sign_in_status: status,
+ ..
+ }) = &mut this.server
+ {
+ if let SignInStatus::SigningIn {
+ prompt: prompt_flow,
+ ..
+ } = status
+ {
+ *prompt_flow = Some(flow.clone());
+ cx.notify();
+ }
+ }
+ });
+ let response = lsp
+ .request::<request::SignInConfirm>(
+ request::SignInConfirmParams {
+ user_code: flow.user_code,
+ },
+ )
+ .await?;
+ Ok(response)
+ }
+ }
+ };
+
+ let sign_in = sign_in.await;
+ this.update(&mut cx, |this, cx| match sign_in {
+ Ok(status) => {
+ this.update_sign_in_status(status, cx);
+ Ok(())
+ }
+ Err(error) => {
+ this.update_sign_in_status(
+ request::SignInStatus::NotSignedIn,
+ cx,
+ );
+ Err(Arc::new(error))
+ }
+ })
+ })
+ .shared();
+ server.sign_in_status = SignInStatus::SigningIn {
+ prompt: None,
+ task: task.clone(),
+ };
+ cx.notify();
+ task
+ }
+ };
+
+ cx.foreground()
+ .spawn(task.map_err(|err| anyhow!("{:?}", err)))
+ } else {
+ // If we're downloading, wait until download is finished
+ // If we're in a stuck state, display to the user
+ Task::ready(Err(anyhow!("copilot hasn't started yet")))
+ }
+ }
+
+ fn sign_out(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
+ self.update_sign_in_status(request::SignInStatus::NotSignedIn, cx);
+ if let CopilotServer::Running(RunningCopilotServer { lsp: server, .. }) = &self.server {
+ let server = server.clone();
+ cx.background().spawn(async move {
+ server
+ .request::<request::SignOut>(request::SignOutParams {})
+ .await?;
+ anyhow::Ok(())
+ })
+ } else {
+ Task::ready(Err(anyhow!("copilot hasn't started yet")))
+ }
+ }
+
+ pub fn reinstall(&mut self, cx: &mut ModelContext<Self>) -> Task<()> {
+ let start_task = cx
+ .spawn({
+ let http = self.http.clone();
+ let node_runtime = self.node_runtime.clone();
+ let server_id = self.server_id;
+ move |this, cx| async move {
+ clear_copilot_dir().await;
+ Self::start_language_server(server_id, http, node_runtime, this, cx).await
+ }
+ })
+ .shared();
+
+ self.server = CopilotServer::Starting {
+ task: start_task.clone(),
+ };
+
+ cx.notify();
+
+ cx.foreground().spawn(start_task)
+ }
+
+ pub fn language_server(&self) -> Option<(&LanguageServerName, &Arc<LanguageServer>)> {
+ if let CopilotServer::Running(server) = &self.server {
+ Some((&server.name, &server.lsp))
+ } else {
+ None
+ }
+ }
+
+ pub fn register_buffer(&mut self, buffer: &Handle<Buffer>, cx: &mut ModelContext<Self>) {
+ let weak_buffer = buffer.downgrade();
+ self.buffers.insert(weak_buffer.clone());
+
+ if let CopilotServer::Running(RunningCopilotServer {
+ lsp: server,
+ sign_in_status: status,
+ registered_buffers,
+ ..
+ }) = &mut self.server
+ {
+ if !matches!(status, SignInStatus::Authorized { .. }) {
+ return;
+ }
+
+ registered_buffers.entry(buffer.id()).or_insert_with(|| {
+ let uri: lsp2::Url = uri_for_buffer(buffer, cx);
+ let language_id = id_for_language(buffer.read(cx).language());
+ let snapshot = buffer.read(cx).snapshot();
+ server
+ .notify::<lsp2::notification::DidOpenTextDocument>(
+ lsp2::DidOpenTextDocumentParams {
+ text_document: lsp2::TextDocumentItem {
+ uri: uri.clone(),
+ language_id: language_id.clone(),
+ version: 0,
+ text: snapshot.text(),
+ },
+ },
+ )
+ .log_err();
+
+ RegisteredBuffer {
+ uri,
+ language_id,
+ snapshot,
+ snapshot_version: 0,
+ pending_buffer_change: Task::ready(Some(())),
+ _subscriptions: [
+ cx.subscribe(buffer, |this, buffer, event, cx| {
+ this.handle_buffer_event(buffer, event, cx).log_err();
+ }),
+ cx.observe_release(buffer, move |this, _buffer, _cx| {
+ this.buffers.remove(&weak_buffer);
+ this.unregister_buffer(&weak_buffer);
+ }),
+ ],
+ }
+ });
+ }
+ }
+
+ fn handle_buffer_event(
+ &mut self,
+ buffer: Handle<Buffer>,
+ event: &language2::Event,
+ cx: &mut ModelContext<Self>,
+ ) -> Result<()> {
+ if let Ok(server) = self.server.as_running() {
+ if let Some(registered_buffer) = server.registered_buffers.get_mut(&buffer.id()) {
+ match event {
+ language2::Event::Edited => {
+ let _ = registered_buffer.report_changes(&buffer, cx);
+ }
+ language2::Event::Saved => {
+ server
+ .lsp
+ .notify::<lsp2::notification::DidSaveTextDocument>(
+ lsp2::DidSaveTextDocumentParams {
+ text_document: lsp2::TextDocumentIdentifier::new(
+ registered_buffer.uri.clone(),
+ ),
+ text: None,
+ },
+ )?;
+ }
+ language2::Event::FileHandleChanged | language2::Event::LanguageChanged => {
+ let new_language_id = id_for_language(buffer.read(cx).language());
+ let new_uri = uri_for_buffer(&buffer, cx);
+ if new_uri != registered_buffer.uri
+ || new_language_id != registered_buffer.language_id
+ {
+ let old_uri = mem::replace(&mut registered_buffer.uri, new_uri);
+ registered_buffer.language_id = new_language_id;
+ server
+ .lsp
+ .notify::<lsp2::notification::DidCloseTextDocument>(
+ lsp2::DidCloseTextDocumentParams {
+ text_document: lsp2::TextDocumentIdentifier::new(old_uri),
+ },
+ )?;
+ server
+ .lsp
+ .notify::<lsp2::notification::DidOpenTextDocument>(
+ lsp2::DidOpenTextDocumentParams {
+ text_document: lsp2::TextDocumentItem::new(
+ registered_buffer.uri.clone(),
+ registered_buffer.language_id.clone(),
+ registered_buffer.snapshot_version,
+ registered_buffer.snapshot.text(),
+ ),
+ },
+ )?;
+ }
+ }
+ _ => {}
+ }
+ }
+ }
+
+ Ok(())
+ }
+
+ fn unregister_buffer(&mut self, buffer: &WeakHandle<Buffer>) {
+ if let Ok(server) = self.server.as_running() {
+ if let Some(buffer) = server.registered_buffers.remove(&buffer.id()) {
+ server
+ .lsp
+ .notify::<lsp2::notification::DidCloseTextDocument>(
+ lsp2::DidCloseTextDocumentParams {
+ text_document: lsp2::TextDocumentIdentifier::new(buffer.uri),
+ },
+ )
+ .log_err();
+ }
+ }
+ }
+
+ pub fn completions<T>(
+ &mut self,
+ buffer: &Handle<Buffer>,
+ position: T,
+ cx: &mut ModelContext<Self>,
+ ) -> Task<Result<Vec<Completion>>>
+ where
+ T: ToPointUtf16,
+ {
+ self.request_completions::<request::GetCompletions, _>(buffer, position, cx)
+ }
+
+ pub fn completions_cycling<T>(
+ &mut self,
+ buffer: &Handle<Buffer>,
+ position: T,
+ cx: &mut ModelContext<Self>,
+ ) -> Task<Result<Vec<Completion>>>
+ where
+ T: ToPointUtf16,
+ {
+ self.request_completions::<request::GetCompletionsCycling, _>(buffer, position, cx)
+ }
+
+ pub fn accept_completion(
+ &mut self,
+ completion: &Completion,
+ cx: &mut ModelContext<Self>,
+ ) -> Task<Result<()>> {
+ let server = match self.server.as_authenticated() {
+ Ok(server) => server,
+ Err(error) => return Task::ready(Err(error)),
+ };
+ let request =
+ server
+ .lsp
+ .request::<request::NotifyAccepted>(request::NotifyAcceptedParams {
+ uuid: completion.uuid.clone(),
+ });
+ cx.background().spawn(async move {
+ request.await?;
+ Ok(())
+ })
+ }
+
+ pub fn discard_completions(
+ &mut self,
+ completions: &[Completion],
+ cx: &mut ModelContext<Self>,
+ ) -> Task<Result<()>> {
+ let server = match self.server.as_authenticated() {
+ Ok(server) => server,
+ Err(error) => return Task::ready(Err(error)),
+ };
+ let request =
+ server
+ .lsp
+ .request::<request::NotifyRejected>(request::NotifyRejectedParams {
+ uuids: completions
+ .iter()
+ .map(|completion| completion.uuid.clone())
+ .collect(),
+ });
+ cx.background().spawn(async move {
+ request.await?;
+ Ok(())
+ })
+ }
+
+ fn request_completions<R, T>(
+ &mut self,
+ buffer: &Handle<Buffer>,
+ position: T,
+ cx: &mut ModelContext<Self>,
+ ) -> Task<Result<Vec<Completion>>>
+ where
+ R: 'static
+ + lsp2::request::Request<
+ Params = request::GetCompletionsParams,
+ Result = request::GetCompletionsResult,
+ >,
+ T: ToPointUtf16,
+ {
+ self.register_buffer(buffer, cx);
+
+ let server = match self.server.as_authenticated() {
+ Ok(server) => server,
+ Err(error) => return Task::ready(Err(error)),
+ };
+ let lsp = server.lsp.clone();
+ let registered_buffer = server.registered_buffers.get_mut(&buffer.id()).unwrap();
+ let snapshot = registered_buffer.report_changes(buffer, cx);
+ let buffer = buffer.read(cx);
+ let uri = registered_buffer.uri.clone();
+ let position = position.to_point_utf16(buffer);
+ let settings = language_settings(buffer.language_at(position).as_ref(), buffer.file(), cx);
+ let tab_size = settings.tab_size;
+ let hard_tabs = settings.hard_tabs;
+ let relative_path = buffer
+ .file()
+ .map(|file| file.path().to_path_buf())
+ .unwrap_or_default();
+
+ cx.foreground().spawn(async move {
+ let (version, snapshot) = snapshot.await?;
+ let result = lsp
+ .request::<R>(request::GetCompletionsParams {
+ doc: request::GetCompletionsDocument {
+ uri,
+ tab_size: tab_size.into(),
+ indent_size: 1,
+ insert_spaces: !hard_tabs,
+ relative_path: relative_path.to_string_lossy().into(),
+ position: point_to_lsp(position),
+ version: version.try_into().unwrap(),
+ },
+ })
+ .await?;
+ let completions = result
+ .completions
+ .into_iter()
+ .map(|completion| {
+ let start = snapshot
+ .clip_point_utf16(point_from_lsp(completion.range.start), Bias::Left);
+ let end =
+ snapshot.clip_point_utf16(point_from_lsp(completion.range.end), Bias::Left);
+ Completion {
+ uuid: completion.uuid,
+ range: snapshot.anchor_before(start)..snapshot.anchor_after(end),
+ text: completion.text,
+ }
+ })
+ .collect();
+ anyhow::Ok(completions)
+ })
+ }
+
+ pub fn status(&self) -> Status {
+ match &self.server {
+ CopilotServer::Starting { task } => Status::Starting { task: task.clone() },
+ CopilotServer::Disabled => Status::Disabled,
+ CopilotServer::Error(error) => Status::Error(error.clone()),
+ CopilotServer::Running(RunningCopilotServer { sign_in_status, .. }) => {
+ match sign_in_status {
+ SignInStatus::Authorized { .. } => Status::Authorized,
+ SignInStatus::Unauthorized { .. } => Status::Unauthorized,
+ SignInStatus::SigningIn { prompt, .. } => Status::SigningIn {
+ prompt: prompt.clone(),
+ },
+ SignInStatus::SignedOut => Status::SignedOut,
+ }
+ }
+ }
+ }
+
+ fn update_sign_in_status(
+ &mut self,
+ lsp_status: request::SignInStatus,
+ cx: &mut ModelContext<Self>,
+ ) {
+ self.buffers.retain(|buffer| buffer.is_upgradable(cx));
+
+ if let Ok(server) = self.server.as_running() {
+ match lsp_status {
+ request::SignInStatus::Ok { .. }
+ | request::SignInStatus::MaybeOk { .. }
+ | request::SignInStatus::AlreadySignedIn { .. } => {
+ server.sign_in_status = SignInStatus::Authorized;
+ for buffer in self.buffers.iter().cloned().collect::<Vec<_>>() {
+ if let Some(buffer) = buffer.upgrade(cx) {
+ self.register_buffer(&buffer, cx);
+ }
+ }
+ }
+ request::SignInStatus::NotAuthorized { .. } => {
+ server.sign_in_status = SignInStatus::Unauthorized;
+ for buffer in self.buffers.iter().copied().collect::<Vec<_>>() {
+ self.unregister_buffer(&buffer);
+ }
+ }
+ request::SignInStatus::NotSignedIn => {
+ server.sign_in_status = SignInStatus::SignedOut;
+ for buffer in self.buffers.iter().copied().collect::<Vec<_>>() {
+ self.unregister_buffer(&buffer);
+ }
+ }
+ }
+
+ cx.notify();
+ }
+ }
+}
+
+fn id_for_language(language: Option<&Arc<Language>>) -> String {
+ let language_name = language.map(|language| language.name());
+ match language_name.as_deref() {
+ Some("Plain Text") => "plaintext".to_string(),
+ Some(language_name) => language_name.to_lowercase(),
+ None => "plaintext".to_string(),
+ }
+}
+
+fn uri_for_buffer(buffer: &Handle<Buffer>, cx: &AppContext) -> lsp2::Url {
+ if let Some(file) = buffer.read(cx).file().and_then(|file| file.as_local()) {
+ lsp2::Url::from_file_path(file.abs_path(cx)).unwrap()
+ } else {
+ format!("buffer://{}", buffer.id()).parse().unwrap()
+ }
+}
+
+async fn clear_copilot_dir() {
+ remove_matching(&paths::COPILOT_DIR, |_| true).await
+}
+
+async fn get_copilot_lsp(http: Arc<dyn HttpClient>) -> anyhow::Result<PathBuf> {
+ const SERVER_PATH: &'static str = "dist/agent.js";
+
+ ///Check for the latest copilot language server and download it if we haven't already
+ async fn fetch_latest(http: Arc<dyn HttpClient>) -> anyhow::Result<PathBuf> {
+ let release = latest_github_release("zed-industries/copilot", false, http.clone()).await?;
+
+ let version_dir = &*paths::COPILOT_DIR.join(format!("copilot-{}", release.name));
+
+ fs::create_dir_all(version_dir).await?;
+ let server_path = version_dir.join(SERVER_PATH);
+
+ if fs::metadata(&server_path).await.is_err() {
+ // Copilot LSP looks for this dist dir specifcially, so lets add it in.
+ let dist_dir = version_dir.join("dist");
+ fs::create_dir_all(dist_dir.as_path()).await?;
+
+ let url = &release
+ .assets
+ .get(0)
+ .context("Github release for copilot contained no assets")?
+ .browser_download_url;
+
+ let mut response = http
+ .get(&url, Default::default(), true)
+ .await
+ .map_err(|err| anyhow!("error downloading copilot release: {}", err))?;
+ let decompressed_bytes = GzipDecoder::new(BufReader::new(response.body_mut()));
+ let archive = Archive::new(decompressed_bytes);
+ archive.unpack(dist_dir).await?;
+
+ remove_matching(&paths::COPILOT_DIR, |entry| entry != version_dir).await;
+ }
+
+ Ok(server_path)
+ }
+
+ match fetch_latest(http).await {
+ ok @ Result::Ok(..) => ok,
+ e @ Err(..) => {
+ e.log_err();
+ // Fetch a cached binary, if it exists
+ (|| async move {
+ let mut last_version_dir = None;
+ let mut entries = fs::read_dir(paths::COPILOT_DIR.as_path()).await?;
+ while let Some(entry) = entries.next().await {
+ let entry = entry?;
+ if entry.file_type().await?.is_dir() {
+ last_version_dir = Some(entry.path());
+ }
+ }
+ let last_version_dir =
+ last_version_dir.ok_or_else(|| anyhow!("no cached binary"))?;
+ let server_path = last_version_dir.join(SERVER_PATH);
+ if server_path.exists() {
+ Ok(server_path)
+ } else {
+ Err(anyhow!(
+ "missing executable in directory {:?}",
+ last_version_dir
+ ))
+ }
+ })()
+ .await
+ }
+ }
+}
+
+// #[cfg(test)]
+// mod tests {
+// use super::*;
+// use gpui::{executor::Deterministic, TestAppContext};
+
+// #[gpui::test(iterations = 10)]
+// async fn test_buffer_management(deterministic: Arc<Deterministic>, cx: &mut TestAppContext) {
+// deterministic.forbid_parking();
+// let (copilot, mut lsp) = Copilot::fake(cx);
+
+// let buffer_1 = cx.add_model(|cx| Buffer::new(0, cx.model_id() as u64, "Hello"));
+// let buffer_1_uri: lsp::Url = format!("buffer://{}", buffer_1.id()).parse().unwrap();
+// copilot.update(cx, |copilot, cx| copilot.register_buffer(&buffer_1, cx));
+// assert_eq!(
+// lsp.receive_notification::<lsp::notification::DidOpenTextDocument>()
+// .await,
+// lsp::DidOpenTextDocumentParams {
+// text_document: lsp::TextDocumentItem::new(
+// buffer_1_uri.clone(),
+// "plaintext".into(),
+// 0,
+// "Hello".into()
+// ),
+// }
+// );
+
+// let buffer_2 = cx.add_model(|cx| Buffer::new(0, cx.model_id() as u64, "Goodbye"));
+// let buffer_2_uri: lsp::Url = format!("buffer://{}", buffer_2.id()).parse().unwrap();
+// copilot.update(cx, |copilot, cx| copilot.register_buffer(&buffer_2, cx));
+// assert_eq!(
+// lsp.receive_notification::<lsp::notification::DidOpenTextDocument>()
+// .await,
+// lsp::DidOpenTextDocumentParams {
+// text_document: lsp::TextDocumentItem::new(
+// buffer_2_uri.clone(),
+// "plaintext".into(),
+// 0,
+// "Goodbye".into()
+// ),
+// }
+// );
+
+// buffer_1.update(cx, |buffer, cx| buffer.edit([(5..5, " world")], None, cx));
+// assert_eq!(
+// lsp.receive_notification::<lsp::notification::DidChangeTextDocument>()
+// .await,
+// lsp::DidChangeTextDocumentParams {
+// text_document: lsp::VersionedTextDocumentIdentifier::new(buffer_1_uri.clone(), 1),
+// content_changes: vec![lsp::TextDocumentContentChangeEvent {
+// range: Some(lsp::Range::new(
+// lsp::Position::new(0, 5),
+// lsp::Position::new(0, 5)
+// )),
+// range_length: None,
+// text: " world".into(),
+// }],
+// }
+// );
+
+// // Ensure updates to the file are reflected in the LSP.
+// buffer_1
+// .update(cx, |buffer, cx| {
+// buffer.file_updated(
+// Arc::new(File {
+// abs_path: "/root/child/buffer-1".into(),
+// path: Path::new("child/buffer-1").into(),
+// }),
+// cx,
+// )
+// })
+// .await;
+// assert_eq!(
+// lsp.receive_notification::<lsp::notification::DidCloseTextDocument>()
+// .await,
+// lsp::DidCloseTextDocumentParams {
+// text_document: lsp::TextDocumentIdentifier::new(buffer_1_uri),
+// }
+// );
+// let buffer_1_uri = lsp::Url::from_file_path("/root/child/buffer-1").unwrap();
+// assert_eq!(
+// lsp.receive_notification::<lsp::notification::DidOpenTextDocument>()
+// .await,
+// lsp::DidOpenTextDocumentParams {
+// text_document: lsp::TextDocumentItem::new(
+// buffer_1_uri.clone(),
+// "plaintext".into(),
+// 1,
+// "Hello world".into()
+// ),
+// }
+// );
+
+// // Ensure all previously-registered buffers are closed when signing out.
+// lsp.handle_request::<request::SignOut, _, _>(|_, _| async {
+// Ok(request::SignOutResult {})
+// });
+// copilot
+// .update(cx, |copilot, cx| copilot.sign_out(cx))
+// .await
+// .unwrap();
+// assert_eq!(
+// lsp.receive_notification::<lsp::notification::DidCloseTextDocument>()
+// .await,
+// lsp::DidCloseTextDocumentParams {
+// text_document: lsp::TextDocumentIdentifier::new(buffer_2_uri.clone()),
+// }
+// );
+// assert_eq!(
+// lsp.receive_notification::<lsp::notification::DidCloseTextDocument>()
+// .await,
+// lsp::DidCloseTextDocumentParams {
+// text_document: lsp::TextDocumentIdentifier::new(buffer_1_uri.clone()),
+// }
+// );
+
+// // Ensure all previously-registered buffers are re-opened when signing in.
+// lsp.handle_request::<request::SignInInitiate, _, _>(|_, _| async {
+// Ok(request::SignInInitiateResult::AlreadySignedIn {
+// user: "user-1".into(),
+// })
+// });
+// copilot
+// .update(cx, |copilot, cx| copilot.sign_in(cx))
+// .await
+// .unwrap();
+// assert_eq!(
+// lsp.receive_notification::<lsp::notification::DidOpenTextDocument>()
+// .await,
+// lsp::DidOpenTextDocumentParams {
+// text_document: lsp::TextDocumentItem::new(
+// buffer_2_uri.clone(),
+// "plaintext".into(),
+// 0,
+// "Goodbye".into()
+// ),
+// }
+// );
+// assert_eq!(
+// lsp.receive_notification::<lsp::notification::DidOpenTextDocument>()
+// .await,
+// lsp::DidOpenTextDocumentParams {
+// text_document: lsp::TextDocumentItem::new(
+// buffer_1_uri.clone(),
+// "plaintext".into(),
+// 0,
+// "Hello world".into()
+// ),
+// }
+// );
+
+// // Dropping a buffer causes it to be closed on the LSP side as well.
+// cx.update(|_| drop(buffer_2));
+// assert_eq!(
+// lsp.receive_notification::<lsp::notification::DidCloseTextDocument>()
+// .await,
+// lsp::DidCloseTextDocumentParams {
+// text_document: lsp::TextDocumentIdentifier::new(buffer_2_uri),
+// }
+// );
+// }
+
+// struct File {
+// abs_path: PathBuf,
+// path: Arc<Path>,
+// }
+
+// impl language2::File for File {
+// fn as_local(&self) -> Option<&dyn language2::LocalFile> {
+// Some(self)
+// }
+
+// fn mtime(&self) -> std::time::SystemTime {
+// unimplemented!()
+// }
+
+// fn path(&self) -> &Arc<Path> {
+// &self.path
+// }
+
+// fn full_path(&self, _: &AppContext) -> PathBuf {
+// unimplemented!()
+// }
+
+// fn file_name<'a>(&'a self, _: &'a AppContext) -> &'a std::ffi::OsStr {
+// unimplemented!()
+// }
+
+// fn is_deleted(&self) -> bool {
+// unimplemented!()
+// }
+
+// fn as_any(&self) -> &dyn std::any::Any {
+// unimplemented!()
+// }
+
+// fn to_proto(&self) -> rpc::proto::File {
+// unimplemented!()
+// }
+
+// fn worktree_id(&self) -> usize {
+// 0
+// }
+// }
+
+// impl language::LocalFile for File {
+// fn abs_path(&self, _: &AppContext) -> PathBuf {
+// self.abs_path.clone()
+// }
+
+// fn load(&self, _: &AppContext) -> Task<Result<String>> {
+// unimplemented!()
+// }
+
+// fn buffer_reloaded(
+// &self,
+// _: u64,
+// _: &clock::Global,
+// _: language::RopeFingerprint,
+// _: language::LineEnding,
+// _: std::time::SystemTime,
+// _: &mut AppContext,
+// ) {
+// unimplemented!()
+// }
+// }
+// }
@@ -0,0 +1,376 @@
+// TODO add logging in
+// use crate::{request::PromptUserDeviceFlow, Copilot, Status};
+// use gpui::{
+// elements::*,
+// geometry::rect::RectF,
+// platform::{WindowBounds, WindowKind, WindowOptions},
+// AnyElement, AnyViewHandle, AppContext, ClipboardItem, Element, Entity, View, ViewContext,
+// WindowHandle,
+// };
+// use theme::ui::modal;
+
+// #[derive(PartialEq, Eq, Debug, Clone)]
+// struct CopyUserCode;
+
+// #[derive(PartialEq, Eq, Debug, Clone)]
+// struct OpenGithub;
+
+// const COPILOT_SIGN_UP_URL: &'static str = "https://github.com/features/copilot";
+
+// pub fn init(cx: &mut AppContext) {
+// if let Some(copilot) = Copilot::global(cx) {
+// let mut verification_window: Option<WindowHandle<CopilotCodeVerification>> = None;
+// cx.observe(&copilot, move |copilot, cx| {
+// let status = copilot.read(cx).status();
+
+// match &status {
+// crate::Status::SigningIn { prompt } => {
+// if let Some(window) = verification_window.as_mut() {
+// let updated = window
+// .root(cx)
+// .map(|root| {
+// root.update(cx, |verification, cx| {
+// verification.set_status(status.clone(), cx);
+// cx.activate_window();
+// })
+// })
+// .is_some();
+// if !updated {
+// verification_window = Some(create_copilot_auth_window(cx, &status));
+// }
+// } else if let Some(_prompt) = prompt {
+// verification_window = Some(create_copilot_auth_window(cx, &status));
+// }
+// }
+// Status::Authorized | Status::Unauthorized => {
+// if let Some(window) = verification_window.as_ref() {
+// if let Some(verification) = window.root(cx) {
+// verification.update(cx, |verification, cx| {
+// verification.set_status(status, cx);
+// cx.platform().activate(true);
+// cx.activate_window();
+// });
+// }
+// }
+// }
+// _ => {
+// if let Some(code_verification) = verification_window.take() {
+// code_verification.update(cx, |cx| cx.remove_window());
+// }
+// }
+// }
+// })
+// .detach();
+// }
+// }
+
+// fn create_copilot_auth_window(
+// cx: &mut AppContext,
+// status: &Status,
+// ) -> WindowHandle<CopilotCodeVerification> {
+// let window_size = theme::current(cx).copilot.modal.dimensions();
+// let window_options = WindowOptions {
+// bounds: WindowBounds::Fixed(RectF::new(Default::default(), window_size)),
+// titlebar: None,
+// center: true,
+// focus: true,
+// show: true,
+// kind: WindowKind::Normal,
+// is_movable: true,
+// screen: None,
+// };
+// cx.add_window(window_options, |_cx| {
+// CopilotCodeVerification::new(status.clone())
+// })
+// }
+
+// pub struct CopilotCodeVerification {
+// status: Status,
+// connect_clicked: bool,
+// }
+
+// impl CopilotCodeVerification {
+// pub fn new(status: Status) -> Self {
+// Self {
+// status,
+// connect_clicked: false,
+// }
+// }
+
+// pub fn set_status(&mut self, status: Status, cx: &mut ViewContext<Self>) {
+// self.status = status;
+// cx.notify();
+// }
+
+// fn render_device_code(
+// data: &PromptUserDeviceFlow,
+// style: &theme::Copilot,
+// cx: &mut ViewContext<Self>,
+// ) -> impl Element<Self> {
+// let copied = cx
+// .read_from_clipboard()
+// .map(|item| item.text() == &data.user_code)
+// .unwrap_or(false);
+
+// let device_code_style = &style.auth.prompting.device_code;
+
+// MouseEventHandler::new::<Self, _>(0, cx, |state, _cx| {
+// Flex::row()
+// .with_child(
+// Label::new(data.user_code.clone(), device_code_style.text.clone())
+// .aligned()
+// .contained()
+// .with_style(device_code_style.left_container)
+// .constrained()
+// .with_width(device_code_style.left),
+// )
+// .with_child(
+// Label::new(
+// if copied { "Copied!" } else { "Copy" },
+// device_code_style.cta.style_for(state).text.clone(),
+// )
+// .aligned()
+// .contained()
+// .with_style(*device_code_style.right_container.style_for(state))
+// .constrained()
+// .with_width(device_code_style.right),
+// )
+// .contained()
+// .with_style(device_code_style.cta.style_for(state).container)
+// })
+// .on_click(gpui::platform::MouseButton::Left, {
+// let user_code = data.user_code.clone();
+// move |_, _, cx| {
+// cx.platform()
+// .write_to_clipboard(ClipboardItem::new(user_code.clone()));
+// cx.notify();
+// }
+// })
+// .with_cursor_style(gpui::platform::CursorStyle::PointingHand)
+// }
+
+// fn render_prompting_modal(
+// connect_clicked: bool,
+// data: &PromptUserDeviceFlow,
+// style: &theme::Copilot,
+// cx: &mut ViewContext<Self>,
+// ) -> AnyElement<Self> {
+// enum ConnectButton {}
+
+// Flex::column()
+// .with_child(
+// Flex::column()
+// .with_children([
+// Label::new(
+// "Enable Copilot by connecting",
+// style.auth.prompting.subheading.text.clone(),
+// )
+// .aligned(),
+// Label::new(
+// "your existing license.",
+// style.auth.prompting.subheading.text.clone(),
+// )
+// .aligned(),
+// ])
+// .align_children_center()
+// .contained()
+// .with_style(style.auth.prompting.subheading.container),
+// )
+// .with_child(Self::render_device_code(data, &style, cx))
+// .with_child(
+// Flex::column()
+// .with_children([
+// Label::new(
+// "Paste this code into GitHub after",
+// style.auth.prompting.hint.text.clone(),
+// )
+// .aligned(),
+// Label::new(
+// "clicking the button below.",
+// style.auth.prompting.hint.text.clone(),
+// )
+// .aligned(),
+// ])
+// .align_children_center()
+// .contained()
+// .with_style(style.auth.prompting.hint.container.clone()),
+// )
+// .with_child(theme::ui::cta_button::<ConnectButton, _, _, _>(
+// if connect_clicked {
+// "Waiting for connection..."
+// } else {
+// "Connect to GitHub"
+// },
+// style.auth.content_width,
+// &style.auth.cta_button,
+// cx,
+// {
+// let verification_uri = data.verification_uri.clone();
+// move |_, verification, cx| {
+// cx.platform().open_url(&verification_uri);
+// verification.connect_clicked = true;
+// }
+// },
+// ))
+// .align_children_center()
+// .into_any()
+// }
+
+// fn render_enabled_modal(
+// style: &theme::Copilot,
+// cx: &mut ViewContext<Self>,
+// ) -> AnyElement<Self> {
+// enum DoneButton {}
+
+// let enabled_style = &style.auth.authorized;
+// Flex::column()
+// .with_child(
+// Label::new("Copilot Enabled!", enabled_style.subheading.text.clone())
+// .contained()
+// .with_style(enabled_style.subheading.container)
+// .aligned(),
+// )
+// .with_child(
+// Flex::column()
+// .with_children([
+// Label::new(
+// "You can update your settings or",
+// enabled_style.hint.text.clone(),
+// )
+// .aligned(),
+// Label::new(
+// "sign out from the Copilot menu in",
+// enabled_style.hint.text.clone(),
+// )
+// .aligned(),
+// Label::new("the status bar.", enabled_style.hint.text.clone()).aligned(),
+// ])
+// .align_children_center()
+// .contained()
+// .with_style(enabled_style.hint.container),
+// )
+// .with_child(theme::ui::cta_button::<DoneButton, _, _, _>(
+// "Done",
+// style.auth.content_width,
+// &style.auth.cta_button,
+// cx,
+// |_, _, cx| cx.remove_window(),
+// ))
+// .align_children_center()
+// .into_any()
+// }
+
+// fn render_unauthorized_modal(
+// style: &theme::Copilot,
+// cx: &mut ViewContext<Self>,
+// ) -> AnyElement<Self> {
+// let unauthorized_style = &style.auth.not_authorized;
+
+// Flex::column()
+// .with_child(
+// Flex::column()
+// .with_children([
+// Label::new(
+// "Enable Copilot by connecting",
+// unauthorized_style.subheading.text.clone(),
+// )
+// .aligned(),
+// Label::new(
+// "your existing license.",
+// unauthorized_style.subheading.text.clone(),
+// )
+// .aligned(),
+// ])
+// .align_children_center()
+// .contained()
+// .with_style(unauthorized_style.subheading.container),
+// )
+// .with_child(
+// Flex::column()
+// .with_children([
+// Label::new(
+// "You must have an active copilot",
+// unauthorized_style.warning.text.clone(),
+// )
+// .aligned(),
+// Label::new(
+// "license to use it in Zed.",
+// unauthorized_style.warning.text.clone(),
+// )
+// .aligned(),
+// ])
+// .align_children_center()
+// .contained()
+// .with_style(unauthorized_style.warning.container),
+// )
+// .with_child(theme::ui::cta_button::<Self, _, _, _>(
+// "Subscribe on GitHub",
+// style.auth.content_width,
+// &style.auth.cta_button,
+// cx,
+// |_, _, cx| {
+// cx.remove_window();
+// cx.platform().open_url(COPILOT_SIGN_UP_URL)
+// },
+// ))
+// .align_children_center()
+// .into_any()
+// }
+// }
+
+// impl Entity for CopilotCodeVerification {
+// type Event = ();
+// }
+
+// impl View for CopilotCodeVerification {
+// fn ui_name() -> &'static str {
+// "CopilotCodeVerification"
+// }
+
+// fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
+// cx.notify()
+// }
+
+// fn focus_out(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
+// cx.notify()
+// }
+
+// fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
+// enum ConnectModal {}
+
+// let style = theme::current(cx).clone();
+
+// modal::<ConnectModal, _, _, _, _>(
+// "Connect Copilot to Zed",
+// &style.copilot.modal,
+// cx,
+// |cx| {
+// Flex::column()
+// .with_children([
+// theme::ui::icon(&style.copilot.auth.header).into_any(),
+// match &self.status {
+// Status::SigningIn {
+// prompt: Some(prompt),
+// } => Self::render_prompting_modal(
+// self.connect_clicked,
+// &prompt,
+// &style.copilot,
+// cx,
+// ),
+// Status::Unauthorized => {
+// self.connect_clicked = false;
+// Self::render_unauthorized_modal(&style.copilot, cx)
+// }
+// Status::Authorized => {
+// self.connect_clicked = false;
+// Self::render_enabled_modal(&style.copilot, cx)
+// }
+// _ => Empty::new().into_any(),
+// },
+// ])
+// .align_children_center()
+// },
+// )
+// .into_any()
+// }
+// }