Add copilot crate

Refactor HTTP and github release downloading into util
Lazily download / upgrade the copilot LSP from Zed

Co-authored-by: Max <max@zed.dev>
Co-Authored-By: Antonio <antonio@zed.dev>
This commit is contained in:
Mikayla Maki 2023-03-22 19:22:08 -07:00
parent 35b2aceffb
commit 455cdc8b37
41 changed files with 435 additions and 265 deletions

22
Cargo.lock generated
View File

@ -1113,7 +1113,6 @@ dependencies = [
"futures 0.3.25", "futures 0.3.25",
"gpui", "gpui",
"image", "image",
"isahc",
"lazy_static", "lazy_static",
"log", "log",
"parking_lot 0.11.2", "parking_lot 0.11.2",
@ -1332,6 +1331,22 @@ dependencies = [
"theme", "theme",
] ]
[[package]]
name = "copilot"
version = "0.1.0"
dependencies = [
"anyhow",
"async-compression",
"client",
"futures 0.3.25",
"gpui",
"lsp",
"settings",
"smol",
"util",
"workspace",
]
[[package]] [[package]]
name = "core-foundation" name = "core-foundation"
version = "0.9.3" version = "0.9.3"
@ -7500,11 +7515,15 @@ dependencies = [
"dirs 3.0.2", "dirs 3.0.2",
"futures 0.3.25", "futures 0.3.25",
"git2", "git2",
"isahc",
"lazy_static", "lazy_static",
"log", "log",
"rand 0.8.5", "rand 0.8.5",
"serde",
"serde_json", "serde_json",
"smol",
"tempdir", "tempdir",
"url",
] ]
[[package]] [[package]]
@ -8460,6 +8479,7 @@ dependencies = [
"collections", "collections",
"command_palette", "command_palette",
"context_menu", "context_menu",
"copilot",
"ctor", "ctor",
"db", "db",
"diagnostics", "diagnostics",

View File

@ -13,6 +13,7 @@ members = [
"crates/collections", "crates/collections",
"crates/command_palette", "crates/command_palette",
"crates/context_menu", "crates/context_menu",
"crates/copilot",
"crates/db", "crates/db",
"crates/diagnostics", "crates/diagnostics",
"crates/drag_and_drop", "crates/drag_and_drop",

View File

@ -1,8 +1,7 @@
mod update_notification; mod update_notification;
use anyhow::{anyhow, Context, Result}; use anyhow::{anyhow, Context, Result};
use client::{http::HttpClient, ZED_SECRET_CLIENT_TOKEN}; use client::{ZED_APP_PATH, ZED_APP_VERSION, ZED_SECRET_CLIENT_TOKEN};
use client::{ZED_APP_PATH, ZED_APP_VERSION};
use db::kvp::KEY_VALUE_STORE; use db::kvp::KEY_VALUE_STORE;
use gpui::{ use gpui::{
actions, platform::AppVersion, AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle, 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 std::{ffi::OsString, sync::Arc, time::Duration};
use update_notification::UpdateNotification; use update_notification::UpdateNotification;
use util::channel::ReleaseChannel; use util::channel::ReleaseChannel;
use util::http::HttpClient;
use workspace::Workspace; use workspace::Workspace;
const SHOULD_SHOW_UPDATE_NOTIFICATION_KEY: &str = "auto-updater-should-show-updated-notification"; const SHOULD_SHOW_UPDATE_NOTIFICATION_KEY: &str = "auto-updater-should-show-updated-notification";

View File

@ -23,7 +23,6 @@ async-recursion = "0.3"
async-tungstenite = { version = "0.16", features = ["async-tls"] } async-tungstenite = { version = "0.16", features = ["async-tls"] }
futures = "0.3" futures = "0.3"
image = "0.23" image = "0.23"
isahc = "1.7"
lazy_static = "1.4.0" lazy_static = "1.4.0"
log = { version = "0.4.16", features = ["kv_unstable_serde"] } log = { version = "0.4.16", features = ["kv_unstable_serde"] }
parking_lot = "0.11.1" parking_lot = "0.11.1"

View File

@ -1,7 +1,6 @@
#[cfg(any(test, feature = "test-support"))] #[cfg(any(test, feature = "test-support"))]
pub mod test; pub mod test;
pub mod http;
pub mod telemetry; pub mod telemetry;
pub mod user; pub mod user;
@ -18,7 +17,6 @@ use gpui::{
AnyModelHandle, AnyViewHandle, AnyWeakModelHandle, AnyWeakViewHandle, AppContext, AppVersion, AnyModelHandle, AnyViewHandle, AnyWeakModelHandle, AnyWeakViewHandle, AppContext, AppVersion,
AsyncAppContext, Entity, ModelHandle, MutableAppContext, Task, View, ViewContext, ViewHandle, AsyncAppContext, Entity, ModelHandle, MutableAppContext, Task, View, ViewContext, ViewHandle,
}; };
use http::HttpClient;
use lazy_static::lazy_static; use lazy_static::lazy_static;
use parking_lot::RwLock; use parking_lot::RwLock;
use postage::watch; use postage::watch;
@ -41,6 +39,7 @@ use telemetry::Telemetry;
use thiserror::Error; use thiserror::Error;
use url::Url; use url::Url;
use util::channel::ReleaseChannel; use util::channel::ReleaseChannel;
use util::http::HttpClient;
use util::{ResultExt, TryFutureExt}; use util::{ResultExt, TryFutureExt};
pub use rpc::*; pub use rpc::*;
@ -130,7 +129,7 @@ pub enum EstablishConnectionError {
#[error("{0}")] #[error("{0}")]
Other(#[from] anyhow::Error), Other(#[from] anyhow::Error),
#[error("{0}")] #[error("{0}")]
Http(#[from] http::Error), Http(#[from] util::http::Error),
#[error("{0}")] #[error("{0}")]
Io(#[from] std::io::Error), Io(#[from] std::io::Error),
#[error("{0}")] #[error("{0}")]
@ -1396,10 +1395,11 @@ pub fn decode_worktree_url(url: &str) -> Option<(u64, String)> {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::test::{FakeHttpClient, FakeServer}; use crate::test::FakeServer;
use gpui::{executor::Deterministic, TestAppContext}; use gpui::{executor::Deterministic, TestAppContext};
use parking_lot::Mutex; use parking_lot::Mutex;
use std::future; use std::future;
use util::http::FakeHttpClient;
#[gpui::test(iterations = 10)] #[gpui::test(iterations = 10)]
async fn test_reconnection(cx: &mut TestAppContext) { async fn test_reconnection(cx: &mut TestAppContext) {

View File

@ -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<AsyncBody>;
pub type Response = isahc::Response<AsyncBody>;
pub trait HttpClient: Send + Sync {
fn send(&self, req: Request) -> BoxFuture<Result<Response, Error>>;
fn get<'a>(
&'a self,
uri: &str,
body: AsyncBody,
follow_redirects: bool,
) -> BoxFuture<'a, Result<Response, 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(),
}
}
}
pub fn client() -> Arc<dyn HttpClient> {
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<Result<Response, Error>> {
Box::pin(async move { self.send_async(req).await })
}
}

View File

@ -1,11 +1,9 @@
use crate::http::HttpClient;
use db::kvp::KEY_VALUE_STORE; use db::kvp::KEY_VALUE_STORE;
use gpui::{ use gpui::{
executor::Background, executor::Background,
serde_json::{self, value::Map, Value}, serde_json::{self, value::Map, Value},
AppContext, Task, AppContext, Task,
}; };
use isahc::Request;
use lazy_static::lazy_static; use lazy_static::lazy_static;
use parking_lot::Mutex; use parking_lot::Mutex;
use serde::Serialize; use serde::Serialize;
@ -19,6 +17,7 @@ use std::{
time::{Duration, SystemTime, UNIX_EPOCH}, time::{Duration, SystemTime, UNIX_EPOCH},
}; };
use tempfile::NamedTempFile; use tempfile::NamedTempFile;
use util::http::HttpClient;
use util::{channel::ReleaseChannel, post_inc, ResultExt, TryFutureExt}; use util::{channel::ReleaseChannel, post_inc, ResultExt, TryFutureExt};
use uuid::Uuid; use uuid::Uuid;
@ -220,10 +219,10 @@ impl Telemetry {
"App": true "App": true
}), }),
}])?; }])?;
let request = Request::post(MIXPANEL_ENGAGE_URL)
.header("Content-Type", "application/json") this.http_client
.body(json_bytes.into())?; .post_json(MIXPANEL_ENGAGE_URL, json_bytes.into())
this.http_client.send(request).await?; .await?;
anyhow::Ok(()) anyhow::Ok(())
} }
.log_err(), .log_err(),
@ -316,10 +315,9 @@ impl Telemetry {
json_bytes.clear(); json_bytes.clear();
serde_json::to_writer(&mut json_bytes, &events)?; serde_json::to_writer(&mut json_bytes, &events)?;
let request = Request::post(MIXPANEL_EVENTS_URL) this.http_client
.header("Content-Type", "application/json") .post_json(MIXPANEL_EVENTS_URL, json_bytes.into())
.body(json_bytes.into())?; .await?;
this.http_client.send(request).await?;
anyhow::Ok(()) anyhow::Ok(())
} }
.log_err(), .log_err(),

