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

View File

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

View File

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

View File

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

View File

@ -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) {

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 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(),

View File

@ -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<Peer>,
@ -219,46 +217,3 @@ impl Drop for FakeServer {
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 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)]

View File

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

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 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"))]

View File

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

View File

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

View File

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

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 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");

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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<OsString> {
@ -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(),

View File

@ -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<OsString> {
@ -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 {

View File

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

View File

@ -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)]

View File

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

View File

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

View File

@ -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<OsString> {
@ -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 {

View File

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

View File

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

View File

@ -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<OsString> {
@ -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 {

View File

@ -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<OsString> {
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 {

View File

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

View File

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