From a72434f67b7f11e9892908ebfbad2b6b9dac17c2 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 23 Oct 2023 12:16:33 +0200 Subject: [PATCH] WIP --- Cargo.lock | 28 + Cargo.toml | 1 + crates/copilot2/Cargo.toml | 49 + crates/copilot2/src/copilot2.rs | 1217 +++++++++++++++++++++++++ crates/copilot2/src/request.rs | 225 +++++ crates/copilot2/src/sign_in.rs | 376 ++++++++ crates/gpui2/src/app/model_context.rs | 2 +- crates/project2/src/project2.rs | 10 +- crates/project2/src/terminals.rs | 2 +- crates/zed2/Cargo.toml | 4 +- crates/zed2/src/main.rs | 16 +- 11 files changed, 1914 insertions(+), 16 deletions(-) create mode 100644 crates/copilot2/Cargo.toml create mode 100644 crates/copilot2/src/copilot2.rs create mode 100644 crates/copilot2/src/request.rs create mode 100644 crates/copilot2/src/sign_in.rs diff --git a/Cargo.lock b/Cargo.lock index c392f067c2..9ced096a9b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1779,6 +1779,32 @@ dependencies = [ "util", ] +[[package]] +name = "copilot2" +version = "0.1.0" +dependencies = [ + "anyhow", + "async-compression", + "async-tar", + "clock", + "collections", + "context_menu", + "fs", + "futures 0.3.28", + "gpui2", + "language2", + "log", + "lsp2", + "node_runtime", + "rpc", + "serde", + "serde_derive", + "settings2", + "smol", + "theme", + "util", +] + [[package]] name = "copilot_button" version = "0.1.0" @@ -10606,6 +10632,7 @@ dependencies = [ "cli", "client2", "collections", + "copilot2", "ctor", "db2", "env_logger 0.9.3", @@ -10620,6 +10647,7 @@ dependencies = [ "indexmap 1.9.3", "install_cli", "isahc", + "language2", "language_tools", "lazy_static", "libc", diff --git a/Cargo.toml b/Cargo.toml index 3b301c8833..b62ceb32cb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,6 +19,7 @@ members = [ "crates/component_test", "crates/context_menu", "crates/copilot", + "crates/copilot2", "crates/copilot_button", "crates/db", "crates/db2", diff --git a/crates/copilot2/Cargo.toml b/crates/copilot2/Cargo.toml new file mode 100644 index 0000000000..161a9f3bd8 --- /dev/null +++ b/crates/copilot2/Cargo.toml @@ -0,0 +1,49 @@ +[package] +name = "copilot2" +version = "0.1.0" +edition = "2021" +publish = false + +[lib] +path = "src/copilot2.rs" +doctest = false + +[features] +test-support = [ + "collections/test-support", + "gpui2/test-support", + "language2/test-support", + "lsp2/test-support", + "settings2/test-support", + "util/test-support", +] + +[dependencies] +collections = { path = "../collections" } +context_menu = { path = "../context_menu" } +gpui2 = { path = "../gpui2" } +language2 = { path = "../language2" } +settings2 = { path = "../settings2" } +theme = { path = "../theme" } +lsp2 = { path = "../lsp2" } +node_runtime = { path = "../node_runtime"} +util = { path = "../util" } +async-compression = { version = "0.3", features = ["gzip", "futures-bufread"] } +async-tar = "0.4.2" +anyhow.workspace = true +log.workspace = true +serde.workspace = true +serde_derive.workspace = true +smol.workspace = true +futures.workspace = true + +[dev-dependencies] +clock = { path = "../clock" } +collections = { path = "../collections", features = ["test-support"] } +fs = { path = "../fs", features = ["test-support"] } +gpui2 = { path = "../gpui2", features = ["test-support"] } +language2 = { path = "../language2", features = ["test-support"] } +lsp2 = { path = "../lsp2", features = ["test-support"] } +rpc = { path = "../rpc", features = ["test-support"] } +settings2 = { path = "../settings2", features = ["test-support"] } +util = { path = "../util", features = ["test-support"] } diff --git a/crates/copilot2/src/copilot2.rs b/crates/copilot2/src/copilot2.rs new file mode 100644 index 0000000000..dfe861f1c3 --- /dev/null +++ b/crates/copilot2/src/copilot2.rs @@ -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, + node_runtime: Arc, + 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::(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> }, + Error(Arc), + 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, + sign_in_status: SignInStatus, + registered_buffers: HashMap, +} + +#[derive(Clone, Debug)] +enum SignInStatus { + Authorized, + Unauthorized, + SigningIn { + prompt: Option, + task: Shared>>>, + }, + SignedOut, +} + +#[derive(Debug, Clone)] +pub enum Status { + Starting { + task: Shared>, + }, + Error(Arc), + Disabled, + SignedOut, + SigningIn { + prompt: Option, + }, + 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>, +} + +impl RegisteredBuffer { + fn report_changes( + &mut self, + buffer: &Handle, + cx: &mut ModelContext, + ) -> 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::>() + } + }) + .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::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, + pub text: String, +} + +pub struct Copilot { + http: Arc, + node_runtime: Arc, + server: CopilotServer, + buffers: HashSet>, + 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> { + if cx.has_global::>() { + Some(cx.global::>().clone()) + } else { + None + } + } + + fn start( + new_server_id: LanguageServerId, + http: Arc, + node_runtime: Arc, + cx: &mut ModelContext, + ) -> 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::(move |this, cx| this.enable_or_disable_copilot(cx)) + .detach(); + this + } + + fn shutdown_language_server( + &mut self, + cx: &mut ModelContext, + ) -> impl Future { + 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) { + 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, 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, + node_runtime: Arc, + this: Handle, + mut cx: AsyncAppContext, + ) -> impl Future { + 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 = 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::( + |_, _| { /* Silence the notification */ }, + ) + .detach(); + + let server = server.initialize(Default::default()).await?; + + let status = server + .request::(request::CheckStatusParams { + local_checks_only: false, + }) + .await?; + + server + .request::(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) -> Task> { + 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::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::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) -> Task> { + 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::SignOutParams {}) + .await?; + anyhow::Ok(()) + }) + } else { + Task::ready(Err(anyhow!("copilot hasn't started yet"))) + } + } + + pub fn reinstall(&mut self, cx: &mut ModelContext) -> 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)> { + if let CopilotServer::Running(server) = &self.server { + Some((&server.name, &server.lsp)) + } else { + None + } + } + + pub fn register_buffer(&mut self, buffer: &Handle, cx: &mut ModelContext) { + 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::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, + event: &language2::Event, + cx: &mut ModelContext, + ) -> 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::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::DidCloseTextDocumentParams { + text_document: lsp2::TextDocumentIdentifier::new(old_uri), + }, + )?; + server + .lsp + .notify::( + 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) { + if let Ok(server) = self.server.as_running() { + if let Some(buffer) = server.registered_buffers.remove(&buffer.id()) { + server + .lsp + .notify::( + lsp2::DidCloseTextDocumentParams { + text_document: lsp2::TextDocumentIdentifier::new(buffer.uri), + }, + ) + .log_err(); + } + } + } + + pub fn completions( + &mut self, + buffer: &Handle, + position: T, + cx: &mut ModelContext, + ) -> Task>> + where + T: ToPointUtf16, + { + self.request_completions::(buffer, position, cx) + } + + pub fn completions_cycling( + &mut self, + buffer: &Handle, + position: T, + cx: &mut ModelContext, + ) -> Task>> + where + T: ToPointUtf16, + { + self.request_completions::(buffer, position, cx) + } + + pub fn accept_completion( + &mut self, + completion: &Completion, + cx: &mut ModelContext, + ) -> Task> { + let server = match self.server.as_authenticated() { + Ok(server) => server, + Err(error) => return Task::ready(Err(error)), + }; + let request = + server + .lsp + .request::(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, + ) -> Task> { + let server = match self.server.as_authenticated() { + Ok(server) => server, + Err(error) => return Task::ready(Err(error)), + }; + let request = + server + .lsp + .request::(request::NotifyRejectedParams { + uuids: completions + .iter() + .map(|completion| completion.uuid.clone()) + .collect(), + }); + cx.background().spawn(async move { + request.await?; + Ok(()) + }) + } + + fn request_completions( + &mut self, + buffer: &Handle, + position: T, + cx: &mut ModelContext, + ) -> Task>> + 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::(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.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::>() { + 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::>() { + self.unregister_buffer(&buffer); + } + } + request::SignInStatus::NotSignedIn => { + server.sign_in_status = SignInStatus::SignedOut; + for buffer in self.buffers.iter().copied().collect::>() { + self.unregister_buffer(&buffer); + } + } + } + + cx.notify(); + } + } +} + +fn id_for_language(language: Option<&Arc>) -> 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, 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) -> anyhow::Result { + 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) -> anyhow::Result { + 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, 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::() +// .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::() +// .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::() +// .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::() +// .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::() +// .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::(|_, _| async { +// Ok(request::SignOutResult {}) +// }); +// copilot +// .update(cx, |copilot, cx| copilot.sign_out(cx)) +// .await +// .unwrap(); +// assert_eq!( +// lsp.receive_notification::() +// .await, +// lsp::DidCloseTextDocumentParams { +// text_document: lsp::TextDocumentIdentifier::new(buffer_2_uri.clone()), +// } +// ); +// assert_eq!( +// lsp.receive_notification::() +// .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::(|_, _| 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::() +// .await, +// lsp::DidOpenTextDocumentParams { +// text_document: lsp::TextDocumentItem::new( +// buffer_2_uri.clone(), +// "plaintext".into(), +// 0, +// "Goodbye".into() +// ), +// } +// ); +// assert_eq!( +// lsp.receive_notification::() +// .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::() +// .await, +// lsp::DidCloseTextDocumentParams { +// text_document: lsp::TextDocumentIdentifier::new(buffer_2_uri), +// } +// ); +// } + +// struct File { +// abs_path: PathBuf, +// path: Arc, +// } + +// 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 { +// &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> { +// unimplemented!() +// } + +// fn buffer_reloaded( +// &self, +// _: u64, +// _: &clock::Global, +// _: language::RopeFingerprint, +// _: language::LineEnding, +// _: std::time::SystemTime, +// _: &mut AppContext, +// ) { +// unimplemented!() +// } +// } +// } diff --git a/crates/copilot2/src/request.rs b/crates/copilot2/src/request.rs new file mode 100644 index 0000000000..fee92051dc --- /dev/null +++ b/crates/copilot2/src/request.rs @@ -0,0 +1,225 @@ +use serde::{Deserialize, Serialize}; + +pub enum CheckStatus {} + +#[derive(Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CheckStatusParams { + pub local_checks_only: bool, +} + +impl lsp2::request::Request for CheckStatus { + type Params = CheckStatusParams; + type Result = SignInStatus; + const METHOD: &'static str = "checkStatus"; +} + +pub enum SignInInitiate {} + +#[derive(Debug, Serialize, Deserialize)] +pub struct SignInInitiateParams {} + +#[derive(Debug, Serialize, Deserialize)] +#[serde(tag = "status")] +pub enum SignInInitiateResult { + AlreadySignedIn { user: String }, + PromptUserDeviceFlow(PromptUserDeviceFlow), +} + +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct PromptUserDeviceFlow { + pub user_code: String, + pub verification_uri: String, +} + +impl lsp2::request::Request for SignInInitiate { + type Params = SignInInitiateParams; + type Result = SignInInitiateResult; + const METHOD: &'static str = "signInInitiate"; +} + +pub enum SignInConfirm {} + +#[derive(Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SignInConfirmParams { + pub user_code: String, +} + +#[derive(Debug, Serialize, Deserialize)] +#[serde(tag = "status")] +pub enum SignInStatus { + #[serde(rename = "OK")] + Ok { + user: String, + }, + MaybeOk { + user: String, + }, + AlreadySignedIn { + user: String, + }, + NotAuthorized { + user: String, + }, + NotSignedIn, +} + +impl lsp2::request::Request for SignInConfirm { + type Params = SignInConfirmParams; + type Result = SignInStatus; + const METHOD: &'static str = "signInConfirm"; +} + +pub enum SignOut {} + +#[derive(Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SignOutParams {} + +#[derive(Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SignOutResult {} + +impl lsp2::request::Request for SignOut { + type Params = SignOutParams; + type Result = SignOutResult; + const METHOD: &'static str = "signOut"; +} + +pub enum GetCompletions {} + +#[derive(Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct GetCompletionsParams { + pub doc: GetCompletionsDocument, +} + +#[derive(Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct GetCompletionsDocument { + pub tab_size: u32, + pub indent_size: u32, + pub insert_spaces: bool, + pub uri: lsp2::Url, + pub relative_path: String, + pub position: lsp2::Position, + pub version: usize, +} + +#[derive(Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct GetCompletionsResult { + pub completions: Vec, +} + +#[derive(Clone, Debug, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Completion { + pub text: String, + pub position: lsp2::Position, + pub uuid: String, + pub range: lsp2::Range, + pub display_text: String, +} + +impl lsp2::request::Request for GetCompletions { + type Params = GetCompletionsParams; + type Result = GetCompletionsResult; + const METHOD: &'static str = "getCompletions"; +} + +pub enum GetCompletionsCycling {} + +impl lsp2::request::Request for GetCompletionsCycling { + type Params = GetCompletionsParams; + type Result = GetCompletionsResult; + const METHOD: &'static str = "getCompletionsCycling"; +} + +pub enum LogMessage {} + +#[derive(Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct LogMessageParams { + pub level: u8, + pub message: String, + pub metadata_str: String, + pub extra: Vec, +} + +impl lsp2::notification::Notification for LogMessage { + type Params = LogMessageParams; + const METHOD: &'static str = "LogMessage"; +} + +pub enum StatusNotification {} + +#[derive(Debug, Serialize, Deserialize)] +pub struct StatusNotificationParams { + pub message: String, + pub status: String, // One of Normal/InProgress +} + +impl lsp2::notification::Notification for StatusNotification { + type Params = StatusNotificationParams; + const METHOD: &'static str = "statusNotification"; +} + +pub enum SetEditorInfo {} + +#[derive(Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SetEditorInfoParams { + pub editor_info: EditorInfo, + pub editor_plugin_info: EditorPluginInfo, +} + +impl lsp2::request::Request for SetEditorInfo { + type Params = SetEditorInfoParams; + type Result = String; + const METHOD: &'static str = "setEditorInfo"; +} + +#[derive(Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct EditorInfo { + pub name: String, + pub version: String, +} + +#[derive(Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct EditorPluginInfo { + pub name: String, + pub version: String, +} + +pub enum NotifyAccepted {} + +#[derive(Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct NotifyAcceptedParams { + pub uuid: String, +} + +impl lsp2::request::Request for NotifyAccepted { + type Params = NotifyAcceptedParams; + type Result = String; + const METHOD: &'static str = "notifyAccepted"; +} + +pub enum NotifyRejected {} + +#[derive(Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct NotifyRejectedParams { + pub uuids: Vec, +} + +impl lsp2::request::Request for NotifyRejected { + type Params = NotifyRejectedParams; + type Result = String; + const METHOD: &'static str = "notifyRejected"; +} diff --git a/crates/copilot2/src/sign_in.rs b/crates/copilot2/src/sign_in.rs new file mode 100644 index 0000000000..ab326a8819 --- /dev/null +++ b/crates/copilot2/src/sign_in.rs @@ -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> = 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 { +// 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.status = status; +// cx.notify(); +// } + +// fn render_device_code( +// data: &PromptUserDeviceFlow, +// style: &theme::Copilot, +// cx: &mut ViewContext, +// ) -> impl Element { +// 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::(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, +// ) -> AnyElement { +// 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::( +// 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, +// ) -> AnyElement { +// 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::( +// "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, +// ) -> AnyElement { +// 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::( +// "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) { +// cx.notify() +// } + +// fn focus_out(&mut self, _: AnyViewHandle, cx: &mut ViewContext) { +// cx.notify() +// } + +// fn render(&mut self, cx: &mut ViewContext) -> AnyElement { +// enum ConnectModal {} + +// let style = theme::current(cx).clone(); + +// modal::( +// "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() +// } +// } diff --git a/crates/gpui2/src/app/model_context.rs b/crates/gpui2/src/app/model_context.rs index 176f5e4237..39d459acef 100644 --- a/crates/gpui2/src/app/model_context.rs +++ b/crates/gpui2/src/app/model_context.rs @@ -87,7 +87,7 @@ impl<'a, T: Send + Sync + 'static> ModelContext<'a, T> { pub fn on_app_quit( &mut self, - on_quit: impl Fn(&mut T, &mut AppContext) -> Fut + Send + Sync + 'static, + on_quit: impl Fn(&mut T, &mut ModelContext) -> Fut + Send + Sync + 'static, ) -> Subscription where Fut: 'static + Future + Send, diff --git a/crates/project2/src/project2.rs b/crates/project2/src/project2.rs index 7e6c7efa35..ce691ca0cd 100644 --- a/crates/project2/src/project2.rs +++ b/crates/project2/src/project2.rs @@ -26,8 +26,7 @@ use futures::{ }; use globset::{Glob, GlobSet, GlobSetBuilder}; use gpui2::{ - AnyHandle, AppContext, AsyncAppContext, EventEmitter, Executor, Handle, ModelContext, Task, - WeakHandle, + AnyHandle, AppContext, AsyncAppContext, EventEmitter, Handle, ModelContext, Task, WeakHandle, }; use itertools::Itertools; use language2::{ @@ -821,7 +820,10 @@ impl Project { } } - fn shutdown_language_servers(&mut self) -> impl Future { + fn shutdown_language_servers( + &mut self, + cx: &mut ModelContext, + ) -> impl Future { let shutdown_futures = self .language_servers .drain() @@ -5683,7 +5685,7 @@ impl Project { async fn background_search( unnamed_buffers: Vec>, opened_buffers: HashMap, (Handle, BufferSnapshot)>, - executor: Executor, + executor: Arc, fs: Arc, workers: usize, query: SearchQuery, diff --git a/crates/project2/src/terminals.rs b/crates/project2/src/terminals.rs index e7b18aaef5..b357868cb6 100644 --- a/crates/project2/src/terminals.rs +++ b/crates/project2/src/terminals.rs @@ -116,7 +116,7 @@ impl Project { } } - pub fn local_terminal_handles(&self) -> &Vec> { + pub fn local_terminal_handles(&self) -> &Vec> { &self.terminals.local_handles } } diff --git a/crates/zed2/Cargo.toml b/crates/zed2/Cargo.toml index 171676e5c6..19da608b78 100644 --- a/crates/zed2/Cargo.toml +++ b/crates/zed2/Cargo.toml @@ -29,7 +29,7 @@ collections = { path = "../collections" } # context_menu = { path = "../context_menu" } client2 = { path = "../client2" } # clock = { path = "../clock" } -# copilot = { path = "../copilot" } +copilot2 = { path = "../copilot2" } # copilot_button = { path = "../copilot_button" } # diagnostics = { path = "../diagnostics" } db2 = { path = "../db2" } @@ -44,7 +44,7 @@ fuzzy = { path = "../fuzzy" } gpui2 = { path = "../gpui2" } install_cli = { path = "../install_cli" } # journal = { path = "../journal" } -# language = { path = "../language" } +language2 = { path = "../language2" } # language_selector = { path = "../language_selector" } lsp = { path = "../lsp" } language_tools = { path = "../language_tools" } diff --git a/crates/zed2/src/main.rs b/crates/zed2/src/main.rs index 23551e7811..ceaa176f3e 100644 --- a/crates/zed2/src/main.rs +++ b/crates/zed2/src/main.rs @@ -109,8 +109,8 @@ fn main() { // handle_keymap_file_changes(user_keymap_file_rx, cx); // let client = client2::Client::new(http.clone(), cx); - // let mut languages = LanguageRegistry::new(login_shell_env_loaded); - // let copilot_language_server_id = languages.next_language_server_id(); + let mut languages = LanguageRegistry::new(login_shell_env_loaded); + let copilot_language_server_id = languages.next_language_server_id(); // languages.set_executor(cx.background().clone()); // languages.set_language_server_download_dir(paths::LANGUAGES_DIR.clone()); // let languages = Arc::new(languages); @@ -140,12 +140,12 @@ fn main() { // semantic_index::init(fs.clone(), http.clone(), languages.clone(), cx); // vim::init(cx); // terminal_view::init(cx); - // copilot::init( - // copilot_language_server_id, - // http.clone(), - // node_runtime.clone(), - // cx, - // ); + copilot2::init( + copilot_language_server_id, + http.clone(), + node_runtime.clone(), + cx, + ); // assistant::init(cx); // component_test::init(cx);