View File

@ -1,16 +1,14 @@
use crate::{ use crate::{Client, Connection, Credentials, EstablishConnectionError, UserStore};
http::{self, HttpClient, Request, Response},
Client, Connection, Credentials, EstablishConnectionError, UserStore,
};
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
use futures::{future::BoxFuture, stream::BoxStream, Future, StreamExt}; use futures::{stream::BoxStream, StreamExt};
use gpui::{executor, ModelHandle, TestAppContext}; use gpui::{executor, ModelHandle, TestAppContext};
use parking_lot::Mutex; use parking_lot::Mutex;
use rpc::{ use rpc::{
proto::{self, GetPrivateUserInfo, GetPrivateUserInfoResponse}, proto::{self, GetPrivateUserInfo, GetPrivateUserInfoResponse},
ConnectionId, Peer, Receipt, TypedEnvelope, ConnectionId, Peer, Receipt, TypedEnvelope,
}; };
use std::{fmt, rc::Rc, sync::Arc}; use std::{rc::Rc, sync::Arc};
use util::http::FakeHttpClient;
pub struct FakeServer { pub struct FakeServer {
peer: Arc<Peer>, peer: Arc<Peer>,
@ -219,46 +217,3 @@ impl Drop for FakeServer {
self.disconnect(); self.disconnect();
} }
} }
pub struct FakeHttpClient {
handler: Box<
dyn 'static
+ Send
+ Sync
+ Fn(Request) -> BoxFuture<'static, Result<Response, http::Error>>,
>,
}
impl FakeHttpClient {
pub fn create<Fut, F>(handler: F) -> Arc<dyn HttpClient>
where
Fut: 'static + Send + Future<Output = Result<Response, http::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<dyn HttpClient> {
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<Result<Response, crate::http::Error>> {
let future = (self.handler)(req);
Box::pin(async move { future.await.map(Into::into) })
}
}

