diff --git a/Cargo.lock b/Cargo.lock index 029f9e5ce2..86135c73e3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -177,6 +177,28 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "250f629c0161ad8107cf89319e990051fae62832fd343083bea452d93e2205fd" +[[package]] +name = "alsa" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8512c9117059663fb5606788fbca3619e2a91dac0e3fe516242eab1fa6be5e44" +dependencies = [ + "alsa-sys", + "bitflags", + "libc", + "nix", +] + +[[package]] +name = "alsa-sys" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db8fee663d06c4e303404ef5f40488a53e062f89ba8bfed81f42325aafad1527" +dependencies = [ + "libc", + "pkg-config", +] + [[package]] name = "ambient-authority" version = "0.0.1" @@ -590,6 +612,19 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "audio" +version = "0.1.0" +dependencies = [ + "anyhow", + "collections", + "gpui", + "log", + "parking_lot 0.11.2", + "rodio", + "util", +] + [[package]] name = "auto_update" version = "0.1.0" @@ -756,6 +791,26 @@ dependencies = [ "serde", ] +[[package]] +name = "bindgen" +version = "0.64.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4243e6031260db77ede97ad86c27e501d646a27ab57b59a574f725d98ab1fb4" +dependencies = [ + "bitflags", + "cexpr", + "clang-sys", + "lazy_static", + "lazycell", + "peeking_take_while", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn 1.0.109", +] + [[package]] name = "bindgen" version = "0.65.1" @@ -857,7 +912,7 @@ checksum = "0754613691538d51f329cce9af41d7b7ca150bc973056f1156611489475f54f7" dependencies = [ "borsh-derive-internal", "borsh-schema-derive-internal", - "proc-macro-crate", + "proc-macro-crate 0.1.5", "proc-macro2", "syn 1.0.109", ] @@ -986,6 +1041,7 @@ version = "0.1.0" dependencies = [ "anyhow", "async-broadcast", + "audio", "client", "collections", "fs", @@ -1082,6 +1138,12 @@ dependencies = [ "jobserver", ] +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + [[package]] name = "cexpr" version = "0.6.0" @@ -1155,7 +1217,7 @@ dependencies = [ "bitflags", "clap_derive 3.2.25", "clap_lex 0.2.4", - "indexmap", + "indexmap 1.9.3", "once_cell", "strsim", "termcolor", @@ -1226,6 +1288,12 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2da6da31387c7e4ef160ffab6d5e7f00c42626fe39aea70a7b0f1773f7dd6c1b" +[[package]] +name = "claxon" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bfbf56724aa9eca8afa4fcfadeb479e722935bb2a0900c2d37e0cc477af0688" + [[package]] name = "cli" version = "0.1.0" @@ -1337,6 +1405,7 @@ version = "0.15.0" dependencies = [ "anyhow", "async-tungstenite", + "audio", "axum", "axum-extra", "base64 0.13.1", @@ -1444,6 +1513,16 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" +[[package]] +name = "combine" +version = "4.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35ed6e9d84f0b51a7f52daf1c7d71dd136fd7a3f41a8462b8cdb8c78d920fad4" +dependencies = [ + "bytes 1.4.0", + "memchr", +] + [[package]] name = "command_palette" version = "0.1.0" @@ -1540,11 +1619,17 @@ name = "core-foundation" version = "0.9.3" source = "git+https://github.com/servo/core-foundation-rs?rev=079665882507dd5e2ff77db3de5070c1f6c0fb85#079665882507dd5e2ff77db3de5070c1f6c0fb85" dependencies = [ - "core-foundation-sys", + "core-foundation-sys 0.8.3", "libc", "uuid 0.5.1", ] +[[package]] +name = "core-foundation-sys" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7ca8a5221364ef15ce201e8ed2f609fc312682a8f4e0e3d4aa5879764e0fa3b" + [[package]] name = "core-foundation-sys" version = "0.8.3" @@ -1594,6 +1679,51 @@ dependencies = [ "libc", ] +[[package]] +name = "coreaudio-rs" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb17e2d1795b1996419648915df94bc7103c28f7b48062d7acf4652fc371b2ff" +dependencies = [ + "bitflags", + "core-foundation-sys 0.6.2", + "coreaudio-sys", +] + +[[package]] +name = "coreaudio-sys" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f034b2258e6c4ade2f73bf87b21047567fb913ee9550837c2316d139b0262b24" +dependencies = [ + "bindgen 0.64.0", +] + +[[package]] +name = "cpal" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d959d90e938c5493000514b446987c07aed46c668faaa7d34d6c7a67b1a578c" +dependencies = [ + "alsa", + "core-foundation-sys 0.8.3", + "coreaudio-rs", + "dasp_sample", + "jni 0.19.0", + "js-sys", + "libc", + "mach2", + "ndk", + "ndk-context", + "oboe", + "once_cell", + "parking_lot 0.12.1", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "windows 0.46.0", +] + [[package]] name = "cpp_demangle" version = "0.3.5" @@ -1924,6 +2054,12 @@ dependencies = [ "parking_lot_core 0.9.7", ] +[[package]] +name = "dasp_sample" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c87e182de0887fd5361989c677c4e8f5000cd9491d6d563161a8f3a5519fc7f" + [[package]] name = "data-url" version = "0.1.1" @@ -2233,6 +2369,12 @@ dependencies = [ "serde", ] +[[package]] +name = "equivalent" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88bffebc5d80432c9b140ee17875ff173a8ab62faad5b257da912bd2f6c1c0a1" + [[package]] name = "erased-serde" version = "0.3.25" @@ -2793,7 +2935,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22030e2c5a68ec659fde1e949a745124b48e6fa8b045b7ed5bd1fe4ccc5c4e5d" dependencies = [ "fallible-iterator", - "indexmap", + "indexmap 1.9.3", "stable_deref_trait", ] @@ -2889,7 +3031,7 @@ dependencies = [ "anyhow", "async-task", "backtrace", - "bindgen", + "bindgen 0.65.1", "block", "cc", "cocoa", @@ -2961,7 +3103,7 @@ dependencies = [ "futures-sink", "futures-util", "http", - "indexmap", + "indexmap 1.9.3", "slab", "tokio", "tokio-util 0.7.8", @@ -2995,6 +3137,12 @@ dependencies = [ "ahash 0.8.3", ] +[[package]] +name = "hashbrown" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" + [[package]] name = "hashlink" version = "0.8.1" @@ -3105,6 +3253,12 @@ dependencies = [ "digest 0.10.6", ] +[[package]] +name = "hound" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d13cdbd5dbb29f9c88095bbdc2590c9cba0d0a1269b983fef6b2cdd7e9f4db1" + [[package]] name = "http" version = "0.2.9" @@ -3213,11 +3367,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0722cd7114b7de04316e7ea5456a0bbb20e4adb46fd27a3697adb812cff0f37c" dependencies = [ "android_system_properties", - "core-foundation-sys", + "core-foundation-sys 0.8.3", "iana-time-zone-haiku", "js-sys", "wasm-bindgen", - "windows", + "windows 0.48.0", ] [[package]] @@ -3287,6 +3441,16 @@ dependencies = [ "serde", ] +[[package]] +name = "indexmap" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d" +dependencies = [ + "equivalent", + "hashbrown 0.14.0", +] + [[package]] name = "indoc" version = "1.0.9" @@ -3459,6 +3623,40 @@ dependencies = [ "cc", ] +[[package]] +name = "jni" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6df18c2e3db7e453d3c6ac5b3e9d5182664d28788126d39b91f2d1e22b017ec" +dependencies = [ + "cesu8", + "combine", + "jni-sys", + "log", + "thiserror", + "walkdir", +] + +[[package]] +name = "jni" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "039022cdf4d7b1cf548d31f60ae783138e5fd42013f6271049d7df7afadef96c" +dependencies = [ + "cesu8", + "combine", + "jni-sys", + "log", + "thiserror", + "walkdir", +] + +[[package]] +name = "jni-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" + [[package]] name = "jobserver" version = "0.1.26" @@ -3661,6 +3859,17 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" +[[package]] +name = "lewton" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "777b48df9aaab155475a83a7df3070395ea1ac6902f5cd062b8f2b028075c030" +dependencies = [ + "byteorder", + "ogg", + "tinyvec", +] + [[package]] name = "libc" version = "0.2.144" @@ -3893,6 +4102,15 @@ dependencies = [ "libc", ] +[[package]] +name = "mach2" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d0d1830bcd151a6fc4aea1369af235b36c1528fe976b8ff678683c9995eade8" +dependencies = [ + "libc", +] + [[package]] name = "malloc_buf" version = "0.0.6" @@ -3949,7 +4167,7 @@ name = "media" version = "0.1.0" dependencies = [ "anyhow", - "bindgen", + "bindgen 0.65.1", "block", "bytes 1.4.0", "core-foundation", @@ -4207,6 +4425,35 @@ dependencies = [ "tempfile", ] +[[package]] +name = "ndk" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "451422b7e4718271c8b5b3aadf5adedba43dc76312454b387e98fae0fc951aa0" +dependencies = [ + "bitflags", + "jni-sys", + "ndk-sys", + "num_enum", + "raw-window-handle", + "thiserror", +] + +[[package]] +name = "ndk-context" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" + +[[package]] +name = "ndk-sys" +version = "0.4.1+23.1.7779620" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cf2aae958bd232cac5069850591667ad422d263686d75b52a065f9badeee5a3" +dependencies = [ + "jni-sys", +] + [[package]] name = "net2" version = "0.2.38" @@ -4314,6 +4561,17 @@ dependencies = [ "zeroize", ] +[[package]] +name = "num-derive" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "num-integer" version = "0.1.45" @@ -4366,6 +4624,27 @@ dependencies = [ "libc", ] +[[package]] +name = "num_enum" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f646caf906c20226733ed5b1374287eb97e3c2a5c227ce668c1f2ce20ae57c9" +dependencies = [ + "num_enum_derive", +] + +[[package]] +name = "num_enum_derive" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcbff9bc912032c62bf65ef1d5aea88983b420f4f839db1e9b0c281a25c9c799" +dependencies = [ + "proc-macro-crate 1.3.1", + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "nvim-rs" version = "0.5.0" @@ -4408,7 +4687,7 @@ checksum = "e42c982f2d955fac81dd7e1d0e1426a7d702acd9c98d19ab01083a6a0328c424" dependencies = [ "crc32fast", "hashbrown 0.11.2", - "indexmap", + "indexmap 1.9.3", "memchr", ] @@ -4421,6 +4700,38 @@ dependencies = [ "memchr", ] +[[package]] +name = "oboe" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8868cc237ee02e2d9618539a23a8d228b9bb3fc2e7a5b11eed3831de77c395d0" +dependencies = [ + "jni 0.20.0", + "ndk", + "ndk-context", + "num-derive", + "num-traits", + "oboe-sys", +] + +[[package]] +name = "oboe-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f44155e7fb718d3cfddcf70690b2b51ac4412f347cd9e4fbe511abe9cd7b5f2" +dependencies = [ + "cc", +] + +[[package]] +name = "ogg" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6951b4e8bf21c8193da321bcce9c9dd2e13c858fe078bf9054a288b419ae5d6e" +dependencies = [ + "byteorder", +] + [[package]] name = "once_cell" version = "1.17.1" @@ -4710,7 +5021,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4dd7d28ee937e54fe3080c91faa1c3a46c06de6252988a7f4592ba2310ef22a4" dependencies = [ "fixedbitset", - "indexmap", + "indexmap 1.9.3", ] [[package]] @@ -4787,7 +5098,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9bd9647b268a3d3e14ff09c23201133a62589c658db02bb7388c7246aafe0590" dependencies = [ "base64 0.21.0", - "indexmap", + "indexmap 1.9.3", "line-wrap", "quick-xml", "serde", @@ -4920,6 +5231,16 @@ dependencies = [ "toml", ] +[[package]] +name = "proc-macro-crate" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" +dependencies = [ + "once_cell", + "toml_edit", +] + [[package]] name = "proc-macro-error" version = "1.0.4" @@ -5331,6 +5652,12 @@ dependencies = [ "rand_core 0.5.1", ] +[[package]] +name = "raw-window-handle" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2ff9a1f06a88b01621b7ae906ef0211290d1c8a168a15542486a8f61c0833b9" + [[package]] name = "rayon" version = "1.7.0" @@ -5614,6 +5941,19 @@ dependencies = [ "rmp", ] +[[package]] +name = "rodio" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdf1d4dea18dff2e9eb6dca123724f8b60ef44ad74a9ad283cdfe025df7e73fa" +dependencies = [ + "claxon", + "cpal", + "hound", + "lewton", + "symphonia", +] + [[package]] name = "rope" version = "0.1.0" @@ -6115,7 +6455,7 @@ checksum = "a332be01508d814fed64bf28f798a146d73792121129962fdf335bb3c49a4254" dependencies = [ "bitflags", "core-foundation", - "core-foundation-sys", + "core-foundation-sys 0.8.3", "libc", "security-framework-sys", ] @@ -6126,7 +6466,7 @@ version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "31c9bb296072e961fcbd8853511dd39c2d8be2deb1e17c6860b1d30732b323b4" dependencies = [ - "core-foundation-sys", + "core-foundation-sys 0.8.3", "libc", ] @@ -6200,7 +6540,7 @@ version = "1.0.96" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "057d394a50403bcac12672b2b18fb387ab6d289d957dab67dd201875391e52f1" dependencies = [ - "indexmap", + "indexmap 1.9.3", "itoa 1.0.6", "ryu", "serde", @@ -6212,7 +6552,7 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d7b9ce5b0a63c6269b9623ed828b39259545a6ec0d8a35d6135ad6af6232add" dependencies = [ - "indexmap", + "indexmap 1.9.3", "itoa 0.4.8", "ryu", "serde", @@ -6247,7 +6587,7 @@ version = "0.8.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "578a7433b776b56a35785ed5ce9a7e777ac0598aac5a6dd1b4b18a307c7fc71b" dependencies = [ - "indexmap", + "indexmap 1.9.3", "ryu", "serde", "yaml-rust", @@ -6621,7 +6961,7 @@ dependencies = [ "hex", "hkdf", "hmac 0.12.1", - "indexmap", + "indexmap 1.9.3", "itoa 1.0.6", "libc", "libsqlite3-sys", @@ -6772,6 +7112,56 @@ dependencies = [ "siphasher", ] +[[package]] +name = "symphonia" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62e48dba70095f265fdb269b99619b95d04c89e619538138383e63310b14d941" +dependencies = [ + "lazy_static", + "symphonia-bundle-mp3", + "symphonia-core", + "symphonia-metadata", +] + +[[package]] +name = "symphonia-bundle-mp3" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f31d7fece546f1e6973011a9eceae948133bbd18fd3d52f6073b1e38ae6368a" +dependencies = [ + "bitflags", + "lazy_static", + "log", + "symphonia-core", + "symphonia-metadata", +] + +[[package]] +name = "symphonia-core" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c73eb88fee79705268cc7b742c7bc93a7b76e092ab751d0833866970754142" +dependencies = [ + "arrayvec 0.7.2", + "bitflags", + "bytemuck", + "lazy_static", + "log", +] + +[[package]] +name = "symphonia-metadata" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89c3e1937e31d0e068bbe829f66b2f2bfaa28d056365279e0ef897172c3320c0" +dependencies = [ + "encoding_rs", + "lazy_static", + "log", + "symphonia-core", +] + [[package]] name = "syn" version = "1.0.109" @@ -6817,7 +7207,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a902e9050fca0a5d6877550b769abd2bd1ce8c04634b941dbe2809735e1a1e33" dependencies = [ "cfg-if 1.0.0", - "core-foundation-sys", + "core-foundation-sys 0.8.3", "libc", "ntapi 0.4.1", "once_cell", @@ -6986,7 +7376,7 @@ dependencies = [ "anyhow", "fs", "gpui", - "indexmap", + "indexmap 1.9.3", "parking_lot 0.11.2", "schemars", "serde", @@ -7292,6 +7682,23 @@ dependencies = [ "serde", ] +[[package]] +name = "toml_datetime" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" + +[[package]] +name = "toml_edit" +version = "0.19.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "266f016b7f039eec8a1a80dfe6156b633d208b9fccca5e4db1d6775b0c4e34a7" +dependencies = [ + "indexmap 2.0.0", + "toml_datetime", + "winnow", +] + [[package]] name = "tonic" version = "0.6.2" @@ -7331,7 +7738,7 @@ checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" dependencies = [ "futures-core", "futures-util", - "indexmap", + "indexmap 1.9.3", "pin-project", "pin-project-lite 0.2.9", "rand 0.8.5", @@ -8188,7 +8595,7 @@ version = "0.85.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "570460c58b21e9150d2df0eaaedbb7816c34bcec009ae0dcc976e40ba81463e7" dependencies = [ - "indexmap", + "indexmap 1.9.3", ] [[package]] @@ -8202,7 +8609,7 @@ dependencies = [ "backtrace", "bincode", "cfg-if 1.0.0", - "indexmap", + "indexmap 1.9.3", "lazy_static", "libc", "log", @@ -8276,7 +8683,7 @@ dependencies = [ "anyhow", "cranelift-entity", "gimli 0.26.2", - "indexmap", + "indexmap 1.9.3", "log", "more-asserts", "object 0.28.4", @@ -8346,7 +8753,7 @@ dependencies = [ "backtrace", "cc", "cfg-if 1.0.0", - "indexmap", + "indexmap 1.9.3", "libc", "log", "mach", @@ -8602,6 +9009,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdacb41e6a96a052c6cb63a144f24900236121c6f63f4f8219fef5977ecb0c25" +dependencies = [ + "windows-targets 0.42.2", +] + [[package]] name = "windows" version = "0.48.0" @@ -8758,6 +9174,15 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" +[[package]] +name = "winnow" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca0ace3845f0d96209f0375e6d367e3eb87eb65d27d445bdc9f1843a26f39448" +dependencies = [ + "memchr", +] + [[package]] name = "winreg" version = "0.10.1" @@ -8909,7 +9334,7 @@ dependencies = [ [[package]] name = "zed" -version = "0.93.0" +version = "0.93.4" dependencies = [ "activity_indicator", "ai", @@ -8918,6 +9343,7 @@ dependencies = [ "async-recursion 0.3.2", "async-tar", "async-trait", + "audio", "auto_update", "backtrace", "breadcrumbs", @@ -8947,7 +9373,7 @@ dependencies = [ "gpui", "ignore", "image", - "indexmap", + "indexmap 1.9.3", "install_cli", "isahc", "journal", diff --git a/Cargo.toml b/Cargo.toml index 3f3953096e..4cc7e488cf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,6 +2,7 @@ members = [ "crates/activity_indicator", "crates/ai", + "crates/audio", "crates/auto_update", "crates/breadcrumbs", "crates/call", diff --git a/assets/keymaps/atom.json b/assets/keymaps/atom.json index 60acf5ea6f..af845ae4f2 100644 --- a/assets/keymaps/atom.json +++ b/assets/keymaps/atom.json @@ -24,9 +24,7 @@ ], "ctrl-shift-down": "editor::AddSelectionBelow", "ctrl-shift-up": "editor::AddSelectionAbove", - "cmd-shift-backspace": "editor::DeleteToBeginningOfLine", - "cmd-shift-enter": "editor::NewlineAbove", - "cmd-enter": "editor::NewlineBelow" + "cmd-shift-backspace": "editor::DeleteToBeginningOfLine" } }, { diff --git a/assets/keymaps/sublime_text.json b/assets/keymaps/sublime_text.json index 2d32b77d58..ca20802295 100644 --- a/assets/keymaps/sublime_text.json +++ b/assets/keymaps/sublime_text.json @@ -24,9 +24,7 @@ "ctrl-.": "editor::GoToHunk", "ctrl-,": "editor::GoToPrevHunk", "ctrl-backspace": "editor::DeleteToPreviousWordStart", - "ctrl-delete": "editor::DeleteToNextWordEnd", - "cmd-shift-enter": "editor::NewlineAbove", - "cmd-enter": "editor::NewlineBelow" + "ctrl-delete": "editor::DeleteToNextWordEnd" } }, { diff --git a/assets/keymaps/textmate.json b/assets/keymaps/textmate.json index 06be727429..591d6e443f 100644 --- a/assets/keymaps/textmate.json +++ b/assets/keymaps/textmate.json @@ -12,8 +12,6 @@ "ctrl-shift-d": "editor::DuplicateLine", "cmd-b": "editor::GoToDefinition", "cmd-j": "editor::ScrollCursorCenter", - "cmd-alt-enter": "editor::NewlineAbove", - "cmd-enter": "editor::NewlineBelow", "cmd-shift-l": "editor::SelectLine", "cmd-shift-t": "outline::Toggle", "alt-backspace": "editor::DeleteToPreviousWordStart", @@ -56,7 +54,9 @@ }, { "context": "Editor && mode == full", - "bindings": {} + "bindings": { + "cmd-alt-enter": "editor::NewlineAbove" + } }, { "context": "BufferSearchBar", diff --git a/assets/sounds/joined_call.wav b/assets/sounds/joined_call.wav new file mode 100644 index 0000000000..cf6e5ba4df Binary files /dev/null and b/assets/sounds/joined_call.wav differ diff --git a/assets/sounds/leave_call.wav b/assets/sounds/leave_call.wav new file mode 100644 index 0000000000..478b28204f Binary files /dev/null and b/assets/sounds/leave_call.wav differ diff --git a/assets/sounds/mute.wav b/assets/sounds/mute.wav new file mode 100644 index 0000000000..69e8456f6c Binary files /dev/null and b/assets/sounds/mute.wav differ diff --git a/assets/sounds/start_screenshare.wav b/assets/sounds/start_screenshare.wav new file mode 100644 index 0000000000..7b72a90af1 Binary files /dev/null and b/assets/sounds/start_screenshare.wav differ diff --git a/assets/sounds/stop_screenshare.wav b/assets/sounds/stop_screenshare.wav new file mode 100644 index 0000000000..1fe13e21b4 Binary files /dev/null and b/assets/sounds/stop_screenshare.wav differ diff --git a/assets/sounds/unmute.wav b/assets/sounds/unmute.wav new file mode 100644 index 0000000000..f8c90f6916 Binary files /dev/null and b/assets/sounds/unmute.wav differ diff --git a/crates/ai/src/assistant.rs b/crates/ai/src/assistant.rs index 9ca54e661a..ae3a34b690 100644 --- a/crates/ai/src/assistant.rs +++ b/crates/ai/src/assistant.rs @@ -147,8 +147,9 @@ impl AssistantPanel { .await .log_err() .unwrap_or_default(); - this.update(&mut cx, |this, _| { - this.saved_conversations = saved_conversations + this.update(&mut cx, |this, cx| { + this.saved_conversations = saved_conversations; + cx.notify(); }) .ok(); } diff --git a/crates/audio/Cargo.toml b/crates/audio/Cargo.toml new file mode 100644 index 0000000000..182e421eb8 --- /dev/null +++ b/crates/audio/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "audio" +version = "0.1.0" +edition = "2021" +publish = false + +[lib] +path = "src/audio.rs" +doctest = false + +[dependencies] +gpui = { path = "../gpui" } +collections = { path = "../collections" } +util = { path = "../util" } + +rodio = "0.17.1" + +log.workspace = true + +anyhow.workspace = true +parking_lot.workspace = true + +[dev-dependencies] diff --git a/crates/audio/src/assets.rs b/crates/audio/src/assets.rs new file mode 100644 index 0000000000..b58e1f6aee --- /dev/null +++ b/crates/audio/src/assets.rs @@ -0,0 +1,44 @@ +use std::{io::Cursor, sync::Arc}; + +use anyhow::Result; +use collections::HashMap; +use gpui::{AppContext, AssetSource}; +use rodio::{ + source::{Buffered, SamplesConverter}, + Decoder, Source, +}; + +type Sound = Buffered>>, f32>>; + +pub struct SoundRegistry { + cache: Arc>>, + assets: Box, +} + +impl SoundRegistry { + pub fn new(source: impl AssetSource) -> Arc { + Arc::new(Self { + cache: Default::default(), + assets: Box::new(source), + }) + } + + pub fn global(cx: &AppContext) -> Arc { + cx.global::>().clone() + } + + pub fn get(&self, name: &str) -> Result> { + if let Some(wav) = self.cache.lock().get(name) { + return Ok(wav.clone()); + } + + let path = format!("sounds/{}.wav", name); + let bytes = self.assets.load(&path)?.into_owned(); + let cursor = Cursor::new(bytes); + let source = Decoder::new(cursor)?.convert_samples::().buffered(); + + self.cache.lock().insert(name.to_string(), source.clone()); + + Ok(source) + } +} diff --git a/crates/audio/src/audio.rs b/crates/audio/src/audio.rs new file mode 100644 index 0000000000..233b0f62aa --- /dev/null +++ b/crates/audio/src/audio.rs @@ -0,0 +1,67 @@ +use assets::SoundRegistry; +use gpui::{AppContext, AssetSource}; +use rodio::{OutputStream, OutputStreamHandle}; +use util::ResultExt; + +mod assets; + +pub fn init(source: impl AssetSource, cx: &mut AppContext) { + cx.set_global(SoundRegistry::new(source)); + cx.set_global(Audio::new()); +} + +pub enum Sound { + Joined, + Leave, + Mute, + Unmute, + StartScreenshare, + StopScreenshare, +} + +impl Sound { + fn file(&self) -> &'static str { + match self { + Self::Joined => "joined_call", + Self::Leave => "leave_call", + Self::Mute => "mute", + Self::Unmute => "unmute", + Self::StartScreenshare => "start_screenshare", + Self::StopScreenshare => "stop_screenshare", + } + } +} + +pub struct Audio { + _output_stream: Option, + output_handle: Option, +} + +impl Audio { + pub fn new() -> Self { + let (_output_stream, output_handle) = OutputStream::try_default().log_err().unzip(); + + Self { + _output_stream, + output_handle, + } + } + + pub fn play_sound(sound: Sound, cx: &AppContext) { + if !cx.has_global::() { + return; + } + + let this = cx.global::(); + + let Some(output_handle) = this.output_handle.as_ref() else { + return; + }; + + let Some(source) = SoundRegistry::global(cx).get(sound.file()).log_err() else { + return; + }; + + output_handle.play_raw(source).log_err(); + } +} diff --git a/crates/call/Cargo.toml b/crates/call/Cargo.toml index 4e83b552fb..61f3593247 100644 --- a/crates/call/Cargo.toml +++ b/crates/call/Cargo.toml @@ -19,6 +19,7 @@ test-support = [ ] [dependencies] +audio = { path = "../audio" } client = { path = "../client" } collections = { path = "../collections" } gpui = { path = "../gpui" } diff --git a/crates/call/src/room.rs b/crates/call/src/room.rs index da298f9ca2..87e6faf988 100644 --- a/crates/call/src/room.rs +++ b/crates/call/src/room.rs @@ -3,6 +3,7 @@ use crate::{ IncomingCall, }; use anyhow::{anyhow, Result}; +use audio::{Audio, Sound}; use client::{ proto::{self, PeerId}, Client, TypedEnvelope, User, UserStore, @@ -151,6 +152,7 @@ impl Room { let connect = room.connect(&connection_info.server_url, &connection_info.token); cx.spawn(|this, mut cx| async move { connect.await?; + this.update(&mut cx, |this, cx| this.share_microphone(cx)) .await?; @@ -176,6 +178,8 @@ impl Room { let maintain_connection = cx.spawn_weak(|this, cx| Self::maintain_connection(this, client.clone(), cx).log_err()); + Audio::play_sound(Sound::Joined, cx); + Self { id, live_kit: live_kit_room, @@ -265,6 +269,7 @@ impl Room { room.apply_room_update(room_proto, cx)?; anyhow::Ok(()) })?; + Ok(room) }) } @@ -306,6 +311,8 @@ impl Room { } } + Audio::play_sound(Sound::Leave, cx); + self.status = RoomStatus::Offline; self.remote_participants.clear(); self.pending_participants.clear(); @@ -656,6 +663,8 @@ impl Room { }, ); + Audio::play_sound(Sound::Joined, cx); + if let Some(live_kit) = this.live_kit.as_ref() { let video_tracks = live_kit.room.remote_video_tracks(&user.id.to_string()); @@ -922,6 +931,7 @@ impl Room { cx.spawn(|this, mut cx| async move { let project = Project::remote(id, client, user_store, language_registry, fs, cx.clone()).await?; + this.update(&mut cx, |this, cx| { this.joined_projects.retain(|project| { if let Some(project) = project.upgrade(cx) { @@ -1212,6 +1222,9 @@ impl Room { }; cx.notify(); } + + Audio::play_sound(Sound::StartScreenshare, cx); + Ok(()) } Err(error) => { @@ -1227,38 +1240,20 @@ impl Room { }) }) } - fn set_mute( - live_kit: &mut LiveKitRoom, - should_mute: bool, - cx: &mut ModelContext, - ) -> Result>> { - if !should_mute { - // clear user muting state. - live_kit.muted_by_user = false; - } - match &mut live_kit.microphone_track { - LocalTrack::None => Err(anyhow!("microphone was not shared")), - LocalTrack::Pending { muted, .. } => { - *muted = should_mute; - cx.notify(); - Ok(Task::Ready(Some(Ok(())))) - } - LocalTrack::Published { - track_publication, - muted, - } => { - *muted = should_mute; - cx.notify(); - Ok(cx.background().spawn(track_publication.set_mute(*muted))) - } - } - } + pub fn toggle_mute(&mut self, cx: &mut ModelContext) -> Result>> { let should_mute = !self.is_muted(); if let Some(live_kit) = self.live_kit.as_mut() { - let ret = Self::set_mute(live_kit, should_mute, cx); + let (ret_task, old_muted) = live_kit.set_mute(should_mute, cx)?; live_kit.muted_by_user = should_mute; - ret + + if old_muted == true && live_kit.deafened == true { + if let Some(task) = self.toggle_deafen(cx).ok() { + task.detach(); + } + } + + Ok(ret_task) } else { Err(anyhow!("LiveKit not started")) } @@ -1274,7 +1269,7 @@ impl Room { // When deafening, mute user's mic as well. // When undeafening, unmute user's mic unless it was manually muted prior to deafening. if live_kit.deafened || !live_kit.muted_by_user { - mute_task = Some(Self::set_mute(live_kit, live_kit.deafened, cx)?); + mute_task = Some(live_kit.set_mute(live_kit.deafened, cx)?.0); }; for participant in self.remote_participants.values() { for track in live_kit @@ -1319,6 +1314,8 @@ impl Room { } => { live_kit.room.unpublish_track(track_publication); cx.notify(); + + Audio::play_sound(Sound::StopScreenshare, cx); Ok(()) } } @@ -1347,6 +1344,51 @@ struct LiveKitRoom { _maintain_tracks: [Task<()>; 2], } +impl LiveKitRoom { + fn set_mute( + self: &mut LiveKitRoom, + should_mute: bool, + cx: &mut ModelContext, + ) -> Result<(Task>, bool)> { + if !should_mute { + // clear user muting state. + self.muted_by_user = false; + } + + let (result, old_muted) = match &mut self.microphone_track { + LocalTrack::None => Err(anyhow!("microphone was not shared")), + LocalTrack::Pending { muted, .. } => { + let old_muted = *muted; + *muted = should_mute; + cx.notify(); + Ok((Task::Ready(Some(Ok(()))), old_muted)) + } + LocalTrack::Published { + track_publication, + muted, + } => { + let old_muted = *muted; + *muted = should_mute; + cx.notify(); + Ok(( + cx.background().spawn(track_publication.set_mute(*muted)), + old_muted, + )) + } + }?; + + if old_muted != should_mute { + if should_mute { + Audio::play_sound(Sound::Mute, cx); + } else { + Audio::play_sound(Sound::Unmute, cx); + } + } + + Ok((result, old_muted)) + } +} + enum LocalTrack { None, Pending { diff --git a/crates/collab/Cargo.toml b/crates/collab/Cargo.toml index a87234ded7..537082173c 100644 --- a/crates/collab/Cargo.toml +++ b/crates/collab/Cargo.toml @@ -14,6 +14,7 @@ name = "seed" required-features = ["seed-support"] [dependencies] +audio = { path = "../audio" } collections = { path = "../collections" } live_kit_server = { path = "../live_kit_server" } rpc = { path = "../rpc" } diff --git a/crates/collab/src/tests.rs b/crates/collab/src/tests.rs index b51c5240a8..b1d0bedb2c 100644 --- a/crates/collab/src/tests.rs +++ b/crates/collab/src/tests.rs @@ -203,6 +203,7 @@ impl TestServer { language::init(cx); editor::init_settings(cx); workspace::init(app_state.clone(), cx); + audio::init((), cx); call::init(client.clone(), user_store.clone(), cx); }); diff --git a/crates/fs/src/fs.rs b/crates/fs/src/fs.rs index e487b64c4e..592e6c9a53 100644 --- a/crates/fs/src/fs.rs +++ b/crates/fs/src/fs.rs @@ -388,6 +388,7 @@ struct FakeFsState { event_txs: Vec>>, events_paused: bool, buffered_events: Vec, + metadata_call_count: usize, read_dir_call_count: usize, } @@ -538,6 +539,7 @@ impl FakeFs { buffered_events: Vec::new(), events_paused: false, read_dir_call_count: 0, + metadata_call_count: 0, }), }) } @@ -774,10 +776,16 @@ impl FakeFs { result } + /// How many `read_dir` calls have been issued. pub fn read_dir_call_count(&self) -> usize { self.state.lock().read_dir_call_count } + /// How many `metadata` calls have been issued. + pub fn metadata_call_count(&self) -> usize { + self.state.lock().metadata_call_count + } + async fn simulate_random_delay(&self) { self.executor .upgrade() @@ -1098,7 +1106,8 @@ impl Fs for FakeFs { async fn metadata(&self, path: &Path) -> Result> { self.simulate_random_delay().await; let path = normalize_path(path); - let state = self.state.lock(); + let mut state = self.state.lock(); + state.metadata_call_count += 1; if let Some((mut entry, _)) = state.try_read_path(&path, false) { let is_symlink = entry.lock().is_symlink(); if is_symlink { diff --git a/crates/project/src/project_tests.rs b/crates/project/src/project_tests.rs index 478fad74a9..16e706a77e 100644 --- a/crates/project/src/project_tests.rs +++ b/crates/project/src/project_tests.rs @@ -596,6 +596,8 @@ async fn test_reporting_fs_changes_to_language_servers(cx: &mut gpui::TestAppCon ); }); + let prev_read_dir_count = fs.read_dir_call_count(); + // Keep track of the FS events reported to the language server. let fake_server = fake_servers.next().await.unwrap(); let file_changes = Arc::new(Mutex::new(Vec::new())); @@ -607,6 +609,12 @@ async fn test_reporting_fs_changes_to_language_servers(cx: &mut gpui::TestAppCon register_options: serde_json::to_value( lsp::DidChangeWatchedFilesRegistrationOptions { watchers: vec![ + lsp::FileSystemWatcher { + glob_pattern: lsp::GlobPattern::String( + "/the-root/Cargo.toml".to_string(), + ), + kind: None, + }, lsp::FileSystemWatcher { glob_pattern: lsp::GlobPattern::String( "/the-root/src/*.{rs,c}".to_string(), @@ -638,6 +646,7 @@ async fn test_reporting_fs_changes_to_language_servers(cx: &mut gpui::TestAppCon cx.foreground().run_until_parked(); assert_eq!(mem::take(&mut *file_changes.lock()), &[]); + assert_eq!(fs.read_dir_call_count() - prev_read_dir_count, 4); // Now the language server has asked us to watch an ignored directory path, // so we recursively load it. diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index be3bcd05fa..2f793c84c9 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -2140,6 +2140,7 @@ impl LocalSnapshot { impl BackgroundScannerState { fn should_scan_directory(&self, entry: &Entry) -> bool { (!entry.is_external && !entry.is_ignored) + || entry.path.file_name() == Some(&*DOT_GIT) || self.scanned_dirs.contains(&entry.id) // If we've ever scanned it, keep scanning || self .paths_to_scan @@ -2319,6 +2320,7 @@ impl BackgroundScannerState { .entry_for_id(entry_id) .map(|entry| RepositoryWorkDirectory(entry.path.clone())) else { continue }; + log::info!("reload git repository {:?}", dot_git_dir); let repository = repository.repo_ptr.lock(); let branch = repository.branch_name(); repository.reload_index(); @@ -2359,6 +2361,8 @@ impl BackgroundScannerState { } fn build_repository(&mut self, dot_git_path: Arc, fs: &dyn Fs) -> Option<()> { + log::info!("build git repository {:?}", dot_git_path); + let work_dir_path: Arc = dot_git_path.parent().unwrap().into(); // Guard against repositories inside the repository metadata @@ -3071,17 +3075,20 @@ impl BackgroundScanner { path_prefix = self.path_prefixes_to_scan_rx.recv().fuse() => { let Ok(path_prefix) = path_prefix else { break }; + log::trace!("adding path prefix {:?}", path_prefix); - self.forcibly_load_paths(&[path_prefix.clone()]).await; + let did_scan = self.forcibly_load_paths(&[path_prefix.clone()]).await; + if did_scan { + let abs_path = + { + let mut state = self.state.lock(); + state.path_prefixes_to_scan.insert(path_prefix.clone()); + state.snapshot.abs_path.join(&path_prefix) + }; - let abs_path = - { - let mut state = self.state.lock(); - state.path_prefixes_to_scan.insert(path_prefix.clone()); - state.snapshot.abs_path.join(path_prefix) - }; - if let Some(abs_path) = self.fs.canonicalize(&abs_path).await.log_err() { - self.process_events(vec![abs_path]).await; + if let Some(abs_path) = self.fs.canonicalize(&abs_path).await.log_err() { + self.process_events(vec![abs_path]).await; + } } } @@ -3097,10 +3104,13 @@ impl BackgroundScanner { } } - async fn process_scan_request(&self, request: ScanRequest, scanning: bool) -> bool { + async fn process_scan_request(&self, mut request: ScanRequest, scanning: bool) -> bool { log::debug!("rescanning paths {:?}", request.relative_paths); - let root_path = self.forcibly_load_paths(&request.relative_paths).await; + request.relative_paths.sort_unstable(); + self.forcibly_load_paths(&request.relative_paths).await; + + let root_path = self.state.lock().snapshot.abs_path.clone(); let root_canonical_path = match self.fs.canonicalize(&root_path).await { Ok(path) => path, Err(err) => { @@ -3108,10 +3118,9 @@ impl BackgroundScanner { return false; } }; - let abs_paths = request .relative_paths - .into_iter() + .iter() .map(|path| { if path.file_name().is_some() { root_canonical_path.join(path) @@ -3120,14 +3129,19 @@ impl BackgroundScanner { } }) .collect::>(); - self.reload_entries_for_paths(root_path, root_canonical_path, abs_paths, None) - .await; + + self.reload_entries_for_paths( + root_path, + root_canonical_path, + &request.relative_paths, + abs_paths, + None, + ) + .await; self.send_status_update(scanning, Some(request.done)) } - async fn process_events(&mut self, abs_paths: Vec) { - log::debug!("received fs events {:?}", abs_paths); - + async fn process_events(&mut self, mut abs_paths: Vec) { let root_path = self.state.lock().snapshot.abs_path.clone(); let root_canonical_path = match self.fs.canonicalize(&root_path).await { Ok(path) => path, @@ -3137,15 +3151,52 @@ impl BackgroundScanner { } }; + let mut relative_paths = Vec::with_capacity(abs_paths.len()); + abs_paths.sort_unstable(); + abs_paths.dedup_by(|a, b| a.starts_with(&b)); + abs_paths.retain(|abs_path| { + let snapshot = &self.state.lock().snapshot; + { + let relative_path: Arc = + if let Ok(path) = abs_path.strip_prefix(&root_canonical_path) { + path.into() + } else { + log::error!( + "ignoring event {abs_path:?} outside of root path {root_canonical_path:?}", + ); + return false; + }; + + let parent_dir_is_loaded = relative_path.parent().map_or(true, |parent| { + snapshot + .entry_for_path(parent) + .map_or(false, |entry| entry.kind == EntryKind::Dir) + }); + if !parent_dir_is_loaded { + log::debug!("ignoring event {relative_path:?} within unloaded directory"); + return false; + } + + relative_paths.push(relative_path); + true + } + }); + + if relative_paths.is_empty() { + return; + } + + log::debug!("received fs events {:?}", relative_paths); + let (scan_job_tx, scan_job_rx) = channel::unbounded(); - let paths = self - .reload_entries_for_paths( - root_path, - root_canonical_path, - abs_paths, - Some(scan_job_tx.clone()), - ) - .await; + self.reload_entries_for_paths( + root_path, + root_canonical_path, + &relative_paths, + abs_paths, + Some(scan_job_tx.clone()), + ) + .await; drop(scan_job_tx); self.scan_dirs(false, scan_job_rx).await; @@ -3155,7 +3206,7 @@ impl BackgroundScanner { { let mut state = self.state.lock(); - state.reload_repositories(&paths, self.fs.as_ref()); + state.reload_repositories(&relative_paths, self.fs.as_ref()); state.snapshot.completed_scan_id = state.snapshot.scan_id; for (_, entry_id) in mem::take(&mut state.removed_entry_ids) { state.scanned_dirs.remove(&entry_id); @@ -3165,12 +3216,11 @@ impl BackgroundScanner { self.send_status_update(false, None); } - async fn forcibly_load_paths(&self, paths: &[Arc]) -> Arc { - let root_path; + async fn forcibly_load_paths(&self, paths: &[Arc]) -> bool { let (scan_job_tx, mut scan_job_rx) = channel::unbounded(); { let mut state = self.state.lock(); - root_path = state.snapshot.abs_path.clone(); + let root_path = state.snapshot.abs_path.clone(); for path in paths { for ancestor in path.ancestors() { if let Some(entry) = state.snapshot.entry_for_path(ancestor) { @@ -3201,8 +3251,8 @@ impl BackgroundScanner { while let Some(job) = scan_job_rx.next().await { self.scan_dir(&job).await.log_err(); } - self.state.lock().paths_to_scan.clear(); - root_path + + mem::take(&mut self.state.lock().paths_to_scan).len() > 0 } async fn scan_dirs( @@ -3475,7 +3525,7 @@ impl BackgroundScanner { .expect("channel is unbounded"); } } else { - log::debug!("defer scanning directory {:?} {:?}", entry.path, entry.kind); + log::debug!("defer scanning directory {:?}", entry.path); entry.kind = EntryKind::UnloadedDir; } } @@ -3490,26 +3540,10 @@ impl BackgroundScanner { &self, root_abs_path: Arc, root_canonical_path: PathBuf, - mut abs_paths: Vec, + relative_paths: &[Arc], + abs_paths: Vec, scan_queue_tx: Option>, - ) -> Vec> { - let mut event_paths = Vec::>::with_capacity(abs_paths.len()); - abs_paths.sort_unstable(); - abs_paths.dedup_by(|a, b| a.starts_with(&b)); - abs_paths.retain(|abs_path| { - if let Ok(path) = abs_path.strip_prefix(&root_canonical_path) { - event_paths.push(path.into()); - true - } else { - log::error!( - "unexpected event {:?} for root path {:?}", - abs_path, - root_canonical_path - ); - false - } - }); - + ) { let metadata = futures::future::join_all( abs_paths .iter() @@ -3538,30 +3572,15 @@ impl BackgroundScanner { // Remove any entries for paths that no longer exist or are being recursively // refreshed. Do this before adding any new entries, so that renames can be // detected regardless of the order of the paths. - for (path, metadata) in event_paths.iter().zip(metadata.iter()) { + for (path, metadata) in relative_paths.iter().zip(metadata.iter()) { if matches!(metadata, Ok(None)) || doing_recursive_update { log::trace!("remove path {:?}", path); state.remove_path(path); } } - for (path, metadata) in event_paths.iter().zip(metadata.iter()) { - if let (Some(parent), true) = (path.parent(), doing_recursive_update) { - if state - .snapshot - .entry_for_path(parent) - .map_or(true, |entry| entry.kind != EntryKind::Dir) - { - log::debug!( - "ignoring event {path:?} within unloaded directory {:?}", - parent - ); - continue; - } - } - + for (path, metadata) in relative_paths.iter().zip(metadata.iter()) { let abs_path: Arc = root_abs_path.join(&path).into(); - match metadata { Ok(Some((metadata, canonical_path))) => { let ignore_stack = state @@ -3594,23 +3613,28 @@ impl BackgroundScanner { } } - let fs_entry = state.insert_entry(fs_entry, self.fs.as_ref()); - - if let Some(scan_queue_tx) = &scan_queue_tx { - let mut ancestor_inodes = state.snapshot.ancestor_inodes_for_path(&path); - if metadata.is_dir && !ancestor_inodes.contains(&metadata.inode) { - ancestor_inodes.insert(metadata.inode); - smol::block_on(scan_queue_tx.send(ScanJob { - abs_path, - path: path.clone(), - ignore_stack, - ancestor_inodes, - is_external: fs_entry.is_external, - scan_queue: scan_queue_tx.clone(), - })) - .unwrap(); + if let (Some(scan_queue_tx), true) = (&scan_queue_tx, fs_entry.is_dir()) { + if state.should_scan_directory(&fs_entry) { + let mut ancestor_inodes = + state.snapshot.ancestor_inodes_for_path(&path); + if !ancestor_inodes.contains(&metadata.inode) { + ancestor_inodes.insert(metadata.inode); + smol::block_on(scan_queue_tx.send(ScanJob { + abs_path, + path: path.clone(), + ignore_stack, + ancestor_inodes, + is_external: fs_entry.is_external, + scan_queue: scan_queue_tx.clone(), + })) + .unwrap(); + } + } else { + fs_entry.kind = EntryKind::UnloadedDir; } } + + state.insert_entry(fs_entry, self.fs.as_ref()); } Ok(None) => { self.remove_repo_path(&path, &mut state.snapshot); @@ -3624,12 +3648,10 @@ impl BackgroundScanner { util::extend_sorted( &mut state.changed_paths, - event_paths.iter().cloned(), + relative_paths.iter().cloned(), usize::MAX, Ord::cmp, ); - - event_paths } fn remove_repo_path(&self, path: &Path, snapshot: &mut LocalSnapshot) -> Option<()> { @@ -3760,25 +3782,22 @@ impl BackgroundScanner { // Scan any directories that were previously ignored and weren't // previously scanned. - if was_ignored - && !entry.is_ignored - && !entry.is_external - && entry.kind == EntryKind::UnloadedDir - { - job.scan_queue - .try_send(ScanJob { - abs_path: abs_path.clone(), - path: entry.path.clone(), - ignore_stack: child_ignore_stack.clone(), - scan_queue: job.scan_queue.clone(), - ancestor_inodes: self - .state - .lock() - .snapshot - .ancestor_inodes_for_path(&entry.path), - is_external: false, - }) - .unwrap(); + if was_ignored && !entry.is_ignored && entry.kind.is_unloaded() { + let state = self.state.lock(); + if state.should_scan_directory(&entry) { + job.scan_queue + .try_send(ScanJob { + abs_path: abs_path.clone(), + path: entry.path.clone(), + ignore_stack: child_ignore_stack.clone(), + scan_queue: job.scan_queue.clone(), + ancestor_inodes: state + .snapshot + .ancestor_inodes_for_path(&entry.path), + is_external: false, + }) + .unwrap(); + } } job.ignore_queue diff --git a/crates/project/src/worktree_tests.rs b/crates/project/src/worktree_tests.rs index 553c5e2cca..c9ed06dea7 100644 --- a/crates/project/src/worktree_tests.rs +++ b/crates/project/src/worktree_tests.rs @@ -454,6 +454,10 @@ async fn test_open_gitignored_files(cx: &mut TestAppContext) { "b1.js": "b1", "b2.js": "b2", }, + "c": { + "c1.js": "c1", + "c2.js": "c2", + } }, }, "two": { @@ -521,6 +525,7 @@ async fn test_open_gitignored_files(cx: &mut TestAppContext) { (Path::new("one/node_modules/b"), true), (Path::new("one/node_modules/b/b1.js"), true), (Path::new("one/node_modules/b/b2.js"), true), + (Path::new("one/node_modules/c"), true), (Path::new("two"), false), (Path::new("two/x.js"), false), (Path::new("two/y.js"), false), @@ -564,6 +569,7 @@ async fn test_open_gitignored_files(cx: &mut TestAppContext) { (Path::new("one/node_modules/b"), true), (Path::new("one/node_modules/b/b1.js"), true), (Path::new("one/node_modules/b/b2.js"), true), + (Path::new("one/node_modules/c"), true), (Path::new("two"), false), (Path::new("two/x.js"), false), (Path::new("two/y.js"), false), @@ -578,6 +584,17 @@ async fn test_open_gitignored_files(cx: &mut TestAppContext) { // Only the newly-expanded directory is scanned. assert_eq!(fs.read_dir_call_count() - prev_read_dir_count, 1); }); + + // No work happens when files and directories change within an unloaded directory. + let prev_fs_call_count = fs.read_dir_call_count() + fs.metadata_call_count(); + fs.create_dir("/root/one/node_modules/c/lib".as_ref()) + .await + .unwrap(); + cx.foreground().run_until_parked(); + assert_eq!( + fs.read_dir_call_count() + fs.metadata_call_count() - prev_fs_call_count, + 0 + ); } #[gpui::test] @@ -1637,6 +1654,23 @@ async fn test_git_status(deterministic: Arc, cx: &mut TestAppCont })); + const A_TXT: &'static str = "a.txt"; + const B_TXT: &'static str = "b.txt"; + const E_TXT: &'static str = "c/d/e.txt"; + const F_TXT: &'static str = "f.txt"; + const DOTGITIGNORE: &'static str = ".gitignore"; + const BUILD_FILE: &'static str = "target/build_file"; + let project_path = Path::new("project"); + + // Set up git repository before creating the worktree. + let work_dir = root.path().join("project"); + let mut repo = git_init(work_dir.as_path()); + repo.add_ignore_rule(IGNORE_RULE).unwrap(); + git_add(A_TXT, &repo); + git_add(E_TXT, &repo); + git_add(DOTGITIGNORE, &repo); + git_commit("Initial commit", &repo); + let tree = Worktree::local( build_client(cx), root.path(), @@ -1648,26 +1682,9 @@ async fn test_git_status(deterministic: Arc, cx: &mut TestAppCont .await .unwrap(); + tree.flush_fs_events(cx).await; cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete()) .await; - - const A_TXT: &'static str = "a.txt"; - const B_TXT: &'static str = "b.txt"; - const E_TXT: &'static str = "c/d/e.txt"; - const F_TXT: &'static str = "f.txt"; - const DOTGITIGNORE: &'static str = ".gitignore"; - const BUILD_FILE: &'static str = "target/build_file"; - let project_path: &Path = &Path::new("project"); - - let work_dir = root.path().join("project"); - let mut repo = git_init(work_dir.as_path()); - repo.add_ignore_rule(IGNORE_RULE).unwrap(); - git_add(Path::new(A_TXT), &repo); - git_add(Path::new(E_TXT), &repo); - git_add(Path::new(DOTGITIGNORE), &repo); - git_commit("Initial commit", &repo); - - tree.flush_fs_events(cx).await; deterministic.run_until_parked(); // Check that the right git state is observed on startup @@ -1687,39 +1704,39 @@ async fn test_git_status(deterministic: Arc, cx: &mut TestAppCont ); }); + // Modify a file in the working copy. std::fs::write(work_dir.join(A_TXT), "aa").unwrap(); - tree.flush_fs_events(cx).await; deterministic.run_until_parked(); + // The worktree detects that the file's git status has changed. tree.read_with(cx, |tree, _cx| { let snapshot = tree.snapshot(); - assert_eq!( snapshot.status_for_file(project_path.join(A_TXT)), Some(GitFileStatus::Modified) ); }); - git_add(Path::new(A_TXT), &repo); - git_add(Path::new(B_TXT), &repo); + // Create a commit in the git repository. + git_add(A_TXT, &repo); + git_add(B_TXT, &repo); git_commit("Committing modified and added", &repo); tree.flush_fs_events(cx).await; deterministic.run_until_parked(); - // Check that repo only changes are tracked + // The worktree detects that the files' git status have changed. tree.read_with(cx, |tree, _cx| { let snapshot = tree.snapshot(); - assert_eq!( snapshot.status_for_file(project_path.join(F_TXT)), Some(GitFileStatus::Added) ); - assert_eq!(snapshot.status_for_file(project_path.join(B_TXT)), None); assert_eq!(snapshot.status_for_file(project_path.join(A_TXT)), None); }); + // Modify files in the working copy and perform git operations on other files. git_reset(0, &repo); git_remove_index(Path::new(B_TXT), &repo); git_stash(&mut repo); diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index f00346510d..4e2a3c98e2 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -3,7 +3,7 @@ authors = ["Nathan Sobo "] description = "The fast, collaborative code editor." edition = "2021" name = "zed" -version = "0.93.0" +version = "0.93.4" publish = false [lib] @@ -16,6 +16,7 @@ name = "Zed" path = "src/main.rs" [dependencies] +audio = { path = "../audio" } activity_indicator = { path = "../activity_indicator" } auto_update = { path = "../auto_update" } breadcrumbs = { path = "../breadcrumbs" } diff --git a/crates/zed/RELEASE_CHANNEL b/crates/zed/RELEASE_CHANNEL index 90012116c0..870bbe4e50 100644 --- a/crates/zed/RELEASE_CHANNEL +++ b/crates/zed/RELEASE_CHANNEL @@ -1 +1 @@ -dev \ No newline at end of file +stable \ No newline at end of file diff --git a/crates/zed/src/assets.rs b/crates/zed/src/assets.rs index 6eb8a44f0f..574016c25d 100644 --- a/crates/zed/src/assets.rs +++ b/crates/zed/src/assets.rs @@ -7,6 +7,7 @@ use rust_embed::RustEmbed; #[include = "fonts/**/*"] #[include = "icons/**/*"] #[include = "themes/**/*"] +#[include = "sounds/**/*"] #[include = "*.md"] #[exclude = "*.DS_Store"] pub struct Assets; diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 6cf524c9e0..918e19fe28 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -180,6 +180,8 @@ fn main() { background_actions, }); cx.set_global(Arc::downgrade(&app_state)); + + audio::init(Assets, cx); auto_update::init(http.clone(), client::ZED_SERVER_URL.clone(), cx); workspace::init(app_state.clone(), cx); diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 9b204374d1..6fc3ae1cd9 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -2160,6 +2160,7 @@ mod tests { state.initialize_workspace = initialize_workspace; state.build_window_options = build_window_options; theme::init((), cx); + audio::init((), cx); call::init(app_state.client.clone(), app_state.user_store.clone(), cx); workspace::init(app_state.clone(), cx); Project::init_settings(cx); diff --git a/styles/src/component/icon_button.ts b/styles/src/component/icon_button.ts index 43f8d0f9c4..39c1adb5e5 100644 --- a/styles/src/component/icon_button.ts +++ b/styles/src/component/icon_button.ts @@ -1,25 +1,32 @@ -import { ColorScheme } from "../common"; -import { interactive, toggleable } from "../element"; -import { background, foreground } from "../styleTree/components"; +import { ColorScheme } from "../common" +import { interactive, toggleable } from "../element" +import { background, foreground } from "../styleTree/components" export type Margin = { - top: number; - bottom: number; - left: number; - right: number; + top: number + bottom: number + left: number + right: number } interface IconButtonOptions { - layer?: ColorScheme['lowest'] | ColorScheme['middle'] | ColorScheme['highest']; - color?: keyof ColorScheme['lowest']; - margin?: Partial; + layer?: + | ColorScheme["lowest"] + | ColorScheme["middle"] + | ColorScheme["highest"] + color?: keyof ColorScheme["lowest"] + margin?: Partial } -type ToggleableIconButtonOptions = IconButtonOptions & { active_color?: keyof ColorScheme['lowest'] }; +type ToggleableIconButtonOptions = IconButtonOptions & { + active_color?: keyof ColorScheme["lowest"] +} -export function icon_button(theme: ColorScheme, { color, margin, layer }: IconButtonOptions) { - if (!color) - color = "base"; +export function icon_button( + theme: ColorScheme, + { color, margin, layer }: IconButtonOptions +) { + if (!color) color = "base" const m = { top: margin?.top ?? 0, @@ -51,25 +58,29 @@ export function icon_button(theme: ColorScheme, { color, margin, layer }: IconBu hovered: { background: background(layer ?? theme.lowest, color, "hovered"), color: foreground(layer ?? theme.lowest, color, "hovered"), - }, clicked: { background: background(layer ?? theme.lowest, color, "pressed"), color: foreground(layer ?? theme.lowest, color, "pressed"), - }, }, - }); + }) } -export function toggleable_icon_button(theme: ColorScheme, { color, active_color, margin }: ToggleableIconButtonOptions) { - if (!color) - color = "base"; +export function toggleable_icon_button( + theme: ColorScheme, + { color, active_color, margin }: ToggleableIconButtonOptions +) { + if (!color) color = "base" return toggleable({ state: { inactive: icon_button(theme, { color, margin }), - active: icon_button(theme, { color: active_color ? active_color : color, margin, layer: theme.middle }), - } + active: icon_button(theme, { + color: active_color ? active_color : color, + margin, + layer: theme.middle, + }), + }, }) } diff --git a/styles/src/component/text_button.ts b/styles/src/component/text_button.ts index ae7fede900..b8a2272cd3 100644 --- a/styles/src/component/text_button.ts +++ b/styles/src/component/text_button.ts @@ -1,25 +1,37 @@ -import { ColorScheme } from "../common"; -import { interactive, toggleable } from "../element"; -import { TextProperties, background, foreground, text } from "../styleTree/components"; -import { Margin } from "./icon_button"; +import { ColorScheme } from "../common" +import { interactive, toggleable } from "../element" +import { + TextProperties, + background, + foreground, + text, +} from "../styleTree/components" +import { Margin } from "./icon_button" interface TextButtonOptions { - layer?: ColorScheme['lowest'] | ColorScheme['middle'] | ColorScheme['highest']; - color?: keyof ColorScheme['lowest']; - margin?: Partial; - text_properties?: TextProperties; + layer?: + | ColorScheme["lowest"] + | ColorScheme["middle"] + | ColorScheme["highest"] + color?: keyof ColorScheme["lowest"] + margin?: Partial + text_properties?: TextProperties } -type ToggleableTextButtonOptions = TextButtonOptions & { active_color?: keyof ColorScheme['lowest'] }; +type ToggleableTextButtonOptions = TextButtonOptions & { + active_color?: keyof ColorScheme["lowest"] +} -export function text_button(theme: ColorScheme, { color, layer, margin, text_properties }: TextButtonOptions) { - if (!color) - color = "base"; +export function text_button( + theme: ColorScheme, + { color, layer, margin, text_properties }: TextButtonOptions +) { + if (!color) color = "base" const text_options: TextProperties = { size: "xs", weight: "normal", - ...text_properties + ...text_properties, } const m = { @@ -40,7 +52,7 @@ export function text_button(theme: ColorScheme, { color, layer, margin, text_pro }, margin: m, button_height: 22, - ...text(layer ?? theme.lowest, "sans", color, text_options) + ...text(layer ?? theme.lowest, "sans", color, text_options), }, state: { default: { @@ -50,25 +62,29 @@ export function text_button(theme: ColorScheme, { color, layer, margin, text_pro hovered: { background: background(layer ?? theme.lowest, color, "hovered"), color: foreground(layer ?? theme.lowest, color, "hovered"), - }, clicked: { background: background(layer ?? theme.lowest, color, "pressed"), color: foreground(layer ?? theme.lowest, color, "pressed"), - }, }, - }); + }) } -export function toggleable_text_button(theme: ColorScheme, { color, active_color, margin }: ToggleableTextButtonOptions) { - if (!color) - color = "base"; +export function toggleable_text_button( + theme: ColorScheme, + { color, active_color, margin }: ToggleableTextButtonOptions +) { + if (!color) color = "base" return toggleable({ state: { inactive: text_button(theme, { color, margin }), - active: text_button(theme, { color: active_color ? active_color : color, margin, layer: theme.middle }), - } + active: text_button(theme, { + color: active_color ? active_color : color, + margin, + layer: theme.middle, + }), + }, }) } diff --git a/styles/src/element/interactive.ts b/styles/src/element/interactive.ts index 79fee70cb9..03a1f7f5ce 100644 --- a/styles/src/element/interactive.ts +++ b/styles/src/element/interactive.ts @@ -1,7 +1,7 @@ import merge from "ts-deepmerge" import { DeepPartial } from "utility-types" -type InteractiveState = +export type InteractiveState = | "default" | "hovered" | "clicked" diff --git a/styles/src/styleTree/assistant.ts b/styles/src/styleTree/assistant.ts index b6e97303f2..30e109df1a 100644 --- a/styles/src/styleTree/assistant.ts +++ b/styles/src/styleTree/assistant.ts @@ -26,15 +26,15 @@ export default function assistant(colorScheme: ColorScheme) { }, container: { padding: { left: 12, right: 8.5 }, - } + }, }, state: { hovered: { icon: { - color: foreground(layer, "hovered") - } - } - } + color: foreground(layer, "hovered"), + }, + }, + }, }), splitButton: interactive({ base: { @@ -48,15 +48,15 @@ export default function assistant(colorScheme: ColorScheme) { }, container: { padding: { left: 8.5, right: 8.5 }, - } + }, }, state: { hovered: { icon: { - color: foreground(layer, "hovered") - } - } - } + color: foreground(layer, "hovered"), + }, + }, + }, }), quoteButton: interactive({ base: { @@ -70,15 +70,15 @@ export default function assistant(colorScheme: ColorScheme) { }, container: { padding: { left: 8.5, right: 8.5 }, - } + }, }, state: { hovered: { icon: { - color: foreground(layer, "hovered") - } - } - } + color: foreground(layer, "hovered"), + }, + }, + }, }), assistButton: interactive({ base: { @@ -92,15 +92,15 @@ export default function assistant(colorScheme: ColorScheme) { }, container: { padding: { left: 8.5, right: 8.5 }, - } + }, }, state: { hovered: { icon: { - color: foreground(layer, "hovered") - } - } - } + color: foreground(layer, "hovered"), + }, + }, + }, }), zoomInButton: interactive({ base: { @@ -114,15 +114,15 @@ export default function assistant(colorScheme: ColorScheme) { }, container: { padding: { left: 10, right: 10 }, - } + }, }, state: { hovered: { icon: { - color: foreground(layer, "hovered") - } - } - } + color: foreground(layer, "hovered"), + }, + }, + }, }), zoomOutButton: interactive({ base: { @@ -136,15 +136,15 @@ export default function assistant(colorScheme: ColorScheme) { }, container: { padding: { left: 10, right: 10 }, - } + }, }, state: { hovered: { icon: { - color: foreground(layer, "hovered") - } - } - } + color: foreground(layer, "hovered"), + }, + }, + }, }), plusButton: interactive({ base: { @@ -158,29 +158,29 @@ export default function assistant(colorScheme: ColorScheme) { }, container: { padding: { left: 10, right: 10 }, - } + }, }, state: { hovered: { icon: { - color: foreground(layer, "hovered") - } - } - } + color: foreground(layer, "hovered"), + }, + }, + }, }), title: { - ...text(layer, "sans", "default", { size: "sm" }) + ...text(layer, "sans", "default", { size: "sm" }), }, savedConversation: { container: interactive({ base: { background: background(layer, "on"), - padding: { top: 4, bottom: 4 } + padding: { top: 4, bottom: 4 }, }, state: { hovered: { background: background(layer, "on", "hovered"), - } + }, }, }), savedAt: { @@ -189,8 +189,11 @@ export default function assistant(colorScheme: ColorScheme) { }, title: { margin: { left: 16 }, - ...text(layer, "sans", "default", { size: "sm", weight: "bold" }), - } + ...text(layer, "sans", "default", { + size: "sm", + weight: "bold", + }), + }, }, userSender: { default: { diff --git a/styles/src/styleTree/components.ts b/styles/src/styleTree/components.ts index bfd1f75cb0..3b6f26066b 100644 --- a/styles/src/styleTree/components.ts +++ b/styles/src/styleTree/components.ts @@ -93,6 +93,14 @@ interface Text extends Object { underline?: boolean } +export interface TextStyle extends Object { + family: keyof typeof fontFamilies + color: string + size: number + weight?: FontWeight + underline?: boolean +} + export interface TextProperties { size?: keyof typeof fontSizes weight?: FontWeight diff --git a/styles/src/styleTree/projectPanel.ts b/styles/src/styleTree/projectPanel.ts index 6bec951288..b45dc36ab7 100644 --- a/styles/src/styleTree/projectPanel.ts +++ b/styles/src/styleTree/projectPanel.ts @@ -1,73 +1,125 @@ import { ColorScheme } from "../theme/colorScheme" import { withOpacity } from "../theme/color" -import { background, border, foreground, text } from "./components" +import { + Border, + TextStyle, + background, + border, + foreground, + text, +} from "./components" import { interactive, toggleable } from "../element" +import merge from "ts-deepmerge" export default function projectPanel(colorScheme: ColorScheme) { const { isLight } = colorScheme let layer = colorScheme.middle - let baseEntry = { - height: 22, - iconColor: foreground(layer, "variant"), - iconSize: 7, - iconSpacing: 5, + type EntryStateProps = { + background?: string + border?: Border + text?: TextStyle + iconColor?: string } - let status = { - git: { - modified: isLight - ? colorScheme.ramps.yellow(0.6).hex() - : colorScheme.ramps.yellow(0.5).hex(), - inserted: isLight - ? colorScheme.ramps.green(0.45).hex() - : colorScheme.ramps.green(0.5).hex(), - conflict: isLight - ? colorScheme.ramps.red(0.6).hex() - : colorScheme.ramps.red(0.5).hex(), - }, + type EntryState = { + default: EntryStateProps + hovered?: EntryStateProps + clicked?: EntryStateProps } - const default_entry = interactive({ - base: { - ...baseEntry, - text: text(layer, "mono", "variant", { size: "sm" }), - status, - }, - state: { - default: { - background: background(layer), + const entry = (unselected?: EntryState, selected?: EntryState) => { + const git_status = { + git: { + modified: isLight + ? colorScheme.ramps.yellow(0.6).hex() + : colorScheme.ramps.yellow(0.5).hex(), + inserted: isLight + ? colorScheme.ramps.green(0.45).hex() + : colorScheme.ramps.green(0.5).hex(), + conflict: isLight + ? colorScheme.ramps.red(0.6).hex() + : colorScheme.ramps.red(0.5).hex(), }, - hovered: { - background: background(layer, "variant", "hovered"), - }, - clicked: { - background: background(layer, "variant", "pressed"), - }, - }, - }) + } - let entry = toggleable({ - base: default_entry, - state: { - active: interactive({ - base: { - ...default_entry, - }, - state: { - default: { - background: background(colorScheme.lowest), + const base_properties = { + height: 22, + background: background(layer), + iconColor: foreground(layer, "variant"), + iconSize: 7, + iconSpacing: 5, + text: text(layer, "sans", "variant", { size: "sm" }), + status: { + ...git_status, + }, + } + + const selectedStyle: EntryState | undefined = selected + ? selected + : unselected + + const unselected_default_style = merge( + base_properties, + unselected?.default ?? {}, + {} + ) + const unselected_hovered_style = merge( + base_properties, + { background: background(layer, "hovered") }, + unselected?.hovered ?? {}, + ) + const unselected_clicked_style = merge( + base_properties, + { background: background(layer, "pressed") }, + unselected?.clicked ?? {}, + ) + const selected_default_style = merge( + base_properties, + { + background: background(colorScheme.lowest), + text: text(colorScheme.lowest, "sans", { size: "sm" }), + }, + selectedStyle?.default ?? {}, + ) + const selected_hovered_style = merge( + base_properties, + { + background: background(colorScheme.lowest, "hovered"), + text: text(colorScheme.lowest, "sans", { size: "sm" }), + }, + selectedStyle?.hovered ?? {}, + ) + const selected_clicked_style = merge( + base_properties, + { + background: background(colorScheme.lowest, "pressed"), + text: text(colorScheme.lowest, "sans", { size: "sm" }), + }, + selectedStyle?.clicked ?? {}, + ) + + return toggleable({ + state: { + inactive: interactive({ + state: { + default: unselected_default_style, + hovered: unselected_hovered_style, + clicked: unselected_clicked_style, }, - hovered: { - background: background(colorScheme.lowest, "hovered"), + }), + active: interactive({ + state: { + default: selected_default_style, + hovered: selected_hovered_style, + clicked: selected_clicked_style, }, - clicked: { - background: background(colorScheme.lowest, "pressed"), - }, - }, - }), - }, - }) + }), + }, + }) + } + + const defaultEntry = entry() return { openProjectButton: interactive({ @@ -104,38 +156,41 @@ export default function projectPanel(colorScheme: ColorScheme) { background: background(layer), padding: { left: 6, right: 6, top: 0, bottom: 6 }, indentWidth: 12, - entry, + entry: defaultEntry, draggedEntry: { - ...baseEntry, - text: text(layer, "mono", "on", { size: "sm" }), + ...defaultEntry.inactive.default, + text: text(layer, "sans", "on", { size: "sm" }), background: withOpacity(background(layer, "on"), 0.9), border: border(layer), - status, }, - ignoredEntry: { - ...entry, - iconColor: foreground(layer, "disabled"), - text: text(layer, "mono", "disabled"), - active: { - ...entry.active, - iconColor: foreground(layer, "variant"), - }, - }, - cutEntry: { - ...entry, - text: text(layer, "mono", "disabled"), - active: { - ...entry.active, + ignoredEntry: entry( + { default: { - ...entry.active.default, - background: background(layer, "active"), - text: text(layer, "mono", "disabled", { size: "sm" }), + text: text(layer, "sans", "disabled"), }, }, - }, + { + default: { + iconColor: foreground(layer, "variant"), + }, + } + ), + cutEntry: entry( + { + default: { + text: text(layer, "sans", "disabled"), + }, + }, + { + default: { + background: background(layer, "active"), + text: text(layer, "sans", "disabled", { size: "sm" }), + }, + } + ), filenameEditor: { background: background(layer, "on"), - text: text(layer, "mono", "on", { size: "sm" }), + text: text(layer, "sans", "on", { size: "sm" }), selection: colorScheme.players[0], }, } diff --git a/styles/src/styleTree/titlebar.ts b/styles/src/styleTree/titlebar.ts index fe0885c8e7..3c7318a56e 100644 --- a/styles/src/styleTree/titlebar.ts +++ b/styles/src/styleTree/titlebar.ts @@ -1,9 +1,9 @@ -import { ColorScheme } from "../common"; +import { ColorScheme } from "../common" import { icon_button, toggleable_icon_button } from "../component/icon_button" import { toggleable_text_button } from "../component/text_button" import { interactive, toggleable } from "../element" -import { withOpacity } from "../theme/color"; -import { background, border, foreground, text } from "./components"; +import { withOpacity } from "../theme/color" +import { background, border, foreground, text } from "./components" const ITEM_SPACING = 8 const TITLEBAR_HEIGHT = 32 @@ -25,7 +25,7 @@ function build_spacing( function call_controls(theme: ColorScheme) { const button_height = 18 - const space = build_spacing(TITLEBAR_HEIGHT, button_height, ITEM_SPACING); + const space = build_spacing(TITLEBAR_HEIGHT, button_height, ITEM_SPACING) const marginY = { top: space.marginY, bottom: space.marginY, @@ -38,14 +38,14 @@ function call_controls(theme: ColorScheme) { left: space.group, right: space.half_item, }, - active_color: 'negative' + active_color: "negative", }), toggle_speakers_button: toggleable_icon_button(theme, { margin: { ...marginY, left: space.half_item, - right: space.half_item + right: space.half_item, }, }), @@ -53,9 +53,9 @@ function call_controls(theme: ColorScheme) { margin: { ...marginY, left: space.half_item, - right: space.group + right: space.group, }, - active_color: 'accent' + active_color: "accent", }), muted: foreground(theme.lowest, "negative"), @@ -64,15 +64,15 @@ function call_controls(theme: ColorScheme) { } /** -* Opens the User Menu when toggled -* -* When logged in shows the user's avatar and a chevron, -* When logged out only shows a chevron. -*/ + * Opens the User Menu when toggled + * + * When logged in shows the user's avatar and a chevron, + * When logged out only shows a chevron. + */ function user_menu(theme: ColorScheme) { const button_height = 18 - const space = build_spacing(TITLEBAR_HEIGHT, button_height, ITEM_SPACING); + const space = build_spacing(TITLEBAR_HEIGHT, button_height, ITEM_SPACING) const build_button = ({ online }: { online: boolean }) => { const button = toggleable({ @@ -124,8 +124,8 @@ function user_menu(theme: ColorScheme) { background: background(theme.middle, "pressed"), }, }, - } - }); + }, + }) return { user_menu: button, @@ -134,7 +134,7 @@ function user_menu(theme: ColorScheme) { icon_height: 16, corner_radius: 4, outer_width: 16, - outer_corner_radius: 16 + outer_corner_radius: 16, }, icon: { margin: { @@ -145,8 +145,8 @@ function user_menu(theme: ColorScheme) { }, width: 11, height: 11, - color: foreground(theme.lowest) - } + color: foreground(theme.lowest), + }, } } return { @@ -240,7 +240,7 @@ export function titlebar(theme: ColorScheme) { leave_call_button: icon_button(theme, { margin: { left: ITEM_SPACING / 2, - right: ITEM_SPACING + right: ITEM_SPACING, }, }), @@ -261,6 +261,6 @@ export function titlebar(theme: ColorScheme) { background: foreground(theme.lowest, "accent"), }, shareButton: toggleable_text_button(theme, {}), - user_menu: user_menu(theme) + user_menu: user_menu(theme), } } diff --git a/styles/src/themes/rose-pine/common.ts b/styles/src/themes/rose-pine/common.ts index 8b3b3b7000..146305890b 100644 --- a/styles/src/themes/rose-pine/common.ts +++ b/styles/src/themes/rose-pine/common.ts @@ -1,39 +1,39 @@ -import { ThemeSyntax } from "../../common"; +import { ThemeSyntax } from "../../common" export const color = { default: { - base: '#191724', - surface: '#1f1d2e', - overlay: '#26233a', - muted: '#6e6a86', - subtle: '#908caa', - text: '#e0def4', - love: '#eb6f92', - gold: '#f6c177', - rose: '#ebbcba', - pine: '#31748f', - foam: '#9ccfd8', - iris: '#c4a7e7', - highlightLow: '#21202e', - highlightMed: '#403d52', - highlightHigh: '#524f67', + base: "#191724", + surface: "#1f1d2e", + overlay: "#26233a", + muted: "#6e6a86", + subtle: "#908caa", + text: "#e0def4", + love: "#eb6f92", + gold: "#f6c177", + rose: "#ebbcba", + pine: "#31748f", + foam: "#9ccfd8", + iris: "#c4a7e7", + highlightLow: "#21202e", + highlightMed: "#403d52", + highlightHigh: "#524f67", }, moon: { - base: '#232136', - surface: '#2a273f', - overlay: '#393552', - muted: '#6e6a86', - subtle: '#908caa', - text: '#e0def4', - love: '#eb6f92', - gold: '#f6c177', - rose: '#ea9a97', - pine: '#3e8fb0', - foam: '#9ccfd8', - iris: '#c4a7e7', - highlightLow: '#2a283e', - highlightMed: '#44415a', - highlightHigh: '#56526e', + base: "#232136", + surface: "#2a273f", + overlay: "#393552", + muted: "#6e6a86", + subtle: "#908caa", + text: "#e0def4", + love: "#eb6f92", + gold: "#f6c177", + rose: "#ea9a97", + pine: "#3e8fb0", + foam: "#9ccfd8", + iris: "#c4a7e7", + highlightLow: "#2a283e", + highlightMed: "#44415a", + highlightHigh: "#56526e", }, dawn: { base: "#faf4ed", @@ -51,8 +51,8 @@ export const color = { highlightLow: "#f4ede8", highlightMed: "#dfdad9", highlightHigh: "#cecacd", - } -}; + }, +} export const syntax = (c: typeof color.default): Partial => { return { diff --git a/styles/src/themes/rose-pine/rose-pine-dawn.ts b/styles/src/themes/rose-pine/rose-pine-dawn.ts index 3ec5d30bb6..15d7b5de2d 100644 --- a/styles/src/themes/rose-pine/rose-pine-dawn.ts +++ b/styles/src/themes/rose-pine/rose-pine-dawn.ts @@ -6,12 +6,12 @@ import { ThemeConfig, } from "../../common" -import { color as c, syntax } from "./common"; +import { color as c, syntax } from "./common" const color = c.dawn -const green = chroma.mix(color.foam, "#10b981", 0.6, 'lab'); -const magenta = chroma.mix(color.love, color.pine, 0.5, 'lab'); +const green = chroma.mix(color.foam, "#10b981", 0.6, "lab") +const magenta = chroma.mix(color.love, color.pine, 0.5, "lab") export const theme: ThemeConfig = { name: "Rosé Pine Dawn", @@ -21,7 +21,19 @@ export const theme: ThemeConfig = { licenseUrl: "https://github.com/edunfelt/base16-rose-pine-scheme", licenseFile: `${__dirname}/LICENSE`, inputColor: { - neutral: chroma.scale([color.base, color.surface, color.highlightHigh, color.overlay, color.muted, color.subtle, color.text].reverse()).domain([0, 0.35, 0.45, 0.65, 0.7, 0.8, 0.9, 1]), + neutral: chroma + .scale( + [ + color.base, + color.surface, + color.highlightHigh, + color.overlay, + color.muted, + color.subtle, + color.text, + ].reverse() + ) + .domain([0, 0.35, 0.45, 0.65, 0.7, 0.8, 0.9, 1]), red: colorRamp(chroma(color.love)), orange: colorRamp(chroma(color.iris)), yellow: colorRamp(chroma(color.gold)), @@ -32,6 +44,6 @@ export const theme: ThemeConfig = { magenta: colorRamp(chroma(magenta)), }, override: { - syntax: syntax(color) - } + syntax: syntax(color), + }, } diff --git a/styles/src/themes/rose-pine/rose-pine-moon.ts b/styles/src/themes/rose-pine/rose-pine-moon.ts index 4e70d2f0ad..c5ef0c997f 100644 --- a/styles/src/themes/rose-pine/rose-pine-moon.ts +++ b/styles/src/themes/rose-pine/rose-pine-moon.ts @@ -6,12 +6,12 @@ import { ThemeConfig, } from "../../common" -import { color as c, syntax } from "./common"; +import { color as c, syntax } from "./common" const color = c.moon -const green = chroma.mix(color.foam, "#10b981", 0.6, 'lab'); -const magenta = chroma.mix(color.love, color.pine, 0.5, 'lab'); +const green = chroma.mix(color.foam, "#10b981", 0.6, "lab") +const magenta = chroma.mix(color.love, color.pine, 0.5, "lab") export const theme: ThemeConfig = { name: "Rosé Pine Moon", @@ -21,7 +21,17 @@ export const theme: ThemeConfig = { licenseUrl: "https://github.com/edunfelt/base16-rose-pine-scheme", licenseFile: `${__dirname}/LICENSE`, inputColor: { - neutral: chroma.scale([color.base, color.surface, color.highlightHigh, color.overlay, color.muted, color.subtle, color.text]).domain([0, 0.3, 0.55, 1]), + neutral: chroma + .scale([ + color.base, + color.surface, + color.highlightHigh, + color.overlay, + color.muted, + color.subtle, + color.text, + ]) + .domain([0, 0.3, 0.55, 1]), red: colorRamp(chroma(color.love)), orange: colorRamp(chroma(color.iris)), yellow: colorRamp(chroma(color.gold)), @@ -32,6 +42,6 @@ export const theme: ThemeConfig = { magenta: colorRamp(chroma(magenta)), }, override: { - syntax: syntax(color) - } + syntax: syntax(color), + }, } diff --git a/styles/src/themes/rose-pine/rose-pine.ts b/styles/src/themes/rose-pine/rose-pine.ts index 6c4a6ac49b..0f3b439338 100644 --- a/styles/src/themes/rose-pine/rose-pine.ts +++ b/styles/src/themes/rose-pine/rose-pine.ts @@ -5,12 +5,12 @@ import { ThemeLicenseType, ThemeConfig, } from "../../common" -import { color as c, syntax } from "./common"; +import { color as c, syntax } from "./common" const color = c.default -const green = chroma.mix(color.foam, "#10b981", 0.6, 'lab'); -const magenta = chroma.mix(color.love, color.pine, 0.5, 'lab'); +const green = chroma.mix(color.foam, "#10b981", 0.6, "lab") +const magenta = chroma.mix(color.love, color.pine, 0.5, "lab") export const theme: ThemeConfig = { name: "Rosé Pine", @@ -20,7 +20,15 @@ export const theme: ThemeConfig = { licenseUrl: "https://github.com/edunfelt/base16-rose-pine-scheme", licenseFile: `${__dirname}/LICENSE`, inputColor: { - neutral: chroma.scale([color.base, color.surface, color.highlightHigh, color.overlay, color.muted, color.subtle, color.text]), + neutral: chroma.scale([ + color.base, + color.surface, + color.highlightHigh, + color.overlay, + color.muted, + color.subtle, + color.text, + ]), red: colorRamp(chroma(color.love)), orange: colorRamp(chroma(color.iris)), yellow: colorRamp(chroma(color.gold)), @@ -31,6 +39,6 @@ export const theme: ThemeConfig = { magenta: colorRamp(chroma(magenta)), }, override: { - syntax: syntax(color) - } + syntax: syntax(color), + }, }