mirror of
https://github.com/zed-industries/zed.git
synced 2024-11-10 05:37:29 +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]]
|
[[package]]
|
||||||
name = "bindgen"
|
name = "bindgen"
|
||||||
version = "0.58.1"
|
version = "0.59.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0f8523b410d7187a43085e7e064416ea32ded16bd0a4e6fc025e21616d01258f"
|
checksum = "2bd2a9a458e8f4304c52c43ebb0cfbd520289f8379a52e329a38afda99bf8eb8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
"bitflags",
|
||||||
"cexpr",
|
"cexpr",
|
||||||
@ -503,7 +503,7 @@ dependencies = [
|
|||||||
"regex",
|
"regex",
|
||||||
"rustc-hash",
|
"rustc-hash",
|
||||||
"shlex",
|
"shlex",
|
||||||
"which 3.1.1",
|
"which",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -616,6 +616,17 @@ version = "1.1.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8"
|
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]]
|
[[package]]
|
||||||
name = "cache-padded"
|
name = "cache-padded"
|
||||||
version = "1.1.1"
|
version = "1.1.1"
|
||||||
@ -639,11 +650,11 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cexpr"
|
name = "cexpr"
|
||||||
version = "0.4.0"
|
version = "0.6.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f4aedb84272dbe89af497cf81375129abda4fc0a9e7c5d317498c15cc30c0d27"
|
checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"nom 5.1.2",
|
"nom 7.1.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -956,6 +967,7 @@ version = "0.1.0"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"client",
|
"client",
|
||||||
|
"collections",
|
||||||
"editor",
|
"editor",
|
||||||
"futures",
|
"futures",
|
||||||
"fuzzy",
|
"fuzzy",
|
||||||
@ -1444,9 +1456,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "env_logger"
|
name = "env_logger"
|
||||||
version = "0.8.3"
|
version = "0.9.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "17392a012ea30ef05a610aa97dfb49496e71c9f676b27879922ea5bdf60d9d3f"
|
checksum = "0b2cf0344971ee6c64c31be0d530793fba457d322dfec2810c453d0ef228f9c3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"atty",
|
"atty",
|
||||||
"humantime",
|
"humantime",
|
||||||
@ -1539,9 +1551,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fixedbitset"
|
name = "fixedbitset"
|
||||||
version = "0.2.0"
|
version = "0.4.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "37ab347416e802de484e4d03c7316c48f1ecb56574dfd4a46a80f173ce1de04d"
|
checksum = "279fb028e20b3c4c320317955b77c5e0c9701f05a1d309905d6fc702cdc5053e"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "flate2"
|
name = "flate2"
|
||||||
@ -1987,12 +1999,6 @@ dependencies = [
|
|||||||
"tracing",
|
"tracing",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "hashbrown"
|
|
||||||
version = "0.9.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hashbrown"
|
name = "hashbrown"
|
||||||
version = "0.11.2"
|
version = "0.11.2"
|
||||||
@ -2008,7 +2014,7 @@ version = "0.7.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7249a3129cbc1ffccd74857f81464a323a152173cdb134e0fd81bc803b29facf"
|
checksum = "7249a3129cbc1ffccd74857f81464a323a152173cdb134e0fd81bc803b29facf"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"hashbrown 0.11.2",
|
"hashbrown",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -2240,12 +2246,12 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "indexmap"
|
name = "indexmap"
|
||||||
version = "1.6.2"
|
version = "1.8.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "824845a0bf897a9042383849b02c1bc219c2383772efcd5c6f9766fa4b81aef3"
|
checksum = "e6012d540c5baa3589337a98ce73408de9b5a25ec9fc2c6fd6be8f0d39e0ca5a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"autocfg 1.0.1",
|
"autocfg 1.0.1",
|
||||||
"hashbrown 0.9.1",
|
"hashbrown",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -2481,9 +2487,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.119"
|
version = "0.2.126"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1bf2e165bb3457c8e098ea76f3e3bc9db55f87aa90d52d0e6be741470916aaa4"
|
checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libloading"
|
name = "libloading"
|
||||||
@ -2511,6 +2517,21 @@ dependencies = [
|
|||||||
"libc",
|
"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]]
|
[[package]]
|
||||||
name = "libz-sys"
|
name = "libz-sys"
|
||||||
version = "1.1.3"
|
version = "1.1.3"
|
||||||
@ -2721,6 +2742,12 @@ version = "0.3.16"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d"
|
checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "minimal-lexical"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "miniz_oxide"
|
name = "miniz_oxide"
|
||||||
version = "0.3.7"
|
version = "0.3.7"
|
||||||
@ -2847,16 +2874,6 @@ dependencies = [
|
|||||||
"winapi 0.3.9",
|
"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]]
|
[[package]]
|
||||||
name = "nom"
|
name = "nom"
|
||||||
version = "6.1.2"
|
version = "6.1.2"
|
||||||
@ -2870,6 +2887,16 @@ dependencies = [
|
|||||||
"version_check",
|
"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]]
|
[[package]]
|
||||||
name = "ntapi"
|
name = "ntapi"
|
||||||
version = "0.3.7"
|
version = "0.3.7"
|
||||||
@ -3190,9 +3217,9 @@ checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "petgraph"
|
name = "petgraph"
|
||||||
version = "0.5.1"
|
version = "0.6.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "467d164a6de56270bd7c4d070df81d07beace25012d5103ced4e9ff08d6afdb7"
|
checksum = "e6d5014253a1331579ce62aa67443b4a658c5e7dd03d4bc6d302b94474888143"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"fixedbitset",
|
"fixedbitset",
|
||||||
"indexmap",
|
"indexmap",
|
||||||
@ -3394,6 +3421,7 @@ dependencies = [
|
|||||||
"postage",
|
"postage",
|
||||||
"rand 0.8.3",
|
"rand 0.8.3",
|
||||||
"regex",
|
"regex",
|
||||||
|
"rocksdb",
|
||||||
"rpc",
|
"rpc",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
@ -3473,20 +3501,22 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "prost-build"
|
name = "prost-build"
|
||||||
version = "0.8.0"
|
version = "0.9.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "355f634b43cdd80724ee7848f95770e7e70eefa6dcf14fea676216573b8fd603"
|
checksum = "62941722fb675d463659e49c4f3fe1fe792ff24fe5bbaa9c08cd3b98a1c354f5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"heck 0.3.3",
|
"heck 0.3.3",
|
||||||
"itertools",
|
"itertools",
|
||||||
|
"lazy_static",
|
||||||
"log",
|
"log",
|
||||||
"multimap",
|
"multimap",
|
||||||
"petgraph",
|
"petgraph",
|
||||||
"prost 0.8.0",
|
"prost 0.9.0",
|
||||||
"prost-types",
|
"prost-types",
|
||||||
|
"regex",
|
||||||
"tempfile",
|
"tempfile",
|
||||||
"which 4.1.0",
|
"which",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -3517,12 +3547,12 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "prost-types"
|
name = "prost-types"
|
||||||
version = "0.8.0"
|
version = "0.9.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "603bbd6394701d13f3f25aada59c7de9d35a6a5887cfc156181234a44002771b"
|
checksum = "534b7a0e836e3c482d2693070f982e39e7611da9695d4d1f5a4b186b51faef0a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"prost 0.8.0",
|
"prost 0.9.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -3710,9 +3740,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex"
|
name = "regex"
|
||||||
version = "1.5.4"
|
version = "1.5.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461"
|
checksum = "d83f127d94bdbcda4c8cc2e50f6f84f4b611f69c902699ca385a39c3a75f9ff1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aho-corasick",
|
"aho-corasick",
|
||||||
"memchr",
|
"memchr",
|
||||||
@ -3730,9 +3760,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex-syntax"
|
name = "regex-syntax"
|
||||||
version = "0.6.25"
|
version = "0.6.26"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
|
checksum = "49b3de9ec5dc0a3417da371aab17d729997c15010e7fd24ff707773a33bddb64"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "remove_dir_all"
|
name = "remove_dir_all"
|
||||||
@ -3819,6 +3849,16 @@ dependencies = [
|
|||||||
"winapi 0.3.9",
|
"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]]
|
[[package]]
|
||||||
name = "roxmltree"
|
name = "roxmltree"
|
||||||
version = "0.14.1"
|
version = "0.14.1"
|
||||||
@ -5821,20 +5861,12 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "which"
|
name = "which"
|
||||||
version = "3.1.1"
|
version = "4.2.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d011071ae14a2f6671d0b74080ae0cd8ebf3a6f8c9589a2cd45f23126fe29724"
|
checksum = "5c4fb54e6113b6a8772ee41c3404fb0301ac79604489467e0a9ce1f3e97c24ae"
|
||||||
dependencies = [
|
|
||||||
"libc",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "which"
|
|
||||||
version = "4.1.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "b55551e42cbdf2ce2bedd2203d0cc08dba002c27510f86dab6d0ce304cba3dfe"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"either",
|
"either",
|
||||||
|
"lazy_static",
|
||||||
"libc",
|
"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,9 +67,14 @@ pub struct Client {
|
|||||||
peer: Arc<Peer>,
|
peer: Arc<Peer>,
|
||||||
http: Arc<dyn HttpClient>,
|
http: Arc<dyn HttpClient>,
|
||||||
state: RwLock<ClientState>,
|
state: RwLock<ClientState>,
|
||||||
authenticate:
|
|
||||||
|
#[cfg(any(test, feature = "test-support"))]
|
||||||
|
authenticate: RwLock<
|
||||||
Option<Box<dyn 'static + Send + Sync + Fn(&AsyncAppContext) -> Task<Result<Credentials>>>>,
|
Option<Box<dyn 'static + Send + Sync + Fn(&AsyncAppContext) -> Task<Result<Credentials>>>>,
|
||||||
establish_connection: Option<
|
>,
|
||||||
|
#[cfg(any(test, feature = "test-support"))]
|
||||||
|
establish_connection: RwLock<
|
||||||
|
Option<
|
||||||
Box<
|
Box<
|
||||||
dyn 'static
|
dyn 'static
|
||||||
+ Send
|
+ Send
|
||||||
@ -80,6 +85,7 @@ pub struct Client {
|
|||||||
) -> Task<Result<Connection, EstablishConnectionError>>,
|
) -> Task<Result<Connection, EstablishConnectionError>>,
|
||||||
>,
|
>,
|
||||||
>,
|
>,
|
||||||
|
>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
@ -235,8 +241,11 @@ impl Client {
|
|||||||
peer: Peer::new(),
|
peer: Peer::new(),
|
||||||
http,
|
http,
|
||||||
state: Default::default(),
|
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"))]
|
#[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
|
where
|
||||||
F: 'static + Send + Sync + Fn(&AsyncAppContext) -> Task<Result<Credentials>>,
|
F: 'static + Send + Sync + Fn(&AsyncAppContext) -> Task<Result<Credentials>>,
|
||||||
{
|
{
|
||||||
self.authenticate = Some(Box::new(authenticate));
|
*self.authenticate.write() = Some(Box::new(authenticate));
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(any(test, feature = "test-support"))]
|
#[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
|
where
|
||||||
F: 'static
|
F: 'static
|
||||||
+ Send
|
+ Send
|
||||||
+ Sync
|
+ Sync
|
||||||
+ Fn(&Credentials, &AsyncAppContext) -> Task<Result<Connection, EstablishConnectionError>>,
|
+ Fn(&Credentials, &AsyncAppContext) -> Task<Result<Connection, EstablishConnectionError>>,
|
||||||
{
|
{
|
||||||
self.establish_connection = Some(Box::new(connect));
|
*self.establish_connection.write() = Some(Box::new(connect));
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -755,11 +764,12 @@ impl Client {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn authenticate(self: &Arc<Self>, cx: &AsyncAppContext) -> Task<Result<Credentials>> {
|
fn authenticate(self: &Arc<Self>, cx: &AsyncAppContext) -> Task<Result<Credentials>> {
|
||||||
if let Some(callback) = self.authenticate.as_ref() {
|
#[cfg(any(test, feature = "test-support"))]
|
||||||
callback(cx)
|
if let Some(callback) = self.authenticate.read().as_ref() {
|
||||||
} else {
|
return callback(cx);
|
||||||
self.authenticate_with_browser(cx)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.authenticate_with_browser(cx)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn establish_connection(
|
fn establish_connection(
|
||||||
@ -767,11 +777,12 @@ impl Client {
|
|||||||
credentials: &Credentials,
|
credentials: &Credentials,
|
||||||
cx: &AsyncAppContext,
|
cx: &AsyncAppContext,
|
||||||
) -> Task<Result<Connection, EstablishConnectionError>> {
|
) -> Task<Result<Connection, EstablishConnectionError>> {
|
||||||
if let Some(callback) = self.establish_connection.as_ref() {
|
#[cfg(any(test, feature = "test-support"))]
|
||||||
callback(credentials, cx)
|
if let Some(callback) = self.establish_connection.read().as_ref() {
|
||||||
} else {
|
return callback(credentials, cx);
|
||||||
self.establish_websocket_connection(credentials, cx)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.establish_websocket_connection(credentials, cx)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn establish_websocket_connection(
|
fn establish_websocket_connection(
|
||||||
|
@ -28,7 +28,7 @@ struct FakeServerState {
|
|||||||
impl FakeServer {
|
impl FakeServer {
|
||||||
pub async fn for_client(
|
pub async fn for_client(
|
||||||
client_user_id: u64,
|
client_user_id: u64,
|
||||||
client: &mut Arc<Client>,
|
client: &Arc<Client>,
|
||||||
cx: &TestAppContext,
|
cx: &TestAppContext,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let server = Self {
|
let server = Self {
|
||||||
@ -38,8 +38,7 @@ impl FakeServer {
|
|||||||
executor: cx.foreground(),
|
executor: cx.foreground(),
|
||||||
};
|
};
|
||||||
|
|
||||||
Arc::get_mut(client)
|
client
|
||||||
.unwrap()
|
|
||||||
.override_authenticate({
|
.override_authenticate({
|
||||||
let state = Arc::downgrade(&server.state);
|
let state = Arc::downgrade(&server.state);
|
||||||
move |cx| {
|
move |cx| {
|
||||||
@ -179,6 +178,12 @@ impl FakeServer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Drop for FakeServer {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
self.disconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct FakeHttpClient {
|
pub struct FakeHttpClient {
|
||||||
handler: Box<
|
handler: Box<
|
||||||
dyn 'static
|
dyn 'static
|
||||||
|
@ -42,7 +42,7 @@ pub struct Contact {
|
|||||||
pub projects: Vec<ProjectMetadata>,
|
pub projects: Vec<ProjectMetadata>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
pub struct ProjectMetadata {
|
pub struct ProjectMetadata {
|
||||||
pub id: u64,
|
pub id: u64,
|
||||||
pub worktree_root_names: Vec<String>,
|
pub worktree_root_names: Vec<String>,
|
||||||
@ -99,6 +99,7 @@ impl Entity for UserStore {
|
|||||||
|
|
||||||
enum UpdateContacts {
|
enum UpdateContacts {
|
||||||
Update(proto::UpdateContacts),
|
Update(proto::UpdateContacts),
|
||||||
|
Wait(postage::barrier::Sender),
|
||||||
Clear(postage::barrier::Sender),
|
Clear(postage::barrier::Sender),
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -217,6 +218,10 @@ impl UserStore {
|
|||||||
cx: &mut ModelContext<Self>,
|
cx: &mut ModelContext<Self>,
|
||||||
) -> Task<Result<()>> {
|
) -> Task<Result<()>> {
|
||||||
match message {
|
match message {
|
||||||
|
UpdateContacts::Wait(barrier) => {
|
||||||
|
drop(barrier);
|
||||||
|
Task::ready(Ok(()))
|
||||||
|
}
|
||||||
UpdateContacts::Clear(barrier) => {
|
UpdateContacts::Clear(barrier) => {
|
||||||
self.contacts.clear();
|
self.contacts.clear();
|
||||||
self.incoming_contact_requests.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(
|
pub fn get_users(
|
||||||
&mut self,
|
&mut self,
|
||||||
mut user_ids: Vec<u64>,
|
mut user_ids: Vec<u64>,
|
||||||
|
@ -65,7 +65,7 @@ settings = { path = "../settings", features = ["test-support"] }
|
|||||||
theme = { path = "../theme" }
|
theme = { path = "../theme" }
|
||||||
workspace = { path = "../workspace", features = ["test-support"] }
|
workspace = { path = "../workspace", features = ["test-support"] }
|
||||||
ctor = "0.1"
|
ctor = "0.1"
|
||||||
env_logger = "0.8"
|
env_logger = "0.9"
|
||||||
util = { path = "../util" }
|
util = { path = "../util" }
|
||||||
lazy_static = "1.4"
|
lazy_static = "1.4"
|
||||||
serde_json = { version = "1.0", features = ["preserve_order"] }
|
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;
|
use tracing_subscriber::layer::SubscriberExt;
|
||||||
let rust_log = config.rust_log.clone()?;
|
let rust_log = config.rust_log.clone()?;
|
||||||
|
|
||||||
println!("HEY!");
|
|
||||||
LogTracer::init().log_err()?;
|
LogTracer::init().log_err()?;
|
||||||
|
|
||||||
let subscriber = tracing_subscriber::Registry::default()
|
let subscriber = tracing_subscriber::Registry::default()
|
||||||
|
@ -141,12 +141,11 @@ impl Server {
|
|||||||
server
|
server
|
||||||
.add_request_handler(Server::ping)
|
.add_request_handler(Server::ping)
|
||||||
.add_request_handler(Server::register_project)
|
.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_request_handler(Server::join_project)
|
||||||
.add_message_handler(Server::leave_project)
|
.add_message_handler(Server::leave_project)
|
||||||
.add_message_handler(Server::respond_to_join_project_request)
|
.add_message_handler(Server::respond_to_join_project_request)
|
||||||
.add_request_handler(Server::register_worktree)
|
.add_message_handler(Server::update_project)
|
||||||
.add_message_handler(Server::unregister_worktree)
|
|
||||||
.add_request_handler(Server::update_worktree)
|
.add_request_handler(Server::update_worktree)
|
||||||
.add_message_handler(Server::start_language_server)
|
.add_message_handler(Server::start_language_server)
|
||||||
.add_message_handler(Server::update_language_server)
|
.add_message_handler(Server::update_language_server)
|
||||||
@ -477,21 +476,22 @@ impl Server {
|
|||||||
request: TypedEnvelope<proto::RegisterProject>,
|
request: TypedEnvelope<proto::RegisterProject>,
|
||||||
response: Response<proto::RegisterProject>,
|
response: Response<proto::RegisterProject>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let user_id;
|
|
||||||
let project_id;
|
let project_id;
|
||||||
{
|
{
|
||||||
let mut state = self.store_mut().await;
|
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);
|
project_id = state.register_project(request.sender_id, user_id);
|
||||||
};
|
};
|
||||||
self.update_user_contacts(user_id).await?;
|
|
||||||
response.send(proto::RegisterProjectResponse { project_id })?;
|
response.send(proto::RegisterProjectResponse { project_id })?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn unregister_project(
|
async fn unregister_project(
|
||||||
self: Arc<Server>,
|
self: Arc<Server>,
|
||||||
request: TypedEnvelope<proto::UnregisterProject>,
|
request: TypedEnvelope<proto::UnregisterProject>,
|
||||||
|
response: Response<proto::UnregisterProject>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let (user_id, project) = {
|
let (user_id, project) = {
|
||||||
let mut state = self.store_mut().await;
|
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?;
|
self.update_user_contacts(user_id).await?;
|
||||||
|
response.send(proto::Ack {})?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -568,6 +574,7 @@ impl Server {
|
|||||||
response: Response<proto::JoinProject>,
|
response: Response<proto::JoinProject>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let project_id = request.payload.project_id;
|
let project_id = request.payload.project_id;
|
||||||
|
|
||||||
let host_user_id;
|
let host_user_id;
|
||||||
let guest_user_id;
|
let guest_user_id;
|
||||||
let host_connection_id;
|
let host_connection_id;
|
||||||
@ -768,63 +775,28 @@ impl Server {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn register_worktree(
|
async fn update_project(
|
||||||
self: Arc<Server>,
|
self: Arc<Server>,
|
||||||
request: TypedEnvelope<proto::RegisterWorktree>,
|
request: TypedEnvelope<proto::UpdateProject>,
|
||||||
response: Response<proto::RegisterWorktree>,
|
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let host_user_id;
|
let user_id;
|
||||||
{
|
{
|
||||||
let mut state = self.store_mut().await;
|
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
|
let guest_connection_ids = state
|
||||||
.read_project(request.payload.project_id, request.sender_id)?
|
.read_project(request.payload.project_id, request.sender_id)?
|
||||||
.guest_connection_ids();
|
.guest_connection_ids();
|
||||||
state.register_worktree(
|
state.update_project(
|
||||||
request.payload.project_id,
|
request.payload.project_id,
|
||||||
request.payload.worktree_id,
|
&request.payload.worktrees,
|
||||||
request.sender_id,
|
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| {
|
broadcast(request.sender_id, guest_connection_ids, |connection_id| {
|
||||||
self.peer
|
self.peer
|
||||||
.forward_send(request.sender_id, connection_id, request.payload.clone())
|
.forward_send(request.sender_id, connection_id, request.payload.clone())
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
self.update_user_contacts(host_user_id).await?;
|
self.update_user_contacts(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?;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -833,10 +805,11 @@ impl Server {
|
|||||||
request: TypedEnvelope<proto::UpdateWorktree>,
|
request: TypedEnvelope<proto::UpdateWorktree>,
|
||||||
response: Response<proto::UpdateWorktree>,
|
response: Response<proto::UpdateWorktree>,
|
||||||
) -> Result<()> {
|
) -> 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.sender_id,
|
||||||
request.payload.project_id,
|
request.payload.project_id,
|
||||||
request.payload.worktree_id,
|
request.payload.worktree_id,
|
||||||
|
&request.payload.root_name,
|
||||||
&request.payload.removed_entries,
|
&request.payload.removed_entries,
|
||||||
&request.payload.updated_entries,
|
&request.payload.updated_entries,
|
||||||
request.payload.scan_id,
|
request.payload.scan_id,
|
||||||
@ -846,6 +819,13 @@ impl Server {
|
|||||||
self.peer
|
self.peer
|
||||||
.forward_send(request.sender_id, connection_id, request.payload.clone())
|
.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 {})?;
|
response.send(proto::Ack {})?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -32,7 +32,7 @@ pub struct Project {
|
|||||||
#[serde(skip)]
|
#[serde(skip)]
|
||||||
pub join_requests: HashMap<UserId, Vec<Receipt<proto::JoinProject>>>,
|
pub join_requests: HashMap<UserId, Vec<Receipt<proto::JoinProject>>>,
|
||||||
pub active_replica_ids: HashSet<ReplicaId>,
|
pub active_replica_ids: HashSet<ReplicaId>,
|
||||||
pub worktrees: HashMap<u64, Worktree>,
|
pub worktrees: BTreeMap<u64, Worktree>,
|
||||||
pub language_servers: Vec<proto::LanguageServer>,
|
pub language_servers: Vec<proto::LanguageServer>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -312,19 +312,32 @@ impl Store {
|
|||||||
project_id
|
project_id
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn register_worktree(
|
pub fn update_project(
|
||||||
&mut self,
|
&mut self,
|
||||||
project_id: u64,
|
project_id: u64,
|
||||||
worktree_id: u64,
|
worktrees: &[proto::WorktreeMetadata],
|
||||||
connection_id: ConnectionId,
|
connection_id: ConnectionId,
|
||||||
worktree: Worktree,
|
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let project = self
|
let project = self
|
||||||
.projects
|
.projects
|
||||||
.get_mut(&project_id)
|
.get_mut(&project_id)
|
||||||
.ok_or_else(|| anyhow!("no such project"))?;
|
.ok_or_else(|| anyhow!("no such project"))?;
|
||||||
if project.host_connection_id == connection_id {
|
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(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
Err(anyhow!("no such project"))?
|
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(
|
pub fn update_diagnostic_summary(
|
||||||
&mut self,
|
&mut self,
|
||||||
project_id: u64,
|
project_id: u64,
|
||||||
@ -573,15 +565,15 @@ impl Store {
|
|||||||
connection_id: ConnectionId,
|
connection_id: ConnectionId,
|
||||||
project_id: u64,
|
project_id: u64,
|
||||||
worktree_id: u64,
|
worktree_id: u64,
|
||||||
|
worktree_root_name: &str,
|
||||||
removed_entries: &[u64],
|
removed_entries: &[u64],
|
||||||
updated_entries: &[proto::Entry],
|
updated_entries: &[proto::Entry],
|
||||||
scan_id: u64,
|
scan_id: u64,
|
||||||
) -> Result<Vec<ConnectionId>> {
|
) -> Result<(Vec<ConnectionId>, bool)> {
|
||||||
let project = self.write_project(project_id, connection_id)?;
|
let project = self.write_project(project_id, connection_id)?;
|
||||||
let worktree = project
|
let mut worktree = project.worktrees.entry(worktree_id).or_default();
|
||||||
.worktrees
|
let metadata_changed = worktree_root_name != worktree.root_name;
|
||||||
.get_mut(&worktree_id)
|
worktree.root_name = worktree_root_name.to_string();
|
||||||
.ok_or_else(|| anyhow!("no such worktree"))?;
|
|
||||||
for entry_id in removed_entries {
|
for entry_id in removed_entries {
|
||||||
worktree.entries.remove(&entry_id);
|
worktree.entries.remove(&entry_id);
|
||||||
}
|
}
|
||||||
@ -590,7 +582,7 @@ impl Store {
|
|||||||
}
|
}
|
||||||
worktree.scan_id = scan_id;
|
worktree.scan_id = scan_id;
|
||||||
let connection_ids = project.connection_ids();
|
let connection_ids = project.connection_ids();
|
||||||
Ok(connection_ids)
|
Ok((connection_ids, metadata_changed))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn project_connection_ids(
|
pub fn project_connection_ids(
|
||||||
|
@ -25,4 +25,4 @@ project = { path = "../project", features = ["test-support"] }
|
|||||||
serde_json = { version = "1.0", features = ["preserve_order"] }
|
serde_json = { version = "1.0", features = ["preserve_order"] }
|
||||||
workspace = { path = "../workspace", features = ["test-support"] }
|
workspace = { path = "../workspace", features = ["test-support"] }
|
||||||
ctor = "0.1"
|
ctor = "0.1"
|
||||||
env_logger = "0.8"
|
env_logger = "0.9"
|
||||||
|
@ -9,6 +9,7 @@ doctest = false
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
client = { path = "../client" }
|
client = { path = "../client" }
|
||||||
|
collections = { path = "../collections" }
|
||||||
editor = { path = "../editor" }
|
editor = { path = "../editor" }
|
||||||
fuzzy = { path = "../fuzzy" }
|
fuzzy = { path = "../fuzzy" }
|
||||||
gpui = { path = "../gpui" }
|
gpui = { path = "../gpui" }
|
||||||
|
@ -13,15 +13,16 @@ use gpui::{
|
|||||||
impl_actions, impl_internal_actions,
|
impl_actions, impl_internal_actions,
|
||||||
platform::CursorStyle,
|
platform::CursorStyle,
|
||||||
AppContext, ClipboardItem, Element, ElementBox, Entity, ModelHandle, MutableAppContext,
|
AppContext, ClipboardItem, Element, ElementBox, Entity, ModelHandle, MutableAppContext,
|
||||||
RenderContext, Subscription, View, ViewContext, ViewHandle, WeakViewHandle,
|
RenderContext, Subscription, View, ViewContext, ViewHandle, WeakModelHandle, WeakViewHandle,
|
||||||
};
|
};
|
||||||
use join_project_notification::JoinProjectNotification;
|
use join_project_notification::JoinProjectNotification;
|
||||||
use menu::{Confirm, SelectNext, SelectPrev};
|
use menu::{Confirm, SelectNext, SelectPrev};
|
||||||
|
use project::{Project, ProjectStore};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use settings::Settings;
|
use settings::Settings;
|
||||||
use std::sync::Arc;
|
use std::{ops::DerefMut, sync::Arc};
|
||||||
use theme::IconButton;
|
use theme::IconButton;
|
||||||
use workspace::{sidebar::SidebarItem, JoinProject, Workspace};
|
use workspace::{sidebar::SidebarItem, JoinProject, ToggleProjectOnline, Workspace};
|
||||||
|
|
||||||
impl_actions!(
|
impl_actions!(
|
||||||
contacts_panel,
|
contacts_panel,
|
||||||
@ -37,13 +38,14 @@ enum Section {
|
|||||||
Offline,
|
Offline,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone)]
|
||||||
enum ContactEntry {
|
enum ContactEntry {
|
||||||
Header(Section),
|
Header(Section),
|
||||||
IncomingRequest(Arc<User>),
|
IncomingRequest(Arc<User>),
|
||||||
OutgoingRequest(Arc<User>),
|
OutgoingRequest(Arc<User>),
|
||||||
Contact(Arc<Contact>),
|
Contact(Arc<Contact>),
|
||||||
ContactProject(Arc<Contact>, usize),
|
ContactProject(Arc<Contact>, usize, Option<WeakModelHandle<Project>>),
|
||||||
|
OfflineProject(WeakModelHandle<Project>),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@ -54,6 +56,7 @@ pub struct ContactsPanel {
|
|||||||
match_candidates: Vec<StringMatchCandidate>,
|
match_candidates: Vec<StringMatchCandidate>,
|
||||||
list_state: ListState,
|
list_state: ListState,
|
||||||
user_store: ModelHandle<UserStore>,
|
user_store: ModelHandle<UserStore>,
|
||||||
|
project_store: ModelHandle<ProjectStore>,
|
||||||
filter_editor: ViewHandle<Editor>,
|
filter_editor: ViewHandle<Editor>,
|
||||||
collapsed_sections: Vec<Section>,
|
collapsed_sections: Vec<Section>,
|
||||||
selection: Option<usize>,
|
selection: Option<usize>,
|
||||||
@ -89,6 +92,7 @@ pub fn init(cx: &mut MutableAppContext) {
|
|||||||
impl ContactsPanel {
|
impl ContactsPanel {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
user_store: ModelHandle<UserStore>,
|
user_store: ModelHandle<UserStore>,
|
||||||
|
project_store: ModelHandle<ProjectStore>,
|
||||||
workspace: WeakViewHandle<Workspace>,
|
workspace: WeakViewHandle<Workspace>,
|
||||||
cx: &mut ViewContext<Self>,
|
cx: &mut ViewContext<Self>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
@ -148,23 +152,17 @@ impl ContactsPanel {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
cx.subscribe(&user_store, {
|
cx.observe(&project_store, |this, _, cx| this.update_entries(cx))
|
||||||
let user_store = user_store.downgrade();
|
.detach();
|
||||||
move |_, _, event, cx| {
|
|
||||||
if let Some((workspace, user_store)) =
|
cx.subscribe(&user_store, move |_, user_store, event, cx| {
|
||||||
workspace.upgrade(cx).zip(user_store.upgrade(cx))
|
if let Some(workspace) = workspace.upgrade(cx) {
|
||||||
{
|
|
||||||
workspace.update(cx, |workspace, cx| match event {
|
workspace.update(cx, |workspace, cx| match event {
|
||||||
client::Event::Contact { user, kind } => match kind {
|
client::Event::Contact { user, kind } => match kind {
|
||||||
ContactEventKind::Requested | ContactEventKind::Accepted => workspace
|
ContactEventKind::Requested | ContactEventKind::Accepted => workspace
|
||||||
.show_notification(user.id as usize, cx, |cx| {
|
.show_notification(user.id as usize, cx, |cx| {
|
||||||
cx.add_view(|cx| {
|
cx.add_view(|cx| {
|
||||||
ContactNotification::new(
|
ContactNotification::new(user.clone(), *kind, user_store, cx)
|
||||||
user.clone(),
|
|
||||||
*kind,
|
|
||||||
user_store,
|
|
||||||
cx,
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
}),
|
}),
|
||||||
_ => {}
|
_ => {}
|
||||||
@ -176,28 +174,29 @@ impl ContactsPanel {
|
|||||||
if let client::Event::ShowContacts = event {
|
if let client::Event::ShowContacts = event {
|
||||||
cx.emit(Event::Activate);
|
cx.emit(Event::Activate);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.detach();
|
.detach();
|
||||||
|
|
||||||
let mut this = Self {
|
let list_state = ListState::new(0, Orientation::Top, 1000., cx, move |this, ix, cx| {
|
||||||
list_state: ListState::new(0, Orientation::Top, 1000., cx, {
|
|
||||||
move |this, ix, cx| {
|
|
||||||
let theme = cx.global::<Settings>().theme.clone();
|
let theme = cx.global::<Settings>().theme.clone();
|
||||||
let theme = &theme.contacts_panel;
|
let current_user_id = this.user_store.read(cx).current_user().map(|user| user.id);
|
||||||
let current_user_id =
|
|
||||||
this.user_store.read(cx).current_user().map(|user| user.id);
|
|
||||||
let is_selected = this.selection == Some(ix);
|
let is_selected = this.selection == Some(ix);
|
||||||
|
|
||||||
match &this.entries[ix] {
|
match &this.entries[ix] {
|
||||||
ContactEntry::Header(section) => {
|
ContactEntry::Header(section) => {
|
||||||
let is_collapsed = this.collapsed_sections.contains(§ion);
|
let is_collapsed = this.collapsed_sections.contains(§ion);
|
||||||
Self::render_header(*section, theme, is_selected, is_collapsed, cx)
|
Self::render_header(
|
||||||
|
*section,
|
||||||
|
&theme.contacts_panel,
|
||||||
|
is_selected,
|
||||||
|
is_collapsed,
|
||||||
|
cx,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
ContactEntry::IncomingRequest(user) => Self::render_contact_request(
|
ContactEntry::IncomingRequest(user) => Self::render_contact_request(
|
||||||
user.clone(),
|
user.clone(),
|
||||||
this.user_store.clone(),
|
this.user_store.clone(),
|
||||||
theme,
|
&theme.contacts_panel,
|
||||||
true,
|
true,
|
||||||
is_selected,
|
is_selected,
|
||||||
cx,
|
cx,
|
||||||
@ -205,36 +204,47 @@ impl ContactsPanel {
|
|||||||
ContactEntry::OutgoingRequest(user) => Self::render_contact_request(
|
ContactEntry::OutgoingRequest(user) => Self::render_contact_request(
|
||||||
user.clone(),
|
user.clone(),
|
||||||
this.user_store.clone(),
|
this.user_store.clone(),
|
||||||
theme,
|
&theme.contacts_panel,
|
||||||
false,
|
false,
|
||||||
is_selected,
|
is_selected,
|
||||||
cx,
|
cx,
|
||||||
),
|
),
|
||||||
ContactEntry::Contact(contact) => {
|
ContactEntry::Contact(contact) => {
|
||||||
Self::render_contact(contact.clone(), theme, is_selected)
|
Self::render_contact(&contact.user, &theme.contacts_panel, is_selected)
|
||||||
}
|
}
|
||||||
ContactEntry::ContactProject(contact, project_ix) => {
|
ContactEntry::ContactProject(contact, project_ix, open_project) => {
|
||||||
let is_last_project_for_contact =
|
let is_last_project_for_contact =
|
||||||
this.entries.get(ix + 1).map_or(true, |next| {
|
this.entries.get(ix + 1).map_or(true, |next| {
|
||||||
if let ContactEntry::ContactProject(next_contact, _) = next {
|
if let ContactEntry::ContactProject(next_contact, _, _) = next {
|
||||||
next_contact.user.id != contact.user.id
|
next_contact.user.id != contact.user.id
|
||||||
} else {
|
} else {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
Self::render_contact_project(
|
Self::render_project(
|
||||||
contact.clone(),
|
contact.clone(),
|
||||||
current_user_id,
|
current_user_id,
|
||||||
*project_ix,
|
*project_ix,
|
||||||
theme,
|
open_project.clone(),
|
||||||
|
&theme.contacts_panel,
|
||||||
|
&theme.tooltip,
|
||||||
is_last_project_for_contact,
|
is_last_project_for_contact,
|
||||||
is_selected,
|
is_selected,
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
ContactEntry::OfflineProject(project) => Self::render_offline_project(
|
||||||
|
project.clone(),
|
||||||
|
&theme.contacts_panel,
|
||||||
|
&theme.tooltip,
|
||||||
|
is_selected,
|
||||||
|
cx,
|
||||||
|
),
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
}),
|
|
||||||
|
let mut this = Self {
|
||||||
|
list_state,
|
||||||
selection: None,
|
selection: None,
|
||||||
collapsed_sections: Default::default(),
|
collapsed_sections: Default::default(),
|
||||||
entries: Default::default(),
|
entries: Default::default(),
|
||||||
@ -242,6 +252,7 @@ impl ContactsPanel {
|
|||||||
filter_editor,
|
filter_editor,
|
||||||
_maintain_contacts: cx.observe(&user_store, |this, _, cx| this.update_entries(cx)),
|
_maintain_contacts: cx.observe(&user_store, |this, _, cx| this.update_entries(cx)),
|
||||||
user_store,
|
user_store,
|
||||||
|
project_store,
|
||||||
};
|
};
|
||||||
this.update_entries(cx);
|
this.update_entries(cx);
|
||||||
this
|
this
|
||||||
@ -300,13 +311,9 @@ impl ContactsPanel {
|
|||||||
.boxed()
|
.boxed()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_contact(
|
fn render_contact(user: &User, theme: &theme::ContactsPanel, is_selected: bool) -> ElementBox {
|
||||||
contact: Arc<Contact>,
|
|
||||||
theme: &theme::ContactsPanel,
|
|
||||||
is_selected: bool,
|
|
||||||
) -> ElementBox {
|
|
||||||
Flex::row()
|
Flex::row()
|
||||||
.with_children(contact.user.avatar.clone().map(|avatar| {
|
.with_children(user.avatar.clone().map(|avatar| {
|
||||||
Image::new(avatar)
|
Image::new(avatar)
|
||||||
.with_style(theme.contact_avatar)
|
.with_style(theme.contact_avatar)
|
||||||
.aligned()
|
.aligned()
|
||||||
@ -315,7 +322,7 @@ impl ContactsPanel {
|
|||||||
}))
|
}))
|
||||||
.with_child(
|
.with_child(
|
||||||
Label::new(
|
Label::new(
|
||||||
contact.user.github_login.clone(),
|
user.github_login.clone(),
|
||||||
theme.contact_username.text.clone(),
|
theme.contact_username.text.clone(),
|
||||||
)
|
)
|
||||||
.contained()
|
.contained()
|
||||||
@ -332,11 +339,13 @@ impl ContactsPanel {
|
|||||||
.boxed()
|
.boxed()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_contact_project(
|
fn render_project(
|
||||||
contact: Arc<Contact>,
|
contact: Arc<Contact>,
|
||||||
current_user_id: Option<u64>,
|
current_user_id: Option<u64>,
|
||||||
project_index: usize,
|
project_index: usize,
|
||||||
|
open_project: Option<WeakModelHandle<Project>>,
|
||||||
theme: &theme::ContactsPanel,
|
theme: &theme::ContactsPanel,
|
||||||
|
tooltip_style: &TooltipStyle,
|
||||||
is_last_project: bool,
|
is_last_project: bool,
|
||||||
is_selected: bool,
|
is_selected: bool,
|
||||||
cx: &mut RenderContext<Self>,
|
cx: &mut RenderContext<Self>,
|
||||||
@ -344,6 +353,7 @@ impl ContactsPanel {
|
|||||||
let project = &contact.projects[project_index];
|
let project = &contact.projects[project_index];
|
||||||
let project_id = project.id;
|
let project_id = project.id;
|
||||||
let is_host = Some(contact.user.id) == current_user_id;
|
let is_host = Some(contact.user.id) == current_user_id;
|
||||||
|
let open_project = open_project.and_then(|p| p.upgrade(cx.deref_mut()));
|
||||||
|
|
||||||
let font_cache = cx.font_cache();
|
let font_cache = cx.font_cache();
|
||||||
let host_avatar_height = theme
|
let host_avatar_height = theme
|
||||||
@ -358,15 +368,17 @@ impl ContactsPanel {
|
|||||||
let baseline_offset =
|
let baseline_offset =
|
||||||
row.name.text.baseline_offset(font_cache) + (theme.row_height - line_height) / 2.;
|
row.name.text.baseline_offset(font_cache) + (theme.row_height - line_height) / 2.;
|
||||||
|
|
||||||
MouseEventHandler::new::<JoinProject, _, _>(project_id as usize, cx, |mouse_state, _| {
|
MouseEventHandler::new::<JoinProject, _, _>(project_id as usize, cx, |mouse_state, cx| {
|
||||||
let tree_branch = *tree_branch.style_for(mouse_state, is_selected);
|
let tree_branch = *tree_branch.style_for(mouse_state, is_selected);
|
||||||
let row = theme.project_row.style_for(mouse_state, is_selected);
|
let row = theme.project_row.style_for(mouse_state, is_selected);
|
||||||
|
|
||||||
Flex::row()
|
Flex::row()
|
||||||
|
.with_child(
|
||||||
|
Stack::new()
|
||||||
.with_child(
|
.with_child(
|
||||||
Canvas::new(move |bounds, _, cx| {
|
Canvas::new(move |bounds, _, cx| {
|
||||||
let start_x =
|
let start_x = bounds.min_x() + (bounds.width() / 2.)
|
||||||
bounds.min_x() + (bounds.width() / 2.) - (tree_branch.width / 2.);
|
- (tree_branch.width / 2.);
|
||||||
let end_x = bounds.max_x();
|
let end_x = bounds.max_x();
|
||||||
let start_y = bounds.min_y();
|
let start_y = bounds.min_y();
|
||||||
let end_y = bounds.min_y() + baseline_offset - (cap_height / 2.);
|
let end_y = bounds.min_y() + baseline_offset - (cap_height / 2.);
|
||||||
@ -397,6 +409,53 @@ impl ContactsPanel {
|
|||||||
corner_radius: 0.,
|
corner_radius: 0.,
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
|
.boxed(),
|
||||||
|
)
|
||||||
|
.with_children(open_project.and_then(|open_project| {
|
||||||
|
let is_going_offline = !open_project.read(cx).is_online();
|
||||||
|
if !mouse_state.hovered && !is_going_offline {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let button = MouseEventHandler::new::<ToggleProjectOnline, _, _>(
|
||||||
|
project_id as usize,
|
||||||
|
cx,
|
||||||
|
|state, _| {
|
||||||
|
let mut icon_style =
|
||||||
|
*theme.private_button.style_for(state, false);
|
||||||
|
icon_style.container.background_color =
|
||||||
|
row.container.background_color;
|
||||||
|
if is_going_offline {
|
||||||
|
icon_style.color = theme.disabled_button.color;
|
||||||
|
}
|
||||||
|
render_icon_button(&icon_style, "icons/lock-8.svg")
|
||||||
|
.aligned()
|
||||||
|
.boxed()
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
if is_going_offline {
|
||||||
|
Some(button.boxed())
|
||||||
|
} else {
|
||||||
|
Some(
|
||||||
|
button
|
||||||
|
.with_cursor_style(CursorStyle::PointingHand)
|
||||||
|
.on_click(move |_, _, cx| {
|
||||||
|
cx.dispatch_action(ToggleProjectOnline {
|
||||||
|
project: Some(open_project.clone()),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.with_tooltip(
|
||||||
|
project_id as usize,
|
||||||
|
"Take project offline".to_string(),
|
||||||
|
None,
|
||||||
|
tooltip_style.clone(),
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
.boxed(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}))
|
||||||
.constrained()
|
.constrained()
|
||||||
.with_width(host_avatar_height)
|
.with_width(host_avatar_height)
|
||||||
.boxed(),
|
.boxed(),
|
||||||
@ -446,6 +505,94 @@ impl ContactsPanel {
|
|||||||
.boxed()
|
.boxed()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn render_offline_project(
|
||||||
|
project: WeakModelHandle<Project>,
|
||||||
|
theme: &theme::ContactsPanel,
|
||||||
|
tooltip_style: &TooltipStyle,
|
||||||
|
is_selected: bool,
|
||||||
|
cx: &mut RenderContext<Self>,
|
||||||
|
) -> ElementBox {
|
||||||
|
let project = if let Some(project) = project.upgrade(cx.deref_mut()) {
|
||||||
|
project
|
||||||
|
} else {
|
||||||
|
return Empty::new().boxed();
|
||||||
|
};
|
||||||
|
|
||||||
|
let host_avatar_height = theme
|
||||||
|
.contact_avatar
|
||||||
|
.width
|
||||||
|
.or(theme.contact_avatar.height)
|
||||||
|
.unwrap_or(0.);
|
||||||
|
|
||||||
|
enum LocalProject {}
|
||||||
|
enum ToggleOnline {}
|
||||||
|
|
||||||
|
let project_id = project.id();
|
||||||
|
MouseEventHandler::new::<LocalProject, _, _>(project_id, cx, |state, cx| {
|
||||||
|
let row = theme.project_row.style_for(state, is_selected);
|
||||||
|
let mut worktree_root_names = String::new();
|
||||||
|
let project_ = project.read(cx);
|
||||||
|
let is_going_online = project_.is_online();
|
||||||
|
for tree in project_.visible_worktrees(cx) {
|
||||||
|
if !worktree_root_names.is_empty() {
|
||||||
|
worktree_root_names.push_str(", ");
|
||||||
|
}
|
||||||
|
worktree_root_names.push_str(tree.read(cx).root_name());
|
||||||
|
}
|
||||||
|
|
||||||
|
Flex::row()
|
||||||
|
.with_child({
|
||||||
|
let button =
|
||||||
|
MouseEventHandler::new::<ToggleOnline, _, _>(project_id, cx, |state, _| {
|
||||||
|
let mut style = *theme.private_button.style_for(state, false);
|
||||||
|
if is_going_online {
|
||||||
|
style.color = theme.disabled_button.color;
|
||||||
|
}
|
||||||
|
render_icon_button(&style, "icons/lock-8.svg")
|
||||||
|
.aligned()
|
||||||
|
.constrained()
|
||||||
|
.with_width(host_avatar_height)
|
||||||
|
.boxed()
|
||||||
|
});
|
||||||
|
|
||||||
|
if is_going_online {
|
||||||
|
button.boxed()
|
||||||
|
} else {
|
||||||
|
button
|
||||||
|
.with_cursor_style(CursorStyle::PointingHand)
|
||||||
|
.on_click(move |_, _, cx| {
|
||||||
|
cx.dispatch_action(ToggleProjectOnline {
|
||||||
|
project: Some(project.clone()),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.with_tooltip(
|
||||||
|
project_id,
|
||||||
|
"Take project online".to_string(),
|
||||||
|
None,
|
||||||
|
tooltip_style.clone(),
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
.boxed()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.with_child(
|
||||||
|
Label::new(worktree_root_names, row.name.text.clone())
|
||||||
|
.aligned()
|
||||||
|
.left()
|
||||||
|
.contained()
|
||||||
|
.with_style(row.name.container)
|
||||||
|
.flex(1., false)
|
||||||
|
.boxed(),
|
||||||
|
)
|
||||||
|
.constrained()
|
||||||
|
.with_height(theme.row_height)
|
||||||
|
.contained()
|
||||||
|
.with_style(row.container)
|
||||||
|
.boxed()
|
||||||
|
})
|
||||||
|
.boxed()
|
||||||
|
}
|
||||||
|
|
||||||
fn render_contact_request(
|
fn render_contact_request(
|
||||||
user: Arc<User>,
|
user: Arc<User>,
|
||||||
user_store: ModelHandle<UserStore>,
|
user_store: ModelHandle<UserStore>,
|
||||||
@ -487,7 +634,7 @@ impl ContactsPanel {
|
|||||||
row.add_children([
|
row.add_children([
|
||||||
MouseEventHandler::new::<Decline, _, _>(user.id as usize, cx, |mouse_state, _| {
|
MouseEventHandler::new::<Decline, _, _>(user.id as usize, cx, |mouse_state, _| {
|
||||||
let button_style = if is_contact_request_pending {
|
let button_style = if is_contact_request_pending {
|
||||||
&theme.disabled_contact_button
|
&theme.disabled_button
|
||||||
} else {
|
} else {
|
||||||
&theme.contact_button.style_for(mouse_state, false)
|
&theme.contact_button.style_for(mouse_state, false)
|
||||||
};
|
};
|
||||||
@ -509,7 +656,7 @@ impl ContactsPanel {
|
|||||||
.boxed(),
|
.boxed(),
|
||||||
MouseEventHandler::new::<Accept, _, _>(user.id as usize, cx, |mouse_state, _| {
|
MouseEventHandler::new::<Accept, _, _>(user.id as usize, cx, |mouse_state, _| {
|
||||||
let button_style = if is_contact_request_pending {
|
let button_style = if is_contact_request_pending {
|
||||||
&theme.disabled_contact_button
|
&theme.disabled_button
|
||||||
} else {
|
} else {
|
||||||
&theme.contact_button.style_for(mouse_state, false)
|
&theme.contact_button.style_for(mouse_state, false)
|
||||||
};
|
};
|
||||||
@ -531,7 +678,7 @@ impl ContactsPanel {
|
|||||||
row.add_child(
|
row.add_child(
|
||||||
MouseEventHandler::new::<Cancel, _, _>(user.id as usize, cx, |mouse_state, _| {
|
MouseEventHandler::new::<Cancel, _, _>(user.id as usize, cx, |mouse_state, _| {
|
||||||
let button_style = if is_contact_request_pending {
|
let button_style = if is_contact_request_pending {
|
||||||
&theme.disabled_contact_button
|
&theme.disabled_button
|
||||||
} else {
|
} else {
|
||||||
&theme.contact_button.style_for(mouse_state, false)
|
&theme.contact_button.style_for(mouse_state, false)
|
||||||
};
|
};
|
||||||
@ -557,6 +704,7 @@ impl ContactsPanel {
|
|||||||
|
|
||||||
fn update_entries(&mut self, cx: &mut ViewContext<Self>) {
|
fn update_entries(&mut self, cx: &mut ViewContext<Self>) {
|
||||||
let user_store = self.user_store.read(cx);
|
let user_store = self.user_store.read(cx);
|
||||||
|
let project_store = self.project_store.read(cx);
|
||||||
let query = self.filter_editor.read(cx).text(cx);
|
let query = self.filter_editor.read(cx).text(cx);
|
||||||
let executor = cx.background().clone();
|
let executor = cx.background().clone();
|
||||||
|
|
||||||
@ -629,20 +777,37 @@ impl ContactsPanel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let current_user = user_store.current_user();
|
||||||
|
|
||||||
let contacts = user_store.contacts();
|
let contacts = user_store.contacts();
|
||||||
if !contacts.is_empty() {
|
if !contacts.is_empty() {
|
||||||
|
// Always put the current user first.
|
||||||
self.match_candidates.clear();
|
self.match_candidates.clear();
|
||||||
self.match_candidates
|
self.match_candidates.reserve(contacts.len());
|
||||||
.extend(
|
self.match_candidates.push(StringMatchCandidate {
|
||||||
contacts
|
id: 0,
|
||||||
.iter()
|
string: Default::default(),
|
||||||
.enumerate()
|
char_bag: Default::default(),
|
||||||
.map(|(ix, contact)| StringMatchCandidate {
|
});
|
||||||
|
for (ix, contact) in contacts.iter().enumerate() {
|
||||||
|
let candidate = StringMatchCandidate {
|
||||||
id: ix,
|
id: ix,
|
||||||
string: contact.user.github_login.clone(),
|
string: contact.user.github_login.clone(),
|
||||||
char_bag: contact.user.github_login.chars().collect(),
|
char_bag: contact.user.github_login.chars().collect(),
|
||||||
}),
|
};
|
||||||
);
|
if current_user
|
||||||
|
.as_ref()
|
||||||
|
.map_or(false, |current_user| current_user.id == contact.user.id)
|
||||||
|
{
|
||||||
|
self.match_candidates[0] = candidate;
|
||||||
|
} else {
|
||||||
|
self.match_candidates.push(candidate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if self.match_candidates[0].string.is_empty() {
|
||||||
|
self.match_candidates.remove(0);
|
||||||
|
}
|
||||||
|
|
||||||
let matches = executor.block(match_strings(
|
let matches = executor.block(match_strings(
|
||||||
&self.match_candidates,
|
&self.match_candidates,
|
||||||
&query,
|
&query,
|
||||||
@ -666,16 +831,60 @@ impl ContactsPanel {
|
|||||||
for mat in matches {
|
for mat in matches {
|
||||||
let contact = &contacts[mat.candidate_id];
|
let contact = &contacts[mat.candidate_id];
|
||||||
self.entries.push(ContactEntry::Contact(contact.clone()));
|
self.entries.push(ContactEntry::Contact(contact.clone()));
|
||||||
self.entries
|
|
||||||
.extend(contact.projects.iter().enumerate().filter_map(
|
let is_current_user = current_user
|
||||||
|
.as_ref()
|
||||||
|
.map_or(false, |user| user.id == contact.user.id);
|
||||||
|
if is_current_user {
|
||||||
|
let mut open_projects =
|
||||||
|
project_store.projects(cx).collect::<Vec<_>>();
|
||||||
|
self.entries.extend(
|
||||||
|
contact.projects.iter().enumerate().filter_map(
|
||||||
|
|(ix, project)| {
|
||||||
|
let open_project = open_projects
|
||||||
|
.iter()
|
||||||
|
.position(|p| {
|
||||||
|
p.read(cx).remote_id() == Some(project.id)
|
||||||
|
})
|
||||||
|
.map(|ix| open_projects.remove(ix).downgrade());
|
||||||
|
if project.worktree_root_names.is_empty() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(ContactEntry::ContactProject(
|
||||||
|
contact.clone(),
|
||||||
|
ix,
|
||||||
|
open_project,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
self.entries.extend(open_projects.into_iter().filter_map(
|
||||||
|
|project| {
|
||||||
|
if project.read(cx).visible_worktrees(cx).next().is_none() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(ContactEntry::OfflineProject(project.downgrade()))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
self.entries.extend(
|
||||||
|
contact.projects.iter().enumerate().filter_map(
|
||||||
|(ix, project)| {
|
|(ix, project)| {
|
||||||
if project.worktree_root_names.is_empty() {
|
if project.worktree_root_names.is_empty() {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
Some(ContactEntry::ContactProject(contact.clone(), ix))
|
Some(ContactEntry::ContactProject(
|
||||||
|
contact.clone(),
|
||||||
|
ix,
|
||||||
|
None,
|
||||||
|
))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
));
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -757,11 +966,18 @@ impl ContactsPanel {
|
|||||||
let section = *section;
|
let section = *section;
|
||||||
self.toggle_expanded(&ToggleExpanded(section), cx);
|
self.toggle_expanded(&ToggleExpanded(section), cx);
|
||||||
}
|
}
|
||||||
ContactEntry::ContactProject(contact, project_index) => cx
|
ContactEntry::ContactProject(contact, project_index, open_project) => {
|
||||||
.dispatch_global_action(JoinProject {
|
if let Some(open_project) = open_project {
|
||||||
|
workspace::activate_workspace_for_project(cx, |_, cx| {
|
||||||
|
cx.model_id() == open_project.id()
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
cx.dispatch_global_action(JoinProject {
|
||||||
contact: contact.clone(),
|
contact: contact.clone(),
|
||||||
project_index: *project_index,
|
project_index: *project_index,
|
||||||
}),
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -952,11 +1168,16 @@ impl PartialEq for ContactEntry {
|
|||||||
return contact_1.user.id == contact_2.user.id;
|
return contact_1.user.id == contact_2.user.id;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ContactEntry::ContactProject(contact_1, ix_1) => {
|
ContactEntry::ContactProject(contact_1, ix_1, _) => {
|
||||||
if let ContactEntry::ContactProject(contact_2, ix_2) = other {
|
if let ContactEntry::ContactProject(contact_2, ix_2, _) = other {
|
||||||
return contact_1.user.id == contact_2.user.id && ix_1 == ix_2;
|
return contact_1.user.id == contact_2.user.id && ix_1 == ix_2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
ContactEntry::OfflineProject(project_1) => {
|
||||||
|
if let ContactEntry::OfflineProject(project_2) = other {
|
||||||
|
return project_1.id() == project_2.id();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
@ -965,20 +1186,70 @@ impl PartialEq for ContactEntry {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use client::{proto, test::FakeServer, Client};
|
use client::{
|
||||||
use gpui::TestAppContext;
|
proto,
|
||||||
|
test::{FakeHttpClient, FakeServer},
|
||||||
|
Client,
|
||||||
|
};
|
||||||
|
use collections::HashSet;
|
||||||
|
use gpui::{serde_json::json, TestAppContext};
|
||||||
use language::LanguageRegistry;
|
use language::LanguageRegistry;
|
||||||
use project::Project;
|
use project::{FakeFs, Project};
|
||||||
use theme::ThemeRegistry;
|
|
||||||
use workspace::AppState;
|
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
async fn test_contact_panel(cx: &mut TestAppContext) {
|
async fn test_contact_panel(cx: &mut TestAppContext) {
|
||||||
let (app_state, server) = init(cx).await;
|
Settings::test_async(cx);
|
||||||
let project = Project::test(app_state.fs.clone(), [], cx).await;
|
let current_user_id = 100;
|
||||||
let workspace = cx.add_view(0, |cx| Workspace::new(project, cx));
|
|
||||||
|
let languages = Arc::new(LanguageRegistry::test());
|
||||||
|
let http_client = FakeHttpClient::with_404_response();
|
||||||
|
let client = Client::new(http_client.clone());
|
||||||
|
let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http_client, cx));
|
||||||
|
let project_store = cx.add_model(|_| ProjectStore::new(project::Db::open_fake()));
|
||||||
|
let server = FakeServer::for_client(current_user_id, &client, &cx).await;
|
||||||
|
let fs = FakeFs::new(cx.background());
|
||||||
|
fs.insert_tree("/private_dir", json!({ "one.rs": "" }))
|
||||||
|
.await;
|
||||||
|
let project = cx.update(|cx| {
|
||||||
|
Project::local(
|
||||||
|
false,
|
||||||
|
client.clone(),
|
||||||
|
user_store.clone(),
|
||||||
|
project_store.clone(),
|
||||||
|
languages,
|
||||||
|
fs,
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
});
|
||||||
|
let worktree_id = project
|
||||||
|
.update(cx, |project, cx| {
|
||||||
|
project.find_or_create_local_worktree("/private_dir", true, cx)
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.0
|
||||||
|
.read_with(cx, |worktree, _| worktree.id().to_proto());
|
||||||
|
|
||||||
|
let workspace = cx.add_view(0, |cx| Workspace::new(project.clone(), cx));
|
||||||
let panel = cx.add_view(0, |cx| {
|
let panel = cx.add_view(0, |cx| {
|
||||||
ContactsPanel::new(app_state.user_store.clone(), workspace.downgrade(), cx)
|
ContactsPanel::new(
|
||||||
|
user_store.clone(),
|
||||||
|
project_store.clone(),
|
||||||
|
workspace.downgrade(),
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
workspace.update(cx, |_, cx| {
|
||||||
|
cx.observe(&panel, |_, panel, cx| {
|
||||||
|
let entries = render_to_strings(&panel, cx);
|
||||||
|
assert!(
|
||||||
|
entries.iter().collect::<HashSet<_>>().len() == entries.len(),
|
||||||
|
"Duplicate contact panel entries {:?}",
|
||||||
|
entries
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.detach();
|
||||||
});
|
});
|
||||||
|
|
||||||
let get_users_request = server.receive::<proto::GetUsers>().await.unwrap();
|
let get_users_request = server.receive::<proto::GetUsers>().await.unwrap();
|
||||||
@ -1001,6 +1272,11 @@ mod tests {
|
|||||||
github_login: name.to_string(),
|
github_login: name.to_string(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
|
.chain([proto::User {
|
||||||
|
id: current_user_id,
|
||||||
|
github_login: "the_current_user".to_string(),
|
||||||
|
..Default::default()
|
||||||
|
}])
|
||||||
.collect(),
|
.collect(),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@ -1039,19 +1315,219 @@ mod tests {
|
|||||||
should_notify: false,
|
should_notify: false,
|
||||||
projects: vec![],
|
projects: vec![],
|
||||||
},
|
},
|
||||||
|
proto::Contact {
|
||||||
|
user_id: current_user_id,
|
||||||
|
online: true,
|
||||||
|
should_notify: false,
|
||||||
|
projects: vec![proto::ProjectMetadata {
|
||||||
|
id: 103,
|
||||||
|
worktree_root_names: vec!["dir3".to_string()],
|
||||||
|
guests: vec![3],
|
||||||
|
}],
|
||||||
|
},
|
||||||
],
|
],
|
||||||
..Default::default()
|
..Default::default()
|
||||||
});
|
});
|
||||||
|
|
||||||
cx.foreground().run_until_parked();
|
cx.foreground().run_until_parked();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
render_to_strings(&panel, cx),
|
cx.read(|cx| render_to_strings(&panel, cx)),
|
||||||
&[
|
&[
|
||||||
"+",
|
|
||||||
"v Requests",
|
"v Requests",
|
||||||
" incoming user_one",
|
" incoming user_one",
|
||||||
" outgoing user_two",
|
" outgoing user_two",
|
||||||
"v Online",
|
"v Online",
|
||||||
|
" the_current_user",
|
||||||
|
" dir3",
|
||||||
|
" 🔒 private_dir",
|
||||||
|
" user_four",
|
||||||
|
" dir2",
|
||||||
|
" user_three",
|
||||||
|
" dir1",
|
||||||
|
"v Offline",
|
||||||
|
" user_five",
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
// Take a project online. It appears as loading, since the project
|
||||||
|
// isn't yet visible to other contacts.
|
||||||
|
project.update(cx, |project, cx| project.set_online(true, cx));
|
||||||
|
cx.foreground().run_until_parked();
|
||||||
|
assert_eq!(
|
||||||
|
cx.read(|cx| render_to_strings(&panel, cx)),
|
||||||
|
&[
|
||||||
|
"v Requests",
|
||||||
|
" incoming user_one",
|
||||||
|
" outgoing user_two",
|
||||||
|
"v Online",
|
||||||
|
" the_current_user",
|
||||||
|
" dir3",
|
||||||
|
" 🔒 private_dir (going online...)",
|
||||||
|
" user_four",
|
||||||
|
" dir2",
|
||||||
|
" user_three",
|
||||||
|
" dir1",
|
||||||
|
"v Offline",
|
||||||
|
" user_five",
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
// The server responds, assigning the project a remote id. It still appears
|
||||||
|
// as loading, because the server hasn't yet sent out the updated contact
|
||||||
|
// state for the current user.
|
||||||
|
let request = server.receive::<proto::RegisterProject>().await.unwrap();
|
||||||
|
server
|
||||||
|
.respond(
|
||||||
|
request.receipt(),
|
||||||
|
proto::RegisterProjectResponse { project_id: 200 },
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
cx.foreground().run_until_parked();
|
||||||
|
assert_eq!(
|
||||||
|
cx.read(|cx| render_to_strings(&panel, cx)),
|
||||||
|
&[
|
||||||
|
"v Requests",
|
||||||
|
" incoming user_one",
|
||||||
|
" outgoing user_two",
|
||||||
|
"v Online",
|
||||||
|
" the_current_user",
|
||||||
|
" dir3",
|
||||||
|
" 🔒 private_dir (going online...)",
|
||||||
|
" user_four",
|
||||||
|
" dir2",
|
||||||
|
" user_three",
|
||||||
|
" dir1",
|
||||||
|
"v Offline",
|
||||||
|
" user_five",
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
// The server receives the project's metadata and updates the contact metadata
|
||||||
|
// for the current user. Now the project appears as online.
|
||||||
|
assert_eq!(
|
||||||
|
server
|
||||||
|
.receive::<proto::UpdateProject>()
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.payload
|
||||||
|
.worktrees,
|
||||||
|
&[proto::WorktreeMetadata {
|
||||||
|
id: worktree_id,
|
||||||
|
root_name: "private_dir".to_string(),
|
||||||
|
visible: true,
|
||||||
|
}],
|
||||||
|
);
|
||||||
|
server.send(proto::UpdateContacts {
|
||||||
|
contacts: vec![proto::Contact {
|
||||||
|
user_id: current_user_id,
|
||||||
|
online: true,
|
||||||
|
should_notify: false,
|
||||||
|
projects: vec![
|
||||||
|
proto::ProjectMetadata {
|
||||||
|
id: 103,
|
||||||
|
worktree_root_names: vec!["dir3".to_string()],
|
||||||
|
guests: vec![3],
|
||||||
|
},
|
||||||
|
proto::ProjectMetadata {
|
||||||
|
id: 200,
|
||||||
|
worktree_root_names: vec!["private_dir".to_string()],
|
||||||
|
guests: vec![3],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}],
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
cx.foreground().run_until_parked();
|
||||||
|
assert_eq!(
|
||||||
|
cx.read(|cx| render_to_strings(&panel, cx)),
|
||||||
|
&[
|
||||||
|
"v Requests",
|
||||||
|
" incoming user_one",
|
||||||
|
" outgoing user_two",
|
||||||
|
"v Online",
|
||||||
|
" the_current_user",
|
||||||
|
" dir3",
|
||||||
|
" private_dir",
|
||||||
|
" user_four",
|
||||||
|
" dir2",
|
||||||
|
" user_three",
|
||||||
|
" dir1",
|
||||||
|
"v Offline",
|
||||||
|
" user_five",
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
// Take the project offline. It appears as loading.
|
||||||
|
project.update(cx, |project, cx| project.set_online(false, cx));
|
||||||
|
cx.foreground().run_until_parked();
|
||||||
|
assert_eq!(
|
||||||
|
cx.read(|cx| render_to_strings(&panel, cx)),
|
||||||
|
&[
|
||||||
|
"v Requests",
|
||||||
|
" incoming user_one",
|
||||||
|
" outgoing user_two",
|
||||||
|
"v Online",
|
||||||
|
" the_current_user",
|
||||||
|
" dir3",
|
||||||
|
" private_dir (going offline...)",
|
||||||
|
" user_four",
|
||||||
|
" dir2",
|
||||||
|
" user_three",
|
||||||
|
" dir1",
|
||||||
|
"v Offline",
|
||||||
|
" user_five",
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
// The server receives the unregister request and updates the contact
|
||||||
|
// metadata for the current user. The project is now offline.
|
||||||
|
let request = server.receive::<proto::UnregisterProject>().await.unwrap();
|
||||||
|
server.send(proto::UpdateContacts {
|
||||||
|
contacts: vec![proto::Contact {
|
||||||
|
user_id: current_user_id,
|
||||||
|
online: true,
|
||||||
|
should_notify: false,
|
||||||
|
projects: vec![proto::ProjectMetadata {
|
||||||
|
id: 103,
|
||||||
|
worktree_root_names: vec!["dir3".to_string()],
|
||||||
|
guests: vec![3],
|
||||||
|
}],
|
||||||
|
}],
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
cx.foreground().run_until_parked();
|
||||||
|
assert_eq!(
|
||||||
|
cx.read(|cx| render_to_strings(&panel, cx)),
|
||||||
|
&[
|
||||||
|
"v Requests",
|
||||||
|
" incoming user_one",
|
||||||
|
" outgoing user_two",
|
||||||
|
"v Online",
|
||||||
|
" the_current_user",
|
||||||
|
" dir3",
|
||||||
|
" 🔒 private_dir",
|
||||||
|
" user_four",
|
||||||
|
" dir2",
|
||||||
|
" user_three",
|
||||||
|
" dir1",
|
||||||
|
"v Offline",
|
||||||
|
" user_five",
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
// The server responds to the unregister request.
|
||||||
|
server.respond(request.receipt(), proto::Ack {}).await;
|
||||||
|
cx.foreground().run_until_parked();
|
||||||
|
assert_eq!(
|
||||||
|
cx.read(|cx| render_to_strings(&panel, cx)),
|
||||||
|
&[
|
||||||
|
"v Requests",
|
||||||
|
" incoming user_one",
|
||||||
|
" outgoing user_two",
|
||||||
|
"v Online",
|
||||||
|
" the_current_user",
|
||||||
|
" dir3",
|
||||||
|
" 🔒 private_dir",
|
||||||
" user_four",
|
" user_four",
|
||||||
" dir2",
|
" dir2",
|
||||||
" user_three",
|
" user_three",
|
||||||
@ -1068,9 +1544,8 @@ mod tests {
|
|||||||
});
|
});
|
||||||
cx.foreground().run_until_parked();
|
cx.foreground().run_until_parked();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
render_to_strings(&panel, cx),
|
cx.read(|cx| render_to_strings(&panel, cx)),
|
||||||
&[
|
&[
|
||||||
"+",
|
|
||||||
"v Online",
|
"v Online",
|
||||||
" user_four <=== selected",
|
" user_four <=== selected",
|
||||||
" dir2",
|
" dir2",
|
||||||
@ -1083,9 +1558,8 @@ mod tests {
|
|||||||
panel.select_next(&Default::default(), cx);
|
panel.select_next(&Default::default(), cx);
|
||||||
});
|
});
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
render_to_strings(&panel, cx),
|
cx.read(|cx| render_to_strings(&panel, cx)),
|
||||||
&[
|
&[
|
||||||
"+",
|
|
||||||
"v Online",
|
"v Online",
|
||||||
" user_four",
|
" user_four",
|
||||||
" dir2 <=== selected",
|
" dir2 <=== selected",
|
||||||
@ -1098,9 +1572,8 @@ mod tests {
|
|||||||
panel.select_next(&Default::default(), cx);
|
panel.select_next(&Default::default(), cx);
|
||||||
});
|
});
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
render_to_strings(&panel, cx),
|
cx.read(|cx| render_to_strings(&panel, cx)),
|
||||||
&[
|
&[
|
||||||
"+",
|
|
||||||
"v Online",
|
"v Online",
|
||||||
" user_four",
|
" user_four",
|
||||||
" dir2",
|
" dir2",
|
||||||
@ -1110,10 +1583,9 @@ mod tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_to_strings(panel: &ViewHandle<ContactsPanel>, cx: &TestAppContext) -> Vec<String> {
|
fn render_to_strings(panel: &ViewHandle<ContactsPanel>, cx: &AppContext) -> Vec<String> {
|
||||||
panel.read_with(cx, |panel, _| {
|
let panel = panel.read(cx);
|
||||||
let mut entries = Vec::new();
|
let mut entries = Vec::new();
|
||||||
entries.push("+".to_string());
|
|
||||||
entries.extend(panel.entries.iter().enumerate().map(|(ix, entry)| {
|
entries.extend(panel.entries.iter().enumerate().map(|(ix, entry)| {
|
||||||
let mut string = match entry {
|
let mut string = match entry {
|
||||||
ContactEntry::Header(name) => {
|
ContactEntry::Header(name) => {
|
||||||
@ -1133,10 +1605,33 @@ mod tests {
|
|||||||
ContactEntry::Contact(contact) => {
|
ContactEntry::Contact(contact) => {
|
||||||
format!(" {}", contact.user.github_login)
|
format!(" {}", contact.user.github_login)
|
||||||
}
|
}
|
||||||
ContactEntry::ContactProject(contact, project_ix) => {
|
ContactEntry::ContactProject(contact, project_ix, project) => {
|
||||||
|
let project = project
|
||||||
|
.and_then(|p| p.upgrade(cx))
|
||||||
|
.map(|project| project.read(cx));
|
||||||
format!(
|
format!(
|
||||||
" {}",
|
" {}{}",
|
||||||
contact.projects[*project_ix].worktree_root_names.join(", ")
|
contact.projects[*project_ix].worktree_root_names.join(", "),
|
||||||
|
if project.map_or(true, |project| project.is_online()) {
|
||||||
|
""
|
||||||
|
} else {
|
||||||
|
" (going offline...)"
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
ContactEntry::OfflineProject(project) => {
|
||||||
|
let project = project.upgrade(cx).unwrap().read(cx);
|
||||||
|
format!(
|
||||||
|
" 🔒 {}{}",
|
||||||
|
project
|
||||||
|
.worktree_root_names(cx)
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join(", "),
|
||||||
|
if project.is_online() {
|
||||||
|
" (going online...)"
|
||||||
|
} else {
|
||||||
|
""
|
||||||
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -1148,30 +1643,5 @@ mod tests {
|
|||||||
string
|
string
|
||||||
}));
|
}));
|
||||||
entries
|
entries
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn init(cx: &mut TestAppContext) -> (Arc<AppState>, FakeServer) {
|
|
||||||
cx.update(|cx| cx.set_global(Settings::test(cx)));
|
|
||||||
let themes = ThemeRegistry::new((), cx.font_cache());
|
|
||||||
let fs = project::FakeFs::new(cx.background().clone());
|
|
||||||
let languages = Arc::new(LanguageRegistry::test());
|
|
||||||
let http_client = client::test::FakeHttpClient::with_404_response();
|
|
||||||
let mut client = Client::new(http_client.clone());
|
|
||||||
let server = FakeServer::for_client(100, &mut client, &cx).await;
|
|
||||||
let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http_client, cx));
|
|
||||||
|
|
||||||
(
|
|
||||||
Arc::new(AppState {
|
|
||||||
languages,
|
|
||||||
themes,
|
|
||||||
client,
|
|
||||||
user_store: user_store.clone(),
|
|
||||||
fs,
|
|
||||||
build_window_options: || Default::default(),
|
|
||||||
initialize_workspace: |_, _, _| {},
|
|
||||||
}),
|
|
||||||
server,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -59,7 +59,7 @@ project = { path = "../project", features = ["test-support"] }
|
|||||||
settings = { path = "../settings", features = ["test-support"] }
|
settings = { path = "../settings", features = ["test-support"] }
|
||||||
workspace = { path = "../workspace", features = ["test-support"] }
|
workspace = { path = "../workspace", features = ["test-support"] }
|
||||||
ctor = "0.1"
|
ctor = "0.1"
|
||||||
env_logger = "0.8"
|
env_logger = "0.9"
|
||||||
rand = "0.8"
|
rand = "0.8"
|
||||||
unindent = "0.1.7"
|
unindent = "0.1.7"
|
||||||
tree-sitter = "0.20"
|
tree-sitter = "0.20"
|
||||||
|
@ -25,4 +25,4 @@ gpui = { path = "../gpui", features = ["test-support"] }
|
|||||||
serde_json = { version = "1.0", features = ["preserve_order"] }
|
serde_json = { version = "1.0", features = ["preserve_order"] }
|
||||||
workspace = { path = "../workspace", features = ["test-support"] }
|
workspace = { path = "../workspace", features = ["test-support"] }
|
||||||
ctor = "0.1"
|
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 }
|
backtrace = { version = "0.3", optional = true }
|
||||||
ctor = "0.1"
|
ctor = "0.1"
|
||||||
dhat = { version = "0.3", optional = true }
|
dhat = { version = "0.3", optional = true }
|
||||||
env_logger = { version = "0.8", optional = true }
|
env_logger = { version = "0.9", optional = true }
|
||||||
etagere = "0.2"
|
etagere = "0.2"
|
||||||
futures = "0.3"
|
futures = "0.3"
|
||||||
image = "0.23"
|
image = "0.23"
|
||||||
@ -47,14 +47,14 @@ usvg = "0.14"
|
|||||||
waker-fn = "1.1.0"
|
waker-fn = "1.1.0"
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
bindgen = "0.58.1"
|
bindgen = "0.59.2"
|
||||||
cc = "1.0.67"
|
cc = "1.0.67"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
backtrace = "0.3"
|
backtrace = "0.3"
|
||||||
collections = { path = "../collections", features = ["test-support"] }
|
collections = { path = "../collections", features = ["test-support"] }
|
||||||
dhat = "0.3"
|
dhat = "0.3"
|
||||||
env_logger = "0.8"
|
env_logger = "0.9"
|
||||||
png = "0.16"
|
png = "0.16"
|
||||||
simplelog = "0.9"
|
simplelog = "0.9"
|
||||||
|
|
||||||
|
@ -499,7 +499,14 @@ impl TestAppContext {
|
|||||||
Fut: 'static + Future<Output = T>,
|
Fut: 'static + Future<Output = T>,
|
||||||
T: 'static,
|
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>) {
|
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
|
self.view_id
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn window_id(&self) -> usize {
|
||||||
|
self.window_id
|
||||||
|
}
|
||||||
|
|
||||||
pub fn upgrade(&self, cx: &impl UpgradeViewHandle) -> Option<ViewHandle<T>> {
|
pub fn upgrade(&self, cx: &impl UpgradeViewHandle) -> Option<ViewHandle<T>> {
|
||||||
cx.upgrade_view_handle(self)
|
cx.upgrade_view_handle(self)
|
||||||
}
|
}
|
||||||
|
@ -147,6 +147,12 @@ pub struct AppVersion {
|
|||||||
patch: usize,
|
patch: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for CursorStyle {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::Arrow
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl FromStr for AppVersion {
|
impl FromStr for AppVersion {
|
||||||
type Err = anyhow::Error;
|
type Err = anyhow::Error;
|
||||||
|
|
||||||
|
@ -57,7 +57,7 @@ lsp = { path = "../lsp", features = ["test-support"] }
|
|||||||
text = { path = "../text", features = ["test-support"] }
|
text = { path = "../text", features = ["test-support"] }
|
||||||
util = { path = "../util", features = ["test-support"] }
|
util = { path = "../util", features = ["test-support"] }
|
||||||
ctor = "0.1"
|
ctor = "0.1"
|
||||||
env_logger = "0.8"
|
env_logger = "0.9"
|
||||||
rand = "0.8.3"
|
rand = "0.8.3"
|
||||||
tree-sitter-json = "*"
|
tree-sitter-json = "*"
|
||||||
tree-sitter-rust = "*"
|
tree-sitter-rust = "*"
|
||||||
|
@ -30,5 +30,5 @@ gpui = { path = "../gpui", features = ["test-support"] }
|
|||||||
util = { path = "../util", features = ["test-support"] }
|
util = { path = "../util", features = ["test-support"] }
|
||||||
async-pipe = { git = "https://github.com/zed-industries/async-pipe-rs", rev = "82d00a04211cf4e1236029aa03e6b6ce2a74c553" }
|
async-pipe = { git = "https://github.com/zed-industries/async-pipe-rs", rev = "82d00a04211cf4e1236029aa03e6b6ce2a74c553" }
|
||||||
ctor = "0.1"
|
ctor = "0.1"
|
||||||
env_logger = "0.8"
|
env_logger = "0.9"
|
||||||
unindent = "0.1.7"
|
unindent = "0.1.7"
|
||||||
|
@ -21,4 +21,4 @@ gpui = { path = "../gpui", features = ["test-support"] }
|
|||||||
serde_json = { version = "1.0", features = ["preserve_order"] }
|
serde_json = { version = "1.0", features = ["preserve_order"] }
|
||||||
workspace = { path = "../workspace", features = ["test-support"] }
|
workspace = { path = "../workspace", features = ["test-support"] }
|
||||||
ctor = "0.1"
|
ctor = "0.1"
|
||||||
env_logger = "0.8"
|
env_logger = "0.9"
|
||||||
|
@ -47,6 +47,7 @@ similar = "1.3"
|
|||||||
smol = "1.2.5"
|
smol = "1.2.5"
|
||||||
thiserror = "1.0.29"
|
thiserror = "1.0.29"
|
||||||
toml = "0.5"
|
toml = "0.5"
|
||||||
|
rocksdb = "0.18"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
client = { path = "../client", features = ["test-support"] }
|
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;
|
pub mod fs;
|
||||||
mod ignore;
|
mod ignore;
|
||||||
mod lsp_command;
|
mod lsp_command;
|
||||||
@ -25,6 +26,7 @@ use language::{
|
|||||||
use lsp::{DiagnosticSeverity, DiagnosticTag, DocumentHighlightKind, LanguageServer};
|
use lsp::{DiagnosticSeverity, DiagnosticTag, DocumentHighlightKind, LanguageServer};
|
||||||
use lsp_command::*;
|
use lsp_command::*;
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
|
use postage::stream::Stream;
|
||||||
use postage::watch;
|
use postage::watch;
|
||||||
use rand::prelude::*;
|
use rand::prelude::*;
|
||||||
use search::SearchQuery;
|
use search::SearchQuery;
|
||||||
@ -52,6 +54,7 @@ use std::{
|
|||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use util::{post_inc, ResultExt, TryFutureExt as _};
|
use util::{post_inc, ResultExt, TryFutureExt as _};
|
||||||
|
|
||||||
|
pub use db::Db;
|
||||||
pub use fs::*;
|
pub use fs::*;
|
||||||
pub use worktree::*;
|
pub use worktree::*;
|
||||||
|
|
||||||
@ -59,6 +62,11 @@ pub trait Item: Entity {
|
|||||||
fn entry_id(&self, cx: &AppContext) -> Option<ProjectEntryId>;
|
fn entry_id(&self, cx: &AppContext) -> Option<ProjectEntryId>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct ProjectStore {
|
||||||
|
db: Arc<Db>,
|
||||||
|
projects: Vec<WeakModelHandle<Project>>,
|
||||||
|
}
|
||||||
|
|
||||||
pub struct Project {
|
pub struct Project {
|
||||||
worktrees: Vec<WorktreeHandle>,
|
worktrees: Vec<WorktreeHandle>,
|
||||||
active_entry: Option<ProjectEntryId>,
|
active_entry: Option<ProjectEntryId>,
|
||||||
@ -75,6 +83,7 @@ pub struct Project {
|
|||||||
next_entry_id: Arc<AtomicUsize>,
|
next_entry_id: Arc<AtomicUsize>,
|
||||||
next_diagnostic_group_id: usize,
|
next_diagnostic_group_id: usize,
|
||||||
user_store: ModelHandle<UserStore>,
|
user_store: ModelHandle<UserStore>,
|
||||||
|
project_store: ModelHandle<ProjectStore>,
|
||||||
fs: Arc<dyn Fs>,
|
fs: Arc<dyn Fs>,
|
||||||
client_state: ProjectClientState,
|
client_state: ProjectClientState,
|
||||||
collaborators: HashMap<PeerId, Collaborator>,
|
collaborators: HashMap<PeerId, Collaborator>,
|
||||||
@ -90,6 +99,7 @@ pub struct Project {
|
|||||||
opened_buffers: HashMap<u64, OpenBuffer>,
|
opened_buffers: HashMap<u64, OpenBuffer>,
|
||||||
buffer_snapshots: HashMap<u64, Vec<(i32, TextBufferSnapshot)>>,
|
buffer_snapshots: HashMap<u64, Vec<(i32, TextBufferSnapshot)>>,
|
||||||
nonce: u128,
|
nonce: u128,
|
||||||
|
initialized_persistent_state: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
@ -120,6 +130,8 @@ enum ProjectClientState {
|
|||||||
is_shared: bool,
|
is_shared: bool,
|
||||||
remote_id_tx: watch::Sender<Option<u64>>,
|
remote_id_tx: watch::Sender<Option<u64>>,
|
||||||
remote_id_rx: watch::Receiver<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<()>>,
|
_maintain_remote_id_task: Task<Option<()>>,
|
||||||
},
|
},
|
||||||
Remote {
|
Remote {
|
||||||
@ -273,8 +285,7 @@ impl Project {
|
|||||||
client.add_model_message_handler(Self::handle_update_language_server);
|
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_remove_collaborator);
|
||||||
client.add_model_message_handler(Self::handle_join_project_request_cancelled);
|
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_update_project);
|
||||||
client.add_model_message_handler(Self::handle_unregister_worktree);
|
|
||||||
client.add_model_message_handler(Self::handle_unregister_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_project_unshared);
|
||||||
client.add_model_message_handler(Self::handle_update_buffer_file);
|
client.add_model_message_handler(Self::handle_update_buffer_file);
|
||||||
@ -305,34 +316,42 @@ impl Project {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn local(
|
pub fn local(
|
||||||
|
online: bool,
|
||||||
client: Arc<Client>,
|
client: Arc<Client>,
|
||||||
user_store: ModelHandle<UserStore>,
|
user_store: ModelHandle<UserStore>,
|
||||||
|
project_store: ModelHandle<ProjectStore>,
|
||||||
languages: Arc<LanguageRegistry>,
|
languages: Arc<LanguageRegistry>,
|
||||||
fs: Arc<dyn Fs>,
|
fs: Arc<dyn Fs>,
|
||||||
cx: &mut MutableAppContext,
|
cx: &mut MutableAppContext,
|
||||||
) -> ModelHandle<Self> {
|
) -> ModelHandle<Self> {
|
||||||
cx.add_model(|cx: &mut ModelContext<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 (remote_id_tx, remote_id_rx) = watch::channel();
|
||||||
let _maintain_remote_id_task = cx.spawn_weak({
|
let _maintain_remote_id_task = cx.spawn_weak({
|
||||||
let rpc = client.clone();
|
let status_rx = client.clone().status();
|
||||||
move |this, mut cx| {
|
let online_rx = online_rx.clone();
|
||||||
async move {
|
move |this, mut cx| async move {
|
||||||
let mut status = rpc.status();
|
let mut stream = Stream::map(status_rx.clone(), drop)
|
||||||
while let Some(status) = status.next().await {
|
.merge(Stream::map(online_rx.clone(), drop));
|
||||||
if let Some(this) = this.upgrade(&cx) {
|
while stream.recv().await.is_some() {
|
||||||
if status.is_connected() {
|
let this = this.upgrade(&cx)?;
|
||||||
this.update(&mut cx, |this, cx| this.register(cx)).await?;
|
if status_rx.borrow().is_connected() && *online_rx.borrow() {
|
||||||
|
this.update(&mut cx, |this, cx| this.register(cx))
|
||||||
|
.await
|
||||||
|
.log_err()?;
|
||||||
} else {
|
} else {
|
||||||
this.update(&mut cx, |this, cx| this.unregister(cx));
|
this.update(&mut cx, |this, cx| this.unregister(cx))
|
||||||
|
.await
|
||||||
|
.log_err();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
None
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
.log_err()
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
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();
|
let (opened_buffer_tx, opened_buffer_rx) = watch::channel();
|
||||||
Self {
|
Self {
|
||||||
worktrees: Default::default(),
|
worktrees: Default::default(),
|
||||||
@ -346,6 +365,8 @@ impl Project {
|
|||||||
is_shared: false,
|
is_shared: false,
|
||||||
remote_id_tx,
|
remote_id_tx,
|
||||||
remote_id_rx,
|
remote_id_rx,
|
||||||
|
online_tx,
|
||||||
|
online_rx,
|
||||||
_maintain_remote_id_task,
|
_maintain_remote_id_task,
|
||||||
},
|
},
|
||||||
opened_buffer: (Rc::new(RefCell::new(opened_buffer_tx)), opened_buffer_rx),
|
opened_buffer: (Rc::new(RefCell::new(opened_buffer_tx)), opened_buffer_rx),
|
||||||
@ -354,6 +375,7 @@ impl Project {
|
|||||||
languages,
|
languages,
|
||||||
client,
|
client,
|
||||||
user_store,
|
user_store,
|
||||||
|
project_store,
|
||||||
fs,
|
fs,
|
||||||
next_entry_id: Default::default(),
|
next_entry_id: Default::default(),
|
||||||
next_diagnostic_group_id: Default::default(),
|
next_diagnostic_group_id: Default::default(),
|
||||||
@ -364,6 +386,7 @@ impl Project {
|
|||||||
language_server_settings: Default::default(),
|
language_server_settings: Default::default(),
|
||||||
next_language_server_id: 0,
|
next_language_server_id: 0,
|
||||||
nonce: StdRng::from_entropy().gen(),
|
nonce: StdRng::from_entropy().gen(),
|
||||||
|
initialized_persistent_state: false,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -372,9 +395,10 @@ impl Project {
|
|||||||
remote_id: u64,
|
remote_id: u64,
|
||||||
client: Arc<Client>,
|
client: Arc<Client>,
|
||||||
user_store: ModelHandle<UserStore>,
|
user_store: ModelHandle<UserStore>,
|
||||||
|
project_store: ModelHandle<ProjectStore>,
|
||||||
languages: Arc<LanguageRegistry>,
|
languages: Arc<LanguageRegistry>,
|
||||||
fs: Arc<dyn Fs>,
|
fs: Arc<dyn Fs>,
|
||||||
cx: &mut AsyncAppContext,
|
mut cx: AsyncAppContext,
|
||||||
) -> Result<ModelHandle<Self>, JoinProjectError> {
|
) -> Result<ModelHandle<Self>, JoinProjectError> {
|
||||||
client.authenticate_and_connect(true, &cx).await?;
|
client.authenticate_and_connect(true, &cx).await?;
|
||||||
|
|
||||||
@ -414,6 +438,9 @@ impl Project {
|
|||||||
|
|
||||||
let (opened_buffer_tx, opened_buffer_rx) = watch::channel();
|
let (opened_buffer_tx, opened_buffer_rx) = watch::channel();
|
||||||
let this = cx.add_model(|cx: &mut ModelContext<Self>| {
|
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 {
|
let mut this = Self {
|
||||||
worktrees: Vec::new(),
|
worktrees: Vec::new(),
|
||||||
loading_buffers: Default::default(),
|
loading_buffers: Default::default(),
|
||||||
@ -424,6 +451,7 @@ impl Project {
|
|||||||
collaborators: Default::default(),
|
collaborators: Default::default(),
|
||||||
languages,
|
languages,
|
||||||
user_store: user_store.clone(),
|
user_store: user_store.clone(),
|
||||||
|
project_store,
|
||||||
fs,
|
fs,
|
||||||
next_entry_id: Default::default(),
|
next_entry_id: Default::default(),
|
||||||
next_diagnostic_group_id: Default::default(),
|
next_diagnostic_group_id: Default::default(),
|
||||||
@ -471,6 +499,7 @@ impl Project {
|
|||||||
opened_buffers: Default::default(),
|
opened_buffers: Default::default(),
|
||||||
buffer_snapshots: Default::default(),
|
buffer_snapshots: Default::default(),
|
||||||
nonce: StdRng::from_entropy().gen(),
|
nonce: StdRng::from_entropy().gen(),
|
||||||
|
initialized_persistent_state: false,
|
||||||
};
|
};
|
||||||
for worktree in worktrees {
|
for worktree in worktrees {
|
||||||
this.add_worktree(&worktree, cx);
|
this.add_worktree(&worktree, cx);
|
||||||
@ -484,15 +513,15 @@ impl Project {
|
|||||||
.map(|peer| peer.user_id)
|
.map(|peer| peer.user_id)
|
||||||
.collect();
|
.collect();
|
||||||
user_store
|
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?;
|
.await?;
|
||||||
let mut collaborators = HashMap::default();
|
let mut collaborators = HashMap::default();
|
||||||
for message in response.collaborators {
|
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);
|
collaborators.insert(collaborator.peer_id, collaborator);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.update(cx, |this, _| {
|
this.update(&mut cx, |this, _| {
|
||||||
this.collaborators = collaborators;
|
this.collaborators = collaborators;
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -509,7 +538,10 @@ impl Project {
|
|||||||
let http_client = client::test::FakeHttpClient::with_404_response();
|
let http_client = client::test::FakeHttpClient::with_404_response();
|
||||||
let client = client::Client::new(http_client.clone());
|
let client = client::Client::new(http_client.clone());
|
||||||
let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http_client, cx));
|
let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http_client, cx));
|
||||||
let project = 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 {
|
for path in root_paths {
|
||||||
let (tree, _) = project
|
let (tree, _) = project
|
||||||
.update(cx, |project, cx| {
|
.update(cx, |project, cx| {
|
||||||
@ -523,6 +555,53 @@ impl Project {
|
|||||||
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>> {
|
pub fn buffer_for_id(&self, remote_id: u64, cx: &AppContext) -> Option<ModelHandle<Buffer>> {
|
||||||
self.opened_buffers
|
self.opened_buffers
|
||||||
.get(&remote_id)
|
.get(&remote_id)
|
||||||
@ -541,6 +620,10 @@ impl Project {
|
|||||||
self.user_store.clone()
|
self.user_store.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn project_store(&self) -> ModelHandle<ProjectStore> {
|
||||||
|
self.project_store.clone()
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(any(test, feature = "test-support"))]
|
#[cfg(any(test, feature = "test-support"))]
|
||||||
pub fn check_invariants(&self, cx: &AppContext) {
|
pub fn check_invariants(&self, cx: &AppContext) {
|
||||||
if self.is_local() {
|
if self.is_local() {
|
||||||
@ -598,54 +681,84 @@ impl Project {
|
|||||||
&self.fs
|
&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);
|
self.unshared(cx);
|
||||||
for worktree in &self.worktrees {
|
if let ProjectClientState::Local { remote_id_rx, .. } = &mut self.client_state {
|
||||||
if let Some(worktree) = worktree.upgrade(cx) {
|
if let Some(remote_id) = *remote_id_rx.borrow() {
|
||||||
worktree.update(cx, |worktree, _| {
|
let request = self.client.request(proto::UnregisterProject {
|
||||||
worktree.as_local_mut().unwrap().unregister();
|
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)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Task::ready(Ok(()))
|
||||||
if let ProjectClientState::Local { remote_id_tx, .. } = &mut self.client_state {
|
|
||||||
*remote_id_tx.borrow_mut() = None;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.subscriptions.clear();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn register(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
|
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 {});
|
let response = self.client.request(proto::RegisterProject {});
|
||||||
cx.spawn(|this, mut cx| async move {
|
cx.spawn(|this, mut cx| async move {
|
||||||
let remote_id = response.await?.project_id;
|
let remote_id = response.await?.project_id;
|
||||||
|
|
||||||
let mut registrations = Vec::new();
|
|
||||||
this.update(&mut cx, |this, cx| {
|
this.update(&mut cx, |this, cx| {
|
||||||
if let ProjectClientState::Local { remote_id_tx, .. } = &mut this.client_state {
|
if let ProjectClientState::Local { remote_id_tx, .. } = &mut this.client_state {
|
||||||
*remote_id_tx.borrow_mut() = Some(remote_id);
|
*remote_id_tx.borrow_mut() = Some(remote_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.metadata_changed(false, cx);
|
||||||
cx.emit(Event::RemoteIdChanged(Some(remote_id)));
|
cx.emit(Event::RemoteIdChanged(Some(remote_id)));
|
||||||
|
|
||||||
this.subscriptions
|
this.subscriptions
|
||||||
.push(this.client.add_model_for_remote_entity(remote_id, cx));
|
.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(())
|
||||||
})
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn remote_id(&self) -> Option<u64> {
|
pub fn remote_id(&self) -> Option<u64> {
|
||||||
@ -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> {
|
pub fn collaborators(&self) -> &HashMap<PeerId, Collaborator> {
|
||||||
&self.collaborators
|
&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(
|
pub fn worktree_for_id(
|
||||||
&self,
|
&self,
|
||||||
id: WorktreeId,
|
id: WorktreeId,
|
||||||
@ -757,6 +924,20 @@ impl Project {
|
|||||||
.map(|worktree| worktree.read(cx).id())
|
.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(
|
pub fn create_entry(
|
||||||
&mut self,
|
&mut self,
|
||||||
project_path: impl Into<ProjectPath>,
|
project_path: impl Into<ProjectPath>,
|
||||||
@ -3619,37 +3800,18 @@ impl Project {
|
|||||||
});
|
});
|
||||||
let worktree = worktree?;
|
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.add_worktree(&worktree, cx);
|
||||||
project.remote_id()
|
project.shared_remote_id()
|
||||||
});
|
});
|
||||||
|
|
||||||
if let Some(project_id) = remote_project_id {
|
if let Some(project_id) = project_id {
|
||||||
// Because sharing is async, we may have *unshared* the project by the time it completes,
|
worktree
|
||||||
// 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| {
|
.update(&mut cx, |worktree, cx| {
|
||||||
worktree.as_local_mut().unwrap().share(project_id, cx)
|
worktree.as_local_mut().unwrap().share(project_id, cx)
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.is_ok()
|
.log_err();
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
worktree
|
|
||||||
.update(&mut cx, |worktree, cx| {
|
|
||||||
worktree
|
|
||||||
.as_local_mut()
|
|
||||||
.unwrap()
|
|
||||||
.register(project_id, cx)
|
|
||||||
})
|
|
||||||
.await?;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(worktree)
|
Ok(worktree)
|
||||||
@ -3681,6 +3843,7 @@ impl Project {
|
|||||||
false
|
false
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
self.metadata_changed(true, cx);
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3710,6 +3873,7 @@ impl Project {
|
|||||||
self.worktrees
|
self.worktrees
|
||||||
.push(WorktreeHandle::Weak(worktree.downgrade()));
|
.push(WorktreeHandle::Weak(worktree.downgrade()));
|
||||||
}
|
}
|
||||||
|
self.metadata_changed(true, cx);
|
||||||
cx.emit(Event::WorktreeAdded);
|
cx.emit(Event::WorktreeAdded);
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
@ -3992,40 +4156,51 @@ impl Project {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_register_worktree(
|
async fn handle_update_project(
|
||||||
this: ModelHandle<Self>,
|
this: ModelHandle<Self>,
|
||||||
envelope: TypedEnvelope<proto::RegisterWorktree>,
|
envelope: TypedEnvelope<proto::UpdateProject>,
|
||||||
client: Arc<Client>,
|
client: Arc<Client>,
|
||||||
mut cx: AsyncAppContext,
|
mut cx: AsyncAppContext,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
this.update(&mut cx, |this, cx| {
|
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 replica_id = this.replica_id();
|
||||||
|
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 {
|
let worktree = proto::Worktree {
|
||||||
id: envelope.payload.worktree_id,
|
id: worktree.id,
|
||||||
root_name: envelope.payload.root_name,
|
root_name: worktree.root_name,
|
||||||
entries: Default::default(),
|
entries: Default::default(),
|
||||||
diagnostic_summaries: Default::default(),
|
diagnostic_summaries: Default::default(),
|
||||||
visible: envelope.payload.visible,
|
visible: worktree.visible,
|
||||||
scan_id: 0,
|
scan_id: 0,
|
||||||
};
|
};
|
||||||
let (worktree, load_task) =
|
let (worktree, load_task) =
|
||||||
Worktree::remote(remote_id, replica_id, worktree, client, cx);
|
Worktree::remote(remote_id, replica_id, worktree, client.clone(), cx);
|
||||||
this.add_worktree(&worktree, cx);
|
this.add_worktree(&worktree, cx);
|
||||||
load_task.detach();
|
load_task.detach();
|
||||||
Ok(())
|
}
|
||||||
})
|
}
|
||||||
|
|
||||||
|
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(())
|
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 {
|
impl WorktreeHandle {
|
||||||
pub fn upgrade(&self, cx: &AppContext) -> Option<ModelHandle<Worktree>> {
|
pub fn upgrade(&self, cx: &AppContext) -> Option<ModelHandle<Worktree>> {
|
||||||
match self {
|
match self {
|
||||||
@ -5210,10 +5428,16 @@ impl<'a> Iterator for CandidateSetIter<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Entity for ProjectStore {
|
||||||
|
type Event = ();
|
||||||
|
}
|
||||||
|
|
||||||
impl Entity for Project {
|
impl Entity for Project {
|
||||||
type Event = Event;
|
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 {
|
match &self.client_state {
|
||||||
ProjectClientState::Local { remote_id_rx, .. } => {
|
ProjectClientState::Local { remote_id_rx, .. } => {
|
||||||
if let Some(project_id) = *remote_id_rx.borrow() {
|
if let Some(project_id) = *remote_id_rx.borrow() {
|
||||||
|
@ -68,7 +68,6 @@ pub struct LocalWorktree {
|
|||||||
last_scan_state_rx: watch::Receiver<ScanState>,
|
last_scan_state_rx: watch::Receiver<ScanState>,
|
||||||
_background_scanner_task: Option<Task<()>>,
|
_background_scanner_task: Option<Task<()>>,
|
||||||
poll_task: Option<Task<()>>,
|
poll_task: Option<Task<()>>,
|
||||||
registration: Registration,
|
|
||||||
share: Option<ShareState>,
|
share: Option<ShareState>,
|
||||||
diagnostics: HashMap<Arc<Path>, Vec<DiagnosticEntry<PointUtf16>>>,
|
diagnostics: HashMap<Arc<Path>, Vec<DiagnosticEntry<PointUtf16>>>,
|
||||||
diagnostic_summaries: TreeMap<PathKey, DiagnosticSummary>,
|
diagnostic_summaries: TreeMap<PathKey, DiagnosticSummary>,
|
||||||
@ -129,13 +128,6 @@ enum ScanState {
|
|||||||
Err(Arc<anyhow::Error>),
|
Err(Arc<anyhow::Error>),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Eq, PartialEq)]
|
|
||||||
enum Registration {
|
|
||||||
None,
|
|
||||||
Pending,
|
|
||||||
Done { project_id: u64 },
|
|
||||||
}
|
|
||||||
|
|
||||||
struct ShareState {
|
struct ShareState {
|
||||||
project_id: u64,
|
project_id: u64,
|
||||||
snapshots_tx: Sender<LocalSnapshot>,
|
snapshots_tx: Sender<LocalSnapshot>,
|
||||||
@ -148,19 +140,6 @@ pub enum Event {
|
|||||||
|
|
||||||
impl Entity for Worktree {
|
impl Entity for Worktree {
|
||||||
type Event = Event;
|
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 {
|
impl Worktree {
|
||||||
@ -486,7 +465,6 @@ impl LocalWorktree {
|
|||||||
background_snapshot: Arc::new(Mutex::new(snapshot)),
|
background_snapshot: Arc::new(Mutex::new(snapshot)),
|
||||||
last_scan_state_rx,
|
last_scan_state_rx,
|
||||||
_background_scanner_task: None,
|
_background_scanner_task: None,
|
||||||
registration: Registration::None,
|
|
||||||
share: None,
|
share: None,
|
||||||
poll_task: None,
|
poll_task: None,
|
||||||
diagnostics: Default::default(),
|
diagnostics: Default::default(),
|
||||||
@ -608,6 +586,14 @@ impl LocalWorktree {
|
|||||||
self.snapshot.clone()
|
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)>> {
|
fn load(&self, path: &Path, cx: &mut ModelContext<Worktree>) -> Task<Result<(File, String)>> {
|
||||||
let handle = cx.handle();
|
let handle = cx.handle();
|
||||||
let path = Arc::from(path);
|
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<()>> {
|
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 (share_tx, share_rx) = oneshot::channel();
|
||||||
let (snapshots_to_send_tx, snapshots_to_send_rx) =
|
let (snapshots_to_send_tx, snapshots_to_send_rx) =
|
||||||
smol::channel::unbounded::<LocalSnapshot>();
|
smol::channel::unbounded::<LocalSnapshot>();
|
||||||
@ -1048,7 +995,6 @@ impl LocalWorktree {
|
|||||||
}
|
}
|
||||||
|
|
||||||
cx.spawn_weak(|this, cx| async move {
|
cx.spawn_weak(|this, cx| async move {
|
||||||
register.await?;
|
|
||||||
if let Some(this) = this.upgrade(&cx) {
|
if let Some(this) = this.upgrade(&cx) {
|
||||||
this.read_with(&cx, |this, _| {
|
this.read_with(&cx, |this, _| {
|
||||||
let this = this.as_local().unwrap();
|
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) {
|
pub fn unshare(&mut self) {
|
||||||
self.share.take();
|
self.share.take();
|
||||||
}
|
}
|
||||||
|
@ -31,7 +31,7 @@ tracing = { version = "0.1.34", features = ["log"] }
|
|||||||
zstd = "0.9"
|
zstd = "0.9"
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
prost-build = "0.8"
|
prost-build = "0.9"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
collections = { path = "../collections", features = ["test-support"] }
|
collections = { path = "../collections", features = ["test-support"] }
|
||||||
|
@ -35,8 +35,7 @@ message Envelope {
|
|||||||
OpenBufferForSymbol open_buffer_for_symbol = 28;
|
OpenBufferForSymbol open_buffer_for_symbol = 28;
|
||||||
OpenBufferForSymbolResponse open_buffer_for_symbol_response = 29;
|
OpenBufferForSymbolResponse open_buffer_for_symbol_response = 29;
|
||||||
|
|
||||||
RegisterWorktree register_worktree = 30;
|
UpdateProject update_project = 30;
|
||||||
UnregisterWorktree unregister_worktree = 31;
|
|
||||||
UpdateWorktree update_worktree = 32;
|
UpdateWorktree update_worktree = 32;
|
||||||
|
|
||||||
CreateProjectEntry create_project_entry = 33;
|
CreateProjectEntry create_project_entry = 33;
|
||||||
@ -129,6 +128,11 @@ message UnregisterProject {
|
|||||||
uint64 project_id = 1;
|
uint64 project_id = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message UpdateProject {
|
||||||
|
uint64 project_id = 1;
|
||||||
|
repeated WorktreeMetadata worktrees = 2;
|
||||||
|
}
|
||||||
|
|
||||||
message RequestJoinProject {
|
message RequestJoinProject {
|
||||||
uint64 requester_id = 1;
|
uint64 requester_id = 1;
|
||||||
uint64 project_id = 2;
|
uint64 project_id = 2;
|
||||||
@ -177,18 +181,6 @@ message LeaveProject {
|
|||||||
uint64 project_id = 1;
|
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 {
|
message UpdateWorktree {
|
||||||
uint64 project_id = 1;
|
uint64 project_id = 1;
|
||||||
uint64 worktree_id = 2;
|
uint64 worktree_id = 2;
|
||||||
@ -934,3 +926,9 @@ message ProjectMetadata {
|
|||||||
repeated string worktree_root_names = 3;
|
repeated string worktree_root_names = 3;
|
||||||
repeated uint64 guests = 4;
|
repeated uint64 guests = 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message WorktreeMetadata {
|
||||||
|
uint64 id = 1;
|
||||||
|
string root_name = 2;
|
||||||
|
bool visible = 3;
|
||||||
|
}
|
||||||
|
@ -132,7 +132,6 @@ messages!(
|
|||||||
(Ping, Foreground),
|
(Ping, Foreground),
|
||||||
(ProjectUnshared, Foreground),
|
(ProjectUnshared, Foreground),
|
||||||
(RegisterProject, Foreground),
|
(RegisterProject, Foreground),
|
||||||
(RegisterWorktree, Foreground),
|
|
||||||
(ReloadBuffers, Foreground),
|
(ReloadBuffers, Foreground),
|
||||||
(ReloadBuffersResponse, Foreground),
|
(ReloadBuffersResponse, Foreground),
|
||||||
(RemoveProjectCollaborator, Foreground),
|
(RemoveProjectCollaborator, Foreground),
|
||||||
@ -151,7 +150,6 @@ messages!(
|
|||||||
(Test, Foreground),
|
(Test, Foreground),
|
||||||
(Unfollow, Foreground),
|
(Unfollow, Foreground),
|
||||||
(UnregisterProject, Foreground),
|
(UnregisterProject, Foreground),
|
||||||
(UnregisterWorktree, Foreground),
|
|
||||||
(UpdateBuffer, Foreground),
|
(UpdateBuffer, Foreground),
|
||||||
(UpdateBufferFile, Foreground),
|
(UpdateBufferFile, Foreground),
|
||||||
(UpdateContacts, Foreground),
|
(UpdateContacts, Foreground),
|
||||||
@ -159,6 +157,7 @@ messages!(
|
|||||||
(UpdateFollowers, Foreground),
|
(UpdateFollowers, Foreground),
|
||||||
(UpdateInviteInfo, Foreground),
|
(UpdateInviteInfo, Foreground),
|
||||||
(UpdateLanguageServer, Foreground),
|
(UpdateLanguageServer, Foreground),
|
||||||
|
(UpdateProject, Foreground),
|
||||||
(UpdateWorktree, Foreground),
|
(UpdateWorktree, Foreground),
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -192,7 +191,6 @@ request_messages!(
|
|||||||
(PerformRename, PerformRenameResponse),
|
(PerformRename, PerformRenameResponse),
|
||||||
(PrepareRename, PrepareRenameResponse),
|
(PrepareRename, PrepareRenameResponse),
|
||||||
(RegisterProject, RegisterProjectResponse),
|
(RegisterProject, RegisterProjectResponse),
|
||||||
(RegisterWorktree, Ack),
|
|
||||||
(ReloadBuffers, ReloadBuffersResponse),
|
(ReloadBuffers, ReloadBuffersResponse),
|
||||||
(RequestContact, Ack),
|
(RequestContact, Ack),
|
||||||
(RemoveContact, Ack),
|
(RemoveContact, Ack),
|
||||||
@ -202,6 +200,7 @@ request_messages!(
|
|||||||
(SearchProject, SearchProjectResponse),
|
(SearchProject, SearchProjectResponse),
|
||||||
(SendChannelMessage, SendChannelMessageResponse),
|
(SendChannelMessage, SendChannelMessageResponse),
|
||||||
(Test, Test),
|
(Test, Test),
|
||||||
|
(UnregisterProject, Ack),
|
||||||
(UpdateBuffer, Ack),
|
(UpdateBuffer, Ack),
|
||||||
(UpdateWorktree, Ack),
|
(UpdateWorktree, Ack),
|
||||||
);
|
);
|
||||||
@ -242,13 +241,12 @@ entity_messages!(
|
|||||||
StartLanguageServer,
|
StartLanguageServer,
|
||||||
Unfollow,
|
Unfollow,
|
||||||
UnregisterProject,
|
UnregisterProject,
|
||||||
UnregisterWorktree,
|
|
||||||
UpdateBuffer,
|
UpdateBuffer,
|
||||||
UpdateBufferFile,
|
UpdateBufferFile,
|
||||||
UpdateDiagnosticSummary,
|
UpdateDiagnosticSummary,
|
||||||
UpdateFollowers,
|
UpdateFollowers,
|
||||||
UpdateLanguageServer,
|
UpdateLanguageServer,
|
||||||
RegisterWorktree,
|
UpdateProject,
|
||||||
UpdateWorktree,
|
UpdateWorktree,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -6,4 +6,4 @@ pub use conn::Connection;
|
|||||||
pub use peer::*;
|
pub use peer::*;
|
||||||
mod macros;
|
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)]
|
#[derive(Clone)]
|
||||||
pub struct Settings {
|
pub struct Settings {
|
||||||
|
pub projects_online_by_default: bool,
|
||||||
pub buffer_font_family: FamilyId,
|
pub buffer_font_family: FamilyId,
|
||||||
pub buffer_font_size: f32,
|
pub buffer_font_size: f32,
|
||||||
pub default_buffer_font_size: f32,
|
pub default_buffer_font_size: f32,
|
||||||
@ -49,6 +50,8 @@ pub enum SoftWrap {
|
|||||||
|
|
||||||
#[derive(Clone, Debug, Default, Deserialize, JsonSchema)]
|
#[derive(Clone, Debug, Default, Deserialize, JsonSchema)]
|
||||||
pub struct SettingsFileContent {
|
pub struct SettingsFileContent {
|
||||||
|
#[serde(default)]
|
||||||
|
pub projects_online_by_default: Option<bool>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub buffer_font_family: Option<String>,
|
pub buffer_font_family: Option<String>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
@ -81,6 +84,7 @@ impl Settings {
|
|||||||
preferred_line_length: 80,
|
preferred_line_length: 80,
|
||||||
language_overrides: Default::default(),
|
language_overrides: Default::default(),
|
||||||
format_on_save: true,
|
format_on_save: true,
|
||||||
|
projects_online_by_default: true,
|
||||||
theme,
|
theme,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -135,6 +139,7 @@ impl Settings {
|
|||||||
preferred_line_length: 80,
|
preferred_line_length: 80,
|
||||||
format_on_save: true,
|
format_on_save: true,
|
||||||
language_overrides: Default::default(),
|
language_overrides: Default::default(),
|
||||||
|
projects_online_by_default: true,
|
||||||
theme: gpui::fonts::with_font_cache(cx.font_cache().clone(), || Default::default()),
|
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.buffer_font_size, data.buffer_font_size);
|
||||||
merge(&mut self.default_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);
|
merge(&mut self.vim_mode, data.vim_mode);
|
||||||
|
@ -13,5 +13,5 @@ log = { version = "0.4.16", features = ["kv_unstable_serde"] }
|
|||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
ctor = "0.1"
|
ctor = "0.1"
|
||||||
env_logger = "0.8"
|
env_logger = "0.9"
|
||||||
rand = "0.8.3"
|
rand = "0.8.3"
|
||||||
|
@ -28,5 +28,5 @@ collections = { path = "../collections", features = ["test-support"] }
|
|||||||
gpui = { path = "../gpui", features = ["test-support"] }
|
gpui = { path = "../gpui", features = ["test-support"] }
|
||||||
util = { path = "../util", features = ["test-support"] }
|
util = { path = "../util", features = ["test-support"] }
|
||||||
ctor = "0.1"
|
ctor = "0.1"
|
||||||
env_logger = "0.8"
|
env_logger = "0.9"
|
||||||
rand = "0.8.3"
|
rand = "0.8.3"
|
||||||
|
@ -279,8 +279,9 @@ pub struct ContactsPanel {
|
|||||||
pub contact_username: ContainedText,
|
pub contact_username: ContainedText,
|
||||||
pub contact_button: Interactive<IconButton>,
|
pub contact_button: Interactive<IconButton>,
|
||||||
pub contact_button_spacing: f32,
|
pub contact_button_spacing: f32,
|
||||||
pub disabled_contact_button: IconButton,
|
pub disabled_button: IconButton,
|
||||||
pub tree_branch: Interactive<TreeBranch>,
|
pub tree_branch: Interactive<TreeBranch>,
|
||||||
|
pub private_button: Interactive<IconButton>,
|
||||||
pub section_icon_size: f32,
|
pub section_icon_size: f32,
|
||||||
pub invite_row: Interactive<ContainedLabel>,
|
pub invite_row: Interactive<ContainedLabel>,
|
||||||
}
|
}
|
||||||
@ -318,7 +319,7 @@ pub struct Icon {
|
|||||||
pub path: String,
|
pub path: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Deserialize, Default)]
|
#[derive(Deserialize, Clone, Copy, Default)]
|
||||||
pub struct IconButton {
|
pub struct IconButton {
|
||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
pub container: ContainerStyle,
|
pub container: ContainerStyle,
|
||||||
|
@ -85,9 +85,10 @@ impl WaitingRoom {
|
|||||||
project_id,
|
project_id,
|
||||||
app_state.client.clone(),
|
app_state.client.clone(),
|
||||||
app_state.user_store.clone(),
|
app_state.user_store.clone(),
|
||||||
|
app_state.project_store.clone(),
|
||||||
app_state.languages.clone(),
|
app_state.languages.clone(),
|
||||||
app_state.fs.clone(),
|
app_state.fs.clone(),
|
||||||
&mut cx,
|
cx.clone(),
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
|
@ -17,19 +17,20 @@ use gpui::{
|
|||||||
color::Color,
|
color::Color,
|
||||||
elements::*,
|
elements::*,
|
||||||
geometry::{rect::RectF, vector::vec2f, PathBuilder},
|
geometry::{rect::RectF, vector::vec2f, PathBuilder},
|
||||||
impl_internal_actions,
|
impl_actions, impl_internal_actions,
|
||||||
json::{self, ToJson},
|
json::{self, ToJson},
|
||||||
platform::{CursorStyle, WindowOptions},
|
platform::{CursorStyle, WindowOptions},
|
||||||
AnyModelHandle, AnyViewHandle, AppContext, AsyncAppContext, Border, Entity, ImageData,
|
AnyModelHandle, AnyViewHandle, AppContext, AsyncAppContext, Border, Entity, ImageData,
|
||||||
ModelHandle, MutableAppContext, PathPromptOptions, PromptLevel, RenderContext, Task, View,
|
ModelContext, ModelHandle, MutableAppContext, PathPromptOptions, PromptLevel, RenderContext,
|
||||||
ViewContext, ViewHandle, WeakViewHandle,
|
Task, View, ViewContext, ViewHandle, WeakViewHandle,
|
||||||
};
|
};
|
||||||
use language::LanguageRegistry;
|
use language::LanguageRegistry;
|
||||||
use log::error;
|
use log::error;
|
||||||
pub use pane::*;
|
pub use pane::*;
|
||||||
pub use pane_group::*;
|
pub use pane_group::*;
|
||||||
use postage::prelude::Stream;
|
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 settings::Settings;
|
||||||
use sidebar::{Side, Sidebar, SidebarButtons, ToggleSidebarItem, ToggleSidebarItemFocus};
|
use sidebar::{Side, Sidebar, SidebarButtons, ToggleSidebarItem, ToggleSidebarItemFocus};
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
@ -98,6 +99,12 @@ pub struct OpenPaths {
|
|||||||
pub paths: Vec<PathBuf>,
|
pub paths: Vec<PathBuf>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Deserialize)]
|
||||||
|
pub struct ToggleProjectOnline {
|
||||||
|
#[serde(skip_deserializing)]
|
||||||
|
pub project: Option<ModelHandle<Project>>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct ToggleFollow(pub PeerId);
|
pub struct ToggleFollow(pub PeerId);
|
||||||
|
|
||||||
@ -116,6 +123,7 @@ impl_internal_actions!(
|
|||||||
RemoveFolderFromProject
|
RemoveFolderFromProject
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
impl_actions!(workspace, [ToggleProjectOnline]);
|
||||||
|
|
||||||
pub fn init(app_state: Arc<AppState>, cx: &mut MutableAppContext) {
|
pub fn init(app_state: Arc<AppState>, cx: &mut MutableAppContext) {
|
||||||
pane::init(cx);
|
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_async_action(Workspace::save_all);
|
||||||
cx.add_action(Workspace::add_folder_to_project);
|
cx.add_action(Workspace::add_folder_to_project);
|
||||||
cx.add_action(Workspace::remove_folder_from_project);
|
cx.add_action(Workspace::remove_folder_from_project);
|
||||||
|
cx.add_action(Workspace::toggle_project_online);
|
||||||
cx.add_action(
|
cx.add_action(
|
||||||
|workspace: &mut Workspace, _: &Unfollow, cx: &mut ViewContext<Workspace>| {
|
|workspace: &mut Workspace, _: &Unfollow, cx: &mut ViewContext<Workspace>| {
|
||||||
let pane = workspace.active_pane().clone();
|
let pane = workspace.active_pane().clone();
|
||||||
@ -222,6 +231,7 @@ pub struct AppState {
|
|||||||
pub themes: Arc<ThemeRegistry>,
|
pub themes: Arc<ThemeRegistry>,
|
||||||
pub client: Arc<client::Client>,
|
pub client: Arc<client::Client>,
|
||||||
pub user_store: ModelHandle<client::UserStore>,
|
pub user_store: ModelHandle<client::UserStore>,
|
||||||
|
pub project_store: ModelHandle<ProjectStore>,
|
||||||
pub fs: Arc<dyn fs::Fs>,
|
pub fs: Arc<dyn fs::Fs>,
|
||||||
pub build_window_options: fn() -> WindowOptions<'static>,
|
pub build_window_options: fn() -> WindowOptions<'static>,
|
||||||
pub initialize_workspace: fn(&mut Workspace, &Arc<AppState>, &mut ViewContext<Workspace>),
|
pub initialize_workspace: fn(&mut Workspace, &Arc<AppState>, &mut ViewContext<Workspace>),
|
||||||
@ -682,6 +692,7 @@ impl AppState {
|
|||||||
let languages = Arc::new(LanguageRegistry::test());
|
let languages = Arc::new(LanguageRegistry::test());
|
||||||
let http_client = client::test::FakeHttpClient::with_404_response();
|
let http_client = client::test::FakeHttpClient::with_404_response();
|
||||||
let client = Client::new(http_client.clone());
|
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 user_store = cx.add_model(|cx| UserStore::new(client.clone(), http_client, cx));
|
||||||
let themes = ThemeRegistry::new((), cx.font_cache().clone());
|
let themes = ThemeRegistry::new((), cx.font_cache().clone());
|
||||||
Arc::new(Self {
|
Arc::new(Self {
|
||||||
@ -690,6 +701,7 @@ impl AppState {
|
|||||||
fs,
|
fs,
|
||||||
languages,
|
languages,
|
||||||
user_store,
|
user_store,
|
||||||
|
project_store,
|
||||||
initialize_workspace: |_, _, _| {},
|
initialize_workspace: |_, _, _| {},
|
||||||
build_window_options: || Default::default(),
|
build_window_options: || Default::default(),
|
||||||
})
|
})
|
||||||
@ -837,10 +849,7 @@ impl Workspace {
|
|||||||
_observe_current_user,
|
_observe_current_user,
|
||||||
};
|
};
|
||||||
this.project_remote_id_changed(this.project.read(cx).remote_id(), cx);
|
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
|
this
|
||||||
}
|
}
|
||||||
@ -876,20 +885,6 @@ impl Workspace {
|
|||||||
self.project.read(cx).worktrees(cx)
|
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 {
|
pub fn worktree_scans_complete(&self, cx: &AppContext) -> impl Future<Output = ()> + 'static {
|
||||||
let futures = self
|
let futures = self
|
||||||
.worktrees(cx)
|
.worktrees(cx)
|
||||||
@ -1054,6 +1049,17 @@ impl Workspace {
|
|||||||
.update(cx, |project, cx| project.remove_worktree(*worktree_id, cx));
|
.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(
|
fn project_path_for_path(
|
||||||
&self,
|
&self,
|
||||||
abs_path: &Path,
|
abs_path: &Path,
|
||||||
@ -1668,8 +1674,15 @@ impl Workspace {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn render_titlebar(&self, theme: &Theme, cx: &mut RenderContext<Self>) -> ElementBox {
|
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();
|
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(
|
ConstrainedBox::new(
|
||||||
Container::new(
|
Container::new(
|
||||||
@ -1686,7 +1699,7 @@ impl Workspace {
|
|||||||
.with_children(self.render_collaborators(theme, cx))
|
.with_children(self.render_collaborators(theme, cx))
|
||||||
.with_children(self.render_current_user(
|
.with_children(self.render_current_user(
|
||||||
self.user_store.read(cx).current_user().as_ref(),
|
self.user_store.read(cx).current_user().as_ref(),
|
||||||
self.project.read(cx).replica_id(),
|
replica_id,
|
||||||
theme,
|
theme,
|
||||||
cx,
|
cx,
|
||||||
))
|
))
|
||||||
@ -1714,6 +1727,7 @@ impl Workspace {
|
|||||||
|
|
||||||
fn update_window_title(&mut self, cx: &mut ViewContext<Self>) {
|
fn update_window_title(&mut self, cx: &mut ViewContext<Self>) {
|
||||||
let mut title = String::new();
|
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)) {
|
if let Some(path) = self.active_item(cx).and_then(|item| item.project_path(cx)) {
|
||||||
let filename = path
|
let filename = path
|
||||||
.path
|
.path
|
||||||
@ -1721,8 +1735,7 @@ impl Workspace {
|
|||||||
.map(|s| s.to_string_lossy())
|
.map(|s| s.to_string_lossy())
|
||||||
.or_else(|| {
|
.or_else(|| {
|
||||||
Some(Cow::Borrowed(
|
Some(Cow::Borrowed(
|
||||||
self.project()
|
project
|
||||||
.read(cx)
|
|
||||||
.worktree_for_id(path.worktree_id, cx)?
|
.worktree_for_id(path.worktree_id, cx)?
|
||||||
.read(cx)
|
.read(cx)
|
||||||
.root_name(),
|
.root_name(),
|
||||||
@ -1733,22 +1746,18 @@ impl Workspace {
|
|||||||
title.push_str(" — ");
|
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() {
|
if title.is_empty() {
|
||||||
title = "empty project".to_string();
|
title = "empty project".to_string();
|
||||||
}
|
}
|
||||||
cx.set_window_title(&title);
|
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> {
|
fn render_collaborators(&self, theme: &Theme, cx: &mut RenderContext<Self>) -> Vec<ElementBox> {
|
||||||
let mut collaborators = self
|
let mut collaborators = self
|
||||||
.project
|
.project
|
||||||
@ -2365,6 +2374,22 @@ fn open(_: &Open, cx: &mut MutableAppContext) {
|
|||||||
|
|
||||||
pub struct WorkspaceCreated(WeakViewHandle<Workspace>);
|
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(
|
pub fn open_paths(
|
||||||
abs_paths: &[PathBuf],
|
abs_paths: &[PathBuf],
|
||||||
app_state: &Arc<AppState>,
|
app_state: &Arc<AppState>,
|
||||||
@ -2376,26 +2401,13 @@ pub fn open_paths(
|
|||||||
log::info!("open paths {:?}", abs_paths);
|
log::info!("open paths {:?}", abs_paths);
|
||||||
|
|
||||||
// Open paths in existing workspace if possible
|
// Open paths in existing workspace if possible
|
||||||
let mut existing = None;
|
let existing =
|
||||||
for window_id in cx.window_ids().collect::<Vec<_>>() {
|
activate_workspace_for_project(cx, |project, cx| project.contains_paths(abs_paths, cx));
|
||||||
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 app_state = app_state.clone();
|
let app_state = app_state.clone();
|
||||||
let abs_paths = abs_paths.to_vec();
|
let abs_paths = abs_paths.to_vec();
|
||||||
cx.spawn(|mut cx| async move {
|
cx.spawn(|mut cx| async move {
|
||||||
|
let mut new_project = None;
|
||||||
let workspace = if let Some(existing) = existing {
|
let workspace = if let Some(existing) = existing {
|
||||||
existing
|
existing
|
||||||
} else {
|
} else {
|
||||||
@ -2405,16 +2417,17 @@ pub fn open_paths(
|
|||||||
.contains(&false);
|
.contains(&false);
|
||||||
|
|
||||||
cx.add_window((app_state.build_window_options)(), |cx| {
|
cx.add_window((app_state.build_window_options)(), |cx| {
|
||||||
let mut workspace = Workspace::new(
|
let project = Project::local(
|
||||||
Project::local(
|
false,
|
||||||
app_state.client.clone(),
|
app_state.client.clone(),
|
||||||
app_state.user_store.clone(),
|
app_state.user_store.clone(),
|
||||||
|
app_state.project_store.clone(),
|
||||||
app_state.languages.clone(),
|
app_state.languages.clone(),
|
||||||
app_state.fs.clone(),
|
app_state.fs.clone(),
|
||||||
cx,
|
cx,
|
||||||
),
|
|
||||||
cx,
|
|
||||||
);
|
);
|
||||||
|
new_project = Some(project.clone());
|
||||||
|
let mut workspace = Workspace::new(project, cx);
|
||||||
(app_state.initialize_workspace)(&mut workspace, &app_state, cx);
|
(app_state.initialize_workspace)(&mut workspace, &app_state, cx);
|
||||||
if contains_directory {
|
if contains_directory {
|
||||||
workspace.toggle_sidebar_item(
|
workspace.toggle_sidebar_item(
|
||||||
@ -2433,6 +2446,14 @@ pub fn open_paths(
|
|||||||
let items = workspace
|
let items = workspace
|
||||||
.update(&mut cx, |workspace, cx| workspace.open_paths(abs_paths, cx))
|
.update(&mut cx, |workspace, cx| workspace.open_paths(abs_paths, cx))
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
|
if let Some(project) = new_project {
|
||||||
|
project
|
||||||
|
.update(&mut cx, |project, cx| project.restore_state(cx))
|
||||||
|
.await
|
||||||
|
.log_err();
|
||||||
|
}
|
||||||
|
|
||||||
(workspace, items)
|
(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 (window_id, workspace) = cx.add_window((app_state.build_window_options)(), |cx| {
|
||||||
let mut workspace = Workspace::new(
|
let mut workspace = Workspace::new(
|
||||||
Project::local(
|
Project::local(
|
||||||
|
false,
|
||||||
app_state.client.clone(),
|
app_state.client.clone(),
|
||||||
app_state.user_store.clone(),
|
app_state.user_store.clone(),
|
||||||
|
app_state.project_store.clone(),
|
||||||
app_state.languages.clone(),
|
app_state.languages.clone(),
|
||||||
app_state.fs.clone(),
|
app_state.fs.clone(),
|
||||||
cx,
|
cx,
|
||||||
|
@ -59,7 +59,7 @@ chrono = "0.4"
|
|||||||
ctor = "0.1.20"
|
ctor = "0.1.20"
|
||||||
dirs = "3.0"
|
dirs = "3.0"
|
||||||
easy-parallel = "3.1.0"
|
easy-parallel = "3.1.0"
|
||||||
env_logger = "0.8"
|
env_logger = "0.9"
|
||||||
futures = "0.3"
|
futures = "0.3"
|
||||||
http-auth-basic = "0.1.3"
|
http-auth-basic = "0.1.3"
|
||||||
ignore = "0.4"
|
ignore = "0.4"
|
||||||
@ -108,7 +108,7 @@ client = { path = "../client", features = ["test-support"] }
|
|||||||
settings = { path = "../settings", features = ["test-support"] }
|
settings = { path = "../settings", features = ["test-support"] }
|
||||||
util = { path = "../util", features = ["test-support"] }
|
util = { path = "../util", features = ["test-support"] }
|
||||||
workspace = { path = "../workspace", 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"] }
|
serde_json = { version = "1.0", features = ["preserve_order"] }
|
||||||
unindent = "0.1.7"
|
unindent = "0.1.7"
|
||||||
|
|
||||||
|
@ -23,7 +23,7 @@ use gpui::{executor::Background, App, AssetSource, AsyncAppContext, Task};
|
|||||||
use isahc::{config::Configurable, AsyncBody, Request};
|
use isahc::{config::Configurable, AsyncBody, Request};
|
||||||
use log::LevelFilter;
|
use log::LevelFilter;
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use project::Fs;
|
use project::{Fs, ProjectStore};
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use settings::{self, KeymapFileContent, Settings, SettingsFileContent};
|
use settings::{self, KeymapFileContent, Settings, SettingsFileContent};
|
||||||
use smol::process::Command;
|
use smol::process::Command;
|
||||||
@ -48,9 +48,10 @@ use zed::{
|
|||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let http = http::client();
|
let http = http::client();
|
||||||
let logs_dir_path = dirs::home_dir()
|
let home_dir = dirs::home_dir().expect("could not find home dir");
|
||||||
.expect("could not find home dir")
|
let db_dir_path = home_dir.join("Library/Application Support/Zed");
|
||||||
.join("Library/Logs/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");
|
fs::create_dir_all(&logs_dir_path).expect("could not create logs path");
|
||||||
init_logger(&logs_dir_path);
|
init_logger(&logs_dir_path);
|
||||||
|
|
||||||
@ -59,6 +60,11 @@ fn main() {
|
|||||||
.or_else(|| app.platform().app_version().ok())
|
.or_else(|| app.platform().app_version().ok())
|
||||||
.map_or("dev".to_string(), |v| v.to_string());
|
.map_or("dev".to_string(), |v| v.to_string());
|
||||||
init_panic_hook(logs_dir_path, app_version, http.clone(), app.background());
|
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);
|
load_embedded_fonts(&app);
|
||||||
|
|
||||||
@ -169,6 +175,7 @@ fn main() {
|
|||||||
search::init(cx);
|
search::init(cx);
|
||||||
vim::init(cx);
|
vim::init(cx);
|
||||||
|
|
||||||
|
let db = cx.background().block(db);
|
||||||
let (settings_file, keymap_file) = cx.background().block(config_files).unwrap();
|
let (settings_file, keymap_file) = cx.background().block(config_files).unwrap();
|
||||||
let mut settings_rx = settings_from_files(
|
let mut settings_rx = settings_from_files(
|
||||||
default_settings,
|
default_settings,
|
||||||
@ -204,11 +211,13 @@ fn main() {
|
|||||||
.detach();
|
.detach();
|
||||||
cx.set_global(settings);
|
cx.set_global(settings);
|
||||||
|
|
||||||
|
let project_store = cx.add_model(|_| ProjectStore::new(db));
|
||||||
let app_state = Arc::new(AppState {
|
let app_state = Arc::new(AppState {
|
||||||
languages,
|
languages,
|
||||||
themes,
|
themes,
|
||||||
client: client.clone(),
|
client: client.clone(),
|
||||||
user_store,
|
user_store,
|
||||||
|
project_store,
|
||||||
fs,
|
fs,
|
||||||
build_window_options,
|
build_window_options,
|
||||||
initialize_workspace,
|
initialize_workspace,
|
||||||
|
@ -181,7 +181,12 @@ pub fn initialize_workspace(
|
|||||||
|
|
||||||
let project_panel = ProjectPanel::new(workspace.project().clone(), cx);
|
let project_panel = ProjectPanel::new(workspace.project().clone(), cx);
|
||||||
let contact_panel = cx.add_view(|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| {
|
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 (_, workspace) = cx.add_window((app_state.build_window_options)(), |cx| {
|
||||||
let mut workspace = Workspace::new(
|
let mut workspace = Workspace::new(
|
||||||
Project::local(
|
Project::local(
|
||||||
|
false,
|
||||||
app_state.client.clone(),
|
app_state.client.clone(),
|
||||||
app_state.user_store.clone(),
|
app_state.user_store.clone(),
|
||||||
|
app_state.project_store.clone(),
|
||||||
app_state.languages.clone(),
|
app_state.languages.clone(),
|
||||||
app_state.fs.clone(),
|
app_state.fs.clone(),
|
||||||
cx,
|
cx,
|
||||||
|
@ -68,6 +68,12 @@ export default function contactsPanel(theme: Theme) {
|
|||||||
buttonWidth: 8,
|
buttonWidth: 8,
|
||||||
iconWidth: 8,
|
iconWidth: 8,
|
||||||
},
|
},
|
||||||
|
privateButton: {
|
||||||
|
iconWidth: 8,
|
||||||
|
color: iconColor(theme, "primary"),
|
||||||
|
cornerRadius: 5,
|
||||||
|
buttonWidth: 12,
|
||||||
|
},
|
||||||
rowHeight: 28,
|
rowHeight: 28,
|
||||||
sectionIconSize: 8,
|
sectionIconSize: 8,
|
||||||
headerRow: {
|
headerRow: {
|
||||||
@ -118,7 +124,7 @@ export default function contactsPanel(theme: Theme) {
|
|||||||
background: backgroundColor(theme, 100, "hovered"),
|
background: backgroundColor(theme, 100, "hovered"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
disabledContactButton: {
|
disabledButton: {
|
||||||
...contactButton,
|
...contactButton,
|
||||||
background: backgroundColor(theme, 100),
|
background: backgroundColor(theme, 100),
|
||||||
color: iconColor(theme, "muted"),
|
color: iconColor(theme, "muted"),
|
||||||
|
Loading…
Reference in New Issue
Block a user