View File

@ -1,4 +1,4 @@
use super::{http::HttpClient, proto, Client, Status, TypedEnvelope}; use super::{proto, Client, Status, TypedEnvelope};
use anyhow::{anyhow, Context, Result}; use anyhow::{anyhow, Context, Result};
use collections::{hash_map::Entry, HashMap, HashSet}; use collections::{hash_map::Entry, HashMap, HashSet};
use futures::{channel::mpsc, future, AsyncReadExt, Future, StreamExt}; use futures::{channel::mpsc, future, AsyncReadExt, Future, StreamExt};
@ -7,6 +7,7 @@ use postage::{sink::Sink, watch};
use rpc::proto::{RequestMessage, UsersResponse}; use rpc::proto::{RequestMessage, UsersResponse};
use settings::Settings; use settings::Settings;
use std::sync::{Arc, Weak}; use std::sync::{Arc, Weak};
use util::http::HttpClient;
use util::{StaffMode, TryFutureExt as _}; use util::{StaffMode, TryFutureExt as _};
#[derive(Default, Debug)] #[derive(Default, Debug)]

View File

@ -7,8 +7,7 @@ use crate::{
use anyhow::anyhow; use anyhow::anyhow;
use call::ActiveCall; use call::ActiveCall;
use client::{ use client::{
self, proto::PeerId, test::FakeHttpClient, Client, Connection, Credentials, self, proto::PeerId, Client, Connection, Credentials, EstablishConnectionError, UserStore,
EstablishConnectionError, UserStore,
}; };
use collections::{HashMap, HashSet}; use collections::{HashMap, HashSet};
use fs::FakeFs; use fs::FakeFs;
@ -28,6 +27,7 @@ use std::{
}, },
}; };
use theme::ThemeRegistry; use theme::ThemeRegistry;
use util::http::FakeHttpClient;
use workspace::Workspace; use workspace::Workspace;
mod integration_tests; mod integration_tests;

21
crates/copilot/Cargo.toml Normal file
View File

@ -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"

21
crates/copilot/readme.md Normal file
View File

@ -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 doesnt 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 were signed out. when we sign in, we set the global. then, the editor can access the global (e.g. cx.global::<Option<Copilot>>) 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 its kinda orthogonal to the language servers we store in the project. what do you think?

View File

@ -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<Client>, 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<dyn HttpClient>, cx: &mut MutableAppContext) {
let copilot = cx.global::<Option<Arc<Copilot>>>().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<dyn HttpClient>) -> anyhow::Result<PathBuf> {
///Check for the latest copilot language server and download it if we haven't already
async fn fetch_latest(http: Arc<dyn HttpClient>) -> anyhow::Result<PathBuf> {
let release = latest_github_release("zed-industries/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,
<fs::Permissions as fs::unix::PermissionsExt>::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
}
}
}

View File

