diff --git a/Cargo.lock b/Cargo.lock index 67f43cc564..bdd8bf26c4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4087,6 +4087,27 @@ dependencies = [ "xmlparser", ] +[[package]] +name = "rpc_client" +version = "0.1.0" +dependencies = [ + "anyhow", + "async-recursion", + "async-tungstenite", + "gpui", + "lazy_static", + "log", + "parking_lot", + "postage", + "rand 0.8.3", + "smol", + "surf", + "thiserror", + "tiny_http", + "util", + "zrpc", +] + [[package]] name = "rsa" version = "0.4.0" @@ -5625,6 +5646,18 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" +[[package]] +name = "util" +version = "0.1.0" +dependencies = [ + "anyhow", + "futures", + "log", + "serde_json 1.0.64", + "surf", + "tempdir", +] + [[package]] name = "uuid" version = "0.5.1" @@ -5897,6 +5930,36 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "worktree" +version = "0.1.0" +dependencies = [ + "anyhow", + "async-trait", + "buffer", + "clock", + "fsevent", + "futures", + "fuzzy", + "gpui", + "ignore", + "lazy_static", + "libc", + "log", + "parking_lot", + "postage", + "rand 0.8.3", + "rpc_client", + "serde 1.0.125", + "serde_json 1.0.64", + "smol", + "sum_tree", + "tempdir", + "toml 0.5.8", + "util", + "zrpc", +] + [[package]] name = "wyz" version = "0.2.0" @@ -5962,6 +6025,7 @@ dependencies = [ "parking_lot", "postage", "rand 0.8.3", + "rpc_client", "rsa", "rust-embed", "serde 1.0.125", @@ -5981,6 +6045,8 @@ dependencies = [ "tree-sitter-rust", "unindent", "url", + "util", + "worktree", "zrpc", ] diff --git a/Cargo.toml b/Cargo.toml index 8701e16cd5..79e86753fa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,8 +6,11 @@ members = [ "fuzzy", "gpui", "gpui_macros", + "rpc_client", "server", "sum_tree", + "util", + "worktree", "zed", "zrpc" ] diff --git a/buffer/src/lib.rs b/buffer/src/lib.rs index 2c080ca57d..07865af34c 100644 --- a/buffer/src/lib.rs +++ b/buffer/src/lib.rs @@ -20,6 +20,8 @@ use lazy_static::lazy_static; use operation_queue::OperationQueue; use parking_lot::Mutex; pub use point::*; +#[cfg(any(test, feature = "test-support"))] +pub use random_char_iter::*; pub use rope::{Chunks, Rope, TextSummary}; use seahash::SeaHasher; pub use selection::*; diff --git a/rpc_client/Cargo.toml b/rpc_client/Cargo.toml new file mode 100644 index 0000000000..9e80487e7e --- /dev/null +++ b/rpc_client/Cargo.toml @@ -0,0 +1,55 @@ +[package] +name = "rpc_client" +version = "0.1.0" +edition = "2018" + +[features] +test-support = [] + +[dependencies] +anyhow = "1.0.38" +async-recursion = "0.3" +# async-trait = "0.1" +async-tungstenite = { version = "0.14", features = ["async-tls"] } +# buffer = { path = "../buffer" } +# clock = { path = "../clock" } +# crossbeam-channel = "0.5.0" +# ctor = "0.1.20" +# dirs = "3.0" +# easy-parallel = "3.1.0" +# fsevent = { path = "../fsevent" } +# futures = "0.3" +# fuzzy = { path = "../fuzzy" } +gpui = { path = "../gpui" } +# http-auth-basic = "0.1.3" +# ignore = "0.4" +# image = "0.23" +# indexmap = "1.6.2" +lazy_static = "1.4.0" +# libc = "0.2" +log = "0.4" +# log-panics = { version = "2.0", features = ["with-backtrace"] } +# num_cpus = "1.13.0" +parking_lot = "0.11.1" +postage = { version = "0.4.1", features = ["futures-traits"] } +rand = "0.8.3" +# rsa = "0.4" +# rust-embed = { version = "6.2", features = ["include-exclude"] } +# serde = { version = "1", features = ["derive"] } +# serde_json = { version = "1.0.64", features = ["preserve_order"] } +# serde_path_to_error = "0.1.4" +# simplelog = "0.9" +# smallvec = { version = "1.6", features = ["union"] } +smol = "1.2.5" +# sum_tree = { path = "../sum_tree" } +surf = "2.2" +# tempdir = { version = "0.3.7", optional = true } +thiserror = "1.0.29" +# time = { version = "0.3" } +tiny_http = "0.8" +# toml = "0.5" +# tree-sitter = "0.19.5" +# tree-sitter-rust = "0.19.0" +util = { path = "../util" } +# url = "2.2" +zrpc = { path = "../zrpc" } diff --git a/zed/src/rpc.rs b/rpc_client/src/lib.rs similarity index 99% rename from zed/src/rpc.rs rename to rpc_client/src/lib.rs index 86f484607d..097fe1e66d 100644 --- a/zed/src/rpc.rs +++ b/rpc_client/src/lib.rs @@ -1,4 +1,6 @@ -use crate::util::ResultExt; +#[cfg(any(test, feature = "test-support"))] +pub mod test; + use anyhow::{anyhow, Context, Result}; use async_recursion::async_recursion; use async_tungstenite::tungstenite::{ @@ -21,6 +23,7 @@ use std::{ }; use surf::Url; use thiserror::Error; +use util::ResultExt; pub use zrpc::{proto, ConnectionId, PeerId, TypedEnvelope}; use zrpc::{ proto::{AnyTypedEnvelope, EntityMessage, EnvelopedMessage, RequestMessage}, diff --git a/rpc_client/src/test.rs b/rpc_client/src/test.rs new file mode 100644 index 0000000000..055c5c1f8a --- /dev/null +++ b/rpc_client/src/test.rs @@ -0,0 +1,156 @@ +use super::*; +use std::sync::atomic::Ordering::SeqCst; + +use super::Client; +use gpui::TestAppContext; +use parking_lot::Mutex; +use postage::{mpsc, prelude::Stream}; +use std::sync::{ + atomic::{AtomicBool, AtomicUsize}, + Arc, +}; +use zrpc::{proto, ConnectionId, Peer, Receipt, TypedEnvelope}; + +pub struct FakeServer { + peer: Arc, + incoming: Mutex>>>, + connection_id: Mutex>, + forbid_connections: AtomicBool, + auth_count: AtomicUsize, + access_token: AtomicUsize, + user_id: u64, +} + +impl FakeServer { + pub async fn for_client( + client_user_id: u64, + client: &mut Arc, + cx: &TestAppContext, + ) -> Arc { + let server = Arc::new(Self { + peer: Peer::new(), + incoming: Default::default(), + connection_id: Default::default(), + forbid_connections: Default::default(), + auth_count: Default::default(), + access_token: Default::default(), + user_id: client_user_id, + }); + + Arc::get_mut(client) + .unwrap() + .override_authenticate({ + let server = server.clone(); + move |cx| { + server.auth_count.fetch_add(1, SeqCst); + let access_token = server.access_token.load(SeqCst).to_string(); + cx.spawn(move |_| async move { + Ok(Credentials { + user_id: client_user_id, + access_token, + }) + }) + } + }) + .override_establish_connection({ + let server = server.clone(); + move |credentials, cx| { + let credentials = credentials.clone(); + cx.spawn({ + let server = server.clone(); + move |cx| async move { server.establish_connection(&credentials, &cx).await } + }) + } + }); + + client + .authenticate_and_connect(&cx.to_async()) + .await + .unwrap(); + server + } + + pub async fn disconnect(&self) { + self.peer.disconnect(self.connection_id()).await; + self.connection_id.lock().take(); + self.incoming.lock().take(); + } + + async fn establish_connection( + &self, + credentials: &Credentials, + cx: &AsyncAppContext, + ) -> Result { + assert_eq!(credentials.user_id, self.user_id); + + if self.forbid_connections.load(SeqCst) { + Err(EstablishConnectionError::Other(anyhow!( + "server is forbidding connections" + )))? + } + + if credentials.access_token != self.access_token.load(SeqCst).to_string() { + Err(EstablishConnectionError::Unauthorized)? + } + + let (client_conn, server_conn, _) = Connection::in_memory(); + let (connection_id, io, incoming) = self.peer.add_connection(server_conn).await; + cx.background().spawn(io).detach(); + *self.incoming.lock() = Some(incoming); + *self.connection_id.lock() = Some(connection_id); + Ok(client_conn) + } + + pub fn auth_count(&self) -> usize { + self.auth_count.load(SeqCst) + } + + pub fn roll_access_token(&self) { + self.access_token.fetch_add(1, SeqCst); + } + + pub fn forbid_connections(&self) { + self.forbid_connections.store(true, SeqCst); + } + + pub fn allow_connections(&self) { + self.forbid_connections.store(false, SeqCst); + } + + pub async fn send(&self, message: T) { + self.peer.send(self.connection_id(), message).await.unwrap(); + } + + pub async fn receive(&self) -> Result> { + let message = self + .incoming + .lock() + .as_mut() + .expect("not connected") + .recv() + .await + .ok_or_else(|| anyhow!("other half hung up"))?; + let type_name = message.payload_type_name(); + Ok(*message + .into_any() + .downcast::>() + .unwrap_or_else(|_| { + panic!( + "fake server received unexpected message type: {:?}", + type_name + ); + })) + } + + pub async fn respond( + &self, + receipt: Receipt, + response: T::Response, + ) { + self.peer.respond(receipt, response).await.unwrap() + } + + fn connection_id(&self) -> ConnectionId { + self.connection_id.lock().expect("not connected") + } +} diff --git a/util/Cargo.toml b/util/Cargo.toml new file mode 100644 index 0000000000..419a99af2b --- /dev/null +++ b/util/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "util" +version = "0.1.0" +edition = "2018" + +[features] +test-support = ["serde_json", "tempdir"] + +[dependencies] +anyhow = "1.0.38" +futures = "0.3" +log = "0.4" +surf = "2.2" +tempdir = { version = "0.3.7", optional = true } +serde_json = { version = "1.0.64", features = ["preserve_order"], optional = true } diff --git a/zed/src/util.rs b/util/src/lib.rs similarity index 79% rename from zed/src/util.rs rename to util/src/lib.rs index e199925d98..e4706766cd 100644 --- a/zed/src/util.rs +++ b/util/src/lib.rs @@ -1,11 +1,12 @@ +#[cfg(feature = "test-support")] +pub mod test; + use futures::Future; -use rand::prelude::*; use std::{ cmp::Ordering, pin::Pin, task::{Context, Poll}, }; -pub use sum_tree::Bias; pub fn post_inc(value: &mut usize) -> usize { let prev = *value; @@ -35,35 +36,6 @@ where } } } - -pub struct RandomCharIter(T); - -impl RandomCharIter { - #[cfg(test)] - pub fn new(rng: T) -> Self { - Self(rng) - } -} - -impl Iterator for RandomCharIter { - type Item = char; - - fn next(&mut self) -> Option { - match self.0.gen_range(0..100) { - // whitespace - 0..=19 => [' ', '\n', '\t'].choose(&mut self.0).copied(), - // two-byte greek letters - 20..=32 => char::from_u32(self.0.gen_range(('α' as u32)..('ω' as u32 + 1))), - // three-byte characters - 33..=45 => ['✋', '✅', '❌', '❎', '⭐'].choose(&mut self.0).copied(), - // four-byte characters - 46..=58 => ['🍐', '🏀', '🍗', '🎉'].choose(&mut self.0).copied(), - // ascii letters - _ => Some(self.0.gen_range(b'a'..b'z' + 1).into()), - } - } -} - pub trait ResultExt { type Ok; diff --git a/util/src/test.rs b/util/src/test.rs new file mode 100644 index 0000000000..57a4b21105 --- /dev/null +++ b/util/src/test.rs @@ -0,0 +1,37 @@ +use std::path::{Path, PathBuf}; +use tempdir::TempDir; + +pub fn temp_tree(tree: serde_json::Value) -> TempDir { + let dir = TempDir::new("").unwrap(); + write_tree(dir.path(), tree); + dir +} + +fn write_tree(path: &Path, tree: serde_json::Value) { + use serde_json::Value; + use std::fs; + + if let Value::Object(map) = tree { + for (name, contents) in map { + let mut path = PathBuf::from(path); + path.push(name); + match contents { + Value::Object(_) => { + fs::create_dir(&path).unwrap(); + write_tree(&path, contents); + } + Value::Null => { + fs::create_dir(&path).unwrap(); + } + Value::String(contents) => { + fs::write(&path, contents).unwrap(); + } + _ => { + panic!("JSON object must contain only objects, strings, or null"); + } + } + } + } else { + panic!("You must pass a JSON object to this helper") + } +} diff --git a/worktree/Cargo.toml b/worktree/Cargo.toml new file mode 100644 index 0000000000..78f539002b --- /dev/null +++ b/worktree/Cargo.toml @@ -0,0 +1,61 @@ +[package] +name = "worktree" +version = "0.1.0" +edition = "2018" + +[features] +test-support = [] + +[dependencies] +anyhow = "1.0.38" +# async-recursion = "0.3" +async-trait = "0.1" +# async-tungstenite = { version = "0.14", features = ["async-tls"] } +buffer = { path = "../buffer" } +clock = { path = "../clock" } +# crossbeam-channel = "0.5.0" +# ctor = "0.1.20" +# dirs = "3.0" +# easy-parallel = "3.1.0" +fsevent = { path = "../fsevent" } +futures = "0.3" +fuzzy = { path = "../fuzzy" } +gpui = { path = "../gpui" } +# http-auth-basic = "0.1.3" +ignore = "0.4" +# image = "0.23" +# indexmap = "1.6.2" +lazy_static = "1.4.0" +libc = "0.2" +log = "0.4" +# log-panics = { version = "2.0", features = ["with-backtrace"] } +# num_cpus = "1.13.0" +parking_lot = "0.11.1" +postage = { version = "0.4.1", features = ["futures-traits"] } +rpc_client = { path = "../rpc_client" } +# rand = "0.8.3" +# rsa = "0.4" +# rust-embed = { version = "6.2", features = ["include-exclude"] } +serde = { version = "1", features = ["derive"] } +serde_json = { version = "1.0.64", features = ["preserve_order"] } +# serde_path_to_error = "0.1.4" +# simplelog = "0.9" +# smallvec = { version = "1.6", features = ["union"] } +smol = "1.2.5" +sum_tree = { path = "../sum_tree" } +util = { path = "../util" } +# surf = "2.2" +# tempdir = { version = "0.3.7", optional = true } +# thiserror = "1.0.29" +# time = { version = "0.3" } +# tiny_http = "0.8" +toml = "0.5" +# tree-sitter = "0.19.5" +# tree-sitter-rust = "0.19.0" +# url = "2.2" +zrpc = { path = "../zrpc" } + +[dev-dependencies] +rand = "0.8.3" +tempdir = { version = "0.3.7" } +util = { path = "../util", features = ["test-support"] } diff --git a/zed/src/fs.rs b/worktree/src/fs.rs similarity index 100% rename from zed/src/fs.rs rename to worktree/src/fs.rs diff --git a/zed/src/worktree/ignore.rs b/worktree/src/ignore.rs similarity index 100% rename from zed/src/worktree/ignore.rs rename to worktree/src/ignore.rs diff --git a/zed/src/worktree.rs b/worktree/src/lib.rs similarity index 99% rename from zed/src/worktree.rs rename to worktree/src/lib.rs index 4f2d9149ed..e6d8769853 100644 --- a/zed/src/worktree.rs +++ b/worktree/src/lib.rs @@ -1,17 +1,14 @@ +pub mod fs; mod ignore; use self::ignore::IgnoreStack; -use crate::{ - fs::{self, Fs}, - fuzzy::CharBag, - rpc::{self, proto, Status}, - util::{Bias, TryFutureExt}, -}; use ::ignore::gitignore::{Gitignore, GitignoreBuilder}; use anyhow::{anyhow, Result}; use buffer::{self, Buffer, History, LanguageRegistry, Operation, Rope}; use clock::ReplicaId; +pub use fs::*; use futures::{Stream, StreamExt}; +use fuzzy::CharBag; use gpui::{ executor, AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle, MutableAppContext, Task, UpgradeModelHandle, WeakModelHandle, @@ -22,6 +19,7 @@ use postage::{ prelude::{Sink as _, Stream as _}, watch, }; +use rpc_client as rpc; use serde::Deserialize; use smol::channel::{self, Sender}; use std::{ @@ -40,8 +38,10 @@ use std::{ }, time::{Duration, SystemTime}, }; +use sum_tree::Bias; use sum_tree::{self, Edit, SeekTarget, SumTree}; -use zrpc::{PeerId, TypedEnvelope}; +use util::TryFutureExt; +use zrpc::{proto, PeerId, TypedEnvelope}; lazy_static! { static ref GITIGNORE: &'static OsStr = OsStr::new(".gitignore"); @@ -755,7 +755,7 @@ impl LocalWorktree { let mut status = rpc.status(); while let Some(status) = status.recv().await { if let Some(this) = this.upgrade(&cx) { - let remote_id = if let Status::Connected { .. } = status { + let remote_id = if let rpc::Status::Connected { .. } = status { let collaborator_logins = this.read_with(&cx, |this, _| { this.as_local().unwrap().config.collaborators.clone() }); @@ -2832,16 +2832,19 @@ impl<'a> TryFrom<(&'a CharBag, proto::Entry)> for Entry { #[cfg(test)] mod tests { use super::*; + use rpc_client::test::FakeServer; use crate::fs::FakeFs; - use crate::test::*; use anyhow::Result; use fs::RealFs; use rand::prelude::*; use serde_json::json; - use std::cell::RefCell; - use std::rc::Rc; - use std::time::UNIX_EPOCH; - use std::{env, fmt::Write, time::SystemTime}; + use std::{cell::RefCell, rc::Rc}; + use std::{ + env, + fmt::Write, + time::{SystemTime, UNIX_EPOCH}, + }; + use util::test::temp_tree; #[gpui::test] async fn test_traversal(cx: gpui::TestAppContext) { diff --git a/zed/Cargo.toml b/zed/Cargo.toml index 95c07117e8..0eb5e764d8 100644 --- a/zed/Cargo.toml +++ b/zed/Cargo.toml @@ -17,7 +17,9 @@ path = "src/main.rs" test-support = [ "buffer/test-support", "gpui/test-support", + "rpc_client/test-support", "tempdir", + "worktree/test-support", "zrpc/test-support", ] @@ -48,6 +50,7 @@ num_cpus = "1.13.0" parking_lot = "0.11.1" postage = { version = "0.4.1", features = ["futures-traits"] } rand = "0.8.3" +rpc_client = { path = "../rpc_client" } rsa = "0.4" rust-embed = { version = "6.2", features = ["include-exclude"] } serde = { version = "1", features = ["derive"] } @@ -66,6 +69,8 @@ toml = "0.5" tree-sitter = "0.19.5" tree-sitter-rust = "0.19.0" url = "2.2" +util = { path = "../util" } +worktree = { path = "../worktree" } zrpc = { path = "../zrpc" } [dev-dependencies] @@ -75,8 +80,11 @@ serde_json = { version = "1.0.64", features = ["preserve_order"] } tempdir = { version = "0.3.7" } unindent = "0.1.7" buffer = { path = "../buffer", features = ["test-support"] } -zrpc = { path = "../zrpc", features = ["test-support"] } gpui = { path = "../gpui", features = ["test-support"] } +rpc_client = { path = "../rpc_client", features = ["test-support"] } +util = { path = "../util", features = ["test-support"] } +worktree = { path = "../worktree", features = ["test-support"] } +zrpc = { path = "../zrpc", features = ["test-support"] } [package.metadata.bundle] icon = ["app-icon@2x.png", "app-icon.png"] diff --git a/zed/src/channel.rs b/zed/src/channel.rs index 5b4dfa6d39..3da50032e9 100644 --- a/zed/src/channel.rs +++ b/zed/src/channel.rs @@ -1,14 +1,11 @@ -use crate::{ - rpc::{self, Client}, - user::{User, UserStore}, - util::{post_inc, TryFutureExt}, -}; +use crate::user::{User, UserStore}; use anyhow::{anyhow, Context, Result}; use gpui::{ AsyncAppContext, Entity, ModelContext, ModelHandle, MutableAppContext, Task, WeakModelHandle, }; use postage::prelude::Stream; use rand::prelude::*; +use rpc_client as rpc; use std::{ collections::{HashMap, HashSet}, mem, @@ -17,6 +14,7 @@ use std::{ }; use sum_tree::{self, Bias, SumTree}; use time::OffsetDateTime; +use util::{post_inc, TryFutureExt}; use zrpc::{ proto::{self, ChannelMessageSent}, TypedEnvelope, @@ -25,7 +23,7 @@ use zrpc::{ pub struct ChannelList { available_channels: Option>, channels: HashMap>, - rpc: Arc, + rpc: Arc, user_store: ModelHandle, _task: Task>, } @@ -42,7 +40,7 @@ pub struct Channel { loaded_all_messages: bool, next_pending_message_id: usize, user_store: ModelHandle, - rpc: Arc, + rpc: Arc, rng: StdRng, _subscription: rpc::Subscription, } @@ -187,7 +185,7 @@ impl Channel { pub fn new( details: ChannelDetails, user_store: ModelHandle, - rpc: Arc, + rpc: Arc, cx: &mut ModelContext, ) -> Self { let _subscription = rpc.subscribe_to_entity(details.id, cx, Self::handle_message_sent); @@ -594,14 +592,15 @@ impl<'a> sum_tree::Dimension<'a, ChannelMessageSummary> for Count { #[cfg(test)] mod tests { use super::*; - use crate::test::{FakeHttpClient, FakeServer}; + use crate::test::FakeHttpClient; use gpui::TestAppContext; + use rpc_client::test::FakeServer; use surf::http::Response; #[gpui::test] async fn test_channel_messages(mut cx: TestAppContext) { let user_id = 5; - let mut client = Client::new(); + let mut client = rpc::Client::new(); let http_client = FakeHttpClient::new(|_| async move { Ok(Response::new(404)) }); let server = FakeServer::for_client(user_id, &mut client, &cx).await; let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http_client, cx)); diff --git a/zed/src/chat_panel.rs b/zed/src/chat_panel.rs index 5b07214efb..ebfb6151c1 100644 --- a/zed/src/chat_panel.rs +++ b/zed/src/chat_panel.rs @@ -3,10 +3,7 @@ use std::sync::Arc; use crate::{ channel::{Channel, ChannelEvent, ChannelList, ChannelMessage}, editor::Editor, - rpc::{self, Client}, - theme, - util::{ResultExt, TryFutureExt}, - Settings, + theme, Settings, }; use gpui::{ action, @@ -18,12 +15,14 @@ use gpui::{ ViewContext, ViewHandle, }; use postage::{prelude::Stream, watch}; +use rpc_client as rpc; use time::{OffsetDateTime, UtcOffset}; +use util::{ResultExt, TryFutureExt}; const MESSAGE_LOADING_THRESHOLD: usize = 50; pub struct ChatPanel { - rpc: Arc, + rpc: Arc, channel_list: ModelHandle, active_channel: Option<(ModelHandle, Subscription)>, message_list: ListState, @@ -48,7 +47,7 @@ pub fn init(cx: &mut MutableAppContext) { impl ChatPanel { pub fn new( - rpc: Arc, + rpc: Arc, channel_list: ModelHandle, settings: watch::Receiver, cx: &mut ViewContext, diff --git a/zed/src/editor.rs b/zed/src/editor.rs index 172c04456f..e5c5d5bd99 100644 --- a/zed/src/editor.rs +++ b/zed/src/editor.rs @@ -2,14 +2,7 @@ pub mod display_map; mod element; pub mod movement; -use crate::{ - project::ProjectPath, - settings::Settings, - theme::Theme, - util::{post_inc, Bias}, - workspace, - worktree::Worktree, -}; +use crate::{project::ProjectPath, settings::Settings, theme::Theme, workspace}; use anyhow::Result; use buffer::*; use clock::ReplicaId; @@ -35,6 +28,9 @@ use std::{ sync::Arc, time::Duration, }; +use sum_tree::Bias; +use util::post_inc; +use worktree::Worktree; const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500); const MAX_LINE_LEN: usize = 1024; diff --git a/zed/src/editor/display_map.rs b/zed/src/editor/display_map.rs index c79c4caf0d..ad2b6659bd 100644 --- a/zed/src/editor/display_map.rs +++ b/zed/src/editor/display_map.rs @@ -357,8 +357,8 @@ impl ToDisplayPoint for Anchor { #[cfg(test)] mod tests { use super::*; - use crate::{editor::movement, test::*, util::RandomCharIter}; - use buffer::{History, Language, LanguageConfig, SelectionGoal, SyntaxTheme}; + use crate::{editor::movement, test::*}; + use buffer::{History, Language, LanguageConfig, RandomCharIter, SelectionGoal, SyntaxTheme}; use gpui::{color::Color, MutableAppContext}; use rand::{prelude::StdRng, Rng}; use std::{env, sync::Arc}; diff --git a/zed/src/editor/display_map/fold_map.rs b/zed/src/editor/display_map/fold_map.rs index ef8ff64fb5..a58033ea55 100644 --- a/zed/src/editor/display_map/fold_map.rs +++ b/zed/src/editor/display_map/fold_map.rs @@ -1128,7 +1128,8 @@ impl FoldEdit { #[cfg(test)] mod tests { use super::*; - use crate::{editor::ToPoint, test::sample_text, util::RandomCharIter}; + use crate::{editor::ToPoint, test::sample_text}; + use buffer::RandomCharIter; use rand::prelude::*; use std::{env, mem}; use Bias::{Left, Right}; diff --git a/zed/src/editor/display_map/tab_map.rs b/zed/src/editor/display_map/tab_map.rs index ffd5a1c5c5..4fa684c47e 100644 --- a/zed/src/editor/display_map/tab_map.rs +++ b/zed/src/editor/display_map/tab_map.rs @@ -1,8 +1,8 @@ use super::fold_map::{self, FoldEdit, FoldPoint, Snapshot as FoldSnapshot}; -use crate::util::Bias; use buffer::{rope, HighlightId}; use parking_lot::Mutex; use std::{mem, ops::Range}; +use sum_tree::Bias; pub struct TabMap(Mutex); diff --git a/zed/src/editor/display_map/wrap_map.rs b/zed/src/editor/display_map/wrap_map.rs index a5e5f99f03..8377ec30a9 100644 --- a/zed/src/editor/display_map/wrap_map.rs +++ b/zed/src/editor/display_map/wrap_map.rs @@ -2,13 +2,13 @@ use super::{ fold_map, tab_map::{self, Edit as TabEdit, Snapshot as TabSnapshot, TabPoint, TextSummary}, }; -use crate::{editor::Point, util::Bias}; +use crate::editor::Point; use buffer::HighlightId; use gpui::{fonts::FontId, text_layout::LineWrapper, Entity, ModelContext, Task}; use lazy_static::lazy_static; use smol::future::yield_now; use std::{collections::VecDeque, ops::Range, time::Duration}; -use sum_tree::{self, Cursor, SumTree}; +use sum_tree::{self, Bias, Cursor, SumTree}; pub struct WrapMap { snapshot: Snapshot, @@ -902,8 +902,8 @@ mod tests { Buffer, }, test::Observer, - util::RandomCharIter, }; + use buffer::RandomCharIter; use rand::prelude::*; use std::env; diff --git a/zed/src/file_finder.rs b/zed/src/file_finder.rs index 66c52b2093..44569c54a7 100644 --- a/zed/src/file_finder.rs +++ b/zed/src/file_finder.rs @@ -3,7 +3,6 @@ use crate::{ fuzzy::PathMatch, project::{Project, ProjectPath}, settings::Settings, - util, workspace::Workspace, }; use gpui::{ @@ -26,6 +25,7 @@ use std::{ Arc, }, }; +use util::post_inc; pub struct FileFinder { handle: WeakViewHandle, @@ -315,7 +315,7 @@ impl FileFinder { editor::Event::Edited => { let query = self.query_editor.update(cx, |buffer, cx| buffer.text(cx)); if query.is_empty() { - self.latest_search_id = util::post_inc(&mut self.search_count); + self.latest_search_id = post_inc(&mut self.search_count); self.matches.clear(); cx.notify(); } else { @@ -422,12 +422,12 @@ mod tests { use super::*; use crate::{ editor::{self, Insert}, - fs::FakeFs, test::test_app_state, workspace::Workspace, }; use serde_json::json; use std::path::PathBuf; + use worktree::fs::FakeFs; #[gpui::test] async fn test_matching_paths(mut cx: gpui::TestAppContext) { diff --git a/zed/src/fuzzy.rs b/zed/src/fuzzy.rs index bbc519c7cc..990bca6cbf 100644 --- a/zed/src/fuzzy.rs +++ b/zed/src/fuzzy.rs @@ -1,12 +1,10 @@ -use crate::{ - util, - worktree::{EntryKind, Snapshot}, -}; use gpui::executor; use std::{ cmp, sync::{atomic::AtomicBool, Arc}, }; +use util; +use worktree::{EntryKind, Snapshot}; pub use fuzzy::*; diff --git a/zed/src/lib.rs b/zed/src/lib.rs index 9ab6a3b9ae..8536d53271 100644 --- a/zed/src/lib.rs +++ b/zed/src/lib.rs @@ -3,7 +3,6 @@ pub mod channel; pub mod chat_panel; pub mod editor; pub mod file_finder; -pub mod fs; mod fuzzy; pub mod http; pub mod language; @@ -11,27 +10,25 @@ pub mod menus; pub mod people_panel; pub mod project; pub mod project_panel; -pub mod rpc; pub mod settings; #[cfg(any(test, feature = "test-support"))] pub mod test; pub mod theme; pub mod theme_selector; pub mod user; -mod util; pub mod workspace; -pub mod worktree; -use crate::util::TryFutureExt; pub use buffer; use buffer::LanguageRegistry; use channel::ChannelList; use gpui::{action, keymap::Binding, ModelHandle}; use parking_lot::Mutex; use postage::watch; -use std::sync::Arc; - +pub use rpc_client as rpc; pub use settings::Settings; +use std::sync::Arc; +use util::TryFutureExt; +pub use worktree::{self, fs}; action!(About); action!(Quit); diff --git a/zed/src/project.rs b/zed/src/project.rs index 0a7338fc34..3e69de5706 100644 --- a/zed/src/project.rs +++ b/zed/src/project.rs @@ -1,25 +1,24 @@ use crate::{ - fs::Fs, fuzzy::{self, PathMatch}, - rpc::Client, - util::TryFutureExt as _, - worktree::{self, Worktree}, AppState, }; use anyhow::Result; use buffer::LanguageRegistry; use futures::Future; use gpui::{AppContext, Entity, ModelContext, ModelHandle, Task}; +use rpc_client as rpc; use std::{ path::Path, sync::{atomic::AtomicBool, Arc}, }; +use util::TryFutureExt as _; +use worktree::{fs::Fs, Worktree}; pub struct Project { worktrees: Vec>, active_entry: Option, languages: Arc, - rpc: Arc, + rpc: Arc, fs: Arc, } @@ -237,12 +236,11 @@ impl Entity for Project { #[cfg(test)] mod tests { use super::*; - use crate::{ - fs::RealFs, - test::{temp_tree, test_app_state}, - }; + use crate::test::test_app_state; use serde_json::json; use std::{os::unix, path::PathBuf}; + use util::test::temp_tree; + use worktree::fs::RealFs; #[gpui::test] async fn test_populate_and_search(mut cx: gpui::TestAppContext) { diff --git a/zed/src/project_panel.rs b/zed/src/project_panel.rs index d56d7ce7f7..3d243dbebb 100644 --- a/zed/src/project_panel.rs +++ b/zed/src/project_panel.rs @@ -2,7 +2,6 @@ use crate::{ project::{self, Project, ProjectEntry, ProjectPath}, theme, workspace::Workspace, - worktree::{self, Worktree}, Settings, }; use gpui::{ @@ -26,6 +25,7 @@ use std::{ ffi::OsStr, ops::Range, }; +use worktree::Worktree; pub struct ProjectPanel { project: ModelHandle, diff --git a/zed/src/test.rs b/zed/src/test.rs index a97d44a217..7051f29ef0 100644 --- a/zed/src/test.rs +++ b/zed/src/test.rs @@ -1,32 +1,21 @@ use crate::{ assets::Assets, channel::ChannelList, - fs::FakeFs, http::{HttpClient, Request, Response, ServerResponse}, language, - rpc::{self, Client, Credentials, EstablishConnectionError}, settings::{self, ThemeRegistry}, user::UserStore, AppState, }; -use anyhow::{anyhow, Result}; +use anyhow::Result; use buffer::LanguageRegistry; use futures::{future::BoxFuture, Future}; -use gpui::{AsyncAppContext, Entity, ModelHandle, MutableAppContext, TestAppContext}; +use gpui::{Entity, ModelHandle, MutableAppContext}; use parking_lot::Mutex; -use postage::{mpsc, prelude::Stream as _}; +use rpc_client as rpc; use smol::channel; -use std::{ - fmt, - marker::PhantomData, - path::{Path, PathBuf}, - sync::{ - atomic::{AtomicBool, AtomicUsize, Ordering::SeqCst}, - Arc, - }, -}; -use tempdir::TempDir; -use zrpc::{proto, Connection, ConnectionId, Peer, Receipt, TypedEnvelope}; +use std::{fmt, marker::PhantomData, sync::Arc}; +use worktree::fs::FakeFs; #[cfg(test)] #[ctor::ctor] @@ -47,41 +36,6 @@ pub fn sample_text(rows: usize, cols: usize) -> String { text } -pub fn temp_tree(tree: serde_json::Value) -> TempDir { - let dir = TempDir::new("").unwrap(); - write_tree(dir.path(), tree); - dir -} - -fn write_tree(path: &Path, tree: serde_json::Value) { - use serde_json::Value; - use std::fs; - - if let Value::Object(map) = tree { - for (name, contents) in map { - let mut path = PathBuf::from(path); - path.push(name); - match contents { - Value::Object(_) => { - fs::create_dir(&path).unwrap(); - write_tree(&path, contents); - } - Value::Null => { - fs::create_dir(&path).unwrap(); - } - Value::String(contents) => { - fs::write(&path, contents).unwrap(); - } - _ => { - panic!("JSON object must contain only objects, strings, or null"); - } - } - } - } else { - panic!("You must pass a JSON object to this helper") - } -} - pub fn test_app_state(cx: &mut MutableAppContext) -> Arc { let (settings_tx, settings) = settings::test(cx); let mut languages = LanguageRegistry::new(); @@ -125,150 +79,6 @@ impl Observer { } } -pub struct FakeServer { - peer: Arc, - incoming: Mutex>>>, - connection_id: Mutex>, - forbid_connections: AtomicBool, - auth_count: AtomicUsize, - access_token: AtomicUsize, - user_id: u64, -} - -impl FakeServer { - pub async fn for_client( - client_user_id: u64, - client: &mut Arc, - cx: &TestAppContext, - ) -> Arc { - let server = Arc::new(Self { - peer: Peer::new(), - incoming: Default::default(), - connection_id: Default::default(), - forbid_connections: Default::default(), - auth_count: Default::default(), - access_token: Default::default(), - user_id: client_user_id, - }); - - Arc::get_mut(client) - .unwrap() - .override_authenticate({ - let server = server.clone(); - move |cx| { - server.auth_count.fetch_add(1, SeqCst); - let access_token = server.access_token.load(SeqCst).to_string(); - cx.spawn(move |_| async move { - Ok(Credentials { - user_id: client_user_id, - access_token, - }) - }) - } - }) - .override_establish_connection({ - let server = server.clone(); - move |credentials, cx| { - let credentials = credentials.clone(); - cx.spawn({ - let server = server.clone(); - move |cx| async move { server.establish_connection(&credentials, &cx).await } - }) - } - }); - - client - .authenticate_and_connect(&cx.to_async()) - .await - .unwrap(); - server - } - - pub async fn disconnect(&self) { - self.peer.disconnect(self.connection_id()).await; - self.connection_id.lock().take(); - self.incoming.lock().take(); - } - - async fn establish_connection( - &self, - credentials: &Credentials, - cx: &AsyncAppContext, - ) -> Result { - assert_eq!(credentials.user_id, self.user_id); - - if self.forbid_connections.load(SeqCst) { - Err(EstablishConnectionError::Other(anyhow!( - "server is forbidding connections" - )))? - } - - if credentials.access_token != self.access_token.load(SeqCst).to_string() { - Err(EstablishConnectionError::Unauthorized)? - } - - let (client_conn, server_conn, _) = Connection::in_memory(); - let (connection_id, io, incoming) = self.peer.add_connection(server_conn).await; - cx.background().spawn(io).detach(); - *self.incoming.lock() = Some(incoming); - *self.connection_id.lock() = Some(connection_id); - Ok(client_conn) - } - - pub fn auth_count(&self) -> usize { - self.auth_count.load(SeqCst) - } - - pub fn roll_access_token(&self) { - self.access_token.fetch_add(1, SeqCst); - } - - pub fn forbid_connections(&self) { - self.forbid_connections.store(true, SeqCst); - } - - pub fn allow_connections(&self) { - self.forbid_connections.store(false, SeqCst); - } - - pub async fn send(&self, message: T) { - self.peer.send(self.connection_id(), message).await.unwrap(); - } - - pub async fn receive(&self) -> Result> { - let message = self - .incoming - .lock() - .as_mut() - .expect("not connected") - .recv() - .await - .ok_or_else(|| anyhow!("other half hung up"))?; - let type_name = message.payload_type_name(); - Ok(*message - .into_any() - .downcast::>() - .unwrap_or_else(|_| { - panic!( - "fake server received unexpected message type: {:?}", - type_name - ); - })) - } - - pub async fn respond( - &self, - receipt: Receipt, - response: T::Response, - ) { - self.peer.respond(receipt, response).await.unwrap() - } - - fn connection_id(&self) -> ConnectionId { - self.connection_id.lock().expect("not connected") - } -} - pub struct FakeHttpClient { handler: Box BoxFuture<'static, Result>>, diff --git a/zed/src/user.rs b/zed/src/user.rs index 7467b16f9b..63718345b7 100644 --- a/zed/src/user.rs +++ b/zed/src/user.rs @@ -1,16 +1,14 @@ -use crate::{ - http::{HttpClient, Method, Request, Url}, - rpc::{Client, Status}, - util::TryFutureExt, -}; +use crate::http::{HttpClient, Method, Request, Url}; use anyhow::{anyhow, Context, Result}; use futures::future; use gpui::{AsyncAppContext, Entity, ImageData, ModelContext, ModelHandle, Task}; use postage::{prelude::Stream, sink::Sink, watch}; +use rpc_client as rpc; use std::{ collections::{HashMap, HashSet}, sync::Arc, }; +use util::TryFutureExt as _; use zrpc::{proto, TypedEnvelope}; #[derive(Debug)] @@ -38,7 +36,7 @@ pub struct UserStore { users: HashMap>, current_user: watch::Receiver>>, collaborators: Arc<[Collaborator]>, - rpc: Arc, + rpc: Arc, http: Arc, _maintain_collaborators: Task<()>, _maintain_current_user: Task<()>, @@ -51,7 +49,11 @@ impl Entity for UserStore { } impl UserStore { - pub fn new(rpc: Arc, http: Arc, cx: &mut ModelContext) -> Self { + pub fn new( + rpc: Arc, + http: Arc, + cx: &mut ModelContext, + ) -> Self { let (mut current_user_tx, current_user_rx) = watch::channel(); let (mut update_collaborators_tx, mut update_collaborators_rx) = watch::channel::>(); @@ -82,7 +84,7 @@ impl UserStore { let mut status = rpc.status(); while let Some(status) = status.recv().await { match status { - Status::Connected { .. } => { + rpc::Status::Connected { .. } => { if let Some((this, user_id)) = this.upgrade(&cx).zip(rpc.user_id()) { let user = this .update(&mut cx, |this, cx| this.fetch_user(user_id, cx)) @@ -91,7 +93,7 @@ impl UserStore { current_user_tx.send(user).await.ok(); } } - Status::SignedOut => { + rpc::Status::SignedOut => { current_user_tx.send(None).await.ok(); } _ => {} diff --git a/zed/src/workspace.rs b/zed/src/workspace.rs index 38233d9fc8..c2aaa75963 100644 --- a/zed/src/workspace.rs +++ b/zed/src/workspace.rs @@ -12,7 +12,6 @@ use crate::{ settings::Settings, user, workspace::sidebar::{Side, Sidebar, SidebarItemId, ToggleSidebarItem, ToggleSidebarItemFocus}, - worktree::Worktree, AppState, Authenticate, }; use anyhow::Result; @@ -38,6 +37,7 @@ use std::{ path::{Path, PathBuf}, sync::Arc, }; +use worktree::Worktree; action!(Open, Arc); action!(OpenPaths, OpenParams); @@ -1159,10 +1159,11 @@ mod tests { use crate::{ editor::{Editor, Insert}, fs::FakeFs, - test::{temp_tree, test_app_state}, + test::test_app_state, }; use serde_json::json; use std::collections::HashSet; + use util::test::temp_tree; #[gpui::test] async fn test_open_paths_action(mut cx: gpui::TestAppContext) {