diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3bb40ac12e65838f1454f51a8d9bf2888ab727e6..b5929f7912be34fd1d105c6b3f4c7d955c8a3be4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -32,15 +32,24 @@ jobs: path: | ~/.cargo/registry ~/.cargo/git + ~/.rustup target key: ${{ runner.os }}-cargo-${{ hashFiles('Cargo.lock') }} - - name: Install rust + - name: Install Rust if: steps.cache.outputs.cache-hit != 'true' uses: actions-rs/toolchain@v1 with: toolchain: stable + target: x86_64-apple-darwin profile: minimal - name: Run tests run: cargo test --no-fail-fast + + - name: Create and upload app bundle + run: script/bundle + - uses: actions/upload-artifact@v2 + with: + name: Zed.dmg + path: target/release/Zed.dmg diff --git a/Cargo.lock b/Cargo.lock index b8b7d9ad22f080d3e54ffb0fdefc0395b3813b5b..a7fa8a95f7f79083e0f7cbc126f6561305aea5f7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,5 +1,14 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. +[[package]] +name = "addr2line" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a55f82cfe485775d02112886f4169bde0c5894d75e79ead7eafe7e40a25e45f7" +dependencies = [ + "gimli", +] + [[package]] name = "adler" version = "1.0.2" @@ -27,7 +36,7 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" dependencies = [ - "winapi", + "winapi 0.3.9", ] [[package]] @@ -36,6 +45,12 @@ version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "afddf7f520a80dbf76e6f50a35bca42a2331ef227a28b3b6dc5c2e2338d114b1" +[[package]] +name = "ar" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35c7a5669cb64f085739387e1308b74e6d44022464b7f1b63bbd4ceb6379ec31" + [[package]] name = "arrayref" version = "0.3.6" @@ -101,7 +116,7 @@ dependencies = [ "polling", "vec-arena", "waker-fn", - "winapi", + "winapi 0.3.9", ] [[package]] @@ -138,7 +153,7 @@ dependencies = [ "futures-lite", "once_cell", "signal-hook", - "winapi", + "winapi 0.3.9", ] [[package]] @@ -152,7 +167,7 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3410529e8288c463bedb5930f82833bc0c90e5d2fe639a56582a4d09220b281" dependencies = [ - "autocfg", + "autocfg 1.0.1", ] [[package]] @@ -169,15 +184,35 @@ checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" dependencies = [ "hermit-abi", "libc", - "winapi", + "winapi 0.3.9", ] +[[package]] +name = "autocfg" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d49d90015b3c36167a20fe2810c5cd875ad504b39cff3d4eae7977e6b7c1cb2" + [[package]] name = "autocfg" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" +[[package]] +name = "backtrace" +version = "0.3.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d117600f438b1707d4e4ae15d3595657288f8235a0eb593e80ecc98ab34e1bc" +dependencies = [ + "addr2line", + "cfg-if 1.0.0", + "libc", + "miniz_oxide 0.4.4", + "object", + "rustc-demangle", +] + [[package]] name = "base64" version = "0.13.0" @@ -190,7 +225,7 @@ version = "0.57.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd4865004a46a0aafb2a0a5eb19d3c9fc46ee5f063a6cfc605c69ac9ecf5263d" dependencies = [ - "bitflags", + "bitflags 1.2.1", "cexpr", "clang-sys", "clap", @@ -207,6 +242,12 @@ dependencies = [ "which", ] +[[package]] +name = "bitflags" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aad18937a628ec6abcd26d1489012cc0e18c21798210f491af69ded9b881106d" + [[package]] name = "bitflags" version = "1.2.1" @@ -265,12 +306,52 @@ version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae44d1a3d5a19df61dd0c8beb138458ac2a53a7ac09eba97d55592540004306b" +[[package]] +name = "cab" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9edc5cbf053f06eff0700c840a4e8ef14c6d3c39f277b8340f03481ebac99d0" +dependencies = [ + "byteorder", + "chrono", + "flate2", +] + [[package]] name = "cache-padded" version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "631ae5198c9be5e753e5cc215e1bd73c2b466a3565173db433f52bb9d3e66dba" +[[package]] +name = "cargo-bundle" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e23d4864473346d528b06e570ee2c3d8ad4eeee482c7aa5ec3b62f297e2976f" +dependencies = [ + "ar", + "cab", + "chrono", + "clap", + "dirs 1.0.5", + "error-chain", + "glob 0.2.11", + "icns", + "image", + "libflate", + "md5", + "msi", + "serde 1.0.125", + "serde_derive", + "strsim 0.7.0", + "tar", + "target_build_utils", + "term", + "toml", + "uuid", + "walkdir", +] + [[package]] name = "cc" version = "1.0.67" @@ -286,6 +367,16 @@ dependencies = [ "nom", ] +[[package]] +name = "cfb" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e7fb075b9b54e939006aa12e1f6cd2d3194041ff4ebe7f2efcbedf18f25b667" +dependencies = [ + "byteorder", + "uuid", +] + [[package]] name = "cfg-if" version = "0.1.10" @@ -306,9 +397,9 @@ checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" dependencies = [ "libc", "num-integer", - "num-traits", + "num-traits 0.2.14", "time", - "winapi", + "winapi 0.3.9", ] [[package]] @@ -317,7 +408,7 @@ version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f54d78e30b388d4815220c8dd03fea5656b6c6d32adb59e89061552a102f8da1" dependencies = [ - "glob", + "glob 0.3.0", "libc", "libloading", ] @@ -330,13 +421,22 @@ checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002" dependencies = [ "ansi_term", "atty", - "bitflags", - "strsim", + "bitflags 1.2.1", + "strsim 0.8.0", "textwrap", "unicode-width", "vec_map", ] +[[package]] +name = "cloudabi" +version = "0.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" +dependencies = [ + "bitflags 1.2.1", +] + [[package]] name = "cmake" version = "0.1.45" @@ -351,7 +451,7 @@ name = "cocoa" version = "0.24.0" source = "git+https://github.com/servo/core-foundation-rs?rev=e9a65bb15d591ec22649e03659db8095d4f2dd60#e9a65bb15d591ec22649e03659db8095d4f2dd60" dependencies = [ - "bitflags", + "bitflags 1.2.1", "block", "cocoa-foundation", "core-foundation", @@ -366,7 +466,7 @@ name = "cocoa-foundation" version = "0.1.0" source = "git+https://github.com/servo/core-foundation-rs?rev=e9a65bb15d591ec22649e03659db8095d4f2dd60#e9a65bb15d591ec22649e03659db8095d4f2dd60" dependencies = [ - "bitflags", + "bitflags 1.2.1", "block", "core-foundation", "core-graphics-types", @@ -375,6 +475,12 @@ dependencies = [ "objc", ] +[[package]] +name = "color_quant" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" + [[package]] name = "concurrent-queue" version = "1.2.2" @@ -409,7 +515,7 @@ name = "core-graphics" version = "0.22.2" source = "git+https://github.com/servo/core-foundation-rs?rev=e9a65bb15d591ec22649e03659db8095d4f2dd60#e9a65bb15d591ec22649e03659db8095d4f2dd60" dependencies = [ - "bitflags", + "bitflags 1.2.1", "core-foundation", "core-graphics-types", "foreign-types", @@ -421,7 +527,7 @@ name = "core-graphics-types" version = "0.1.1" source = "git+https://github.com/servo/core-foundation-rs?rev=e9a65bb15d591ec22649e03659db8095d4f2dd60#e9a65bb15d591ec22649e03659db8095d4f2dd60" dependencies = [ - "bitflags", + "bitflags 1.2.1", "core-foundation", "foreign-types", "libc", @@ -458,6 +564,31 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "crossbeam-deque" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94af6efb46fef72616855b036a624cf27ba656ffc9be1b9a3c931cfc7749a9a9" +dependencies = [ + "cfg-if 1.0.0", + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d60ab4a8dba064f2fbb5aa270c28da5cf4bbd0e72dae1140a6b0353a779dbe00" +dependencies = [ + "cfg-if 1.0.0", + "crossbeam-utils", + "lazy_static", + "loom", + "memoffset", + "scopeguard", +] + [[package]] name = "crossbeam-queue" version = "0.3.1" @@ -474,7 +605,7 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bae8f328835f8f5a6ceb6a7842a7f2d0c03692adb5c889347235d59194731fe3" dependencies = [ - "autocfg", + "autocfg 1.0.1", "cfg-if 1.0.0", "lazy_static", "loom", @@ -499,6 +630,16 @@ dependencies = [ "matches", ] +[[package]] +name = "deflate" +version = "0.7.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "707b6a7b384888a70c8d2e8650b3e60170dfc6a67bb4aa67b6dfca57af4bedb4" +dependencies = [ + "adler32", + "byteorder", +] + [[package]] name = "deflate" version = "0.8.6" @@ -509,6 +650,17 @@ dependencies = [ "byteorder", ] +[[package]] +name = "dirs" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fd78930633bd1c6e35c4b42b1df7b0cbc6bc191146e512bb3bedf243fcc3901" +dependencies = [ + "libc", + "redox_users 0.3.5", + "winapi 0.3.9", +] + [[package]] name = "dirs" version = "3.0.1" @@ -536,7 +688,7 @@ checksum = "8e93d7f5705de3e49895a2b5e0b8855a1c27f080192ae9c32a6432d50741a57a" dependencies = [ "libc", "redox_users 0.3.5", - "winapi", + "winapi 0.3.9", ] [[package]] @@ -547,9 +699,15 @@ checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" dependencies = [ "libc", "redox_users 0.4.0", - "winapi", + "winapi 0.3.9", ] +[[package]] +name = "dtoa" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56899898ce76aaf4a0f24d914c97ea6ed976d42fec6ad33fcbb0a1103e07b2b0" + [[package]] name = "dwrote" version = "0.11.0" @@ -558,7 +716,7 @@ checksum = "439a1c2ba5611ad3ed731280541d36d2e9c4ac5e7fb818a27b604bdc5a6aa65b" dependencies = [ "lazy_static", "libc", - "winapi", + "winapi 0.3.9", "wio", ] @@ -568,6 +726,85 @@ version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1dd4afd79212583ff429b913ad6605242ed7eec277e950b1438f300748f948f4" +[[package]] +name = "either" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" + +[[package]] +name = "encoding" +version = "0.2.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b0d943856b990d12d3b55b359144ff341533e516d94098b1d3fc1ac666d36ec" +dependencies = [ + "encoding-index-japanese", + "encoding-index-korean", + "encoding-index-simpchinese", + "encoding-index-singlebyte", + "encoding-index-tradchinese", +] + +[[package]] +name = "encoding-index-japanese" +version = "1.20141219.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04e8b2ff42e9a05335dbf8b5c6f7567e5591d0d916ccef4e0b1710d32a0d0c91" +dependencies = [ + "encoding_index_tests", +] + +[[package]] +name = "encoding-index-korean" +version = "1.20141219.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dc33fb8e6bcba213fe2f14275f0963fd16f0a02c878e3095ecfdf5bee529d81" +dependencies = [ + "encoding_index_tests", +] + +[[package]] +name = "encoding-index-simpchinese" +version = "1.20141219.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d87a7194909b9118fc707194baa434a4e3b0fb6a5a757c73c3adb07aa25031f7" +dependencies = [ + "encoding_index_tests", +] + +[[package]] +name = "encoding-index-singlebyte" +version = "1.20141219.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3351d5acffb224af9ca265f435b859c7c01537c0849754d3db3fdf2bfe2ae84a" +dependencies = [ + "encoding_index_tests", +] + +[[package]] +name = "encoding-index-tradchinese" +version = "1.20141219.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd0e20d5688ce3cab59eb3ef3a2083a5c77bf496cb798dc6fcdb75f323890c18" +dependencies = [ + "encoding_index_tests", +] + +[[package]] +name = "encoding_index_tests" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a246d82be1c9d791c5dfde9a2bd045fc3cbba3fa2b11ad558f27d01712f00569" + +[[package]] +name = "enum_primitive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4551092f4d519593039259a9ed8daedf0da12e5109c5280338073eaeb81180" +dependencies = [ + "num-traits 0.1.43", +] + [[package]] name = "env_logger" version = "0.8.3" @@ -581,6 +818,16 @@ dependencies = [ "termcolor", ] +[[package]] +name = "error-chain" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d2f06b9cac1506ece98fe3231e3cc9c4410ec3d5b1f24ae1c8946f0742cdefc" +dependencies = [ + "backtrace", + "version_check", +] + [[package]] name = "etagere" version = "0.2.4" @@ -597,7 +844,7 @@ version = "0.22.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51e5bac4ec41ece6346fd867815a57a221abdf48f4eb931b033789b5b4b6fc70" dependencies = [ - "num-traits", + "num-traits 0.2.14", ] [[package]] @@ -625,6 +872,18 @@ dependencies = [ "instant", ] +[[package]] +name = "filetime" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d34cfa13a63ae058bfa601fe9e313bbdb3746427c1459185464ce0fcf62e1e8" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "redox_syscall 0.2.5", + "winapi 0.3.9", +] + [[package]] name = "flate2" version = "1.0.20" @@ -660,7 +919,7 @@ name = "font-kit" version = "0.10.0" source = "git+https://github.com/zed-industries/font-kit?rev=8eaf7a918eafa28b0a37dc759e2e0e7683fa24f1#8eaf7a918eafa28b0a37dc759e2e0e7683fa24f1" dependencies = [ - "bitflags", + "bitflags 1.2.1", "byteorder", "core-foundation", "core-graphics", @@ -676,7 +935,7 @@ dependencies = [ "pathfinder_simd", "servo-fontconfig", "walkdir", - "winapi", + "winapi 0.3.9", ] [[package]] @@ -730,7 +989,7 @@ dependencies = [ name = "fsevent" version = "2.0.2" dependencies = [ - "bitflags", + "bitflags 1.2.1", "fsevent-sys", "parking_lot", "tempdir", @@ -837,7 +1096,7 @@ dependencies = [ "libc", "log", "rustc_version", - "winapi", + "winapi 0.3.9", ] [[package]] @@ -862,6 +1121,28 @@ dependencies = [ "wasi 0.10.0+wasi-snapshot-preview1", ] +[[package]] +name = "gif" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2e41945ba23db3bf51b24756d73d81acb4f28d85c3dccc32c6fae904438c25f" +dependencies = [ + "color_quant", + "lzw", +] + +[[package]] +name = "gimli" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6503fe142514ca4799d4c26297c4248239fe8838d827db6bd6065c6ed29a6ce" + +[[package]] +name = "glob" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8be18de09a56b60ed0edf84bc9df007e30040691af7acd1c41874faac5895bfb" + [[package]] name = "glob" version = "0.3.0" @@ -907,15 +1188,15 @@ dependencies = [ "parking_lot", "pathfinder_color", "pathfinder_geometry", - "png", + "png 0.16.8", "postage", "rand 0.8.3", "replace_with", "resvg", "scoped-pool", "seahash", - "serde", - "serde_json", + "serde 1.0.125", + "serde_json 1.0.64", "simplelog", "smallvec", "smol", @@ -945,6 +1226,16 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" +[[package]] +name = "icns" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b324f19f93365e45c7375968a81ff3c52dd99893a48b886099d1664df6d38e68" +dependencies = [ + "byteorder", + "png 0.11.0", +] + [[package]] name = "ignore" version = "0.4.17" @@ -963,16 +1254,48 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "image" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d95816db758249fe16f23a4e23f1a3a817fe11892dbfd1c5836f625324702158" +dependencies = [ + "byteorder", + "enum_primitive", + "gif", + "jpeg-decoder", + "num-iter", + "num-rational", + "num-traits 0.1.43", + "png 0.6.2", + "scoped_threadpool", +] + [[package]] name = "indexmap" version = "1.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "824845a0bf897a9042383849b02c1bc219c2383772efcd5c6f9766fa4b81aef3" dependencies = [ - "autocfg", + "autocfg 1.0.1", "hashbrown", ] +[[package]] +name = "inflate" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7e0062d2dc2f17d2f13750d95316ae8a2ff909af0fda957084f5defd87c43bb" + +[[package]] +name = "inflate" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5f9f47468e9a76a6452271efadc88fe865a82be91fe75e6c0c57b87ccea59d4" +dependencies = [ + "adler32", +] + [[package]] name = "instant" version = "0.1.9" @@ -982,6 +1305,12 @@ dependencies = [ "cfg-if 1.0.0", ] +[[package]] +name = "itoa" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8324a32baf01e2ae060e9de58ed0bc2320c9a2833491ee36cd3b4c414de4db8c" + [[package]] name = "itoa" version = "0.4.7" @@ -993,6 +1322,19 @@ name = "jpeg-decoder" version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "229d53d58899083193af11e15917b5640cd40b29ff475a1fe4ef725deb02d0f2" +dependencies = [ + "rayon", +] + +[[package]] +name = "kernel32-sys" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" +dependencies = [ + "winapi 0.2.8", + "winapi-build", +] [[package]] name = "kurbo" @@ -1021,6 +1363,18 @@ version = "0.2.86" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7282d924be3275cec7f6756ff4121987bc6481325397dde6ba3e7802b1a8b1c" +[[package]] +name = "libflate" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9135df43b1f5d0e333385cb6e7897ecd1a43d7d11b91ac003f4d2c2d2401fdd" +dependencies = [ + "adler32", + "crc32fast", + "rle-decode-fast", + "take_mut", +] + [[package]] name = "libloading" version = "0.7.0" @@ -1028,7 +1382,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f84d96438c15fcd6c3f244c8fce01d1e2b9c6b5623e9c711dc9286d8fc92d6a" dependencies = [ "cfg-if 1.0.0", - "winapi", + "winapi 0.3.9", ] [[package]] @@ -1060,6 +1414,12 @@ dependencies = [ "scoped-tls", ] +[[package]] +name = "lzw" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d947cbb889ed21c2a84be6ffbaebf5b4e0f4340638cba0444907e38b56be084" + [[package]] name = "malloc_buf" version = "0.0.6" @@ -1075,6 +1435,12 @@ version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" +[[package]] +name = "md5" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79c56d6a0b07f9e19282511c83fc5b086364cbae4ba8c7d5f190c3d9b0425a48" + [[package]] name = "memchr" version = "2.3.4" @@ -1099,13 +1465,22 @@ dependencies = [ "libc", ] +[[package]] +name = "memoffset" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83fb6581e8ed1f85fd45c116db8405483899489e38406156c25eb743554361d" +dependencies = [ + "autocfg 1.0.1", +] + [[package]] name = "metal" version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4598d719460ade24c7d91f335daf055bf2a7eec030728ce751814c50cdd6a26c" dependencies = [ - "bitflags", + "bitflags 1.2.1", "block", "cocoa-foundation", "foreign-types", @@ -1129,7 +1504,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b" dependencies = [ "adler", - "autocfg", + "autocfg 1.0.1", +] + +[[package]] +name = "msi" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a20bdea5e04f55fae0d8f89e88beec71822f2d63f61487ff2205d9d05b677923" +dependencies = [ + "byteorder", + "cfb", + "encoding", + "uuid", ] [[package]] @@ -1158,8 +1545,38 @@ version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" dependencies = [ - "autocfg", - "num-traits", + "autocfg 1.0.1", + "num-traits 0.2.14", +] + +[[package]] +name = "num-iter" +version = "0.1.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2021c8337a54d21aca0d59a92577a029af9431cb59b909b03252b9c164fad59" +dependencies = [ + "autocfg 1.0.1", + "num-integer", + "num-traits 0.2.14", +] + +[[package]] +name = "num-rational" +version = "0.1.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee314c74bd753fc86b4780aa9475da469155f3848473a261d2d18e35245a784e" +dependencies = [ + "num-integer", + "num-traits 0.2.14", +] + +[[package]] +name = "num-traits" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92e5113e9fd4cc14ded8e499429f396a20f98c772a47cc8622a736e1ec843c31" +dependencies = [ + "num-traits 0.2.14", ] [[package]] @@ -1168,7 +1585,7 @@ version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" dependencies = [ - "autocfg", + "autocfg 1.0.1", ] [[package]] @@ -1200,6 +1617,12 @@ dependencies = [ "cc", ] +[[package]] +name = "object" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9a7ab5d64814df0fe4a4b5ead45ed6c5f181ee3ff04ba344313a6c80446c5d4" + [[package]] name = "once_cell" version = "1.5.2" @@ -1212,7 +1635,7 @@ version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "766f840da25490628d8e63e529cd21c014f6600c6b8517add12a6fa6167a6218" dependencies = [ - "num-traits", + "num-traits 0.2.14", ] [[package]] @@ -1243,7 +1666,7 @@ dependencies = [ "libc", "redox_syscall 0.2.5", "smallvec", - "winapi", + "winapi 0.3.9", ] [[package]] @@ -1280,6 +1703,44 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" +[[package]] +name = "phf" +version = "0.7.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3da44b85f8e8dfaec21adae67f95d93244b2ecf6ad2a692320598dcc8e6dd18" +dependencies = [ + "phf_shared", +] + +[[package]] +name = "phf_codegen" +version = "0.7.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b03e85129e324ad4166b06b2c7491ae27fe3ec353af72e72cd1654c7225d517e" +dependencies = [ + "phf_generator", + "phf_shared", +] + +[[package]] +name = "phf_generator" +version = "0.7.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09364cc93c159b8b06b1f4dd8a4398984503483891b0c26b867cf431fb132662" +dependencies = [ + "phf_shared", + "rand 0.6.5", +] + +[[package]] +name = "phf_shared" +version = "0.7.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "234f71a15de2288bcb7e3b6515828d22af7ec8598ee6d24c3b526fa0a80b67a0" +dependencies = [ + "siphasher", +] + [[package]] name = "pico-args" version = "0.4.0" @@ -1324,15 +1785,39 @@ version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c" +[[package]] +name = "png" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cb773e9a557edb568ce9935cf783e3cdcabe06a9449d41b3e5506d88e582c82" +dependencies = [ + "bitflags 0.7.0", + "deflate 0.7.20", + "inflate 0.1.1", + "num-iter", +] + +[[package]] +name = "png" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0b0cabbbd20c2d7f06dbf015e06aad59b6ca3d9ed14848783e98af9aaf19925" +dependencies = [ + "bitflags 1.2.1", + "deflate 0.7.20", + "inflate 0.3.4", + "num-iter", +] + [[package]] name = "png" version = "0.16.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c3287920cb847dee3de33d301c463fba14dda99db24214ddf93f83d3021f4c6" dependencies = [ - "bitflags", + "bitflags 1.2.1", "crc32fast", - "deflate", + "deflate 0.8.6", "miniz_oxide 0.3.7", ] @@ -1346,7 +1831,7 @@ dependencies = [ "libc", "log", "wepoll-sys", - "winapi", + "winapi 0.3.9", ] [[package]] @@ -1405,7 +1890,26 @@ dependencies = [ "libc", "rand_core 0.3.1", "rdrand", - "winapi", + "winapi 0.3.9", +] + +[[package]] +name = "rand" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca" +dependencies = [ + "autocfg 0.1.7", + "libc", + "rand_chacha 0.1.1", + "rand_core 0.4.2", + "rand_hc 0.1.0", + "rand_isaac", + "rand_jitter", + "rand_os", + "rand_pcg", + "rand_xorshift", + "winapi 0.3.9", ] [[package]] @@ -1415,9 +1919,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ef9e7e66b4468674bfcb0c81af8b7fa0bb154fa9f28eb840da5c447baeb8d7e" dependencies = [ "libc", - "rand_chacha", + "rand_chacha 0.3.0", "rand_core 0.6.2", - "rand_hc", + "rand_hc 0.3.0", +] + +[[package]] +name = "rand_chacha" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "556d3a1ca6600bfcbab7c7c91ccb085ac7fbbcd70e008a98742e7847f4f7bcef" +dependencies = [ + "autocfg 0.1.7", + "rand_core 0.3.1", ] [[package]] @@ -1454,6 +1968,15 @@ dependencies = [ "getrandom 0.2.2", ] +[[package]] +name = "rand_hc" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b40677c7be09ae76218dc623efbf7b18e34bced3f38883af07bb75630a21bc4" +dependencies = [ + "rand_core 0.3.1", +] + [[package]] name = "rand_hc" version = "0.3.0" @@ -1463,6 +1986,84 @@ dependencies = [ "rand_core 0.6.2", ] +[[package]] +name = "rand_isaac" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ded997c9d5f13925be2a6fd7e66bf1872597f759fd9dd93513dd7e92e5a5ee08" +dependencies = [ + "rand_core 0.3.1", +] + +[[package]] +name = "rand_jitter" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1166d5c91dc97b88d1decc3285bb0a99ed84b05cfd0bc2341bdf2d43fc41e39b" +dependencies = [ + "libc", + "rand_core 0.4.2", + "winapi 0.3.9", +] + +[[package]] +name = "rand_os" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071" +dependencies = [ + "cloudabi", + "fuchsia-cprng", + "libc", + "rand_core 0.4.2", + "rdrand", + "winapi 0.3.9", +] + +[[package]] +name = "rand_pcg" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abf9b09b01790cfe0364f52bf32995ea3c39f4d2dd011eac241d2914146d0b44" +dependencies = [ + "autocfg 0.1.7", + "rand_core 0.4.2", +] + +[[package]] +name = "rand_xorshift" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbf7e9e623549b0e21f6e97cf8ecf247c1a8fd2e8a992ae265314300b2455d5c" +dependencies = [ + "rand_core 0.3.1", +] + +[[package]] +name = "rayon" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b0d8e0819fadc20c74ea8373106ead0600e3a67ef1fe8da56e39b9ae7275674" +dependencies = [ + "autocfg 1.0.1", + "crossbeam-deque", + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ab346ac5921dc62ffa9f89b7a773907511cdfa5490c572ae9be1be33e8afa4a" +dependencies = [ + "crossbeam-channel", + "crossbeam-deque", + "crossbeam-utils", + "lazy_static", + "num_cpus", +] + [[package]] name = "rctree" version = "0.3.3" @@ -1490,7 +2091,7 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94341e4e44e24f6b591b59e47a8a027df12e008d73fd5672dbea9cc22f4507d9" dependencies = [ - "bitflags", + "bitflags 1.2.1", ] [[package]] @@ -1538,7 +2139,7 @@ version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" dependencies = [ - "winapi", + "winapi 0.3.9", ] [[package]] @@ -1556,7 +2157,7 @@ dependencies = [ "jpeg-decoder", "log", "pico-args", - "png", + "png 0.16.8", "rgb", "svgfilters", "tiny-skia", @@ -1572,6 +2173,12 @@ dependencies = [ "bytemuck", ] +[[package]] +name = "rle-decode-fast" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cabe4fa914dec5870285fa7f71f602645da47c486e68486d2b4ceb4a343e90ac" + [[package]] name = "roxmltree" version = "0.14.1" @@ -1626,6 +2233,12 @@ dependencies = [ "walkdir", ] +[[package]] +name = "rustc-demangle" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e3bad0ee36814ca07d7968269dd4b7ec89ec2da10c4bb613928d3077083c232" + [[package]] name = "rustc-hash" version = "1.1.0" @@ -1647,7 +2260,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ab463a295d00f3692e0974a0bfd83c7a9bcd119e27e07c2beecdb1b44a09d10" dependencies = [ - "bitflags", + "bitflags 1.2.1", "bytemuck", "smallvec", "ttf-parser 0.9.0", @@ -1694,6 +2307,12 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2" +[[package]] +name = "scoped_threadpool" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d51f5df5af43ab3f1360b429fa5e0152ac5ce8c0bd6485cae490332e96846a8" + [[package]] name = "scopeguard" version = "1.1.0" @@ -1721,6 +2340,12 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" +[[package]] +name = "serde" +version = "0.9.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34b623917345a631dc9608d5194cc206b3fe6c3554cd1c75b937e55e285254af" + [[package]] name = "serde" version = "1.0.125" @@ -1741,6 +2366,18 @@ dependencies = [ "syn", ] +[[package]] +name = "serde_json" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad8bcf487be7d2e15d3d543f04312de991d631cfe1b43ea0ade69e6a8a5b16a1" +dependencies = [ + "dtoa", + "itoa 0.3.4", + "num-traits 0.1.43", + "serde 0.9.15", +] + [[package]] name = "serde_json" version = "1.0.64" @@ -1748,9 +2385,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "799e97dc9fdae36a5c8b8f2cae9ce2ee9fdce2058c57a93e6099d919fd982f79" dependencies = [ "indexmap", - "itoa", + "itoa 0.4.7", "ryu", - "serde", + "serde 1.0.125", ] [[package]] @@ -1774,6 +2411,12 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "sha1" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc30b1e1e8c40c121ca33b86c23308a090d19974ef001b4bf6e61fd1a0fb095c" + [[package]] name = "shlex" version = "0.1.1" @@ -1857,7 +2500,7 @@ checksum = "122e570113d28d773067fab24266b66753f6ea915758651696b6e35e49f88d6e" dependencies = [ "cfg-if 1.0.0", "libc", - "winapi", + "winapi 0.3.9", ] [[package]] @@ -1866,6 +2509,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "strsim" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb4f380125926a99e52bc279241539c018323fab05ad6368b56f93d9369ff550" + [[package]] name = "strsim" version = "0.8.0" @@ -1909,6 +2558,34 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "take_mut" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f764005d11ee5f36500a149ace24e00e3da98b0158b3e2d53a7495660d3f4d60" + +[[package]] +name = "tar" +version = "0.4.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0bcfbd6a598361fda270d82469fff3d65089dc33e175c9a131f7b4cd395f228" +dependencies = [ + "filetime", + "libc", + "xattr", +] + +[[package]] +name = "target_build_utils" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "013d134ae4a25ee744ad6129db589018558f620ddfa44043887cdd45fa08e75c" +dependencies = [ + "phf", + "phf_codegen", + "serde_json 0.9.10", +] + [[package]] name = "tempdir" version = "0.3.7" @@ -1919,6 +2596,16 @@ dependencies = [ "remove_dir_all", ] +[[package]] +name = "term" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa63644f74ce96fbeb9b794f66aff2a52d601cbd5e80f4b97123e3899f4570f1" +dependencies = [ + "kernel32-sys", + "winapi 0.2.8", +] + [[package]] name = "termcolor" version = "1.1.2" @@ -1974,7 +2661,7 @@ checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" dependencies = [ "libc", "wasi 0.10.0+wasi-snapshot-preview1", - "winapi", + "winapi 0.3.9", ] [[package]] @@ -1987,10 +2674,19 @@ dependencies = [ "arrayvec", "bytemuck", "cfg-if 1.0.0", - "png", + "png 0.16.8", "safe_arch", ] +[[package]] +name = "toml" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "758664fc71a3a69038656bee8b6be6477d2a6c315a6b81f7081f591bffa4111f" +dependencies = [ + "serde 1.0.125", +] + [[package]] name = "tree-sitter" version = "0.17.1" @@ -2097,6 +2793,15 @@ dependencies = [ "xmlwriter", ] +[[package]] +name = "uuid" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcc7e3b898aa6f6c08e5295b6c89258d1331e9ac578cc992fb818759951bdc22" +dependencies = [ + "sha1", +] + [[package]] name = "vec-arena" version = "1.0.0" @@ -2128,7 +2833,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "777182bc735b6424e1a57516d35ed72cb8019d85c8c9bf536dccb3445c1a2f7d" dependencies = [ "same-file", - "winapi", + "winapi 0.3.9", "winapi-util", ] @@ -2162,6 +2867,12 @@ dependencies = [ "libc", ] +[[package]] +name = "winapi" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" + [[package]] name = "winapi" version = "0.3.9" @@ -2172,6 +2883,12 @@ dependencies = [ "winapi-x86_64-pc-windows-gnu", ] +[[package]] +name = "winapi-build" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" + [[package]] name = "winapi-i686-pc-windows-gnu" version = "0.4.0" @@ -2184,7 +2901,7 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" dependencies = [ - "winapi", + "winapi 0.3.9", ] [[package]] @@ -2199,7 +2916,16 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d129932f4644ac2396cb456385cbf9e63b5b30c6e8dc4820bdca4eb082037a5" dependencies = [ - "winapi", + "winapi 0.3.9", +] + +[[package]] +name = "xattr" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "244c3741f4240ef46274860397c7c74e50eb23624996930e484c16679633a54c" +dependencies = [ + "libc", ] [[package]] @@ -2220,9 +2946,10 @@ version = "0.1.0" dependencies = [ "anyhow", "arrayvec", + "cargo-bundle", "crossbeam-channel", "ctor", - "dirs", + "dirs 3.0.1", "easy-parallel", "env_logger", "fsevent", @@ -2238,8 +2965,8 @@ dependencies = [ "rand 0.8.3", "rust-embed", "seahash", - "serde", - "serde_json", + "serde 1.0.125", + "serde_json 1.0.64", "simplelog", "smallvec", "smol", diff --git a/gpui/src/app.rs b/gpui/src/app.rs index 02a1626a304c546f89fd8385fbef057e643ab985..ac4c4e69b1782a416f5058025db0eafb974b7e7a 100644 --- a/gpui/src/app.rs +++ b/gpui/src/app.rs @@ -379,7 +379,8 @@ pub struct MutableAppContext { next_window_id: usize, next_task_id: usize, subscriptions: HashMap>, - observations: HashMap>, + model_observations: HashMap>, + view_observations: HashMap>, async_observations: HashMap>, window_invalidations: HashMap, presenters_and_platform_windows: @@ -420,7 +421,8 @@ impl MutableAppContext { next_window_id: 0, next_task_id: 0, subscriptions: HashMap::new(), - observations: HashMap::new(), + model_observations: HashMap::new(), + view_observations: HashMap::new(), async_observations: HashMap::new(), window_invalidations: HashMap::new(), presenters_and_platform_windows: HashMap::new(), @@ -871,13 +873,13 @@ impl MutableAppContext { for model_id in dropped_models { self.ctx.models.remove(&model_id); self.subscriptions.remove(&model_id); - self.observations.remove(&model_id); + self.model_observations.remove(&model_id); self.async_observations.remove(&model_id); } for (window_id, view_id) in dropped_views { self.subscriptions.remove(&view_id); - self.observations.remove(&view_id); + self.model_observations.remove(&view_id); self.async_observations.remove(&view_id); if let Some(window) = self.ctx.windows.get_mut(&window_id) { self.window_invalidations @@ -1004,11 +1006,11 @@ impl MutableAppContext { } fn notify_model_observers(&mut self, observed_id: usize) { - if let Some(observations) = self.observations.remove(&observed_id) { + if let Some(observations) = self.model_observations.remove(&observed_id) { if self.ctx.models.contains_key(&observed_id) { for mut observation in observations { let alive = match &mut observation { - Observation::FromModel { model_id, callback } => { + ModelObservation::FromModel { model_id, callback } => { if let Some(mut model) = self.ctx.models.remove(model_id) { callback(model.as_any_mut(), observed_id, self, *model_id); self.ctx.models.insert(*model_id, model); @@ -1017,7 +1019,7 @@ impl MutableAppContext { false } } - Observation::FromView { + ModelObservation::FromView { window_id, view_id, callback, @@ -1049,7 +1051,7 @@ impl MutableAppContext { }; if alive { - self.observations + self.model_observations .entry(observed_id) .or_default() .push(observation); @@ -1072,6 +1074,44 @@ impl MutableAppContext { .updated .insert(view_id); + if let Some(observations) = self.view_observations.remove(&view_id) { + if self.ctx.models.contains_key(&view_id) { + for mut observation in observations { + let alive = if let Some(mut view) = self + .ctx + .windows + .get_mut(&observation.window_id) + .and_then(|w| w.views.remove(&observation.view_id)) + { + (observation.callback)( + view.as_any_mut(), + view_id, + window_id, + self, + observation.window_id, + observation.view_id, + ); + self.ctx + .windows + .get_mut(&observation.window_id) + .unwrap() + .views + .insert(observation.view_id, view); + true + } else { + false + }; + + if alive { + self.view_observations + .entry(view_id) + .or_default() + .push(observation); + } + } + } + } + if let Entry::Occupied(mut entry) = self.async_observations.entry(view_id) { if entry.get_mut().blocking_send(()).is_err() { entry.remove_entry(); @@ -1576,10 +1616,10 @@ impl<'a, T: Entity> ModelContext<'a, T> { F: 'static + FnMut(&mut T, ModelHandle, &mut ModelContext), { self.app - .observations + .model_observations .entry(handle.model_id) .or_default() - .push(Observation::FromModel { + .push(ModelObservation::FromModel { model_id: self.model_id, callback: Box::new(move |model, observed_id, app, model_id| { let model = model.downcast_mut().expect("downcast is type safe"); @@ -1812,7 +1852,7 @@ impl<'a, T: View> ViewContext<'a, T> { window_id: self.window_id, view_id: self.view_id, callback: Box::new(move |view, payload, app, window_id, view_id| { - if let Some(emitter_handle) = emitter_handle.upgrade(app.as_ref()) { + if let Some(emitter_handle) = emitter_handle.upgrade(&app) { let model = view.downcast_mut().expect("downcast is type safe"); let payload = payload.downcast_ref().expect("downcast is type safe"); let mut ctx = ViewContext::new(app, window_id, view_id); @@ -1829,16 +1869,16 @@ impl<'a, T: View> ViewContext<'a, T> { }); } - pub fn observe(&mut self, handle: &ModelHandle, mut callback: F) + pub fn observe_model(&mut self, handle: &ModelHandle, mut callback: F) where S: Entity, F: 'static + FnMut(&mut T, ModelHandle, &mut ViewContext), { self.app - .observations + .model_observations .entry(handle.id()) .or_default() - .push(Observation::FromView { + .push(ModelObservation::FromView { window_id: self.window_id, view_id: self.view_id, callback: Box::new(move |view, observed_id, app, window_id, view_id| { @@ -1850,6 +1890,38 @@ impl<'a, T: View> ViewContext<'a, T> { }); } + pub fn observe_view(&mut self, handle: &ViewHandle, mut callback: F) + where + S: View, + F: 'static + FnMut(&mut T, ViewHandle, &mut ViewContext), + { + self.app + .view_observations + .entry(handle.id()) + .or_default() + .push(ViewObservation { + window_id: self.window_id, + view_id: self.view_id, + callback: Box::new( + move |view, + observed_view_id, + observed_window_id, + app, + observing_window_id, + observing_view_id| { + let view = view.downcast_mut().expect("downcast is type safe"); + let observed_handle = ViewHandle::new( + observed_view_id, + observed_window_id, + &app.ctx.ref_counts, + ); + let mut ctx = ViewContext::new(app, observing_window_id, observing_view_id); + callback(view, observed_handle, &mut ctx); + }, + ), + }); + } + pub fn notify(&mut self) { self.app.notify_view(self.window_id, self.view_id); } @@ -1918,6 +1990,12 @@ impl<'a, T: View> ViewContext<'a, T> { } } +impl AsRef for &AppContext { + fn as_ref(&self) -> &AppContext { + self + } +} + impl AsRef for ViewContext<'_, M> { fn as_ref(&self) -> &AppContext { &self.app.ctx @@ -2346,8 +2424,9 @@ impl WeakViewHandle { } } - pub fn upgrade(&self, app: &AppContext) -> Option> { - if app + pub fn upgrade(&self, ctx: impl AsRef) -> Option> { + let ctx = ctx.as_ref(); + if ctx .windows .get(&self.window_id) .and_then(|w| w.views.get(&self.view_id)) @@ -2356,7 +2435,7 @@ impl WeakViewHandle { Some(ViewHandle::new( self.window_id, self.view_id, - &app.ref_counts, + &ctx.ref_counts, )) } else { None @@ -2496,7 +2575,7 @@ enum Subscription { }, } -enum Observation { +enum ModelObservation { FromModel { model_id: usize, callback: Box, @@ -2508,6 +2587,12 @@ enum Observation { }, } +struct ViewObservation { + window_id: usize, + view_id: usize, + callback: Box, +} + type FutureHandler = Box, &mut MutableAppContext) -> Box>; struct StreamHandler { @@ -2639,7 +2724,7 @@ mod tests { assert_eq!(app.ctx.models.len(), 1); assert!(app.subscriptions.is_empty()); - assert!(app.observations.is_empty()); + assert!(app.model_observations.is_empty()); }); } @@ -2842,7 +2927,7 @@ mod tests { assert_eq!(app.ctx.windows[&window_id].views.len(), 2); assert!(app.subscriptions.is_empty()); - assert!(app.observations.is_empty()); + assert!(app.model_observations.is_empty()); }) } @@ -2988,7 +3073,7 @@ mod tests { let model = app.add_model(|_| Model::default()); view.update(app, |_, c| { - c.observe(&model, |me, observed, c| { + c.observe_model(&model, |me, observed, c| { me.events.push(observed.read(c).count) }); }); @@ -3032,7 +3117,7 @@ mod tests { let observed_model = app.add_model(|_| Model); observing_view.update(app, |_, ctx| { - ctx.observe(&observed_model, |_, _, _| {}); + ctx.observe_model(&observed_model, |_, _, _| {}); }); observing_model.update(app, |_, ctx| { ctx.observe(&observed_model, |_, _, _| {}); diff --git a/script/bundle b/script/bundle new file mode 100755 index 0000000000000000000000000000000000000000..197bc7821c4ae26a13a9a50faa667d33a914ef99 --- /dev/null +++ b/script/bundle @@ -0,0 +1,29 @@ +#!/bin/bash + +set -e + +# Build the app bundle for x86_64 +pushd zed > /dev/null +cargo bundle --release --target x86_64-apple-darwin +popd > /dev/null + +# Build the binary for aarch64 (Apple M1) +cargo build --release --target aarch64-apple-darwin + +# Replace the bundle's binary with a "fat binary" that combines the two architecture-specific binaries +lipo -create target/x86_64-apple-darwin/release/Zed target/aarch64-apple-darwin/release/Zed -output target/x86_64-apple-darwin/release/bundle/osx/Zed.app/Contents/MacOS/zed + +# Sign the app bundle with an ad-hoc signature so it runs on the M1. We need a real certificate but this works for now. +codesign --force --deep -s - target/x86_64-apple-darwin/release/bundle/osx/Zed.app + +# Create a DMG +mkdir -p target/release +hdiutil create -volname Zed -srcfolder target/x86_64-apple-darwin/release/bundle/osx -ov -format UDZO target/release/Zed.dmg + +# If -o option is specified, open the target/release directory in Finder to reveal the DMG +while getopts o flag +do + case "${flag}" in + o) open target/release;; + esac +done diff --git a/zed/Cargo.toml b/zed/Cargo.toml index c38db66d0f69294d25a86629e83111eee9d16dba..1ad7a4550a5f852319bf1496257642b6661aa83e 100644 --- a/zed/Cargo.toml +++ b/zed/Cargo.toml @@ -1,5 +1,6 @@ [package] authors = ["Nathan Sobo "] +description = "The fast, collaborative code editor." edition = "2018" name = "zed" version = "0.1.0" @@ -38,7 +39,13 @@ smallvec = "1.6.1" smol = "1.2.5" [dev-dependencies] +cargo-bundle = "0.5.0" env_logger = "0.8" serde_json = {version = "1.0.64", features = ["preserve_order"]} tempdir = "0.3.7" unindent = "0.1.7" + +[package.metadata.bundle] +icon = ["app-icon@2x.png", "app-icon.png"] +identifier = "dev.zed.Zed" +name = "Zed" diff --git a/zed/app-icon.png b/zed/app-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..8d8c77e8e759376bad79ac9bb9013c9c99d76c13 Binary files /dev/null and b/zed/app-icon.png differ diff --git a/zed/app-icon@2x.png b/zed/app-icon@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..666d4e541e162a4622c360b42764a0c0bfb77a5e Binary files /dev/null and b/zed/app-icon@2x.png differ diff --git a/zed/src/editor/buffer/mod.rs b/zed/src/editor/buffer/mod.rs index 5e142b6f3387c9475df5e42f2731a7c66ccb261e..363a6b926b733eb08c3251095fc449860463c9dd 100644 --- a/zed/src/editor/buffer/mod.rs +++ b/zed/src/editor/buffer/mod.rs @@ -18,16 +18,14 @@ use crate::{ worktree::FileHandle, }; use anyhow::{anyhow, Result}; -use gpui::{AppContext, Entity, ModelContext}; +use gpui::{Entity, ModelContext}; use lazy_static::lazy_static; use rand::prelude::*; use std::{ cmp, - ffi::OsString, hash::BuildHasher, iter::{self, Iterator}, ops::{AddAssign, Range}, - path::Path, str, sync::Arc, time::{Duration, Instant}, @@ -59,7 +57,6 @@ type HashMap = std::collections::HashMap; type HashSet = std::collections::HashSet; pub struct Buffer { - file: Option, fragments: SumTree, insertion_splits: HashMap>, pub version: time::Global, @@ -354,29 +351,15 @@ pub struct UndoOperation { } impl Buffer { - pub fn new>>( - replica_id: ReplicaId, - base_text: T, - ctx: &mut ModelContext, - ) -> Self { - Self::build(replica_id, None, History::new(base_text.into()), ctx) + pub fn new>>(replica_id: ReplicaId, base_text: T) -> Self { + Self::build(replica_id, History::new(base_text.into())) } - pub fn from_history( - replica_id: ReplicaId, - file: FileHandle, - history: History, - ctx: &mut ModelContext, - ) -> Self { - Self::build(replica_id, Some(file), history, ctx) + pub fn from_history(replica_id: ReplicaId, history: History) -> Self { + Self::build(replica_id, history) } - fn build( - replica_id: ReplicaId, - file: Option, - history: History, - ctx: &mut ModelContext, - ) -> Self { + fn build(replica_id: ReplicaId, history: History) -> Self { let mut insertion_splits = HashMap::default(); let mut fragments = SumTree::new(); @@ -425,12 +408,7 @@ impl Buffer { }); } - if let Some(file) = file.as_ref() { - file.observe_from_model(ctx, |_, _, ctx| ctx.emit(Event::FileHandleChanged)); - } - Self { - file, fragments, insertion_splits, version: time::Global::new(), @@ -448,41 +426,27 @@ impl Buffer { } } - pub fn file_name(&self, ctx: &AppContext) -> Option { - self.file.as_ref().and_then(|file| file.file_name(ctx)) - } - - pub fn path(&self) -> Option> { - self.file.as_ref().map(|file| file.path()) - } - - pub fn entry_id(&self) -> Option<(usize, Arc)> { - self.file.as_ref().map(|file| file.entry_id()) - } - pub fn snapshot(&self) -> Snapshot { Snapshot { fragments: self.fragments.clone(), } } - pub fn save(&mut self, ctx: &mut ModelContext) -> LocalBoxFuture<'static, Result<()>> { - if let Some(file) = &self.file { - dbg!(file.path()); - - let snapshot = self.snapshot(); - let version = self.version.clone(); - let save_task = file.save(snapshot, ctx.as_ref()); - let task = ctx.spawn(save_task, |me, save_result, ctx| { - if save_result.is_ok() { - me.did_save(version, ctx); - } - save_result - }); - Box::pin(task) - } else { - Box::pin(async { Ok(()) }) - } + pub fn save( + &mut self, + file: &FileHandle, + ctx: &mut ModelContext, + ) -> LocalBoxFuture<'static, Result<()>> { + let snapshot = self.snapshot(); + let version = self.version.clone(); + let save_task = file.save(snapshot, ctx.as_ref()); + let task = ctx.spawn(save_task, |me, save_result, ctx| { + if save_result.is_ok() { + me.did_save(version, ctx); + } + save_result + }); + Box::pin(task) } fn did_save(&mut self, version: time::Global, ctx: &mut ModelContext) { @@ -1759,7 +1723,6 @@ impl Buffer { impl Clone for Buffer { fn clone(&self) -> Self { Self { - file: self.file.clone(), fragments: self.fragments.clone(), insertion_splits: self.insertion_splits.clone(), version: self.version.clone(), @@ -2329,8 +2292,8 @@ mod tests { #[test] fn test_edit() { App::test((), |ctx| { - ctx.add_model(|ctx| { - let mut buffer = Buffer::new(0, "abc", ctx); + ctx.add_model(|_| { + let mut buffer = Buffer::new(0, "abc"); assert_eq!(buffer.text(), "abc"); buffer.edit(vec![3..3], "def", None).unwrap(); assert_eq!(buffer.text(), "abcdef"); @@ -2354,8 +2317,8 @@ mod tests { let buffer_1_events = Rc::new(RefCell::new(Vec::new())); let buffer_2_events = Rc::new(RefCell::new(Vec::new())); - let buffer1 = app.add_model(|ctx| Buffer::new(0, "abcdef", ctx)); - let buffer2 = app.add_model(|ctx| Buffer::new(1, "abcdef", ctx)); + let buffer1 = app.add_model(|_| Buffer::new(0, "abcdef")); + let buffer2 = app.add_model(|_| Buffer::new(1, "abcdef")); let mut buffer_ops = Vec::new(); buffer1.update(app, |buffer, ctx| { let buffer_1_events = buffer_1_events.clone(); @@ -2418,8 +2381,8 @@ mod tests { let mut reference_string = RandomCharIter::new(&mut rng) .take(reference_string_len) .collect::(); - ctx.add_model(|ctx| { - let mut buffer = Buffer::new(0, reference_string.as_str(), ctx); + ctx.add_model(|_| { + let mut buffer = Buffer::new(0, reference_string.as_str()); let mut buffer_versions = Vec::new(); for _i in 0..10 { let (old_ranges, new_text, _) = buffer.randomly_mutate(rng, None); @@ -2504,8 +2467,8 @@ mod tests { #[test] fn test_line_len() { App::test((), |ctx| { - ctx.add_model(|ctx| { - let mut buffer = Buffer::new(0, "", ctx); + ctx.add_model(|_| { + let mut buffer = Buffer::new(0, ""); buffer.edit(vec![0..0], "abcd\nefg\nhij", None).unwrap(); buffer.edit(vec![12..12], "kl\nmno", None).unwrap(); buffer.edit(vec![18..18], "\npqrs\n", None).unwrap(); @@ -2526,8 +2489,8 @@ mod tests { #[test] fn test_rightmost_point() { App::test((), |ctx| { - ctx.add_model(|ctx| { - let mut buffer = Buffer::new(0, "", ctx); + ctx.add_model(|_| { + let mut buffer = Buffer::new(0, ""); assert_eq!(buffer.rightmost_point().row, 0); buffer.edit(vec![0..0], "abcd\nefg\nhij", None).unwrap(); assert_eq!(buffer.rightmost_point().row, 0); @@ -2547,8 +2510,8 @@ mod tests { #[test] fn test_text_summary_for_range() { App::test((), |ctx| { - ctx.add_model(|ctx| { - let buffer = Buffer::new(0, "ab\nefg\nhklm\nnopqrs\ntuvwxyz", ctx); + ctx.add_model(|_| { + let buffer = Buffer::new(0, "ab\nefg\nhklm\nnopqrs\ntuvwxyz"); let text = Text::from(buffer.text()); assert_eq!( buffer.text_summary_for_range(1..3), @@ -2578,8 +2541,8 @@ mod tests { #[test] fn test_chars_at() { App::test((), |ctx| { - ctx.add_model(|ctx| { - let mut buffer = Buffer::new(0, "", ctx); + ctx.add_model(|_| { + let mut buffer = Buffer::new(0, ""); buffer.edit(vec![0..0], "abcd\nefgh\nij", None).unwrap(); buffer.edit(vec![12..12], "kl\nmno", None).unwrap(); buffer.edit(vec![18..18], "\npqrs", None).unwrap(); @@ -2601,7 +2564,7 @@ mod tests { assert_eq!(chars.collect::(), "PQrs"); // Regression test: - let mut buffer = Buffer::new(0, "", ctx); + let mut buffer = Buffer::new(0, ""); buffer.edit(vec![0..0], "[workspace]\nmembers = [\n \"xray_core\",\n \"xray_server\",\n \"xray_cli\",\n \"xray_wasm\",\n]\n", None).unwrap(); buffer.edit(vec![60..60], "\n", None).unwrap(); @@ -2730,8 +2693,8 @@ mod tests { #[test] fn test_anchors() { App::test((), |ctx| { - ctx.add_model(|ctx| { - let mut buffer = Buffer::new(0, "", ctx); + ctx.add_model(|_| { + let mut buffer = Buffer::new(0, ""); buffer.edit(vec![0..0], "abc", None).unwrap(); let left_anchor = buffer.anchor_before(2).unwrap(); let right_anchor = buffer.anchor_after(2).unwrap(); @@ -2895,8 +2858,8 @@ mod tests { #[test] fn test_anchors_at_start_and_end() { App::test((), |ctx| { - ctx.add_model(|ctx| { - let mut buffer = Buffer::new(0, "", ctx); + ctx.add_model(|_| { + let mut buffer = Buffer::new(0, ""); let before_start_anchor = buffer.anchor_before(0).unwrap(); let after_end_anchor = buffer.anchor_after(0).unwrap(); @@ -2923,7 +2886,7 @@ mod tests { #[test] fn test_is_modified() { App::test((), |app| { - let model = app.add_model(|ctx| Buffer::new(0, "abc", ctx)); + let model = app.add_model(|_| Buffer::new(0, "abc")); let events = Rc::new(RefCell::new(Vec::new())); // initially, the buffer isn't dirty. @@ -2985,8 +2948,8 @@ mod tests { #[test] fn test_undo_redo() { App::test((), |app| { - app.add_model(|ctx| { - let mut buffer = Buffer::new(0, "1234", ctx); + app.add_model(|_| { + let mut buffer = Buffer::new(0, "1234"); let edit1 = buffer.edit(vec![1..1], "abx", None).unwrap(); let edit2 = buffer.edit(vec![3..4], "yzef", None).unwrap(); @@ -3022,9 +2985,9 @@ mod tests { #[test] fn test_history() { App::test((), |app| { - app.add_model(|ctx| { + app.add_model(|_| { let mut now = Instant::now(); - let mut buffer = Buffer::new(0, "123456", ctx); + let mut buffer = Buffer::new(0, "123456"); let (set_id, _) = buffer .add_selection_set(buffer.selections_from_ranges(vec![4..4]).unwrap(), None); @@ -3109,7 +3072,7 @@ mod tests { let mut network = Network::new(); for i in 0..PEERS { let buffer = - ctx.add_model(|ctx| Buffer::new(i as ReplicaId, base_text.as_str(), ctx)); + ctx.add_model(|_| Buffer::new(i as ReplicaId, base_text.as_str())); buffers.push(buffer); replica_ids.push(i as u16); network.add_peer(i as u16); diff --git a/zed/src/editor/buffer_view.rs b/zed/src/editor/buffer_view.rs index effcb30662d8c85ad050b536e7e5ad1bc43bb994..65dacacae950ad8effa22959eb66fc5f030d7076 100644 --- a/zed/src/editor/buffer_view.rs +++ b/zed/src/editor/buffer_view.rs @@ -2,7 +2,7 @@ use super::{ buffer, movement, Anchor, Bias, Buffer, BufferElement, DisplayMap, DisplayPoint, Point, Selection, SelectionSetId, ToOffset, ToPoint, }; -use crate::{settings::Settings, watch, workspace}; +use crate::{settings::Settings, watch, workspace, worktree::FileHandle}; use anyhow::Result; use futures_core::future::LocalBoxFuture; use gpui::{ @@ -254,6 +254,7 @@ pub enum SelectAction { pub struct BufferView { handle: WeakViewHandle, buffer: ModelHandle, + file: Option, display_map: DisplayMap, selection_set_id: SelectionSetId, pending_selection: Option, @@ -275,20 +276,25 @@ struct ClipboardSelection { impl BufferView { pub fn single_line(settings: watch::Receiver, ctx: &mut ViewContext) -> Self { - let buffer = ctx.add_model(|ctx| Buffer::new(0, String::new(), ctx)); - let mut view = Self::for_buffer(buffer, settings, ctx); + let buffer = ctx.add_model(|_| Buffer::new(0, String::new())); + let mut view = Self::for_buffer(buffer, None, settings, ctx); view.single_line = true; view } pub fn for_buffer( buffer: ModelHandle, + file: Option, settings: watch::Receiver, ctx: &mut ViewContext, ) -> Self { settings.notify_view_on_change(ctx); - ctx.observe(&buffer, Self::on_buffer_changed); + if let Some(file) = file.as_ref() { + file.observe_from_view(ctx, |_, _, ctx| ctx.emit(Event::FileHandleChanged)); + } + + ctx.observe_model(&buffer, Self::on_buffer_changed); ctx.subscribe_to_model(&buffer, Self::on_buffer_event); let display_map = DisplayMap::new( buffer.clone(), @@ -310,6 +316,7 @@ impl BufferView { Self { handle: ctx.handle().downgrade(), buffer, + file, display_map, selection_set_id, pending_selection: None, @@ -580,7 +587,7 @@ impl BufferView { Ok(()) } - fn insert(&mut self, text: &String, ctx: &mut ViewContext) { + pub fn insert(&mut self, text: &String, ctx: &mut ViewContext) { let mut offset_ranges = SmallVec::<[Range; 32]>::new(); { let buffer = self.buffer.read(ctx); @@ -2175,18 +2182,6 @@ impl View for BufferView { } } -impl workspace::Item for Buffer { - type View = BufferView; - - fn build_view( - buffer: ModelHandle, - settings: watch::Receiver, - ctx: &mut ViewContext, - ) -> Self::View { - BufferView::for_buffer(buffer, settings, ctx) - } -} - impl workspace::ItemView for BufferView { fn should_activate_item_on_event(event: &Self::Event) -> bool { matches!(event, Event::Activate) @@ -2200,28 +2195,39 @@ impl workspace::ItemView for BufferView { } fn title(&self, app: &AppContext) -> std::string::String { - if let Some(name) = self.buffer.read(app).file_name(app) { + let filename = self.file.as_ref().and_then(|file| file.file_name(app)); + if let Some(name) = filename { name.to_string_lossy().into() } else { "untitled".into() } } - fn entry_id(&self, app: &AppContext) -> Option<(usize, Arc)> { - self.buffer.read(app).entry_id() + fn entry_id(&self, _: &AppContext) -> Option<(usize, Arc)> { + self.file.as_ref().map(|file| file.entry_id()) } fn clone_on_split(&self, ctx: &mut ViewContext) -> Option where Self: Sized, { - let clone = BufferView::for_buffer(self.buffer.clone(), self.settings.clone(), ctx); + let clone = BufferView::for_buffer( + self.buffer.clone(), + self.file.clone(), + self.settings.clone(), + ctx, + ); *clone.scroll_position.lock() = *self.scroll_position.lock(); Some(clone) } fn save(&self, ctx: &mut ViewContext) -> LocalBoxFuture<'static, Result<()>> { - self.buffer.update(ctx, |buffer, ctx| buffer.save(ctx)) + if let Some(file) = self.file.as_ref() { + self.buffer + .update(ctx, |buffer, ctx| buffer.save(file, ctx)) + } else { + Box::pin(async { Ok(()) }) + } } fn is_dirty(&self, ctx: &AppContext) -> bool { @@ -2239,11 +2245,10 @@ mod tests { #[test] fn test_selection_with_mouse() { App::test((), |app| { - let buffer = - app.add_model(|ctx| Buffer::new(0, "aaaaaa\nbbbbbb\ncccccc\ndddddd\n", ctx)); + let buffer = app.add_model(|_| Buffer::new(0, "aaaaaa\nbbbbbb\ncccccc\ndddddd\n")); let settings = settings::channel(&app.font_cache()).unwrap().1; let (_, buffer_view) = - app.add_window(|ctx| BufferView::for_buffer(buffer, settings, ctx)); + app.add_window(|ctx| BufferView::for_buffer(buffer, None, settings, ctx)); buffer_view.update(app, |view, ctx| { view.begin_selection(DisplayPoint::new(2, 2), false, ctx); @@ -2354,11 +2359,11 @@ mod tests { let layout_cache = TextLayoutCache::new(app.platform().fonts()); let font_cache = app.font_cache().clone(); - let buffer = app.add_model(|ctx| Buffer::new(0, sample_text(6, 6), ctx)); + let buffer = app.add_model(|_| Buffer::new(0, sample_text(6, 6))); let settings = settings::channel(&font_cache).unwrap().1; let (_, view) = - app.add_window(|ctx| BufferView::for_buffer(buffer.clone(), settings, ctx)); + app.add_window(|ctx| BufferView::for_buffer(buffer.clone(), None, settings, ctx)); let layouts = view .read(app) @@ -2371,7 +2376,7 @@ mod tests { #[test] fn test_fold() { App::test((), |app| { - let buffer = app.add_model(|ctx| { + let buffer = app.add_model(|_| { Buffer::new( 0, " @@ -2392,12 +2397,11 @@ mod tests { } " .unindent(), - ctx, ) }); let settings = settings::channel(&app.font_cache()).unwrap().1; let (_, view) = - app.add_window(|ctx| BufferView::for_buffer(buffer.clone(), settings, ctx)); + app.add_window(|ctx| BufferView::for_buffer(buffer.clone(), None, settings, ctx)); view.update(app, |view, ctx| { view.select_display_ranges( @@ -2466,10 +2470,10 @@ mod tests { #[test] fn test_move_cursor() { App::test((), |app| { - let buffer = app.add_model(|ctx| Buffer::new(0, sample_text(6, 6), ctx)); + let buffer = app.add_model(|_| Buffer::new(0, sample_text(6, 6))); let settings = settings::channel(&app.font_cache()).unwrap().1; let (_, view) = - app.add_window(|ctx| BufferView::for_buffer(buffer.clone(), settings, ctx)); + app.add_window(|ctx| BufferView::for_buffer(buffer.clone(), None, settings, ctx)); buffer.update(app, |buffer, ctx| { buffer @@ -2544,9 +2548,10 @@ mod tests { #[test] fn test_beginning_end_of_line() { App::test((), |app| { - let buffer = app.add_model(|ctx| Buffer::new(0, "abc\n def", ctx)); + let buffer = app.add_model(|_| Buffer::new(0, "abc\n def")); let settings = settings::channel(&app.font_cache()).unwrap().1; - let (_, view) = app.add_window(|ctx| BufferView::for_buffer(buffer, settings, ctx)); + let (_, view) = + app.add_window(|ctx| BufferView::for_buffer(buffer, None, settings, ctx)); view.update(app, |view, ctx| { view.select_display_ranges( &[ @@ -2672,10 +2677,11 @@ mod tests { #[test] fn test_prev_next_word_boundary() { App::test((), |app| { - let buffer = app - .add_model(|ctx| Buffer::new(0, "use std::str::{foo, bar}\n\n {baz.qux()}", ctx)); + let buffer = + app.add_model(|_| Buffer::new(0, "use std::str::{foo, bar}\n\n {baz.qux()}")); let settings = settings::channel(&app.font_cache()).unwrap().1; - let (_, view) = app.add_window(|ctx| BufferView::for_buffer(buffer, settings, ctx)); + let (_, view) = + app.add_window(|ctx| BufferView::for_buffer(buffer, None, settings, ctx)); view.update(app, |view, ctx| { view.select_display_ranges( &[ @@ -2854,16 +2860,12 @@ mod tests { #[test] fn test_backspace() { App::test((), |app| { - let buffer = app.add_model(|ctx| { - Buffer::new( - 0, - "one two three\nfour five six\nseven eight nine\nten\n", - ctx, - ) + let buffer = app.add_model(|_| { + Buffer::new(0, "one two three\nfour five six\nseven eight nine\nten\n") }); let settings = settings::channel(&app.font_cache()).unwrap().1; let (_, view) = - app.add_window(|ctx| BufferView::for_buffer(buffer.clone(), settings, ctx)); + app.add_window(|ctx| BufferView::for_buffer(buffer.clone(), None, settings, ctx)); view.update(app, |view, ctx| { view.select_display_ranges( @@ -2891,16 +2893,12 @@ mod tests { #[test] fn test_delete() { App::test((), |app| { - let buffer = app.add_model(|ctx| { - Buffer::new( - 0, - "one two three\nfour five six\nseven eight nine\nten\n", - ctx, - ) + let buffer = app.add_model(|_| { + Buffer::new(0, "one two three\nfour five six\nseven eight nine\nten\n") }); let settings = settings::channel(&app.font_cache()).unwrap().1; let (_, view) = - app.add_window(|ctx| BufferView::for_buffer(buffer.clone(), settings, ctx)); + app.add_window(|ctx| BufferView::for_buffer(buffer.clone(), None, settings, ctx)); view.update(app, |view, ctx| { view.select_display_ranges( @@ -2929,8 +2927,9 @@ mod tests { fn test_delete_line() { App::test((), |app| { let settings = settings::channel(&app.font_cache()).unwrap().1; - let buffer = app.add_model(|ctx| Buffer::new(0, "abc\ndef\nghi\n", ctx)); - let (_, view) = app.add_window(|ctx| BufferView::for_buffer(buffer, settings, ctx)); + let buffer = app.add_model(|_| Buffer::new(0, "abc\ndef\nghi\n")); + let (_, view) = + app.add_window(|ctx| BufferView::for_buffer(buffer, None, settings, ctx)); view.update(app, |view, ctx| { view.select_display_ranges( &[ @@ -2953,8 +2952,9 @@ mod tests { ); let settings = settings::channel(&app.font_cache()).unwrap().1; - let buffer = app.add_model(|ctx| Buffer::new(0, "abc\ndef\nghi\n", ctx)); - let (_, view) = app.add_window(|ctx| BufferView::for_buffer(buffer, settings, ctx)); + let buffer = app.add_model(|_| Buffer::new(0, "abc\ndef\nghi\n")); + let (_, view) = + app.add_window(|ctx| BufferView::for_buffer(buffer, None, settings, ctx)); view.update(app, |view, ctx| { view.select_display_ranges( &[DisplayPoint::new(2, 0)..DisplayPoint::new(0, 1)], @@ -2975,8 +2975,9 @@ mod tests { fn test_duplicate_line() { App::test((), |app| { let settings = settings::channel(&app.font_cache()).unwrap().1; - let buffer = app.add_model(|ctx| Buffer::new(0, "abc\ndef\nghi\n", ctx)); - let (_, view) = app.add_window(|ctx| BufferView::for_buffer(buffer, settings, ctx)); + let buffer = app.add_model(|_| Buffer::new(0, "abc\ndef\nghi\n")); + let (_, view) = + app.add_window(|ctx| BufferView::for_buffer(buffer, None, settings, ctx)); view.update(app, |view, ctx| { view.select_display_ranges( &[ @@ -3005,8 +3006,9 @@ mod tests { ); let settings = settings::channel(&app.font_cache()).unwrap().1; - let buffer = app.add_model(|ctx| Buffer::new(0, "abc\ndef\nghi\n", ctx)); - let (_, view) = app.add_window(|ctx| BufferView::for_buffer(buffer, settings, ctx)); + let buffer = app.add_model(|_| Buffer::new(0, "abc\ndef\nghi\n")); + let (_, view) = + app.add_window(|ctx| BufferView::for_buffer(buffer, None, settings, ctx)); view.update(app, |view, ctx| { view.select_display_ranges( &[ @@ -3035,10 +3037,10 @@ mod tests { #[test] fn test_clipboard() { App::test((), |app| { - let buffer = app.add_model(|ctx| Buffer::new(0, "one two three four five six ", ctx)); + let buffer = app.add_model(|_| Buffer::new(0, "one two three four five six ")); let settings = settings::channel(&app.font_cache()).unwrap().1; let view = app - .add_window(|ctx| BufferView::for_buffer(buffer.clone(), settings, ctx)) + .add_window(|ctx| BufferView::for_buffer(buffer.clone(), None, settings, ctx)) .1; // Cut with three selections. Clipboard text is divided into three slices. @@ -3176,9 +3178,10 @@ mod tests { #[test] fn test_select_all() { App::test((), |app| { - let buffer = app.add_model(|ctx| Buffer::new(0, "abc\nde\nfgh", ctx)); + let buffer = app.add_model(|_| Buffer::new(0, "abc\nde\nfgh")); let settings = settings::channel(&app.font_cache()).unwrap().1; - let (_, view) = app.add_window(|ctx| BufferView::for_buffer(buffer, settings, ctx)); + let (_, view) = + app.add_window(|ctx| BufferView::for_buffer(buffer, None, settings, ctx)); view.update(app, |b, ctx| b.select_all(&(), ctx)); assert_eq!( view.read(app).selection_ranges(app.as_ref()), diff --git a/zed/src/editor/display_map/fold_map.rs b/zed/src/editor/display_map/fold_map.rs index 8240954345dc5c86f12f46e602e1e8499828ed88..d1bf169ef6673d23ca593f5aada72a2e1fdcf904 100644 --- a/zed/src/editor/display_map/fold_map.rs +++ b/zed/src/editor/display_map/fold_map.rs @@ -535,7 +535,7 @@ mod tests { #[test] fn test_basic_folds() { App::test((), |app| { - let buffer = app.add_model(|ctx| Buffer::new(0, sample_text(5, 6), ctx)); + let buffer = app.add_model(|_| Buffer::new(0, sample_text(5, 6))); let mut map = FoldMap::new(buffer.clone(), app.as_ref()); map.fold( @@ -580,7 +580,7 @@ mod tests { #[test] fn test_adjacent_folds() { App::test((), |app| { - let buffer = app.add_model(|ctx| Buffer::new(0, "abcdefghijkl", ctx)); + let buffer = app.add_model(|_| Buffer::new(0, "abcdefghijkl")); { let mut map = FoldMap::new(buffer.clone(), app.as_ref()); @@ -623,7 +623,7 @@ mod tests { #[test] fn test_overlapping_folds() { App::test((), |app| { - let buffer = app.add_model(|ctx| Buffer::new(0, sample_text(5, 6), ctx)); + let buffer = app.add_model(|_| Buffer::new(0, sample_text(5, 6))); let mut map = FoldMap::new(buffer.clone(), app.as_ref()); map.fold( vec![ @@ -642,7 +642,7 @@ mod tests { #[test] fn test_merging_folds_via_edit() { App::test((), |app| { - let buffer = app.add_model(|ctx| Buffer::new(0, sample_text(5, 6), ctx)); + let buffer = app.add_model(|_| Buffer::new(0, sample_text(5, 6))); let mut map = FoldMap::new(buffer.clone(), app.as_ref()); map.fold( @@ -667,7 +667,7 @@ mod tests { #[test] fn test_folds_in_range() { App::test((), |app| { - let buffer = app.add_model(|ctx| Buffer::new(0, sample_text(5, 6), ctx)); + let buffer = app.add_model(|_| Buffer::new(0, sample_text(5, 6))); let mut map = FoldMap::new(buffer.clone(), app.as_ref()); let buffer = buffer.read(app); @@ -723,10 +723,10 @@ mod tests { let mut rng = StdRng::seed_from_u64(seed); App::test((), |app| { - let buffer = app.add_model(|ctx| { + let buffer = app.add_model(|_| { let len = rng.gen_range(0..10); let text = RandomCharIter::new(&mut rng).take(len).collect::(); - Buffer::new(0, text, ctx) + Buffer::new(0, text) }); let mut map = FoldMap::new(buffer.clone(), app.as_ref()); @@ -853,7 +853,7 @@ mod tests { fn test_buffer_rows() { App::test((), |app| { let text = sample_text(6, 6) + "\n"; - let buffer = app.add_model(|ctx| Buffer::new(0, text, ctx)); + let buffer = app.add_model(|_| Buffer::new(0, text)); let mut map = FoldMap::new(buffer.clone(), app.as_ref()); diff --git a/zed/src/editor/display_map/mod.rs b/zed/src/editor/display_map/mod.rs index 96392610d093861cfebeafbd6fdd42b6bbda797c..92ccd21acbda2fc62c0e9423dea42dde33686df6 100644 --- a/zed/src/editor/display_map/mod.rs +++ b/zed/src/editor/display_map/mod.rs @@ -345,7 +345,7 @@ mod tests { fn test_chars_at() { App::test((), |app| { let text = sample_text(6, 6); - let buffer = app.add_model(|ctx| Buffer::new(0, text, ctx)); + let buffer = app.add_model(|_| Buffer::new(0, text)); let map = DisplayMap::new(buffer.clone(), 4, app.as_ref()); buffer .update(app, |buffer, ctx| { @@ -414,7 +414,7 @@ mod tests { #[test] fn test_max_point() { App::test((), |app| { - let buffer = app.add_model(|ctx| Buffer::new(0, "aaa\n\t\tbbb", ctx)); + let buffer = app.add_model(|_| Buffer::new(0, "aaa\n\t\tbbb")); let map = DisplayMap::new(buffer.clone(), 4, app.as_ref()); assert_eq!(map.max_point(app.as_ref()), DisplayPoint::new(1, 11)) }); diff --git a/zed/src/file_finder.rs b/zed/src/file_finder.rs index 0884cbdf5a118be6dd4a499cecc66ee92893c538..0965cdcda5bc77ca4d35a7840855e53a2004a688 100644 --- a/zed/src/file_finder.rs +++ b/zed/src/file_finder.rs @@ -2,7 +2,7 @@ use crate::{ editor::{buffer_view, BufferView}, settings::Settings, util, watch, - workspace::{Workspace, WorkspaceView}, + workspace::Workspace, worktree::{match_paths, PathMatch, Worktree}, }; use gpui::{ @@ -11,8 +11,8 @@ use gpui::{ fonts::{Properties, Weight}, geometry::vector::vec2f, keymap::{self, Binding}, - AppContext, Axis, Border, Entity, ModelHandle, MutableAppContext, View, ViewContext, - ViewHandle, WeakViewHandle, + AppContext, Axis, Border, Entity, MutableAppContext, View, ViewContext, ViewHandle, + WeakViewHandle, }; use std::{ cmp, @@ -26,7 +26,7 @@ use std::{ pub struct FileFinder { handle: WeakViewHandle, settings: watch::Receiver, - workspace: ModelHandle, + workspace: WeakViewHandle, query_buffer: ViewHandle, search_count: usize, latest_search_id: usize, @@ -253,25 +253,21 @@ impl FileFinder { }) } - fn toggle(workspace_view: &mut WorkspaceView, _: &(), ctx: &mut ViewContext) { + fn toggle(workspace_view: &mut Workspace, _: &(), ctx: &mut ViewContext) { workspace_view.toggle_modal(ctx, |ctx, workspace_view| { - let handle = ctx.add_view(|ctx| { - Self::new( - workspace_view.settings.clone(), - workspace_view.workspace.clone(), - ctx, - ) - }); - ctx.subscribe_to_view(&handle, Self::on_event); - handle + let workspace = ctx.handle(); + let finder = + ctx.add_view(|ctx| Self::new(workspace_view.settings.clone(), workspace, ctx)); + ctx.subscribe_to_view(&finder, Self::on_event); + finder }); } fn on_event( - workspace_view: &mut WorkspaceView, + workspace_view: &mut Workspace, _: ViewHandle, event: &Event, - ctx: &mut ViewContext, + ctx: &mut ViewContext, ) { match event { Event::Selected(tree_id, path) => { @@ -288,10 +284,10 @@ impl FileFinder { pub fn new( settings: watch::Receiver, - workspace: ModelHandle, + workspace: ViewHandle, ctx: &mut ViewContext, ) -> Self { - ctx.observe(&workspace, Self::workspace_updated); + ctx.observe_view(&workspace, Self::workspace_updated); let query_buffer = ctx.add_view(|ctx| BufferView::single_line(settings.clone(), ctx)); ctx.subscribe_to_view(&query_buffer, Self::on_query_buffer_event); @@ -301,7 +297,7 @@ impl FileFinder { Self { handle: ctx.handle().downgrade(), settings, - workspace, + workspace: workspace.downgrade(), query_buffer, search_count: 0, latest_search_id: 0, @@ -314,7 +310,7 @@ impl FileFinder { } } - fn workspace_updated(&mut self, _: ModelHandle, ctx: &mut ViewContext) { + fn workspace_updated(&mut self, _: ViewHandle, ctx: &mut ViewContext) { self.spawn_search(self.query_buffer.read(ctx).text(ctx.as_ref()), ctx); } @@ -390,9 +386,10 @@ impl FileFinder { ctx.emit(Event::Selected(*tree_id, path.clone())); } - fn spawn_search(&mut self, query: String, ctx: &mut ViewContext) { + fn spawn_search(&mut self, query: String, ctx: &mut ViewContext) -> Option<()> { let snapshots = self .workspace + .upgrade(&ctx)? .read(ctx) .worktrees() .iter() @@ -420,6 +417,8 @@ impl FileFinder { }); ctx.spawn(task, Self::update_matches).detach(); + + Some(()) } fn update_matches( @@ -443,6 +442,7 @@ impl FileFinder { fn worktree<'a>(&'a self, tree_id: usize, app: &'a AppContext) -> Option<&'a Worktree> { self.workspace + .upgrade(app)? .read(app) .worktrees() .get(&tree_id) @@ -453,11 +453,7 @@ impl FileFinder { #[cfg(test)] mod tests { use super::*; - use crate::{ - editor, settings, - test::temp_tree, - workspace::{Workspace, WorkspaceView}, - }; + use crate::{editor, settings, test::temp_tree, workspace::Workspace}; use gpui::App; use serde_json::json; use std::fs; @@ -476,20 +472,22 @@ mod tests { }); let settings = settings::channel(&app.font_cache()).unwrap().1; - let workspace = app.add_model(|ctx| Workspace::new(vec![tmp_dir.path().into()], ctx)); - let (window_id, workspace_view) = - app.add_window(|ctx| WorkspaceView::new(workspace.clone(), settings, ctx)); + let (window_id, workspace) = app.add_window(|ctx| { + let mut workspace = Workspace::new(0, settings, ctx); + workspace.add_worktree(tmp_dir.path(), ctx); + workspace + }); app.read(|ctx| workspace.read(ctx).worktree_scans_complete(ctx)) .await; app.dispatch_action( window_id, - vec![workspace_view.id()], + vec![workspace.id()], "file_finder:toggle".into(), (), ); let finder = app.read(|ctx| { - workspace_view + workspace .read(ctx) .modal() .cloned() @@ -507,16 +505,16 @@ mod tests { .condition(&app, |finder, _| finder.matches.len() == 2) .await; - let active_pane = app.read(|ctx| workspace_view.read(ctx).active_pane().clone()); + let active_pane = app.read(|ctx| workspace.read(ctx).active_pane().clone()); app.dispatch_action( window_id, - vec![workspace_view.id(), finder.id()], + vec![workspace.id(), finder.id()], "menu:select_next", (), ); app.dispatch_action( window_id, - vec![workspace_view.id(), finder.id()], + vec![workspace.id(), finder.id()], "file_finder:confirm", (), ); @@ -543,7 +541,11 @@ mod tests { "hiccup": "", })); let settings = settings::channel(&app.font_cache()).unwrap().1; - let workspace = app.add_model(|ctx| Workspace::new(vec![tmp_dir.path().into()], ctx)); + let (_, workspace) = app.add_window(|ctx| { + let mut workspace = Workspace::new(0, settings.clone(), ctx); + workspace.add_worktree(tmp_dir.path(), ctx); + workspace + }); app.read(|ctx| workspace.read(ctx).worktree_scans_complete(ctx)) .await; let (_, finder) = @@ -596,7 +598,11 @@ mod tests { fs::write(&file_path, "").unwrap(); let settings = settings::channel(&app.font_cache()).unwrap().1; - let workspace = app.add_model(|ctx| Workspace::new(vec![file_path], ctx)); + let (_, workspace) = app.add_window(|ctx| { + let mut workspace = Workspace::new(0, settings.clone(), ctx); + workspace.add_worktree(&file_path, ctx); + workspace + }); app.read(|ctx| workspace.read(ctx).worktree_scans_complete(ctx)) .await; let (_, finder) = @@ -633,14 +639,20 @@ mod tests { "dir2": { "a.txt": "" } })); let settings = settings::channel(&app.font_cache()).unwrap().1; - let workspace = app.add_model(|ctx| { - Workspace::new( - vec![tmp_dir.path().join("dir1"), tmp_dir.path().join("dir2")], - ctx, - ) - }); + + let (_, workspace) = app.add_window(|ctx| Workspace::new(0, settings.clone(), ctx)); + + workspace + .update(&mut app, |workspace, ctx| { + workspace.open_paths( + &[tmp_dir.path().join("dir1"), tmp_dir.path().join("dir2")], + ctx, + ) + }) + .await; app.read(|ctx| workspace.read(ctx).worktree_scans_complete(ctx)) .await; + let (_, finder) = app.add_window(|ctx| FileFinder::new(settings, workspace.clone(), ctx)); diff --git a/zed/src/workspace/workspace_view.rs b/zed/src/workspace.rs similarity index 56% rename from zed/src/workspace/workspace_view.rs rename to zed/src/workspace.rs index 6815a1e2c95778da781e5a671d802489dbc4e593..629d23beecce0bdf1e1c59a76cd6fbe465f92f9b 100644 --- a/zed/src/workspace/workspace_view.rs +++ b/zed/src/workspace.rs @@ -1,25 +1,99 @@ -use super::{pane, Pane, PaneGroup, SplitDirection, Workspace}; -use crate::{settings::Settings, watch}; +pub mod pane; +pub mod pane_group; +pub use pane::*; +pub use pane_group::*; + +use crate::{ + settings::Settings, + watch::{self, Receiver}, +}; +use gpui::{MutableAppContext, PathPromptOptions}; +use std::path::PathBuf; +pub fn init(app: &mut MutableAppContext) { + app.add_global_action("workspace:open", open); + app.add_global_action("workspace:open_paths", open_paths); + app.add_global_action("app:quit", quit); + app.add_action("workspace:save", Workspace::save_active_item); + app.add_action("workspace:debug_elements", Workspace::debug_elements); + app.add_bindings(vec![ + Binding::new("cmd-s", "workspace:save", None), + Binding::new("cmd-alt-i", "workspace:debug_elements", None), + ]); + pane::init(app); +} +use crate::{ + editor::{Buffer, BufferView}, + time::ReplicaId, + worktree::{Worktree, WorktreeHandle}, +}; use futures_core::{future::LocalBoxFuture, Future}; use gpui::{ color::rgbu, elements::*, json::to_string_pretty, keymap::Binding, AnyViewHandle, AppContext, - ClipboardItem, Entity, EntityTask, ModelHandle, MutableAppContext, View, ViewContext, - ViewHandle, + ClipboardItem, Entity, EntityTask, ModelHandle, View, ViewContext, ViewHandle, }; use log::error; +use smol::prelude::*; use std::{ - collections::HashSet, - path::{Path, PathBuf}, + collections::{hash_map::Entry, HashMap, HashSet}, + path::Path, sync::Arc, }; -pub fn init(app: &mut MutableAppContext) { - app.add_action("workspace:save", WorkspaceView::save_active_item); - app.add_action("workspace:debug_elements", WorkspaceView::debug_elements); - app.add_bindings(vec![ - Binding::new("cmd-s", "workspace:save", None), - Binding::new("cmd-alt-i", "workspace:debug_elements", None), - ]); +pub struct OpenParams { + pub paths: Vec, + pub settings: watch::Receiver, +} + +fn open(settings: &Receiver, ctx: &mut MutableAppContext) { + let settings = settings.clone(); + ctx.prompt_for_paths( + PathPromptOptions { + files: true, + directories: true, + multiple: true, + }, + move |paths, ctx| { + if let Some(paths) = paths { + ctx.dispatch_global_action("workspace:open_paths", OpenParams { paths, settings }); + } + }, + ); +} + +fn open_paths(params: &OpenParams, app: &mut MutableAppContext) { + log::info!("open paths {:?}", params.paths); + + // Open paths in existing workspace if possible + for window_id in app.window_ids().collect::>() { + if let Some(handle) = app.root_view::(window_id) { + if handle.update(app, |view, ctx| { + if view.contains_paths(¶ms.paths, ctx.as_ref()) { + let open_paths = view.open_paths(¶ms.paths, ctx); + ctx.foreground().spawn(open_paths).detach(); + log::info!("open paths on existing workspace"); + true + } else { + false + } + }) { + return; + } + } + } + + log::info!("open new workspace"); + + // Add a new workspace if necessary + app.add_window(|ctx| { + let mut view = Workspace::new(0, params.settings.clone(), ctx); + let open_paths = view.open_paths(¶ms.paths, ctx); + ctx.foreground().spawn(open_paths).detach(); + view + }); +} + +fn quit(_: &(), app: &mut MutableAppContext) { + app.platform().quit(); } pub trait ItemView: View { @@ -122,24 +196,27 @@ pub struct State { pub center: PaneGroup, } -pub struct WorkspaceView { - pub workspace: ModelHandle, +pub struct Workspace { pub settings: watch::Receiver, modal: Option, center: PaneGroup, panes: Vec>, active_pane: ViewHandle, loading_entries: HashSet<(usize, Arc)>, + replica_id: ReplicaId, + worktrees: HashSet>, + buffers: HashMap< + (usize, u64), + postage::watch::Receiver, Arc>>>, + >, } -impl WorkspaceView { +impl Workspace { pub fn new( - workspace: ModelHandle, + replica_id: ReplicaId, settings: watch::Receiver, ctx: &mut ViewContext, ) -> Self { - ctx.observe(&workspace, Self::workspace_updated); - let pane = ctx.add_view(|_| Pane::new(settings.clone())); let pane_id = pane.id(); ctx.subscribe_to_view(&pane, move |me, _, event, ctx| { @@ -147,29 +224,65 @@ impl WorkspaceView { }); ctx.focus(&pane); - WorkspaceView { - workspace, + Workspace { modal: None, center: PaneGroup::new(pane.id()), panes: vec![pane.clone()], active_pane: pane.clone(), loading_entries: HashSet::new(), settings, + replica_id, + worktrees: Default::default(), + buffers: Default::default(), } } + pub fn worktrees(&self) -> &HashSet> { + &self.worktrees + } + pub fn contains_paths(&self, paths: &[PathBuf], app: &AppContext) -> bool { - self.workspace.read(app).contains_paths(paths, app) + paths.iter().all(|path| self.contains_path(&path, app)) + } + + pub fn contains_path(&self, path: &Path, app: &AppContext) -> bool { + self.worktrees + .iter() + .any(|worktree| worktree.read(app).contains_abs_path(path)) + } + + pub fn worktree_scans_complete(&self, ctx: &AppContext) -> impl Future + 'static { + let futures = self + .worktrees + .iter() + .map(|worktree| worktree.read(ctx).scan_complete()) + .collect::>(); + async move { + for future in futures { + future.await; + } + } } pub fn open_paths( - &self, + &mut self, paths: &[PathBuf], ctx: &mut ViewContext, ) -> impl Future { - let entries = self - .workspace - .update(ctx, |workspace, ctx| workspace.open_paths(paths, ctx)); + let entries = paths + .iter() + .cloned() + .map(|path| { + for tree in self.worktrees.iter() { + if let Ok(relative_path) = path.strip_prefix(tree.read(ctx).abs_path()) { + return (tree.id(), relative_path.into()); + } + } + let worktree_id = self.add_worktree(&path, ctx); + (worktree_id, Path::new("").into()) + }) + .collect::>(); + let bg = ctx.background_executor().clone(); let tasks = paths .iter() @@ -197,6 +310,15 @@ impl WorkspaceView { } } + pub fn add_worktree(&mut self, path: &Path, ctx: &mut ViewContext) -> usize { + let worktree = ctx.add_model(|ctx| Worktree::new(path, ctx)); + let worktree_id = worktree.id(); + ctx.observe_model(&worktree, |_, _, ctx| ctx.notify()); + self.worktrees.insert(worktree); + ctx.notify(); + worktree_id + } + pub fn toggle_modal(&mut self, ctx: &mut ViewContext, add_view: F) where V: 'static + View, @@ -241,31 +363,81 @@ impl WorkspaceView { return None; } - self.loading_entries.insert(entry.clone()); + let (worktree_id, path) = entry.clone(); - match self.workspace.update(ctx, |workspace, ctx| { - workspace.open_entry(entry.clone(), ctx) - }) { - Err(error) => { - error!("{}", error); - None + let worktree = match self.worktrees.get(&worktree_id).cloned() { + Some(worktree) => worktree, + None => { + log::error!("worktree {} does not exist", worktree_id); + return None; } - Ok(item) => { - let settings = self.settings.clone(); - Some(ctx.spawn(item, move |me, item, ctx| { - me.loading_entries.remove(&entry); - match item { - Ok(item) => { - let item_view = item.add_view(ctx.window_id(), settings, ctx.as_mut()); - me.add_item(item_view, ctx); - } - Err(error) => { - error!("{}", error); - } - } - })) + }; + + let inode = match worktree.read(ctx).inode_for_path(&path) { + Some(inode) => inode, + None => { + log::error!("path {:?} does not exist", path); + return None; } + }; + + let file = match worktree.file(path.clone(), ctx.as_ref()) { + Some(file) => file, + None => { + log::error!("path {:?} does not exist", path); + return None; + } + }; + + self.loading_entries.insert(entry.clone()); + + if let Entry::Vacant(entry) = self.buffers.entry((worktree_id, inode)) { + let (mut tx, rx) = postage::watch::channel(); + entry.insert(rx); + let history = file.load_history(ctx.as_ref()); + let replica_id = self.replica_id; + let buffer = ctx + .background_executor() + .spawn(async move { Ok(Buffer::from_history(replica_id, history.await?)) }); + ctx.spawn(buffer, move |_, from_history_result, ctx| { + *tx.borrow_mut() = Some(match from_history_result { + Ok(buffer) => Ok(ctx.add_model(|_| buffer)), + Err(error) => Err(Arc::new(error)), + }) + }) + .detach() } + + let mut watch = self.buffers.get(&(worktree_id, inode)).unwrap().clone(); + Some(ctx.spawn( + async move { + loop { + if let Some(load_result) = watch.borrow().as_ref() { + return load_result.clone(); + } + watch.next().await; + } + }, + move |me, load_result, ctx| { + me.loading_entries.remove(&entry); + match load_result { + Ok(buffer_handle) => { + let buffer_view = Box::new(ctx.add_view(|ctx| { + BufferView::for_buffer( + buffer_handle, + Some(file), + me.settings.clone(), + ctx, + ) + })); + me.add_item(buffer_view, ctx); + } + Err(error) => { + log::error!("error opening item: {}", error); + } + } + }, + )) } pub fn save_active_item(&mut self, _: &(), ctx: &mut ViewContext) { @@ -299,10 +471,6 @@ impl WorkspaceView { }; } - fn workspace_updated(&mut self, _: ModelHandle, ctx: &mut ViewContext) { - ctx.notify(); - } - fn add_pane(&mut self, ctx: &mut ViewContext) -> ViewHandle { let pane = ctx.add_view(|_| Pane::new(self.settings.clone())); let pane_id = pane.id(); @@ -388,11 +556,11 @@ impl WorkspaceView { } } -impl Entity for WorkspaceView { +impl Entity for Workspace { type Event = (); } -impl View for WorkspaceView { +impl View for Workspace { fn ui_name() -> &'static str { "Workspace" } @@ -414,13 +582,95 @@ impl View for WorkspaceView { } } +#[cfg(test)] +pub trait WorkspaceHandle { + fn file_entries(&self, app: &AppContext) -> Vec<(usize, Arc)>; +} + +#[cfg(test)] +impl WorkspaceHandle for ViewHandle { + fn file_entries(&self, app: &AppContext) -> Vec<(usize, Arc)> { + self.read(app) + .worktrees() + .iter() + .flat_map(|tree| { + let tree_id = tree.id(); + tree.read(app) + .files(0) + .map(move |f| (tree_id, f.path().clone())) + }) + .collect::>() + } +} + #[cfg(test)] mod tests { - use super::{pane, Workspace, WorkspaceView}; - use crate::{settings, test::temp_tree, workspace::WorkspaceHandle as _}; + use super::*; + use crate::{editor::BufferView, settings, test::temp_tree}; use gpui::App; use serde_json::json; - use std::collections::HashSet; + use std::{collections::HashSet, os::unix}; + + #[test] + fn test_open_paths_action() { + App::test((), |app| { + let settings = settings::channel(&app.font_cache()).unwrap().1; + + init(app); + + let dir = temp_tree(json!({ + "a": { + "aa": null, + "ab": null, + }, + "b": { + "ba": null, + "bb": null, + }, + "c": { + "ca": null, + "cb": null, + }, + })); + + app.dispatch_global_action( + "workspace:open_paths", + OpenParams { + paths: vec![ + dir.path().join("a").to_path_buf(), + dir.path().join("b").to_path_buf(), + ], + settings: settings.clone(), + }, + ); + assert_eq!(app.window_ids().count(), 1); + + app.dispatch_global_action( + "workspace:open_paths", + OpenParams { + paths: vec![dir.path().join("a").to_path_buf()], + settings: settings.clone(), + }, + ); + assert_eq!(app.window_ids().count(), 1); + let workspace_view_1 = app + .root_view::(app.window_ids().next().unwrap()) + .unwrap(); + assert_eq!(workspace_view_1.read(app).worktrees().len(), 2); + + app.dispatch_global_action( + "workspace:open_paths", + OpenParams { + paths: vec![ + dir.path().join("b").to_path_buf(), + dir.path().join("c").to_path_buf(), + ], + settings: settings.clone(), + }, + ); + assert_eq!(app.window_ids().count(), 2); + }); + } #[test] fn test_open_entry() { @@ -434,7 +684,13 @@ mod tests { })); let settings = settings::channel(&app.font_cache()).unwrap().1; - let workspace = app.add_model(|ctx| Workspace::new(vec![dir.path().into()], ctx)); + + let (_, workspace) = app.add_window(|ctx| { + let mut workspace = Workspace::new(0, settings, ctx); + workspace.add_worktree(dir.path(), ctx); + workspace + }); + app.read(|ctx| workspace.read(ctx).worktree_scans_complete(ctx)) .await; let entries = app.read(|ctx| workspace.file_entries(ctx)); @@ -442,12 +698,10 @@ mod tests { let file2 = entries[1].clone(); let file3 = entries[2].clone(); - let (_, workspace_view) = - app.add_window(|ctx| WorkspaceView::new(workspace.clone(), settings, ctx)); - let pane = app.read(|ctx| workspace_view.read(ctx).active_pane().clone()); + let pane = app.read(|ctx| workspace.read(ctx).active_pane().clone()); // Open the first entry - workspace_view + workspace .update(&mut app, |w, ctx| w.open_entry(file1.clone(), ctx)) .unwrap() .await; @@ -461,7 +715,7 @@ mod tests { }); // Open the second entry - workspace_view + workspace .update(&mut app, |w, ctx| w.open_entry(file2.clone(), ctx)) .unwrap() .await; @@ -475,7 +729,7 @@ mod tests { }); // Open the first entry again. The existing pane item is activated. - workspace_view.update(&mut app, |w, ctx| { + workspace.update(&mut app, |w, ctx| { assert!(w.open_entry(file1.clone(), ctx).is_none()) }); app.read(|ctx| { @@ -488,7 +742,7 @@ mod tests { }); // Open the third entry twice concurrently. Only one pane item is added. - workspace_view + workspace .update(&mut app, |w, ctx| { let task = w.open_entry(file3.clone(), ctx).unwrap(); assert!(w.open_entry(file3.clone(), ctx).is_none()); @@ -516,22 +770,24 @@ mod tests { "b.txt": "", })); - let workspace = app.add_model(|ctx| Workspace::new(vec![dir1.path().into()], ctx)); let settings = settings::channel(&app.font_cache()).unwrap().1; - let (_, workspace_view) = - app.add_window(|ctx| WorkspaceView::new(workspace.clone(), settings, ctx)); + let (_, workspace) = app.add_window(|ctx| { + let mut workspace = Workspace::new(0, settings, ctx); + workspace.add_worktree(dir1.path(), ctx); + workspace + }); app.read(|ctx| workspace.read(ctx).worktree_scans_complete(ctx)) .await; // Open a file within an existing worktree. app.update(|ctx| { - workspace_view.update(ctx, |view, ctx| { + workspace.update(ctx, |view, ctx| { view.open_paths(&[dir1.path().join("a.txt")], ctx) }) }) .await; app.read(|ctx| { - workspace_view + workspace .read(ctx) .active_pane() .read(ctx) @@ -543,7 +799,7 @@ mod tests { // Open a file outside of any existing worktree. app.update(|ctx| { - workspace_view.update(ctx, |view, ctx| { + workspace.update(ctx, |view, ctx| { view.open_paths(&[dir2.path().join("b.txt")], ctx) }) }) @@ -563,7 +819,7 @@ mod tests { ); }); app.read(|ctx| { - workspace_view + workspace .read(ctx) .active_pane() .read(ctx) @@ -575,6 +831,67 @@ mod tests { }); } + #[test] + fn test_open_two_paths_to_the_same_file() { + use crate::workspace::ItemViewHandle; + + App::test_async((), |mut app| async move { + // Create a worktree with a symlink: + // dir + // ├── hello.txt + // └── hola.txt -> hello.txt + let temp_dir = temp_tree(json!({ "hello.txt": "hi" })); + let dir = temp_dir.path(); + unix::fs::symlink(dir.join("hello.txt"), dir.join("hola.txt")).unwrap(); + + let settings = settings::channel(&app.font_cache()).unwrap().1; + let (_, workspace) = app.add_window(|ctx| { + let mut workspace = Workspace::new(0, settings, ctx); + workspace.add_worktree(dir, ctx); + workspace + }); + app.read(|ctx| workspace.read(ctx).worktree_scans_complete(ctx)) + .await; + + // Simultaneously open both the original file and the symlink to the same file. + app.update(|ctx| { + workspace.update(ctx, |view, ctx| { + view.open_paths(&[dir.join("hello.txt"), dir.join("hola.txt")], ctx) + }) + }) + .await; + + // The same content shows up with two different editors. + let buffer_views = app.read(|ctx| { + workspace + .read(ctx) + .active_pane() + .read(ctx) + .items() + .iter() + .map(|i| i.to_any().downcast::().unwrap()) + .collect::>() + }); + app.read(|ctx| { + assert_eq!(buffer_views[0].title(ctx), "hello.txt"); + assert_eq!(buffer_views[1].title(ctx), "hola.txt"); + assert_eq!(buffer_views[0].read(ctx).text(ctx), "hi"); + assert_eq!(buffer_views[1].read(ctx).text(ctx), "hi"); + }); + + // When modifying one buffer, the changes appear in both editors. + app.update(|ctx| { + buffer_views[0].update(ctx, |buf, ctx| { + buf.insert(&"oh, ".to_string(), ctx); + }); + }); + app.read(|ctx| { + assert_eq!(buffer_views[0].read(ctx).text(ctx), "oh, hi"); + assert_eq!(buffer_views[1].read(ctx).text(ctx), "oh, hi"); + }); + }); + } + #[test] fn test_pane_actions() { App::test_async((), |mut app| async move { @@ -589,17 +906,19 @@ mod tests { })); let settings = settings::channel(&app.font_cache()).unwrap().1; - let workspace = app.add_model(|ctx| Workspace::new(vec![dir.path().into()], ctx)); + let (window_id, workspace) = app.add_window(|ctx| { + let mut workspace = Workspace::new(0, settings, ctx); + workspace.add_worktree(dir.path(), ctx); + workspace + }); app.read(|ctx| workspace.read(ctx).worktree_scans_complete(ctx)) .await; let entries = app.read(|ctx| workspace.file_entries(ctx)); let file1 = entries[0].clone(); - let (window_id, workspace_view) = - app.add_window(|ctx| WorkspaceView::new(workspace.clone(), settings, ctx)); - let pane_1 = app.read(|ctx| workspace_view.read(ctx).active_pane().clone()); + let pane_1 = app.read(|ctx| workspace.read(ctx).active_pane().clone()); - workspace_view + workspace .update(&mut app, |w, ctx| w.open_entry(file1.clone(), ctx)) .unwrap() .await; @@ -612,14 +931,14 @@ mod tests { app.dispatch_action(window_id, vec![pane_1.id()], "pane:split_right", ()); app.update(|ctx| { - let pane_2 = workspace_view.read(ctx).active_pane().clone(); + let pane_2 = workspace.read(ctx).active_pane().clone(); assert_ne!(pane_1, pane_2); let pane2_item = pane_2.read(ctx).active_item().unwrap(); assert_eq!(pane2_item.entry_id(ctx.as_ref()), Some(file1.clone())); ctx.dispatch_action(window_id, vec![pane_2.id()], "pane:close_active_item", ()); - let workspace_view = workspace_view.read(ctx); + let workspace_view = workspace.read(ctx); assert_eq!(workspace_view.panes.len(), 1); assert_eq!(workspace_view.active_pane(), &pane_1); }); diff --git a/zed/src/workspace/mod.rs b/zed/src/workspace/mod.rs deleted file mode 100644 index 504bb9a8c05b4965487820d745682e49395a0165..0000000000000000000000000000000000000000 --- a/zed/src/workspace/mod.rs +++ /dev/null @@ -1,159 +0,0 @@ -pub mod pane; -pub mod pane_group; -pub mod workspace; -pub mod workspace_view; - -pub use pane::*; -pub use pane_group::*; -pub use workspace::*; -pub use workspace_view::*; - -use crate::{ - settings::Settings, - watch::{self, Receiver}, -}; -use gpui::{MutableAppContext, PathPromptOptions}; -use std::path::PathBuf; - -pub fn init(app: &mut MutableAppContext) { - app.add_global_action("workspace:open", open); - app.add_global_action("workspace:open_paths", open_paths); - app.add_global_action("app:quit", quit); - pane::init(app); - workspace_view::init(app); -} - -pub struct OpenParams { - pub paths: Vec, - pub settings: watch::Receiver, -} - -fn open(settings: &Receiver, ctx: &mut MutableAppContext) { - let settings = settings.clone(); - ctx.prompt_for_paths( - PathPromptOptions { - files: true, - directories: true, - multiple: true, - }, - move |paths, ctx| { - if let Some(paths) = paths { - ctx.dispatch_global_action("workspace:open_paths", OpenParams { paths, settings }); - } - }, - ); -} - -fn open_paths(params: &OpenParams, app: &mut MutableAppContext) { - log::info!("open paths {:?}", params.paths); - - // Open paths in existing workspace if possible - for window_id in app.window_ids().collect::>() { - if let Some(handle) = app.root_view::(window_id) { - if handle.update(app, |view, ctx| { - if view.contains_paths(¶ms.paths, ctx.as_ref()) { - let open_paths = view.open_paths(¶ms.paths, ctx); - ctx.foreground().spawn(open_paths).detach(); - log::info!("open paths on existing workspace"); - true - } else { - false - } - }) { - return; - } - } - } - - log::info!("open new workspace"); - - // Add a new workspace if necessary - let workspace = app.add_model(|ctx| Workspace::new(vec![], ctx)); - app.add_window(|ctx| { - let view = WorkspaceView::new(workspace, params.settings.clone(), ctx); - let open_paths = view.open_paths(¶ms.paths, ctx); - ctx.foreground().spawn(open_paths).detach(); - view - }); -} - -fn quit(_: &(), app: &mut MutableAppContext) { - app.platform().quit(); -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::{settings, test::*}; - use gpui::App; - use serde_json::json; - - #[test] - fn test_open_paths_action() { - App::test((), |app| { - let settings = settings::channel(&app.font_cache()).unwrap().1; - - init(app); - - let dir = temp_tree(json!({ - "a": { - "aa": null, - "ab": null, - }, - "b": { - "ba": null, - "bb": null, - }, - "c": { - "ca": null, - "cb": null, - }, - })); - - app.dispatch_global_action( - "workspace:open_paths", - OpenParams { - paths: vec![ - dir.path().join("a").to_path_buf(), - dir.path().join("b").to_path_buf(), - ], - settings: settings.clone(), - }, - ); - assert_eq!(app.window_ids().count(), 1); - - app.dispatch_global_action( - "workspace:open_paths", - OpenParams { - paths: vec![dir.path().join("a").to_path_buf()], - settings: settings.clone(), - }, - ); - assert_eq!(app.window_ids().count(), 1); - let workspace_view_1 = app - .root_view::(app.window_ids().next().unwrap()) - .unwrap(); - assert_eq!( - workspace_view_1 - .read(app) - .workspace - .read(app) - .worktrees() - .len(), - 2 - ); - - app.dispatch_global_action( - "workspace:open_paths", - OpenParams { - paths: vec![ - dir.path().join("b").to_path_buf(), - dir.path().join("c").to_path_buf(), - ], - settings: settings.clone(), - }, - ); - assert_eq!(app.window_ids().count(), 2); - }); - } -} diff --git a/zed/src/workspace/workspace.rs b/zed/src/workspace/workspace.rs deleted file mode 100644 index 0f0272957260f4c4dbb952ab44eb3ab3475ea0a4..0000000000000000000000000000000000000000 --- a/zed/src/workspace/workspace.rs +++ /dev/null @@ -1,298 +0,0 @@ -use super::{ItemView, ItemViewHandle}; -use crate::{ - editor::{Buffer, History}, - settings::Settings, - time::ReplicaId, - watch, - worktree::{Worktree, WorktreeHandle as _}, -}; -use anyhow::anyhow; -use gpui::{AppContext, Entity, Handle, ModelContext, ModelHandle, MutableAppContext, ViewContext}; -use smol::prelude::*; -use std::{ - collections::{HashMap, HashSet}, - fmt::Debug, - path::{Path, PathBuf}, - pin::Pin, - sync::Arc, -}; - -pub trait Item -where - Self: Sized, -{ - type View: ItemView; - fn build_view( - handle: ModelHandle, - settings: watch::Receiver, - ctx: &mut ViewContext, - ) -> Self::View; -} - -pub trait ItemHandle: Debug + Send + Sync { - fn add_view( - &self, - window_id: usize, - settings: watch::Receiver, - app: &mut MutableAppContext, - ) -> Box; - fn id(&self) -> usize; - fn boxed_clone(&self) -> Box; -} - -impl ItemHandle for ModelHandle { - fn add_view( - &self, - window_id: usize, - settings: watch::Receiver, - app: &mut MutableAppContext, - ) -> Box { - Box::new(app.add_view(window_id, |ctx| T::build_view(self.clone(), settings, ctx))) - } - - fn id(&self) -> usize { - Handle::id(self) - } - - fn boxed_clone(&self) -> Box { - Box::new(self.clone()) - } -} - -impl Clone for Box { - fn clone(&self) -> Self { - self.boxed_clone() - } -} - -pub type OpenResult = Result, Arc>; - -#[derive(Clone)] -enum OpenedItem { - Loading(watch::Receiver>), - Loaded(Box), -} - -pub struct Workspace { - replica_id: ReplicaId, - worktrees: HashSet>, - items: HashMap<(usize, u64), OpenedItem>, -} - -impl Workspace { - pub fn new(paths: Vec, ctx: &mut ModelContext) -> Self { - let mut workspace = Self { - replica_id: 0, - worktrees: HashSet::new(), - items: HashMap::new(), - }; - workspace.open_paths(&paths, ctx); - workspace - } - - pub fn worktrees(&self) -> &HashSet> { - &self.worktrees - } - - pub fn worktree_scans_complete(&self, ctx: &AppContext) -> impl Future + 'static { - let futures = self - .worktrees - .iter() - .map(|worktree| worktree.read(ctx).scan_complete()) - .collect::>(); - async move { - for future in futures { - future.await; - } - } - } - - pub fn contains_paths(&self, paths: &[PathBuf], app: &AppContext) -> bool { - paths.iter().all(|path| self.contains_path(&path, app)) - } - - pub fn contains_path(&self, path: &Path, app: &AppContext) -> bool { - self.worktrees - .iter() - .any(|worktree| worktree.read(app).contains_abs_path(path)) - } - - pub fn open_paths( - &mut self, - paths: &[PathBuf], - ctx: &mut ModelContext, - ) -> Vec<(usize, Arc)> { - paths - .iter() - .cloned() - .map(move |path| self.open_path(path, ctx)) - .collect() - } - - fn open_path(&mut self, path: PathBuf, ctx: &mut ModelContext) -> (usize, Arc) { - for tree in self.worktrees.iter() { - if let Ok(relative_path) = path.strip_prefix(tree.read(ctx).abs_path()) { - return (tree.id(), relative_path.into()); - } - } - - let worktree = ctx.add_model(|ctx| Worktree::new(path.clone(), ctx)); - let worktree_id = worktree.id(); - ctx.observe(&worktree, Self::on_worktree_updated); - self.worktrees.insert(worktree); - ctx.notify(); - (worktree_id, Path::new("").into()) - } - - pub fn open_entry( - &mut self, - (worktree_id, path): (usize, Arc), - ctx: &mut ModelContext<'_, Self>, - ) -> anyhow::Result + Send>>> { - let worktree = self - .worktrees - .get(&worktree_id) - .cloned() - .ok_or_else(|| anyhow!("worktree {} does not exist", worktree_id,))?; - - let inode = worktree - .read(ctx) - .inode_for_path(&path) - .ok_or_else(|| anyhow!("path {:?} does not exist", path))?; - - let item_key = (worktree_id, inode); - if let Some(item) = self.items.get(&item_key).cloned() { - return Ok(async move { - match item { - OpenedItem::Loaded(handle) => { - return Ok(handle); - } - OpenedItem::Loading(rx) => loop { - rx.updated().await; - - if let Some(result) = smol::block_on(rx.read()).clone() { - return result; - } - }, - } - } - .boxed()); - } - - let replica_id = self.replica_id; - let file = worktree.file(path.clone(), ctx.as_ref())?; - let history = file.load_history(ctx.as_ref()); - - let (mut tx, rx) = watch::channel(None); - self.items.insert(item_key, OpenedItem::Loading(rx)); - ctx.spawn( - history, - move |me, history: anyhow::Result, ctx| match history { - Ok(history) => { - let handle = Box::new( - ctx.add_model(|ctx| Buffer::from_history(replica_id, file, history, ctx)), - ) as Box; - me.items - .insert(item_key, OpenedItem::Loaded(handle.clone())); - ctx.spawn( - async move { - tx.update(|value| *value = Some(Ok(handle))).await; - }, - |_, _, _| {}, - ) - .detach(); - } - Err(error) => { - ctx.spawn( - async move { - tx.update(|value| *value = Some(Err(Arc::new(error)))).await; - }, - |_, _, _| {}, - ) - .detach(); - } - }, - ) - .detach(); - - self.open_entry((worktree_id, path), ctx) - } - - fn on_worktree_updated(&mut self, _: ModelHandle, ctx: &mut ModelContext) { - ctx.notify(); - } -} - -impl Entity for Workspace { - type Event = (); -} - -#[cfg(test)] -pub trait WorkspaceHandle { - fn file_entries(&self, app: &AppContext) -> Vec<(usize, Arc)>; -} - -#[cfg(test)] -impl WorkspaceHandle for ModelHandle { - fn file_entries(&self, app: &AppContext) -> Vec<(usize, Arc)> { - self.read(app) - .worktrees() - .iter() - .flat_map(|tree| { - let tree_id = tree.id(); - tree.read(app) - .files(0) - .map(move |f| (tree_id, f.path().clone())) - }) - .collect::>() - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::test::temp_tree; - use gpui::App; - use serde_json::json; - - #[test] - fn test_open_entry() { - App::test_async((), |mut app| async move { - let dir = temp_tree(json!({ - "a": { - "aa": "aa contents", - "ab": "ab contents", - }, - })); - - let workspace = app.add_model(|ctx| Workspace::new(vec![dir.path().into()], ctx)); - app.read(|ctx| workspace.read(ctx).worktree_scans_complete(ctx)) - .await; - - // Get the first file entry. - let tree = app.read(|ctx| workspace.read(ctx).worktrees.iter().next().unwrap().clone()); - let path = app.read(|ctx| tree.read(ctx).files(0).next().unwrap().path().clone()); - let entry = (tree.id(), path); - - // Open the same entry twice before it finishes loading. - let (future_1, future_2) = workspace.update(&mut app, |w, app| { - ( - w.open_entry(entry.clone(), app).unwrap(), - w.open_entry(entry.clone(), app).unwrap(), - ) - }); - - let handle_1 = future_1.await.unwrap(); - let handle_2 = future_2.await.unwrap(); - assert_eq!(handle_1.id(), handle_2.id()); - - // Open the same entry again now that it has loaded - let handle_3 = workspace - .update(&mut app, |w, app| w.open_entry(entry, app).unwrap()) - .await - .unwrap(); - - assert_eq!(handle_3.id(), handle_1.id()); - }) - } -} diff --git a/zed/src/worktree.rs b/zed/src/worktree.rs index 7152316938a9a7b917d11c6ef61d18cbeb79d53c..ea023fb813d49f2260abbea835aa97b07a33470e 100644 --- a/zed/src/worktree.rs +++ b/zed/src/worktree.rs @@ -7,9 +7,9 @@ use crate::{ sum_tree::{self, Cursor, Edit, SeekBias, SumTree}, }; use ::ignore::gitignore::Gitignore; -use anyhow::{anyhow, Context, Result}; +use anyhow::{Context, Result}; pub use fuzzy::{match_paths, PathMatch}; -use gpui::{scoped_pool, AppContext, Entity, ModelContext, ModelHandle, Task}; +use gpui::{scoped_pool, AppContext, Entity, ModelContext, ModelHandle, Task, View, ViewContext}; use lazy_static::lazy_static; use parking_lot::Mutex; use postage::{ @@ -419,14 +419,14 @@ impl FileHandle { (self.worktree.id(), self.path()) } - pub fn observe_from_model( + pub fn observe_from_view( &self, - ctx: &mut ModelContext, - mut callback: impl FnMut(&mut T, FileHandle, &mut ModelContext) + 'static, + ctx: &mut ViewContext, + mut callback: impl FnMut(&mut T, FileHandle, &mut ViewContext) + 'static, ) { let mut prev_state = self.state.lock().clone(); let cur_state = Arc::downgrade(&self.state); - ctx.observe(&self.worktree, move |observer, worktree, ctx| { + ctx.observe_model(&self.worktree, move |observer, worktree, ctx| { if let Some(cur_state) = cur_state.upgrade() { let cur_state_unlocked = cur_state.lock(); if *cur_state_unlocked != prev_state { @@ -1126,15 +1126,14 @@ struct UpdateIgnoreStatusJob { } pub trait WorktreeHandle { - fn file(&self, path: impl AsRef, app: &AppContext) -> Result; + fn file(&self, path: impl AsRef, app: &AppContext) -> Option; } impl WorktreeHandle for ModelHandle { - fn file(&self, path: impl AsRef, app: &AppContext) -> Result { + fn file(&self, path: impl AsRef, app: &AppContext) -> Option { let tree = self.read(app); - let entry = tree - .entry_for_path(&path) - .ok_or_else(|| anyhow!("path does not exist in tree"))?; + let entry = tree.entry_for_path(&path)?; + let path = entry.path().clone(); let mut handles = tree.handles.lock(); let state = if let Some(state) = handles.get(&path).and_then(Weak::upgrade) { @@ -1148,7 +1147,7 @@ impl WorktreeHandle for ModelHandle { state }; - Ok(FileHandle { + Some(FileHandle { worktree: self.clone(), state, }) @@ -1347,8 +1346,7 @@ mod tests { app.read(|ctx| tree.read(ctx).scan_complete()).await; app.read(|ctx| assert_eq!(tree.read(ctx).file_count(), 1)); - let buffer = - app.add_model(|ctx| Buffer::new(1, "a line of text.\n".repeat(10 * 1024), ctx)); + let buffer = app.add_model(|_| Buffer::new(1, "a line of text.\n".repeat(10 * 1024))); let path = tree.update(&mut app, |tree, ctx| { let path = tree.files(0).next().unwrap().path().clone();