@ -10,7 +10,6 @@ mod buffer_tests;
use anyhow::{anyhow, Context, Result}; use anyhow::{anyhow, Context, Result};
use async_trait::async_trait; use async_trait::async_trait;
use client::http::HttpClient;
use collections::HashMap; use collections::HashMap;
use futures::{ use futures::{
channel::oneshot, channel::oneshot,
@ -45,6 +44,7 @@ use syntax_map::SyntaxSnapshot;
use theme::{SyntaxTheme, Theme}; use theme::{SyntaxTheme, Theme};
use tree_sitter::{self, Query}; use tree_sitter::{self, Query};
use unicase::UniCase; use unicase::UniCase;
use util::http::HttpClient;
use util::{merge_json_value_into, post_inc, ResultExt, TryFutureExt as _, UnwrapFuture}; use util::{merge_json_value_into, post_inc, ResultExt, TryFutureExt as _, UnwrapFuture};
#[cfg(any(test, feature = "test-support"))] #[cfg(any(test, feature = "test-support"))]

View File

@ -568,7 +568,7 @@ impl Project {
let mut languages = LanguageRegistry::test(); let mut languages = LanguageRegistry::test();
languages.set_executor(cx.background()); 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 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 user_store = cx.add_model(|cx| UserStore::new(client.clone(), http_client, cx));
let project = let project =

View File

@ -3114,13 +3114,14 @@ impl<'a> TryFrom<(&'a CharBag, proto::Entry)> for Entry {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use client::test::FakeHttpClient;
use fs::repository::FakeGitRepository; use fs::repository::FakeGitRepository;
use fs::{FakeFs, RealFs}; use fs::{FakeFs, RealFs};
use gpui::{executor::Deterministic, TestAppContext}; use gpui::{executor::Deterministic, TestAppContext};
use rand::prelude::*; use rand::prelude::*;
use serde_json::json; use serde_json::json;
use std::{env, fmt::Write}; use std::{env, fmt::Write};
use util::http::FakeHttpClient;
use util::test::temp_tree; use util::test::temp_tree;
#[gpui::test] #[gpui::test]

View File

@ -14,11 +14,15 @@ test-support = ["tempdir", "git2"]
[dependencies] [dependencies]
anyhow = "1.0.38" anyhow = "1.0.38"
backtrace = "0.3" backtrace = "0.3"
futures = "0.3"
log = { version = "0.4.16", features = ["kv_unstable_serde"] } log = { version = "0.4.16", features = ["kv_unstable_serde"] }
lazy_static = "1.4.0" lazy_static = "1.4.0"
futures = "0.3"
isahc = "1.7"
smol = "1.2.5"
url = "2.2"
rand = { workspace = true } rand = { workspace = true }
tempdir = { version = "0.3.7", optional = true } tempdir = { version = "0.3.7", optional = true }
serde = { version = "1.0", features = ["derive", "rc"] }
serde_json = { version = "1.0", features = ["preserve_order"] } serde_json = { version = "1.0", features = ["preserve_order"] }
git2 = { version = "0.15", default-features = false, optional = true } git2 = { version = "0.15", default-features = false, optional = true }
dirs = "3.0" dirs = "3.0"

28
crates/util/src/fs.rs Normal file
View File

@ -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<F>(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();
}
}
}
}
}
}
}

40
crates/util/src/github.rs Normal file
View File

@ -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<GithubReleaseAsset>,
}
#[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<dyn HttpClient>,
) -> Result<GithubRelease, anyhow::Error> {
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)
}

117
crates/util/src/http.rs Normal file
View File

@ -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<AsyncBody>) -> BoxFuture<Result<Response<AsyncBody>, Error>>;
fn get<'a>(
&'a self,
uri: &str,
body: AsyncBody,
follow_redirects: bool,
) -> BoxFuture<'a, Result<Response<AsyncBody>, 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<Response<AsyncBody>, 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<dyn HttpClient> {
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<AsyncBody>) -> BoxFuture<Result<Response<AsyncBody>, 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<AsyncBody>) -> BoxFuture<'static, Result<Response<AsyncBody>, Error>>,
>,
}
#[cfg(feature = "test-support")]
impl FakeHttpClient {
pub fn create<Fut, F>(handler: F) -> Arc<dyn HttpClient>
where
Fut: 'static + Send + futures::Future<Output = Result<Response<AsyncBody>, Error>>,
F: 'static + Send + Sync + Fn(Request<AsyncBody>) -> Fut,
{
Arc::new(Self {
handler: Box::new(move |req| Box::pin(handler(req))),
})
}
pub fn with_404_response() -> Arc<dyn HttpClient> {
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<AsyncBody>) -> BoxFuture<Result<Response<AsyncBody>, Error>> {
let future = (self.handler)(req);
Box::pin(async move { future.await.map(Into::into) })
}
}

View File

@ -6,6 +6,7 @@ lazy_static::lazy_static! {
pub static ref LOGS_DIR: PathBuf = HOME.join("Library/Logs/Zed"); 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 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 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 DB_DIR: PathBuf = HOME.join("Library/Application Support/Zed/db");
pub static ref SETTINGS: PathBuf = CONFIG_DIR.join("settings.json"); pub static ref SETTINGS: PathBuf = CONFIG_DIR.join("settings.json");
pub static ref KEYMAP: PathBuf = CONFIG_DIR.join("keymap.json"); pub static ref KEYMAP: PathBuf = CONFIG_DIR.join("keymap.json");

View File

@ -1,4 +1,7 @@
pub mod channel; pub mod channel;
pub mod fs;
pub mod github;
pub mod http;
pub mod paths; pub mod paths;
#[cfg(any(test, feature = "test-support"))] #[cfg(any(test, feature = "test-support"))]
pub mod test; pub mod test;

View File

@ -449,7 +449,7 @@ impl AppState {
let fs = fs::FakeFs::new(cx.background().clone()); let fs = fs::FakeFs::new(cx.background().clone());
let languages = Arc::new(LanguageRegistry::test()); 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 client = Client::new(http_client.clone(), cx);
let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http_client, cx)); let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http_client, cx));
let themes = ThemeRegistry::new((), cx.font_cache().clone()); let themes = ThemeRegistry::new((), cx.font_cache().clone());

