mirror of
https://github.com/zed-industries/zed.git
synced 2024-11-09 21:26:14 +03:00
Merge pull request #1084 from zed-industries/private-projects
Offline projects
This commit is contained in:
commit
ff3e3d0799
144
Cargo.lock
generated
144
Cargo.lock
generated
@ -485,9 +485,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "bindgen"
|
||||
version = "0.58.1"
|
||||
version = "0.59.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0f8523b410d7187a43085e7e064416ea32ded16bd0a4e6fc025e21616d01258f"
|
||||
checksum = "2bd2a9a458e8f4304c52c43ebb0cfbd520289f8379a52e329a38afda99bf8eb8"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cexpr",
|
||||
@ -503,7 +503,7 @@ dependencies = [
|
||||
"regex",
|
||||
"rustc-hash",
|
||||
"shlex",
|
||||
"which 3.1.1",
|
||||
"which",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -616,6 +616,17 @@ version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8"
|
||||
|
||||
[[package]]
|
||||
name = "bzip2-sys"
|
||||
version = "0.1.11+1.0.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "736a955f3fa7875102d57c82b8cac37ec45224a07fd32d58f9f7a186b6cd4cdc"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cache-padded"
|
||||
version = "1.1.1"
|
||||
@ -639,11 +650,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cexpr"
|
||||
version = "0.4.0"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f4aedb84272dbe89af497cf81375129abda4fc0a9e7c5d317498c15cc30c0d27"
|
||||
checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766"
|
||||
dependencies = [
|
||||
"nom 5.1.2",
|
||||
"nom 7.1.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -956,6 +967,7 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"client",
|
||||
"collections",
|
||||
"editor",
|
||||
"futures",
|
||||
"fuzzy",
|
||||
@ -1444,9 +1456,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "env_logger"
|
||||
version = "0.8.3"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "17392a012ea30ef05a610aa97dfb49496e71c9f676b27879922ea5bdf60d9d3f"
|
||||
checksum = "0b2cf0344971ee6c64c31be0d530793fba457d322dfec2810c453d0ef228f9c3"
|
||||
dependencies = [
|
||||
"atty",
|
||||
"humantime",
|
||||
@ -1539,9 +1551,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "fixedbitset"
|
||||
version = "0.2.0"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "37ab347416e802de484e4d03c7316c48f1ecb56574dfd4a46a80f173ce1de04d"
|
||||
checksum = "279fb028e20b3c4c320317955b77c5e0c9701f05a1d309905d6fc702cdc5053e"
|
||||
|
||||
[[package]]
|
||||
name = "flate2"
|
||||
@ -1987,12 +1999,6 @@ dependencies = [
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04"
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.11.2"
|
||||
@ -2008,7 +2014,7 @@ version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7249a3129cbc1ffccd74857f81464a323a152173cdb134e0fd81bc803b29facf"
|
||||
dependencies = [
|
||||
"hashbrown 0.11.2",
|
||||
"hashbrown",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2240,12 +2246,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "1.6.2"
|
||||
version = "1.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "824845a0bf897a9042383849b02c1bc219c2383772efcd5c6f9766fa4b81aef3"
|
||||
checksum = "e6012d540c5baa3589337a98ce73408de9b5a25ec9fc2c6fd6be8f0d39e0ca5a"
|
||||
dependencies = [
|
||||
"autocfg 1.0.1",
|
||||
"hashbrown 0.9.1",
|
||||
"hashbrown",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2481,9 +2487,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.119"
|
||||
version = "0.2.126"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1bf2e165bb3457c8e098ea76f3e3bc9db55f87aa90d52d0e6be741470916aaa4"
|
||||
checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836"
|
||||
|
||||
[[package]]
|
||||
name = "libloading"
|
||||
@ -2511,6 +2517,21 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "librocksdb-sys"
|
||||
version = "0.6.1+6.28.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "81bc587013734dadb7cf23468e531aa120788b87243648be42e2d3a072186291"
|
||||
dependencies = [
|
||||
"bindgen",
|
||||
"bzip2-sys",
|
||||
"cc",
|
||||
"glob",
|
||||
"libc",
|
||||
"libz-sys",
|
||||
"zstd-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libz-sys"
|
||||
version = "1.1.3"
|
||||
@ -2721,6 +2742,12 @@ version = "0.3.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d"
|
||||
|
||||
[[package]]
|
||||
name = "minimal-lexical"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
|
||||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.3.7"
|
||||
@ -2847,16 +2874,6 @@ dependencies = [
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nom"
|
||||
version = "5.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ffb4262d26ed83a1c0a33a38fe2bb15797329c85770da05e6b828ddb782627af"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nom"
|
||||
version = "6.1.2"
|
||||
@ -2870,6 +2887,16 @@ dependencies = [
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nom"
|
||||
version = "7.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a8903e5a29a317527874d0402f867152a3d21c908bb0b933e416c65e301d4c36"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"minimal-lexical",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ntapi"
|
||||
version = "0.3.7"
|
||||
@ -3190,9 +3217,9 @@ checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"
|
||||
|
||||
[[package]]
|
||||
name = "petgraph"
|
||||
version = "0.5.1"
|
||||
version = "0.6.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "467d164a6de56270bd7c4d070df81d07beace25012d5103ced4e9ff08d6afdb7"
|
||||
checksum = "e6d5014253a1331579ce62aa67443b4a658c5e7dd03d4bc6d302b94474888143"
|
||||
dependencies = [
|
||||
"fixedbitset",
|
||||
"indexmap",
|
||||
@ -3394,6 +3421,7 @@ dependencies = [
|
||||
"postage",
|
||||
"rand 0.8.3",
|
||||
"regex",
|
||||
"rocksdb",
|
||||
"rpc",
|
||||
"serde",
|
||||
"serde_json",
|
||||
@ -3473,20 +3501,22 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "prost-build"
|
||||
version = "0.8.0"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "355f634b43cdd80724ee7848f95770e7e70eefa6dcf14fea676216573b8fd603"
|
||||
checksum = "62941722fb675d463659e49c4f3fe1fe792ff24fe5bbaa9c08cd3b98a1c354f5"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"heck 0.3.3",
|
||||
"itertools",
|
||||
"lazy_static",
|
||||
"log",
|
||||
"multimap",
|
||||
"petgraph",
|
||||
"prost 0.8.0",
|
||||
"prost 0.9.0",
|
||||
"prost-types",
|
||||
"regex",
|
||||
"tempfile",
|
||||
"which 4.1.0",
|
||||
"which",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3517,12 +3547,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "prost-types"
|
||||
version = "0.8.0"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "603bbd6394701d13f3f25aada59c7de9d35a6a5887cfc156181234a44002771b"
|
||||
checksum = "534b7a0e836e3c482d2693070f982e39e7611da9695d4d1f5a4b186b51faef0a"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"prost 0.8.0",
|
||||
"prost 0.9.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3710,9 +3740,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.5.4"
|
||||
version = "1.5.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461"
|
||||
checksum = "d83f127d94bdbcda4c8cc2e50f6f84f4b611f69c902699ca385a39c3a75f9ff1"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
@ -3730,9 +3760,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.6.25"
|
||||
version = "0.6.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
|
||||
checksum = "49b3de9ec5dc0a3417da371aab17d729997c15010e7fd24ff707773a33bddb64"
|
||||
|
||||
[[package]]
|
||||
name = "remove_dir_all"
|
||||
@ -3819,6 +3849,16 @@ dependencies = [
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rocksdb"
|
||||
version = "0.18.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "620f4129485ff1a7128d184bc687470c21c7951b64779ebc9cfdad3dcd920290"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"librocksdb-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "roxmltree"
|
||||
version = "0.14.1"
|
||||
@ -5821,20 +5861,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "which"
|
||||
version = "3.1.1"
|
||||
version = "4.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d011071ae14a2f6671d0b74080ae0cd8ebf3a6f8c9589a2cd45f23126fe29724"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "which"
|
||||
version = "4.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b55551e42cbdf2ce2bedd2203d0cc08dba002c27510f86dab6d0ce304cba3dfe"
|
||||
checksum = "5c4fb54e6113b6a8772ee41c3404fb0301ac79604489467e0a9ce1f3e97c24ae"
|
||||
dependencies = [
|
||||
"either",
|
||||
"lazy_static",
|
||||
"libc",
|
||||
]
|
||||
|
||||
|
3
assets/icons/lock-8.svg
Normal file
3
assets/icons/lock-8.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg width="8" height="8" viewBox="0 0 8 8" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M1.75 3V2.25C1.75 1.00734 2.75781 0 4 0C5.24219 0 6.25 1.00734 6.25 2.25V3H6.5C7.05156 3 7.5 3.44844 7.5 4V7C7.5 7.55156 7.05156 8 6.5 8H1.5C0.947656 8 0.5 7.55156 0.5 7V4C0.5 3.44844 0.947656 3 1.5 3H1.75ZM2.75 3H5.25V2.25C5.25 1.55969 4.69063 1 4 1C3.30938 1 2.75 1.55969 2.75 2.25V3Z" fill="#8B8792"/>
|
||||
</svg>
|
After Width: | Height: | Size: 413 B |
@ -67,17 +67,23 @@ pub struct Client {
|
||||
peer: Arc<Peer>,
|
||||
http: Arc<dyn HttpClient>,
|
||||
state: RwLock<ClientState>,
|
||||
authenticate:
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
authenticate: RwLock<
|
||||
Option<Box<dyn 'static + Send + Sync + Fn(&AsyncAppContext) -> Task<Result<Credentials>>>>,
|
||||
establish_connection: Option<
|
||||
Box<
|
||||
dyn 'static
|
||||
+ Send
|
||||
+ Sync
|
||||
+ Fn(
|
||||
&Credentials,
|
||||
&AsyncAppContext,
|
||||
) -> Task<Result<Connection, EstablishConnectionError>>,
|
||||
>,
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
establish_connection: RwLock<
|
||||
Option<
|
||||
Box<
|
||||
dyn 'static
|
||||
+ Send
|
||||
+ Sync
|
||||
+ Fn(
|
||||
&Credentials,
|
||||
&AsyncAppContext,
|
||||
) -> Task<Result<Connection, EstablishConnectionError>>,
|
||||
>,
|
||||
>,
|
||||
>,
|
||||
}
|
||||
@ -235,8 +241,11 @@ impl Client {
|
||||
peer: Peer::new(),
|
||||
http,
|
||||
state: Default::default(),
|
||||
authenticate: None,
|
||||
establish_connection: None,
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
authenticate: Default::default(),
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
establish_connection: Default::default(),
|
||||
})
|
||||
}
|
||||
|
||||
@ -260,23 +269,23 @@ impl Client {
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub fn override_authenticate<F>(&mut self, authenticate: F) -> &mut Self
|
||||
pub fn override_authenticate<F>(&self, authenticate: F) -> &Self
|
||||
where
|
||||
F: 'static + Send + Sync + Fn(&AsyncAppContext) -> Task<Result<Credentials>>,
|
||||
{
|
||||
self.authenticate = Some(Box::new(authenticate));
|
||||
*self.authenticate.write() = Some(Box::new(authenticate));
|
||||
self
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub fn override_establish_connection<F>(&mut self, connect: F) -> &mut Self
|
||||
pub fn override_establish_connection<F>(&self, connect: F) -> &Self
|
||||
where
|
||||
F: 'static
|
||||
+ Send
|
||||
+ Sync
|
||||
+ Fn(&Credentials, &AsyncAppContext) -> Task<Result<Connection, EstablishConnectionError>>,
|
||||
{
|
||||
self.establish_connection = Some(Box::new(connect));
|
||||
*self.establish_connection.write() = Some(Box::new(connect));
|
||||
self
|
||||
}
|
||||
|
||||
@ -755,11 +764,12 @@ impl Client {
|
||||
}
|
||||
|
||||
fn authenticate(self: &Arc<Self>, cx: &AsyncAppContext) -> Task<Result<Credentials>> {
|
||||
if let Some(callback) = self.authenticate.as_ref() {
|
||||
callback(cx)
|
||||
} else {
|
||||
self.authenticate_with_browser(cx)
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
if let Some(callback) = self.authenticate.read().as_ref() {
|
||||
return callback(cx);
|
||||
}
|
||||
|
||||
self.authenticate_with_browser(cx)
|
||||
}
|
||||
|
||||
fn establish_connection(
|
||||
@ -767,11 +777,12 @@ impl Client {
|
||||
credentials: &Credentials,
|
||||
cx: &AsyncAppContext,
|
||||
) -> Task<Result<Connection, EstablishConnectionError>> {
|
||||
if let Some(callback) = self.establish_connection.as_ref() {
|
||||
callback(credentials, cx)
|
||||
} else {
|
||||
self.establish_websocket_connection(credentials, cx)
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
if let Some(callback) = self.establish_connection.read().as_ref() {
|
||||
return callback(credentials, cx);
|
||||
}
|
||||
|
||||
self.establish_websocket_connection(credentials, cx)
|
||||
}
|
||||
|
||||
fn establish_websocket_connection(
|
||||
|
@ -28,7 +28,7 @@ struct FakeServerState {
|
||||
impl FakeServer {
|
||||
pub async fn for_client(
|
||||
client_user_id: u64,
|
||||
client: &mut Arc<Client>,
|
||||
client: &Arc<Client>,
|
||||
cx: &TestAppContext,
|
||||
) -> Self {
|
||||
let server = Self {
|
||||
@ -38,8 +38,7 @@ impl FakeServer {
|
||||
executor: cx.foreground(),
|
||||
};
|
||||
|
||||
Arc::get_mut(client)
|
||||
.unwrap()
|
||||
client
|
||||
.override_authenticate({
|
||||
let state = Arc::downgrade(&server.state);
|
||||
move |cx| {
|
||||
@ -179,6 +178,12 @@ impl FakeServer {
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for FakeServer {
|
||||
fn drop(&mut self) {
|
||||
self.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
pub struct FakeHttpClient {
|
||||
handler: Box<
|
||||
dyn 'static
|
||||
|
@ -42,7 +42,7 @@ pub struct Contact {
|
||||
pub projects: Vec<ProjectMetadata>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct ProjectMetadata {
|
||||
pub id: u64,
|
||||
pub worktree_root_names: Vec<String>,
|
||||
@ -99,6 +99,7 @@ impl Entity for UserStore {
|
||||
|
||||
enum UpdateContacts {
|
||||
Update(proto::UpdateContacts),
|
||||
Wait(postage::barrier::Sender),
|
||||
Clear(postage::barrier::Sender),
|
||||
}
|
||||
|
||||
@ -217,6 +218,10 @@ impl UserStore {
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Task<Result<()>> {
|
||||
match message {
|
||||
UpdateContacts::Wait(barrier) => {
|
||||
drop(barrier);
|
||||
Task::ready(Ok(()))
|
||||
}
|
||||
UpdateContacts::Clear(barrier) => {
|
||||
self.contacts.clear();
|
||||
self.incoming_contact_requests.clear();
|
||||
@ -497,6 +502,16 @@ impl UserStore {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn contact_updates_done(&mut self) -> impl Future<Output = ()> {
|
||||
let (tx, mut rx) = postage::barrier::channel();
|
||||
self.update_contacts_tx
|
||||
.unbounded_send(UpdateContacts::Wait(tx))
|
||||
.unwrap();
|
||||
async move {
|
||||
rx.recv().await;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_users(
|
||||
&mut self,
|
||||
mut user_ids: Vec<u64>,
|
||||
|
@ -65,7 +65,7 @@ settings = { path = "../settings", features = ["test-support"] }
|
||||
theme = { path = "../theme" }
|
||||
workspace = { path = "../workspace", features = ["test-support"] }
|
||||
ctor = "0.1"
|
||||
env_logger = "0.8"
|
||||
env_logger = "0.9"
|
||||
util = { path = "../util" }
|
||||
lazy_static = "1.4"
|
||||
serde_json = { version = "1.0", features = ["preserve_order"] }
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -82,7 +82,6 @@ pub fn init_tracing(config: &Config) -> Option<()> {
|
||||
use tracing_subscriber::layer::SubscriberExt;
|
||||
let rust_log = config.rust_log.clone()?;
|
||||
|
||||
println!("HEY!");
|
||||
LogTracer::init().log_err()?;
|
||||
|
||||
let subscriber = tracing_subscriber::Registry::default()
|
||||
|
@ -141,12 +141,11 @@ impl Server {
|
||||
server
|
||||
.add_request_handler(Server::ping)
|
||||
.add_request_handler(Server::register_project)
|
||||
.add_message_handler(Server::unregister_project)
|
||||
.add_request_handler(Server::unregister_project)
|
||||
.add_request_handler(Server::join_project)
|
||||
.add_message_handler(Server::leave_project)
|
||||
.add_message_handler(Server::respond_to_join_project_request)
|
||||
.add_request_handler(Server::register_worktree)
|
||||
.add_message_handler(Server::unregister_worktree)
|
||||
.add_message_handler(Server::update_project)
|
||||
.add_request_handler(Server::update_worktree)
|
||||
.add_message_handler(Server::start_language_server)
|
||||
.add_message_handler(Server::update_language_server)
|
||||
@ -477,21 +476,22 @@ impl Server {
|
||||
request: TypedEnvelope<proto::RegisterProject>,
|
||||
response: Response<proto::RegisterProject>,
|
||||
) -> Result<()> {
|
||||
let user_id;
|
||||
let project_id;
|
||||
{
|
||||
let mut state = self.store_mut().await;
|
||||
user_id = state.user_id_for_connection(request.sender_id)?;
|
||||
let user_id = state.user_id_for_connection(request.sender_id)?;
|
||||
project_id = state.register_project(request.sender_id, user_id);
|
||||
};
|
||||
self.update_user_contacts(user_id).await?;
|
||||
|
||||
response.send(proto::RegisterProjectResponse { project_id })?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn unregister_project(
|
||||
self: Arc<Server>,
|
||||
request: TypedEnvelope<proto::UnregisterProject>,
|
||||
response: Response<proto::UnregisterProject>,
|
||||
) -> Result<()> {
|
||||
let (user_id, project) = {
|
||||
let mut state = self.store_mut().await;
|
||||
@ -528,7 +528,13 @@ impl Server {
|
||||
}
|
||||
}
|
||||
|
||||
// Send out the `UpdateContacts` message before responding to the unregister
|
||||
// request. This way, when the project's host can keep track of the project's
|
||||
// remote id until after they've received the `UpdateContacts` message for
|
||||
// themself.
|
||||
self.update_user_contacts(user_id).await?;
|
||||
response.send(proto::Ack {})?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -568,6 +574,7 @@ impl Server {
|
||||
response: Response<proto::JoinProject>,
|
||||
) -> Result<()> {
|
||||
let project_id = request.payload.project_id;
|
||||
|
||||
let host_user_id;
|
||||
let guest_user_id;
|
||||
let host_connection_id;
|
||||
@ -768,63 +775,28 @@ impl Server {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn register_worktree(
|
||||
async fn update_project(
|
||||
self: Arc<Server>,
|
||||
request: TypedEnvelope<proto::RegisterWorktree>,
|
||||
response: Response<proto::RegisterWorktree>,
|
||||
request: TypedEnvelope<proto::UpdateProject>,
|
||||
) -> Result<()> {
|
||||
let host_user_id;
|
||||
let user_id;
|
||||
{
|
||||
let mut state = self.store_mut().await;
|
||||
host_user_id = state.user_id_for_connection(request.sender_id)?;
|
||||
|
||||
user_id = state.user_id_for_connection(request.sender_id)?;
|
||||
let guest_connection_ids = state
|
||||
.read_project(request.payload.project_id, request.sender_id)?
|
||||
.guest_connection_ids();
|
||||
state.register_worktree(
|
||||
state.update_project(
|
||||
request.payload.project_id,
|
||||
request.payload.worktree_id,
|
||||
&request.payload.worktrees,
|
||||
request.sender_id,
|
||||
Worktree {
|
||||
root_name: request.payload.root_name.clone(),
|
||||
visible: request.payload.visible,
|
||||
..Default::default()
|
||||
},
|
||||
)?;
|
||||
|
||||
broadcast(request.sender_id, guest_connection_ids, |connection_id| {
|
||||
self.peer
|
||||
.forward_send(request.sender_id, connection_id, request.payload.clone())
|
||||
});
|
||||
}
|
||||
self.update_user_contacts(host_user_id).await?;
|
||||
response.send(proto::Ack {})?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn unregister_worktree(
|
||||
self: Arc<Server>,
|
||||
request: TypedEnvelope<proto::UnregisterWorktree>,
|
||||
) -> Result<()> {
|
||||
let host_user_id;
|
||||
let project_id = request.payload.project_id;
|
||||
let worktree_id = request.payload.worktree_id;
|
||||
{
|
||||
let mut state = self.store_mut().await;
|
||||
let (_, guest_connection_ids) =
|
||||
state.unregister_worktree(project_id, worktree_id, request.sender_id)?;
|
||||
host_user_id = state.user_id_for_connection(request.sender_id)?;
|
||||
broadcast(request.sender_id, guest_connection_ids, |conn_id| {
|
||||
self.peer.send(
|
||||
conn_id,
|
||||
proto::UnregisterWorktree {
|
||||
project_id,
|
||||
worktree_id,
|
||||
},
|
||||
)
|
||||
});
|
||||
}
|
||||
self.update_user_contacts(host_user_id).await?;
|
||||
};
|
||||
self.update_user_contacts(user_id).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -833,10 +805,11 @@ impl Server {
|
||||
request: TypedEnvelope<proto::UpdateWorktree>,
|
||||
response: Response<proto::UpdateWorktree>,
|
||||
) -> Result<()> {
|
||||
let connection_ids = self.store_mut().await.update_worktree(
|
||||
let (connection_ids, metadata_changed) = self.store_mut().await.update_worktree(
|
||||
request.sender_id,
|
||||
request.payload.project_id,
|
||||
request.payload.worktree_id,
|
||||
&request.payload.root_name,
|
||||
&request.payload.removed_entries,
|
||||
&request.payload.updated_entries,
|
||||
request.payload.scan_id,
|
||||
@ -846,6 +819,13 @@ impl Server {
|
||||
self.peer
|
||||
.forward_send(request.sender_id, connection_id, request.payload.clone())
|
||||
});
|
||||
if metadata_changed {
|
||||
let user_id = self
|
||||
.store()
|
||||
.await
|
||||
.user_id_for_connection(request.sender_id)?;
|
||||
self.update_user_contacts(user_id).await?;
|
||||
}
|
||||
response.send(proto::Ack {})?;
|
||||
Ok(())
|
||||
}
|
||||
|
@ -32,7 +32,7 @@ pub struct Project {
|
||||
#[serde(skip)]
|
||||
pub join_requests: HashMap<UserId, Vec<Receipt<proto::JoinProject>>>,
|
||||
pub active_replica_ids: HashSet<ReplicaId>,
|
||||
pub worktrees: HashMap<u64, Worktree>,
|
||||
pub worktrees: BTreeMap<u64, Worktree>,
|
||||
pub language_servers: Vec<proto::LanguageServer>,
|
||||
}
|
||||
|
||||
@ -312,19 +312,32 @@ impl Store {
|
||||
project_id
|
||||
}
|
||||
|
||||
pub fn register_worktree(
|
||||
pub fn update_project(
|
||||
&mut self,
|
||||
project_id: u64,
|
||||
worktree_id: u64,
|
||||
worktrees: &[proto::WorktreeMetadata],
|
||||
connection_id: ConnectionId,
|
||||
worktree: Worktree,
|
||||
) -> Result<()> {
|
||||
let project = self
|
||||
.projects
|
||||
.get_mut(&project_id)
|
||||
.ok_or_else(|| anyhow!("no such project"))?;
|
||||
if project.host_connection_id == connection_id {
|
||||
project.worktrees.insert(worktree_id, worktree);
|
||||
let mut old_worktrees = mem::take(&mut project.worktrees);
|
||||
for worktree in worktrees {
|
||||
if let Some(old_worktree) = old_worktrees.remove(&worktree.id) {
|
||||
project.worktrees.insert(worktree.id, old_worktree);
|
||||
} else {
|
||||
project.worktrees.insert(
|
||||
worktree.id,
|
||||
Worktree {
|
||||
root_name: worktree.root_name.clone(),
|
||||
visible: worktree.visible,
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
} else {
|
||||
Err(anyhow!("no such project"))?
|
||||
@ -374,27 +387,6 @@ impl Store {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn unregister_worktree(
|
||||
&mut self,
|
||||
project_id: u64,
|
||||
worktree_id: u64,
|
||||
acting_connection_id: ConnectionId,
|
||||
) -> Result<(Worktree, Vec<ConnectionId>)> {
|
||||
let project = self
|
||||
.projects
|
||||
.get_mut(&project_id)
|
||||
.ok_or_else(|| anyhow!("no such project"))?;
|
||||
if project.host_connection_id != acting_connection_id {
|
||||
Err(anyhow!("not your worktree"))?;
|
||||
}
|
||||
|
||||
let worktree = project
|
||||
.worktrees
|
||||
.remove(&worktree_id)
|
||||
.ok_or_else(|| anyhow!("no such worktree"))?;
|
||||
Ok((worktree, project.guest_connection_ids()))
|
||||
}
|
||||
|
||||
pub fn update_diagnostic_summary(
|
||||
&mut self,
|
||||
project_id: u64,
|
||||
@ -573,15 +565,15 @@ impl Store {
|
||||
connection_id: ConnectionId,
|
||||
project_id: u64,
|
||||
worktree_id: u64,
|
||||
worktree_root_name: &str,
|
||||
removed_entries: &[u64],
|
||||
updated_entries: &[proto::Entry],
|
||||
scan_id: u64,
|
||||
) -> Result<Vec<ConnectionId>> {
|
||||
) -> Result<(Vec<ConnectionId>, bool)> {
|
||||
let project = self.write_project(project_id, connection_id)?;
|
||||
let worktree = project
|
||||
.worktrees
|
||||
.get_mut(&worktree_id)
|
||||
.ok_or_else(|| anyhow!("no such worktree"))?;
|
||||
let mut worktree = project.worktrees.entry(worktree_id).or_default();
|
||||
let metadata_changed = worktree_root_name != worktree.root_name;
|
||||
worktree.root_name = worktree_root_name.to_string();
|
||||
for entry_id in removed_entries {
|
||||
worktree.entries.remove(&entry_id);
|
||||
}
|
||||
@ -590,7 +582,7 @@ impl Store {
|
||||
}
|
||||
worktree.scan_id = scan_id;
|
||||
let connection_ids = project.connection_ids();
|
||||
Ok(connection_ids)
|
||||
Ok((connection_ids, metadata_changed))
|
||||
}
|
||||
|
||||
pub fn project_connection_ids(
|
||||
|
@ -25,4 +25,4 @@ project = { path = "../project", features = ["test-support"] }
|
||||
serde_json = { version = "1.0", features = ["preserve_order"] }
|
||||
workspace = { path = "../workspace", features = ["test-support"] }
|
||||
ctor = "0.1"
|
||||
env_logger = "0.8"
|
||||
env_logger = "0.9"
|
||||
|
@ -9,6 +9,7 @@ doctest = false
|
||||
|
||||
[dependencies]
|
||||
client = { path = "../client" }
|
||||
collections = { path = "../collections" }
|
||||
editor = { path = "../editor" }
|
||||
fuzzy = { path = "../fuzzy" }
|
||||
gpui = { path = "../gpui" }
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -59,7 +59,7 @@ project = { path = "../project", features = ["test-support"] }
|
||||
settings = { path = "../settings", features = ["test-support"] }
|
||||
workspace = { path = "../workspace", features = ["test-support"] }
|
||||
ctor = "0.1"
|
||||
env_logger = "0.8"
|
||||
env_logger = "0.9"
|
||||
rand = "0.8"
|
||||
unindent = "0.1.7"
|
||||
tree-sitter = "0.20"
|
||||
|
@ -25,4 +25,4 @@ gpui = { path = "../gpui", features = ["test-support"] }
|
||||
serde_json = { version = "1.0", features = ["preserve_order"] }
|
||||
workspace = { path = "../workspace", features = ["test-support"] }
|
||||
ctor = "0.1"
|
||||
env_logger = "0.8"
|
||||
env_logger = "0.9"
|
||||
|
@ -20,7 +20,7 @@ async-task = "4.0.3"
|
||||
backtrace = { version = "0.3", optional = true }
|
||||
ctor = "0.1"
|
||||
dhat = { version = "0.3", optional = true }
|
||||
env_logger = { version = "0.8", optional = true }
|
||||
env_logger = { version = "0.9", optional = true }
|
||||
etagere = "0.2"
|
||||
futures = "0.3"
|
||||
image = "0.23"
|
||||
@ -47,14 +47,14 @@ usvg = "0.14"
|
||||
waker-fn = "1.1.0"
|
||||
|
||||
[build-dependencies]
|
||||
bindgen = "0.58.1"
|
||||
bindgen = "0.59.2"
|
||||
cc = "1.0.67"
|
||||
|
||||
[dev-dependencies]
|
||||
backtrace = "0.3"
|
||||
collections = { path = "../collections", features = ["test-support"] }
|
||||
dhat = "0.3"
|
||||
env_logger = "0.8"
|
||||
env_logger = "0.9"
|
||||
png = "0.16"
|
||||
simplelog = "0.9"
|
||||
|
||||
|
@ -499,7 +499,14 @@ impl TestAppContext {
|
||||
Fut: 'static + Future<Output = T>,
|
||||
T: 'static,
|
||||
{
|
||||
self.cx.borrow_mut().spawn(f)
|
||||
let foreground = self.foreground();
|
||||
let future = f(self.to_async());
|
||||
let cx = self.to_async();
|
||||
foreground.spawn(async move {
|
||||
let result = future.await;
|
||||
cx.0.borrow_mut().flush_effects();
|
||||
result
|
||||
})
|
||||
}
|
||||
|
||||
pub fn simulate_new_path_selection(&self, result: impl FnOnce(PathBuf) -> Option<PathBuf>) {
|
||||
@ -4604,6 +4611,10 @@ impl<T: View> WeakViewHandle<T> {
|
||||
self.view_id
|
||||
}
|
||||
|
||||
pub fn window_id(&self) -> usize {
|
||||
self.window_id
|
||||
}
|
||||
|
||||
pub fn upgrade(&self, cx: &impl UpgradeViewHandle) -> Option<ViewHandle<T>> {
|
||||
cx.upgrade_view_handle(self)
|
||||
}
|
||||
|
@ -147,6 +147,12 @@ pub struct AppVersion {
|
||||
patch: usize,
|
||||
}
|
||||
|
||||
impl Default for CursorStyle {
|
||||
fn default() -> Self {
|
||||
Self::Arrow
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for AppVersion {
|
||||
type Err = anyhow::Error;
|
||||
|
||||
|
@ -57,7 +57,7 @@ lsp = { path = "../lsp", features = ["test-support"] }
|
||||
text = { path = "../text", features = ["test-support"] }
|
||||
util = { path = "../util", features = ["test-support"] }
|
||||
ctor = "0.1"
|
||||
env_logger = "0.8"
|
||||
env_logger = "0.9"
|
||||
rand = "0.8.3"
|
||||
tree-sitter-json = "*"
|
||||
tree-sitter-rust = "*"
|
||||
|
@ -30,5 +30,5 @@ gpui = { path = "../gpui", features = ["test-support"] }
|
||||
util = { path = "../util", features = ["test-support"] }
|
||||
async-pipe = { git = "https://github.com/zed-industries/async-pipe-rs", rev = "82d00a04211cf4e1236029aa03e6b6ce2a74c553" }
|
||||
ctor = "0.1"
|
||||
env_logger = "0.8"
|
||||
env_logger = "0.9"
|
||||
unindent = "0.1.7"
|
||||
|
@ -21,4 +21,4 @@ gpui = { path = "../gpui", features = ["test-support"] }
|
||||
serde_json = { version = "1.0", features = ["preserve_order"] }
|
||||
workspace = { path = "../workspace", features = ["test-support"] }
|
||||
ctor = "0.1"
|
||||
env_logger = "0.8"
|
||||
env_logger = "0.9"
|
||||
|
@ -47,6 +47,7 @@ similar = "1.3"
|
||||
smol = "1.2.5"
|
||||
thiserror = "1.0.29"
|
||||
toml = "0.5"
|
||||
rocksdb = "0.18"
|
||||
|
||||
[dev-dependencies]
|
||||
client = { path = "../client", features = ["test-support"] }
|
||||
|
161
crates/project/src/db.rs
Normal file
161
crates/project/src/db.rs
Normal file
@ -0,0 +1,161 @@
|
||||
use anyhow::Result;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub struct Db(DbStore);
|
||||
|
||||
enum DbStore {
|
||||
Null,
|
||||
Real(rocksdb::DB),
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
Fake {
|
||||
data: parking_lot::Mutex<collections::HashMap<Vec<u8>, Vec<u8>>>,
|
||||
},
|
||||
}
|
||||
|
||||
impl Db {
|
||||
/// Open or create a database at the given file path.
|
||||
pub fn open(path: PathBuf) -> Result<Arc<Self>> {
|
||||
let db = rocksdb::DB::open_default(&path)?;
|
||||
Ok(Arc::new(Self(DbStore::Real(db))))
|
||||
}
|
||||
|
||||
/// Open a null database that stores no data, for use as a fallback
|
||||
/// when there is an error opening the real database.
|
||||
pub fn null() -> Arc<Self> {
|
||||
Arc::new(Self(DbStore::Null))
|
||||
}
|
||||
|
||||
/// Open a fake database for testing.
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub fn open_fake() -> Arc<Self> {
|
||||
Arc::new(Self(DbStore::Fake {
|
||||
data: Default::default(),
|
||||
}))
|
||||
}
|
||||
|
||||
pub fn read<K, I>(&self, keys: I) -> Result<Vec<Option<Vec<u8>>>>
|
||||
where
|
||||
K: AsRef<[u8]>,
|
||||
I: IntoIterator<Item = K>,
|
||||
{
|
||||
match &self.0 {
|
||||
DbStore::Real(db) => db
|
||||
.multi_get(keys)
|
||||
.into_iter()
|
||||
.map(|e| e.map_err(Into::into))
|
||||
.collect(),
|
||||
|
||||
DbStore::Null => Ok(keys.into_iter().map(|_| None).collect()),
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
DbStore::Fake { data: db } => {
|
||||
let db = db.lock();
|
||||
Ok(keys
|
||||
.into_iter()
|
||||
.map(|key| db.get(key.as_ref()).cloned())
|
||||
.collect())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn delete<K, I>(&self, keys: I) -> Result<()>
|
||||
where
|
||||
K: AsRef<[u8]>,
|
||||
I: IntoIterator<Item = K>,
|
||||
{
|
||||
match &self.0 {
|
||||
DbStore::Real(db) => {
|
||||
let mut batch = rocksdb::WriteBatch::default();
|
||||
for key in keys {
|
||||
batch.delete(key);
|
||||
}
|
||||
db.write(batch)?;
|
||||
}
|
||||
|
||||
DbStore::Null => {}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
DbStore::Fake { data: db } => {
|
||||
let mut db = db.lock();
|
||||
for key in keys {
|
||||
db.remove(key.as_ref());
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn write<K, V, I>(&self, entries: I) -> Result<()>
|
||||
where
|
||||
K: AsRef<[u8]>,
|
||||
V: AsRef<[u8]>,
|
||||
I: IntoIterator<Item = (K, V)>,
|
||||
{
|
||||
match &self.0 {
|
||||
DbStore::Real(db) => {
|
||||
let mut batch = rocksdb::WriteBatch::default();
|
||||
for (key, value) in entries {
|
||||
batch.put(key, value);
|
||||
}
|
||||
db.write(batch)?;
|
||||
}
|
||||
|
||||
DbStore::Null => {}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
DbStore::Fake { data: db } => {
|
||||
let mut db = db.lock();
|
||||
for (key, value) in entries {
|
||||
db.insert(key.as_ref().into(), value.as_ref().into());
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use tempdir::TempDir;
|
||||
|
||||
#[gpui::test]
|
||||
fn test_db() {
|
||||
let dir = TempDir::new("db-test").unwrap();
|
||||
let fake_db = Db::open_fake();
|
||||
let real_db = Db::open(dir.path().join("test.db")).unwrap();
|
||||
|
||||
for db in [&real_db, &fake_db] {
|
||||
assert_eq!(
|
||||
db.read(["key-1", "key-2", "key-3"]).unwrap(),
|
||||
&[None, None, None]
|
||||
);
|
||||
|
||||
db.write([("key-1", "one"), ("key-3", "three")]).unwrap();
|
||||
assert_eq!(
|
||||
db.read(["key-1", "key-2", "key-3"]).unwrap(),
|
||||
&[
|
||||
Some("one".as_bytes().to_vec()),
|
||||
None,
|
||||
Some("three".as_bytes().to_vec())
|
||||
]
|
||||
);
|
||||
|
||||
db.delete(["key-3", "key-4"]).unwrap();
|
||||
assert_eq!(
|
||||
db.read(["key-1", "key-2", "key-3"]).unwrap(),
|
||||
&[Some("one".as_bytes().to_vec()), None, None,]
|
||||
);
|
||||
}
|
||||
|
||||
drop(real_db);
|
||||
|
||||
let real_db = Db::open(dir.path().join("test.db")).unwrap();
|
||||
assert_eq!(
|
||||
real_db.read(["key-1", "key-2", "key-3"]).unwrap(),
|
||||
&[Some("one".as_bytes().to_vec()), None, None,]
|
||||
);
|
||||
}
|
||||
}
|
@ -1,3 +1,4 @@
|
||||
mod db;
|
||||
pub mod fs;
|
||||
mod ignore;
|
||||
mod lsp_command;
|
||||
@ -25,6 +26,7 @@ use language::{
|
||||
use lsp::{DiagnosticSeverity, DiagnosticTag, DocumentHighlightKind, LanguageServer};
|
||||
use lsp_command::*;
|
||||
use parking_lot::Mutex;
|
||||
use postage::stream::Stream;
|
||||
use postage::watch;
|
||||
use rand::prelude::*;
|
||||
use search::SearchQuery;
|
||||
@ -52,6 +54,7 @@ use std::{
|
||||
use thiserror::Error;
|
||||
use util::{post_inc, ResultExt, TryFutureExt as _};
|
||||
|
||||
pub use db::Db;
|
||||
pub use fs::*;
|
||||
pub use worktree::*;
|
||||
|
||||
@ -59,6 +62,11 @@ pub trait Item: Entity {
|
||||
fn entry_id(&self, cx: &AppContext) -> Option<ProjectEntryId>;
|
||||
}
|
||||
|
||||
pub struct ProjectStore {
|
||||
db: Arc<Db>,
|
||||
projects: Vec<WeakModelHandle<Project>>,
|
||||
}
|
||||
|
||||
pub struct Project {
|
||||
worktrees: Vec<WorktreeHandle>,
|
||||
active_entry: Option<ProjectEntryId>,
|
||||
@ -75,6 +83,7 @@ pub struct Project {
|
||||
next_entry_id: Arc<AtomicUsize>,
|
||||
next_diagnostic_group_id: usize,
|
||||
user_store: ModelHandle<UserStore>,
|
||||
project_store: ModelHandle<ProjectStore>,
|
||||
fs: Arc<dyn Fs>,
|
||||
client_state: ProjectClientState,
|
||||
collaborators: HashMap<PeerId, Collaborator>,
|
||||
@ -90,6 +99,7 @@ pub struct Project {
|
||||
opened_buffers: HashMap<u64, OpenBuffer>,
|
||||
buffer_snapshots: HashMap<u64, Vec<(i32, TextBufferSnapshot)>>,
|
||||
nonce: u128,
|
||||
initialized_persistent_state: bool,
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
@ -120,6 +130,8 @@ enum ProjectClientState {
|
||||
is_shared: bool,
|
||||
remote_id_tx: watch::Sender<Option<u64>>,
|
||||
remote_id_rx: watch::Receiver<Option<u64>>,
|
||||
online_tx: watch::Sender<bool>,
|
||||
online_rx: watch::Receiver<bool>,
|
||||
_maintain_remote_id_task: Task<Option<()>>,
|
||||
},
|
||||
Remote {
|
||||
@ -273,8 +285,7 @@ impl Project {
|
||||
client.add_model_message_handler(Self::handle_update_language_server);
|
||||
client.add_model_message_handler(Self::handle_remove_collaborator);
|
||||
client.add_model_message_handler(Self::handle_join_project_request_cancelled);
|
||||
client.add_model_message_handler(Self::handle_register_worktree);
|
||||
client.add_model_message_handler(Self::handle_unregister_worktree);
|
||||
client.add_model_message_handler(Self::handle_update_project);
|
||||
client.add_model_message_handler(Self::handle_unregister_project);
|
||||
client.add_model_message_handler(Self::handle_project_unshared);
|
||||
client.add_model_message_handler(Self::handle_update_buffer_file);
|
||||
@ -305,34 +316,42 @@ impl Project {
|
||||
}
|
||||
|
||||
pub fn local(
|
||||
online: bool,
|
||||
client: Arc<Client>,
|
||||
user_store: ModelHandle<UserStore>,
|
||||
project_store: ModelHandle<ProjectStore>,
|
||||
languages: Arc<LanguageRegistry>,
|
||||
fs: Arc<dyn Fs>,
|
||||
cx: &mut MutableAppContext,
|
||||
) -> ModelHandle<Self> {
|
||||
cx.add_model(|cx: &mut ModelContext<Self>| {
|
||||
let (online_tx, online_rx) = watch::channel_with(online);
|
||||
let (remote_id_tx, remote_id_rx) = watch::channel();
|
||||
let _maintain_remote_id_task = cx.spawn_weak({
|
||||
let rpc = client.clone();
|
||||
move |this, mut cx| {
|
||||
async move {
|
||||
let mut status = rpc.status();
|
||||
while let Some(status) = status.next().await {
|
||||
if let Some(this) = this.upgrade(&cx) {
|
||||
if status.is_connected() {
|
||||
this.update(&mut cx, |this, cx| this.register(cx)).await?;
|
||||
} else {
|
||||
this.update(&mut cx, |this, cx| this.unregister(cx));
|
||||
}
|
||||
}
|
||||
let status_rx = client.clone().status();
|
||||
let online_rx = online_rx.clone();
|
||||
move |this, mut cx| async move {
|
||||
let mut stream = Stream::map(status_rx.clone(), drop)
|
||||
.merge(Stream::map(online_rx.clone(), drop));
|
||||
while stream.recv().await.is_some() {
|
||||
let this = this.upgrade(&cx)?;
|
||||
if status_rx.borrow().is_connected() && *online_rx.borrow() {
|
||||
this.update(&mut cx, |this, cx| this.register(cx))
|
||||
.await
|
||||
.log_err()?;
|
||||
} else {
|
||||
this.update(&mut cx, |this, cx| this.unregister(cx))
|
||||
.await
|
||||
.log_err();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
.log_err()
|
||||
None
|
||||
}
|
||||
});
|
||||
|
||||
let handle = cx.weak_handle();
|
||||
project_store.update(cx, |store, cx| store.add_project(handle, cx));
|
||||
|
||||
let (opened_buffer_tx, opened_buffer_rx) = watch::channel();
|
||||
Self {
|
||||
worktrees: Default::default(),
|
||||
@ -346,6 +365,8 @@ impl Project {
|
||||
is_shared: false,
|
||||
remote_id_tx,
|
||||
remote_id_rx,
|
||||
online_tx,
|
||||
online_rx,
|
||||
_maintain_remote_id_task,
|
||||
},
|
||||
opened_buffer: (Rc::new(RefCell::new(opened_buffer_tx)), opened_buffer_rx),
|
||||
@ -354,6 +375,7 @@ impl Project {
|
||||
languages,
|
||||
client,
|
||||
user_store,
|
||||
project_store,
|
||||
fs,
|
||||
next_entry_id: Default::default(),
|
||||
next_diagnostic_group_id: Default::default(),
|
||||
@ -364,6 +386,7 @@ impl Project {
|
||||
language_server_settings: Default::default(),
|
||||
next_language_server_id: 0,
|
||||
nonce: StdRng::from_entropy().gen(),
|
||||
initialized_persistent_state: false,
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -372,9 +395,10 @@ impl Project {
|
||||
remote_id: u64,
|
||||
client: Arc<Client>,
|
||||
user_store: ModelHandle<UserStore>,
|
||||
project_store: ModelHandle<ProjectStore>,
|
||||
languages: Arc<LanguageRegistry>,
|
||||
fs: Arc<dyn Fs>,
|
||||
cx: &mut AsyncAppContext,
|
||||
mut cx: AsyncAppContext,
|
||||
) -> Result<ModelHandle<Self>, JoinProjectError> {
|
||||
client.authenticate_and_connect(true, &cx).await?;
|
||||
|
||||
@ -414,6 +438,9 @@ impl Project {
|
||||
|
||||
let (opened_buffer_tx, opened_buffer_rx) = watch::channel();
|
||||
let this = cx.add_model(|cx: &mut ModelContext<Self>| {
|
||||
let handle = cx.weak_handle();
|
||||
project_store.update(cx, |store, cx| store.add_project(handle, cx));
|
||||
|
||||
let mut this = Self {
|
||||
worktrees: Vec::new(),
|
||||
loading_buffers: Default::default(),
|
||||
@ -424,6 +451,7 @@ impl Project {
|
||||
collaborators: Default::default(),
|
||||
languages,
|
||||
user_store: user_store.clone(),
|
||||
project_store,
|
||||
fs,
|
||||
next_entry_id: Default::default(),
|
||||
next_diagnostic_group_id: Default::default(),
|
||||
@ -471,6 +499,7 @@ impl Project {
|
||||
opened_buffers: Default::default(),
|
||||
buffer_snapshots: Default::default(),
|
||||
nonce: StdRng::from_entropy().gen(),
|
||||
initialized_persistent_state: false,
|
||||
};
|
||||
for worktree in worktrees {
|
||||
this.add_worktree(&worktree, cx);
|
||||
@ -484,15 +513,15 @@ impl Project {
|
||||
.map(|peer| peer.user_id)
|
||||
.collect();
|
||||
user_store
|
||||
.update(cx, |user_store, cx| user_store.get_users(user_ids, cx))
|
||||
.update(&mut cx, |user_store, cx| user_store.get_users(user_ids, cx))
|
||||
.await?;
|
||||
let mut collaborators = HashMap::default();
|
||||
for message in response.collaborators {
|
||||
let collaborator = Collaborator::from_proto(message, &user_store, cx).await?;
|
||||
let collaborator = Collaborator::from_proto(message, &user_store, &mut cx).await?;
|
||||
collaborators.insert(collaborator.peer_id, collaborator);
|
||||
}
|
||||
|
||||
this.update(cx, |this, _| {
|
||||
this.update(&mut cx, |this, _| {
|
||||
this.collaborators = collaborators;
|
||||
});
|
||||
|
||||
@ -509,7 +538,10 @@ impl Project {
|
||||
let http_client = client::test::FakeHttpClient::with_404_response();
|
||||
let client = client::Client::new(http_client.clone());
|
||||
let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http_client, cx));
|
||||
let project = cx.update(|cx| Project::local(client, user_store, languages, fs, cx));
|
||||
let project_store = cx.add_model(|_| ProjectStore::new(Db::open_fake()));
|
||||
let project = cx.update(|cx| {
|
||||
Project::local(true, client, user_store, project_store, languages, fs, cx)
|
||||
});
|
||||
for path in root_paths {
|
||||
let (tree, _) = project
|
||||
.update(cx, |project, cx| {
|
||||
@ -523,6 +555,53 @@ impl Project {
|
||||
project
|
||||
}
|
||||
|
||||
pub fn restore_state(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
|
||||
if self.is_remote() {
|
||||
return Task::ready(Ok(()));
|
||||
}
|
||||
|
||||
let db = self.project_store.read(cx).db.clone();
|
||||
let keys = self.db_keys_for_online_state(cx);
|
||||
let online_by_default = cx.global::<Settings>().projects_online_by_default;
|
||||
let read_online = cx.background().spawn(async move {
|
||||
let values = db.read(keys)?;
|
||||
anyhow::Ok(
|
||||
values
|
||||
.into_iter()
|
||||
.all(|e| e.map_or(online_by_default, |e| e == [true as u8])),
|
||||
)
|
||||
});
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
let online = read_online.await.log_err().unwrap_or(false);
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.initialized_persistent_state = true;
|
||||
if let ProjectClientState::Local { online_tx, .. } = &mut this.client_state {
|
||||
let mut online_tx = online_tx.borrow_mut();
|
||||
if *online_tx != online {
|
||||
*online_tx = online;
|
||||
drop(online_tx);
|
||||
this.metadata_changed(false, cx);
|
||||
}
|
||||
}
|
||||
});
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
fn persist_state(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
|
||||
if self.is_remote() || !self.initialized_persistent_state {
|
||||
return Task::ready(Ok(()));
|
||||
}
|
||||
|
||||
let db = self.project_store.read(cx).db.clone();
|
||||
let keys = self.db_keys_for_online_state(cx);
|
||||
let is_online = self.is_online();
|
||||
cx.background().spawn(async move {
|
||||
let value = &[is_online as u8];
|
||||
db.write(keys.into_iter().map(|key| (key, value)))
|
||||
})
|
||||
}
|
||||
|
||||
pub fn buffer_for_id(&self, remote_id: u64, cx: &AppContext) -> Option<ModelHandle<Buffer>> {
|
||||
self.opened_buffers
|
||||
.get(&remote_id)
|
||||
@ -541,6 +620,10 @@ impl Project {
|
||||
self.user_store.clone()
|
||||
}
|
||||
|
||||
pub fn project_store(&self) -> ModelHandle<ProjectStore> {
|
||||
self.project_store.clone()
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub fn check_invariants(&self, cx: &AppContext) {
|
||||
if self.is_local() {
|
||||
@ -598,53 +681,83 @@ impl Project {
|
||||
&self.fs
|
||||
}
|
||||
|
||||
fn unregister(&mut self, cx: &mut ModelContext<Self>) {
|
||||
pub fn set_online(&mut self, online: bool, cx: &mut ModelContext<Self>) {
|
||||
if let ProjectClientState::Local { online_tx, .. } = &mut self.client_state {
|
||||
let mut online_tx = online_tx.borrow_mut();
|
||||
if *online_tx != online {
|
||||
*online_tx = online;
|
||||
drop(online_tx);
|
||||
self.metadata_changed(true, cx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_online(&self) -> bool {
|
||||
match &self.client_state {
|
||||
ProjectClientState::Local { online_rx, .. } => *online_rx.borrow(),
|
||||
ProjectClientState::Remote { .. } => true,
|
||||
}
|
||||
}
|
||||
|
||||
fn unregister(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
|
||||
self.unshared(cx);
|
||||
for worktree in &self.worktrees {
|
||||
if let Some(worktree) = worktree.upgrade(cx) {
|
||||
worktree.update(cx, |worktree, _| {
|
||||
worktree.as_local_mut().unwrap().unregister();
|
||||
if let ProjectClientState::Local { remote_id_rx, .. } = &mut self.client_state {
|
||||
if let Some(remote_id) = *remote_id_rx.borrow() {
|
||||
let request = self.client.request(proto::UnregisterProject {
|
||||
project_id: remote_id,
|
||||
});
|
||||
return cx.spawn(|this, mut cx| async move {
|
||||
let response = request.await;
|
||||
|
||||
// Unregistering the project causes the server to send out a
|
||||
// contact update removing this project from the host's list
|
||||
// of online projects. Wait until this contact update has been
|
||||
// processed before clearing out this project's remote id, so
|
||||
// that there is no moment where this project appears in the
|
||||
// contact metadata and *also* has no remote id.
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.user_store()
|
||||
.update(cx, |store, _| store.contact_updates_done())
|
||||
})
|
||||
.await;
|
||||
|
||||
this.update(&mut cx, |this, cx| {
|
||||
if let ProjectClientState::Local { remote_id_tx, .. } =
|
||||
&mut this.client_state
|
||||
{
|
||||
*remote_id_tx.borrow_mut() = None;
|
||||
}
|
||||
this.subscriptions.clear();
|
||||
this.metadata_changed(false, cx);
|
||||
});
|
||||
response.map(drop)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if let ProjectClientState::Local { remote_id_tx, .. } = &mut self.client_state {
|
||||
*remote_id_tx.borrow_mut() = None;
|
||||
}
|
||||
|
||||
self.subscriptions.clear();
|
||||
Task::ready(Ok(()))
|
||||
}
|
||||
|
||||
fn register(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
|
||||
self.unregister(cx);
|
||||
if let ProjectClientState::Local { remote_id_rx, .. } = &self.client_state {
|
||||
if remote_id_rx.borrow().is_some() {
|
||||
return Task::ready(Ok(()));
|
||||
}
|
||||
}
|
||||
|
||||
let response = self.client.request(proto::RegisterProject {});
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
let remote_id = response.await?.project_id;
|
||||
|
||||
let mut registrations = Vec::new();
|
||||
this.update(&mut cx, |this, cx| {
|
||||
if let ProjectClientState::Local { remote_id_tx, .. } = &mut this.client_state {
|
||||
*remote_id_tx.borrow_mut() = Some(remote_id);
|
||||
}
|
||||
|
||||
this.metadata_changed(false, cx);
|
||||
cx.emit(Event::RemoteIdChanged(Some(remote_id)));
|
||||
|
||||
this.subscriptions
|
||||
.push(this.client.add_model_for_remote_entity(remote_id, cx));
|
||||
|
||||
for worktree in &this.worktrees {
|
||||
if let Some(worktree) = worktree.upgrade(cx) {
|
||||
registrations.push(worktree.update(cx, |worktree, cx| {
|
||||
let worktree = worktree.as_local_mut().unwrap();
|
||||
worktree.register(remote_id, cx)
|
||||
}));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
futures::future::try_join_all(registrations).await?;
|
||||
Ok(())
|
||||
Ok(())
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@ -702,6 +815,38 @@ impl Project {
|
||||
}
|
||||
}
|
||||
|
||||
fn metadata_changed(&mut self, persist: bool, cx: &mut ModelContext<Self>) {
|
||||
if let ProjectClientState::Local {
|
||||
remote_id_rx,
|
||||
online_rx,
|
||||
..
|
||||
} = &self.client_state
|
||||
{
|
||||
if let (Some(project_id), true) = (*remote_id_rx.borrow(), *online_rx.borrow()) {
|
||||
self.client
|
||||
.send(proto::UpdateProject {
|
||||
project_id,
|
||||
worktrees: self
|
||||
.worktrees
|
||||
.iter()
|
||||
.filter_map(|worktree| {
|
||||
worktree.upgrade(&cx).map(|worktree| {
|
||||
worktree.read(cx).as_local().unwrap().metadata_proto()
|
||||
})
|
||||
})
|
||||
.collect(),
|
||||
})
|
||||
.log_err();
|
||||
}
|
||||
|
||||
self.project_store.update(cx, |_, cx| cx.notify());
|
||||
if persist {
|
||||
self.persist_state(cx).detach_and_log_err(cx);
|
||||
}
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn collaborators(&self) -> &HashMap<PeerId, Collaborator> {
|
||||
&self.collaborators
|
||||
}
|
||||
@ -730,6 +875,28 @@ impl Project {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn worktree_root_names<'a>(&'a self, cx: &'a AppContext) -> impl Iterator<Item = &'a str> {
|
||||
self.visible_worktrees(cx)
|
||||
.map(|tree| tree.read(cx).root_name())
|
||||
}
|
||||
|
||||
fn db_keys_for_online_state(&self, cx: &AppContext) -> Vec<String> {
|
||||
self.worktrees
|
||||
.iter()
|
||||
.filter_map(|worktree| {
|
||||
let worktree = worktree.upgrade(&cx)?.read(cx);
|
||||
if worktree.is_visible() {
|
||||
Some(format!(
|
||||
"project-path-online:{}",
|
||||
worktree.as_local().unwrap().abs_path().to_string_lossy()
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
}
|
||||
|
||||
pub fn worktree_for_id(
|
||||
&self,
|
||||
id: WorktreeId,
|
||||
@ -757,6 +924,20 @@ impl Project {
|
||||
.map(|worktree| worktree.read(cx).id())
|
||||
}
|
||||
|
||||
pub fn contains_paths(&self, paths: &[PathBuf], cx: &AppContext) -> bool {
|
||||
paths.iter().all(|path| self.contains_path(&path, cx))
|
||||
}
|
||||
|
||||
pub fn contains_path(&self, path: &Path, cx: &AppContext) -> bool {
|
||||
for worktree in self.worktrees(cx) {
|
||||
let worktree = worktree.read(cx).as_local();
|
||||
if worktree.map_or(false, |w| w.contains_abs_path(path)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
pub fn create_entry(
|
||||
&mut self,
|
||||
project_path: impl Into<ProjectPath>,
|
||||
@ -3619,37 +3800,18 @@ impl Project {
|
||||
});
|
||||
let worktree = worktree?;
|
||||
|
||||
let remote_project_id = project.update(&mut cx, |project, cx| {
|
||||
let project_id = project.update(&mut cx, |project, cx| {
|
||||
project.add_worktree(&worktree, cx);
|
||||
project.remote_id()
|
||||
project.shared_remote_id()
|
||||
});
|
||||
|
||||
if let Some(project_id) = remote_project_id {
|
||||
// Because sharing is async, we may have *unshared* the project by the time it completes,
|
||||
// in which case we need to register the worktree instead.
|
||||
loop {
|
||||
if project.read_with(&cx, |project, _| project.is_shared()) {
|
||||
if worktree
|
||||
.update(&mut cx, |worktree, cx| {
|
||||
worktree.as_local_mut().unwrap().share(project_id, cx)
|
||||
})
|
||||
.await
|
||||
.is_ok()
|
||||
{
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
worktree
|
||||
.update(&mut cx, |worktree, cx| {
|
||||
worktree
|
||||
.as_local_mut()
|
||||
.unwrap()
|
||||
.register(project_id, cx)
|
||||
})
|
||||
.await?;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if let Some(project_id) = project_id {
|
||||
worktree
|
||||
.update(&mut cx, |worktree, cx| {
|
||||
worktree.as_local_mut().unwrap().share(project_id, cx)
|
||||
})
|
||||
.await
|
||||
.log_err();
|
||||
}
|
||||
|
||||
Ok(worktree)
|
||||
@ -3681,6 +3843,7 @@ impl Project {
|
||||
false
|
||||
}
|
||||
});
|
||||
self.metadata_changed(true, cx);
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
@ -3710,6 +3873,7 @@ impl Project {
|
||||
self.worktrees
|
||||
.push(WorktreeHandle::Weak(worktree.downgrade()));
|
||||
}
|
||||
self.metadata_changed(true, cx);
|
||||
cx.emit(Event::WorktreeAdded);
|
||||
cx.notify();
|
||||
}
|
||||
@ -3992,40 +4156,51 @@ impl Project {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn handle_register_worktree(
|
||||
async fn handle_update_project(
|
||||
this: ModelHandle<Self>,
|
||||
envelope: TypedEnvelope<proto::RegisterWorktree>,
|
||||
envelope: TypedEnvelope<proto::UpdateProject>,
|
||||
client: Arc<Client>,
|
||||
mut cx: AsyncAppContext,
|
||||
) -> Result<()> {
|
||||
this.update(&mut cx, |this, cx| {
|
||||
let remote_id = this.remote_id().ok_or_else(|| anyhow!("invalid project"))?;
|
||||
let replica_id = this.replica_id();
|
||||
let worktree = proto::Worktree {
|
||||
id: envelope.payload.worktree_id,
|
||||
root_name: envelope.payload.root_name,
|
||||
entries: Default::default(),
|
||||
diagnostic_summaries: Default::default(),
|
||||
visible: envelope.payload.visible,
|
||||
scan_id: 0,
|
||||
};
|
||||
let (worktree, load_task) =
|
||||
Worktree::remote(remote_id, replica_id, worktree, client, cx);
|
||||
this.add_worktree(&worktree, cx);
|
||||
load_task.detach();
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
let remote_id = this.remote_id().ok_or_else(|| anyhow!("invalid project"))?;
|
||||
|
||||
let mut old_worktrees_by_id = this
|
||||
.worktrees
|
||||
.drain(..)
|
||||
.filter_map(|worktree| {
|
||||
let worktree = worktree.upgrade(cx)?;
|
||||
Some((worktree.read(cx).id(), worktree))
|
||||
})
|
||||
.collect::<HashMap<_, _>>();
|
||||
|
||||
for worktree in envelope.payload.worktrees {
|
||||
if let Some(old_worktree) =
|
||||
old_worktrees_by_id.remove(&WorktreeId::from_proto(worktree.id))
|
||||
{
|
||||
this.worktrees.push(WorktreeHandle::Strong(old_worktree));
|
||||
} else {
|
||||
let worktree = proto::Worktree {
|
||||
id: worktree.id,
|
||||
root_name: worktree.root_name,
|
||||
entries: Default::default(),
|
||||
diagnostic_summaries: Default::default(),
|
||||
visible: worktree.visible,
|
||||
scan_id: 0,
|
||||
};
|
||||
let (worktree, load_task) =
|
||||
Worktree::remote(remote_id, replica_id, worktree, client.clone(), cx);
|
||||
this.add_worktree(&worktree, cx);
|
||||
load_task.detach();
|
||||
}
|
||||
}
|
||||
|
||||
this.metadata_changed(true, cx);
|
||||
for (id, _) in old_worktrees_by_id {
|
||||
cx.emit(Event::WorktreeRemoved(id));
|
||||
}
|
||||
|
||||
async fn handle_unregister_worktree(
|
||||
this: ModelHandle<Self>,
|
||||
envelope: TypedEnvelope<proto::UnregisterWorktree>,
|
||||
_: Arc<Client>,
|
||||
mut cx: AsyncAppContext,
|
||||
) -> Result<()> {
|
||||
this.update(&mut cx, |this, cx| {
|
||||
let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
|
||||
this.remove_worktree(worktree_id, cx);
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
@ -5132,6 +5307,49 @@ impl Project {
|
||||
}
|
||||
}
|
||||
|
||||
impl ProjectStore {
|
||||
pub fn new(db: Arc<Db>) -> Self {
|
||||
Self {
|
||||
db,
|
||||
projects: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn projects<'a>(
|
||||
&'a self,
|
||||
cx: &'a AppContext,
|
||||
) -> impl 'a + Iterator<Item = ModelHandle<Project>> {
|
||||
self.projects
|
||||
.iter()
|
||||
.filter_map(|project| project.upgrade(cx))
|
||||
}
|
||||
|
||||
fn add_project(&mut self, project: WeakModelHandle<Project>, cx: &mut ModelContext<Self>) {
|
||||
if let Err(ix) = self
|
||||
.projects
|
||||
.binary_search_by_key(&project.id(), WeakModelHandle::id)
|
||||
{
|
||||
self.projects.insert(ix, project);
|
||||
}
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn prune_projects(&mut self, cx: &mut ModelContext<Self>) {
|
||||
let mut did_change = false;
|
||||
self.projects.retain(|project| {
|
||||
if project.is_upgradable(cx) {
|
||||
true
|
||||
} else {
|
||||
did_change = true;
|
||||
false
|
||||
}
|
||||
});
|
||||
if did_change {
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl WorktreeHandle {
|
||||
pub fn upgrade(&self, cx: &AppContext) -> Option<ModelHandle<Worktree>> {
|
||||
match self {
|
||||
@ -5210,10 +5428,16 @@ impl<'a> Iterator for CandidateSetIter<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
impl Entity for ProjectStore {
|
||||
type Event = ();
|
||||
}
|
||||
|
||||
impl Entity for Project {
|
||||
type Event = Event;
|
||||
|
||||
fn release(&mut self, _: &mut gpui::MutableAppContext) {
|
||||
fn release(&mut self, cx: &mut gpui::MutableAppContext) {
|
||||
self.project_store.update(cx, ProjectStore::prune_projects);
|
||||
|
||||
match &self.client_state {
|
||||
ProjectClientState::Local { remote_id_rx, .. } => {
|
||||
if let Some(project_id) = *remote_id_rx.borrow() {
|
||||
|
@ -68,7 +68,6 @@ pub struct LocalWorktree {
|
||||
last_scan_state_rx: watch::Receiver<ScanState>,
|
||||
_background_scanner_task: Option<Task<()>>,
|
||||
poll_task: Option<Task<()>>,
|
||||
registration: Registration,
|
||||
share: Option<ShareState>,
|
||||
diagnostics: HashMap<Arc<Path>, Vec<DiagnosticEntry<PointUtf16>>>,
|
||||
diagnostic_summaries: TreeMap<PathKey, DiagnosticSummary>,
|
||||
@ -129,13 +128,6 @@ enum ScanState {
|
||||
Err(Arc<anyhow::Error>),
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq)]
|
||||
enum Registration {
|
||||
None,
|
||||
Pending,
|
||||
Done { project_id: u64 },
|
||||
}
|
||||
|
||||
struct ShareState {
|
||||
project_id: u64,
|
||||
snapshots_tx: Sender<LocalSnapshot>,
|
||||
@ -148,19 +140,6 @@ pub enum Event {
|
||||
|
||||
impl Entity for Worktree {
|
||||
type Event = Event;
|
||||
|
||||
fn release(&mut self, _: &mut MutableAppContext) {
|
||||
if let Some(worktree) = self.as_local_mut() {
|
||||
if let Registration::Done { project_id } = worktree.registration {
|
||||
let client = worktree.client.clone();
|
||||
let unregister_message = proto::UnregisterWorktree {
|
||||
project_id,
|
||||
worktree_id: worktree.id().to_proto(),
|
||||
};
|
||||
client.send(unregister_message).log_err();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Worktree {
|
||||
@ -486,7 +465,6 @@ impl LocalWorktree {
|
||||
background_snapshot: Arc::new(Mutex::new(snapshot)),
|
||||
last_scan_state_rx,
|
||||
_background_scanner_task: None,
|
||||
registration: Registration::None,
|
||||
share: None,
|
||||
poll_task: None,
|
||||
diagnostics: Default::default(),
|
||||
@ -608,6 +586,14 @@ impl LocalWorktree {
|
||||
self.snapshot.clone()
|
||||
}
|
||||
|
||||
pub fn metadata_proto(&self) -> proto::WorktreeMetadata {
|
||||
proto::WorktreeMetadata {
|
||||
id: self.id().to_proto(),
|
||||
root_name: self.root_name().to_string(),
|
||||
visible: self.visible,
|
||||
}
|
||||
}
|
||||
|
||||
fn load(&self, path: &Path, cx: &mut ModelContext<Worktree>) -> Task<Result<(File, String)>> {
|
||||
let handle = cx.handle();
|
||||
let path = Arc::from(path);
|
||||
@ -904,46 +890,7 @@ impl LocalWorktree {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn register(
|
||||
&mut self,
|
||||
project_id: u64,
|
||||
cx: &mut ModelContext<Worktree>,
|
||||
) -> Task<anyhow::Result<()>> {
|
||||
if self.registration != Registration::None {
|
||||
return Task::ready(Ok(()));
|
||||
}
|
||||
|
||||
self.registration = Registration::Pending;
|
||||
let client = self.client.clone();
|
||||
let register_message = proto::RegisterWorktree {
|
||||
project_id,
|
||||
worktree_id: self.id().to_proto(),
|
||||
root_name: self.root_name().to_string(),
|
||||
visible: self.visible,
|
||||
};
|
||||
let request = client.request(register_message);
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
let response = request.await;
|
||||
this.update(&mut cx, |this, _| {
|
||||
let worktree = this.as_local_mut().unwrap();
|
||||
match response {
|
||||
Ok(_) => {
|
||||
if worktree.registration == Registration::Pending {
|
||||
worktree.registration = Registration::Done { project_id };
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
Err(error) => {
|
||||
worktree.registration = Registration::None;
|
||||
Err(error)
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
pub fn share(&mut self, project_id: u64, cx: &mut ModelContext<Worktree>) -> Task<Result<()>> {
|
||||
let register = self.register(project_id, cx);
|
||||
let (share_tx, share_rx) = oneshot::channel();
|
||||
let (snapshots_to_send_tx, snapshots_to_send_rx) =
|
||||
smol::channel::unbounded::<LocalSnapshot>();
|
||||
@ -1048,7 +995,6 @@ impl LocalWorktree {
|
||||
}
|
||||
|
||||
cx.spawn_weak(|this, cx| async move {
|
||||
register.await?;
|
||||
if let Some(this) = this.upgrade(&cx) {
|
||||
this.read_with(&cx, |this, _| {
|
||||
let this = this.as_local().unwrap();
|
||||
@ -1061,11 +1007,6 @@ impl LocalWorktree {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn unregister(&mut self) {
|
||||
self.unshare();
|
||||
self.registration = Registration::None;
|
||||
}
|
||||
|
||||
pub fn unshare(&mut self) {
|
||||
self.share.take();
|
||||
}
|
||||
|
@ -31,7 +31,7 @@ tracing = { version = "0.1.34", features = ["log"] }
|
||||
zstd = "0.9"
|
||||
|
||||
[build-dependencies]
|
||||
prost-build = "0.8"
|
||||
prost-build = "0.9"
|
||||
|
||||
[dev-dependencies]
|
||||
collections = { path = "../collections", features = ["test-support"] }
|
||||
|
@ -35,8 +35,7 @@ message Envelope {
|
||||
OpenBufferForSymbol open_buffer_for_symbol = 28;
|
||||
OpenBufferForSymbolResponse open_buffer_for_symbol_response = 29;
|
||||
|
||||
RegisterWorktree register_worktree = 30;
|
||||
UnregisterWorktree unregister_worktree = 31;
|
||||
UpdateProject update_project = 30;
|
||||
UpdateWorktree update_worktree = 32;
|
||||
|
||||
CreateProjectEntry create_project_entry = 33;
|
||||
@ -129,6 +128,11 @@ message UnregisterProject {
|
||||
uint64 project_id = 1;
|
||||
}
|
||||
|
||||
message UpdateProject {
|
||||
uint64 project_id = 1;
|
||||
repeated WorktreeMetadata worktrees = 2;
|
||||
}
|
||||
|
||||
message RequestJoinProject {
|
||||
uint64 requester_id = 1;
|
||||
uint64 project_id = 2;
|
||||
@ -177,18 +181,6 @@ message LeaveProject {
|
||||
uint64 project_id = 1;
|
||||
}
|
||||
|
||||
message RegisterWorktree {
|
||||
uint64 project_id = 1;
|
||||
uint64 worktree_id = 2;
|
||||
string root_name = 3;
|
||||
bool visible = 4;
|
||||
}
|
||||
|
||||
message UnregisterWorktree {
|
||||
uint64 project_id = 1;
|
||||
uint64 worktree_id = 2;
|
||||
}
|
||||
|
||||
message UpdateWorktree {
|
||||
uint64 project_id = 1;
|
||||
uint64 worktree_id = 2;
|
||||
@ -934,3 +926,9 @@ message ProjectMetadata {
|
||||
repeated string worktree_root_names = 3;
|
||||
repeated uint64 guests = 4;
|
||||
}
|
||||
|
||||
message WorktreeMetadata {
|
||||
uint64 id = 1;
|
||||
string root_name = 2;
|
||||
bool visible = 3;
|
||||
}
|
||||
|
@ -132,7 +132,6 @@ messages!(
|
||||
(Ping, Foreground),
|
||||
(ProjectUnshared, Foreground),
|
||||
(RegisterProject, Foreground),
|
||||
(RegisterWorktree, Foreground),
|
||||
(ReloadBuffers, Foreground),
|
||||
(ReloadBuffersResponse, Foreground),
|
||||
(RemoveProjectCollaborator, Foreground),
|
||||
@ -151,7 +150,6 @@ messages!(
|
||||
(Test, Foreground),
|
||||
(Unfollow, Foreground),
|
||||
(UnregisterProject, Foreground),
|
||||
(UnregisterWorktree, Foreground),
|
||||
(UpdateBuffer, Foreground),
|
||||
(UpdateBufferFile, Foreground),
|
||||
(UpdateContacts, Foreground),
|
||||
@ -159,6 +157,7 @@ messages!(
|
||||
(UpdateFollowers, Foreground),
|
||||
(UpdateInviteInfo, Foreground),
|
||||
(UpdateLanguageServer, Foreground),
|
||||
(UpdateProject, Foreground),
|
||||
(UpdateWorktree, Foreground),
|
||||
);
|
||||
|
||||
@ -192,7 +191,6 @@ request_messages!(
|
||||
(PerformRename, PerformRenameResponse),
|
||||
(PrepareRename, PrepareRenameResponse),
|
||||
(RegisterProject, RegisterProjectResponse),
|
||||
(RegisterWorktree, Ack),
|
||||
(ReloadBuffers, ReloadBuffersResponse),
|
||||
(RequestContact, Ack),
|
||||
(RemoveContact, Ack),
|
||||
@ -202,6 +200,7 @@ request_messages!(
|
||||
(SearchProject, SearchProjectResponse),
|
||||
(SendChannelMessage, SendChannelMessageResponse),
|
||||
(Test, Test),
|
||||
(UnregisterProject, Ack),
|
||||
(UpdateBuffer, Ack),
|
||||
(UpdateWorktree, Ack),
|
||||
);
|
||||
@ -242,13 +241,12 @@ entity_messages!(
|
||||
StartLanguageServer,
|
||||
Unfollow,
|
||||
UnregisterProject,
|
||||
UnregisterWorktree,
|
||||
UpdateBuffer,
|
||||
UpdateBufferFile,
|
||||
UpdateDiagnosticSummary,
|
||||
UpdateFollowers,
|
||||
UpdateLanguageServer,
|
||||
RegisterWorktree,
|
||||
UpdateProject,
|
||||
UpdateWorktree,
|
||||
);
|
||||
|
||||
|
@ -6,4 +6,4 @@ pub use conn::Connection;
|
||||
pub use peer::*;
|
||||
mod macros;
|
||||
|
||||
pub const PROTOCOL_VERSION: u32 = 21;
|
||||
pub const PROTOCOL_VERSION: u32 = 22;
|
||||
|
@ -19,6 +19,7 @@ pub use keymap_file::{keymap_file_json_schema, KeymapFileContent};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Settings {
|
||||
pub projects_online_by_default: bool,
|
||||
pub buffer_font_family: FamilyId,
|
||||
pub buffer_font_size: f32,
|
||||
pub default_buffer_font_size: f32,
|
||||
@ -49,6 +50,8 @@ pub enum SoftWrap {
|
||||
|
||||
#[derive(Clone, Debug, Default, Deserialize, JsonSchema)]
|
||||
pub struct SettingsFileContent {
|
||||
#[serde(default)]
|
||||
pub projects_online_by_default: Option<bool>,
|
||||
#[serde(default)]
|
||||
pub buffer_font_family: Option<String>,
|
||||
#[serde(default)]
|
||||
@ -81,6 +84,7 @@ impl Settings {
|
||||
preferred_line_length: 80,
|
||||
language_overrides: Default::default(),
|
||||
format_on_save: true,
|
||||
projects_online_by_default: true,
|
||||
theme,
|
||||
})
|
||||
}
|
||||
@ -135,6 +139,7 @@ impl Settings {
|
||||
preferred_line_length: 80,
|
||||
format_on_save: true,
|
||||
language_overrides: Default::default(),
|
||||
projects_online_by_default: true,
|
||||
theme: gpui::fonts::with_font_cache(cx.font_cache().clone(), || Default::default()),
|
||||
}
|
||||
}
|
||||
@ -164,6 +169,10 @@ impl Settings {
|
||||
}
|
||||
}
|
||||
|
||||
merge(
|
||||
&mut self.projects_online_by_default,
|
||||
data.projects_online_by_default,
|
||||
);
|
||||
merge(&mut self.buffer_font_size, data.buffer_font_size);
|
||||
merge(&mut self.default_buffer_font_size, data.buffer_font_size);
|
||||
merge(&mut self.vim_mode, data.vim_mode);
|
||||
|
@ -13,5 +13,5 @@ log = { version = "0.4.16", features = ["kv_unstable_serde"] }
|
||||
|
||||
[dev-dependencies]
|
||||
ctor = "0.1"
|
||||
env_logger = "0.8"
|
||||
env_logger = "0.9"
|
||||
rand = "0.8.3"
|
||||
|
@ -28,5 +28,5 @@ collections = { path = "../collections", features = ["test-support"] }
|
||||
gpui = { path = "../gpui", features = ["test-support"] }
|
||||
util = { path = "../util", features = ["test-support"] }
|
||||
ctor = "0.1"
|
||||
env_logger = "0.8"
|
||||
env_logger = "0.9"
|
||||
rand = "0.8.3"
|
||||
|
@ -279,8 +279,9 @@ pub struct ContactsPanel {
|
||||
pub contact_username: ContainedText,
|
||||
pub contact_button: Interactive<IconButton>,
|
||||
pub contact_button_spacing: f32,
|
||||
pub disabled_contact_button: IconButton,
|
||||
pub disabled_button: IconButton,
|
||||
pub tree_branch: Interactive<TreeBranch>,
|
||||
pub private_button: Interactive<IconButton>,
|
||||
pub section_icon_size: f32,
|
||||
pub invite_row: Interactive<ContainedLabel>,
|
||||
}
|
||||
@ -318,7 +319,7 @@ pub struct Icon {
|
||||
pub path: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Deserialize, Default)]
|
||||
#[derive(Deserialize, Clone, Copy, Default)]
|
||||
pub struct IconButton {
|
||||
#[serde(flatten)]
|
||||
pub container: ContainerStyle,
|
||||
|
@ -85,9 +85,10 @@ impl WaitingRoom {
|
||||
project_id,
|
||||
app_state.client.clone(),
|
||||
app_state.user_store.clone(),
|
||||
app_state.project_store.clone(),
|
||||
app_state.languages.clone(),
|
||||
app_state.fs.clone(),
|
||||
&mut cx,
|
||||
cx.clone(),
|
||||
)
|
||||
.await;
|
||||
|
||||
|
@ -17,19 +17,20 @@ use gpui::{
|
||||
color::Color,
|
||||
elements::*,
|
||||
geometry::{rect::RectF, vector::vec2f, PathBuilder},
|
||||
impl_internal_actions,
|
||||
impl_actions, impl_internal_actions,
|
||||
json::{self, ToJson},
|
||||
platform::{CursorStyle, WindowOptions},
|
||||
AnyModelHandle, AnyViewHandle, AppContext, AsyncAppContext, Border, Entity, ImageData,
|
||||
ModelHandle, MutableAppContext, PathPromptOptions, PromptLevel, RenderContext, Task, View,
|
||||
ViewContext, ViewHandle, WeakViewHandle,
|
||||
ModelContext, ModelHandle, MutableAppContext, PathPromptOptions, PromptLevel, RenderContext,
|
||||
Task, View, ViewContext, ViewHandle, WeakViewHandle,
|
||||
};
|
||||
use language::LanguageRegistry;
|
||||
use log::error;
|
||||
pub use pane::*;
|
||||
pub use pane_group::*;
|
||||
use postage::prelude::Stream;
|
||||
use project::{fs, Fs, Project, ProjectEntryId, ProjectPath, Worktree, WorktreeId};
|
||||
use project::{fs, Fs, Project, ProjectEntryId, ProjectPath, ProjectStore, Worktree, WorktreeId};
|
||||
use serde::Deserialize;
|
||||
use settings::Settings;
|
||||
use sidebar::{Side, Sidebar, SidebarButtons, ToggleSidebarItem, ToggleSidebarItemFocus};
|
||||
use smallvec::SmallVec;
|
||||
@ -98,6 +99,12 @@ pub struct OpenPaths {
|
||||
pub paths: Vec<PathBuf>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Deserialize)]
|
||||
pub struct ToggleProjectOnline {
|
||||
#[serde(skip_deserializing)]
|
||||
pub project: Option<ModelHandle<Project>>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ToggleFollow(pub PeerId);
|
||||
|
||||
@ -116,6 +123,7 @@ impl_internal_actions!(
|
||||
RemoveFolderFromProject
|
||||
]
|
||||
);
|
||||
impl_actions!(workspace, [ToggleProjectOnline]);
|
||||
|
||||
pub fn init(app_state: Arc<AppState>, cx: &mut MutableAppContext) {
|
||||
pane::init(cx);
|
||||
@ -160,6 +168,7 @@ pub fn init(app_state: Arc<AppState>, cx: &mut MutableAppContext) {
|
||||
cx.add_async_action(Workspace::save_all);
|
||||
cx.add_action(Workspace::add_folder_to_project);
|
||||
cx.add_action(Workspace::remove_folder_from_project);
|
||||
cx.add_action(Workspace::toggle_project_online);
|
||||
cx.add_action(
|
||||
|workspace: &mut Workspace, _: &Unfollow, cx: &mut ViewContext<Workspace>| {
|
||||
let pane = workspace.active_pane().clone();
|
||||
@ -222,6 +231,7 @@ pub struct AppState {
|
||||
pub themes: Arc<ThemeRegistry>,
|
||||
pub client: Arc<client::Client>,
|
||||
pub user_store: ModelHandle<client::UserStore>,
|
||||
pub project_store: ModelHandle<ProjectStore>,
|
||||
pub fs: Arc<dyn fs::Fs>,
|
||||
pub build_window_options: fn() -> WindowOptions<'static>,
|
||||
pub initialize_workspace: fn(&mut Workspace, &Arc<AppState>, &mut ViewContext<Workspace>),
|
||||
@ -682,6 +692,7 @@ impl AppState {
|
||||
let languages = Arc::new(LanguageRegistry::test());
|
||||
let http_client = client::test::FakeHttpClient::with_404_response();
|
||||
let client = Client::new(http_client.clone());
|
||||
let project_store = cx.add_model(|_| ProjectStore::new(project::Db::open_fake()));
|
||||
let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http_client, cx));
|
||||
let themes = ThemeRegistry::new((), cx.font_cache().clone());
|
||||
Arc::new(Self {
|
||||
@ -690,6 +701,7 @@ impl AppState {
|
||||
fs,
|
||||
languages,
|
||||
user_store,
|
||||
project_store,
|
||||
initialize_workspace: |_, _, _| {},
|
||||
build_window_options: || Default::default(),
|
||||
})
|
||||
@ -837,10 +849,7 @@ impl Workspace {
|
||||
_observe_current_user,
|
||||
};
|
||||
this.project_remote_id_changed(this.project.read(cx).remote_id(), cx);
|
||||
|
||||
cx.defer(|this, cx| {
|
||||
this.update_window_title(cx);
|
||||
});
|
||||
cx.defer(|this, cx| this.update_window_title(cx));
|
||||
|
||||
this
|
||||
}
|
||||
@ -876,20 +885,6 @@ impl Workspace {
|
||||
self.project.read(cx).worktrees(cx)
|
||||
}
|
||||
|
||||
pub fn contains_paths(&self, paths: &[PathBuf], cx: &AppContext) -> bool {
|
||||
paths.iter().all(|path| self.contains_path(&path, cx))
|
||||
}
|
||||
|
||||
pub fn contains_path(&self, path: &Path, cx: &AppContext) -> bool {
|
||||
for worktree in self.worktrees(cx) {
|
||||
let worktree = worktree.read(cx).as_local();
|
||||
if worktree.map_or(false, |w| w.contains_abs_path(path)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
pub fn worktree_scans_complete(&self, cx: &AppContext) -> impl Future<Output = ()> + 'static {
|
||||
let futures = self
|
||||
.worktrees(cx)
|
||||
@ -1054,6 +1049,17 @@ impl Workspace {
|
||||
.update(cx, |project, cx| project.remove_worktree(*worktree_id, cx));
|
||||
}
|
||||
|
||||
fn toggle_project_online(&mut self, action: &ToggleProjectOnline, cx: &mut ViewContext<Self>) {
|
||||
let project = action
|
||||
.project
|
||||
.clone()
|
||||
.unwrap_or_else(|| self.project.clone());
|
||||
project.update(cx, |project, cx| {
|
||||
let public = !project.is_online();
|
||||
project.set_online(public, cx);
|
||||
});
|
||||
}
|
||||
|
||||
fn project_path_for_path(
|
||||
&self,
|
||||
abs_path: &Path,
|
||||
@ -1668,8 +1674,15 @@ impl Workspace {
|
||||
}
|
||||
|
||||
fn render_titlebar(&self, theme: &Theme, cx: &mut RenderContext<Self>) -> ElementBox {
|
||||
let project = &self.project.read(cx);
|
||||
let replica_id = project.replica_id();
|
||||
let mut worktree_root_names = String::new();
|
||||
self.worktree_root_names(&mut worktree_root_names, cx);
|
||||
for (i, name) in project.worktree_root_names(cx).enumerate() {
|
||||
if i > 0 {
|
||||
worktree_root_names.push_str(", ");
|
||||
}
|
||||
worktree_root_names.push_str(name);
|
||||
}
|
||||
|
||||
ConstrainedBox::new(
|
||||
Container::new(
|
||||
@ -1686,7 +1699,7 @@ impl Workspace {
|
||||
.with_children(self.render_collaborators(theme, cx))
|
||||
.with_children(self.render_current_user(
|
||||
self.user_store.read(cx).current_user().as_ref(),
|
||||
self.project.read(cx).replica_id(),
|
||||
replica_id,
|
||||
theme,
|
||||
cx,
|
||||
))
|
||||
@ -1714,6 +1727,7 @@ impl Workspace {
|
||||
|
||||
fn update_window_title(&mut self, cx: &mut ViewContext<Self>) {
|
||||
let mut title = String::new();
|
||||
let project = self.project().read(cx);
|
||||
if let Some(path) = self.active_item(cx).and_then(|item| item.project_path(cx)) {
|
||||
let filename = path
|
||||
.path
|
||||
@ -1721,8 +1735,7 @@ impl Workspace {
|
||||
.map(|s| s.to_string_lossy())
|
||||
.or_else(|| {
|
||||
Some(Cow::Borrowed(
|
||||
self.project()
|
||||
.read(cx)
|
||||
project
|
||||
.worktree_for_id(path.worktree_id, cx)?
|
||||
.read(cx)
|
||||
.root_name(),
|
||||
@ -1733,22 +1746,18 @@ impl Workspace {
|
||||
title.push_str(" — ");
|
||||
}
|
||||
}
|
||||
self.worktree_root_names(&mut title, cx);
|
||||
for (i, name) in project.worktree_root_names(cx).enumerate() {
|
||||
if i > 0 {
|
||||
title.push_str(", ");
|
||||
}
|
||||
title.push_str(name);
|
||||
}
|
||||
if title.is_empty() {
|
||||
title = "empty project".to_string();
|
||||
}
|
||||
cx.set_window_title(&title);
|
||||
}
|
||||
|
||||
fn worktree_root_names(&self, string: &mut String, cx: &mut MutableAppContext) {
|
||||
for (i, worktree) in self.project.read(cx).visible_worktrees(cx).enumerate() {
|
||||
if i != 0 {
|
||||
string.push_str(", ");
|
||||
}
|
||||
string.push_str(worktree.read(cx).root_name());
|
||||
}
|
||||
}
|
||||
|
||||
fn render_collaborators(&self, theme: &Theme, cx: &mut RenderContext<Self>) -> Vec<ElementBox> {
|
||||
let mut collaborators = self
|
||||
.project
|
||||
@ -2365,6 +2374,22 @@ fn open(_: &Open, cx: &mut MutableAppContext) {
|
||||
|
||||
pub struct WorkspaceCreated(WeakViewHandle<Workspace>);
|
||||
|
||||
pub fn activate_workspace_for_project(
|
||||
cx: &mut MutableAppContext,
|
||||
predicate: impl Fn(&mut Project, &mut ModelContext<Project>) -> bool,
|
||||
) -> Option<ViewHandle<Workspace>> {
|
||||
for window_id in cx.window_ids().collect::<Vec<_>>() {
|
||||
if let Some(workspace_handle) = cx.root_view::<Workspace>(window_id) {
|
||||
let project = workspace_handle.read(cx).project.clone();
|
||||
if project.update(cx, &predicate) {
|
||||
cx.activate_window(window_id);
|
||||
return Some(workspace_handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub fn open_paths(
|
||||
abs_paths: &[PathBuf],
|
||||
app_state: &Arc<AppState>,
|
||||
@ -2376,26 +2401,13 @@ pub fn open_paths(
|
||||
log::info!("open paths {:?}", abs_paths);
|
||||
|
||||
// Open paths in existing workspace if possible
|
||||
let mut existing = None;
|
||||
for window_id in cx.window_ids().collect::<Vec<_>>() {
|
||||
if let Some(workspace_handle) = cx.root_view::<Workspace>(window_id) {
|
||||
if workspace_handle.update(cx, |workspace, cx| {
|
||||
if workspace.contains_paths(abs_paths, cx.as_ref()) {
|
||||
cx.activate_window(window_id);
|
||||
existing = Some(workspace_handle.clone());
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
let existing =
|
||||
activate_workspace_for_project(cx, |project, cx| project.contains_paths(abs_paths, cx));
|
||||
|
||||
let app_state = app_state.clone();
|
||||
let abs_paths = abs_paths.to_vec();
|
||||
cx.spawn(|mut cx| async move {
|
||||
let mut new_project = None;
|
||||
let workspace = if let Some(existing) = existing {
|
||||
existing
|
||||
} else {
|
||||
@ -2405,16 +2417,17 @@ pub fn open_paths(
|
||||
.contains(&false);
|
||||
|
||||
cx.add_window((app_state.build_window_options)(), |cx| {
|
||||
let mut workspace = Workspace::new(
|
||||
Project::local(
|
||||
app_state.client.clone(),
|
||||
app_state.user_store.clone(),
|
||||
app_state.languages.clone(),
|
||||
app_state.fs.clone(),
|
||||
cx,
|
||||
),
|
||||
let project = Project::local(
|
||||
false,
|
||||
app_state.client.clone(),
|
||||
app_state.user_store.clone(),
|
||||
app_state.project_store.clone(),
|
||||
app_state.languages.clone(),
|
||||
app_state.fs.clone(),
|
||||
cx,
|
||||
);
|
||||
new_project = Some(project.clone());
|
||||
let mut workspace = Workspace::new(project, cx);
|
||||
(app_state.initialize_workspace)(&mut workspace, &app_state, cx);
|
||||
if contains_directory {
|
||||
workspace.toggle_sidebar_item(
|
||||
@ -2433,6 +2446,14 @@ pub fn open_paths(
|
||||
let items = workspace
|
||||
.update(&mut cx, |workspace, cx| workspace.open_paths(abs_paths, cx))
|
||||
.await;
|
||||
|
||||
if let Some(project) = new_project {
|
||||
project
|
||||
.update(&mut cx, |project, cx| project.restore_state(cx))
|
||||
.await
|
||||
.log_err();
|
||||
}
|
||||
|
||||
(workspace, items)
|
||||
})
|
||||
}
|
||||
@ -2463,8 +2484,10 @@ fn open_new(app_state: &Arc<AppState>, cx: &mut MutableAppContext) {
|
||||
let (window_id, workspace) = cx.add_window((app_state.build_window_options)(), |cx| {
|
||||
let mut workspace = Workspace::new(
|
||||
Project::local(
|
||||
false,
|
||||
app_state.client.clone(),
|
||||
app_state.user_store.clone(),
|
||||
app_state.project_store.clone(),
|
||||
app_state.languages.clone(),
|
||||
app_state.fs.clone(),
|
||||
cx,
|
||||
|
@ -59,7 +59,7 @@ chrono = "0.4"
|
||||
ctor = "0.1.20"
|
||||
dirs = "3.0"
|
||||
easy-parallel = "3.1.0"
|
||||
env_logger = "0.8"
|
||||
env_logger = "0.9"
|
||||
futures = "0.3"
|
||||
http-auth-basic = "0.1.3"
|
||||
ignore = "0.4"
|
||||
@ -108,7 +108,7 @@ client = { path = "../client", features = ["test-support"] }
|
||||
settings = { path = "../settings", features = ["test-support"] }
|
||||
util = { path = "../util", features = ["test-support"] }
|
||||
workspace = { path = "../workspace", features = ["test-support"] }
|
||||
env_logger = "0.8"
|
||||
env_logger = "0.9"
|
||||
serde_json = { version = "1.0", features = ["preserve_order"] }
|
||||
unindent = "0.1.7"
|
||||
|
||||
|
@ -23,7 +23,7 @@ use gpui::{executor::Background, App, AssetSource, AsyncAppContext, Task};
|
||||
use isahc::{config::Configurable, AsyncBody, Request};
|
||||
use log::LevelFilter;
|
||||
use parking_lot::Mutex;
|
||||
use project::Fs;
|
||||
use project::{Fs, ProjectStore};
|
||||
use serde_json::json;
|
||||
use settings::{self, KeymapFileContent, Settings, SettingsFileContent};
|
||||
use smol::process::Command;
|
||||
@ -48,9 +48,10 @@ use zed::{
|
||||
|
||||
fn main() {
|
||||
let http = http::client();
|
||||
let logs_dir_path = dirs::home_dir()
|
||||
.expect("could not find home dir")
|
||||
.join("Library/Logs/Zed");
|
||||
let home_dir = dirs::home_dir().expect("could not find home dir");
|
||||
let db_dir_path = home_dir.join("Library/Application Support/Zed");
|
||||
let logs_dir_path = home_dir.join("Library/Logs/Zed");
|
||||
fs::create_dir_all(&db_dir_path).expect("could not create database path");
|
||||
fs::create_dir_all(&logs_dir_path).expect("could not create logs path");
|
||||
init_logger(&logs_dir_path);
|
||||
|
||||
@ -59,6 +60,11 @@ fn main() {
|
||||
.or_else(|| app.platform().app_version().ok())
|
||||
.map_or("dev".to_string(), |v| v.to_string());
|
||||
init_panic_hook(logs_dir_path, app_version, http.clone(), app.background());
|
||||
let db = app.background().spawn(async move {
|
||||
project::Db::open(db_dir_path.join("zed.db"))
|
||||
.log_err()
|
||||
.unwrap_or(project::Db::null())
|
||||
});
|
||||
|
||||
load_embedded_fonts(&app);
|
||||
|
||||
@ -169,6 +175,7 @@ fn main() {
|
||||
search::init(cx);
|
||||
vim::init(cx);
|
||||
|
||||
let db = cx.background().block(db);
|
||||
let (settings_file, keymap_file) = cx.background().block(config_files).unwrap();
|
||||
let mut settings_rx = settings_from_files(
|
||||
default_settings,
|
||||
@ -204,11 +211,13 @@ fn main() {
|
||||
.detach();
|
||||
cx.set_global(settings);
|
||||
|
||||
let project_store = cx.add_model(|_| ProjectStore::new(db));
|
||||
let app_state = Arc::new(AppState {
|
||||
languages,
|
||||
themes,
|
||||
client: client.clone(),
|
||||
user_store,
|
||||
project_store,
|
||||
fs,
|
||||
build_window_options,
|
||||
initialize_workspace,
|
||||
|
@ -181,7 +181,12 @@ pub fn initialize_workspace(
|
||||
|
||||
let project_panel = ProjectPanel::new(workspace.project().clone(), cx);
|
||||
let contact_panel = cx.add_view(|cx| {
|
||||
ContactsPanel::new(app_state.user_store.clone(), workspace.weak_handle(), cx)
|
||||
ContactsPanel::new(
|
||||
app_state.user_store.clone(),
|
||||
app_state.project_store.clone(),
|
||||
workspace.weak_handle(),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
|
||||
workspace.left_sidebar().update(cx, |sidebar, cx| {
|
||||
@ -295,8 +300,10 @@ fn open_config_file(
|
||||
let (_, workspace) = cx.add_window((app_state.build_window_options)(), |cx| {
|
||||
let mut workspace = Workspace::new(
|
||||
Project::local(
|
||||
false,
|
||||
app_state.client.clone(),
|
||||
app_state.user_store.clone(),
|
||||
app_state.project_store.clone(),
|
||||
app_state.languages.clone(),
|
||||
app_state.fs.clone(),
|
||||
cx,
|
||||
|
@ -68,6 +68,12 @@ export default function contactsPanel(theme: Theme) {
|
||||
buttonWidth: 8,
|
||||
iconWidth: 8,
|
||||
},
|
||||
privateButton: {
|
||||
iconWidth: 8,
|
||||
color: iconColor(theme, "primary"),
|
||||
cornerRadius: 5,
|
||||
buttonWidth: 12,
|
||||
},
|
||||
rowHeight: 28,
|
||||
sectionIconSize: 8,
|
||||
headerRow: {
|
||||
@ -118,7 +124,7 @@ export default function contactsPanel(theme: Theme) {
|
||||
background: backgroundColor(theme, 100, "hovered"),
|
||||
},
|
||||
},
|
||||
disabledContactButton: {
|
||||
disabledButton: {
|
||||
...contactButton,
|
||||
background: backgroundColor(theme, 100),
|
||||
color: iconColor(theme, "muted"),
|
||||
|
Loading…
Reference in New Issue
Block a user