From 455cdc8b37016563cb359ba10286b5e7a94b5264 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Wed, 22 Mar 2023 19:22:08 -0700 Subject: [PATCH] Add copilot crate Refactor HTTP and github release downloading into util Lazily download / upgrade the copilot LSP from Zed Co-authored-by: Max Co-Authored-By: Antonio --- Cargo.lock | 22 +++- Cargo.toml | 1 + crates/auto_update/src/auto_update.rs | 4 +- crates/client/Cargo.toml | 1 - crates/client/src/client.rs | 8 +- crates/client/src/http.rs | 57 ---------- crates/client/src/telemetry.rs | 18 ++- crates/client/src/test.rs | 53 +-------- crates/client/src/user.rs | 3 +- crates/collab/src/tests.rs | 4 +- crates/copilot/Cargo.toml | 21 ++++ crates/copilot/readme.md | 21 ++++ crates/copilot/src/copilot.rs | 97 ++++++++++++++++ crates/language/src/language.rs | 2 +- crates/project/src/project.rs | 2 +- crates/project/src/worktree.rs | 3 +- crates/util/Cargo.toml | 6 +- crates/util/src/fs.rs | 28 +++++ crates/util/src/github.rs | 40 +++++++ crates/util/src/http.rs | 117 ++++++++++++++++++++ crates/util/src/paths.rs | 1 + crates/util/src/util.rs | 3 + crates/workspace/src/workspace.rs | 2 +- crates/zed/Cargo.toml | 1 + crates/zed/src/languages.rs | 2 +- crates/zed/src/languages/c.rs | 18 +-- crates/zed/src/languages/elixir.rs | 24 +--- crates/zed/src/languages/github.rs | 2 +- crates/zed/src/languages/go.rs | 24 ++-- crates/zed/src/languages/html.rs | 23 +--- crates/zed/src/languages/json.rs | 13 +-- crates/zed/src/languages/language_plugin.rs | 2 +- crates/zed/src/languages/lua.rs | 8 +- crates/zed/src/languages/node_runtime.rs | 2 +- crates/zed/src/languages/python.rs | 14 +-- crates/zed/src/languages/ruby.rs | 2 +- crates/zed/src/languages/rust.rs | 14 +-- crates/zed/src/languages/typescript.rs | 14 +-- crates/zed/src/languages/yaml.rs | 13 +-- crates/zed/src/main.rs | 8 +- crates/zed/src/zed.rs | 2 +- 41 files changed, 435 insertions(+), 265 deletions(-) delete mode 100644 crates/client/src/http.rs create mode 100644 crates/copilot/Cargo.toml create mode 100644 crates/copilot/readme.md create mode 100644 crates/copilot/src/copilot.rs create mode 100644 crates/util/src/fs.rs create mode 100644 crates/util/src/github.rs create mode 100644 crates/util/src/http.rs diff --git a/Cargo.lock b/Cargo.lock index 02b27566e42f29eccd5b4a0ca145567e885cf36b..0e1f5a807c913fdd3290f727d9a157abd0c4e8e5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1113,7 +1113,6 @@ dependencies = [ "futures 0.3.25", "gpui", "image", - "isahc", "lazy_static", "log", "parking_lot 0.11.2", @@ -1332,6 +1331,22 @@ dependencies = [ "theme", ] +[[package]] +name = "copilot" +version = "0.1.0" +dependencies = [ + "anyhow", + "async-compression", + "client", + "futures 0.3.25", + "gpui", + "lsp", + "settings", + "smol", + "util", + "workspace", +] + [[package]] name = "core-foundation" version = "0.9.3" @@ -7500,11 +7515,15 @@ dependencies = [ "dirs 3.0.2", "futures 0.3.25", "git2", + "isahc", "lazy_static", "log", "rand 0.8.5", + "serde", "serde_json", + "smol", "tempdir", + "url", ] [[package]] @@ -8460,6 +8479,7 @@ dependencies = [ "collections", "command_palette", "context_menu", + "copilot", "ctor", "db", "diagnostics", diff --git a/Cargo.toml b/Cargo.toml index 9f795992d5888d24ef083abe231ed1a4edd1a950..bf9214f49ee28867d65f5ed5a0298205ce253564 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,7 @@ members = [ "crates/collections", "crates/command_palette", "crates/context_menu", + "crates/copilot", "crates/db", "crates/diagnostics", "crates/drag_and_drop", diff --git a/crates/auto_update/src/auto_update.rs b/crates/auto_update/src/auto_update.rs index 4272d7b1afa82ea1449fbd7f55ed79f2e3585a26..3ad3380d2619f1b562af9b4594919f6ce6ec20c6 100644 --- a/crates/auto_update/src/auto_update.rs +++ b/crates/auto_update/src/auto_update.rs @@ -1,8 +1,7 @@ mod update_notification; use anyhow::{anyhow, Context, Result}; -use client::{http::HttpClient, ZED_SECRET_CLIENT_TOKEN}; -use client::{ZED_APP_PATH, ZED_APP_VERSION}; +use client::{ZED_APP_PATH, ZED_APP_VERSION, ZED_SECRET_CLIENT_TOKEN}; use db::kvp::KEY_VALUE_STORE; use gpui::{ actions, platform::AppVersion, AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle, @@ -14,6 +13,7 @@ use smol::{fs::File, io::AsyncReadExt, process::Command}; use std::{ffi::OsString, sync::Arc, time::Duration}; use update_notification::UpdateNotification; use util::channel::ReleaseChannel; +use util::http::HttpClient; use workspace::Workspace; const SHOULD_SHOW_UPDATE_NOTIFICATION_KEY: &str = "auto-updater-should-show-updated-notification"; diff --git a/crates/client/Cargo.toml b/crates/client/Cargo.toml index cb6f29a42e5855d8d142c90fcadf5e5d5e5fe1c7..9c772f519b3258ebe325b273503dfd12b82a5dd9 100644 --- a/crates/client/Cargo.toml +++ b/crates/client/Cargo.toml @@ -23,7 +23,6 @@ async-recursion = "0.3" async-tungstenite = { version = "0.16", features = ["async-tls"] } futures = "0.3" image = "0.23" -isahc = "1.7" lazy_static = "1.4.0" log = { version = "0.4.16", features = ["kv_unstable_serde"] } parking_lot = "0.11.1" diff --git a/crates/client/src/client.rs b/crates/client/src/client.rs index 86d6bc9912c74e1e8f2efcaec0fc0b5532bcdaa9..bb39b0669989d55b121bc455d891f72a20dbd480 100644 --- a/crates/client/src/client.rs +++ b/crates/client/src/client.rs @@ -1,7 +1,6 @@ #[cfg(any(test, feature = "test-support"))] pub mod test; -pub mod http; pub mod telemetry; pub mod user; @@ -18,7 +17,6 @@ use gpui::{ AnyModelHandle, AnyViewHandle, AnyWeakModelHandle, AnyWeakViewHandle, AppContext, AppVersion, AsyncAppContext, Entity, ModelHandle, MutableAppContext, Task, View, ViewContext, ViewHandle, }; -use http::HttpClient; use lazy_static::lazy_static; use parking_lot::RwLock; use postage::watch; @@ -41,6 +39,7 @@ use telemetry::Telemetry; use thiserror::Error; use url::Url; use util::channel::ReleaseChannel; +use util::http::HttpClient; use util::{ResultExt, TryFutureExt}; pub use rpc::*; @@ -130,7 +129,7 @@ pub enum EstablishConnectionError { #[error("{0}")] Other(#[from] anyhow::Error), #[error("{0}")] - Http(#[from] http::Error), + Http(#[from] util::http::Error), #[error("{0}")] Io(#[from] std::io::Error), #[error("{0}")] @@ -1396,10 +1395,11 @@ pub fn decode_worktree_url(url: &str) -> Option<(u64, String)> { #[cfg(test)] mod tests { use super::*; - use crate::test::{FakeHttpClient, FakeServer}; + use crate::test::FakeServer; use gpui::{executor::Deterministic, TestAppContext}; use parking_lot::Mutex; use std::future; + use util::http::FakeHttpClient; #[gpui::test(iterations = 10)] async fn test_reconnection(cx: &mut TestAppContext) { diff --git a/crates/client/src/http.rs b/crates/client/src/http.rs deleted file mode 100644 index 0757cebf3ad5d836edbd59f47a03cbab02c0c211..0000000000000000000000000000000000000000 --- a/crates/client/src/http.rs +++ /dev/null @@ -1,57 +0,0 @@ -pub use anyhow::{anyhow, Result}; -use futures::future::BoxFuture; -use isahc::{ - config::{Configurable, RedirectPolicy}, - AsyncBody, -}; -pub use isahc::{ - http::{Method, Uri}, - Error, -}; -use smol::future::FutureExt; -use std::{sync::Arc, time::Duration}; -pub use url::Url; - -pub type Request = isahc::Request; -pub type Response = isahc::Response; - -pub trait HttpClient: Send + Sync { - fn send(&self, req: Request) -> BoxFuture>; - - fn get<'a>( - &'a self, - uri: &str, - body: AsyncBody, - follow_redirects: bool, - ) -> BoxFuture<'a, Result> { - let request = isahc::Request::builder() - .redirect_policy(if follow_redirects { - RedirectPolicy::Follow - } else { - RedirectPolicy::None - }) - .method(Method::GET) - .uri(uri) - .body(body); - match request { - Ok(request) => self.send(request), - Err(error) => async move { Err(error.into()) }.boxed(), - } - } -} - -pub fn client() -> Arc { - Arc::new( - isahc::HttpClient::builder() - .connect_timeout(Duration::from_secs(5)) - .low_speed_timeout(100, Duration::from_secs(5)) - .build() - .unwrap(), - ) -} - -impl HttpClient for isahc::HttpClient { - fn send(&self, req: Request) -> BoxFuture> { - Box::pin(async move { self.send_async(req).await }) - } -} diff --git a/crates/client/src/telemetry.rs b/crates/client/src/telemetry.rs index 9d486619d255dd7ab214a145f7f31162dd4a9c36..7ee099dfabf8f67256e7cd945ff7981c4cd5d3e7 100644 --- a/crates/client/src/telemetry.rs +++ b/crates/client/src/telemetry.rs @@ -1,11 +1,9 @@ -use crate::http::HttpClient; use db::kvp::KEY_VALUE_STORE; use gpui::{ executor::Background, serde_json::{self, value::Map, Value}, AppContext, Task, }; -use isahc::Request; use lazy_static::lazy_static; use parking_lot::Mutex; use serde::Serialize; @@ -19,6 +17,7 @@ use std::{ time::{Duration, SystemTime, UNIX_EPOCH}, }; use tempfile::NamedTempFile; +use util::http::HttpClient; use util::{channel::ReleaseChannel, post_inc, ResultExt, TryFutureExt}; use uuid::Uuid; @@ -220,10 +219,10 @@ impl Telemetry { "App": true }), }])?; - let request = Request::post(MIXPANEL_ENGAGE_URL) - .header("Content-Type", "application/json") - .body(json_bytes.into())?; - this.http_client.send(request).await?; + + this.http_client + .post_json(MIXPANEL_ENGAGE_URL, json_bytes.into()) + .await?; anyhow::Ok(()) } .log_err(), @@ -316,10 +315,9 @@ impl Telemetry { json_bytes.clear(); serde_json::to_writer(&mut json_bytes, &events)?; - let request = Request::post(MIXPANEL_EVENTS_URL) - .header("Content-Type", "application/json") - .body(json_bytes.into())?; - this.http_client.send(request).await?; + this.http_client + .post_json(MIXPANEL_EVENTS_URL, json_bytes.into()) + .await?; anyhow::Ok(()) } .log_err(), diff --git a/crates/client/src/test.rs b/crates/client/src/test.rs index db9e0d8c487b27a7474373af9d2c25a29e04b9d7..4c12a205660f7932a6a7b412c6ee686a6199372c 100644 --- a/crates/client/src/test.rs +++ b/crates/client/src/test.rs @@ -1,16 +1,14 @@ -use crate::{ - http::{self, HttpClient, Request, Response}, - Client, Connection, Credentials, EstablishConnectionError, UserStore, -}; +use crate::{Client, Connection, Credentials, EstablishConnectionError, UserStore}; use anyhow::{anyhow, Result}; -use futures::{future::BoxFuture, stream::BoxStream, Future, StreamExt}; +use futures::{stream::BoxStream, StreamExt}; use gpui::{executor, ModelHandle, TestAppContext}; use parking_lot::Mutex; use rpc::{ proto::{self, GetPrivateUserInfo, GetPrivateUserInfoResponse}, ConnectionId, Peer, Receipt, TypedEnvelope, }; -use std::{fmt, rc::Rc, sync::Arc}; +use std::{rc::Rc, sync::Arc}; +use util::http::FakeHttpClient; pub struct FakeServer { peer: Arc, @@ -219,46 +217,3 @@ impl Drop for FakeServer { self.disconnect(); } } - -pub struct FakeHttpClient { - handler: Box< - dyn 'static - + Send - + Sync - + Fn(Request) -> BoxFuture<'static, Result>, - >, -} - -impl FakeHttpClient { - pub fn create(handler: F) -> Arc - where - Fut: 'static + Send + Future>, - F: 'static + Send + Sync + Fn(Request) -> Fut, - { - Arc::new(Self { - handler: Box::new(move |req| Box::pin(handler(req))), - }) - } - - pub fn with_404_response() -> Arc { - Self::create(|_| async move { - Ok(isahc::Response::builder() - .status(404) - .body(Default::default()) - .unwrap()) - }) - } -} - -impl fmt::Debug for FakeHttpClient { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("FakeHttpClient").finish() - } -} - -impl HttpClient for FakeHttpClient { - fn send(&self, req: Request) -> BoxFuture> { - let future = (self.handler)(req); - Box::pin(async move { future.await.map(Into::into) }) - } -} diff --git a/crates/client/src/user.rs b/crates/client/src/user.rs index a0a730871d01e571ab1ce67e5966cefa03c763d5..8c6b1410017f311a73981ba8368a13a8edc865b7 100644 --- a/crates/client/src/user.rs +++ b/crates/client/src/user.rs @@ -1,4 +1,4 @@ -use super::{http::HttpClient, proto, Client, Status, TypedEnvelope}; +use super::{proto, Client, Status, TypedEnvelope}; use anyhow::{anyhow, Context, Result}; use collections::{hash_map::Entry, HashMap, HashSet}; use futures::{channel::mpsc, future, AsyncReadExt, Future, StreamExt}; @@ -7,6 +7,7 @@ use postage::{sink::Sink, watch}; use rpc::proto::{RequestMessage, UsersResponse}; use settings::Settings; use std::sync::{Arc, Weak}; +use util::http::HttpClient; use util::{StaffMode, TryFutureExt as _}; #[derive(Default, Debug)] diff --git a/crates/collab/src/tests.rs b/crates/collab/src/tests.rs index 91af40dc5a688077422d4e892aad59b5ad237c10..9c0f9f3bd896e599a1f7d459a5fd049f09e60977 100644 --- a/crates/collab/src/tests.rs +++ b/crates/collab/src/tests.rs @@ -7,8 +7,7 @@ use crate::{ use anyhow::anyhow; use call::ActiveCall; use client::{ - self, proto::PeerId, test::FakeHttpClient, Client, Connection, Credentials, - EstablishConnectionError, UserStore, + self, proto::PeerId, Client, Connection, Credentials, EstablishConnectionError, UserStore, }; use collections::{HashMap, HashSet}; use fs::FakeFs; @@ -28,6 +27,7 @@ use std::{ }, }; use theme::ThemeRegistry; +use util::http::FakeHttpClient; use workspace::Workspace; mod integration_tests; diff --git a/crates/copilot/Cargo.toml b/crates/copilot/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..bd79d053d1bdd9753594f2a7745298526e25ff58 --- /dev/null +++ b/crates/copilot/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "copilot" +version = "0.1.0" +edition = "2021" +publish = false + +[lib] +path = "src/copilot.rs" +doctest = false + +[dependencies] +gpui = { path = "../gpui" } +settings = { path = "../settings" } +lsp = { path = "../lsp" } +util = { path = "../util" } +client = { path = "../client" } +workspace = { path = "../workspace" } +async-compression = { version = "0.3", features = ["gzip", "futures-bufread"] } +anyhow = "1.0" +smol = "1.2.5" +futures = "0.3" diff --git a/crates/copilot/readme.md b/crates/copilot/readme.md new file mode 100644 index 0000000000000000000000000000000000000000..a91608197006fa2bd0b2ac2b90c5d36548060063 --- /dev/null +++ b/crates/copilot/readme.md @@ -0,0 +1,21 @@ +Basic idea: + +Run the `copilot-node-server` as an LSP +Reuse our LSP code to use it + +Issues: +- Re-use our github authentication for copilot - ?? +- Integrate Copilot suggestions with `SuggestionMap` + + + +THE PLAN: +- Copilot crate. +- Instantiated with a project / listens to them +- Listens to events from the project about adding worktrees +- Manages the copilot language servers per worktree +- Editor <-?-> Copilot + + +From anotonio in Slack: +- soooo regarding copilot i was thinking… if it doesn’t really behave like a language server (but they implemented like that because of the protocol, etc.), it might be nice to just have a singleton that is not even set when we’re signed out. when we sign in, we set the global. then, the editor can access the global (e.g. cx.global::>) after typing some character (and with some debouncing mechanism). the Copilot struct could hold a lsp::LanguageServer and then our job is to write an adapter that can then be used to start the language server, but it’s kinda orthogonal to the language servers we store in the project. what do you think? diff --git a/crates/copilot/src/copilot.rs b/crates/copilot/src/copilot.rs new file mode 100644 index 0000000000000000000000000000000000000000..097f3187b83dbc1e5e46c71f48ebc254e5a194b4 --- /dev/null +++ b/crates/copilot/src/copilot.rs @@ -0,0 +1,97 @@ +use anyhow::{anyhow, Ok}; +use async_compression::futures::bufread::GzipDecoder; +use client::Client; +use gpui::{actions, MutableAppContext}; +use smol::{fs, io::BufReader, stream::StreamExt}; +use std::{env::consts, path::PathBuf, sync::Arc}; +use util::{ + fs::remove_matching, github::latest_github_release, http::HttpClient, paths, ResultExt, +}; + +actions!(copilot, [SignIn]); + +pub fn init(client: Arc, cx: &mut MutableAppContext) { + cx.add_global_action(move |_: &SignIn, cx: &mut MutableAppContext| { + Copilot::sign_in(client.http_client(), cx) + }); +} + +struct Copilot { + copilot_server: PathBuf, +} + +impl Copilot { + fn sign_in(http: Arc, cx: &mut MutableAppContext) { + let copilot = cx.global::>>().clone(); + + cx.spawn(|mut cx| async move { + // Lazily download / initialize copilot LSP + let copilot = if let Some(copilot) = copilot { + copilot + } else { + let copilot_server = get_lsp_binary(http).await?; // TODO: Make this error user visible + let new_copilot = Arc::new(Copilot { copilot_server }); + cx.update({ + let new_copilot = new_copilot.clone(); + move |cx| cx.set_global(Some(new_copilot.clone())) + }); + new_copilot + }; + + Ok(()) + }) + .detach(); + } +} + +async fn get_lsp_binary(http: Arc) -> anyhow::Result { + ///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/copilotserver", http.clone()).await?; + let asset_name = format!("copilot-darwin-{}.gz", consts::ARCH); + let asset = release + .assets + .iter() + .find(|asset| asset.name == asset_name) + .ok_or_else(|| anyhow!("no asset found matching {:?}", asset_name))?; + + let destination_path = + paths::COPILOT_DIR.join(format!("copilot-{}-{}", release.name, consts::ARCH)); + + if fs::metadata(&destination_path).await.is_err() { + let mut response = http + .get(&asset.browser_download_url, Default::default(), true) + .await + .map_err(|err| anyhow!("error downloading release: {}", err))?; + let decompressed_bytes = GzipDecoder::new(BufReader::new(response.body_mut())); + let mut file = fs::File::create(&destination_path).await?; + futures::io::copy(decompressed_bytes, &mut file).await?; + fs::set_permissions( + &destination_path, + ::from_mode(0o755), + ) + .await?; + + remove_matching(&paths::COPILOT_DIR, |entry| entry != destination_path).await; + } + + Ok(destination_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 = None; + let mut entries = fs::read_dir(paths::COPILOT_DIR.as_path()).await?; + while let Some(entry) = entries.next().await { + last = Some(entry?.path()); + } + last.ok_or_else(|| anyhow!("no cached binary")) + })() + .await + } + } +} diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index 69053e9fc4c58e0711efa9e1ecb3f9c9833d242a..427f5e62f6c82ade99c2d494d8e20236236d9ed7 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -10,7 +10,6 @@ mod buffer_tests; use anyhow::{anyhow, Context, Result}; use async_trait::async_trait; -use client::http::HttpClient; use collections::HashMap; use futures::{ channel::oneshot, @@ -45,6 +44,7 @@ use syntax_map::SyntaxSnapshot; use theme::{SyntaxTheme, Theme}; use tree_sitter::{self, Query}; use unicase::UniCase; +use util::http::HttpClient; use util::{merge_json_value_into, post_inc, ResultExt, TryFutureExt as _, UnwrapFuture}; #[cfg(any(test, feature = "test-support"))] diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index fedfa0c863f299ccc00be3a55ca664c7f8342877..8e3fc77aa81dbacd9ffe9c58156f46a1a8868113 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -568,7 +568,7 @@ impl Project { let mut languages = LanguageRegistry::test(); languages.set_executor(cx.background()); - let http_client = client::test::FakeHttpClient::with_404_response(); + let http_client = util::http::FakeHttpClient::with_404_response(); let client = cx.update(|cx| client::Client::new(http_client.clone(), cx)); let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http_client, cx)); let project = diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index 1d40dad86480a8180c85fac262f9050c04e07c91..2357052d2cb13ad819caa5eecce4f2f210675ea3 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -3114,13 +3114,14 @@ impl<'a> TryFrom<(&'a CharBag, proto::Entry)> for Entry { #[cfg(test)] mod tests { use super::*; - use client::test::FakeHttpClient; use fs::repository::FakeGitRepository; use fs::{FakeFs, RealFs}; use gpui::{executor::Deterministic, TestAppContext}; use rand::prelude::*; use serde_json::json; use std::{env, fmt::Write}; + use util::http::FakeHttpClient; + use util::test::temp_tree; #[gpui::test] diff --git a/crates/util/Cargo.toml b/crates/util/Cargo.toml index b13b8af956c0f174c1897a920d47555bde119543..558ca588b49215deebffd588b1603d2ec84b61ff 100644 --- a/crates/util/Cargo.toml +++ b/crates/util/Cargo.toml @@ -14,11 +14,15 @@ test-support = ["tempdir", "git2"] [dependencies] anyhow = "1.0.38" backtrace = "0.3" -futures = "0.3" log = { version = "0.4.16", features = ["kv_unstable_serde"] } lazy_static = "1.4.0" +futures = "0.3" +isahc = "1.7" +smol = "1.2.5" +url = "2.2" rand = { workspace = true } tempdir = { version = "0.3.7", optional = true } +serde = { version = "1.0", features = ["derive", "rc"] } serde_json = { version = "1.0", features = ["preserve_order"] } git2 = { version = "0.15", default-features = false, optional = true } dirs = "3.0" diff --git a/crates/util/src/fs.rs b/crates/util/src/fs.rs new file mode 100644 index 0000000000000000000000000000000000000000..c6d562d15cc38531b7453d278dc8a349c9124035 --- /dev/null +++ b/crates/util/src/fs.rs @@ -0,0 +1,28 @@ +use std::path::Path; + +use smol::{fs, stream::StreamExt}; + +use crate::ResultExt; + +// Removes all files and directories matching the given predicate +pub async fn remove_matching(dir: &Path, predicate: F) +where + F: Fn(&Path) -> bool, +{ + if let Some(mut entries) = fs::read_dir(dir).await.log_err() { + while let Some(entry) = entries.next().await { + if let Some(entry) = entry.log_err() { + let entry_path = entry.path(); + if predicate(entry_path.as_path()) { + if let Ok(metadata) = fs::metadata(&entry_path).await { + if metadata.is_file() { + fs::remove_file(&entry_path).await.log_err(); + } else { + fs::remove_dir_all(&entry_path).await.log_err(); + } + } + } + } + } + } +} diff --git a/crates/util/src/github.rs b/crates/util/src/github.rs new file mode 100644 index 0000000000000000000000000000000000000000..33c0ea6a1ae24d9085f14973bb015a32546fa113 --- /dev/null +++ b/crates/util/src/github.rs @@ -0,0 +1,40 @@ +use crate::http::HttpClient; +use anyhow::{Context, Result}; +use futures::AsyncReadExt; +use serde::Deserialize; +use std::sync::Arc; + +#[derive(Deserialize)] +pub struct GithubRelease { + pub name: String, + pub assets: Vec, +} + +#[derive(Deserialize)] +pub struct GithubReleaseAsset { + pub name: String, + pub browser_download_url: String, +} + +pub async fn latest_github_release( + repo_name_with_owner: &str, + http: Arc, +) -> Result { + let mut response = http + .get( + &format!("https://api.github.com/repos/{repo_name_with_owner}/releases/latest"), + Default::default(), + true, + ) + .await + .context("error fetching latest release")?; + let mut body = Vec::new(); + response + .body_mut() + .read_to_end(&mut body) + .await + .context("error reading latest release")?; + let release: GithubRelease = + serde_json::from_slice(body.as_slice()).context("error deserializing latest release")?; + Ok(release) +} diff --git a/crates/util/src/http.rs b/crates/util/src/http.rs new file mode 100644 index 0000000000000000000000000000000000000000..e29768a53e891e165dd859fd27be5325f35cdfa5 --- /dev/null +++ b/crates/util/src/http.rs @@ -0,0 +1,117 @@ +pub use anyhow::{anyhow, Result}; +use futures::future::BoxFuture; +use isahc::config::{Configurable, RedirectPolicy}; +pub use isahc::{ + http::{Method, Uri}, + Error, +}; +pub use isahc::{AsyncBody, Request, Response}; +use smol::future::FutureExt; +#[cfg(feature = "test-support")] +use std::fmt; +use std::{sync::Arc, time::Duration}; +pub use url::Url; + +pub trait HttpClient: Send + Sync { + fn send(&self, req: Request) -> BoxFuture, Error>>; + + fn get<'a>( + &'a self, + uri: &str, + body: AsyncBody, + follow_redirects: bool, + ) -> BoxFuture<'a, Result, Error>> { + let request = isahc::Request::builder() + .redirect_policy(if follow_redirects { + RedirectPolicy::Follow + } else { + RedirectPolicy::None + }) + .method(Method::GET) + .uri(uri) + .body(body); + match request { + Ok(request) => self.send(request), + Err(error) => async move { Err(error.into()) }.boxed(), + } + } + + fn post_json<'a>( + &'a self, + uri: &str, + body: AsyncBody, + ) -> BoxFuture<'a, Result, Error>> { + let request = isahc::Request::builder() + .method(Method::POST) + .uri(uri) + .header("Content-Type", "application/json") + .body(body); + match request { + Ok(request) => self.send(request), + Err(error) => async move { Err(error.into()) }.boxed(), + } + } +} + +pub fn client() -> Arc { + Arc::new( + isahc::HttpClient::builder() + .connect_timeout(Duration::from_secs(5)) + .low_speed_timeout(100, Duration::from_secs(5)) + .build() + .unwrap(), + ) +} + +impl HttpClient for isahc::HttpClient { + fn send(&self, req: Request) -> BoxFuture, Error>> { + Box::pin(async move { self.send_async(req).await }) + } +} + +#[cfg(feature = "test-support")] +pub struct FakeHttpClient { + handler: Box< + dyn 'static + + Send + + Sync + + Fn(Request) -> BoxFuture<'static, Result, Error>>, + >, +} + +#[cfg(feature = "test-support")] +impl FakeHttpClient { + pub fn create(handler: F) -> Arc + where + Fut: 'static + Send + futures::Future, Error>>, + F: 'static + Send + Sync + Fn(Request) -> Fut, + { + Arc::new(Self { + handler: Box::new(move |req| Box::pin(handler(req))), + }) + } + + pub fn with_404_response() -> Arc { + Self::create(|_| async move { + Ok(Response::builder() + .status(404) + .body(Default::default()) + .unwrap()) + }) + } +} + +#[cfg(feature = "test-support")] +impl fmt::Debug for FakeHttpClient { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("FakeHttpClient").finish() + } +} + +#[cfg(feature = "test-support")] +impl HttpClient for FakeHttpClient { + fn send(&self, req: Request) -> BoxFuture, Error>> { + let future = (self.handler)(req); + Box::pin(async move { future.await.map(Into::into) }) + } +} diff --git a/crates/util/src/paths.rs b/crates/util/src/paths.rs index 63c3c6d884a4e91fdeaff3766adc97a99072ec24..e38f76d8a6e294cb52555cf28d9f164a5264c7e3 100644 --- a/crates/util/src/paths.rs +++ b/crates/util/src/paths.rs @@ -6,6 +6,7 @@ lazy_static::lazy_static! { pub static ref LOGS_DIR: PathBuf = HOME.join("Library/Logs/Zed"); pub static ref SUPPORT_DIR: PathBuf = HOME.join("Library/Application Support/Zed"); pub static ref LANGUAGES_DIR: PathBuf = HOME.join("Library/Application Support/Zed/languages"); + pub static ref COPILOT_DIR: PathBuf = HOME.join("Library/Application Support/Zed/copilot"); pub static ref DB_DIR: PathBuf = HOME.join("Library/Application Support/Zed/db"); pub static ref SETTINGS: PathBuf = CONFIG_DIR.join("settings.json"); pub static ref KEYMAP: PathBuf = CONFIG_DIR.join("keymap.json"); diff --git a/crates/util/src/util.rs b/crates/util/src/util.rs index 6a5ccb8bd52e95b4aa54aa88c5b07d0f71b1906f..07b2ffd0da69512bf0c211300bc4f6168c1dad92 100644 --- a/crates/util/src/util.rs +++ b/crates/util/src/util.rs @@ -1,4 +1,7 @@ pub mod channel; +pub mod fs; +pub mod github; +pub mod http; pub mod paths; #[cfg(any(test, feature = "test-support"))] pub mod test; diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index fe60065486bff9c71b00ccf477df09b1c0d55208..eb04e052860545d2d1a5c4e38d31cf907e104079 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -449,7 +449,7 @@ impl AppState { let fs = fs::FakeFs::new(cx.background().clone()); let languages = Arc::new(LanguageRegistry::test()); - let http_client = client::test::FakeHttpClient::with_404_response(); + let http_client = util::http::FakeHttpClient::with_404_response(); let client = Client::new(http_client.clone(), cx); let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http_client, cx)); let themes = ThemeRegistry::new((), cx.font_cache().clone()); diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index 4d7ce828d69635a4e8f43fafb7b05e0089c0aa5e..812fae9e0a84c0a7bb80a2d2bda84bb6c2f86643 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -28,6 +28,7 @@ command_palette = { path = "../command_palette" } context_menu = { path = "../context_menu" } client = { path = "../client" } clock = { path = "../clock" } +copilot = { path = "../copilot" } diagnostics = { path = "../diagnostics" } db = { path = "../db" } editor = { path = "../editor" } diff --git a/crates/zed/src/languages.rs b/crates/zed/src/languages.rs index c49c77f076d73d344345926fb3129fe74f6cf8e7..3a23afb9700c3fdfcc02232d581854edf92bfd78 100644 --- a/crates/zed/src/languages.rs +++ b/crates/zed/src/languages.rs @@ -1,11 +1,11 @@ use anyhow::Context; -use client::http::HttpClient; use gpui::executor::Background; pub use language::*; use node_runtime::NodeRuntime; use rust_embed::RustEmbed; use std::{borrow::Cow, str, sync::Arc}; use theme::ThemeRegistry; +use util::http::HttpClient; mod c; mod elixir; diff --git a/crates/zed/src/languages/c.rs b/crates/zed/src/languages/c.rs index 906592fc2d4b92f947b3328caa5182e32614fb1e..88f5c4553b84db4d610c4acf78f3a4dd8eda2990 100644 --- a/crates/zed/src/languages/c.rs +++ b/crates/zed/src/languages/c.rs @@ -1,13 +1,16 @@ -use super::github::{latest_github_release, GitHubLspBinaryVersion}; use anyhow::{anyhow, Context, Result}; use async_trait::async_trait; -use client::http::HttpClient; use futures::StreamExt; pub use language::*; use smol::fs::{self, File}; use std::{any::Any, path::PathBuf, sync::Arc}; +use util::fs::remove_matching; +use util::github::latest_github_release; +use util::http::HttpClient; use util::ResultExt; +use super::github::GitHubLspBinaryVersion; + pub struct CLspAdapter; #[async_trait] @@ -69,16 +72,7 @@ impl super::LspAdapter for CLspAdapter { Err(anyhow!("failed to unzip clangd archive"))?; } - if let Some(mut entries) = fs::read_dir(&container_dir).await.log_err() { - while let Some(entry) = entries.next().await { - if let Some(entry) = entry.log_err() { - let entry_path = entry.path(); - if entry_path.as_path() != version_dir { - fs::remove_dir_all(&entry_path).await.log_err(); - } - } - } - } + remove_matching(&container_dir, |entry| entry != version_dir).await; } Ok(LanguageServerBinary { diff --git a/crates/zed/src/languages/elixir.rs b/crates/zed/src/languages/elixir.rs index 9f921a0c402c1d87f21e96aa3ecded5de55b1899..ecd4028fe0bec13b782783c32ce927d82c52e963 100644 --- a/crates/zed/src/languages/elixir.rs +++ b/crates/zed/src/languages/elixir.rs @@ -1,14 +1,17 @@ -use super::github::{latest_github_release, GitHubLspBinaryVersion}; use anyhow::{anyhow, Context, Result}; use async_trait::async_trait; -use client::http::HttpClient; use futures::StreamExt; pub use language::*; use lsp::{CompletionItemKind, SymbolKind}; use smol::fs::{self, File}; use std::{any::Any, path::PathBuf, sync::Arc}; +use util::fs::remove_matching; +use util::github::latest_github_release; +use util::http::HttpClient; use util::ResultExt; +use super::github::GitHubLspBinaryVersion; + pub struct ElixirLspAdapter; #[async_trait] @@ -76,22 +79,7 @@ impl LspAdapter for ElixirLspAdapter { Err(anyhow!("failed to unzip clangd archive"))?; } - if let Some(mut entries) = fs::read_dir(&container_dir).await.log_err() { - while let Some(entry) = entries.next().await { - if let Some(entry) = entry.log_err() { - let entry_path = entry.path(); - if entry_path.as_path() != version_dir { - if let Ok(metadata) = fs::metadata(&entry_path).await { - if metadata.is_file() { - fs::remove_file(&entry_path).await.log_err(); - } else { - fs::remove_dir_all(&entry_path).await.log_err(); - } - } - } - } - } - } + remove_matching(&container_dir, |entry| entry != version_dir).await; } Ok(LanguageServerBinary { diff --git a/crates/zed/src/languages/github.rs b/crates/zed/src/languages/github.rs index 8fdef507908bbd1690a2f0e7b1ce735827e2a04b..9e0dd9b582a56b672acb499d0faa498b8496d275 100644 --- a/crates/zed/src/languages/github.rs +++ b/crates/zed/src/languages/github.rs @@ -1,8 +1,8 @@ use anyhow::{Context, Result}; -use client::http::HttpClient; use serde::Deserialize; use smol::io::AsyncReadExt; use std::sync::Arc; +use util::http::HttpClient; pub struct GitHubLspBinaryVersion { pub name: String, diff --git a/crates/zed/src/languages/go.rs b/crates/zed/src/languages/go.rs index 9af309839fedb17a47b98b2d45ffe7fa79b0d940..760c5f353d0d98c6f58d85d5472a8c519a2c37da 100644 --- a/crates/zed/src/languages/go.rs +++ b/crates/zed/src/languages/go.rs @@ -1,13 +1,15 @@ -use super::github::latest_github_release; use anyhow::{anyhow, Result}; use async_trait::async_trait; -use client::http::HttpClient; use futures::StreamExt; pub use language::*; use lazy_static::lazy_static; use regex::Regex; use smol::{fs, process}; -use std::{any::Any, ffi::OsString, ops::Range, path::PathBuf, str, sync::Arc}; +use std::ffi::{OsStr, OsString}; +use std::{any::Any, ops::Range, path::PathBuf, str, sync::Arc}; +use util::fs::remove_matching; +use util::github::latest_github_release; +use util::http::HttpClient; use util::ResultExt; fn server_binary_arguments() -> Vec { @@ -55,18 +57,10 @@ impl super::LspAdapter for GoLspAdapter { let binary_path = container_dir.join(&format!("gopls_{version}")); if let Ok(metadata) = fs::metadata(&binary_path).await { if metadata.is_file() { - if let Some(mut entries) = fs::read_dir(&container_dir).await.log_err() { - while let Some(entry) = entries.next().await { - if let Some(entry) = entry.log_err() { - let entry_path = entry.path(); - if entry_path.as_path() != binary_path - && entry.file_name() != "gobin" - { - fs::remove_file(&entry_path).await.log_err(); - } - } - } - } + remove_matching(&container_dir, |entry| { + entry != binary_path && entry.file_name() != Some(OsStr::new("gobin")) + }) + .await; return Ok(LanguageServerBinary { path: binary_path.to_path_buf(), diff --git a/crates/zed/src/languages/html.rs b/crates/zed/src/languages/html.rs index a2cfbac96b4ff47b7cf06e49551185f1fa1892cd..f77b264fbfacc0e1a5981ad2352699c5030fa8ee 100644 --- a/crates/zed/src/languages/html.rs +++ b/crates/zed/src/languages/html.rs @@ -1,17 +1,15 @@ use super::node_runtime::NodeRuntime; use anyhow::{anyhow, Context, Result}; use async_trait::async_trait; -use client::http::HttpClient; use futures::StreamExt; use language::{LanguageServerBinary, LanguageServerName, LspAdapter}; use serde_json::json; use smol::fs; -use std::{ - any::Any, - ffi::OsString, - path::{Path, PathBuf}, - sync::Arc, -}; +use std::ffi::OsString; +use std::path::Path; +use std::{any::Any, path::PathBuf, sync::Arc}; +use util::fs::remove_matching; +use util::http::HttpClient; use util::ResultExt; fn server_binary_arguments(server_path: &Path) -> Vec { @@ -69,16 +67,7 @@ impl LspAdapter for HtmlLspAdapter { ) .await?; - if let Some(mut entries) = fs::read_dir(&container_dir).await.log_err() { - while let Some(entry) = entries.next().await { - if let Some(entry) = entry.log_err() { - let entry_path = entry.path(); - if entry_path.as_path() != version_dir { - fs::remove_dir_all(&entry_path).await.log_err(); - } - } - } - } + remove_matching(container_dir.as_path(), |entry| entry != version_dir).await; } Ok(LanguageServerBinary { diff --git a/crates/zed/src/languages/json.rs b/crates/zed/src/languages/json.rs index 479308f370e6268d25d2b7bfae04ed383432e4d2..97c158fd1f8720a6236fcfc67bd65ac3df93ded2 100644 --- a/crates/zed/src/languages/json.rs +++ b/crates/zed/src/languages/json.rs @@ -1,7 +1,6 @@ use super::node_runtime::NodeRuntime; use anyhow::{anyhow, Context, Result}; use async_trait::async_trait; -use client::http::HttpClient; use collections::HashMap; use futures::{future::BoxFuture, FutureExt, StreamExt}; use gpui::MutableAppContext; @@ -17,6 +16,7 @@ use std::{ sync::Arc, }; use theme::ThemeRegistry; +use util::{fs::remove_matching, http::HttpClient}; use util::{paths, ResultExt, StaffMode}; const SERVER_PATH: &'static str = @@ -84,16 +84,7 @@ impl LspAdapter for JsonLspAdapter { ) .await?; - if let Some(mut entries) = fs::read_dir(&container_dir).await.log_err() { - while let Some(entry) = entries.next().await { - if let Some(entry) = entry.log_err() { - let entry_path = entry.path(); - if entry_path.as_path() != version_dir { - fs::remove_dir_all(&entry_path).await.log_err(); - } - } - } - } + remove_matching(&container_dir, |entry| entry != server_path).await; } Ok(LanguageServerBinary { diff --git a/crates/zed/src/languages/language_plugin.rs b/crates/zed/src/languages/language_plugin.rs index 38f50d2d88710d66d43cdb7cafe932f92c1be57d..9b82713d082d5d12a0243ebaf674b9a16550c3d4 100644 --- a/crates/zed/src/languages/language_plugin.rs +++ b/crates/zed/src/languages/language_plugin.rs @@ -1,12 +1,12 @@ use anyhow::{anyhow, Result}; use async_trait::async_trait; -use client::http::HttpClient; use collections::HashMap; use futures::lock::Mutex; use gpui::executor::Background; use language::{LanguageServerBinary, LanguageServerName, LspAdapter}; use plugin_runtime::{Plugin, PluginBinary, PluginBuilder, WasiFn}; use std::{any::Any, path::PathBuf, sync::Arc}; +use util::http::HttpClient; use util::ResultExt; #[allow(dead_code)] diff --git a/crates/zed/src/languages/lua.rs b/crates/zed/src/languages/lua.rs index 7ffdac5218cb92a6a50622ae91e30eb0d8a07ead..f16761d87047e75ade4acb66353341d12961c9c6 100644 --- a/crates/zed/src/languages/lua.rs +++ b/crates/zed/src/languages/lua.rs @@ -1,16 +1,14 @@ -use std::{any::Any, env::consts, ffi::OsString, path::PathBuf, sync::Arc}; - use anyhow::{anyhow, bail, Result}; use async_compression::futures::bufread::GzipDecoder; use async_tar::Archive; use async_trait::async_trait; -use client::http::HttpClient; use futures::{io::BufReader, StreamExt}; use language::{LanguageServerBinary, LanguageServerName}; use smol::fs; -use util::{async_iife, ResultExt}; +use std::{any::Any, env::consts, ffi::OsString, path::PathBuf, sync::Arc}; +use util::{async_iife, github::latest_github_release, http::HttpClient, ResultExt}; -use super::github::{latest_github_release, GitHubLspBinaryVersion}; +use super::github::GitHubLspBinaryVersion; #[derive(Copy, Clone)] pub struct LuaLspAdapter; diff --git a/crates/zed/src/languages/node_runtime.rs b/crates/zed/src/languages/node_runtime.rs index 41cbefbb732fc14fd70397213dde450582209716..079b6a5e45a97b920925777b5d6be96d1bca49e0 100644 --- a/crates/zed/src/languages/node_runtime.rs +++ b/crates/zed/src/languages/node_runtime.rs @@ -1,7 +1,6 @@ use anyhow::{anyhow, bail, Context, Result}; use async_compression::futures::bufread::GzipDecoder; use async_tar::Archive; -use client::http::HttpClient; use futures::{future::Shared, FutureExt}; use gpui::{executor::Background, Task}; use parking_lot::Mutex; @@ -12,6 +11,7 @@ use std::{ path::{Path, PathBuf}, sync::Arc, }; +use util::http::HttpClient; const VERSION: &str = "v18.15.0"; diff --git a/crates/zed/src/languages/python.rs b/crates/zed/src/languages/python.rs index 9a09c63bb6ca2447667ba41e484a0fb990412522..3a671c60f6eacab5233ea2ce9b234438965b194f 100644 --- a/crates/zed/src/languages/python.rs +++ b/crates/zed/src/languages/python.rs @@ -1,7 +1,6 @@ use super::node_runtime::NodeRuntime; use anyhow::{anyhow, Context, Result}; use async_trait::async_trait; -use client::http::HttpClient; use futures::StreamExt; use language::{LanguageServerBinary, LanguageServerName, LspAdapter}; use smol::fs; @@ -11,6 +10,8 @@ use std::{ path::{Path, PathBuf}, sync::Arc, }; +use util::fs::remove_matching; +use util::http::HttpClient; use util::ResultExt; fn server_binary_arguments(server_path: &Path) -> Vec { @@ -60,16 +61,7 @@ impl LspAdapter for PythonLspAdapter { .npm_install_packages([("pyright", version.as_str())], &version_dir) .await?; - if let Some(mut entries) = fs::read_dir(&container_dir).await.log_err() { - while let Some(entry) = entries.next().await { - if let Some(entry) = entry.log_err() { - let entry_path = entry.path(); - if entry_path.as_path() != version_dir { - fs::remove_dir_all(&entry_path).await.log_err(); - } - } - } - } + remove_matching(&container_dir, |entry| entry != version_dir).await; } Ok(LanguageServerBinary { diff --git a/crates/zed/src/languages/ruby.rs b/crates/zed/src/languages/ruby.rs index 662c1f464d1fd1f216b0af37925950658e083ba4..d387f815f0cd345c4173f522370ab17495989592 100644 --- a/crates/zed/src/languages/ruby.rs +++ b/crates/zed/src/languages/ruby.rs @@ -1,8 +1,8 @@ use anyhow::{anyhow, Result}; use async_trait::async_trait; -use client::http::HttpClient; use language::{LanguageServerBinary, LanguageServerName, LspAdapter}; use std::{any::Any, path::PathBuf, sync::Arc}; +use util::http::HttpClient; pub struct RubyLanguageServer; diff --git a/crates/zed/src/languages/rust.rs b/crates/zed/src/languages/rust.rs index 0f8e90d7b268a71712375f0fda0406f0745110d6..b95a64fa1eb688341b8489b15d54c1a05460fe38 100644 --- a/crates/zed/src/languages/rust.rs +++ b/crates/zed/src/languages/rust.rs @@ -2,13 +2,14 @@ use super::github::{latest_github_release, GitHubLspBinaryVersion}; use anyhow::{anyhow, Result}; use async_compression::futures::bufread::GzipDecoder; use async_trait::async_trait; -use client::http::HttpClient; use futures::{io::BufReader, StreamExt}; pub use language::*; use lazy_static::lazy_static; use regex::Regex; use smol::fs::{self, File}; use std::{any::Any, borrow::Cow, env::consts, path::PathBuf, str, sync::Arc}; +use util::fs::remove_matching; +use util::http::HttpClient; use util::ResultExt; pub struct RustLspAdapter; @@ -60,16 +61,7 @@ impl LspAdapter for RustLspAdapter { ) .await?; - if let Some(mut entries) = fs::read_dir(&container_dir).await.log_err() { - while let Some(entry) = entries.next().await { - if let Some(entry) = entry.log_err() { - let entry_path = entry.path(); - if entry_path.as_path() != destination_path { - fs::remove_file(&entry_path).await.log_err(); - } - } - } - } + remove_matching(&container_dir, |entry| entry != destination_path).await; } Ok(LanguageServerBinary { diff --git a/crates/zed/src/languages/typescript.rs b/crates/zed/src/languages/typescript.rs index f9baf4f8f78f14c9ab6a7d7616b3affd3b992c37..d3704c84c876948b1c88fd73ec014bdeccc0568d 100644 --- a/crates/zed/src/languages/typescript.rs +++ b/crates/zed/src/languages/typescript.rs @@ -1,7 +1,6 @@ use super::node_runtime::NodeRuntime; use anyhow::{anyhow, Context, Result}; use async_trait::async_trait; -use client::http::HttpClient; use futures::StreamExt; use language::{LanguageServerBinary, LanguageServerName, LspAdapter}; use serde_json::json; @@ -12,6 +11,8 @@ use std::{ path::{Path, PathBuf}, sync::Arc, }; +use util::fs::remove_matching; +use util::http::HttpClient; use util::ResultExt; fn server_binary_arguments(server_path: &Path) -> Vec { @@ -90,16 +91,7 @@ impl LspAdapter for TypeScriptLspAdapter { ) .await?; - if let Some(mut entries) = fs::read_dir(&container_dir).await.log_err() { - while let Some(entry) = entries.next().await { - if let Some(entry) = entry.log_err() { - let entry_path = entry.path(); - if entry_path.as_path() != version_dir { - fs::remove_dir_all(&entry_path).await.log_err(); - } - } - } - } + remove_matching(&container_dir, |entry| entry != version_dir).await; } Ok(LanguageServerBinary { diff --git a/crates/zed/src/languages/yaml.rs b/crates/zed/src/languages/yaml.rs index b6e82842dea80c777c1885e33fedfaf8fda439d5..6028ecd13433fa3ea109c2bed41f29759aad9bf1 100644 --- a/crates/zed/src/languages/yaml.rs +++ b/crates/zed/src/languages/yaml.rs @@ -1,7 +1,6 @@ use super::node_runtime::NodeRuntime; use anyhow::{anyhow, Context, Result}; use async_trait::async_trait; -use client::http::HttpClient; use futures::{future::BoxFuture, FutureExt, StreamExt}; use gpui::MutableAppContext; use language::{LanguageServerBinary, LanguageServerName, LspAdapter}; @@ -16,6 +15,7 @@ use std::{ sync::Arc, }; use util::ResultExt; +use util::{fs::remove_matching, http::HttpClient}; fn server_binary_arguments(server_path: &Path) -> Vec { vec![server_path.into(), "--stdio".into()] @@ -68,16 +68,7 @@ impl LspAdapter for YamlLspAdapter { .npm_install_packages([("yaml-language-server", version.as_str())], &version_dir) .await?; - if let Some(mut entries) = fs::read_dir(&container_dir).await.log_err() { - while let Some(entry) = entries.next().await { - if let Some(entry) = entry.log_err() { - let entry_path = entry.path(); - if entry_path.as_path() != version_dir { - fs::remove_dir_all(&entry_path).await.log_err(); - } - } - } - } + remove_matching(&container_dir, |entry| entry != version_dir).await; } Ok(LanguageServerBinary { diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index fb6c6227c35af0bf350580e873f591629532c781..c88f2e94f9a58bfc5abe30d6ea5fa9b6bbb77edd 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -8,11 +8,7 @@ use cli::{ ipc::{self, IpcSender}, CliRequest, CliResponse, IpcHandshake, }; -use client::{ - self, - http::{self, HttpClient}, - UserStore, ZED_APP_VERSION, ZED_SECRET_CLIENT_TOKEN, -}; +use client::{self, UserStore, ZED_APP_VERSION, ZED_SECRET_CLIENT_TOKEN}; use db::kvp::KEY_VALUE_STORE; use futures::{ channel::{mpsc, oneshot}, @@ -36,6 +32,7 @@ use std::{ path::PathBuf, sync::Arc, thread, time::Duration, }; use terminal_view::{get_working_directory, TerminalView}; +use util::http::{self, HttpClient}; use welcome::{show_welcome_experience, FIRST_OPEN}; use fs::RealFs; @@ -165,6 +162,7 @@ fn main() { terminal_view::init(cx); theme_testbench::init(cx); recent_projects::init(cx); + copilot::init(client.clone(), cx); cx.spawn(|cx| watch_themes(fs.clone(), themes.clone(), cx)) .detach(); diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 788be77e7587b46f191d344867a96ca9af7539f8..32706cb47f61b966637aeeaf6fd811177789f263 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -652,7 +652,6 @@ fn open_bundled_file( mod tests { use super::*; use assets::Assets; - use client::test::FakeHttpClient; use editor::{scroll::autoscroll::Autoscroll, DisplayPoint, Editor}; use gpui::{ executor::Deterministic, AssetSource, MutableAppContext, TestAppContext, ViewHandle, @@ -665,6 +664,7 @@ mod tests { path::{Path, PathBuf}, }; use theme::ThemeRegistry; + use util::http::FakeHttpClient; use workspace::{ item::{Item, ItemHandle}, open_new, open_paths, pane, NewFile, Pane, SplitDirection, WorkspaceHandle,