View File

@ -28,6 +28,7 @@ command_palette = { path = "../command_palette" }
context_menu = { path = "../context_menu" } context_menu = { path = "../context_menu" }
client = { path = "../client" } client = { path = "../client" }
clock = { path = "../clock" } clock = { path = "../clock" }
copilot = { path = "../copilot" }
diagnostics = { path = "../diagnostics" } diagnostics = { path = "../diagnostics" }
db = { path = "../db" } db = { path = "../db" }
editor = { path = "../editor" } editor = { path = "../editor" }

View File

@ -1,11 +1,11 @@
use anyhow::Context; use anyhow::Context;
use client::http::HttpClient;
use gpui::executor::Background; use gpui::executor::Background;
pub use language::*; pub use language::*;
use node_runtime::NodeRuntime; use node_runtime::NodeRuntime;
use rust_embed::RustEmbed; use rust_embed::RustEmbed;
use std::{borrow::Cow, str, sync::Arc}; use std::{borrow::Cow, str, sync::Arc};
use theme::ThemeRegistry; use theme::ThemeRegistry;
use util::http::HttpClient;
mod c; mod c;
mod elixir; mod elixir;

View File

@ -1,13 +1,16 @@
use super::github::{latest_github_release, GitHubLspBinaryVersion};
use anyhow::{anyhow, Context, Result}; use anyhow::{anyhow, Context, Result};
use async_trait::async_trait; use async_trait::async_trait;
use client::http::HttpClient;
use futures::StreamExt; use futures::StreamExt;
pub use language::*; pub use language::*;
use smol::fs::{self, File}; use smol::fs::{self, File};
use std::{any::Any, path::PathBuf, sync::Arc}; 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 util::ResultExt;
use super::github::GitHubLspBinaryVersion;
pub struct CLspAdapter; pub struct CLspAdapter;
#[async_trait] #[async_trait]
@ -69,16 +72,7 @@ impl super::LspAdapter for CLspAdapter {
Err(anyhow!("failed to unzip clangd archive"))?; Err(anyhow!("failed to unzip clangd archive"))?;
} }
if let Some(mut entries) = fs::read_dir(&container_dir).await.log_err() { remove_matching(&container_dir, |entry| entry != version_dir).await;
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();
}
}
}
}
} }
Ok(LanguageServerBinary { Ok(LanguageServerBinary {

View File

@ -1,14 +1,17 @@
use super::github::{latest_github_release, GitHubLspBinaryVersion};
use anyhow::{anyhow, Context, Result}; use anyhow::{anyhow, Context, Result};
use async_trait::async_trait; use async_trait::async_trait;
use client::http::HttpClient;
use futures::StreamExt; use futures::StreamExt;
pub use language::*; pub use language::*;
use lsp::{CompletionItemKind, SymbolKind}; use lsp::{CompletionItemKind, SymbolKind};
use smol::fs::{self, File}; use smol::fs::{self, File};
use std::{any::Any, path::PathBuf, sync::Arc}; 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 util::ResultExt;
use super::github::GitHubLspBinaryVersion;
pub struct ElixirLspAdapter; pub struct ElixirLspAdapter;
#[async_trait] #[async_trait]
@ -76,22 +79,7 @@ impl LspAdapter for ElixirLspAdapter {
Err(anyhow!("failed to unzip clangd archive"))?; Err(anyhow!("failed to unzip clangd archive"))?;
} }
if let Some(mut entries) = fs::read_dir(&container_dir).await.log_err() { remove_matching(&container_dir, |entry| entry != version_dir).await;
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();
}
}
}
}
}
}
} }
Ok(LanguageServerBinary { Ok(LanguageServerBinary {

View File

@ -1,8 +1,8 @@
use anyhow::{Context, Result}; use anyhow::{Context, Result};
use client::http::HttpClient;
use serde::Deserialize; use serde::Deserialize;
use smol::io::AsyncReadExt; use smol::io::AsyncReadExt;
use std::sync::Arc; use std::sync::Arc;
use util::http::HttpClient;
pub struct GitHubLspBinaryVersion { pub struct GitHubLspBinaryVersion {
pub name: String, pub name: String,

View File

@ -1,13 +1,15 @@
use super::github::latest_github_release;
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
use async_trait::async_trait; use async_trait::async_trait;
use client::http::HttpClient;
use futures::StreamExt; use futures::StreamExt;
pub use language::*; pub use language::*;
use lazy_static::lazy_static; use lazy_static::lazy_static;
use regex::Regex; use regex::Regex;
use smol::{fs, process}; 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; use util::ResultExt;
fn server_binary_arguments() -> Vec<OsString> { fn server_binary_arguments() -> Vec<OsString> {
@ -55,18 +57,10 @@ impl super::LspAdapter for GoLspAdapter {
let binary_path = container_dir.join(&format!("gopls_{version}")); let binary_path = container_dir.join(&format!("gopls_{version}"));
if let Ok(metadata) = fs::metadata(&binary_path).await { if let Ok(metadata) = fs::metadata(&binary_path).await {
if metadata.is_file() { if metadata.is_file() {
if let Some(mut entries) = fs::read_dir(&container_dir).await.log_err() { remove_matching(&container_dir, |entry| {
while let Some(entry) = entries.next().await { entry != binary_path && entry.file_name() != Some(OsStr::new("gobin"))
if let Some(entry) = entry.log_err() { })
let entry_path = entry.path(); .await;
if entry_path.as_path() != binary_path
&& entry.file_name() != "gobin"
{
fs::remove_file(&entry_path).await.log_err();
}
}
}
}
return Ok(LanguageServerBinary { return Ok(LanguageServerBinary {
path: binary_path.to_path_buf(), path: binary_path.to_path_buf(),

View File

@ -1,17 +1,15 @@
use super::node_runtime::NodeRuntime; use super::node_runtime::NodeRuntime;
use anyhow::{anyhow, Context, Result}; use anyhow::{anyhow, Context, Result};
use async_trait::async_trait; use async_trait::async_trait;
use client::http::HttpClient;
use futures::StreamExt; use futures::StreamExt;
use language::{LanguageServerBinary, LanguageServerName, LspAdapter}; use language::{LanguageServerBinary, LanguageServerName, LspAdapter};
use serde_json::json; use serde_json::json;
use smol::fs; use smol::fs;
use std::{ use std::ffi::OsString;
any::Any, use std::path::Path;
ffi::OsString, use std::{any::Any, path::PathBuf, sync::Arc};
path::{Path, PathBuf}, use util::fs::remove_matching;
sync::Arc, use util::http::HttpClient;
};
use util::ResultExt; use util::ResultExt;
fn server_binary_arguments(server_path: &Path) -> Vec<OsString> { fn server_binary_arguments(server_path: &Path) -> Vec<OsString> {
@ -69,16 +67,7 @@ impl LspAdapter for HtmlLspAdapter {
) )
.await?; .await?;
if let Some(mut entries) = fs::read_dir(&container_dir).await.log_err() { remove_matching(container_dir.as_path(), |entry| entry != version_dir).await;
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();
}
}
}
}
} }
Ok(LanguageServerBinary { Ok(LanguageServerBinary {

View File

@ -1,7 +1,6 @@
use super::node_runtime::NodeRuntime; use super::node_runtime::NodeRuntime;
use anyhow::{anyhow, Context, Result}; use anyhow::{anyhow, Context, Result};
use async_trait::async_trait; use async_trait::async_trait;
use client::http::HttpClient;
use collections::HashMap; use collections::HashMap;
use futures::{future::BoxFuture, FutureExt, StreamExt}; use futures::{future::BoxFuture, FutureExt, StreamExt};
use gpui::MutableAppContext; use gpui::MutableAppContext;
@ -17,6 +16,7 @@ use std::{
sync::Arc, sync::Arc,
}; };
use theme::ThemeRegistry; use theme::ThemeRegistry;
use util::{fs::remove_matching, http::HttpClient};
use util::{paths, ResultExt, StaffMode}; use util::{paths, ResultExt, StaffMode};
const SERVER_PATH: &'static str = const SERVER_PATH: &'static str =
@ -84,16 +84,7 @@ impl LspAdapter for JsonLspAdapter {
) )
.await?; .await?;
if let Some(mut entries) = fs::read_dir(&container_dir).await.log_err() { remove_matching(&container_dir, |entry| entry != server_path).await;
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();
}
}
}
}
} }
Ok(LanguageServerBinary { Ok(LanguageServerBinary {

View File

@ -1,12 +1,12 @@
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
use async_trait::async_trait; use async_trait::async_trait;
use client::http::HttpClient;
use collections::HashMap; use collections::HashMap;
use futures::lock::Mutex; use futures::lock::Mutex;
use gpui::executor::Background; use gpui::executor::Background;
use language::{LanguageServerBinary, LanguageServerName, LspAdapter}; use language::{LanguageServerBinary, LanguageServerName, LspAdapter};
use plugin_runtime::{Plugin, PluginBinary, PluginBuilder, WasiFn}; use plugin_runtime::{Plugin, PluginBinary, PluginBuilder, WasiFn};
use std::{any::Any, path::PathBuf, sync::Arc}; use std::{any::Any, path::PathBuf, sync::Arc};
use util::http::HttpClient;
use util::ResultExt; use util::ResultExt;
#[allow(dead_code)] #[allow(dead_code)]

View File

@ -1,16 +1,14 @@
use std::{any::Any, env::consts, ffi::OsString, path::PathBuf, sync::Arc};
use anyhow::{anyhow, bail, Result}; use anyhow::{anyhow, bail, Result};
use async_compression::futures::bufread::GzipDecoder; use async_compression::futures::bufread::GzipDecoder;
use async_tar::Archive; use async_tar::Archive;
use async_trait::async_trait; use async_trait::async_trait;
use client::http::HttpClient;
use futures::{io::BufReader, StreamExt}; use futures::{io::BufReader, StreamExt};
use language::{LanguageServerBinary, LanguageServerName}; use language::{LanguageServerBinary, LanguageServerName};
use smol::fs; 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)] #[derive(Copy, Clone)]
pub struct LuaLspAdapter; pub struct LuaLspAdapter;

View File

@ -1,7 +1,6 @@
use anyhow::{anyhow, bail, Context, Result}; use anyhow::{anyhow, bail, Context, Result};
use async_compression::futures::bufread::GzipDecoder; use async_compression::futures::bufread::GzipDecoder;
use async_tar::Archive; use async_tar::Archive;
use client::http::HttpClient;
use futures::{future::Shared, FutureExt}; use futures::{future::Shared, FutureExt};
use gpui::{executor::Background, Task}; use gpui::{executor::Background, Task};
use parking_lot::Mutex; use parking_lot::Mutex;
@ -12,6 +11,7 @@ use std::{
path::{Path, PathBuf}, path::{Path, PathBuf},
sync::Arc, sync::Arc,
}; };
use util::http::HttpClient;
const VERSION: &str = "v18.15.0"; const VERSION: &str = "v18.15.0";

View File

@ -1,7 +1,6 @@
use super::node_runtime::NodeRuntime; use super::node_runtime::NodeRuntime;
use anyhow::{anyhow, Context, Result}; use anyhow::{anyhow, Context, Result};
use async_trait::async_trait; use async_trait::async_trait;
use client::http::HttpClient;
use futures::StreamExt; use futures::StreamExt;
use language::{LanguageServerBinary, LanguageServerName, LspAdapter}; use language::{LanguageServerBinary, LanguageServerName, LspAdapter};
use smol::fs; use smol::fs;
@ -11,6 +10,8 @@ use std::{
path::{Path, PathBuf}, path::{Path, PathBuf},
sync::Arc, sync::Arc,
}; };
use util::fs::remove_matching;
use util::http::HttpClient;
use util::ResultExt; use util::ResultExt;
fn server_binary_arguments(server_path: &Path) -> Vec<OsString> { fn server_binary_arguments(server_path: &Path) -> Vec<OsString> {
@ -60,16 +61,7 @@ impl LspAdapter for PythonLspAdapter {
.npm_install_packages([("pyright", version.as_str())], &version_dir) .npm_install_packages([("pyright", version.as_str())], &version_dir)
.await?; .await?;
if let Some(mut entries) = fs::read_dir(&container_dir).await.log_err() { remove_matching(&container_dir, |entry| entry != version_dir).await;
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();
}
}
}
}
} }
Ok(LanguageServerBinary { Ok(LanguageServerBinary {

View File

@ -1,8 +1,8 @@
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
use async_trait::async_trait; use async_trait::async_trait;
use client::http::HttpClient;
use language::{LanguageServerBinary, LanguageServerName, LspAdapter}; use language::{LanguageServerBinary, LanguageServerName, LspAdapter};
use std::{any::Any, path::PathBuf, sync::Arc}; use std::{any::Any, path::PathBuf, sync::Arc};
use util::http::HttpClient;
pub struct RubyLanguageServer; pub struct RubyLanguageServer;

View File

@ -2,13 +2,14 @@ use super::github::{latest_github_release, GitHubLspBinaryVersion};
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
use async_compression::futures::bufread::GzipDecoder; use async_compression::futures::bufread::GzipDecoder;
use async_trait::async_trait; use async_trait::async_trait;
use client::http::HttpClient;
use futures::{io::BufReader, StreamExt}; use futures::{io::BufReader, StreamExt};
pub use language::*; pub use language::*;
use lazy_static::lazy_static; use lazy_static::lazy_static;
use regex::Regex; use regex::Regex;
use smol::fs::{self, File}; use smol::fs::{self, File};
use std::{any::Any, borrow::Cow, env::consts, path::PathBuf, str, sync::Arc}; 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; use util::ResultExt;
pub struct RustLspAdapter; pub struct RustLspAdapter;
@ -60,16 +61,7 @@ impl LspAdapter for RustLspAdapter {
) )
.await?; .await?;
if let Some(mut entries) = fs::read_dir(&container_dir).await.log_err() { remove_matching(&container_dir, |entry| entry != destination_path).await;
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();
}
}
}
}
} }
Ok(LanguageServerBinary { Ok(LanguageServerBinary {

View File

@ -1,7 +1,6 @@
use super::node_runtime::NodeRuntime; use super::node_runtime::NodeRuntime;
use anyhow::{anyhow, Context, Result}; use anyhow::{anyhow, Context, Result};
use async_trait::async_trait; use async_trait::async_trait;
use client::http::HttpClient;
use futures::StreamExt; use futures::StreamExt;
use language::{LanguageServerBinary, LanguageServerName, LspAdapter}; use language::{LanguageServerBinary, LanguageServerName, LspAdapter};
use serde_json::json; use serde_json::json;
@ -12,6 +11,8 @@ use std::{
path::{Path, PathBuf}, path::{Path, PathBuf},
sync::Arc, sync::Arc,
}; };
use util::fs::remove_matching;
use util::http::HttpClient;
use util::ResultExt; use util::ResultExt;
fn server_binary_arguments(server_path: &Path) -> Vec<OsString> { fn server_binary_arguments(server_path: &Path) -> Vec<OsString> {
@ -90,16 +91,7 @@ impl LspAdapter for TypeScriptLspAdapter {
) )
.await?; .await?;
if let Some(mut entries) = fs::read_dir(&container_dir).await.log_err() { remove_matching(&container_dir, |entry| entry != version_dir).await;
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();
}
}
}
}
} }
Ok(LanguageServerBinary { Ok(LanguageServerBinary {

View File

@ -1,7 +1,6 @@
use super::node_runtime::NodeRuntime; use super::node_runtime::NodeRuntime;
use anyhow::{anyhow, Context, Result}; use anyhow::{anyhow, Context, Result};
use async_trait::async_trait; use async_trait::async_trait;
use client::http::HttpClient;
use futures::{future::BoxFuture, FutureExt, StreamExt}; use futures::{future::BoxFuture, FutureExt, StreamExt};
use gpui::MutableAppContext; use gpui::MutableAppContext;
use language::{LanguageServerBinary, LanguageServerName, LspAdapter}; use language::{LanguageServerBinary, LanguageServerName, LspAdapter};
@ -16,6 +15,7 @@ use std::{
sync::Arc, sync::Arc,
}; };
use util::ResultExt; use util::ResultExt;
use util::{fs::remove_matching, http::HttpClient};
fn server_binary_arguments(server_path: &Path) -> Vec<OsString> { fn server_binary_arguments(server_path: &Path) -> Vec<OsString> {
vec![server_path.into(), "--stdio".into()] 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) .npm_install_packages([("yaml-language-server", version.as_str())], &version_dir)
.await?; .await?;
if let Some(mut entries) = fs::read_dir(&container_dir).await.log_err() { remove_matching(&container_dir, |entry| entry != version_dir).await;
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();
}
}
}
}
} }
Ok(LanguageServerBinary { Ok(LanguageServerBinary {

View File

@ -8,11 +8,7 @@ use cli::{
ipc::{self, IpcSender}, ipc::{self, IpcSender},
CliRequest, CliResponse, IpcHandshake, CliRequest, CliResponse, IpcHandshake,
}; };
use client::{ use client::{self, UserStore, ZED_APP_VERSION, ZED_SECRET_CLIENT_TOKEN};
self,
http::{self, HttpClient},
UserStore, ZED_APP_VERSION, ZED_SECRET_CLIENT_TOKEN,
};
use db::kvp::KEY_VALUE_STORE; use db::kvp::KEY_VALUE_STORE;
use futures::{ use futures::{
channel::{mpsc, oneshot}, channel::{mpsc, oneshot},
@ -36,6 +32,7 @@ use std::{
path::PathBuf, sync::Arc, thread, time::Duration, path::PathBuf, sync::Arc, thread, time::Duration,
}; };
use terminal_view::{get_working_directory, TerminalView}; use terminal_view::{get_working_directory, TerminalView};
use util::http::{self, HttpClient};
use welcome::{show_welcome_experience, FIRST_OPEN}; use welcome::{show_welcome_experience, FIRST_OPEN};
use fs::RealFs; use fs::RealFs;
@ -165,6 +162,7 @@ fn main() {
terminal_view::init(cx); terminal_view::init(cx);
theme_testbench::init(cx); theme_testbench::init(cx);
recent_projects::init(cx); recent_projects::init(cx);
copilot::init(client.clone(), cx);
cx.spawn(|cx| watch_themes(fs.clone(), themes.clone(), cx)) cx.spawn(|cx| watch_themes(fs.clone(), themes.clone(), cx))
.detach(); .detach();

View File

@ -652,7 +652,6 @@ fn open_bundled_file(
mod tests { mod tests {
use super::*; use super::*;
use assets::Assets; use assets::Assets;
use client::test::FakeHttpClient;
use editor::{scroll::autoscroll::Autoscroll, DisplayPoint, Editor}; use editor::{scroll::autoscroll::Autoscroll, DisplayPoint, Editor};
use gpui::{ use gpui::{
executor::Deterministic, AssetSource, MutableAppContext, TestAppContext, ViewHandle, executor::Deterministic, AssetSource, MutableAppContext, TestAppContext, ViewHandle,
@ -665,6 +664,7 @@ mod tests {
path::{Path, PathBuf}, path::{Path, PathBuf},
}; };
use theme::ThemeRegistry; use theme::ThemeRegistry;
use util::http::FakeHttpClient;
use workspace::{ use workspace::{
item::{Item, ItemHandle}, item::{Item, ItemHandle},
open_new, open_paths, pane, NewFile, Pane, SplitDirection, WorkspaceHandle, open_new, open_paths, pane, NewFile, Pane, SplitDirection, WorkspaceHandle,