diff --git a/Cargo.lock b/Cargo.lock index 1fba820614ba211ffedf5cbb4455d0100f3db529..ce727a9c6c4af9aa1a5ecdb4c063d5144510f7aa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -14,12 +14,13 @@ version = "0.1.0" dependencies = [ "auto_update", "editor", - "futures 0.3.25", + "futures 0.3.28", "gpui", "language", "project", "settings", "smallvec", + "theme", "util", "workspace", ] @@ -30,7 +31,16 @@ version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9ecd88a8c8378ca913a680cd98f0f13ac67383d35993f86c90a70e3f137816b" dependencies = [ - "gimli", + "gimli 0.26.2", +] + +[[package]] +name = "addr2line" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a76fd60b23679b7d19bd066031410fb7e458ccc5e958eb5c325888ce4baedc97" +dependencies = [ + "gimli 0.27.2", ] [[package]] @@ -51,7 +61,18 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" dependencies = [ - "getrandom 0.2.8", + "getrandom 0.2.9", + "once_cell", + "version_check", +] + +[[package]] +name = "ahash" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f" +dependencies = [ + "cfg-if 1.0.0", "once_cell", "version_check", ] @@ -65,6 +86,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "aho-corasick" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67fc08ce920c31afb70f013dcce1bfc3a3195de6a228474e45e1f145b36f8d04" +dependencies = [ + "memchr", +] + [[package]] name = "alacritty_config" version = "0.1.1-dev" @@ -82,7 +112,7 @@ source = "git+https://github.com/zed-industries/alacritty?rev=a51dbe25d67e84d6ed dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -92,7 +122,7 @@ source = "git+https://github.com/zed-industries/alacritty?rev=a51dbe25d67e84d6ed dependencies = [ "alacritty_config", "alacritty_config_derive", - "base64", + "base64 0.13.1", "bitflags", "dirs 4.0.0", "libc", @@ -145,15 +175,15 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.66" +version = "1.0.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "216261ddc8289130e551ddcd5ce8a064710c0d064a4d2895c67151c92b5443f6" +checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8" [[package]] name = "arrayref" -version = "0.3.6" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544" +checksum = "6b4930d2cb77ce62f89ee5d5289b4ac049559b1c45539271f5ed4fdc7db34545" [[package]] name = "arrayvec" @@ -232,9 +262,9 @@ dependencies = [ [[package]] name = "async-executor" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17adb73da160dfb475c183343c8cccd80721ea5a605d3eb57125f0a7b7a92d0b" +checksum = "6fa3dc5f2a8564f07759c008b9109dc0d39de92a88d5588b8a5036d286383afb" dependencies = [ "async-lock", "async-task", @@ -273,32 +303,31 @@ dependencies = [ [[package]] name = "async-io" -version = "1.12.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c374dda1ed3e7d8f0d9ba58715f924862c63eae6849c92d3a18e7fbde9e2794" +checksum = "0fc5b45d93ef0529756f812ca52e44c221b35341892d3dcc34132ac02f3dd2af" dependencies = [ "async-lock", "autocfg 1.1.0", + "cfg-if 1.0.0", "concurrent-queue", "futures-lite", - "libc", "log", "parking", "polling", + "rustix 0.37.19", "slab", "socket2", "waker-fn", - "windows-sys 0.42.0", ] [[package]] name = "async-lock" -version = "2.6.0" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8101efe8695a6c17e02911402145357e718ac92d3ff88ae8419e84b1707b685" +checksum = "fa24f727524730b077666307f2734b4a1a1c57acb79193127dcc8914d5242dd7" dependencies = [ "event-listener", - "futures-lite", ] [[package]] @@ -318,15 +347,15 @@ name = "async-pipe" version = "0.1.3" source = "git+https://github.com/zed-industries/async-pipe-rs?rev=82d00a04211cf4e1236029aa03e6b6ce2a74c553#82d00a04211cf4e1236029aa03e6b6ce2a74c553" dependencies = [ - "futures 0.3.25", + "futures 0.3.28", "log", ] [[package]] name = "async-process" -version = "1.6.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6381ead98388605d0d9ff86371043b5aa922a3905824244de40dc263a14fcba4" +checksum = "7a9d28b1d97e08915212e2e45310d47854eafa69600756fc735fb788f75199c9" dependencies = [ "async-io", "async-lock", @@ -335,9 +364,9 @@ dependencies = [ "cfg-if 1.0.0", "event-listener", "futures-lite", - "libc", + "rustix 0.37.19", "signal-hook", - "windows-sys 0.42.0", + "windows-sys 0.48.0", ] [[package]] @@ -348,18 +377,18 @@ checksum = "d7d78656ba01f1b93024b7c3a0467f1608e4be67d725749fdcd7d2c7678fd7a2" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] name = "async-recursion" -version = "1.0.0" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cda8f4bcc10624c4e85bc66b3f452cca98cfa5ca002dc83a16aad2367641bea" +checksum = "0e97ce7de6cf12de5d7226c73f5ba9811622f4db3a5b91b55c53e987e5f91cba" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.15", ] [[package]] @@ -372,7 +401,7 @@ dependencies = [ "async-global-executor", "async-io", "async-lock", - "crossbeam-utils 0.8.14", + "crossbeam-utils 0.8.15", "futures-channel", "futures-core", "futures-io", @@ -390,23 +419,24 @@ dependencies = [ [[package]] name = "async-stream" -version = "0.3.3" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dad5c83079eae9969be7fadefe640a1c566901f05ff91ab221de4b6f68d9507e" +checksum = "cd56dd203fef61ac097dd65721a419ddccb106b2d2b70ba60a6b529f03961a51" dependencies = [ "async-stream-impl", "futures-core", + "pin-project-lite 0.2.9", ] [[package]] name = "async-stream-impl" -version = "0.3.3" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10f203db73a71dfa2fb6dd22763990fa26f3d2625a6da2da900d23b87d26be27" +checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.15", ] [[package]] @@ -419,7 +449,7 @@ dependencies = [ "filetime", "libc", "pin-project", - "redox_syscall", + "redox_syscall 0.2.16", "xattr", ] @@ -443,13 +473,13 @@ dependencies = [ [[package]] name = "async-trait" -version = "0.1.59" +version = "0.1.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31e6e93155431f3931513b243d371981bb2770112b370c82745a1d19d2f99364" +checksum = "b9ccdd8f2a161be9bd5c023df56f1b2a0bd1d83872ae53b71a84a12c9bf6e842" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.15", ] [[package]] @@ -486,9 +516,9 @@ dependencies = [ [[package]] name = "atomic-waker" -version = "1.0.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "065374052e7df7ee4047b1160cca5e1467a12351a40b3da123c870ba0b8eda2a" +checksum = "1181e1e0d1fce796a03db1ae795d67167da795f9cf4a39c37589e85ef57f26d3" [[package]] name = "atty" @@ -548,9 +578,9 @@ checksum = "acee9fd5073ab6b045a275b3e709c163dd36c90685219cb21804a147b58dba43" dependencies = [ "async-trait", "axum-core", - "base64", + "base64 0.13.1", "bitflags", - "bytes 1.3.0", + "bytes 1.4.0", "futures-util", "headers", "http", @@ -582,7 +612,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37e5939e02c56fecd5c017c37df4238c0a839fa76b7f97acdd7efb804fd181cc" dependencies = [ "async-trait", - "bytes 1.3.0", + "bytes 1.4.0", "futures-util", "http", "http-body", @@ -598,7 +628,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69034b3b0fd97923eee2ce8a47540edb21e07f48f87f67d44bb4271cec622bdb" dependencies = [ "axum", - "bytes 1.3.0", + "bytes 1.4.0", "futures-util", "http", "mime", @@ -614,16 +644,16 @@ dependencies = [ [[package]] name = "backtrace" -version = "0.3.66" +version = "0.3.67" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cab84319d616cfb654d03394f38ab7e6f0919e181b1b57e1fd15e7fb4077d9a7" +checksum = "233d376d6d185f2a3093e58f283f60f880315b6c60075b01f36b3b85154564ca" dependencies = [ - "addr2line", + "addr2line 0.19.0", "cc", "cfg-if 1.0.0", "libc", - "miniz_oxide 0.5.4", - "object 0.29.0", + "miniz_oxide 0.6.2", + "object 0.30.3", "rustc-demangle", ] @@ -637,7 +667,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -646,11 +676,17 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" +[[package]] +name = "base64" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a" + [[package]] name = "base64ct" -version = "1.5.3" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b645a089122eccb6111b4f81cbc1a49f5900ac4666bb93ac027feaecf15607bf" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" [[package]] name = "bincode" @@ -671,7 +707,7 @@ dependencies = [ "cexpr", "clang-sys", "clap 2.34.0", - "env_logger", + "env_logger 0.9.3", "lazy_static", "lazycell", "log", @@ -707,18 +743,18 @@ dependencies = [ [[package]] name = "block-buffer" -version = "0.10.3" +version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" dependencies = [ "generic-array", ] [[package]] name = "blocking" -version = "1.3.0" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c67b173a56acffd6d2326fb7ab938ba0b00a71480e14902b2591c87bc5741e8" +checksum = "77231a1c8f801696fc0123ec6150ce92cffb8e164a02afb9c8ddee0e9b65ad65" dependencies = [ "async-channel", "async-lock", @@ -726,51 +762,52 @@ dependencies = [ "atomic-waker", "fastrand", "futures-lite", + "log", ] [[package]] name = "borsh" -version = "0.9.3" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15bf3650200d8bffa99015595e10f1fbd17de07abbc25bb067da79e769939bfa" +checksum = "4114279215a005bc675e386011e594e1d9b800918cea18fcadadcce864a2046b" dependencies = [ "borsh-derive", - "hashbrown 0.11.2", + "hashbrown 0.13.2", ] [[package]] name = "borsh-derive" -version = "0.9.3" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6441c552f230375d18e3cc377677914d2ca2b0d36e52129fe15450a2dce46775" +checksum = "0754613691538d51f329cce9af41d7b7ca150bc973056f1156611489475f54f7" dependencies = [ "borsh-derive-internal", "borsh-schema-derive-internal", "proc-macro-crate", "proc-macro2", - "syn", + "syn 1.0.109", ] [[package]] name = "borsh-derive-internal" -version = "0.9.3" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5449c28a7b352f2d1e592a8a28bf139bc71afb0764a14f3c02500935d8c44065" +checksum = "afb438156919598d2c7bad7e1c0adf3d26ed3840dbc010db1a882a65583ca2fb" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] name = "borsh-schema-derive-internal" -version = "0.9.3" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdbd5696d8bfa21d53d9fe39a714a18538bad11492a42d066dbbc395fb1951c0" +checksum = "634205cc43f74a1b9046ef87c4540ebda95696ec0f315024860cad7c5b0f5ccd" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -803,45 +840,47 @@ dependencies = [ [[package]] name = "bstr" -version = "0.2.17" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223" +checksum = "c3d4260bcc2e8fc9df1eac4919a720effeb63a3f0952f5bf4944adfa18897f09" dependencies = [ "memchr", + "serde", ] [[package]] name = "bumpalo" -version = "3.11.1" +version = "3.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba" +checksum = "3c6ed94e98ecff0c12dd1b04c15ec0d7d9458ca8fe806cea6f12954efe74c63b" [[package]] name = "bytecheck" -version = "0.6.9" +version = "0.6.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d11cac2c12b5adc6570dad2ee1b87eff4955dac476fe12d81e5fdd352e52406f" +checksum = "13fe11640a23eb24562225322cd3e452b93a3d4091d62fab69c70542fcd17d1f" dependencies = [ "bytecheck_derive", "ptr_meta", + "simdutf8", ] [[package]] name = "bytecheck_derive" -version = "0.6.9" +version = "0.6.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13e576ebe98e605500b3c8041bb888e966653577172df6dd97398714eb30b9bf" +checksum = "e31225543cb46f81a7e224762764f4a6a0f097b1db0b175f69e8065efaa42de5" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] name = "bytemuck" -version = "1.12.3" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aaa3a8d9a1ca92e282c96a32d6511b695d7d994d1d102ba85d279f9b2756947f" +checksum = "17febce684fd15d89027105661fec94afb475cb995fbc59d2865198446ba2eea" [[package]] name = "byteorder" @@ -861,9 +900,9 @@ dependencies = [ [[package]] name = "bytes" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfb24e866b15a1af2a1b663f10c6b6b8f397a84aadb828f12e5b289ec23a3a3c" +checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" [[package]] name = "call" @@ -874,7 +913,7 @@ dependencies = [ "client", "collections", "fs", - "futures 0.3.25", + "futures 0.3.28", "gpui", "language", "live_kit_client", @@ -894,7 +933,7 @@ checksum = "e54b86398b5852ddd45784b1d9b196b98beb39171821bad4b8b44534a1e87927" dependencies = [ "cap-primitives", "cap-std", - "io-lifetimes", + "io-lifetimes 0.5.3", "winapi 0.3.9", ] @@ -905,13 +944,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fb8fca3e81fae1d91a36e9784ca22a39ef623702b5f7904d89dc31f10184a178" dependencies = [ "ambient-authority", - "errno", + "errno 0.2.8", "fs-set-times", "io-extras", - "io-lifetimes", + "io-lifetimes 0.5.3", "ipnet", "maybe-owned", - "rustix", + "rustix 0.33.7", "winapi 0.3.9", "winapi-util", "winx", @@ -935,9 +974,9 @@ checksum = "2247568946095c7765ad2b441a56caffc08027734c634a6d5edda648f04e32eb" dependencies = [ "cap-primitives", "io-extras", - "io-lifetimes", + "io-lifetimes 0.5.3", "ipnet", - "rustix", + "rustix 0.33.7", ] [[package]] @@ -948,7 +987,7 @@ checksum = "c50472b6ebc302af0401fa3fb939694cd8ff00e0d4c9182001e434fc822ab83a" dependencies = [ "cap-primitives", "once_cell", - "rustix", + "rustix 0.33.7", "winx", ] @@ -960,9 +999,9 @@ checksum = "a2698f953def977c68f935bb0dfa959375ad4638570e969e2f1e9f433cbf1af6" [[package]] name = "cc" -version = "1.0.77" +version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9f73505338f7d905b19d18738976aae232eb46b8efc15554ffc56deb5d9ebe4" +checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" dependencies = [ "jobserver", ] @@ -990,9 +1029,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.23" +version = "0.4.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16b0a3d9ed01224b22057780a37bb8c5dbfe1be8ba48678e7bf57ec4b385411f" +checksum = "4e3c5919066adf22df73762e50cffcde3a758f2a848b113b586d1f86728b673b" dependencies = [ "iana-time-zone", "js-sys", @@ -1006,9 +1045,9 @@ dependencies = [ [[package]] name = "chunked_transfer" -version = "1.4.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fff857943da45f546682664a79488be82e69e43c1a7a2307679ab9afb3a66d2e" +checksum = "cca491388666e04d7248af3f60f0c40cfb0991c72205595d7c396e3510207d1a" [[package]] name = "cipher" @@ -1021,9 +1060,9 @@ dependencies = [ [[package]] name = "clang-sys" -version = "1.4.0" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa2e27ae6ab525c3d369ded447057bca5438d86dc3a68f6faafb8269ba82ebf3" +checksum = "c688fc74432808e3eb684cae8830a86be1d66a2bd58e1f248ed0960a590baf6f" dependencies = [ "glob", "libc", @@ -1047,9 +1086,9 @@ dependencies = [ [[package]] name = "clap" -version = "3.2.23" +version = "3.2.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71655c45cb9845d3270c9d6df84ebe72b4dad3c2ba3f7023ad47c144e4e473a5" +checksum = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123" dependencies = [ "atty", "bitflags", @@ -1064,15 +1103,15 @@ dependencies = [ [[package]] name = "clap_derive" -version = "3.2.18" +version = "3.2.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea0c8bce528c4be4da13ea6fead8965e95b6073585a2f05204bd8f4119f82a65" +checksum = "ae6371b8bdc8b7d3959e9cf7b22d4435ef3e79e138688421ec654acf8c81b008" dependencies = [ - "heck 0.4.0", + "heck 0.4.1", "proc-macro-error", "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -1089,7 +1128,7 @@ name = "cli" version = "0.1.0" dependencies = [ "anyhow", - "clap 3.2.23", + "clap 3.2.25", "core-foundation", "core-services", "dirs 3.0.2", @@ -1109,7 +1148,7 @@ dependencies = [ "async-tungstenite", "collections", "db", - "futures 0.3.25", + "futures 0.3.28", "gpui", "image", "lazy_static", @@ -1118,6 +1157,7 @@ dependencies = [ "postage", "rand 0.8.5", "rpc", + "schemars", "serde", "serde_derive", "settings", @@ -1126,11 +1166,11 @@ dependencies = [ "sum_tree", "tempfile", "thiserror", - "time 0.3.17", + "time 0.3.21", "tiny_http", "url", "util", - "uuid 1.2.2", + "uuid 1.3.2", ] [[package]] @@ -1142,9 +1182,9 @@ dependencies = [ [[package]] name = "cmake" -version = "0.1.49" +version = "0.1.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db34956e100b30725f2eb215f90d4871051239535632f84fea3bc92722c66b7c" +checksum = "a31c789563b815f77f4250caee12365734369f942439b7defd71e18a48197130" dependencies = [ "cc", ] @@ -1196,18 +1236,18 @@ dependencies = [ "async-tungstenite", "axum", "axum-extra", - "base64", + "base64 0.13.1", "call", - "clap 3.2.23", + "clap 3.2.25", "client", "collections", "ctor", "dashmap", "editor", - "env_logger", + "env_logger 0.9.3", "envy", "fs", - "futures 0.3.25", + "futures 0.3.28", "git", "gpui", "hyper", @@ -1237,7 +1277,7 @@ dependencies = [ "sha-1 0.9.8", "sqlx", "theme", - "time 0.3.17", + "time 0.3.21", "tokio", "tokio-tungstenite", "toml", @@ -1264,7 +1304,7 @@ dependencies = [ "context_menu", "editor", "feedback", - "futures 0.3.25", + "futures 0.3.28", "fuzzy", "gpui", "log", @@ -1300,9 +1340,10 @@ dependencies = [ "collections", "ctor", "editor", - "env_logger", + "env_logger 0.9.3", "fuzzy", "gpui", + "language", "picker", "project", "serde_json", @@ -1314,11 +1355,11 @@ dependencies = [ [[package]] name = "concurrent-queue" -version = "2.0.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd7bef69dc86e3c610e4e7aed41035e2a7ed12e72dd7530f61327a6579a4390b" +checksum = "62ec6771ecfa0762d24683ee5a32ad78487a3d3afdc0fb8cae19d2c5deb50b7c" dependencies = [ - "crossbeam-utils 0.8.14", + "crossbeam-utils 0.8.15", ] [[package]] @@ -1349,7 +1390,7 @@ dependencies = [ "collections", "context_menu", "fs", - "futures 0.3.25", + "futures 0.3.28", "gpui", "language", "log", @@ -1373,8 +1414,10 @@ dependencies = [ "context_menu", "copilot", "editor", - "futures 0.3.25", + "fs", + "futures 0.3.28", "gpui", + "language", "settings", "smol", "theme", @@ -1452,9 +1495,9 @@ dependencies = [ [[package]] name = "cpufeatures" -version = "0.2.5" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320" +checksum = "3e4c1eaa2012c47becbbad2ab175484c2a84d1185b566fb2cc5b8707343dfe58" dependencies = [ "libc", ] @@ -1479,7 +1522,7 @@ dependencies = [ "cranelift-codegen-shared", "cranelift-entity", "cranelift-isle", - "gimli", + "gimli 0.26.2", "log", "regalloc2", "smallvec", @@ -1557,18 +1600,18 @@ dependencies = [ [[package]] name = "crc" -version = "3.0.0" +version = "3.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53757d12b596c16c78b83458d732a5d1a17ab3f53f2f7412f6fb57cc8a140ab3" +checksum = "86ec7a15cbe22e59248fc7eadb1907dab5ba09372595da4d73dd805ed4417dfe" dependencies = [ "crc-catalog", ] [[package]] name = "crc-catalog" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d0165d2900ae6778e36e80bbc4da3b5eefccee9ba939761f9c2882a5d9af3ff" +checksum = "9cace84e55f07e7301bae1c519df89cdad8cc3cd868413d3fdbdeca9ff3db484" [[package]] name = "crc32fast" @@ -1591,35 +1634,35 @@ dependencies = [ [[package]] name = "crossbeam-channel" -version = "0.5.6" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2dd04ddaf88237dc3b8d8f9a3c1004b506b54b3313403944054d23c0870c521" +checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200" dependencies = [ "cfg-if 1.0.0", - "crossbeam-utils 0.8.14", + "crossbeam-utils 0.8.15", ] [[package]] name = "crossbeam-deque" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "715e8152b692bba2d374b53d4875445368fdf21a94751410af607a5ac677d1fc" +checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef" dependencies = [ "cfg-if 1.0.0", "crossbeam-epoch", - "crossbeam-utils 0.8.14", + "crossbeam-utils 0.8.15", ] [[package]] name = "crossbeam-epoch" -version = "0.9.13" +version = "0.9.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01a9af1f4c2ef74bb8aa1f7e19706bc72d03598c8a570bb5de72243c7a9d9d5a" +checksum = "46bd5f3f85273295a9d14aedfb86f6aadbff6d8f5295c4a9edb08e819dcf5695" dependencies = [ "autocfg 1.1.0", "cfg-if 1.0.0", - "crossbeam-utils 0.8.14", - "memoffset 0.7.1", + "crossbeam-utils 0.8.15", + "memoffset 0.8.0", "scopeguard", ] @@ -1630,7 +1673,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d1cfb3ea8a53f37c40dea2c7bedcbd88bdfae54f5e2175d6ecaff1c988353add" dependencies = [ "cfg-if 1.0.0", - "crossbeam-utils 0.8.14", + "crossbeam-utils 0.8.15", ] [[package]] @@ -1646,9 +1689,9 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.14" +version = "0.8.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fb766fa798726286dbbb842f174001dab8abc7b627a1dd86e0b7222a95d929f" +checksum = "3c063cd8cc95f5c377ed0d4b49a4b21f632396ff690e8470c29b3359b346984b" dependencies = [ "cfg-if 1.0.0", ] @@ -1680,7 +1723,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d2301688392eb071b0bf1a37be05c469d3cc4dbbd95df672fe28ab021e6a096" dependencies = [ "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -1700,9 +1743,9 @@ dependencies = [ [[package]] name = "curl-sys" -version = "0.4.59+curl-7.86.0" +version = "0.4.61+curl-8.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6cfce34829f448b08f55b7db6d0009e23e2e86a34e8c2b366269bf5799b4a407" +checksum = "14d05c10f541ae6f3bc5b3d923c20001f47db7d5f0b2bc6ad16490133842db79" dependencies = [ "cc", "libc", @@ -1716,9 +1759,9 @@ dependencies = [ [[package]] name = "cxx" -version = "1.0.83" +version = "1.0.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdf07d07d6531bfcdbe9b8b739b104610c6508dcc4d63b410585faf338241daf" +checksum = "f61f1b6389c3fe1c316bf8a4dccc90a38208354b330925bce1f74a6c4756eb93" dependencies = [ "cc", "cxxbridge-flags", @@ -1728,9 +1771,9 @@ dependencies = [ [[package]] name = "cxx-build" -version = "1.0.83" +version = "1.0.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2eb5b96ecdc99f72657332953d4d9c50135af1bac34277801cc3937906ebd39" +checksum = "12cee708e8962df2aeb38f594aae5d827c022b6460ac71a7a3e2c3c2aae5a07b" dependencies = [ "cc", "codespan-reporting", @@ -1738,24 +1781,24 @@ dependencies = [ "proc-macro2", "quote", "scratch", - "syn", + "syn 2.0.15", ] [[package]] name = "cxxbridge-flags" -version = "1.0.83" +version = "1.0.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac040a39517fd1674e0f32177648334b0f4074625b5588a64519804ba0553b12" +checksum = "7944172ae7e4068c533afbb984114a56c46e9ccddda550499caa222902c7f7bb" [[package]] name = "cxxbridge-macro" -version = "1.0.83" +version = "1.0.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1362b0ddcfc4eb0a1f57b68bd77dd99f0e826958a96abd0ae9bd092e114ffed6" +checksum = "2345488264226bf682893e25de0769f3360aac9957980ec49361b083ddaa5bc5" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.15", ] [[package]] @@ -1768,7 +1811,7 @@ dependencies = [ "hashbrown 0.12.3", "lock_api", "once_cell", - "parking_lot_core 0.9.5", + "parking_lot_core 0.9.7", ] [[package]] @@ -1787,7 +1830,7 @@ dependencies = [ "anyhow", "async-trait", "collections", - "env_logger", + "env_logger 0.9.3", "gpui", "indoc", "lazy_static", @@ -1871,7 +1914,7 @@ version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" dependencies = [ - "block-buffer 0.10.3", + "block-buffer 0.10.4", "crypto-common", "subtle", ] @@ -1947,9 +1990,9 @@ dependencies = [ [[package]] name = "dotenvy" -version = "0.15.6" +version = "0.15.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03d8c417d7a8cb362e0c37e5d815f5eb7c37f79ff93707329d5a194e42e54ca0" +checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" [[package]] name = "drag_and_drop" @@ -1973,15 +2016,15 @@ dependencies = [ [[package]] name = "dyn-clone" -version = "1.0.9" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f94fa09c2aeea5b8839e414b7b841bf429fd25b9c522116ac97ee87856d88b2" +checksum = "68b0cf012f1230e43cd00ebb729c6bb58707ecfa8ad08b52ef3a4ccd2697fc30" [[package]] name = "editor" version = "0.1.0" dependencies = [ - "aho-corasick", + "aho-corasick 0.7.20", "anyhow", "client", "clock", @@ -1991,8 +2034,8 @@ dependencies = [ "ctor", "db", "drag_and_drop", - "env_logger", - "futures 0.3.25", + "env_logger 0.9.3", + "futures 0.3.28", "fuzzy", "git", "glob", @@ -2010,6 +2053,7 @@ dependencies = [ "pulldown-cmark", "rand 0.8.5", "rpc", + "schemars", "serde", "serde_derive", "settings", @@ -2024,7 +2068,7 @@ dependencies = [ "tree-sitter-html", "tree-sitter-javascript", "tree-sitter-rust", - "tree-sitter-typescript 0.20.2", + "tree-sitter-typescript 0.20.2 (git+https://github.com/tree-sitter/tree-sitter-typescript?rev=5d20856f34315b068c41edaee2ac8a100081d259)", "unindent", "util", "workspace", @@ -2032,15 +2076,15 @@ dependencies = [ [[package]] name = "either" -version = "1.8.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" +checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" [[package]] name = "encoding_rs" -version = "0.8.31" +version = "0.8.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9852635589dc9f9ea1b6fe9f05b50ef208c85c834a562f0c6abb1c475736ec2b" +checksum = "071a31f4ee85403370b58aca746f01041ede6f0da2730960ad001edc2b71b394" dependencies = [ "cfg-if 1.0.0", ] @@ -2058,6 +2102,19 @@ dependencies = [ "termcolor", ] +[[package]] +name = "env_logger" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85cdab6a89accf66733ad5a1693a4dcced6aeff64602b634530dd73c1f3ee9f0" +dependencies = [ + "humantime", + "is-terminal 0.4.7", + "log", + "regex", + "termcolor", +] + [[package]] name = "envy" version = "0.4.2" @@ -2069,9 +2126,9 @@ dependencies = [ [[package]] name = "erased-serde" -version = "0.3.23" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54558e0ba96fbe24280072642eceb9d7d442e32c7ec0ea9e7ecd7b4ea2cf4e11" +checksum = "4f2b0c2380453a92ea8b6c8e5f64ecaafccddde8ceab55ff7a8ac1029f894569" dependencies = [ "serde", ] @@ -2087,6 +2144,17 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "errno" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" +dependencies = [ + "errno-dragonfly", + "libc", + "windows-sys 0.48.0", +] + [[package]] name = "errno-dragonfly" version = "0.1.2" @@ -2109,9 +2177,9 @@ dependencies = [ [[package]] name = "euclid" -version = "0.22.7" +version = "0.22.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b52c2ef4a78da0ba68fbe1fd920627411096d2ac478f7f4c9f3a54ba6705bade" +checksum = "87f253bc5c813ca05792837a0ff4b3a580336b224512d48f7eda1d7dd9210787" dependencies = [ "num-traits", ] @@ -2130,9 +2198,9 @@ checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" [[package]] name = "fastrand" -version = "1.8.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499" +checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" dependencies = [ "instant", ] @@ -2144,7 +2212,7 @@ dependencies = [ "anyhow", "client", "editor", - "futures 0.3.25", + "futures 0.3.28", "gpui", "human_bytes", "isahc", @@ -2168,11 +2236,11 @@ dependencies = [ [[package]] name = "file-per-thread-logger" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21e16290574b39ee41c71aeb90ae960c504ebaf1e2a1c87bd52aa56ed6e1a02f" +checksum = "84f2e425d9790201ba4af4630191feac6dcc98765b118d4d18e91d23c2353866" dependencies = [ - "env_logger", + "env_logger 0.10.0", "log", ] @@ -2182,9 +2250,10 @@ version = "0.1.0" dependencies = [ "ctor", "editor", - "env_logger", + "env_logger 0.9.3", "fuzzy", "gpui", + "language", "menu", "picker", "postage", @@ -2199,14 +2268,14 @@ dependencies = [ [[package]] name = "filetime" -version = "0.2.19" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e884668cd0c7480504233e951174ddc3b382f7c2666e3b7310b5c4e7b0c37f9" +checksum = "5cbc844cecaee9d4443931972e1289c8ff485cb4cc2767cb03ca139ed6885153" dependencies = [ "cfg-if 1.0.0", "libc", - "redox_syscall", - "windows-sys 0.42.0", + "redox_syscall 0.2.16", + "windows-sys 0.48.0", ] [[package]] @@ -2217,12 +2286,12 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" [[package]] name = "flate2" -version = "1.0.25" +version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8a2db397cb1c8772f31494cb8917e48cd1e64f0fa7efac59fbd741a0a8ce841" +checksum = "3b9429470923de8e8cbd4d2dc513535400b4b3fef0319fb5c4e1f520a7bef743" dependencies = [ "crc32fast", - "miniz_oxide 0.6.2", + "miniz_oxide 0.7.1", ] [[package]] @@ -2246,7 +2315,7 @@ dependencies = [ "futures-core", "futures-sink", "pin-project", - "spin 0.9.4", + "spin 0.9.8", ] [[package]] @@ -2343,7 +2412,7 @@ dependencies = [ "async-trait", "collections", "fsevent", - "futures 0.3.25", + "futures 0.3.28", "git2", "gpui", "lazy_static", @@ -2368,8 +2437,8 @@ version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7df62ee66ee2d532ea8d567b5a3f0d03ecd64636b98bad5be1e93dcc918b92aa" dependencies = [ - "io-lifetimes", - "rustix", + "io-lifetimes 0.5.3", + "rustix 0.33.7", "winapi 0.3.9", ] @@ -2422,9 +2491,9 @@ checksum = "3a471a38ef8ed83cd6e40aa59c1ffe17db6855c18e3604d9c4ed8c08ebc28678" [[package]] name = "futures" -version = "0.3.25" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38390104763dc37a5145a53c29c63c1290b5d316d6086ec32c293f6736051bb0" +checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40" dependencies = [ "futures-channel", "futures-core", @@ -2437,9 +2506,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.25" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52ba265a92256105f45b719605a571ffe2d1f0fea3807304b522c1d778f79eed" +checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" dependencies = [ "futures-core", "futures-sink", @@ -2447,15 +2516,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.25" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04909a7a7e4633ae6c4a9ab280aeb86da1236243a77b694a49eacd659a4bd3ac" +checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" [[package]] name = "futures-executor" -version = "0.3.25" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7acc85df6714c176ab5edf386123fafe217be88c0840ec11f199441134a074e2" +checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0" dependencies = [ "futures-core", "futures-task", @@ -2475,15 +2544,15 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.25" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00f5fb52a06bdcadeb54e8d3671f8888a39697dcb0b81b23b55174030427f4eb" +checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" [[package]] name = "futures-lite" -version = "1.12.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7694489acd39452c77daa48516b894c153f192c3578d5a839b62c58099fcbf48" +checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce" dependencies = [ "fastrand", "futures-core", @@ -2496,32 +2565,32 @@ dependencies = [ [[package]] name = "futures-macro" -version = "0.3.25" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdfb8ce053d86b91919aad980c220b1fb8401a9394410e1c289ed7e66b61835d" +checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.15", ] [[package]] name = "futures-sink" -version = "0.3.25" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39c15cf1a4aa79df40f1bb462fb39676d0ad9e366c2a33b590d7c66f4f81fcf9" +checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" [[package]] name = "futures-task" -version = "0.3.25" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ffb393ac5d9a6eaa9d3fdf37ae2776656b706e200c8e16b1bdb227f5198e6ea" +checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" [[package]] name = "futures-util" -version = "0.3.25" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "197676987abd2f9cadff84926f410af1c183608d36641465df73ae8211dc65d6" +checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" dependencies = [ "futures 0.1.31", "futures-channel", @@ -2556,9 +2625,9 @@ dependencies = [ [[package]] name = "generic-array" -version = "0.14.6" +version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", @@ -2577,9 +2646,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.8" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" +checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4" dependencies = [ "cfg-if 1.0.0", "libc", @@ -2607,6 +2676,12 @@ dependencies = [ "stable_deref_trait", ] +[[package]] +name = "gimli" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad0a93d233ebf96623465aad4046a8d3aa4da22d4f4beba5388838c8a434bbb4" + [[package]] name = "git" version = "0.1.0" @@ -2615,7 +2690,7 @@ dependencies = [ "async-trait", "clock", "collections", - "futures 0.3.25", + "futures 0.3.28", "git2", "lazy_static", "log", @@ -2648,11 +2723,11 @@ checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" [[package]] name = "globset" -version = "0.4.9" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a1e17342619edbc21a964c2afbeb6c820c6a2560032872f397bb97ea127bd0a" +checksum = "029d74589adefde59de1a0c4f4732695c32805624aec7b68d91503d4dba79afc" dependencies = [ - "aho-corasick", + "aho-corasick 0.7.20", "bstr", "fnv", "log", @@ -2681,6 +2756,7 @@ dependencies = [ "postage", "settings", "text", + "theme", "util", "workspace", ] @@ -2702,11 +2778,11 @@ dependencies = [ "core-text", "ctor", "dhat", - "env_logger", + "env_logger 0.9.3", "etagere", "font-kit", "foreign-types", - "futures 0.3.25", + "futures 0.3.28", "gpui_macros", "image", "itertools", @@ -2735,11 +2811,11 @@ dependencies = [ "smol", "sqlez", "sum_tree", - "time 0.3.17", + "time 0.3.21", "tiny-skia", "usvg", "util", - "uuid 1.2.2", + "uuid 1.3.2", "waker-fn", ] @@ -2749,16 +2825,16 @@ version = "0.1.0" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] name = "h2" -version = "0.3.15" +version = "0.3.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f9f29bc9dda355256b2916cf526ab02ce0aeaaaf2bad60d65ef3f12f11dd0f4" +checksum = "17f8a914c2987b688368b5138aa05321db91f4090cf26118185672ad588bce21" dependencies = [ - "bytes 1.3.0", + "bytes 1.4.0", "fnv", "futures-core", "futures-sink", @@ -2767,7 +2843,7 @@ dependencies = [ "indexmap", "slab", "tokio", - "tokio-util 0.7.4", + "tokio-util 0.7.8", "tracing", ] @@ -2777,7 +2853,7 @@ version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" dependencies = [ - "ahash", + "ahash 0.7.6", ] [[package]] @@ -2786,7 +2862,16 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" dependencies = [ - "ahash", + "ahash 0.7.6", +] + +[[package]] +name = "hashbrown" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" +dependencies = [ + "ahash 0.8.3", ] [[package]] @@ -2804,9 +2889,9 @@ version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3e372db8e5c0d213e0cd0b9be18be2aca3d44cf2fe30a9d46a65581cd454584" dependencies = [ - "base64", + "base64 0.13.1", "bitflags", - "bytes 1.3.0", + "bytes 1.4.0", "headers-core", "http", "httpdate", @@ -2834,9 +2919,9 @@ dependencies = [ [[package]] name = "heck" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" dependencies = [ "unicode-segmentation", ] @@ -2859,6 +2944,12 @@ dependencies = [ "libc", ] +[[package]] +name = "hermit-abi" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" + [[package]] name = "hex" version = "0.4.3" @@ -2895,11 +2986,11 @@ dependencies = [ [[package]] name = "http" -version = "0.2.8" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399" +checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" dependencies = [ - "bytes 1.3.0", + "bytes 1.4.0", "fnv", "itoa", ] @@ -2910,7 +3001,7 @@ version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" dependencies = [ - "bytes 1.3.0", + "bytes 1.4.0", "http", "pin-project-lite 0.2.9", ] @@ -2935,9 +3026,9 @@ checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" [[package]] name = "human_bytes" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39b528196c838e8b3da8b665e08c30958a6f2ede91d79f2ffcd0d4664b9c64eb" +checksum = "27e2b089f28ad15597b48d8c0a8fe94eeb1c1cb26ca99b6f66ac9582ae10c5e6" [[package]] name = "humantime" @@ -2947,11 +3038,11 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "hyper" -version = "0.14.23" +version = "0.14.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "034711faac9d2166cb1baf1a2fb0b60b1f277f8492fd72176c17f3515e1abd3c" +checksum = "ab302d72a6f11a3b910431ff93aae7e773078c769f0a3ef15fb9ec692ed147d4" dependencies = [ - "bytes 1.3.0", + "bytes 1.4.0", "futures-channel", "futures-core", "futures-util", @@ -2987,7 +3078,7 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" dependencies = [ - "bytes 1.3.0", + "bytes 1.4.0", "hyper", "native-tls", "tokio", @@ -2996,16 +3087,16 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.53" +version = "0.1.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64c122667b287044802d6ce17ee2ddf13207ed924c712de9a66a5814d5b64765" +checksum = "0722cd7114b7de04316e7ea5456a0bbb20e4adb46fd27a3697adb812cff0f37c" dependencies = [ "android_system_properties", "core-foundation-sys", "iana-time-zone-haiku", "js-sys", "wasm-bindgen", - "winapi 0.3.9", + "windows", ] [[package]] @@ -3030,11 +3121,10 @@ dependencies = [ [[package]] name = "ignore" -version = "0.4.18" +version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "713f1b139373f96a2e0ce3ac931cd01ee973c3c5dd7c40c0c2efe96ad2b6751d" +checksum = "dbe7873dab538a9a44ad79ede1faf5f30d49f9a5c883ddbab48bce81b64b7492" dependencies = [ - "crossbeam-utils 0.8.14", "globset", "lazy_static", "log", @@ -3067,9 +3157,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "1.9.2" +version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" dependencies = [ "autocfg 1.1.0", "hashbrown 0.12.3", @@ -3078,9 +3168,9 @@ dependencies = [ [[package]] name = "indoc" -version = "1.0.7" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adab1eaa3408fb7f0c777a73e7465fd5656136fc93b670eb6df3c88c2c1344e3" +checksum = "bfa799dd5ed20a7e349f3b4639aa80d74549c81716d9ec4f994c9b5815598306" [[package]] name = "install_cli" @@ -3108,7 +3198,7 @@ version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0c937cc9891c12eaa8c63ad347e4a288364b1328b924886970b47a14ab8f8f8" dependencies = [ - "io-lifetimes", + "io-lifetimes 0.5.3", "winapi 0.3.9", ] @@ -3122,6 +3212,17 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "io-lifetimes" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c66c74d2ae7e79a5a8f7ac924adbe38ee42a859c6539ad869eb51f0b52dc220" +dependencies = [ + "hermit-abi 0.3.1", + "libc", + "windows-sys 0.48.0", +] + [[package]] name = "iovec" version = "0.1.4" @@ -3152,9 +3253,9 @@ dependencies = [ [[package]] name = "ipnet" -version = "2.5.1" +version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f88c5561171189e69df9d98bcf18fd5f9558300f7ea7b801eb8a0fd748bd8745" +checksum = "12b6ee2129af8d4fb011108c73d99a1b83a85977f23b82460c0ae2e25bb4b57f" [[package]] name = "is-terminal" @@ -3163,11 +3264,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c89a757e762896bdbdfadf2860d0f8b0cea5e363d8cf3e7bdfeb63d1d976352" dependencies = [ "hermit-abi 0.2.6", - "io-lifetimes", - "rustix", + "io-lifetimes 0.5.3", + "rustix 0.33.7", "winapi 0.3.9", ] +[[package]] +name = "is-terminal" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adcf93614601c8129ddf72e2d5633df827ba6551541c6d8c59520a371475be1f" +dependencies = [ + "hermit-abi 0.3.1", + "io-lifetimes 1.0.10", + "rustix 0.37.19", + "windows-sys 0.48.0", +] + [[package]] name = "isahc" version = "1.7.2" @@ -3176,7 +3289,7 @@ checksum = "334e04b4d781f436dc315cb1e7515bd96826426345d498149e4bde36b67f8ee9" dependencies = [ "async-channel", "castaway", - "crossbeam-utils 0.8.14", + "crossbeam-utils 0.8.15", "curl", "curl-sys", "encoding_rs", @@ -3206,9 +3319,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.4" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc" +checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" [[package]] name = "ittapi-rs" @@ -3221,9 +3334,9 @@ dependencies = [ [[package]] name = "jobserver" -version = "0.1.25" +version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "068b1ee6743e4d11fb9c6a1e6064b3693a1b600e7f5f5988047d98b3dc9fb90b" +checksum = "936cfd212a0155903bcbc060e316fb6cc7cbf2e1907329391ebadc1fe0ce77c2" dependencies = [ "libc", ] @@ -3238,6 +3351,8 @@ dependencies = [ "editor", "gpui", "log", + "schemars", + "serde", "settings", "shellexpand", "util", @@ -3255,9 +3370,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.60" +version = "0.3.62" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47" +checksum = "68c16e1bfd491478ab155fd8b4896b86f9ede344949b641e61501e07c2b8b4d5" dependencies = [ "wasm-bindgen", ] @@ -3274,7 +3389,7 @@ version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6204285f77fe7d9784db3fdc449ecce1a0114927a51d5a41c4c7a292011c015f" dependencies = [ - "base64", + "base64 0.13.1", "crypto-common", "digest 0.10.6", "hmac 0.12.1", @@ -3322,11 +3437,12 @@ dependencies = [ "clock", "collections", "ctor", - "env_logger", + "env_logger 0.9.3", "fs", - "futures 0.3.25", + "futures 0.3.28", "fuzzy", "git", + "glob", "gpui", "indoc", "lazy_static", @@ -3337,6 +3453,7 @@ dependencies = [ "rand 0.8.5", "regex", "rpc", + "schemars", "serde", "serde_derive", "serde_json", @@ -3356,7 +3473,7 @@ dependencies = [ "tree-sitter-python", "tree-sitter-ruby", "tree-sitter-rust", - "tree-sitter-typescript 0.20.1", + "tree-sitter-typescript 0.20.2 (registry+https://github.com/rust-lang/crates.io-index)", "unicase", "unindent", "util", @@ -3402,15 +3519,15 @@ checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" [[package]] name = "libc" -version = "0.2.138" +version = "0.2.144" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db6d7e329c562c5dfab7a46a2afabc8b987ab9a4834c9d1ca04dc54c1546cef8" +checksum = "2b00cc1c228a6782d0f076e7b232802e0c5689d41bb5df366f2a6b6621cfdfe1" [[package]] name = "libgit2-sys" -version = "0.14.0+1.5.0" +version = "0.14.2+1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47a00859c70c8a4f7218e6d1cc32875c4b55f6799445b842b0d8ed5e4c3d959b" +checksum = "7f3d95f6b51075fe9810a7ae22c7095f12b98005ab364d8544797a825ce946a4" dependencies = [ "cc", "libc", @@ -3457,9 +3574,9 @@ dependencies = [ [[package]] name = "libz-sys" -version = "1.1.8" +version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9702761c3935f8cc2f101793272e202c72b99da8f4224a19ddcf1279a6450bbf" +checksum = "56ee889ecc9568871456d42f603d6a0ce59ff328d291063a45cbdf0036baf6db" dependencies = [ "cc", "libc", @@ -3478,9 +3595,9 @@ dependencies = [ [[package]] name = "link-cplusplus" -version = "1.0.7" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9272ab7b96c9046fbc5bc56c06c117cb639fe2d509df0c421cad82d2915cf369" +checksum = "ecd207c9c713c34f95a097a5b029ac2ce6010530c7b49d7fea24d977dede04f5" dependencies = [ "cc", ] @@ -3497,6 +3614,12 @@ version = "0.0.42" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5284f00d480e1c39af34e72f8ad60b94f47007e3481cd3b731c1d67190ddc7b7" +[[package]] +name = "linux-raw-sys" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ece97ea872ece730aed82664c424eb4c8291e1ff2480247ccf7409044bc6479f" + [[package]] name = "lipsum" version = "0.8.2" @@ -3516,13 +3639,13 @@ dependencies = [ "async-trait", "block", "byteorder", - "bytes 1.3.0", + "bytes 1.4.0", "cocoa", "collections", "core-foundation", "core-graphics", "foreign-types", - "futures 0.3.25", + "futures 0.3.28", "gpui", "hmac 0.12.1", "jwt", @@ -3547,7 +3670,7 @@ version = "0.1.0" dependencies = [ "anyhow", "async-trait", - "futures 0.3.25", + "futures 0.3.28", "hmac 0.12.1", "jwt", "log", @@ -3589,8 +3712,8 @@ dependencies = [ "async-pipe", "collections", "ctor", - "env_logger", - "futures 0.3.25", + "env_logger 0.9.3", + "futures 0.3.28", "gpui", "log", "lsp-types", @@ -3624,7 +3747,7 @@ dependencies = [ "anyhow", "collections", "editor", - "futures 0.3.25", + "futures 0.3.28", "gpui", "language", "lsp", @@ -3666,9 +3789,9 @@ dependencies = [ [[package]] name = "matches" -version = "0.1.9" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" +checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" [[package]] name = "matchit" @@ -3704,7 +3827,7 @@ dependencies = [ "anyhow", "bindgen", "block", - "bytes 1.3.0", + "bytes 1.4.0", "core-foundation", "foreign-types", "metal", @@ -3746,9 +3869,9 @@ dependencies = [ [[package]] name = "memoffset" -version = "0.7.1" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" +checksum = "d61c719bcfbcf5d62b3a09efa6088de8c54bc0bfcd3ea7ae39fcc186108b8de1" dependencies = [ "autocfg 1.1.0", ] @@ -3776,9 +3899,9 @@ dependencies = [ [[package]] name = "mime" -version = "0.3.16" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" [[package]] name = "minimal-lexical" @@ -3807,18 +3930,18 @@ dependencies = [ [[package]] name = "miniz_oxide" -version = "0.5.4" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96590ba8f175222643a85693f33d26e9c8a015f599c216509b1a6894af675d34" +checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa" dependencies = [ "adler", ] [[package]] name = "miniz_oxide" -version = "0.6.2" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa" +checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" dependencies = [ "adler", ] @@ -3854,14 +3977,14 @@ dependencies = [ [[package]] name = "mio" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5d732bc30207a6423068df043e3d02e0735b155ad7ce1a6f76fe2baa5b158de" +checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9" dependencies = [ "libc", "log", "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys 0.42.0", + "windows-sys 0.45.0", ] [[package]] @@ -3990,7 +4113,7 @@ dependencies = [ "anyhow", "async-compression", "async-tar", - "futures 0.3.25", + "futures 0.3.28", "gpui", "parking_lot 0.11.2", "serde", @@ -4002,9 +4125,9 @@ dependencies = [ [[package]] name = "nom" -version = "7.1.1" +version = "7.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8903e5a29a317527874d0402f867152a3d21c908bb0b933e416c65e301d4c36" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" dependencies = [ "memchr", "minimal-lexical", @@ -4021,9 +4144,9 @@ dependencies = [ [[package]] name = "ntapi" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc51db7b362b205941f71232e56c625156eb9a929f8cf74a428fd5bc094a4afc" +checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4" dependencies = [ "winapi 0.3.9", ] @@ -4111,11 +4234,11 @@ dependencies = [ [[package]] name = "num_cpus" -version = "1.14.0" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6058e64324c71e02bc2b150e4f3bc8286db6c83092132ffa3f6b1eab0f9def5" +checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" dependencies = [ - "hermit-abi 0.1.19", + "hermit-abi 0.2.6", "libc", ] @@ -4125,13 +4248,13 @@ version = "0.5.0" source = "git+https://github.com/KillTheMule/nvim-rs?branch=master#d701c2790dcb2579f8f4d7003ba30e2100a7d25b" dependencies = [ "async-trait", - "futures 0.3.25", + "futures 0.3.28", "log", "parity-tokio-ipc", "rmp", "rmpv", "tokio", - "tokio-util 0.7.4", + "tokio-util 0.7.8", ] [[package]] @@ -4167,18 +4290,18 @@ dependencies = [ [[package]] name = "object" -version = "0.29.0" +version = "0.30.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21158b2c33aa6d4561f1c0a6ea283ca92bc54802a93b263e910746d679a7eb53" +checksum = "ea86265d3d3dcb6a27fc51bd29a4bf387fae9d2986b823079d4986af253eb439" dependencies = [ "memchr", ] [[package]] name = "once_cell" -version = "1.16.0" +version = "1.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860" +checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" [[package]] name = "opaque-debug" @@ -4188,9 +4311,9 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" [[package]] name = "openssl" -version = "0.10.43" +version = "0.10.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "020433887e44c27ff16365eaa2d380547a94544ad509aff6eb5b6e3e0b27b376" +checksum = "01b8574602df80f7b85fdfc5392fa884a4e3b3f4f35402c070ab34c3d3f78d56" dependencies = [ "bitflags", "cfg-if 1.0.0", @@ -4203,13 +4326,13 @@ dependencies = [ [[package]] name = "openssl-macros" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b501e44f11665960c7e7fcf062c7d96a14ade4aa98116c004b2e37b5be7d736c" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.15", ] [[package]] @@ -4220,11 +4343,10 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.78" +version = "0.9.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07d5c8cb6e57b3a3612064d7b18b117912b4ce70955c2504d4b741c9e244b132" +checksum = "8e17f59264b2809d77ae94f0e1ebabc434773f370d6ca667bd223ea10e06cc7e" dependencies = [ - "autocfg 1.1.0", "cc", "libc", "pkg-config", @@ -4242,15 +4364,15 @@ dependencies = [ [[package]] name = "os_str_bytes" -version = "6.4.1" +version = "6.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b7820b9daea5457c9f21c69448905d723fbd21136ccf521748f23fd49e723ee" +checksum = "ceedf44fb00f2d1984b0bc98102627ce622e083e49a5bacdb3e514fa4238e267" [[package]] name = "ouroboros" -version = "0.15.5" +version = "0.15.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfbb50b356159620db6ac971c6d5c9ab788c9cc38a6f49619fca2a27acb062ca" +checksum = "e1358bd1558bd2a083fed428ffeda486fbfb323e698cdda7794259d592ca72db" dependencies = [ "aliasable", "ouroboros_macro", @@ -4258,15 +4380,15 @@ dependencies = [ [[package]] name = "ouroboros_macro" -version = "0.15.5" +version = "0.15.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a0d9d1a6191c4f391f87219d1ea42b23f09ee84d64763cd05ee6ea88d9f384d" +checksum = "5f7d21ccd03305a674437ee1248f3ab5d4b1db095cf1caf49f1713ddf61956b7" dependencies = [ "Inflector", "proc-macro-error", "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -4283,6 +4405,7 @@ dependencies = [ "settings", "smol", "text", + "theme", "workspace", ] @@ -4307,7 +4430,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9981e32fb75e004cc148f5fb70342f393830e0a4aa62e3cc93b50976218d42b6" dependencies = [ - "futures 0.3.25", + "futures 0.3.28", "libc", "log", "rand 0.7.3", @@ -4317,9 +4440,9 @@ dependencies = [ [[package]] name = "parking" -version = "2.0.0" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "427c3892f9e783d91cc128285287e70a59e206ca452770ece88a76f7a3eddd72" +checksum = "14f2252c834a40ed9bb5422029649578e63aa341ac401f74e719dd1afda8394e" [[package]] name = "parking_lot" @@ -4329,7 +4452,7 @@ checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" dependencies = [ "instant", "lock_api", - "parking_lot_core 0.8.5", + "parking_lot_core 0.8.6", ] [[package]] @@ -4339,34 +4462,34 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" dependencies = [ "lock_api", - "parking_lot_core 0.9.5", + "parking_lot_core 0.9.7", ] [[package]] name = "parking_lot_core" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216" +checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc" dependencies = [ "cfg-if 1.0.0", "instant", "libc", - "redox_syscall", + "redox_syscall 0.2.16", "smallvec", "winapi 0.3.9", ] [[package]] name = "parking_lot_core" -version = "0.9.5" +version = "0.9.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ff9f3fef3968a3ec5945535ed654cb38ff72d7495a25619e2247fb15a2ed9ba" +checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521" dependencies = [ "cfg-if 1.0.0", "libc", - "redox_syscall", + "redox_syscall 0.2.16", "smallvec", - "windows-sys 0.42.0", + "windows-sys 0.45.0", ] [[package]] @@ -4382,9 +4505,9 @@ dependencies = [ [[package]] name = "paste" -version = "1.0.9" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1de2e551fb905ac83f73f7aedf2f0cb4a0da7e35efa24a202a936269f1f18e1" +checksum = "9f746c4065a8fa3fe23974dd82f15431cc8d40779821001404d10d2e79ca7d79" [[package]] name = "pathfinder_color" @@ -4435,7 +4558,7 @@ version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd56cbd21fea48d0c440b41cd69c589faacade08c992d9a54e471b79d0fd13eb" dependencies = [ - "base64", + "base64 0.13.1", "once_cell", "regex", ] @@ -4448,9 +4571,9 @@ checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" [[package]] name = "pest" -version = "2.5.1" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc8bed3549e0f9b0a2a78bf7c0018237a2cdf085eecbbc048e52612438e4e9d0" +checksum = "e68e84bfb01f0507134eac1e9b410a12ba379d064eab48c50ba4ce329a527b70" dependencies = [ "thiserror", "ucd-trie", @@ -4458,9 +4581,9 @@ dependencies = [ [[package]] name = "petgraph" -version = "0.6.2" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6d5014253a1331579ce62aa67443b4a658c5e7dd03d4bc6d302b94474888143" +checksum = "4dd7d28ee937e54fe3080c91faa1c3a46c06de6252988a7f4592ba2310ef22a4" dependencies = [ "fixedbitset", "indexmap", @@ -4472,7 +4595,7 @@ version = "0.1.0" dependencies = [ "ctor", "editor", - "env_logger", + "env_logger 0.9.3", "gpui", "menu", "parking_lot 0.11.2", @@ -4506,7 +4629,7 @@ checksum = "069bdb1e05adc7a8990dce9cc75370895fbe4e3d58b9b73bf1aee56359344a55" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -4529,22 +4652,22 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pkg-config" -version = "0.3.26" +version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" +checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" [[package]] name = "plist" -version = "1.3.1" +version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd39bc6cdc9355ad1dc5eeedefee696bb35c34caf21768741e81826c0bbd7225" +checksum = "9bd9647b268a3d3e14ff09c23201133a62589c658db02bb7388c7246aafe0590" dependencies = [ - "base64", + "base64 0.21.0", "indexmap", "line-wrap", + "quick-xml", "serde", - "time 0.3.17", - "xml-rs", + "time 0.3.21", ] [[package]] @@ -4566,7 +4689,7 @@ dependencies = [ "quote", "serde", "serde_derive", - "syn", + "syn 1.0.109", ] [[package]] @@ -4599,16 +4722,18 @@ dependencies = [ [[package]] name = "polling" -version = "2.5.1" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "166ca89eb77fd403230b9c156612965a81e094ec6ec3aa13663d4c8b113fa748" +checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce" dependencies = [ "autocfg 1.1.0", + "bitflags", "cfg-if 1.0.0", + "concurrent-queue", "libc", "log", - "wepoll-ffi", - "windows-sys 0.42.0", + "pin-project-lite 0.2.9", + "windows-sys 0.48.0", ] [[package]] @@ -4625,7 +4750,7 @@ checksum = "af3fb618632874fb76937c2361a7f22afd393c982a2165595407edc75b06d3c1" dependencies = [ "atomic", "crossbeam-queue", - "futures 0.3.25", + "futures 0.3.28", "log", "parking_lot 0.12.1", "pin-project", @@ -4670,7 +4795,7 @@ dependencies = [ "proc-macro-error-attr", "proc-macro2", "quote", - "syn", + "syn 1.0.109", "version_check", ] @@ -4687,9 +4812,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.47" +version = "1.0.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725" +checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435" dependencies = [ "unicode-ident", ] @@ -4709,7 +4834,7 @@ dependencies = [ name = "project" version = "0.1.0" dependencies = [ - "aho-corasick", + "aho-corasick 0.7.20", "anyhow", "async-trait", "backtrace", @@ -4719,10 +4844,10 @@ dependencies = [ "copilot", "ctor", "db", - "env_logger", + "env_logger 0.9.3", "fs", "fsevent", - "futures 0.3.25", + "futures 0.3.28", "fuzzy", "git", "git2", @@ -4740,6 +4865,7 @@ dependencies = [ "rand 0.8.5", "regex", "rpc", + "schemars", "serde", "serde_derive", "serde_json", @@ -4761,11 +4887,13 @@ dependencies = [ name = "project_panel" version = "0.1.0" dependencies = [ + "client", "context_menu", "drag_and_drop", "editor", - "futures 0.3.25", + "futures 0.3.28", "gpui", + "language", "menu", "postage", "project", @@ -4783,7 +4911,7 @@ version = "0.1.0" dependencies = [ "anyhow", "editor", - "futures 0.3.25", + "futures 0.3.28", "fuzzy", "gpui", "language", @@ -4795,6 +4923,7 @@ dependencies = [ "settings", "smol", "text", + "theme", "util", "workspace", ] @@ -4820,7 +4949,7 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "de5e2533f59d08fcf364fd374ebda0692a70bd6d7e66ef97f306f45c6c5d8020" dependencies = [ - "bytes 1.3.0", + "bytes 1.4.0", "prost-derive 0.8.0", ] @@ -4830,7 +4959,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "444879275cb4fd84958b1a1d5420d15e6fcf7c235fe47f053c9c2a80aceb6001" dependencies = [ - "bytes 1.3.0", + "bytes 1.4.0", "prost-derive 0.9.0", ] @@ -4840,7 +4969,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62941722fb675d463659e49c4f3fe1fe792ff24fe5bbaa9c08cd3b98a1c354f5" dependencies = [ - "bytes 1.3.0", + "bytes 1.4.0", "heck 0.3.3", "itertools", "lazy_static", @@ -4864,7 +4993,7 @@ dependencies = [ "itertools", "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -4877,7 +5006,7 @@ dependencies = [ "itertools", "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -4886,7 +5015,7 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "603bbd6394701d13f3f25aada59c7de9d35a6a5887cfc156181234a44002771b" dependencies = [ - "bytes 1.3.0", + "bytes 1.4.0", "prost 0.8.0", ] @@ -4896,7 +5025,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "534b7a0e836e3c482d2693070f982e39e7611da9695d4d1f5a4b186b51faef0a" dependencies = [ - "bytes 1.3.0", + "bytes 1.4.0", "prost 0.9.0", ] @@ -4932,7 +5061,7 @@ checksum = "16b845dbfca988fa33db069c0e230574d15a3088f147a87b64c7589eb662c9ac" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -4946,11 +5075,20 @@ dependencies = [ "unicase", ] +[[package]] +name = "quick-xml" +version = "0.28.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce5e73202a820a31f8a0ee32ada5e21029c81fd9e3ebf668a40832e4219d9d1" +dependencies = [ + "memchr", +] + [[package]] name = "quote" -version = "1.0.21" +version = "1.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" +checksum = "8f4f29d145265ec1c483c7c654450edde0bfe043d3938d6972630663356d9500" dependencies = [ "proc-macro2", ] @@ -5042,7 +5180,7 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.8", + "getrandom 0.2.9", ] [[package]] @@ -5056,24 +5194,23 @@ dependencies = [ [[package]] name = "rayon" -version = "1.6.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e060280438193c554f654141c9ea9417886713b7acd75974c85b18a69a88e0b" +checksum = "1d2df5196e37bcc87abebc0053e20787d73847bb33134a69841207dd0a47f03b" dependencies = [ - "crossbeam-deque", "either", "rayon-core", ] [[package]] name = "rayon-core" -version = "1.10.1" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cac410af5d00ab6884528b4ab69d1e8e146e8d471201800fa1b4524126de6ad3" +checksum = "4b8f95bd6966f5c87776639160a66bd8ab9895d9d4ab01ddba9fc60661aebe8d" dependencies = [ - "crossbeam-channel 0.5.6", + "crossbeam-channel 0.5.8", "crossbeam-deque", - "crossbeam-utils 0.8.14", + "crossbeam-utils 0.8.15", "num_cpus", ] @@ -5107,6 +5244,7 @@ dependencies = [ "settings", "smol", "text", + "theme", "util", "workspace", ] @@ -5120,14 +5258,23 @@ dependencies = [ "bitflags", ] +[[package]] +name = "redox_syscall" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +dependencies = [ + "bitflags", +] + [[package]] name = "redox_users" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" dependencies = [ - "getrandom 0.2.8", - "redox_syscall", + "getrandom 0.2.9", + "redox_syscall 0.2.16", "thiserror", ] @@ -5145,13 +5292,13 @@ dependencies = [ [[package]] name = "regex" -version = "1.7.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e076559ef8e241f2ae3479e36f97bd5741c0330689e217ad51ce2c76808b868a" +checksum = "af83e617f331cc6ae2da5443c602dfa5af81e517212d9d611a5b3ba1777b5370" dependencies = [ - "aho-corasick", + "aho-corasick 1.0.1", "memchr", - "regex-syntax", + "regex-syntax 0.7.1", ] [[package]] @@ -5160,14 +5307,20 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" dependencies = [ - "regex-syntax", + "regex-syntax 0.6.29", ] [[package]] name = "regex-syntax" -version = "0.6.28" +version = "0.6.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + +[[package]] +name = "regex-syntax" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5996294f19bd3aae0453a862ad728f60e6600695733dd5df01da90c54363a3c" [[package]] name = "region" @@ -5192,21 +5345,21 @@ dependencies = [ [[package]] name = "rend" -version = "0.3.6" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79af64b4b6362ffba04eef3a4e10829718a4896dac19daa741851c86781edf95" +checksum = "581008d2099240d37fb08d77ad713bcaec2c4d89d50b5b21a8bb1996bbab68ab" dependencies = [ "bytecheck", ] [[package]] name = "reqwest" -version = "0.11.13" +version = "0.11.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68cc60575865c7831548863cc02356512e3f1dc2f3f82cb837d7fc4cc8f3c97c" +checksum = "13293b639a097af28fc8a90f22add145a9c954e49d77da06263d58cf44d5fb91" dependencies = [ - "base64", - "bytes 1.3.0", + "base64 0.21.0", + "bytes 1.4.0", "encoding_rs", "futures-core", "futures-util", @@ -5254,9 +5407,9 @@ dependencies = [ [[package]] name = "rgb" -version = "0.8.34" +version = "0.8.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3603b7d71ca82644f79b5a06d1220e9a58ede60bd32255f698cb1af8838b8db3" +checksum = "20ec2d3e3fc7a92ced357df9cebd5a10b6fb2aa1ee797bf7e9ce2f17dffc8f59" dependencies = [ "bytemuck", ] @@ -5278,9 +5431,9 @@ dependencies = [ [[package]] name = "rkyv" -version = "0.7.39" +version = "0.7.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cec2b3485b07d96ddfd3134767b8a447b45ea4eb91448d0a35180ec0ffd5ed15" +checksum = "21499ed91807f07ae081880aabb2ccc0235e9d88011867d984525e9a4c3cfa3e" dependencies = [ "bytecheck", "hashbrown 0.12.3", @@ -5292,13 +5445,13 @@ dependencies = [ [[package]] name = "rkyv_derive" -version = "0.7.39" +version = "0.7.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6eaedadc88b53e36dd32d940ed21ae4d850d5916f2581526921f553a72ac34c4" +checksum = "ac1c672430eb41556291981f45ca900a0239ad007242d1cb4b4167af842db666" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -5352,12 +5505,12 @@ dependencies = [ "anyhow", "async-lock", "async-tungstenite", - "base64", + "base64 0.13.1", "clock", "collections", "ctor", - "env_logger", - "futures 0.3.25", + "env_logger 0.9.3", + "futures 0.3.28", "gpui", "parking_lot 0.11.2", "prost 0.8.0", @@ -5396,9 +5549,9 @@ dependencies = [ [[package]] name = "rust-embed" -version = "6.4.2" +version = "6.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "283ffe2f866869428c92e0d61c2f35dfb4355293cdfdc48f49e895c15f1333d1" +checksum = "1b68543d5527e158213414a92832d2aab11a84d2571a5eb021ebe22c43aab066" dependencies = [ "rust-embed-impl", "rust-embed-utils", @@ -5407,22 +5560,22 @@ dependencies = [ [[package]] name = "rust-embed-impl" -version = "6.3.1" +version = "6.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31ab23d42d71fb9be1b643fe6765d292c5e14d46912d13f3ae2815ca048ea04d" +checksum = "4d4e0f0ced47ded9a68374ac145edd65a6c1fa13a96447b873660b2a568a0fd7" dependencies = [ "proc-macro2", "quote", "rust-embed-utils", - "syn", + "syn 1.0.109", "walkdir", ] [[package]] name = "rust-embed-utils" -version = "7.3.0" +version = "7.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1669d81dfabd1b5f8e2856b8bbe146c6192b0ba22162edc738ac0a5de18f054" +checksum = "512b0ab6853f7e14e3c8754acb43d6f748bb9ced66aa5915a6553ac8213f7731" dependencies = [ "globset", "sha2 0.10.6", @@ -5431,15 +5584,15 @@ dependencies = [ [[package]] name = "rust_decimal" -version = "1.27.0" +version = "1.29.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33c321ee4e17d2b7abe12b5d20c1231db708dd36185c8a21e9de5fed6da4dbe9" +checksum = "26bd36b60561ee1fb5ec2817f198b6fd09fa571c897a5e86d1487cfc2b096dfc" dependencies = [ "arrayvec 0.7.2", "borsh", "bytecheck", "byteorder", - "bytes 1.3.0", + "bytes 1.4.0", "num-traits", "rand 0.8.5", "rkyv", @@ -5449,9 +5602,9 @@ dependencies = [ [[package]] name = "rustc-demangle" -version = "0.1.21" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342" +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" [[package]] name = "rustc-hash" @@ -5475,22 +5628,36 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "938a344304321a9da4973b9ff4f9f8db9caf4597dfd9dda6a60b523340a0fff0" dependencies = [ "bitflags", - "errno", - "io-lifetimes", + "errno 0.2.8", + "io-lifetimes 0.5.3", "itoa", "libc", - "linux-raw-sys", + "linux-raw-sys 0.0.42", "once_cell", "winapi 0.3.9", ] +[[package]] +name = "rustix" +version = "0.37.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acf8729d8542766f1b2cf77eb034d52f40d375bb8b615d0b147089946e16613d" +dependencies = [ + "bitflags", + "errno 0.3.1", + "io-lifetimes 1.0.10", + "libc", + "linux-raw-sys 0.3.7", + "windows-sys 0.48.0", +] + [[package]] name = "rustls" version = "0.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "35edb675feee39aec9c99fa5ff985081995a06d594114ae14cbe797ad7b7a6d7" dependencies = [ - "base64", + "base64 0.13.1", "log", "ring", "sct 0.6.1", @@ -5499,9 +5666,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.20.7" +version = "0.20.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "539a2bfe908f471bfa933876bd1eb6a19cf2176d375f82ef7f99530a40e48c2c" +checksum = "fff78fc74d175294f4e83b28343315ffcfb114b156f0185e9741cb5570f50e2f" dependencies = [ "log", "ring", @@ -5511,18 +5678,18 @@ dependencies = [ [[package]] name = "rustls-pemfile" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0864aeff53f8c05aa08d86e5ef839d3dfcf07aeba2db32f12db0ef716e87bd55" +checksum = "d194b56d58803a43635bdc398cd17e383d6f71f9182b9a192c127ca42494a59b" dependencies = [ - "base64", + "base64 0.21.0", ] [[package]] name = "rustversion" -version = "1.0.9" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97477e48b4cf8603ad5f7aaf897467cf42ab4218a38ef76fb14c2d6773a6d6a8" +checksum = "4f3208ce4d8448b3f3e7d168a73f5e0c43a61e32930de3bceeccedb388b6bf06" [[package]] name = "rustybuzz" @@ -5542,9 +5709,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.11" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" +checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" [[package]] name = "safe_arch" @@ -5581,19 +5748,18 @@ dependencies = [ [[package]] name = "schannel" -version = "0.1.20" +version = "0.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88d6731146462ea25d9244b2ed5fd1d716d25c52e4d54aa4fb0f3c4e9854dbe2" +checksum = "713cfb06c7059f3588fb8044c0fad1d09e3c01d225e25b9220dbfdcf16dbb1b3" dependencies = [ - "lazy_static", - "windows-sys 0.36.1", + "windows-sys 0.42.0", ] [[package]] name = "schemars" -version = "0.8.11" +version = "0.8.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a5fb6c61f29e723026dc8e923d94c694313212abbecbbe5f55a7748eec5b307" +checksum = "02c613288622e5f0c3fdc5dbd4db1c5fbe752746b1d1a56a0630b78fd00de44f" dependencies = [ "dyn-clone", "schemars_derive", @@ -5603,14 +5769,14 @@ dependencies = [ [[package]] name = "schemars_derive" -version = "0.8.11" +version = "0.8.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f188d036977451159430f3b8dc82ec76364a42b7e289c2b18a9a18f4470058e9" +checksum = "109da1e6b197438deb6db99952990c7f959572794b80ff93707d55a232545e7c" dependencies = [ "proc-macro2", "quote", "serde_derive_internals", - "syn", + "syn 1.0.109", ] [[package]] @@ -5627,9 +5793,9 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" [[package]] name = "scratch" -version = "1.0.2" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8132065adcfd6e02db789d9285a0deb2f3fcb04002865ab67d5fb103533898" +checksum = "1792db035ce95be60c3f8853017b3999209281c24e2ba5bc8e59bf97a0c590c1" [[package]] name = "scrypt" @@ -5673,7 +5839,7 @@ dependencies = [ "async-stream", "async-trait", "chrono", - "futures 0.3.25", + "futures 0.3.28", "futures-util", "log", "ouroboros", @@ -5686,10 +5852,10 @@ dependencies = [ "serde_json", "sqlx", "thiserror", - "time 0.3.17", + "time 0.3.21", "tracing", "url", - "uuid 1.2.2", + "uuid 1.3.2", ] [[package]] @@ -5701,7 +5867,7 @@ dependencies = [ "heck 0.3.3", "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -5714,8 +5880,8 @@ dependencies = [ "rust_decimal", "sea-query-derive", "serde_json", - "time 0.3.17", - "uuid 1.2.2", + "time 0.3.21", + "uuid 1.3.2", ] [[package]] @@ -5729,8 +5895,8 @@ dependencies = [ "sea-query", "serde_json", "sqlx", - "time 0.3.17", - "uuid 1.2.2", + "time 0.3.21", + "uuid 1.3.2", ] [[package]] @@ -5742,7 +5908,7 @@ dependencies = [ "heck 0.3.3", "proc-macro2", "quote", - "syn", + "syn 1.0.109", "thiserror", ] @@ -5765,7 +5931,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn", + "syn 1.0.109", ] [[package]] @@ -5779,9 +5945,10 @@ name = "search" version = "0.1.0" dependencies = [ "anyhow", + "client", "collections", "editor", - "futures 0.3.25", + "futures 0.3.28", "glob", "gpui", "language", @@ -5803,9 +5970,9 @@ dependencies = [ [[package]] name = "security-framework" -version = "2.7.0" +version = "2.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bc1bb97804af6631813c55739f771071e0f2ed33ee20b68c86ec505d906356c" +checksum = "a332be01508d814fed64bf28f798a146d73792121129962fdf335bb3c49a4254" dependencies = [ "bitflags", "core-foundation", @@ -5816,9 +5983,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.6.1" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0160a13a177a45bfb43ce71c01580998474f556ad854dcbca936dd2841a5c556" +checksum = "31c9bb296072e961fcbd8853511dd39c2d8be2deb1e17c6860b1d30732b323b4" dependencies = [ "core-foundation-sys", "libc", @@ -5850,22 +6017,22 @@ checksum = "5a9f47faea3cad316faa914d013d24f471cd90bfca1a0c70f05a3f42c6441e99" [[package]] name = "serde" -version = "1.0.148" +version = "1.0.162" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e53f64bb4ba0191d6d0676e1b141ca55047d83b74f5607e6d8eb88126c52c2dc" +checksum = "71b2f6e1ab5c2b98c05f0f35b236b22e8df7ead6ffbf51d7808da7f8817e7ab6" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.148" +version = "1.0.162" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a55492425aa53521babf6137309e7d34c20bbfbbfcfe2c7f3a047fd1f6b92c0c" +checksum = "a2a0814352fd64b58489904a44ea8d90cb1a91dcb6b4f5ebabc32c8318e93cb6" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.15", ] [[package]] @@ -5876,23 +6043,23 @@ checksum = "85bf8229e7920a9f636479437026331ce11aa132b4dde37d121944a44d6e5f3c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] name = "serde_fmt" -version = "1.0.1" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2963a69a2b3918c1dc75a45a18bd3fcd1120e31d3f59deb1b2f9b5d5ffb8baa4" +checksum = "e1d4ddca14104cd60529e8c7f7ba71a2c8acd8f7f5cfcdc2faf97eeb7c3010a4" dependencies = [ "serde", ] [[package]] name = "serde_json" -version = "1.0.89" +version = "1.0.96" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "020ff22c755c2ed3f8cf162dbb41a7268d934702f3ed3631656ea597e08fc3db" +checksum = "057d394a50403bcac12672b2b18fb387ab6d289d957dab67dd201875391e52f1" dependencies = [ "indexmap", "itoa", @@ -5902,13 +6069,13 @@ dependencies = [ [[package]] name = "serde_repr" -version = "0.1.9" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fe39d9fbb0ebf5eb2c7cb7e2a47e4f462fad1379f1166b8ae49ad9eae89a7ca" +checksum = "bcec881020c684085e55a25f7fd888954d56609ef363479dc5a1305eb0d40cab" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.15", ] [[package]] @@ -5943,7 +6110,7 @@ dependencies = [ "assets", "collections", "fs", - "futures 0.3.25", + "futures 0.3.28", "glob", "gpui", "json_comments", @@ -5954,9 +6121,9 @@ dependencies = [ "serde", "serde_derive", "serde_json", + "smallvec", "sqlez", "staff_mode", - "theme", "toml", "tree-sitter", "tree-sitter-json 0.19.0", @@ -6049,9 +6216,9 @@ checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3" [[package]] name = "signal-hook" -version = "0.3.14" +version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a253b5e89e2698464fc26b545c9edceb338e18a89effeeecfea192c3025be29d" +checksum = "732768f1176d21d09e076c23a93123d40bba92d50c4058da34d45c8de8e682b9" dependencies = [ "libc", "signal-hook-registry", @@ -6071,13 +6238,19 @@ dependencies = [ [[package]] name = "signal-hook-registry" -version = "1.4.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" +checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" dependencies = [ "libc", ] +[[package]] +name = "simdutf8" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f27f6278552951f1f2b8cf9da965d10969b2efdea95a6ec47987ab46edfe263a" + [[package]] name = "similar" version = "1.3.0" @@ -6124,18 +6297,18 @@ checksum = "0b8de496cf83d4ed58b6be86c3a275b8602f6ffe98d3024a869e124147a9a3ac" [[package]] name = "slab" -version = "0.4.7" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef" +checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" dependencies = [ "autocfg 1.1.0", ] [[package]] name = "slice-group-by" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03b634d87b960ab1a38c4fe143b508576f075e7c978bfad18217645ebfdfa2ec" +checksum = "826167069c09b99d56f31e9ae5c99049e932a98c9dc2dac47645b08dbbf76ba7" [[package]] name = "sluice" @@ -6191,9 +6364,9 @@ dependencies = [ [[package]] name = "socket2" -version = "0.4.7" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd" +checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" dependencies = [ "libc", "winapi 0.3.9", @@ -6207,9 +6380,9 @@ checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" [[package]] name = "spin" -version = "0.9.4" +version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f6002a767bff9e83f8eeecf883ecb8011875a21ae8da43bffb817a57e78cc09" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" dependencies = [ "lock_api", ] @@ -6225,14 +6398,14 @@ name = "sqlez" version = "0.1.0" dependencies = [ "anyhow", - "futures 0.3.25", + "futures 0.3.28", "indoc", "lazy_static", "libsqlite3-sys", "parking_lot 0.11.2", "smol", "thread_local", - "uuid 1.2.2", + "uuid 1.3.2", ] [[package]] @@ -6244,14 +6417,14 @@ dependencies = [ "quote", "sqlez", "sqlformat", - "syn", + "syn 1.0.109", ] [[package]] name = "sqlformat" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f87e292b4291f154971a43c3774364e2cbcaec599d3f5bf6fa9d122885dbc38a" +checksum = "0c12bc9199d1db8234678b7051747c07f517cdcf019262d1847b94ec8b1aee3e" dependencies = [ "itertools", "nom", @@ -6260,9 +6433,9 @@ dependencies = [ [[package]] name = "sqlx" -version = "0.6.1" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "788841def501aabde58d3666fcea11351ec3962e6ea75dbcd05c84a71d68bcd1" +checksum = "f8de3b03a925878ed54a954f621e64bf55a3c1bd29652d0d1a17830405350188" dependencies = [ "sqlx-core", "sqlx-macros", @@ -6270,16 +6443,16 @@ dependencies = [ [[package]] name = "sqlx-core" -version = "0.6.2" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcbc16ddba161afc99e14d1713a453747a2b07fc097d2009f4c300ec99286105" +checksum = "fa8241483a83a3f33aa5fff7e7d9def398ff9990b2752b6c6112b83c6d246029" dependencies = [ - "ahash", + "ahash 0.7.6", "atoi", - "base64", + "base64 0.13.1", "bitflags", "byteorder", - "bytes 1.3.0", + "bytes 1.4.0", "chrono", "crc", "crossbeam-queue", @@ -6310,7 +6483,7 @@ dependencies = [ "percent-encoding", "rand 0.8.5", "rust_decimal", - "rustls 0.20.7", + "rustls 0.20.8", "rustls-pemfile", "serde", "serde_json", @@ -6321,23 +6494,23 @@ dependencies = [ "sqlx-rt", "stringprep", "thiserror", - "time 0.3.17", + "time 0.3.21", "tokio-stream", "url", - "uuid 1.2.2", - "webpki-roots 0.22.5", + "uuid 1.3.2", + "webpki-roots 0.22.6", "whoami", ] [[package]] name = "sqlx-macros" -version = "0.6.2" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b850fa514dc11f2ee85be9d055c512aa866746adfacd1cb42d867d68e6a5b0d9" +checksum = "9966e64ae989e7e575b19d7265cb79d7fc3cbbdf179835cb0d716f294c2049c9" dependencies = [ "dotenvy", "either", - "heck 0.4.0", + "heck 0.4.1", "once_cell", "proc-macro2", "quote", @@ -6345,15 +6518,15 @@ dependencies = [ "sha2 0.10.6", "sqlx-core", "sqlx-rt", - "syn", + "syn 1.0.109", "url", ] [[package]] name = "sqlx-rt" -version = "0.6.2" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24c5b2d25fa654cc5f841750b8e1cdedbe21189bf9a9382ee90bfa9dd3562396" +checksum = "804d3f245f894e61b1e6263c84b23ca675d96753b5abfd5cc8597d86806e8024" dependencies = [ "once_cell", "tokio", @@ -6414,7 +6587,7 @@ version = "0.1.0" dependencies = [ "arrayvec 0.7.2", "ctor", - "env_logger", + "env_logger 0.9.3", "log", "rand 0.8.5", ] @@ -6456,9 +6629,9 @@ dependencies = [ [[package]] name = "syn" -version = "1.0.105" +version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60b9b43d45702de4c839cb9b51d9f529c5dd26a4aff255b42b1ebc03e88ee908" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ "proc-macro2", "quote", @@ -6466,23 +6639,22 @@ dependencies = [ ] [[package]] -name = "sync_wrapper" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20518fe4a4c9acf048008599e464deb21beeae3d3578418951a189c235a7a9a8" - -[[package]] -name = "synstructure" -version = "0.12.6" +name = "syn" +version = "2.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" +checksum = "a34fcf3e8b60f57e6a14301a2e916d323af98b0ea63c599441eec8558660c822" dependencies = [ "proc-macro2", "quote", - "syn", - "unicode-xid", + "unicode-ident", ] +[[package]] +name = "sync_wrapper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + [[package]] name = "sys-info" version = "0.9.1" @@ -6495,14 +6667,14 @@ dependencies = [ [[package]] name = "sysinfo" -version = "0.27.3" +version = "0.27.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1620f9573034c573376acc550f3b9a2be96daeb08abb3c12c8523e1cee06e80f" +checksum = "a902e9050fca0a5d6877550b769abd2bd1ce8c04634b941dbe2809735e1a1e33" dependencies = [ "cfg-if 1.0.0", "core-foundation-sys", "libc", - "ntapi 0.4.0", + "ntapi 0.4.1", "once_cell", "rayon", "winapi 0.3.9", @@ -6518,8 +6690,8 @@ dependencies = [ "bitflags", "cap-fs-ext", "cap-std", - "io-lifetimes", - "rustix", + "io-lifetimes 0.5.3", + "rustix 0.33.7", "winapi 0.3.9", "winx", ] @@ -6532,9 +6704,9 @@ checksum = "8bdb6fa0dfa67b38c1e66b7041ba9dcf23b99d8121907cd31c807a332f7a0bbb" [[package]] name = "target-lexicon" -version = "0.12.5" +version = "0.12.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9410d0f6853b1d94f0e519fb95df60f29d2c1eff2d921ffdf01a4c8a3b54f12d" +checksum = "fd1ba337640d60c3e96bc6f0638a939b9c9a7f2c316a1598c279828b3d1dc8c5" [[package]] name = "tempdir" @@ -6548,16 +6720,15 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.3.0" +version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" +checksum = "b9fbec84f381d5795b08656e4912bec604d162bff9291d6189a78f4c8ab87998" dependencies = [ "cfg-if 1.0.0", "fastrand", - "libc", - "redox_syscall", - "remove_dir_all", - "winapi 0.3.9", + "redox_syscall 0.3.5", + "rustix 0.37.19", + "windows-sys 0.45.0", ] [[package]] @@ -6577,7 +6748,7 @@ dependencies = [ "anyhow", "db", "dirs 4.0.0", - "futures 0.3.25", + "futures 0.3.28", "gpui", "itertools", "lazy_static", @@ -6586,6 +6757,7 @@ dependencies = [ "ordered-float", "procinfo", "rand 0.8.5", + "schemars", "serde", "serde_derive", "settings", @@ -6607,7 +6779,7 @@ dependencies = [ "db", "dirs 4.0.0", "editor", - "futures 0.3.25", + "futures 0.3.28", "gpui", "itertools", "language", @@ -6640,7 +6812,7 @@ dependencies = [ "collections", "ctor", "digest 0.9.0", - "env_logger", + "env_logger 0.9.3", "fs", "gpui", "lazy_static", @@ -6679,10 +6851,13 @@ dependencies = [ "gpui", "indexmap", "parking_lot 0.11.2", + "schemars", "serde", "serde_derive", "serde_json", + "settings", "toml", + "util", ] [[package]] @@ -6690,6 +6865,7 @@ name = "theme_selector" version = "0.1.0" dependencies = [ "editor", + "fs", "fuzzy", "gpui", "log", @@ -6718,22 +6894,22 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.37" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10deb33631e3c9018b9baf9dcbbc4f737320d2b576bac10f6aefa048fa407e3e" +checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.37" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "982d17546b47146b28f7c22e3d08465f6b8903d0ea13c1660d9d84a6e7adcdbb" +checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.15", ] [[package]] @@ -6744,10 +6920,11 @@ checksum = "3bf63baf9f5039dadc247375c29eb13706706cfde997d0330d05aa63a77d8820" [[package]] name = "thread_local" -version = "1.1.4" +version = "1.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180" +checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" dependencies = [ + "cfg-if 1.0.0", "once_cell", ] @@ -6775,9 +6952,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.17" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a561bf4617eebd33bca6434b988f39ed798e527f51a1e797d0ee4f61c0a38376" +checksum = "8f3403384eaacbca9923fa06940178ac13e4edb725486d70e8e15881d0c836cc" dependencies = [ "itoa", "serde", @@ -6787,15 +6964,15 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd" +checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb" [[package]] name = "time-macros" -version = "0.2.6" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d967f99f534ca7e495c575c62638eebc2898a8c84c119b89e250477bc4ba16b2" +checksum = "372950940a5f07bf38dbe211d7283c9e6d7327df53794992d293e534c733d09b" dependencies = [ "time-core", ] @@ -6838,28 +7015,27 @@ dependencies = [ [[package]] name = "tinyvec_macros" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.22.0" +version = "1.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d76ce4a75fb488c605c54bf610f221cea8b0dafb53333c1a67e8ee199dcd2ae3" +checksum = "0aa32867d44e6f2ce3385e89dceb990188b8bb0fb25b0cf576647a6f98ac5105" dependencies = [ "autocfg 1.1.0", - "bytes 1.3.0", + "bytes 1.4.0", "libc", - "memchr", - "mio 0.8.5", + "mio 0.8.6", "num_cpus", "parking_lot 0.12.1", "pin-project-lite 0.2.9", "signal-hook-registry", "socket2", "tokio-macros", - "winapi 0.3.9", + "windows-sys 0.48.0", ] [[package]] @@ -6885,20 +7061,20 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "1.8.2" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d266c00fde287f55d3f1c3e96c500c362a2b8c695076ec180f27918820bc6df8" +checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.15", ] [[package]] name = "tokio-native-tls" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7d995660bd2b7f8c1568414c1126076c13fbb725c40112dc0120b78eb9b717b" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" dependencies = [ "native-tls", "tokio", @@ -6910,16 +7086,16 @@ version = "0.23.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59" dependencies = [ - "rustls 0.20.7", + "rustls 0.20.8", "tokio", "webpki 0.22.0", ] [[package]] name = "tokio-stream" -version = "0.1.11" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d660770404473ccd7bc9f8b28494a811bc18542b915c0855c51e8f419d5223ce" +checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842" dependencies = [ "futures-core", "pin-project-lite 0.2.9", @@ -6944,7 +7120,7 @@ version = "0.6.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "36943ee01a6d67977dd3f84a5a1d2efeb4ada3a1ae771cadfaa535d9d9fc6507" dependencies = [ - "bytes 1.3.0", + "bytes 1.4.0", "futures-core", "futures-sink", "log", @@ -6954,11 +7130,11 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.4" +version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bb2e075f03b3d66d8d8785356224ba688d2906a371015e225beeb65ca92c740" +checksum = "806fe8c2c87eccc8b3267cbae29ed3ab2d0bd37fca70ab622e46aaa9375ddb7d" dependencies = [ - "bytes 1.3.0", + "bytes 1.4.0", "futures-core", "futures-io", "futures-sink", @@ -6969,9 +7145,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.5.9" +version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7" +checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" dependencies = [ "serde", ] @@ -6984,8 +7160,8 @@ checksum = "ff08f4649d10a70ffa3522ca559031285d8e421d727ac85c60825761818f5d0a" dependencies = [ "async-stream", "async-trait", - "base64", - "bytes 1.3.0", + "base64 0.13.1", + "bytes 1.4.0", "futures-core", "futures-util", "h2", @@ -7021,7 +7197,7 @@ dependencies = [ "rand 0.8.5", "slab", "tokio", - "tokio-util 0.7.4", + "tokio-util 0.7.8", "tower-layer", "tower-service", "tracing", @@ -7034,7 +7210,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f873044bf02dd1e8239e9c1293ea39dad76dc594ec16185d0a1bf31d8dc8d858" dependencies = [ "bitflags", - "bytes 1.3.0", + "bytes 1.4.0", "futures-core", "futures-util", "http", @@ -7073,13 +7249,13 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.23" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a" +checksum = "0f57e3ca2a01450b1a921183a9c9cbfda207fd822cef4ccb00a65402cbba7a74" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.15", ] [[package]] @@ -7125,9 +7301,9 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.16" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6176eae26dd70d0c919749377897b54a9276bd7061339665dd68777926b5a70" +checksum = "30a651bc37f915e81f087d86e62a18eec5f79550c7faff886f7090b4ea757c77" dependencies = [ "matchers", "nu-ansi-term", @@ -7327,9 +7503,9 @@ dependencies = [ [[package]] name = "tree-sitter-typescript" -version = "0.20.1" +version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e8ed0ecb931cdff13c6a13f45ccd615156e2779d9ffb0395864e05505e6e86d" +checksum = "079c695c32d39ad089101c66393aeaca30e967fba3486a91f573d2f0e12d290a" dependencies = [ "cc", "tree-sitter", @@ -7355,9 +7531,9 @@ dependencies = [ [[package]] name = "try-lock" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" +checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" [[package]] name = "ttf-parser" @@ -7377,9 +7553,9 @@ version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ad3713a14ae247f22a728a0456a545df14acf3867f905adff84be99e23b3ad1" dependencies = [ - "base64", + "base64 0.13.1", "byteorder", - "bytes 1.3.0", + "bytes 1.4.0", "http", "httparse", "log", @@ -7396,9 +7572,9 @@ version = "0.17.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e27992fd6a8c29ee7eef28fc78349aa244134e10ad447ce3b9f0ac0ed0fa4ce0" dependencies = [ - "base64", + "base64 0.13.1", "byteorder", - "bytes 1.3.0", + "bytes 1.4.0", "http", "httparse", "log", @@ -7411,9 +7587,9 @@ dependencies = [ [[package]] name = "typenum" -version = "1.15.0" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" +checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" [[package]] name = "ucd-trie" @@ -7432,9 +7608,9 @@ dependencies = [ [[package]] name = "unicode-bidi" -version = "0.3.8" +version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" +checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" [[package]] name = "unicode-bidi-mirroring" @@ -7456,9 +7632,9 @@ checksum = "7f9af028e052a610d99e066b33304625dea9613170a2563314490a4e6ec5cf7f" [[package]] name = "unicode-ident" -version = "1.0.5" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3" +checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" [[package]] name = "unicode-normalization" @@ -7477,9 +7653,9 @@ checksum = "7d817255e1bed6dfd4ca47258685d14d2bdcfbc64fdc9e3819bd5848057b8ecc" [[package]] name = "unicode-segmentation" -version = "1.10.0" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fdbf052a0783de01e944a6ce7a8cb939e295b1e7be835a1112c3b9a7f047a5a" +checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" [[package]] name = "unicode-vo" @@ -7493,12 +7669,6 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" -[[package]] -name = "unicode-xid" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" - [[package]] name = "unicode_categories" version = "0.1.1" @@ -7507,9 +7677,9 @@ checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" [[package]] name = "unindent" -version = "0.1.10" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58ee9362deb4a96cef4d437d1ad49cffc9b9e92d202b6995674e928ce684f112" +checksum = "e1766d682d402817b5ac4490b3c3002d91dfa0d22812f341609f97b08757359c" [[package]] name = "untrusted" @@ -7541,7 +7711,7 @@ version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef8352f317d8f9a918ba5154797fb2a93e2730244041cf7d5be35148266adfa5" dependencies = [ - "base64", + "base64 0.13.1", "data-url", "flate2", "fontdb", @@ -7570,9 +7740,9 @@ checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" [[package]] name = "utf8parse" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "936e4b492acfd135421d8dca4b1aa80a7bfc26e702ef3af710e0752684df5372" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" [[package]] name = "util" @@ -7581,7 +7751,7 @@ dependencies = [ "anyhow", "backtrace", "dirs 3.0.2", - "futures 0.3.25", + "futures 0.3.28", "git2", "isahc", "lazy_static", @@ -7607,16 +7777,16 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" dependencies = [ - "getrandom 0.2.8", + "getrandom 0.2.9", ] [[package]] name = "uuid" -version = "1.2.2" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "422ee0de9031b5b948b97a8fc04e3aa35230001a722ddd27943e0be31564ce4c" +checksum = "4dad5567ad0cf5b760e5665964bec1b47dfd077ba8a2544b513f3556d3d239a2" dependencies = [ - "getrandom 0.2.8", + "getrandom 0.2.9", "serde", ] @@ -7662,6 +7832,7 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" name = "vim" version = "0.1.0" dependencies = [ + "anyhow", "assets", "async-compat", "async-trait", @@ -7715,12 +7886,11 @@ checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca" [[package]] name = "walkdir" -version = "2.3.2" +version = "2.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56" +checksum = "36df944cda56c7d8d8b7496af378e6b16de9284591917d307c9b4d313c44e698" dependencies = [ "same-file", - "winapi 0.3.9", "winapi-util", ] @@ -7766,10 +7936,10 @@ dependencies = [ "cap-time-ext", "fs-set-times", "io-extras", - "io-lifetimes", - "is-terminal", + "io-lifetimes 0.5.3", + "is-terminal 0.1.0", "lazy_static", - "rustix", + "rustix 0.33.7", "system-interface", "tracing", "wasi-common", @@ -7787,7 +7957,7 @@ dependencies = [ "cap-rand", "cap-std", "io-extras", - "rustix", + "rustix 0.33.7", "thiserror", "tracing", "wiggle", @@ -7796,9 +7966,9 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.83" +version = "0.2.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268" +checksum = "5b6cb788c4e39112fbe1822277ef6fb3c55cd86b95cb3d3c4c1c9597e4ac74b4" dependencies = [ "cfg-if 1.0.0", "wasm-bindgen-macro", @@ -7806,24 +7976,24 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.83" +version = "0.2.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142" +checksum = "35e522ed4105a9d626d885b35d62501b30d9666283a5c8be12c14a8bdafe7822" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn", + "syn 2.0.15", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.33" +version = "0.4.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23639446165ca5a5de86ae1d8896b737ae80319560fbaa4c2887b7da6e7ebd7d" +checksum = "083abe15c5d88556b77bdf7aef403625be9e327ad37c62c4e4129af740168163" dependencies = [ "cfg-if 1.0.0", "js-sys", @@ -7833,9 +8003,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.83" +version = "0.2.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810" +checksum = "358a79a0cb89d21db8120cbfb91392335913e4890665b1a7981d9e956903b434" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -7843,28 +8013,28 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.83" +version = "0.2.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c" +checksum = "4783ce29f09b9d93134d41297aded3a712b7b979e9c6f28c32cb88c973a94869" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.15", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.83" +version = "0.2.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f" +checksum = "a901d592cafaa4d711bc324edfaff879ac700b19c3dfd60058d2b445be2691eb" [[package]] name = "wasm-encoder" -version = "0.20.0" +version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05632e0a66a6ed8cca593c24223aabd6262f256c3693ad9822c315285f010614" +checksum = "d05d0b6fcd0aeb98adf16e7975331b3c17222aa815148f5b976370ce589d80ef" dependencies = [ "leb128", ] @@ -7919,12 +8089,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d1df23c642e1376892f3b72f311596976979cbf8b85469680cdd3a8a063d12a2" dependencies = [ "anyhow", - "base64", + "base64 0.13.1", "bincode", "directories-next", "file-per-thread-logger", "log", - "rustix", + "rustix 0.33.7", "serde", "sha2 0.9.9", "toml", @@ -7944,7 +8114,7 @@ dependencies = [ "cranelift-frontend", "cranelift-native", "cranelift-wasm", - "gimli", + "gimli 0.26.2", "log", "more-asserts", "object 0.28.4", @@ -7962,7 +8132,7 @@ checksum = "839d2820e4b830f4b9e7aa08d4c0acabf4a5036105d639f6dfa1c6891c73bdc6" dependencies = [ "anyhow", "cranelift-entity", - "gimli", + "gimli 0.26.2", "indexmap", "log", "more-asserts", @@ -7981,7 +8151,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3248be3c4911233535356025f6562193614a40155ee9094bb6a2b43f0dc82803" dependencies = [ "cc", - "rustix", + "rustix 0.33.7", "winapi 0.3.9", ] @@ -7991,18 +8161,18 @@ version = "0.38.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef0a0bcbfa18b946d890078ba0e1bc76bcc53eccfb40806c0020ec29dcd1bd49" dependencies = [ - "addr2line", + "addr2line 0.17.0", "anyhow", "bincode", "cfg-if 1.0.0", "cpp_demangle", - "gimli", + "gimli 0.26.2", "ittapi-rs", "log", "object 0.28.4", "region", "rustc-demangle", - "rustix", + "rustix 0.33.7", "serde", "target-lexicon", "thiserror", @@ -8020,7 +8190,7 @@ checksum = "4f4779d976206c458edd643d1ac622b6c37e4a0800a8b1d25dfbf245ac2f2cac" dependencies = [ "lazy_static", "object 0.28.4", - "rustix", + "rustix 0.33.7", ] [[package]] @@ -8042,7 +8212,7 @@ dependencies = [ "more-asserts", "rand 0.8.5", "region", - "rustix", + "rustix 0.33.7", "thiserror", "wasmtime-environ", "wasmtime-fiber", @@ -8086,9 +8256,9 @@ dependencies = [ [[package]] name = "wast" -version = "50.0.0" +version = "57.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2cbb59d4ac799842791fe7e806fa5dbbf6b5554d538e51cc8e176db6ff0ae34" +checksum = "6eb0f5ed17ac4421193c7477da05892c2edafd67f9639e3c11a82086416662dc" dependencies = [ "leb128", "memchr", @@ -8098,18 +8268,18 @@ dependencies = [ [[package]] name = "wat" -version = "1.0.52" +version = "1.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "584aaf7a1ecf4d383bbe1a25eeab0cbb8ff96acc6796707ff65cde48f4632f15" +checksum = "ab9ab0d87337c3be2bb6fc5cd331c4ba9fd6bcb4ee85048a0dd59ed9ecf92e53" dependencies = [ - "wast 50.0.0", + "wast 57.0.0", ] [[package]] name = "web-sys" -version = "0.3.60" +version = "0.3.62" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcda906d8be16e728fd5adc5b729afad4e444e106ab28cd1c7256e54fa61510f" +checksum = "16b5f940c7edfdc6d12126d98c9ef4d1b3d470011c47c76a6581df47ad9ba721" dependencies = [ "js-sys", "wasm-bindgen", @@ -8146,9 +8316,9 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "0.22.5" +version = "0.22.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "368bfe657969fb01238bb756d351dcade285e0f6fcbd36dcb23359a5169975be" +checksum = "b6c71e40d7d2c34a5106301fb632274ca37242cd0c9d3e64dbece371a40a2d87" dependencies = [ "webpki 0.22.0", ] @@ -8164,14 +8334,18 @@ name = "welcome" version = "0.1.0" dependencies = [ "anyhow", + "client", "db", "editor", + "fs", "fuzzy", "gpui", "install_cli", "log", "picker", "project", + "schemars", + "serde", "settings", "theme", "theme_selector", @@ -8179,20 +8353,11 @@ dependencies = [ "workspace", ] -[[package]] -name = "wepoll-ffi" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d743fdedc5c64377b5fc2bc036b01c7fd642205a0d96356034ae3404d49eb7fb" -dependencies = [ - "cc", -] - [[package]] name = "which" -version = "4.3.0" +version = "4.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c831fbbee9e129a8cf93e7747a82da9d95ba8e16621cae60ec2cdc849bacb7b" +checksum = "2441c784c52b289a054b7201fc93253e288f094e2f4be9058343127c4226a269" dependencies = [ "either", "libc", @@ -8201,11 +8366,10 @@ dependencies = [ [[package]] name = "whoami" -version = "1.2.3" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6631b6a2fd59b1841b622e8f1a7ad241ef0a46f2d580464ce8140ac94cbd571" +checksum = "2c70234412ca409cc04e864e89523cb0fc37f5e1344ebed5a3ebf4192b6b9f68" dependencies = [ - "bumpalo", "wasm-bindgen", "web-sys", ] @@ -8232,11 +8396,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "63a1dccd6b3fbd9a27417f5d30ce9aa3ee9cf529aad453abbf88a49c5d605b79" dependencies = [ "anyhow", - "heck 0.4.0", + "heck 0.4.1", "proc-macro2", "quote", "shellexpand", - "syn", + "syn 1.0.109", "witx", ] @@ -8248,7 +8412,7 @@ checksum = "f1c368d57d9560c34deaa67e06b0953ccf65edb906c525e5a2c866c849b48ec2" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", "wiggle-generate", ] @@ -8296,16 +8460,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] -name = "windows-sys" -version = "0.36.1" +name = "windows" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" +checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" dependencies = [ - "windows_aarch64_msvc 0.36.1", - "windows_i686_gnu 0.36.1", - "windows_i686_msvc 0.36.1", - "windows_x86_64_gnu 0.36.1", - "windows_x86_64_msvc 0.36.1", + "windows-targets 0.48.0", ] [[package]] @@ -8314,86 +8474,146 @@ version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc 0.42.0", - "windows_i686_gnu 0.42.0", - "windows_i686_msvc 0.42.0", - "windows_x86_64_gnu 0.42.0", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc 0.42.0", + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.0", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-targets" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" +dependencies = [ + "windows_aarch64_gnullvm 0.48.0", + "windows_aarch64_msvc 0.48.0", + "windows_i686_gnu 0.48.0", + "windows_i686_msvc 0.48.0", + "windows_x86_64_gnu 0.48.0", + "windows_x86_64_gnullvm 0.48.0", + "windows_x86_64_msvc 0.48.0", ] [[package]] name = "windows_aarch64_gnullvm" -version = "0.42.0" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" [[package]] name = "windows_aarch64_msvc" -version = "0.36.1" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" [[package]] name = "windows_aarch64_msvc" -version = "0.42.0" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4" +checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" [[package]] name = "windows_i686_gnu" -version = "0.36.1" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" [[package]] name = "windows_i686_gnu" -version = "0.42.0" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7" +checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" [[package]] name = "windows_i686_msvc" -version = "0.36.1" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" [[package]] name = "windows_i686_msvc" -version = "0.42.0" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246" +checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" [[package]] name = "windows_x86_64_gnu" -version = "0.36.1" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" [[package]] name = "windows_x86_64_gnu" -version = "0.42.0" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed" +checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" [[package]] name = "windows_x86_64_gnullvm" -version = "0.42.0" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" [[package]] name = "windows_x86_64_msvc" -version = "0.36.1" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" [[package]] name = "windows_x86_64_msvc" -version = "0.42.0" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5" +checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" [[package]] name = "winreg" @@ -8411,7 +8631,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d5973cb8cd94a77d03ad7e23bbe14889cb29805da1cec0e4aff75e21aebded" dependencies = [ "bitflags", - "io-lifetimes", + "io-lifetimes 0.5.3", "winapi 0.3.9", ] @@ -8442,7 +8662,7 @@ version = "0.1.0" dependencies = [ "anyhow", "assets", - "async-recursion 1.0.0", + "async-recursion 1.0.4", "bincode", "call", "client", @@ -8450,9 +8670,9 @@ dependencies = [ "context_menu", "db", "drag_and_drop", - "env_logger", + "env_logger 0.9.3", "fs", - "futures 0.3.25", + "futures 0.3.28", "gpui", "indoc", "install_cli", @@ -8463,6 +8683,7 @@ dependencies = [ "parking_lot 0.11.2", "postage", "project", + "schemars", "serde", "serde_derive", "serde_json", @@ -8471,7 +8692,7 @@ dependencies = [ "terminal", "theme", "util", - "uuid 1.2.2", + "uuid 1.3.2", ] [[package]] @@ -8493,12 +8714,6 @@ dependencies = [ "libc", ] -[[package]] -name = "xml-rs" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2d7d3948613f75c98fd9328cfdcc45acc4d360655289d0a7d4ec931392200a3" - [[package]] name = "xmlparser" version = "0.13.5" @@ -8567,12 +8782,12 @@ dependencies = [ "db", "diagnostics", "editor", - "env_logger", + "env_logger 0.9.3", "feedback", "file_finder", "fs", "fsevent", - "futures 0.3.25", + "futures 0.3.28", "fuzzy", "go_to_line", "gpui", @@ -8640,13 +8855,13 @@ dependencies = [ "tree-sitter-rust", "tree-sitter-scheme", "tree-sitter-toml", - "tree-sitter-typescript 0.20.2", + "tree-sitter-typescript 0.20.2 (git+https://github.com/tree-sitter/tree-sitter-typescript?rev=5d20856f34315b068c41edaee2ac8a100081d259)", "tree-sitter-yaml", "unindent", "url", "urlencoding", "util", - "uuid 1.2.2", + "uuid 1.3.2", "vim", "welcome", "workspace", @@ -8663,14 +8878,13 @@ dependencies = [ [[package]] name = "zeroize_derive" -version = "1.3.3" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44bf07cb3e50ea2003396695d58bf46bc9887a1f362260446fad6bc4e79bd36c" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn", - "synstructure", + "syn 2.0.15", ] [[package]] @@ -8694,10 +8908,11 @@ dependencies = [ [[package]] name = "zstd-sys" -version = "2.0.4+zstd.1.5.2" +version = "2.0.8+zstd.1.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fa202f2ef00074143e219d15b62ffc317d17cc33909feac471c044087cad7b0" +checksum = "5556e6ee25d32df2586c098bbfa278803692a20d0ab9565e049480d52707ec8c" dependencies = [ "cc", "libc", + "pkg-config", ] diff --git a/Cargo.toml b/Cargo.toml index 15df687d41d3a5933250bb4d2cd66a7ab0ed4075..f14e1c73552f766034ee1d98dd1a51fe3a00bf09 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -85,6 +85,7 @@ parking_lot = { version = "0.11.1" } postage = { version = "0.5", features = ["futures-traits"] } rand = { version = "0.8.5" } regex = { version = "1.5" } +schemars = { version = "0.8" } serde = { version = "1.0", features = ["derive", "rc"] } serde_derive = { version = "1.0", features = ["deserialize_in_place"] } serde_json = { version = "1.0", features = ["preserve_order", "raw_value"] } @@ -93,6 +94,7 @@ smol = { version = "1.2" } tempdir = { version = "0.3.7" } thiserror = { version = "1.0.29" } time = { version = "0.3", features = ["serde", "serde-well-known"] } +toml = { version = "0.5" } unindent = { version = "0.1.7" } [patch.crates-io] diff --git a/assets/settings/default.json b/assets/settings/default.json index 1395edca4ab6f4339e8689e7aea48c1b5f531dc8..4f149edb1053d10dfb6ae4764b439978f5130825 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -1,6 +1,15 @@ { // The name of the Zed theme to use for the UI "theme": "One Dark", + // The name of a base set of key bindings to use. + // This setting can take four values, each named after another + // text editor: + // + // 1. "VSCode" + // 2. "JetBrains" + // 3. "SublimeText" + // 4. "Atom" + "base_keymap": "VSCode", // Features that can be globally enabled or disabled "features": { // Show Copilot icon in status bar diff --git a/crates/activity_indicator/Cargo.toml b/crates/activity_indicator/Cargo.toml index 629aa2c032929d3a6465723b0dab5fe04902c14b..917383234a322f1145e47c218ca0937cae8b339f 100644 --- a/crates/activity_indicator/Cargo.toml +++ b/crates/activity_indicator/Cargo.toml @@ -16,6 +16,8 @@ gpui = { path = "../gpui" } project = { path = "../project" } settings = { path = "../settings" } util = { path = "../util" } +theme = { path = "../theme" } workspace = { path = "../workspace" } + futures.workspace = true smallvec.workspace = true diff --git a/crates/activity_indicator/src/activity_indicator.rs b/crates/activity_indicator/src/activity_indicator.rs index d5ee1364b37100a7e71e4f6365bbee523e630bda..801c8f7172ec40d5c111109482d55e345d037592 100644 --- a/crates/activity_indicator/src/activity_indicator.rs +++ b/crates/activity_indicator/src/activity_indicator.rs @@ -9,7 +9,6 @@ use gpui::{ }; use language::{LanguageRegistry, LanguageServerBinaryStatus}; use project::{LanguageServerProgress, Project}; -use settings::Settings; use smallvec::SmallVec; use std::{cmp::Reverse, fmt::Write, sync::Arc}; use util::ResultExt; @@ -325,12 +324,7 @@ impl View for ActivityIndicator { } = self.content_to_render(cx); let mut element = MouseEventHandler::::new(0, cx, |state, cx| { - let theme = &cx - .global::() - .theme - .workspace - .status_bar - .lsp_status; + let theme = &theme::current(cx).workspace.status_bar.lsp_status; let style = if state.hovered() && on_click.is_some() { theme.hover.as_ref().unwrap_or(&theme.default) } else { diff --git a/crates/auto_update/src/auto_update.rs b/crates/auto_update/src/auto_update.rs index 89b70acec736cdd08309dc1bf310481d3a2cd967..822886b58018e1cb8cef694a2cd2b8274a20c949 100644 --- a/crates/auto_update/src/auto_update.rs +++ b/crates/auto_update/src/auto_update.rs @@ -1,7 +1,7 @@ mod update_notification; use anyhow::{anyhow, Context, Result}; -use client::{Client, ZED_APP_PATH, ZED_APP_VERSION, ZED_SECRET_CLIENT_TOKEN}; +use client::{Client, TelemetrySettings, ZED_APP_PATH, ZED_APP_VERSION, ZED_SECRET_CLIENT_TOKEN}; use db::kvp::KEY_VALUE_STORE; use gpui::{ actions, platform::AppVersion, AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle, @@ -10,7 +10,7 @@ use gpui::{ use isahc::AsyncBody; use serde::Deserialize; use serde_derive::Serialize; -use settings::Settings; +use settings::{Setting, SettingsStore}; use smol::{fs::File, io::AsyncReadExt, process::Command}; use std::{ffi::OsString, sync::Arc, time::Duration}; use update_notification::UpdateNotification; @@ -58,18 +58,37 @@ impl Entity for AutoUpdater { type Event = (); } +struct AutoUpdateSetting(bool); + +impl Setting for AutoUpdateSetting { + const KEY: Option<&'static str> = Some("auto_update"); + + type FileContent = Option; + + fn load( + default_value: &Option, + user_values: &[&Option], + _: &AppContext, + ) -> Result { + Ok(Self( + Self::json_merge(default_value, user_values)?.ok_or_else(Self::missing_default)?, + )) + } +} + pub fn init(http_client: Arc, server_url: String, cx: &mut AppContext) { + settings::register::(cx); + if let Some(version) = (*ZED_APP_VERSION).or_else(|| cx.platform().app_version().ok()) { let auto_updater = cx.add_model(|cx| { let updater = AutoUpdater::new(version, http_client, server_url); - let mut update_subscription = cx - .global::() - .auto_update + let mut update_subscription = settings::get::(cx) + .0 .then(|| updater.start_polling(cx)); - cx.observe_global::(move |updater, cx| { - if cx.global::().auto_update { + cx.observe_global::(move |updater, cx| { + if settings::get::(cx).0 { if update_subscription.is_none() { update_subscription = Some(updater.start_polling(cx)) } @@ -262,7 +281,7 @@ impl AutoUpdater { let release_channel = cx .has_global::() .then(|| cx.global::().display_name()); - let telemetry = cx.global::().telemetry().metrics(); + let telemetry = settings::get::(cx).metrics; (installation_id, release_channel, telemetry) }); diff --git a/crates/auto_update/src/update_notification.rs b/crates/auto_update/src/update_notification.rs index b48ac2a413b00e2f24a41ba742883019b507d961..6f31df614dbbd0f0761c93487d988ae9168f3d9d 100644 --- a/crates/auto_update/src/update_notification.rs +++ b/crates/auto_update/src/update_notification.rs @@ -5,7 +5,6 @@ use gpui::{ Element, Entity, View, ViewContext, }; use menu::Cancel; -use settings::Settings; use util::channel::ReleaseChannel; use workspace::notifications::Notification; @@ -27,7 +26,7 @@ impl View for UpdateNotification { } fn render(&mut self, cx: &mut gpui::ViewContext) -> gpui::AnyElement { - let theme = cx.global::().theme.clone(); + let theme = theme::current(cx).clone(); let theme = &theme.update_notification; let app_name = cx.global::().display_name(); diff --git a/crates/breadcrumbs/src/breadcrumbs.rs b/crates/breadcrumbs/src/breadcrumbs.rs index f3be60f8de7fb343da80012271b3439524d36d4e..906d70abb738e7267d35a59f501be96809fbf5b1 100644 --- a/crates/breadcrumbs/src/breadcrumbs.rs +++ b/crates/breadcrumbs/src/breadcrumbs.rs @@ -4,7 +4,6 @@ use gpui::{ }; use itertools::Itertools; use search::ProjectSearchView; -use settings::Settings; use workspace::{ item::{ItemEvent, ItemHandle}, ToolbarItemLocation, ToolbarItemView, Workspace, @@ -50,7 +49,7 @@ impl View for Breadcrumbs { }; let not_editor = active_item.downcast::().is_none(); - let theme = cx.global::().theme.clone(); + let theme = theme::current(cx).clone(); let style = &theme.workspace.breadcrumbs; let breadcrumbs = match active_item.breadcrumbs(&theme, cx) { diff --git a/crates/client/Cargo.toml b/crates/client/Cargo.toml index 99c492d638e525e63316da7868e3780241178c21..3ecc51598696cd9ec5965c35d346bda069418086 100644 --- a/crates/client/Cargo.toml +++ b/crates/client/Cargo.toml @@ -31,6 +31,7 @@ log.workspace = true parking_lot.workspace = true postage.workspace = true rand.workspace = true +schemars.workspace = true smol.workspace = true thiserror.workspace = true time.workspace = true diff --git a/crates/client/src/client.rs b/crates/client/src/client.rs index 18a0f32ed6de61de2ce668207f194bf7b1a03e3c..311d9a2b8872cd3d63ad861cc986849d93d1e240 100644 --- a/crates/client/src/client.rs +++ b/crates/client/src/client.rs @@ -15,19 +15,17 @@ use futures::{ TryStreamExt, }; use gpui::{ - actions, - platform::AppVersion, - serde_json::{self}, - AnyModelHandle, AnyWeakModelHandle, AnyWeakViewHandle, AppContext, AsyncAppContext, Entity, - ModelHandle, Task, View, ViewContext, WeakViewHandle, + actions, platform::AppVersion, serde_json, AnyModelHandle, AnyWeakModelHandle, + AnyWeakViewHandle, AppContext, AsyncAppContext, Entity, ModelHandle, Task, View, ViewContext, + WeakViewHandle, }; use lazy_static::lazy_static; use parking_lot::RwLock; use postage::watch; use rand::prelude::*; use rpc::proto::{AnyTypedEnvelope, EntityMessage, EnvelopedMessage, PeerId, RequestMessage}; -use serde::Deserialize; -use settings::Settings; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; use std::{ any::TypeId, collections::HashMap, @@ -72,25 +70,34 @@ pub const CONNECTION_TIMEOUT: Duration = Duration::from_secs(5); actions!(client, [SignIn, SignOut]); -pub fn init(client: Arc, cx: &mut AppContext) { +pub fn init_settings(cx: &mut AppContext) { + settings::register::(cx); +} + +pub fn init(client: &Arc, cx: &mut AppContext) { + init_settings(cx); + + let client = Arc::downgrade(client); cx.add_global_action({ let client = client.clone(); move |_: &SignIn, cx| { - let client = client.clone(); - cx.spawn( - |cx| async move { client.authenticate_and_connect(true, &cx).log_err().await }, - ) - .detach(); + if let Some(client) = client.upgrade() { + cx.spawn( + |cx| async move { client.authenticate_and_connect(true, &cx).log_err().await }, + ) + .detach(); + } } }); cx.add_global_action({ let client = client.clone(); move |_: &SignOut, cx| { - let client = client.clone(); - cx.spawn(|cx| async move { - client.disconnect(&cx); - }) - .detach(); + if let Some(client) = client.upgrade() { + cx.spawn(|cx| async move { + client.disconnect(&cx); + }) + .detach(); + } } }); } @@ -326,6 +333,42 @@ impl Drop for PendingEntitySubscription { } } +#[derive(Copy, Clone)] +pub struct TelemetrySettings { + pub diagnostics: bool, + pub metrics: bool, +} + +#[derive(Clone, Serialize, Deserialize, JsonSchema)] +pub struct TelemetrySettingsContent { + pub diagnostics: Option, + pub metrics: Option, +} + +impl settings::Setting for TelemetrySettings { + const KEY: Option<&'static str> = Some("telemetry"); + + type FileContent = TelemetrySettingsContent; + + fn load( + default_value: &Self::FileContent, + user_values: &[&Self::FileContent], + _: &AppContext, + ) -> Result { + Ok(Self { + diagnostics: user_values.first().and_then(|v| v.diagnostics).unwrap_or( + default_value + .diagnostics + .ok_or_else(Self::missing_default)?, + ), + metrics: user_values + .first() + .and_then(|v| v.metrics) + .unwrap_or(default_value.metrics.ok_or_else(Self::missing_default)?), + }) + } +} + impl Client { pub fn new(http: Arc, cx: &AppContext) -> Arc { Arc::new(Self { @@ -447,9 +490,7 @@ impl Client { })); } Status::SignedOut | Status::UpgradeRequired => { - let telemetry_settings = cx.read(|cx| cx.global::().telemetry()); - self.telemetry - .set_authenticated_user_info(None, false, telemetry_settings); + cx.read(|cx| self.telemetry.set_authenticated_user_info(None, false, cx)); state._reconnect_task.take(); } _ => {} @@ -740,7 +781,7 @@ impl Client { self.telemetry().report_mixpanel_event( "read credentials from keychain", Default::default(), - cx.global::().telemetry(), + *settings::get::(cx), ); }); } @@ -1033,7 +1074,8 @@ impl Client { let executor = cx.background(); let telemetry = self.telemetry.clone(); let http = self.http.clone(); - let metrics_enabled = cx.read(|cx| cx.global::().telemetry()); + + let telemetry_settings = cx.read(|cx| *settings::get::(cx)); executor.clone().spawn(async move { // Generate a pair of asymmetric encryption keys. The public key will be used by the @@ -1120,7 +1162,7 @@ impl Client { telemetry.report_mixpanel_event( "authenticate with browser", Default::default(), - metrics_enabled, + telemetry_settings, ); Ok(Credentials { diff --git a/crates/client/src/telemetry.rs b/crates/client/src/telemetry.rs index 5e3c78420d978283b7009bf77d3d576bdc5a4773..b3bdc72c91cc8e9f5bb09ded6034f2d55928e46c 100644 --- a/crates/client/src/telemetry.rs +++ b/crates/client/src/telemetry.rs @@ -1,4 +1,4 @@ -use crate::{ZED_SECRET_CLIENT_TOKEN, ZED_SERVER_URL}; +use crate::{TelemetrySettings, ZED_SECRET_CLIENT_TOKEN, ZED_SERVER_URL}; use db::kvp::KEY_VALUE_STORE; use gpui::{ executor::Background, @@ -9,7 +9,6 @@ use lazy_static::lazy_static; use parking_lot::Mutex; use serde::Serialize; use serde_json::json; -use settings::TelemetrySettings; use std::{ io::Write, mem, @@ -246,9 +245,9 @@ impl Telemetry { self: &Arc, metrics_id: Option, is_staff: bool, - telemetry_settings: TelemetrySettings, + cx: &AppContext, ) { - if !telemetry_settings.metrics() { + if !settings::get::(cx).metrics { return; } @@ -290,7 +289,7 @@ impl Telemetry { event: ClickhouseEvent, telemetry_settings: TelemetrySettings, ) { - if !telemetry_settings.metrics() { + if !telemetry_settings.metrics { return; } @@ -326,7 +325,7 @@ impl Telemetry { properties: Value, telemetry_settings: TelemetrySettings, ) { - if !telemetry_settings.metrics() { + if !telemetry_settings.metrics { return; } diff --git a/crates/client/src/user.rs b/crates/client/src/user.rs index 6b3aa7e442876c94f1968925d7c48c6e9b155fdf..4c2721ffebbfd78e88c5d7fc656d1ab0ca1a1c79 100644 --- a/crates/client/src/user.rs +++ b/crates/client/src/user.rs @@ -5,7 +5,6 @@ use futures::{channel::mpsc, future, AsyncReadExt, Future, StreamExt}; use gpui::{AsyncAppContext, Entity, ImageData, ModelContext, ModelHandle, Task}; use postage::{sink::Sink, watch}; use rpc::proto::{RequestMessage, UsersResponse}; -use settings::Settings; use staff_mode::StaffMode; use std::sync::{Arc, Weak}; use util::http::HttpClient; @@ -144,11 +143,13 @@ impl UserStore { let fetch_metrics_id = client.request(proto::GetPrivateUserInfo {}).log_err(); let (user, info) = futures::join!(fetch_user, fetch_metrics_id); - client.telemetry.set_authenticated_user_info( - info.as_ref().map(|info| info.metrics_id.clone()), - info.as_ref().map(|info| info.staff).unwrap_or(false), - cx.read(|cx| cx.global::().telemetry()), - ); + cx.read(|cx| { + client.telemetry.set_authenticated_user_info( + info.as_ref().map(|info| info.metrics_id.clone()), + info.as_ref().map(|info| info.staff).unwrap_or(false), + cx, + ) + }); cx.update(|cx| { cx.update_default_global(|staff_mode: &mut StaffMode, _| { diff --git a/crates/collab/Cargo.toml b/crates/collab/Cargo.toml index d4941438a02d1e97cdf115d16e9cc51c866e7c19..ba49373641fb8a9056a43316c19e6548f6aa8798 100644 --- a/crates/collab/Cargo.toml +++ b/crates/collab/Cargo.toml @@ -51,7 +51,7 @@ tokio = { version = "1", features = ["full"] } tokio-tungstenite = "0.17" tonic = "0.6" tower = "0.4" -toml = "0.5.8" +toml.workspace = true tracing = "0.1.34" tracing-log = "0.1.3" tracing-subscriber = { version = "0.3.11", features = ["env-filter", "json"] } diff --git a/crates/collab/src/tests.rs b/crates/collab/src/tests.rs index 64768d1a4795679cfd54a8f7231d2c79488f01f7..3c571327eb2c7ab4482779de9e3ec5c7936304ef 100644 --- a/crates/collab/src/tests.rs +++ b/crates/collab/src/tests.rs @@ -19,7 +19,7 @@ use gpui::{ use language::LanguageRegistry; use parking_lot::Mutex; use project::{Project, WorktreeId}; -use settings::Settings; +use settings::SettingsStore; use std::{ cell::{Ref, RefCell, RefMut}, env, @@ -30,7 +30,6 @@ use std::{ Arc, }, }; -use theme::ThemeRegistry; use util::http::FakeHttpClient; use workspace::Workspace; @@ -102,7 +101,7 @@ impl TestServer { async fn create_client(&mut self, cx: &mut TestAppContext, name: &str) -> TestClient { cx.update(|cx| { - cx.set_global(Settings::test(cx)); + cx.set_global(SettingsStore::test(cx)); }); let http = FakeHttpClient::with_404_response(); @@ -191,7 +190,6 @@ impl TestServer { client: client.clone(), user_store: user_store.clone(), languages: Arc::new(LanguageRegistry::test()), - themes: ThemeRegistry::new((), cx.font_cache()), fs: fs.clone(), build_window_options: |_, _, _| Default::default(), initialize_workspace: |_, _, _| unimplemented!(), @@ -199,8 +197,12 @@ impl TestServer { background_actions: || &[], }); - Project::init(&client); cx.update(|cx| { + theme::init((), cx); + Project::init(&client, cx); + client::init(&client, cx); + language::init(cx); + editor::init_settings(cx); workspace::init(app_state.clone(), cx); call::init(client.clone(), user_store.clone(), cx); }); diff --git a/crates/collab/src/tests/integration_tests.rs b/crates/collab/src/tests/integration_tests.rs index 47455c0a704e55d834948fa72fee555d687df79a..d0625066d595c04d9fd99c51776b1018e8d05b6e 100644 --- a/crates/collab/src/tests/integration_tests.rs +++ b/crates/collab/src/tests/integration_tests.rs @@ -18,6 +18,7 @@ use gpui::{ }; use indoc::indoc; use language::{ + language_settings::{AllLanguageSettings, Formatter}, tree_sitter_rust, Anchor, Diagnostic, DiagnosticEntry, FakeLspAdapter, Language, LanguageConfig, OffsetRangeExt, Point, Rope, }; @@ -26,7 +27,7 @@ use lsp::LanguageServerId; use project::{search::SearchQuery, DiagnosticSummary, HoverBlockKind, Project, ProjectPath}; use rand::prelude::*; use serde_json::json; -use settings::{Formatter, Settings}; +use settings::SettingsStore; use std::{ cell::{Cell, RefCell}, env, future, mem, @@ -1438,7 +1439,6 @@ async fn test_host_disconnect( cx_b: &mut TestAppContext, cx_c: &mut TestAppContext, ) { - cx_b.update(editor::init); deterministic.forbid_parking(); let mut server = TestServer::start(&deterministic).await; let client_a = server.create_client(cx_a, "user_a").await; @@ -1448,6 +1448,8 @@ async fn test_host_disconnect( .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b), (&client_c, cx_c)]) .await; + cx_b.update(editor::init); + client_a .fs .insert_tree( @@ -1545,7 +1547,6 @@ async fn test_project_reconnect( cx_a: &mut TestAppContext, cx_b: &mut TestAppContext, ) { - cx_b.update(editor::init); deterministic.forbid_parking(); let mut server = TestServer::start(&deterministic).await; let client_a = server.create_client(cx_a, "user_a").await; @@ -1554,6 +1555,8 @@ async fn test_project_reconnect( .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)]) .await; + cx_b.update(editor::init); + client_a .fs .insert_tree( @@ -4367,10 +4370,12 @@ async fn test_formatting_buffer( // Ensure buffer can be formatted using an external command. Notice how the // host's configuration is honored as opposed to using the guest's settings. cx_a.update(|cx| { - cx.update_global(|settings: &mut Settings, _| { - settings.editor_defaults.formatter = Some(Formatter::External { - command: "awk".to_string(), - arguments: vec!["{sub(/two/,\"{buffer_path}\")}1".to_string()], + cx.update_global(|store: &mut SettingsStore, cx| { + store.update_user_settings::(cx, |file| { + file.defaults.formatter = Some(Formatter::External { + command: "awk".into(), + arguments: vec!["{sub(/two/,\"{buffer_path}\")}1".to_string()].into(), + }); }); }); }); @@ -5137,7 +5142,6 @@ async fn test_collaborating_with_code_actions( cx_b: &mut TestAppContext, ) { deterministic.forbid_parking(); - cx_b.update(editor::init); let mut server = TestServer::start(&deterministic).await; let client_a = server.create_client(cx_a, "user_a").await; let client_b = server.create_client(cx_b, "user_b").await; @@ -5146,6 +5150,8 @@ async fn test_collaborating_with_code_actions( .await; let active_call_a = cx_a.read(ActiveCall::global); + cx_b.update(editor::init); + // Set up a fake language server. let mut language = Language::new( LanguageConfig { @@ -5350,7 +5356,6 @@ async fn test_collaborating_with_renames( cx_b: &mut TestAppContext, ) { deterministic.forbid_parking(); - cx_b.update(editor::init); let mut server = TestServer::start(&deterministic).await; let client_a = server.create_client(cx_a, "user_a").await; let client_b = server.create_client(cx_b, "user_b").await; @@ -5359,6 +5364,8 @@ async fn test_collaborating_with_renames( .await; let active_call_a = cx_a.read(ActiveCall::global); + cx_b.update(editor::init); + // Set up a fake language server. let mut language = Language::new( LanguageConfig { @@ -5540,8 +5547,6 @@ async fn test_language_server_statuses( cx_b: &mut TestAppContext, ) { deterministic.forbid_parking(); - - cx_b.update(editor::init); let mut server = TestServer::start(&deterministic).await; let client_a = server.create_client(cx_a, "user_a").await; let client_b = server.create_client(cx_b, "user_b").await; @@ -5550,6 +5555,8 @@ async fn test_language_server_statuses( .await; let active_call_a = cx_a.read(ActiveCall::global); + cx_b.update(editor::init); + // Set up a fake language server. let mut language = Language::new( LanguageConfig { @@ -6257,8 +6264,6 @@ async fn test_basic_following( cx_d: &mut TestAppContext, ) { deterministic.forbid_parking(); - cx_a.update(editor::init); - cx_b.update(editor::init); let mut server = TestServer::start(&deterministic).await; let client_a = server.create_client(cx_a, "user_a").await; @@ -6276,6 +6281,9 @@ async fn test_basic_following( let active_call_a = cx_a.read(ActiveCall::global); let active_call_b = cx_b.read(ActiveCall::global); + cx_a.update(editor::init); + cx_b.update(editor::init); + client_a .fs .insert_tree( @@ -6854,9 +6862,6 @@ async fn test_following_tab_order( cx_a: &mut TestAppContext, cx_b: &mut TestAppContext, ) { - cx_a.update(editor::init); - cx_b.update(editor::init); - let mut server = TestServer::start(&deterministic).await; let client_a = server.create_client(cx_a, "user_a").await; let client_b = server.create_client(cx_b, "user_b").await; @@ -6866,6 +6871,9 @@ async fn test_following_tab_order( let active_call_a = cx_a.read(ActiveCall::global); let active_call_b = cx_b.read(ActiveCall::global); + cx_a.update(editor::init); + cx_b.update(editor::init); + client_a .fs .insert_tree( @@ -6976,9 +6984,6 @@ async fn test_peers_following_each_other( cx_b: &mut TestAppContext, ) { deterministic.forbid_parking(); - cx_a.update(editor::init); - cx_b.update(editor::init); - let mut server = TestServer::start(&deterministic).await; let client_a = server.create_client(cx_a, "user_a").await; let client_b = server.create_client(cx_b, "user_b").await; @@ -6988,6 +6993,9 @@ async fn test_peers_following_each_other( let active_call_a = cx_a.read(ActiveCall::global); let active_call_b = cx_b.read(ActiveCall::global); + cx_a.update(editor::init); + cx_b.update(editor::init); + // Client A shares a project. client_a .fs @@ -7147,8 +7155,6 @@ async fn test_auto_unfollowing( cx_b: &mut TestAppContext, ) { deterministic.forbid_parking(); - cx_a.update(editor::init); - cx_b.update(editor::init); // 2 clients connect to a server. let mut server = TestServer::start(&deterministic).await; @@ -7160,6 +7166,9 @@ async fn test_auto_unfollowing( let active_call_a = cx_a.read(ActiveCall::global); let active_call_b = cx_b.read(ActiveCall::global); + cx_a.update(editor::init); + cx_b.update(editor::init); + // Client A shares a project. client_a .fs @@ -7314,8 +7323,6 @@ async fn test_peers_simultaneously_following_each_other( cx_b: &mut TestAppContext, ) { deterministic.forbid_parking(); - cx_a.update(editor::init); - cx_b.update(editor::init); let mut server = TestServer::start(&deterministic).await; let client_a = server.create_client(cx_a, "user_a").await; @@ -7325,6 +7332,9 @@ async fn test_peers_simultaneously_following_each_other( .await; let active_call_a = cx_a.read(ActiveCall::global); + cx_a.update(editor::init); + cx_b.update(editor::init); + client_a.fs.insert_tree("/a", json!({})).await; let (project_a, _) = client_a.build_local_project("/a", cx_a).await; let workspace_a = client_a.build_workspace(&project_a, cx_a); diff --git a/crates/collab/src/tests/randomized_integration_tests.rs b/crates/collab/src/tests/randomized_integration_tests.rs index fe4b6190ed0abb50459c73c67231a56fe5b6d05f..3beff6942aed3b1e3c001bcc9190b206663dbc3b 100644 --- a/crates/collab/src/tests/randomized_integration_tests.rs +++ b/crates/collab/src/tests/randomized_integration_tests.rs @@ -21,7 +21,7 @@ use rand::{ prelude::*, }; use serde::{Deserialize, Serialize}; -use settings::Settings; +use settings::SettingsStore; use std::{ env, ops::Range, @@ -149,8 +149,9 @@ async fn test_random_collaboration( for (client, mut cx) in clients { cx.update(|cx| { + let store = cx.remove_global::(); cx.clear_globals(); - cx.set_global(Settings::test(cx)); + cx.set_global(store); drop(client); }); } diff --git a/crates/collab_ui/src/collab_titlebar_item.rs b/crates/collab_ui/src/collab_titlebar_item.rs index 7374b166ca6e4eb5b2b7aae5304c556fea5ff526..eb1755a9ff3c25494c93c5e320362d08da45c56b 100644 --- a/crates/collab_ui/src/collab_titlebar_item.rs +++ b/crates/collab_ui/src/collab_titlebar_item.rs @@ -18,7 +18,6 @@ use gpui::{ ViewContext, ViewHandle, WeakViewHandle, }; use project::Project; -use settings::Settings; use std::{ops::Range, sync::Arc}; use theme::{AvatarStyle, Theme}; use util::ResultExt; @@ -70,7 +69,7 @@ impl View for CollabTitlebarItem { }; let project = self.project.read(cx); - let theme = cx.global::().theme.clone(); + let theme = theme::current(cx).clone(); let mut left_container = Flex::row(); let mut right_container = Flex::row().align_children_center(); @@ -298,7 +297,7 @@ impl CollabTitlebarItem { } pub fn toggle_user_menu(&mut self, _: &ToggleUserMenu, cx: &mut ViewContext) { - let theme = cx.global::().theme.clone(); + let theme = theme::current(cx).clone(); let avatar_style = theme.workspace.titlebar.leader_avatar.clone(); let item_style = theme.context_menu.item.disabled_style().clone(); self.user_menu.update(cx, |user_menu, cx| { @@ -866,7 +865,7 @@ impl CollabTitlebarItem { ) -> Option> { enum ConnectionStatusButton {} - let theme = &cx.global::().theme.clone(); + let theme = &theme::current(cx).clone(); match status { client::Status::ConnectionError | client::Status::ConnectionLost diff --git a/crates/collab_ui/src/contact_finder.rs b/crates/collab_ui/src/contact_finder.rs index 8530867f1479dd653a90b974f2840307bd461ecf..b5f2416a5bc51e1d4bbe763082f77ff80b75922d 100644 --- a/crates/collab_ui/src/contact_finder.rs +++ b/crates/collab_ui/src/contact_finder.rs @@ -1,7 +1,6 @@ use client::{ContactRequestStatus, User, UserStore}; use gpui::{elements::*, AppContext, ModelHandle, MouseState, Task, ViewContext}; use picker::{Picker, PickerDelegate, PickerEvent}; -use settings::Settings; use std::sync::Arc; use util::TryFutureExt; @@ -98,7 +97,7 @@ impl PickerDelegate for ContactFinderDelegate { selected: bool, cx: &gpui::AppContext, ) -> AnyElement> { - let theme = &cx.global::().theme; + let theme = &theme::current(cx); let user = &self.potential_contacts[ix]; let request_status = self.user_store.read(cx).contact_request_status(user); diff --git a/crates/collab_ui/src/contact_list.rs b/crates/collab_ui/src/contact_list.rs index 452867b8c4dd6d74a8ba23b77120f7472dbde7e1..e8dae210c4c5ed196207fefb285c21c5a25bd1e1 100644 --- a/crates/collab_ui/src/contact_list.rs +++ b/crates/collab_ui/src/contact_list.rs @@ -14,7 +14,6 @@ use gpui::{ use menu::{Confirm, SelectNext, SelectPrev}; use project::Project; use serde::Deserialize; -use settings::Settings; use std::{mem, sync::Arc}; use theme::IconButton; use workspace::Workspace; @@ -192,7 +191,7 @@ impl ContactList { .detach(); let list_state = ListState::::new(0, Orientation::Top, 1000., move |this, ix, cx| { - let theme = cx.global::().theme.clone(); + let theme = theme::current(cx).clone(); let is_selected = this.selection == Some(ix); let current_project_id = this.project.read(cx).remote_id(); @@ -1313,7 +1312,7 @@ impl View for ContactList { fn render(&mut self, cx: &mut ViewContext) -> AnyElement { enum AddContact {} - let theme = cx.global::().theme.clone(); + let theme = theme::current(cx).clone(); Flex::column() .with_child( diff --git a/crates/collab_ui/src/contacts_popover.rs b/crates/collab_ui/src/contacts_popover.rs index 35734d81f48c132aa41259f47420680977ea4ed8..1d6d1c84c7353faf85556f621fdc8d1b372b444a 100644 --- a/crates/collab_ui/src/contacts_popover.rs +++ b/crates/collab_ui/src/contacts_popover.rs @@ -9,7 +9,6 @@ use gpui::{ }; use picker::PickerEvent; use project::Project; -use settings::Settings; use workspace::Workspace; actions!(contacts_popover, [ToggleContactFinder]); @@ -108,7 +107,7 @@ impl View for ContactsPopover { } fn render(&mut self, cx: &mut ViewContext) -> AnyElement { - let theme = cx.global::().theme.clone(); + let theme = theme::current(cx).clone(); let child = match &self.child { Child::ContactList(child) => ChildView::new(child, cx), Child::ContactFinder(child) => ChildView::new(child, cx), diff --git a/crates/collab_ui/src/incoming_call_notification.rs b/crates/collab_ui/src/incoming_call_notification.rs index 35484b33090fb6eacc4cb41de8e233b92a19a16d..ed3e6485552eeb8f78173b47dbd58c1dcafd856e 100644 --- a/crates/collab_ui/src/incoming_call_notification.rs +++ b/crates/collab_ui/src/incoming_call_notification.rs @@ -9,7 +9,6 @@ use gpui::{ platform::{CursorStyle, MouseButton, WindowBounds, WindowKind, WindowOptions}, AnyElement, AppContext, Entity, View, ViewContext, }; -use settings::Settings; use util::ResultExt; use workspace::AppState; @@ -26,7 +25,7 @@ pub fn init(app_state: &Arc, cx: &mut AppContext) { if let Some(incoming_call) = incoming_call { const PADDING: f32 = 16.; let window_size = cx.read(|cx| { - let theme = &cx.global::().theme.incoming_call_notification; + let theme = &theme::current(cx).incoming_call_notification; vec2f(theme.window_width, theme.window_height) }); @@ -106,7 +105,7 @@ impl IncomingCallNotification { } fn render_caller(&self, cx: &mut ViewContext) -> AnyElement { - let theme = &cx.global::().theme.incoming_call_notification; + let theme = &theme::current(cx).incoming_call_notification; let default_project = proto::ParticipantProject::default(); let initial_project = self .call @@ -170,10 +169,11 @@ impl IncomingCallNotification { enum Accept {} enum Decline {} + let theme = theme::current(cx); Flex::column() .with_child( - MouseEventHandler::::new(0, cx, |_, cx| { - let theme = &cx.global::().theme.incoming_call_notification; + MouseEventHandler::::new(0, cx, |_, _| { + let theme = &theme.incoming_call_notification; Label::new("Accept", theme.accept_button.text.clone()) .aligned() .contained() @@ -186,8 +186,8 @@ impl IncomingCallNotification { .flex(1., true), ) .with_child( - MouseEventHandler::::new(0, cx, |_, cx| { - let theme = &cx.global::().theme.incoming_call_notification; + MouseEventHandler::::new(0, cx, |_, _| { + let theme = &theme.incoming_call_notification; Label::new("Decline", theme.decline_button.text.clone()) .aligned() .contained() @@ -200,12 +200,7 @@ impl IncomingCallNotification { .flex(1., true), ) .constrained() - .with_width( - cx.global::() - .theme - .incoming_call_notification - .button_width, - ) + .with_width(theme.incoming_call_notification.button_width) .into_any() } } @@ -220,12 +215,7 @@ impl View for IncomingCallNotification { } fn render(&mut self, cx: &mut ViewContext) -> AnyElement { - let background = cx - .global::() - .theme - .incoming_call_notification - .background; - + let background = theme::current(cx).incoming_call_notification.background; Flex::row() .with_child(self.render_caller(cx)) .with_child(self.render_buttons(cx)) diff --git a/crates/collab_ui/src/notifications.rs b/crates/collab_ui/src/notifications.rs index 1dec5a8411205cbb210bb082428e412a91fab897..abeb65b1dcc2d876e45dfa1d69fc3fb86b9d406d 100644 --- a/crates/collab_ui/src/notifications.rs +++ b/crates/collab_ui/src/notifications.rs @@ -4,7 +4,6 @@ use gpui::{ platform::{CursorStyle, MouseButton}, AnyElement, Element, View, ViewContext, }; -use settings::Settings; use std::sync::Arc; enum Dismiss {} @@ -22,7 +21,7 @@ where F: 'static + Fn(&mut V, &mut ViewContext), V: View, { - let theme = cx.global::().theme.clone(); + let theme = theme::current(cx).clone(); let theme = &theme.contact_notification; Flex::column() diff --git a/crates/collab_ui/src/project_shared_notification.rs b/crates/collab_ui/src/project_shared_notification.rs index 8a41368276dded678f6df7b60732175eaf5c8fd0..155209f47079a2587c2bf10814638445031e7ee1 100644 --- a/crates/collab_ui/src/project_shared_notification.rs +++ b/crates/collab_ui/src/project_shared_notification.rs @@ -7,7 +7,6 @@ use gpui::{ platform::{CursorStyle, MouseButton, WindowBounds, WindowKind, WindowOptions}, AppContext, Entity, View, ViewContext, }; -use settings::Settings; use std::sync::{Arc, Weak}; use workspace::AppState; @@ -22,7 +21,7 @@ pub fn init(app_state: &Arc, cx: &mut AppContext) { worktree_root_names, } => { const PADDING: f32 = 16.; - let theme = &cx.global::().theme.project_shared_notification; + let theme = &theme::current(cx).project_shared_notification; let window_size = vec2f(theme.window_width, theme.window_height); for screen in cx.platform().screens() { @@ -109,7 +108,7 @@ impl ProjectSharedNotification { } fn render_owner(&self, cx: &mut ViewContext) -> AnyElement { - let theme = &cx.global::().theme.project_shared_notification; + let theme = &theme::current(cx).project_shared_notification; Flex::row() .with_children(self.owner.avatar.clone().map(|avatar| { Image::from_data(avatar) @@ -167,10 +166,11 @@ impl ProjectSharedNotification { enum Open {} enum Dismiss {} + let theme = theme::current(cx); Flex::column() .with_child( - MouseEventHandler::::new(0, cx, |_, cx| { - let theme = &cx.global::().theme.project_shared_notification; + MouseEventHandler::::new(0, cx, |_, _| { + let theme = &theme.project_shared_notification; Label::new("Open", theme.open_button.text.clone()) .aligned() .contained() @@ -181,8 +181,8 @@ impl ProjectSharedNotification { .flex(1., true), ) .with_child( - MouseEventHandler::::new(0, cx, |_, cx| { - let theme = &cx.global::().theme.project_shared_notification; + MouseEventHandler::::new(0, cx, |_, _| { + let theme = &theme.project_shared_notification; Label::new("Dismiss", theme.dismiss_button.text.clone()) .aligned() .contained() @@ -195,12 +195,7 @@ impl ProjectSharedNotification { .flex(1., true), ) .constrained() - .with_width( - cx.global::() - .theme - .project_shared_notification - .button_width, - ) + .with_width(theme.project_shared_notification.button_width) .into_any() } } @@ -215,11 +210,7 @@ impl View for ProjectSharedNotification { } fn render(&mut self, cx: &mut ViewContext) -> gpui::AnyElement { - let background = cx - .global::() - .theme - .project_shared_notification - .background; + let background = theme::current(cx).project_shared_notification.background; Flex::row() .with_child(self.render_owner(cx)) .with_child(self.render_buttons(cx)) diff --git a/crates/collab_ui/src/sharing_status_indicator.rs b/crates/collab_ui/src/sharing_status_indicator.rs index 9fbe57af65678c140e2fd360673f6eaa7f1cc6d6..3a1dde072f867b0791ae8fab90d6c6328857bb2a 100644 --- a/crates/collab_ui/src/sharing_status_indicator.rs +++ b/crates/collab_ui/src/sharing_status_indicator.rs @@ -6,7 +6,7 @@ use gpui::{ platform::{Appearance, MouseButton}, AnyElement, AppContext, Element, Entity, View, ViewContext, }; -use settings::Settings; +use workspace::WorkspaceSettings; pub fn init(cx: &mut AppContext) { let active_call = ActiveCall::global(cx); @@ -15,7 +15,9 @@ pub fn init(cx: &mut AppContext) { cx.observe(&active_call, move |call, cx| { if let Some(room) = call.read(cx).room() { if room.read(cx).is_screen_sharing() { - if status_indicator.is_none() && cx.global::().show_call_status_icon { + if status_indicator.is_none() + && settings::get::(cx).show_call_status_icon + { status_indicator = Some(cx.add_status_bar_item(|_| SharingStatusIndicator)); } } else if let Some((window_id, _)) = status_indicator.take() { diff --git a/crates/command_palette/Cargo.toml b/crates/command_palette/Cargo.toml index 8ad1843cb677eb5ee00eaddd1a4ab2c5fa9c4f10..95ba452c142dc2fa2c615b000984e2d627a1a2e8 100644 --- a/crates/command_palette/Cargo.toml +++ b/crates/command_palette/Cargo.toml @@ -23,6 +23,7 @@ workspace = { path = "../workspace" } [dev-dependencies] gpui = { path = "../gpui", features = ["test-support"] } editor = { path = "../editor", features = ["test-support"] } +language = { path = "../language", features = ["test-support"] } project = { path = "../project", features = ["test-support"] } serde_json.workspace = true workspace = { path = "../workspace", features = ["test-support"] } diff --git a/crates/command_palette/src/command_palette.rs b/crates/command_palette/src/command_palette.rs index 4e0e776000967fd11185e70d3922e49e5c26c6d7..2ee93a0734dd9b88aae34f07ebda21a8572af513 100644 --- a/crates/command_palette/src/command_palette.rs +++ b/crates/command_palette/src/command_palette.rs @@ -5,7 +5,6 @@ use gpui::{ ViewContext, }; use picker::{Picker, PickerDelegate, PickerEvent}; -use settings::Settings; use std::cmp; use util::ResultExt; use workspace::Workspace; @@ -185,8 +184,7 @@ impl PickerDelegate for CommandPaletteDelegate { ) -> AnyElement> { let mat = &self.matches[ix]; let command = &self.actions[mat.candidate_id]; - let settings = cx.global::(); - let theme = &settings.theme; + let theme = theme::current(cx); let style = theme.picker.item.style_for(mouse_state, selected); let key_style = &theme.command_palette.key.style_for(mouse_state, selected); let keystroke_spacing = theme.command_palette.keystroke_spacing; @@ -294,14 +292,7 @@ mod tests { #[gpui::test] async fn test_command_palette(deterministic: Arc, cx: &mut TestAppContext) { - deterministic.forbid_parking(); - let app_state = cx.update(AppState::test); - - cx.update(|cx| { - editor::init(cx); - workspace::init(app_state.clone(), cx); - init(cx); - }); + let app_state = init_test(cx); let project = Project::test(app_state.fs.clone(), [], cx).await; let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); @@ -369,4 +360,16 @@ mod tests { assert!(palette.delegate().matches.is_empty()) }); } + + fn init_test(cx: &mut TestAppContext) -> Arc { + cx.update(|cx| { + let app_state = AppState::test(cx); + theme::init((), cx); + language::init(cx); + editor::init(cx); + workspace::init(app_state.clone(), cx); + init(cx); + app_state + }) + } } diff --git a/crates/context_menu/src/context_menu.rs b/crates/context_menu/src/context_menu.rs index f0d477e42f84748550dfaa51f157d69e9bb94d51..fb455fe1d0a76cfe2c0fdcc3fff0ec295fb89df8 100644 --- a/crates/context_menu/src/context_menu.rs +++ b/crates/context_menu/src/context_menu.rs @@ -8,7 +8,6 @@ use gpui::{ View, ViewContext, }; use menu::*; -use settings::Settings; use std::{any::TypeId, borrow::Cow, sync::Arc, time::Duration}; pub fn init(cx: &mut AppContext) { @@ -323,7 +322,7 @@ impl ContextMenu { } fn render_menu_for_measurement(&self, cx: &mut ViewContext) -> impl Element { - let style = cx.global::().theme.context_menu.clone(); + let style = theme::current(cx).context_menu.clone(); Flex::row() .with_child( Flex::column().with_children(self.items.iter().enumerate().map(|(ix, item)| { @@ -403,7 +402,7 @@ impl ContextMenu { enum Menu {} enum MenuItem {} - let style = cx.global::().theme.context_menu.clone(); + let style = theme::current(cx).context_menu.clone(); MouseEventHandler::::new(0, cx, |_, cx| { Flex::column() diff --git a/crates/copilot/src/copilot.rs b/crates/copilot/src/copilot.rs index 65d0a19bedb0d90454d492065d0886e596255de5..de9104a6848d504eb78d28ab45da896d771dca29 100644 --- a/crates/copilot/src/copilot.rs +++ b/crates/copilot/src/copilot.rs @@ -10,6 +10,7 @@ use gpui::{ actions, AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle, Task, WeakModelHandle, }; use language::{ + language_settings::{all_language_settings, language_settings}, point_from_lsp, point_to_lsp, Anchor, Bias, Buffer, BufferSnapshot, Language, PointUtf16, ToPointUtf16, }; @@ -17,7 +18,7 @@ use log::{debug, error}; use lsp::{LanguageServer, LanguageServerId}; use node_runtime::NodeRuntime; use request::{LogMessage, StatusNotification}; -use settings::Settings; +use settings::SettingsStore; use smol::{fs, io::BufReader, stream::StreamExt}; use std::{ ffi::OsString, @@ -302,56 +303,34 @@ impl Copilot { node_runtime: Arc, cx: &mut ModelContext, ) -> Self { - cx.observe_global::({ - let http = http.clone(); - let node_runtime = node_runtime.clone(); - move |this, cx| { - if cx.global::().features.copilot { - if matches!(this.server, CopilotServer::Disabled) { - let start_task = cx - .spawn({ - let http = http.clone(); - let node_runtime = node_runtime.clone(); - move |this, cx| { - Self::start_language_server(http, node_runtime, this, cx) - } - }) - .shared(); - this.server = CopilotServer::Starting { task: start_task }; - cx.notify(); - } - } else { - this.server = CopilotServer::Disabled; - cx.notify(); - } - } - }) - .detach(); - - if cx.global::().features.copilot { - let start_task = cx - .spawn({ - let http = http.clone(); - let node_runtime = node_runtime.clone(); - move |this, cx| async { - Self::start_language_server(http, node_runtime, this, cx).await - } - }) - .shared(); + let mut this = Self { + http, + node_runtime, + server: CopilotServer::Disabled, + buffers: Default::default(), + }; + this.enable_or_disable_copilot(cx); + cx.observe_global::(move |this, cx| this.enable_or_disable_copilot(cx)) + .detach(); + this + } - Self { - http, - node_runtime, - server: CopilotServer::Starting { task: start_task }, - buffers: Default::default(), + fn enable_or_disable_copilot(&mut self, cx: &mut ModelContext) { + let http = self.http.clone(); + let node_runtime = self.node_runtime.clone(); + if all_language_settings(cx).copilot_enabled(None, None) { + if matches!(self.server, CopilotServer::Disabled) { + let start_task = cx + .spawn({ + move |this, cx| Self::start_language_server(http, node_runtime, this, cx) + }) + .shared(); + self.server = CopilotServer::Starting { task: start_task }; + cx.notify(); } } else { - Self { - http, - node_runtime, - server: CopilotServer::Disabled, - buffers: Default::default(), - } + self.server = CopilotServer::Disabled; + cx.notify(); } } @@ -805,13 +784,13 @@ impl Copilot { let snapshot = registered_buffer.report_changes(buffer, cx); let buffer = buffer.read(cx); let uri = registered_buffer.uri.clone(); - let settings = cx.global::(); let position = position.to_point_utf16(buffer); - let language = buffer.language_at(position); - let language_name = language.map(|language| language.name()); - let language_name = language_name.as_deref(); - let tab_size = settings.tab_size(language_name); - let hard_tabs = settings.hard_tabs(language_name); + let settings = language_settings( + buffer.language_at(position).map(|l| l.name()).as_deref(), + cx, + ); + let tab_size = settings.tab_size; + let hard_tabs = settings.hard_tabs; let relative_path = buffer .file() .map(|file| file.path().to_path_buf()) diff --git a/crates/copilot/src/sign_in.rs b/crates/copilot/src/sign_in.rs index da3c96956e613941f96d3e58077236f43876f25e..764a0e4df18c3fd1c67db4a4f3ff2ca330026b02 100644 --- a/crates/copilot/src/sign_in.rs +++ b/crates/copilot/src/sign_in.rs @@ -6,7 +6,6 @@ use gpui::{ AnyElement, AnyViewHandle, AppContext, ClipboardItem, Element, Entity, View, ViewContext, ViewHandle, }; -use settings::Settings; use theme::ui::modal; #[derive(PartialEq, Eq, Debug, Clone)] @@ -68,7 +67,7 @@ fn create_copilot_auth_window( cx: &mut AppContext, status: &Status, ) -> ViewHandle { - let window_size = cx.global::().theme.copilot.modal.dimensions(); + let window_size = theme::current(cx).copilot.modal.dimensions(); let window_options = WindowOptions { bounds: WindowBounds::Fixed(RectF::new(Default::default(), window_size)), titlebar: None, @@ -338,7 +337,7 @@ impl View for CopilotCodeVerification { fn render(&mut self, cx: &mut ViewContext) -> AnyElement { enum ConnectModal {} - let style = cx.global::().theme.clone(); + let style = theme::current(cx).clone(); modal::( "Connect Copilot to Zed", diff --git a/crates/copilot_button/Cargo.toml b/crates/copilot_button/Cargo.toml index 2d42b192d95bdfdf528a96140289c4e28709e3b8..ad3febd68c6146bee0688bcdc32060bb00caeb65 100644 --- a/crates/copilot_button/Cargo.toml +++ b/crates/copilot_button/Cargo.toml @@ -12,8 +12,10 @@ doctest = false assets = { path = "../assets" } copilot = { path = "../copilot" } editor = { path = "../editor" } +fs = { path = "../fs" } context_menu = { path = "../context_menu" } gpui = { path = "../gpui" } +language = { path = "../language" } settings = { path = "../settings" } theme = { path = "../theme" } util = { path = "../util" } diff --git a/crates/copilot_button/src/copilot_button.rs b/crates/copilot_button/src/copilot_button.rs index 4b0c9b494a1dc4f6a05455522f95e0301b88b794..73cd8f6a1d8ae4cbec8fe89b53d912a25026e759 100644 --- a/crates/copilot_button/src/copilot_button.rs +++ b/crates/copilot_button/src/copilot_button.rs @@ -2,13 +2,15 @@ use anyhow::Result; use context_menu::{ContextMenu, ContextMenuItem}; use copilot::{Copilot, SignOut, Status}; use editor::{scroll::autoscroll::Autoscroll, Editor}; +use fs::Fs; use gpui::{ elements::*, platform::{CursorStyle, MouseButton}, AnyElement, AppContext, AsyncAppContext, Element, Entity, MouseState, Subscription, View, ViewContext, ViewHandle, WeakViewHandle, WindowContext, }; -use settings::{settings_file::SettingsFile, Settings}; +use language::language_settings::{self, all_language_settings, AllLanguageSettings}; +use settings::{update_settings_file, SettingsStore}; use std::{path::Path, sync::Arc}; use util::{paths, ResultExt}; use workspace::{ @@ -26,6 +28,7 @@ pub struct CopilotButton { editor_enabled: Option, language: Option>, path: Option>, + fs: Arc, } impl Entity for CopilotButton { @@ -38,13 +41,12 @@ impl View for CopilotButton { } fn render(&mut self, cx: &mut ViewContext) -> AnyElement { - let settings = cx.global::(); - - if !settings.features.copilot { + let all_language_settings = &all_language_settings(cx); + if !all_language_settings.copilot.feature_enabled { return Empty::new().into_any(); } - let theme = settings.theme.clone(); + let theme = theme::current(cx).clone(); let active = self.popup_menu.read(cx).visible(); let Some(copilot) = Copilot::global(cx) else { return Empty::new().into_any(); @@ -53,7 +55,7 @@ impl View for CopilotButton { let enabled = self .editor_enabled - .unwrap_or(settings.show_copilot_suggestions(None, None)); + .unwrap_or_else(|| all_language_settings.copilot_enabled(None, None)); Stack::new() .with_child( @@ -143,7 +145,7 @@ impl View for CopilotButton { } impl CopilotButton { - pub fn new(cx: &mut ViewContext) -> Self { + pub fn new(fs: Arc, cx: &mut ViewContext) -> Self { let button_view_id = cx.view_id(); let menu = cx.add_view(|cx| { let mut menu = ContextMenu::new(button_view_id, cx); @@ -155,7 +157,7 @@ impl CopilotButton { Copilot::global(cx).map(|copilot| cx.observe(&copilot, |_, _, cx| cx.notify()).detach()); - cx.observe_global::(move |_, cx| cx.notify()) + cx.observe_global::(move |_, cx| cx.notify()) .detach(); Self { @@ -164,17 +166,19 @@ impl CopilotButton { editor_enabled: None, language: None, path: None, + fs, } } pub fn deploy_copilot_start_menu(&mut self, cx: &mut ViewContext) { let mut menu_options = Vec::with_capacity(2); + let fs = self.fs.clone(); menu_options.push(ContextMenuItem::handler("Sign In", |cx| { initiate_sign_in(cx) })); - menu_options.push(ContextMenuItem::handler("Disable Copilot", |cx| { - hide_copilot(cx) + menu_options.push(ContextMenuItem::handler("Disable Copilot", move |cx| { + hide_copilot(fs.clone(), cx) })); self.popup_menu.update(cx, |menu, cx| { @@ -188,22 +192,26 @@ impl CopilotButton { } pub fn deploy_copilot_menu(&mut self, cx: &mut ViewContext) { - let settings = cx.global::(); - + let fs = self.fs.clone(); let mut menu_options = Vec::with_capacity(8); if let Some(language) = self.language.clone() { - let language_enabled = settings.copilot_enabled_for_language(Some(language.as_ref())); + let fs = fs.clone(); + let language_enabled = + language_settings::language_settings(Some(language.as_ref()), cx) + .show_copilot_suggestions; menu_options.push(ContextMenuItem::handler( format!( "{} Suggestions for {}", if language_enabled { "Hide" } else { "Show" }, language ), - move |cx| toggle_copilot_for_language(language.clone(), cx), + move |cx| toggle_copilot_for_language(language.clone(), fs.clone(), cx), )); } + let settings = settings::get::(cx); + if let Some(path) = self.path.as_ref() { let path_enabled = settings.copilot_enabled_for_path(path); let path = path.clone(); @@ -228,19 +236,19 @@ impl CopilotButton { )); } - let globally_enabled = cx.global::().features.copilot; + let globally_enabled = settings.copilot_enabled(None, None); menu_options.push(ContextMenuItem::handler( if globally_enabled { "Hide Suggestions for All Files" } else { "Show Suggestions for All Files" }, - |cx| toggle_copilot_globally(cx), + move |cx| toggle_copilot_globally(fs.clone(), cx), )); menu_options.push(ContextMenuItem::Separator); - let icon_style = settings.theme.copilot.out_link_icon.clone(); + let icon_style = theme::current(cx).copilot.out_link_icon.clone(); menu_options.push(ContextMenuItem::action( move |state: &mut MouseState, style: &theme::ContextMenuItem| { Flex::row() @@ -266,22 +274,19 @@ impl CopilotButton { pub fn update_enabled(&mut self, editor: ViewHandle, cx: &mut ViewContext) { let editor = editor.read(cx); - let snapshot = editor.buffer().read(cx).snapshot(cx); - let settings = cx.global::(); let suggestion_anchor = editor.selections.newest_anchor().start; - let language_name = snapshot .language_at(suggestion_anchor) .map(|language| language.name()); - let path = snapshot - .file_at(suggestion_anchor) - .map(|file| file.path().clone()); + let path = snapshot.file_at(suggestion_anchor).map(|file| file.path()); - self.editor_enabled = - Some(settings.show_copilot_suggestions(language_name.as_deref(), path.as_deref())); + self.editor_enabled = Some( + all_language_settings(cx) + .copilot_enabled(language_name.as_deref(), path.map(|p| p.as_ref())), + ); self.language = language_name; - self.path = path; + self.path = path.cloned(); cx.notify() } @@ -310,7 +315,7 @@ async fn configure_disabled_globs( let settings_editor = workspace .update(&mut cx, |_, cx| { create_and_open_local_file(&paths::SETTINGS, cx, || { - Settings::initial_user_settings_content(&assets::Assets) + settings::initial_user_settings_content(&assets::Assets) .as_ref() .into() }) @@ -322,10 +327,12 @@ async fn configure_disabled_globs( settings_editor.downgrade().update(&mut cx, |item, cx| { let text = item.buffer().read(cx).snapshot(cx).text(); - let edits = SettingsFile::update_unsaved(&text, cx, |file| { + let settings = cx.global::(); + let edits = settings.edits_for_update::(&text, |file| { let copilot = file.copilot.get_or_insert_with(Default::default); let globs = copilot.disabled_globs.get_or_insert_with(|| { - cx.global::() + settings + .get::(None) .copilot .disabled_globs .clone() @@ -356,32 +363,26 @@ async fn configure_disabled_globs( anyhow::Ok(()) } -fn toggle_copilot_globally(cx: &mut AppContext) { - let show_copilot_suggestions = cx.global::().show_copilot_suggestions(None, None); - SettingsFile::update(cx, move |file_contents| { - file_contents.editor.show_copilot_suggestions = Some((!show_copilot_suggestions).into()) +fn toggle_copilot_globally(fs: Arc, cx: &mut AppContext) { + let show_copilot_suggestions = all_language_settings(cx).copilot_enabled(None, None); + update_settings_file::(fs, cx, move |file| { + file.defaults.show_copilot_suggestions = Some((!show_copilot_suggestions).into()) }); } -fn toggle_copilot_for_language(language: Arc, cx: &mut AppContext) { - let show_copilot_suggestions = cx - .global::() - .show_copilot_suggestions(Some(&language), None); - - SettingsFile::update(cx, move |file_contents| { - file_contents.languages.insert( - language, - settings::EditorSettings { - show_copilot_suggestions: Some((!show_copilot_suggestions).into()), - ..Default::default() - }, - ); +fn toggle_copilot_for_language(language: Arc, fs: Arc, cx: &mut AppContext) { + let show_copilot_suggestions = all_language_settings(cx).copilot_enabled(Some(&language), None); + update_settings_file::(fs, cx, move |file| { + file.languages + .entry(language) + .or_default() + .show_copilot_suggestions = Some(!show_copilot_suggestions); }); } -fn hide_copilot(cx: &mut AppContext) { - SettingsFile::update(cx, move |file_contents| { - file_contents.features.copilot = Some(false) +fn hide_copilot(fs: Arc, cx: &mut AppContext) { + update_settings_file::(fs, cx, move |file| { + file.features.get_or_insert(Default::default()).copilot = Some(false); }); } diff --git a/crates/diagnostics/Cargo.toml b/crates/diagnostics/Cargo.toml index 9d455f520e2d9fa08114a50d9740334f8d0c7f11..4e898cca0a523c63ddc834fedfef34f40753efae 100644 --- a/crates/diagnostics/Cargo.toml +++ b/crates/diagnostics/Cargo.toml @@ -31,6 +31,7 @@ language = { path = "../language", features = ["test-support"] } lsp = { path = "../lsp", features = ["test-support"] } gpui = { path = "../gpui", features = ["test-support"] } workspace = { path = "../workspace", features = ["test-support"] } +theme = { path = "../theme", features = ["test-support"] } serde_json.workspace = true unindent.workspace = true diff --git a/crates/diagnostics/src/diagnostics.rs b/crates/diagnostics/src/diagnostics.rs index d82c653a0924dfd8fc12e3f795f99be7a975756d..a202a6082c0522901a349c9537e49a2fe7e2c6b8 100644 --- a/crates/diagnostics/src/diagnostics.rs +++ b/crates/diagnostics/src/diagnostics.rs @@ -20,7 +20,6 @@ use language::{ use lsp::LanguageServerId; use project::{DiagnosticSummary, Project, ProjectPath}; use serde_json::json; -use settings::Settings; use smallvec::SmallVec; use std::{ any::{Any, TypeId}, @@ -30,6 +29,7 @@ use std::{ path::PathBuf, sync::Arc, }; +use theme::ThemeSettings; use util::TryFutureExt; use workspace::{ item::{BreadcrumbText, Item, ItemEvent, ItemHandle}, @@ -89,7 +89,7 @@ impl View for ProjectDiagnosticsEditor { fn render(&mut self, cx: &mut ViewContext) -> AnyElement { if self.path_states.is_empty() { - let theme = &cx.global::().theme.project_diagnostics; + let theme = &theme::current(cx).project_diagnostics; Label::new("No problems in workspace", theme.empty_message.clone()) .aligned() .contained() @@ -537,7 +537,7 @@ impl Item for ProjectDiagnosticsEditor { render_summary( &self.summary, &style.label.text, - &cx.global::().theme.project_diagnostics, + &theme::current(cx).project_diagnostics, ) } @@ -679,10 +679,10 @@ impl Item for ProjectDiagnosticsEditor { fn diagnostic_header_renderer(diagnostic: Diagnostic) -> RenderBlock { let (message, highlights) = highlight_diagnostic_message(Vec::new(), &diagnostic.message); Arc::new(move |cx| { - let settings = cx.global::(); + let settings = settings::get::(cx); let theme = &settings.theme.editor; let style = theme.diagnostic_header.clone(); - let font_size = (style.text_scale_factor * settings.buffer_font_size).round(); + let font_size = (style.text_scale_factor * settings.buffer_font_size(cx)).round(); let icon_width = cx.em_width * style.icon_width_factor; let icon = if diagnostic.severity == DiagnosticSeverity::ERROR { Svg::new("icons/circle_x_mark_12.svg") @@ -818,33 +818,35 @@ mod tests { use language::{Diagnostic, DiagnosticEntry, DiagnosticSeverity, PointUtf16, Unclipped}; use project::FakeFs; use serde_json::json; + use settings::SettingsStore; use unindent::Unindent as _; #[gpui::test] async fn test_diagnostics(cx: &mut TestAppContext) { - Settings::test_async(cx); + init_test(cx); + let fs = FakeFs::new(cx.background()); fs.insert_tree( "/test", json!({ "consts.rs": " - const a: i32 = 'a'; - const b: i32 = c; - " + const a: i32 = 'a'; + const b: i32 = c; + " .unindent(), "main.rs": " - fn main() { - let x = vec![]; - let y = vec![]; - a(x); - b(y); - // comment 1 - // comment 2 - c(y); - d(x); - } - " + fn main() { + let x = vec![]; + let y = vec![]; + a(x); + b(y); + // comment 1 + // comment 2 + c(y); + d(x); + } + " .unindent(), }), ) @@ -1225,7 +1227,8 @@ mod tests { #[gpui::test] async fn test_diagnostics_multiple_servers(cx: &mut TestAppContext) { - Settings::test_async(cx); + init_test(cx); + let fs = FakeFs::new(cx.background()); fs.insert_tree( "/test", @@ -1489,6 +1492,16 @@ mod tests { }); } + fn init_test(cx: &mut TestAppContext) { + cx.update(|cx| { + cx.set_global(SettingsStore::test(cx)); + theme::init((), cx); + language::init(cx); + client::init_settings(cx); + workspace::init_settings(cx); + }); + } + fn editor_blocks(editor: &ViewHandle, cx: &mut WindowContext) -> Vec<(u32, String)> { editor.update(cx, |editor, cx| { let snapshot = editor.snapshot(cx); diff --git a/crates/diagnostics/src/items.rs b/crates/diagnostics/src/items.rs index f0ceacc6194326a221454edeeb07f385421f5054..f84846eae1e28d4a1dcd89172000983a6219f268 100644 --- a/crates/diagnostics/src/items.rs +++ b/crates/diagnostics/src/items.rs @@ -7,7 +7,6 @@ use gpui::{ }; use language::Diagnostic; use lsp::LanguageServerId; -use settings::Settings; use workspace::{item::ItemHandle, StatusItemView, Workspace}; use crate::ProjectDiagnosticsEditor; @@ -92,13 +91,12 @@ impl View for DiagnosticIndicator { enum Summary {} enum Message {} - let tooltip_style = cx.global::().theme.tooltip.clone(); + let tooltip_style = theme::current(cx).tooltip.clone(); let in_progress = !self.in_progress_checks.is_empty(); let mut element = Flex::row().with_child( MouseEventHandler::::new(0, cx, |state, cx| { - let style = cx - .global::() - .theme + let theme = theme::current(cx); + let style = theme .workspace .status_bar .diagnostic_summary @@ -184,7 +182,7 @@ impl View for DiagnosticIndicator { .into_any(), ); - let style = &cx.global::().theme.workspace.status_bar; + let style = &theme::current(cx).workspace.status_bar; let item_spacing = style.item_spacing; if in_progress { diff --git a/crates/editor/Cargo.toml b/crates/editor/Cargo.toml index e4767a12e24945e3390dfdafc37cfe02c5a694d2..fc7bf4b8abad6732ab338e439db6f30ba2f49e83 100644 --- a/crates/editor/Cargo.toml +++ b/crates/editor/Cargo.toml @@ -49,6 +49,7 @@ workspace = { path = "../workspace" } aho-corasick = "0.7" anyhow.workspace = true futures.workspace = true +glob.workspace = true indoc = "1.0.4" itertools = "0.10" lazy_static.workspace = true @@ -58,6 +59,7 @@ parking_lot.workspace = true postage.workspace = true pulldown-cmark = { version = "0.9.2", default-features = false } rand = { workspace = true, optional = true } +schemars.workspace = true serde.workspace = true serde_derive.workspace = true smallvec.workspace = true diff --git a/crates/editor/src/blink_manager.rs b/crates/editor/src/blink_manager.rs index 409b6f9b0344a3f5ee9f109bab82991772a15221..24ea4774aa5deda8c45154d7b8bf7c4f30a4709c 100644 --- a/crates/editor/src/blink_manager.rs +++ b/crates/editor/src/blink_manager.rs @@ -1,8 +1,8 @@ -use std::time::Duration; - +use crate::EditorSettings; use gpui::{Entity, ModelContext}; -use settings::Settings; +use settings::SettingsStore; use smol::Timer; +use std::time::Duration; pub struct BlinkManager { blink_interval: Duration, @@ -15,8 +15,8 @@ pub struct BlinkManager { impl BlinkManager { pub fn new(blink_interval: Duration, cx: &mut ModelContext) -> Self { - cx.observe_global::(move |this, cx| { - // Make sure we blink the cursors if the setting is re-enabled + // Make sure we blink the cursors if the setting is re-enabled + cx.observe_global::(move |this, cx| { this.blink_cursors(this.blink_epoch, cx) }) .detach(); @@ -64,7 +64,7 @@ impl BlinkManager { } fn blink_cursors(&mut self, epoch: usize, cx: &mut ModelContext) { - if cx.global::().cursor_blink { + if settings::get::(cx).cursor_blink { if epoch == self.blink_epoch && self.enabled && !self.blinking_paused { self.visible = !self.visible; cx.notify(); diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index e190ec7717114dd645b46ef650d24ccba4862d92..366e47ddc640fa6c9ee205debdc7b837b205ebe8 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -13,8 +13,9 @@ use gpui::{ fonts::{FontId, HighlightStyle}, Entity, ModelContext, ModelHandle, }; -use language::{OffsetUtf16, Point, Subscription as BufferSubscription}; -use settings::Settings; +use language::{ + language_settings::language_settings, OffsetUtf16, Point, Subscription as BufferSubscription, +}; use std::{any::TypeId, fmt::Debug, num::NonZeroU32, ops::Range, sync::Arc}; pub use suggestion_map::Suggestion; use suggestion_map::SuggestionMap; @@ -276,8 +277,7 @@ impl DisplayMap { .as_singleton() .and_then(|buffer| buffer.read(cx).language()) .map(|language| language.name()); - - cx.global::().tab_size(language_name.as_deref()) + language_settings(language_name.as_deref(), cx).tab_size } #[cfg(test)] @@ -844,8 +844,12 @@ pub mod tests { use super::*; use crate::{movement, test::marked_display_snapshot}; use gpui::{color::Color, elements::*, test::observe, AppContext}; - use language::{Buffer, Language, LanguageConfig, SelectionGoal}; + use language::{ + language_settings::{AllLanguageSettings, AllLanguageSettingsContent}, + Buffer, Language, LanguageConfig, SelectionGoal, + }; use rand::{prelude::*, Rng}; + use settings::SettingsStore; use smol::stream::StreamExt; use std::{env, sync::Arc}; use theme::SyntaxTheme; @@ -882,9 +886,7 @@ pub mod tests { log::info!("wrap width: {:?}", wrap_width); cx.update(|cx| { - let mut settings = Settings::test(cx); - settings.editor_overrides.tab_size = NonZeroU32::new(tab_size); - cx.set_global(settings) + init_test(cx, |s| s.defaults.tab_size = NonZeroU32::new(tab_size)); }); let buffer = cx.update(|cx| { @@ -939,9 +941,11 @@ pub mod tests { tab_size = *tab_sizes.choose(&mut rng).unwrap(); log::info!("setting tab size to {:?}", tab_size); cx.update(|cx| { - let mut settings = Settings::test(cx); - settings.editor_overrides.tab_size = NonZeroU32::new(tab_size); - cx.set_global(settings) + cx.update_global::(|store, cx| { + store.update_user_settings::(cx, |s| { + s.defaults.tab_size = NonZeroU32::new(tab_size); + }); + }); }); } 30..=44 => { @@ -1119,7 +1123,7 @@ pub mod tests { #[gpui::test(retries = 5)] fn test_soft_wraps(cx: &mut AppContext) { cx.foreground().set_block_on_ticks(usize::MAX..=usize::MAX); - cx.foreground().forbid_parking(); + init_test(cx, |_| {}); let font_cache = cx.font_cache(); @@ -1131,7 +1135,6 @@ pub mod tests { .unwrap(); let font_size = 12.0; let wrap_width = Some(64.); - cx.set_global(Settings::test(cx)); let text = "one two three four five\nsix seven eight"; let buffer = MultiBuffer::build_simple(text, cx); @@ -1211,7 +1214,8 @@ pub mod tests { #[gpui::test] fn test_text_chunks(cx: &mut gpui::AppContext) { - cx.set_global(Settings::test(cx)); + init_test(cx, |_| {}); + let text = sample_text(6, 6, 'a'); let buffer = MultiBuffer::build_simple(&text, cx); let family_id = cx @@ -1225,6 +1229,7 @@ pub mod tests { let font_size = 14.0; let map = cx.add_model(|cx| DisplayMap::new(buffer.clone(), font_id, font_size, None, 1, 1, cx)); + buffer.update(cx, |buffer, cx| { buffer.edit( vec![ @@ -1289,11 +1294,8 @@ pub mod tests { .unwrap(), ); language.set_theme(&theme); - cx.update(|cx| { - let mut settings = Settings::test(cx); - settings.editor_defaults.tab_size = Some(2.try_into().unwrap()); - cx.set_global(settings); - }); + + cx.update(|cx| init_test(cx, |s| s.defaults.tab_size = Some(2.try_into().unwrap()))); let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx)); buffer.condition(cx, |buf, _| !buf.is_parsing()).await; @@ -1382,7 +1384,7 @@ pub mod tests { ); language.set_theme(&theme); - cx.update(|cx| cx.set_global(Settings::test(cx))); + cx.update(|cx| init_test(cx, |_| {})); let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx)); buffer.condition(cx, |buf, _| !buf.is_parsing()).await; @@ -1429,9 +1431,8 @@ pub mod tests { #[gpui::test] async fn test_chunks_with_text_highlights(cx: &mut gpui::TestAppContext) { - cx.foreground().set_block_on_ticks(usize::MAX..=usize::MAX); + cx.update(|cx| init_test(cx, |_| {})); - cx.update(|cx| cx.set_global(Settings::test(cx))); let theme = SyntaxTheme::new(vec![ ("operator".to_string(), Color::red().into()), ("string".to_string(), Color::green().into()), @@ -1510,7 +1511,8 @@ pub mod tests { #[gpui::test] fn test_clip_point(cx: &mut gpui::AppContext) { - cx.set_global(Settings::test(cx)); + init_test(cx, |_| {}); + fn assert(text: &str, shift_right: bool, bias: Bias, cx: &mut gpui::AppContext) { let (unmarked_snapshot, mut markers) = marked_display_snapshot(text, cx); @@ -1559,7 +1561,7 @@ pub mod tests { #[gpui::test] fn test_clip_at_line_ends(cx: &mut gpui::AppContext) { - cx.set_global(Settings::test(cx)); + init_test(cx, |_| {}); fn assert(text: &str, cx: &mut gpui::AppContext) { let (mut unmarked_snapshot, markers) = marked_display_snapshot(text, cx); @@ -1578,7 +1580,8 @@ pub mod tests { #[gpui::test] fn test_tabs_with_multibyte_chars(cx: &mut gpui::AppContext) { - cx.set_global(Settings::test(cx)); + init_test(cx, |_| {}); + let text = "✅\t\tα\nβ\t\n🏀β\t\tγ"; let buffer = MultiBuffer::build_simple(text, cx); let font_cache = cx.font_cache(); @@ -1639,7 +1642,8 @@ pub mod tests { #[gpui::test] fn test_max_point(cx: &mut gpui::AppContext) { - cx.set_global(Settings::test(cx)); + init_test(cx, |_| {}); + let buffer = MultiBuffer::build_simple("aaa\n\t\tbbb", cx); let font_cache = cx.font_cache(); let family_id = font_cache @@ -1718,4 +1722,13 @@ pub mod tests { } chunks } + + fn init_test(cx: &mut AppContext, f: impl Fn(&mut AllLanguageSettingsContent)) { + cx.foreground().forbid_parking(); + cx.set_global(SettingsStore::test(cx)); + language::init(cx); + cx.update_global::(|store, cx| { + store.update_user_settings::(cx, f); + }); + } } diff --git a/crates/editor/src/display_map/block_map.rs b/crates/editor/src/display_map/block_map.rs index 93e43f876c47316e74f12a3e5ced4d6a3351b233..05ff9886f1d33827776fa55795a4b0b6b26efd64 100644 --- a/crates/editor/src/display_map/block_map.rs +++ b/crates/editor/src/display_map/block_map.rs @@ -993,7 +993,7 @@ mod tests { use crate::multi_buffer::MultiBuffer; use gpui::{elements::Empty, Element}; use rand::prelude::*; - use settings::Settings; + use settings::SettingsStore; use std::env; use util::RandomCharIter; @@ -1013,7 +1013,7 @@ mod tests { #[gpui::test] fn test_basic_blocks(cx: &mut gpui::AppContext) { - cx.set_global(Settings::test(cx)); + init_test(cx); let family_id = cx .font_cache() @@ -1189,7 +1189,7 @@ mod tests { #[gpui::test] fn test_blocks_on_wrapped_lines(cx: &mut gpui::AppContext) { - cx.set_global(Settings::test(cx)); + init_test(cx); let family_id = cx .font_cache() @@ -1239,7 +1239,7 @@ mod tests { #[gpui::test(iterations = 100)] fn test_random_blocks(cx: &mut gpui::AppContext, mut rng: StdRng) { - cx.set_global(Settings::test(cx)); + init_test(cx); let operations = env::var("OPERATIONS") .map(|i| i.parse().expect("invalid `OPERATIONS` variable")) @@ -1647,6 +1647,11 @@ mod tests { } } + fn init_test(cx: &mut gpui::AppContext) { + cx.set_global(SettingsStore::test(cx)); + theme::init((), cx); + } + impl TransformBlock { fn as_custom(&self) -> Option<&Block> { match self { diff --git a/crates/editor/src/display_map/fold_map.rs b/crates/editor/src/display_map/fold_map.rs index bd3cd1a620ada42b93fb5dd1a46151dbb0f8c37b..6ef1ebce1da844d1cd23185d5d246524716e082f 100644 --- a/crates/editor/src/display_map/fold_map.rs +++ b/crates/editor/src/display_map/fold_map.rs @@ -1204,7 +1204,7 @@ mod tests { use crate::{MultiBuffer, ToPoint}; use collections::HashSet; use rand::prelude::*; - use settings::Settings; + use settings::SettingsStore; use std::{cmp::Reverse, env, mem, sync::Arc}; use sum_tree::TreeMap; use util::test::sample_text; @@ -1213,7 +1213,7 @@ mod tests { #[gpui::test] fn test_basic_folds(cx: &mut gpui::AppContext) { - cx.set_global(Settings::test(cx)); + init_test(cx); let buffer = MultiBuffer::build_simple(&sample_text(5, 6, 'a'), cx); let subscription = buffer.update(cx, |buffer, _| buffer.subscribe()); let buffer_snapshot = buffer.read(cx).snapshot(cx); @@ -1286,7 +1286,7 @@ mod tests { #[gpui::test] fn test_adjacent_folds(cx: &mut gpui::AppContext) { - cx.set_global(Settings::test(cx)); + init_test(cx); let buffer = MultiBuffer::build_simple("abcdefghijkl", cx); let subscription = buffer.update(cx, |buffer, _| buffer.subscribe()); let buffer_snapshot = buffer.read(cx).snapshot(cx); @@ -1349,7 +1349,7 @@ mod tests { #[gpui::test] fn test_merging_folds_via_edit(cx: &mut gpui::AppContext) { - cx.set_global(Settings::test(cx)); + init_test(cx); let buffer = MultiBuffer::build_simple(&sample_text(5, 6, 'a'), cx); let subscription = buffer.update(cx, |buffer, _| buffer.subscribe()); let buffer_snapshot = buffer.read(cx).snapshot(cx); @@ -1400,7 +1400,7 @@ mod tests { #[gpui::test(iterations = 100)] fn test_random_folds(cx: &mut gpui::AppContext, mut rng: StdRng) { - cx.set_global(Settings::test(cx)); + init_test(cx); let operations = env::var("OPERATIONS") .map(|i| i.parse().expect("invalid `OPERATIONS` variable")) .unwrap_or(10); @@ -1676,6 +1676,10 @@ mod tests { assert_eq!(snapshot.buffer_rows(3).collect::>(), [Some(6)]); } + fn init_test(cx: &mut gpui::AppContext) { + cx.set_global(SettingsStore::test(cx)); + } + impl FoldMap { fn merged_fold_ranges(&self) -> Vec> { let buffer = self.buffer.lock().clone(); diff --git a/crates/editor/src/display_map/suggestion_map.rs b/crates/editor/src/display_map/suggestion_map.rs index f48efc76f41bec4dc9a8847f9446f991227e9428..eac903d0af9c7e9c3d9a4bc184ce842e8d07cf52 100644 --- a/crates/editor/src/display_map/suggestion_map.rs +++ b/crates/editor/src/display_map/suggestion_map.rs @@ -578,7 +578,7 @@ mod tests { use crate::{display_map::fold_map::FoldMap, MultiBuffer}; use gpui::AppContext; use rand::{prelude::StdRng, Rng}; - use settings::Settings; + use settings::SettingsStore; use std::{ env, ops::{Bound, RangeBounds}, @@ -631,7 +631,8 @@ mod tests { #[gpui::test(iterations = 100)] fn test_random_suggestions(cx: &mut AppContext, mut rng: StdRng) { - cx.set_global(Settings::test(cx)); + init_test(cx); + let operations = env::var("OPERATIONS") .map(|i| i.parse().expect("invalid `OPERATIONS` variable")) .unwrap_or(10); @@ -834,6 +835,11 @@ mod tests { } } + fn init_test(cx: &mut AppContext) { + cx.set_global(SettingsStore::test(cx)); + theme::init((), cx); + } + impl SuggestionMap { pub fn randomly_mutate( &self, diff --git a/crates/editor/src/display_map/wrap_map.rs b/crates/editor/src/display_map/wrap_map.rs index e1de4fce77fa13b132b934025d2b9583ea6b31c1..478eaf4c7e1a16f56e55286b8e438cda9d78b4b1 100644 --- a/crates/editor/src/display_map/wrap_map.rs +++ b/crates/editor/src/display_map/wrap_map.rs @@ -1043,16 +1043,16 @@ mod tests { }; use gpui::test::observe; use rand::prelude::*; - use settings::Settings; + use settings::SettingsStore; use smol::stream::StreamExt; use std::{cmp, env, num::NonZeroU32}; use text::Rope; #[gpui::test(iterations = 100)] async fn test_random_wraps(cx: &mut gpui::TestAppContext, mut rng: StdRng) { - cx.update(|cx| cx.set_global(Settings::test(cx))); + init_test(cx); + cx.foreground().set_block_on_ticks(0..=50); - cx.foreground().forbid_parking(); let operations = env::var("OPERATIONS") .map(|i| i.parse().expect("invalid `OPERATIONS` variable")) .unwrap_or(10); @@ -1287,6 +1287,14 @@ mod tests { wrap_map.read_with(cx, |map, _| assert!(map.pending_edits.is_empty())); } + fn init_test(cx: &mut gpui::TestAppContext) { + cx.foreground().forbid_parking(); + cx.update(|cx| { + cx.set_global(SettingsStore::test(cx)); + theme::init((), cx); + }); + } + fn wrap_text( unwrapped_text: &str, wrap_width: Option, diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 218daf08c4154534d6c5cca55a5266ce368d7980..0fb7a10a166b7dd31b7b5014b65d1454d33db479 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -1,5 +1,6 @@ mod blink_manager; pub mod display_map; +mod editor_settings; mod element; mod git; @@ -22,12 +23,13 @@ pub mod test; use aho_corasick::AhoCorasick; use anyhow::{anyhow, Result}; use blink_manager::BlinkManager; -use client::ClickhouseEvent; +use client::{ClickhouseEvent, TelemetrySettings}; use clock::ReplicaId; use collections::{BTreeMap, Bound, HashMap, HashSet, VecDeque}; use copilot::Copilot; pub use display_map::DisplayPoint; use display_map::*; +pub use editor_settings::EditorSettings; pub use element::*; use futures::FutureExt; use fuzzy::{StringMatch, StringMatchCandidate}; @@ -51,6 +53,7 @@ pub use items::MAX_TAB_TITLE_LEN; use itertools::Itertools; pub use language::{char_kind, CharKind}; use language::{ + language_settings::{self, all_language_settings}, AutoindentMode, BracketPair, Buffer, CodeAction, CodeLabel, Completion, CursorShape, Diagnostic, DiagnosticSeverity, File, IndentKind, IndentSize, Language, OffsetRangeExt, OffsetUtf16, Point, Selection, SelectionGoal, TransactionId, @@ -70,7 +73,7 @@ use scroll::{ }; use selections_collection::{resolve_multiple, MutableSelectionsCollection, SelectionsCollection}; use serde::{Deserialize, Serialize}; -use settings::Settings; +use settings::SettingsStore; use smallvec::SmallVec; use snippet::Snippet; use std::{ @@ -85,7 +88,7 @@ use std::{ time::{Duration, Instant}, }; pub use sum_tree::Bias; -use theme::{DiagnosticStyle, Theme}; +use theme::{DiagnosticStyle, Theme, ThemeSettings}; use util::{post_inc, RangeExt, ResultExt, TryFutureExt}; use workspace::{ItemNavHistory, ViewId, Workspace}; @@ -286,7 +289,12 @@ pub enum Direction { Next, } +pub fn init_settings(cx: &mut AppContext) { + settings::register::(cx); +} + pub fn init(cx: &mut AppContext) { + init_settings(cx); cx.add_action(Editor::new_file); cx.add_action(Editor::cancel); cx.add_action(Editor::newline); @@ -436,7 +444,7 @@ pub enum EditorMode { Full, } -#[derive(Clone)] +#[derive(Clone, Debug)] pub enum SoftWrap { None, EditorWidth, @@ -471,7 +479,7 @@ pub struct Editor { select_larger_syntax_node_stack: Vec]>>, ime_transaction: Option, active_diagnostics: Option, - soft_wrap_mode_override: Option, + soft_wrap_mode_override: Option, get_field_editor_theme: Option>, override_text_style: Option>, project: Option>, @@ -1238,8 +1246,8 @@ impl Editor { ) -> Self { let editor_view_id = cx.view_id(); let display_map = cx.add_model(|cx| { - let settings = cx.global::(); - let style = build_style(&*settings, get_field_editor_theme.as_deref(), None, cx); + let settings = settings::get::(cx); + let style = build_style(settings, get_field_editor_theme.as_deref(), None, cx); DisplayMap::new( buffer.clone(), style.text.font_id, @@ -1256,7 +1264,7 @@ impl Editor { let blink_manager = cx.add_model(|cx| BlinkManager::new(CURSOR_BLINK_INTERVAL, cx)); let soft_wrap_mode_override = - (mode == EditorMode::SingleLine).then(|| settings::SoftWrap::None); + (mode == EditorMode::SingleLine).then(|| language_settings::SoftWrap::None); let mut project_subscription = None; if mode == EditorMode::Full && buffer.read(cx).is_singleton() { @@ -1320,7 +1328,7 @@ impl Editor { cx.subscribe(&buffer, Self::on_buffer_event), cx.observe(&display_map, Self::on_display_map_changed), cx.observe(&blink_manager, |_, _, cx| cx.notify()), - cx.observe_global::(Self::settings_changed), + cx.observe_global::(Self::settings_changed), ], }; @@ -1419,7 +1427,7 @@ impl Editor { fn style(&self, cx: &AppContext) -> EditorStyle { build_style( - cx.global::(), + settings::get::(cx), self.get_field_editor_theme.as_deref(), self.override_text_style.as_deref(), cx, @@ -2377,7 +2385,7 @@ impl Editor { } fn trigger_completion_on_input(&mut self, text: &str, cx: &mut ViewContext) { - if !cx.global::().show_completions_on_input { + if !settings::get::(cx).show_completions_on_input { return; } @@ -3144,17 +3152,12 @@ impl Editor { snapshot: &MultiBufferSnapshot, cx: &mut ViewContext, ) -> bool { - let settings = cx.global::(); - - let path = snapshot.file_at(location).map(|file| file.path()); + let path = snapshot.file_at(location).map(|file| file.path().as_ref()); let language_name = snapshot .language_at(location) .map(|language| language.name()); - if !settings.show_copilot_suggestions(language_name.as_deref(), path.map(|p| p.as_ref())) { - return false; - } - - true + let settings = all_language_settings(cx); + settings.copilot_enabled(language_name.as_deref(), path) } fn has_active_copilot_suggestion(&self, cx: &AppContext) -> bool { @@ -3455,12 +3458,9 @@ impl Editor { { let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row); - let language_name = buffer - .language_at(line_buffer_range.start) - .map(|language| language.name()); let indent_len = match indent_size.kind { IndentKind::Space => { - cx.global::().tab_size(language_name.as_deref()) + buffer.settings_at(line_buffer_range.start, cx).tab_size } IndentKind::Tab => NonZeroU32::new(1).unwrap(), }; @@ -3572,12 +3572,11 @@ impl Editor { } // Otherwise, insert a hard or soft tab. - let settings = cx.global::(); - let language_name = buffer.language_at(cursor, cx).map(|l| l.name()); - let tab_size = if settings.hard_tabs(language_name.as_deref()) { + let settings = buffer.settings_at(cursor, cx); + let tab_size = if settings.hard_tabs { IndentSize::tab() } else { - let tab_size = settings.tab_size(language_name.as_deref()).get(); + let tab_size = settings.tab_size.get(); let char_column = snapshot .text_for_range(Point::new(cursor.row, 0)..cursor) .flat_map(str::chars) @@ -3630,10 +3629,9 @@ impl Editor { delta_for_start_row: u32, cx: &AppContext, ) -> u32 { - let language_name = buffer.language_at(selection.start, cx).map(|l| l.name()); - let settings = cx.global::(); - let tab_size = settings.tab_size(language_name.as_deref()).get(); - let indent_kind = if settings.hard_tabs(language_name.as_deref()) { + let settings = buffer.settings_at(selection.start, cx); + let tab_size = settings.tab_size.get(); + let indent_kind = if settings.hard_tabs { IndentKind::Tab } else { IndentKind::Space @@ -3702,11 +3700,8 @@ impl Editor { let buffer = self.buffer.read(cx); let snapshot = buffer.snapshot(cx); for selection in &selections { - let language_name = buffer.language_at(selection.start, cx).map(|l| l.name()); - let tab_size = cx - .global::() - .tab_size(language_name.as_deref()) - .get(); + let settings = buffer.settings_at(selection.start, cx); + let tab_size = settings.tab_size.get(); let mut rows = selection.spanned_rows(false, &display_map); // Avoid re-outdenting a row that has already been outdented by a @@ -6467,27 +6462,24 @@ impl Editor { } pub fn soft_wrap_mode(&self, cx: &AppContext) -> SoftWrap { - let language_name = self - .buffer - .read(cx) - .as_singleton() - .and_then(|singleton_buffer| singleton_buffer.read(cx).language()) - .map(|l| l.name()); - - let settings = cx.global::(); + let settings = self.buffer.read(cx).settings_at(0, cx); let mode = self .soft_wrap_mode_override - .unwrap_or_else(|| settings.soft_wrap(language_name.as_deref())); + .unwrap_or_else(|| settings.soft_wrap); match mode { - settings::SoftWrap::None => SoftWrap::None, - settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth, - settings::SoftWrap::PreferredLineLength => { - SoftWrap::Column(settings.preferred_line_length(language_name.as_deref())) + language_settings::SoftWrap::None => SoftWrap::None, + language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth, + language_settings::SoftWrap::PreferredLineLength => { + SoftWrap::Column(settings.preferred_line_length) } } } - pub fn set_soft_wrap_mode(&mut self, mode: settings::SoftWrap, cx: &mut ViewContext) { + pub fn set_soft_wrap_mode( + &mut self, + mode: language_settings::SoftWrap, + cx: &mut ViewContext, + ) { self.soft_wrap_mode_override = Some(mode); cx.notify(); } @@ -6502,8 +6494,8 @@ impl Editor { self.soft_wrap_mode_override.take(); } else { let soft_wrap = match self.soft_wrap_mode(cx) { - SoftWrap::None => settings::SoftWrap::EditorWidth, - SoftWrap::EditorWidth | SoftWrap::Column(_) => settings::SoftWrap::None, + SoftWrap::None => language_settings::SoftWrap::EditorWidth, + SoftWrap::EditorWidth | SoftWrap::Column(_) => language_settings::SoftWrap::None, }; self.soft_wrap_mode_override = Some(soft_wrap); } @@ -6578,8 +6570,8 @@ impl Editor { let buffer = &snapshot.buffer_snapshot; let start = buffer.anchor_before(0); let end = buffer.anchor_after(buffer.len()); - let theme = cx.global::().theme.as_ref(); - self.background_highlights_in_range(start..end, &snapshot, theme) + let theme = theme::current(cx); + self.background_highlights_in_range(start..end, &snapshot, theme.as_ref()) } fn document_highlights_for_position<'a>( @@ -6910,7 +6902,7 @@ impl Editor { .map(|a| a.to_string()); let telemetry = project.read(cx).client().telemetry().clone(); - let telemetry_settings = cx.global::().telemetry(); + let telemetry_settings = *settings::get::(cx); let event = ClickhouseEvent::Copilot { suggestion_id, @@ -6940,33 +6932,37 @@ impl Editor { .and_then(|e| e.to_str()) .map(|a| a.to_string())); - let settings = cx.global::(); + let vim_mode = cx + .global::() + .untyped_user_settings() + .get("vim_mode") + == Some(&serde_json::Value::Bool(true)); + let telemetry_settings = *settings::get::(cx); + let copilot_enabled = all_language_settings(cx).copilot_enabled(None, None); + let copilot_enabled_for_language = self + .buffer + .read(cx) + .settings_at(0, cx) + .show_copilot_suggestions; let telemetry = project.read(cx).client().telemetry().clone(); telemetry.report_mixpanel_event( - match name { - "open" => "open editor", - "save" => "save editor", - _ => name, - }, - json!({ "File Extension": file_extension, "Vim Mode": settings.vim_mode, "In Clickhouse": true }), - settings.telemetry(), - ); + match name { + "open" => "open editor", + "save" => "save editor", + _ => name, + }, + json!({ "File Extension": file_extension, "Vim Mode": vim_mode, "In Clickhouse": true }), + telemetry_settings, + ); let event = ClickhouseEvent::Editor { file_extension, - vim_mode: settings.vim_mode, + vim_mode, operation: name, - copilot_enabled: settings.features.copilot, - copilot_enabled_for_language: settings.show_copilot_suggestions( - self.language_at(0, cx) - .map(|language| language.name()) - .as_deref(), - self.file_at(0, cx) - .map(|file| file.path().clone()) - .as_deref(), - ), + copilot_enabled, + copilot_enabled_for_language, }; - telemetry.report_clickhouse_event(event, settings.telemetry()) + telemetry.report_clickhouse_event(event, telemetry_settings) } /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines, @@ -6998,7 +6994,7 @@ impl Editor { let mut lines = Vec::new(); let mut line: VecDeque = VecDeque::new(); - let theme = &cx.global::().theme.editor.syntax; + let theme = &theme::current(cx).editor.syntax; for chunk in chunks { let highlight = chunk.syntax_highlight_id.and_then(|id| id.name(theme)); @@ -7420,7 +7416,7 @@ impl View for Editor { } fn build_style( - settings: &Settings, + settings: &ThemeSettings, get_field_editor_theme: Option<&GetFieldEditorTheme>, override_text_style: Option<&OverrideTextStyle>, cx: &AppContext, @@ -7450,7 +7446,7 @@ fn build_style( let font_id = font_cache .select_font(font_family_id, &font_properties) .unwrap(); - let font_size = settings.buffer_font_size; + let font_size = settings.buffer_font_size(cx); EditorStyle { text: TextStyle { color: settings.theme.editor.text_color, @@ -7620,10 +7616,10 @@ pub fn diagnostic_block_renderer(diagnostic: Diagnostic, is_valid: bool) -> Rend } Arc::new(move |cx: &mut BlockContext| { - let settings = cx.global::(); + let settings = settings::get::(cx); let theme = &settings.theme.editor; let style = diagnostic_style(diagnostic.severity, is_valid, theme); - let font_size = (style.text_scale_factor * settings.buffer_font_size).round(); + let font_size = (style.text_scale_factor * settings.buffer_font_size(cx)).round(); Flex::column() .with_children(highlighted_lines.iter().map(|(line, highlights)| { Label::new( diff --git a/crates/editor/src/editor_settings.rs b/crates/editor/src/editor_settings.rs new file mode 100644 index 0000000000000000000000000000000000000000..5108d2740875bd38dabea3c4ca06ca78784d1f35 --- /dev/null +++ b/crates/editor/src/editor_settings.rs @@ -0,0 +1,43 @@ +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use settings::Setting; + +#[derive(Deserialize)] +pub struct EditorSettings { + pub cursor_blink: bool, + pub hover_popover_enabled: bool, + pub show_completions_on_input: bool, + pub show_scrollbars: ShowScrollbars, +} + +#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq, Default)] +#[serde(rename_all = "snake_case")] +pub enum ShowScrollbars { + #[default] + Auto, + System, + Always, + Never, +} + +#[derive(Clone, Serialize, Deserialize, JsonSchema)] +pub struct EditorSettingsContent { + pub cursor_blink: Option, + pub hover_popover_enabled: Option, + pub show_completions_on_input: Option, + pub show_scrollbars: Option, +} + +impl Setting for EditorSettings { + const KEY: Option<&'static str> = None; + + type FileContent = EditorSettingsContent; + + fn load( + default_value: &Self::FileContent, + user_values: &[&Self::FileContent], + _: &gpui::AppContext, + ) -> anyhow::Result { + Self::load_via_json_merge(default_value, user_values) + } +} diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index 0f749bde4841ebce9dbee4c012e0e8ff2fab8830..b61c2a780ffa28027e30e48a01a344bdc4d2a4a7 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -12,10 +12,12 @@ use gpui::{ serde_json, TestAppContext, }; use indoc::indoc; -use language::{BracketPairConfig, FakeLspAdapter, LanguageConfig, LanguageRegistry, Point}; +use language::{ + language_settings::{AllLanguageSettings, AllLanguageSettingsContent, LanguageSettingsContent}, + BracketPairConfig, FakeLspAdapter, LanguageConfig, LanguageRegistry, Point, +}; use parking_lot::Mutex; use project::FakeFs; -use settings::EditorSettings; use std::{cell::RefCell, future::Future, rc::Rc, time::Instant}; use unindent::Unindent; use util::{ @@ -29,7 +31,8 @@ use workspace::{ #[gpui::test] fn test_edit_events(cx: &mut TestAppContext) { - cx.update(|cx| cx.set_global(Settings::test(cx))); + init_test(cx, |_| {}); + let buffer = cx.add_model(|cx| { let mut buffer = language::Buffer::new(0, "123456", cx); buffer.set_group_interval(Duration::from_secs(1)); @@ -156,7 +159,8 @@ fn test_edit_events(cx: &mut TestAppContext) { #[gpui::test] fn test_undo_redo_with_selection_restoration(cx: &mut TestAppContext) { - cx.update(|cx| cx.set_global(Settings::test(cx))); + init_test(cx, |_| {}); + let mut now = Instant::now(); let buffer = cx.add_model(|cx| language::Buffer::new(0, "123456", cx)); let group_interval = buffer.read_with(cx, |buffer, _| buffer.transaction_group_interval()); @@ -226,7 +230,8 @@ fn test_undo_redo_with_selection_restoration(cx: &mut TestAppContext) { #[gpui::test] fn test_ime_composition(cx: &mut TestAppContext) { - cx.update(|cx| cx.set_global(Settings::test(cx))); + init_test(cx, |_| {}); + let buffer = cx.add_model(|cx| { let mut buffer = language::Buffer::new(0, "abcde", cx); // Ensure automatic grouping doesn't occur. @@ -328,7 +333,7 @@ fn test_ime_composition(cx: &mut TestAppContext) { #[gpui::test] fn test_selection_with_mouse(cx: &mut TestAppContext) { - cx.update(|cx| cx.set_global(Settings::test(cx))); + init_test(cx, |_| {}); let (_, editor) = cx.add_window(|cx| { let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx); @@ -395,7 +400,8 @@ fn test_selection_with_mouse(cx: &mut TestAppContext) { #[gpui::test] fn test_canceling_pending_selection(cx: &mut TestAppContext) { - cx.update(|cx| cx.set_global(Settings::test(cx))); + init_test(cx, |_| {}); + let (_, view) = cx.add_window(|cx| { let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx); build_editor(buffer, cx) @@ -429,6 +435,8 @@ fn test_canceling_pending_selection(cx: &mut TestAppContext) { #[gpui::test] fn test_clone(cx: &mut TestAppContext) { + init_test(cx, |_| {}); + let (text, selection_ranges) = marked_text_ranges( indoc! {" one @@ -439,7 +447,6 @@ fn test_clone(cx: &mut TestAppContext) { "}, true, ); - cx.update(|cx| cx.set_global(Settings::test(cx))); let (_, editor) = cx.add_window(|cx| { let buffer = MultiBuffer::build_simple(&text, cx); @@ -487,7 +494,8 @@ fn test_clone(cx: &mut TestAppContext) { #[gpui::test] async fn test_navigation_history(cx: &mut TestAppContext) { - cx.update(|cx| cx.set_global(Settings::test(cx))); + init_test(cx, |_| {}); + cx.set_global(DragAndDrop::::default()); use workspace::item::Item; @@ -600,7 +608,8 @@ async fn test_navigation_history(cx: &mut TestAppContext) { #[gpui::test] fn test_cancel(cx: &mut TestAppContext) { - cx.update(|cx| cx.set_global(Settings::test(cx))); + init_test(cx, |_| {}); + let (_, view) = cx.add_window(|cx| { let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx); build_editor(buffer, cx) @@ -642,7 +651,8 @@ fn test_cancel(cx: &mut TestAppContext) { #[gpui::test] fn test_fold_action(cx: &mut TestAppContext) { - cx.update(|cx| cx.set_global(Settings::test(cx))); + init_test(cx, |_| {}); + let (_, view) = cx.add_window(|cx| { let buffer = MultiBuffer::build_simple( &" @@ -731,7 +741,8 @@ fn test_fold_action(cx: &mut TestAppContext) { #[gpui::test] fn test_move_cursor(cx: &mut TestAppContext) { - cx.update(|cx| cx.set_global(Settings::test(cx))); + init_test(cx, |_| {}); + let buffer = cx.update(|cx| MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx)); let (_, view) = cx.add_window(|cx| build_editor(buffer.clone(), cx)); @@ -806,7 +817,8 @@ fn test_move_cursor(cx: &mut TestAppContext) { #[gpui::test] fn test_move_cursor_multibyte(cx: &mut TestAppContext) { - cx.update(|cx| cx.set_global(Settings::test(cx))); + init_test(cx, |_| {}); + let (_, view) = cx.add_window(|cx| { let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcde\nαβγδε\n", cx); build_editor(buffer.clone(), cx) @@ -910,7 +922,8 @@ fn test_move_cursor_multibyte(cx: &mut TestAppContext) { #[gpui::test] fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) { - cx.update(|cx| cx.set_global(Settings::test(cx))); + init_test(cx, |_| {}); + let (_, view) = cx.add_window(|cx| { let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx); build_editor(buffer.clone(), cx) @@ -959,7 +972,8 @@ fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) { #[gpui::test] fn test_beginning_end_of_line(cx: &mut TestAppContext) { - cx.update(|cx| cx.set_global(Settings::test(cx))); + init_test(cx, |_| {}); + let (_, view) = cx.add_window(|cx| { let buffer = MultiBuffer::build_simple("abc\n def", cx); build_editor(buffer, cx) @@ -1121,7 +1135,8 @@ fn test_beginning_end_of_line(cx: &mut TestAppContext) { #[gpui::test] fn test_prev_next_word_boundary(cx: &mut TestAppContext) { - cx.update(|cx| cx.set_global(Settings::test(cx))); + init_test(cx, |_| {}); + let (_, view) = cx.add_window(|cx| { let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n {baz.qux()}", cx); build_editor(buffer, cx) @@ -1172,7 +1187,8 @@ fn test_prev_next_word_boundary(cx: &mut TestAppContext) { #[gpui::test] fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) { - cx.update(|cx| cx.set_global(Settings::test(cx))); + init_test(cx, |_| {}); + let (_, view) = cx.add_window(|cx| { let buffer = MultiBuffer::build_simple("use one::{\n two::three::four::five\n};", cx); build_editor(buffer, cx) @@ -1229,6 +1245,7 @@ fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) { #[gpui::test] async fn test_move_page_up_page_down(cx: &mut gpui::TestAppContext) { + init_test(cx, |_| {}); let mut cx = EditorTestContext::new(cx); let line_height = cx.editor(|editor, cx| editor.style(cx).text.line_height(cx.font_cache())); @@ -1343,6 +1360,7 @@ async fn test_move_page_up_page_down(cx: &mut gpui::TestAppContext) { #[gpui::test] async fn test_delete_to_beginning_of_line(cx: &mut gpui::TestAppContext) { + init_test(cx, |_| {}); let mut cx = EditorTestContext::new(cx); cx.set_state("one «two threeˇ» four"); cx.update_editor(|editor, cx| { @@ -1353,7 +1371,8 @@ async fn test_delete_to_beginning_of_line(cx: &mut gpui::TestAppContext) { #[gpui::test] fn test_delete_to_word_boundary(cx: &mut TestAppContext) { - cx.update(|cx| cx.set_global(Settings::test(cx))); + init_test(cx, |_| {}); + let (_, view) = cx.add_window(|cx| { let buffer = MultiBuffer::build_simple("one two three four", cx); build_editor(buffer.clone(), cx) @@ -1388,7 +1407,8 @@ fn test_delete_to_word_boundary(cx: &mut TestAppContext) { #[gpui::test] fn test_newline(cx: &mut TestAppContext) { - cx.update(|cx| cx.set_global(Settings::test(cx))); + init_test(cx, |_| {}); + let (_, view) = cx.add_window(|cx| { let buffer = MultiBuffer::build_simple("aaaa\n bbbb\n", cx); build_editor(buffer.clone(), cx) @@ -1410,7 +1430,8 @@ fn test_newline(cx: &mut TestAppContext) { #[gpui::test] fn test_newline_with_old_selections(cx: &mut TestAppContext) { - cx.update(|cx| cx.set_global(Settings::test(cx))); + init_test(cx, |_| {}); + let (_, editor) = cx.add_window(|cx| { let buffer = MultiBuffer::build_simple( " @@ -1491,11 +1512,8 @@ fn test_newline_with_old_selections(cx: &mut TestAppContext) { #[gpui::test] async fn test_newline_above(cx: &mut gpui::TestAppContext) { - let mut cx = EditorTestContext::new(cx); - cx.update(|cx| { - cx.update_global::(|settings, _| { - settings.editor_overrides.tab_size = Some(NonZeroU32::new(4).unwrap()); - }); + init_test(cx, |settings| { + settings.defaults.tab_size = NonZeroU32::new(4) }); let language = Arc::new( @@ -1506,8 +1524,9 @@ async fn test_newline_above(cx: &mut gpui::TestAppContext) { .with_indents_query(r#"(_ "(" ")" @end) @indent"#) .unwrap(), ); - cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx)); + let mut cx = EditorTestContext::new(cx); + cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx)); cx.set_state(indoc! {" const a: ˇA = ( (ˇ @@ -1516,6 +1535,7 @@ async fn test_newline_above(cx: &mut gpui::TestAppContext) { )ˇ ˇ);ˇ "}); + cx.update_editor(|e, cx| e.newline_above(&NewlineAbove, cx)); cx.assert_editor_state(indoc! {" ˇ @@ -1540,11 +1560,8 @@ async fn test_newline_above(cx: &mut gpui::TestAppContext) { #[gpui::test] async fn test_newline_below(cx: &mut gpui::TestAppContext) { - let mut cx = EditorTestContext::new(cx); - cx.update(|cx| { - cx.update_global::(|settings, _| { - settings.editor_overrides.tab_size = Some(NonZeroU32::new(4).unwrap()); - }); + init_test(cx, |settings| { + settings.defaults.tab_size = NonZeroU32::new(4) }); let language = Arc::new( @@ -1555,8 +1572,9 @@ async fn test_newline_below(cx: &mut gpui::TestAppContext) { .with_indents_query(r#"(_ "(" ")" @end) @indent"#) .unwrap(), ); - cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx)); + let mut cx = EditorTestContext::new(cx); + cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx)); cx.set_state(indoc! {" const a: ˇA = ( (ˇ @@ -1565,6 +1583,7 @@ async fn test_newline_below(cx: &mut gpui::TestAppContext) { )ˇ ˇ);ˇ "}); + cx.update_editor(|e, cx| e.newline_below(&NewlineBelow, cx)); cx.assert_editor_state(indoc! {" const a: A = ( @@ -1589,7 +1608,8 @@ async fn test_newline_below(cx: &mut gpui::TestAppContext) { #[gpui::test] fn test_insert_with_old_selections(cx: &mut TestAppContext) { - cx.update(|cx| cx.set_global(Settings::test(cx))); + init_test(cx, |_| {}); + let (_, editor) = cx.add_window(|cx| { let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx); let mut editor = build_editor(buffer.clone(), cx); @@ -1615,12 +1635,11 @@ fn test_insert_with_old_selections(cx: &mut TestAppContext) { #[gpui::test] async fn test_tab(cx: &mut gpui::TestAppContext) { - let mut cx = EditorTestContext::new(cx); - cx.update(|cx| { - cx.update_global::(|settings, _| { - settings.editor_overrides.tab_size = Some(NonZeroU32::new(3).unwrap()); - }); + init_test(cx, |settings| { + settings.defaults.tab_size = NonZeroU32::new(3) }); + + let mut cx = EditorTestContext::new(cx); cx.set_state(indoc! {" ˇabˇc ˇ🏀ˇ🏀ˇefg @@ -1646,6 +1665,8 @@ async fn test_tab(cx: &mut gpui::TestAppContext) { #[gpui::test] async fn test_tab_in_leading_whitespace_auto_indents_lines(cx: &mut gpui::TestAppContext) { + init_test(cx, |_| {}); + let mut cx = EditorTestContext::new(cx); let language = Arc::new( Language::new( @@ -1704,7 +1725,10 @@ async fn test_tab_in_leading_whitespace_auto_indents_lines(cx: &mut gpui::TestAp #[gpui::test] async fn test_tab_with_mixed_whitespace(cx: &mut gpui::TestAppContext) { - let mut cx = EditorTestContext::new(cx); + init_test(cx, |settings| { + settings.defaults.tab_size = NonZeroU32::new(4) + }); + let language = Arc::new( Language::new( LanguageConfig::default(), @@ -1713,14 +1737,9 @@ async fn test_tab_with_mixed_whitespace(cx: &mut gpui::TestAppContext) { .with_indents_query(r#"(_ "{" "}" @end) @indent"#) .unwrap(), ); - cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx)); - - cx.update(|cx| { - cx.update_global::(|settings, _| { - settings.editor_overrides.tab_size = Some(4.try_into().unwrap()); - }); - }); + let mut cx = EditorTestContext::new(cx); + cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx)); cx.set_state(indoc! {" fn a() { if b { @@ -1741,6 +1760,10 @@ async fn test_tab_with_mixed_whitespace(cx: &mut gpui::TestAppContext) { #[gpui::test] async fn test_indent_outdent(cx: &mut gpui::TestAppContext) { + init_test(cx, |settings| { + settings.defaults.tab_size = NonZeroU32::new(4); + }); + let mut cx = EditorTestContext::new(cx); cx.set_state(indoc! {" @@ -1810,13 +1833,12 @@ async fn test_indent_outdent(cx: &mut gpui::TestAppContext) { #[gpui::test] async fn test_indent_outdent_with_hard_tabs(cx: &mut gpui::TestAppContext) { - let mut cx = EditorTestContext::new(cx); - cx.update(|cx| { - cx.update_global::(|settings, _| { - settings.editor_overrides.hard_tabs = Some(true); - }); + init_test(cx, |settings| { + settings.defaults.hard_tabs = Some(true); }); + let mut cx = EditorTestContext::new(cx); + // select two ranges on one line cx.set_state(indoc! {" «oneˇ» «twoˇ» @@ -1907,25 +1929,25 @@ async fn test_indent_outdent_with_hard_tabs(cx: &mut gpui::TestAppContext) { #[gpui::test] fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) { - cx.update(|cx| { - cx.set_global( - Settings::test(cx) - .with_language_defaults( - "TOML", - EditorSettings { - tab_size: Some(2.try_into().unwrap()), - ..Default::default() - }, - ) - .with_language_defaults( - "Rust", - EditorSettings { - tab_size: Some(4.try_into().unwrap()), - ..Default::default() - }, - ), - ); + init_test(cx, |settings| { + settings.languages.extend([ + ( + "TOML".into(), + LanguageSettingsContent { + tab_size: NonZeroU32::new(2), + ..Default::default() + }, + ), + ( + "Rust".into(), + LanguageSettingsContent { + tab_size: NonZeroU32::new(4), + ..Default::default() + }, + ), + ]); }); + let toml_language = Arc::new(Language::new( LanguageConfig { name: "TOML".into(), @@ -2020,6 +2042,8 @@ fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) { #[gpui::test] async fn test_backspace(cx: &mut gpui::TestAppContext) { + init_test(cx, |_| {}); + let mut cx = EditorTestContext::new(cx); // Basic backspace @@ -2067,8 +2091,9 @@ async fn test_backspace(cx: &mut gpui::TestAppContext) { #[gpui::test] async fn test_delete(cx: &mut gpui::TestAppContext) { - let mut cx = EditorTestContext::new(cx); + init_test(cx, |_| {}); + let mut cx = EditorTestContext::new(cx); cx.set_state(indoc! {" onˇe two three fou«rˇ» five six @@ -2095,7 +2120,8 @@ async fn test_delete(cx: &mut gpui::TestAppContext) { #[gpui::test] fn test_delete_line(cx: &mut TestAppContext) { - cx.update(|cx| cx.set_global(Settings::test(cx))); + init_test(cx, |_| {}); + let (_, view) = cx.add_window(|cx| { let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx); build_editor(buffer, cx) @@ -2119,7 +2145,6 @@ fn test_delete_line(cx: &mut TestAppContext) { ); }); - cx.update(|cx| cx.set_global(Settings::test(cx))); let (_, view) = cx.add_window(|cx| { let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx); build_editor(buffer, cx) @@ -2139,7 +2164,8 @@ fn test_delete_line(cx: &mut TestAppContext) { #[gpui::test] fn test_duplicate_line(cx: &mut TestAppContext) { - cx.update(|cx| cx.set_global(Settings::test(cx))); + init_test(cx, |_| {}); + let (_, view) = cx.add_window(|cx| { let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx); build_editor(buffer, cx) @@ -2191,7 +2217,8 @@ fn test_duplicate_line(cx: &mut TestAppContext) { #[gpui::test] fn test_move_line_up_down(cx: &mut TestAppContext) { - cx.update(|cx| cx.set_global(Settings::test(cx))); + init_test(cx, |_| {}); + let (_, view) = cx.add_window(|cx| { let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx); build_editor(buffer, cx) @@ -2289,7 +2316,8 @@ fn test_move_line_up_down(cx: &mut TestAppContext) { #[gpui::test] fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) { - cx.update(|cx| cx.set_global(Settings::test(cx))); + init_test(cx, |_| {}); + let (_, editor) = cx.add_window(|cx| { let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx); build_editor(buffer, cx) @@ -2315,7 +2343,7 @@ fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) { #[gpui::test] fn test_transpose(cx: &mut TestAppContext) { - cx.update(|cx| cx.set_global(Settings::test(cx))); + init_test(cx, |_| {}); _ = cx .add_window(|cx| { @@ -2417,6 +2445,8 @@ fn test_transpose(cx: &mut TestAppContext) { #[gpui::test] async fn test_clipboard(cx: &mut gpui::TestAppContext) { + init_test(cx, |_| {}); + let mut cx = EditorTestContext::new(cx); cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six "); @@ -2497,6 +2527,8 @@ async fn test_clipboard(cx: &mut gpui::TestAppContext) { #[gpui::test] async fn test_paste_multiline(cx: &mut gpui::TestAppContext) { + init_test(cx, |_| {}); + let mut cx = EditorTestContext::new(cx); let language = Arc::new(Language::new( LanguageConfig::default(), @@ -2609,7 +2641,8 @@ async fn test_paste_multiline(cx: &mut gpui::TestAppContext) { #[gpui::test] fn test_select_all(cx: &mut TestAppContext) { - cx.update(|cx| cx.set_global(Settings::test(cx))); + init_test(cx, |_| {}); + let (_, view) = cx.add_window(|cx| { let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx); build_editor(buffer, cx) @@ -2625,7 +2658,8 @@ fn test_select_all(cx: &mut TestAppContext) { #[gpui::test] fn test_select_line(cx: &mut TestAppContext) { - cx.update(|cx| cx.set_global(Settings::test(cx))); + init_test(cx, |_| {}); + let (_, view) = cx.add_window(|cx| { let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx); build_editor(buffer, cx) @@ -2671,7 +2705,8 @@ fn test_select_line(cx: &mut TestAppContext) { #[gpui::test] fn test_split_selection_into_lines(cx: &mut TestAppContext) { - cx.update(|cx| cx.set_global(Settings::test(cx))); + init_test(cx, |_| {}); + let (_, view) = cx.add_window(|cx| { let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx); build_editor(buffer, cx) @@ -2741,7 +2776,8 @@ fn test_split_selection_into_lines(cx: &mut TestAppContext) { #[gpui::test] fn test_add_selection_above_below(cx: &mut TestAppContext) { - cx.update(|cx| cx.set_global(Settings::test(cx))); + init_test(cx, |_| {}); + let (_, view) = cx.add_window(|cx| { let buffer = MultiBuffer::build_simple("abc\ndefghi\n\njk\nlmno\n", cx); build_editor(buffer, cx) @@ -2935,6 +2971,8 @@ fn test_add_selection_above_below(cx: &mut TestAppContext) { #[gpui::test] async fn test_select_next(cx: &mut gpui::TestAppContext) { + init_test(cx, |_| {}); + let mut cx = EditorTestContext::new(cx); cx.set_state("abc\nˇabc abc\ndefabc\nabc"); @@ -2959,7 +2997,8 @@ async fn test_select_next(cx: &mut gpui::TestAppContext) { #[gpui::test] async fn test_select_larger_smaller_syntax_node(cx: &mut gpui::TestAppContext) { - cx.update(|cx| cx.set_global(Settings::test(cx))); + init_test(cx, |_| {}); + let language = Arc::new(Language::new( LanguageConfig::default(), Some(tree_sitter_rust::language()), @@ -3100,7 +3139,8 @@ async fn test_select_larger_smaller_syntax_node(cx: &mut gpui::TestAppContext) { #[gpui::test] async fn test_autoindent_selections(cx: &mut gpui::TestAppContext) { - cx.update(|cx| cx.set_global(Settings::test(cx))); + init_test(cx, |_| {}); + let language = Arc::new( Language::new( LanguageConfig { @@ -3160,6 +3200,8 @@ async fn test_autoindent_selections(cx: &mut gpui::TestAppContext) { #[gpui::test] async fn test_autoclose_pairs(cx: &mut gpui::TestAppContext) { + init_test(cx, |_| {}); + let mut cx = EditorTestContext::new(cx); let language = Arc::new(Language::new( @@ -3329,6 +3371,8 @@ async fn test_autoclose_pairs(cx: &mut gpui::TestAppContext) { #[gpui::test] async fn test_autoclose_with_embedded_language(cx: &mut gpui::TestAppContext) { + init_test(cx, |_| {}); + let mut cx = EditorTestContext::new(cx); let html_language = Arc::new( @@ -3563,6 +3607,8 @@ async fn test_autoclose_with_embedded_language(cx: &mut gpui::TestAppContext) { #[gpui::test] async fn test_autoclose_with_overrides(cx: &mut gpui::TestAppContext) { + init_test(cx, |_| {}); + let mut cx = EditorTestContext::new(cx); let rust_language = Arc::new( @@ -3660,7 +3706,8 @@ async fn test_autoclose_with_overrides(cx: &mut gpui::TestAppContext) { #[gpui::test] async fn test_surround_with_pair(cx: &mut gpui::TestAppContext) { - cx.update(|cx| cx.set_global(Settings::test(cx))); + init_test(cx, |_| {}); + let language = Arc::new(Language::new( LanguageConfig { brackets: BracketPairConfig { @@ -3814,7 +3861,8 @@ async fn test_surround_with_pair(cx: &mut gpui::TestAppContext) { #[gpui::test] async fn test_delete_autoclose_pair(cx: &mut gpui::TestAppContext) { - cx.update(|cx| cx.set_global(Settings::test(cx))); + init_test(cx, |_| {}); + let language = Arc::new(Language::new( LanguageConfig { brackets: BracketPairConfig { @@ -3919,7 +3967,7 @@ async fn test_delete_autoclose_pair(cx: &mut gpui::TestAppContext) { #[gpui::test] async fn test_snippets(cx: &mut gpui::TestAppContext) { - cx.update(|cx| cx.set_global(Settings::test(cx))); + init_test(cx, |_| {}); let (text, insertion_ranges) = marked_text_ranges( indoc! {" @@ -4027,7 +4075,7 @@ async fn test_snippets(cx: &mut gpui::TestAppContext) { #[gpui::test] async fn test_document_format_during_save(cx: &mut gpui::TestAppContext) { - cx.foreground().forbid_parking(); + init_test(cx, |_| {}); let mut language = Language::new( LanguageConfig { @@ -4111,16 +4159,14 @@ async fn test_document_format_during_save(cx: &mut gpui::TestAppContext) { assert!(!cx.read(|cx| editor.is_dirty(cx))); // Set rust language override and assert overriden tabsize is sent to language server - cx.update(|cx| { - cx.update_global::(|settings, _| { - settings.language_overrides.insert( - "Rust".into(), - EditorSettings { - tab_size: Some(8.try_into().unwrap()), - ..Default::default() - }, - ); - }) + update_test_settings(cx, |settings| { + settings.languages.insert( + "Rust".into(), + LanguageSettingsContent { + tab_size: NonZeroU32::new(8), + ..Default::default() + }, + ); }); let save = editor.update(cx, |editor, cx| editor.save(project.clone(), cx)); @@ -4141,7 +4187,7 @@ async fn test_document_format_during_save(cx: &mut gpui::TestAppContext) { #[gpui::test] async fn test_range_format_during_save(cx: &mut gpui::TestAppContext) { - cx.foreground().forbid_parking(); + init_test(cx, |_| {}); let mut language = Language::new( LanguageConfig { @@ -4227,16 +4273,14 @@ async fn test_range_format_during_save(cx: &mut gpui::TestAppContext) { assert!(!cx.read(|cx| editor.is_dirty(cx))); // Set rust language override and assert overriden tabsize is sent to language server - cx.update(|cx| { - cx.update_global::(|settings, _| { - settings.language_overrides.insert( - "Rust".into(), - EditorSettings { - tab_size: Some(8.try_into().unwrap()), - ..Default::default() - }, - ); - }) + update_test_settings(cx, |settings| { + settings.languages.insert( + "Rust".into(), + LanguageSettingsContent { + tab_size: NonZeroU32::new(8), + ..Default::default() + }, + ); }); let save = editor.update(cx, |editor, cx| editor.save(project.clone(), cx)); @@ -4257,7 +4301,7 @@ async fn test_range_format_during_save(cx: &mut gpui::TestAppContext) { #[gpui::test] async fn test_document_format_manual_trigger(cx: &mut gpui::TestAppContext) { - cx.foreground().forbid_parking(); + init_test(cx, |_| {}); let mut language = Language::new( LanguageConfig { @@ -4342,7 +4386,7 @@ async fn test_document_format_manual_trigger(cx: &mut gpui::TestAppContext) { #[gpui::test] async fn test_concurrent_format_requests(cx: &mut gpui::TestAppContext) { - cx.foreground().forbid_parking(); + init_test(cx, |_| {}); let mut cx = EditorLspTestContext::new_rust( lsp::ServerCapabilities { @@ -4399,7 +4443,7 @@ async fn test_concurrent_format_requests(cx: &mut gpui::TestAppContext) { #[gpui::test] async fn test_strip_whitespace_and_format_via_lsp(cx: &mut gpui::TestAppContext) { - cx.foreground().forbid_parking(); + init_test(cx, |_| {}); let mut cx = EditorLspTestContext::new_rust( lsp::ServerCapabilities { @@ -4514,6 +4558,8 @@ async fn test_strip_whitespace_and_format_via_lsp(cx: &mut gpui::TestAppContext) #[gpui::test] async fn test_completion(cx: &mut gpui::TestAppContext) { + init_test(cx, |_| {}); + let mut cx = EditorLspTestContext::new_rust( lsp::ServerCapabilities { completion_provider: Some(lsp::CompletionOptions { @@ -4651,8 +4697,10 @@ async fn test_completion(cx: &mut gpui::TestAppContext) { apply_additional_edits.await.unwrap(); cx.update(|cx| { - cx.update_global::(|settings, _| { - settings.show_completions_on_input = false; + cx.update_global::(|settings, cx| { + settings.update_user_settings::(cx, |settings| { + settings.show_completions_on_input = Some(false); + }); }) }); cx.set_state("editorˇ"); @@ -4681,7 +4729,8 @@ async fn test_completion(cx: &mut gpui::TestAppContext) { #[gpui::test] async fn test_toggle_comment(cx: &mut gpui::TestAppContext) { - cx.update(|cx| cx.set_global(Settings::test(cx))); + init_test(cx, |_| {}); + let language = Arc::new(Language::new( LanguageConfig { line_comment: Some("// ".into()), @@ -4764,8 +4813,7 @@ async fn test_toggle_comment(cx: &mut gpui::TestAppContext) { #[gpui::test] async fn test_advance_downward_on_toggle_comment(cx: &mut gpui::TestAppContext) { - let mut cx = EditorTestContext::new(cx); - cx.update(|cx| cx.set_global(Settings::test(cx))); + init_test(cx, |_| {}); let language = Arc::new(Language::new( LanguageConfig { @@ -4778,6 +4826,7 @@ async fn test_advance_downward_on_toggle_comment(cx: &mut gpui::TestAppContext) let registry = Arc::new(LanguageRegistry::test()); registry.add(language.clone()); + let mut cx = EditorTestContext::new(cx); cx.update_buffer(|buffer, cx| { buffer.set_language_registry(registry); buffer.set_language(Some(language), cx); @@ -4897,6 +4946,8 @@ async fn test_advance_downward_on_toggle_comment(cx: &mut gpui::TestAppContext) #[gpui::test] async fn test_toggle_block_comment(cx: &mut gpui::TestAppContext) { + init_test(cx, |_| {}); + let mut cx = EditorTestContext::new(cx); let html_language = Arc::new( @@ -5021,7 +5072,8 @@ async fn test_toggle_block_comment(cx: &mut gpui::TestAppContext) { #[gpui::test] fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) { - cx.update(|cx| cx.set_global(Settings::test(cx))); + init_test(cx, |_| {}); + let buffer = cx.add_model(|cx| Buffer::new(0, sample_text(3, 4, 'a'), cx)); let multibuffer = cx.add_model(|cx| { let mut multibuffer = MultiBuffer::new(0); @@ -5067,7 +5119,8 @@ fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) { #[gpui::test] fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) { - cx.update(|cx| cx.set_global(Settings::test(cx))); + init_test(cx, |_| {}); + let markers = vec![('[', ']').into(), ('(', ')').into()]; let (initial_text, mut excerpt_ranges) = marked_text_ranges_by( indoc! {" @@ -5140,7 +5193,8 @@ fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) { #[gpui::test] fn test_refresh_selections(cx: &mut TestAppContext) { - cx.update(|cx| cx.set_global(Settings::test(cx))); + init_test(cx, |_| {}); + let buffer = cx.add_model(|cx| Buffer::new(0, sample_text(3, 4, 'a'), cx)); let mut excerpt1_id = None; let multibuffer = cx.add_model(|cx| { @@ -5224,7 +5278,8 @@ fn test_refresh_selections(cx: &mut TestAppContext) { #[gpui::test] fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) { - cx.update(|cx| cx.set_global(Settings::test(cx))); + init_test(cx, |_| {}); + let buffer = cx.add_model(|cx| Buffer::new(0, sample_text(3, 4, 'a'), cx)); let mut excerpt1_id = None; let multibuffer = cx.add_model(|cx| { @@ -5282,7 +5337,8 @@ fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) { #[gpui::test] async fn test_extra_newline_insertion(cx: &mut gpui::TestAppContext) { - cx.update(|cx| cx.set_global(Settings::test(cx))); + init_test(cx, |_| {}); + let language = Arc::new( Language::new( LanguageConfig { @@ -5355,7 +5411,8 @@ async fn test_extra_newline_insertion(cx: &mut gpui::TestAppContext) { #[gpui::test] fn test_highlighted_ranges(cx: &mut TestAppContext) { - cx.update(|cx| cx.set_global(Settings::test(cx))); + init_test(cx, |_| {}); + let (_, editor) = cx.add_window(|cx| { let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx); build_editor(buffer.clone(), cx) @@ -5395,7 +5452,7 @@ fn test_highlighted_ranges(cx: &mut TestAppContext) { let mut highlighted_ranges = editor.background_highlights_in_range( anchor_range(Point::new(3, 4)..Point::new(7, 4)), &snapshot, - cx.global::().theme.as_ref(), + theme::current(cx).as_ref(), ); // Enforce a consistent ordering based on color without relying on the ordering of the // highlight's `TypeId` which is non-deterministic. @@ -5425,7 +5482,7 @@ fn test_highlighted_ranges(cx: &mut TestAppContext) { editor.background_highlights_in_range( anchor_range(Point::new(5, 6)..Point::new(6, 4)), &snapshot, - cx.global::().theme.as_ref(), + theme::current(cx).as_ref(), ), &[( DisplayPoint::new(6, 3)..DisplayPoint::new(6, 5), @@ -5437,7 +5494,8 @@ fn test_highlighted_ranges(cx: &mut TestAppContext) { #[gpui::test] async fn test_following(cx: &mut gpui::TestAppContext) { - Settings::test_async(cx); + init_test(cx, |_| {}); + let fs = FakeFs::new(cx.background()); let project = Project::test(fs, ["/file.rs".as_ref()], cx).await; @@ -5576,7 +5634,8 @@ async fn test_following(cx: &mut gpui::TestAppContext) { #[gpui::test] async fn test_following_with_multiple_excerpts(cx: &mut gpui::TestAppContext) { - Settings::test_async(cx); + init_test(cx, |_| {}); + let fs = FakeFs::new(cx.background()); let project = Project::test(fs, ["/file.rs".as_ref()], cx).await; let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); @@ -5805,6 +5864,8 @@ fn test_combine_syntax_and_fuzzy_match_highlights() { #[gpui::test] async fn go_to_hunk(deterministic: Arc, cx: &mut gpui::TestAppContext) { + init_test(cx, |_| {}); + let mut cx = EditorTestContext::new(cx); let diff_base = r#" @@ -5924,6 +5985,8 @@ fn test_split_words() { #[gpui::test] async fn test_move_to_enclosing_bracket(cx: &mut gpui::TestAppContext) { + init_test(cx, |_| {}); + let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await; let mut assert = |before, after| { let _state_context = cx.set_state(before); @@ -5972,6 +6035,8 @@ async fn test_move_to_enclosing_bracket(cx: &mut gpui::TestAppContext) { #[gpui::test(iterations = 10)] async fn test_copilot(deterministic: Arc, cx: &mut gpui::TestAppContext) { + init_test(cx, |_| {}); + let (copilot, copilot_lsp) = Copilot::fake(cx); cx.update(|cx| cx.set_global(copilot)); let mut cx = EditorLspTestContext::new_rust( @@ -6223,6 +6288,8 @@ async fn test_copilot_completion_invalidation( deterministic: Arc, cx: &mut gpui::TestAppContext, ) { + init_test(cx, |_| {}); + let (copilot, copilot_lsp) = Copilot::fake(cx); cx.update(|cx| cx.set_global(copilot)); let mut cx = EditorLspTestContext::new_rust( @@ -6288,11 +6355,10 @@ async fn test_copilot_multibuffer( deterministic: Arc, cx: &mut gpui::TestAppContext, ) { + init_test(cx, |_| {}); + let (copilot, copilot_lsp) = Copilot::fake(cx); - cx.update(|cx| { - cx.set_global(Settings::test(cx)); - cx.set_global(copilot) - }); + cx.update(|cx| cx.set_global(copilot)); let buffer_1 = cx.add_model(|cx| Buffer::new(0, "a = 1\nb = 2\n", cx)); let buffer_2 = cx.add_model(|cx| Buffer::new(0, "c = 3\nd = 4\n", cx)); @@ -6392,14 +6458,16 @@ async fn test_copilot_disabled_globs( deterministic: Arc, cx: &mut gpui::TestAppContext, ) { - let (copilot, copilot_lsp) = Copilot::fake(cx); - cx.update(|cx| { - let mut settings = Settings::test(cx); - settings.copilot.disabled_globs = vec![glob::Pattern::new(".env*").unwrap()]; - cx.set_global(settings); - cx.set_global(copilot) + init_test(cx, |settings| { + settings + .copilot + .get_or_insert(Default::default()) + .disabled_globs = Some(vec![".env*".to_string()]); }); + let (copilot, copilot_lsp) = Copilot::fake(cx); + cx.update(|cx| cx.set_global(copilot)); + let fs = FakeFs::new(cx.background()); fs.insert_tree( "/test", @@ -6596,3 +6664,30 @@ fn handle_copilot_completion_request( } }); } + +pub(crate) fn update_test_settings( + cx: &mut TestAppContext, + f: impl Fn(&mut AllLanguageSettingsContent), +) { + cx.update(|cx| { + cx.update_global::(|store, cx| { + store.update_user_settings::(cx, f); + }); + }); +} + +pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) { + cx.foreground().forbid_parking(); + + cx.update(|cx| { + cx.set_global(SettingsStore::test(cx)); + theme::init((), cx); + client::init_settings(cx); + language::init(cx); + Project::init_settings(cx); + workspace::init_settings(cx); + crate::init(cx); + }); + + update_test_settings(cx, f); +} diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index e1e38f2e245db4e4e83e49deda9df8029d6a3398..0a17fc8baf915fdfbf4663490b064649eeadabd8 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -5,6 +5,7 @@ use super::{ }; use crate::{ display_map::{BlockStyle, DisplaySnapshot, FoldStatus, TransformBlock}, + editor_settings::ShowScrollbars, git::{diff_hunk_to_display, DisplayDiffHunk}, hover_popover::{ hide_hover, hover_at, HOVER_POPOVER_GAP, MIN_POPOVER_CHARACTER_WIDTH, @@ -13,7 +14,7 @@ use crate::{ link_go_to_definition::{ go_to_fetched_definition, go_to_fetched_type_definition, update_go_to_definition_link, }, - mouse_context_menu, EditorStyle, GutterHover, UnfoldAt, + mouse_context_menu, EditorSettings, EditorStyle, GutterHover, UnfoldAt, }; use clock::ReplicaId; use collections::{BTreeMap, HashMap}; @@ -35,9 +36,11 @@ use gpui::{ }; use itertools::Itertools; use json::json; -use language::{Bias, CursorShape, DiagnosticSeverity, OffsetUtf16, Selection}; +use language::{ + language_settings::ShowWhitespaceSetting, Bias, CursorShape, DiagnosticSeverity, OffsetUtf16, + Selection, +}; use project::ProjectPath; -use settings::{GitGutter, Settings, ShowWhitespaces}; use smallvec::SmallVec; use std::{ borrow::Cow, @@ -47,7 +50,7 @@ use std::{ ops::Range, sync::Arc, }; -use workspace::item::Item; +use workspace::{item::Item, GitGutterSetting, WorkspaceSettings}; enum FoldMarkers {} @@ -547,11 +550,11 @@ impl EditorElement { let scroll_top = scroll_position.y() * line_height; let show_gutter = matches!( - &cx.global::() - .git_overrides + settings::get::(cx) + .git .git_gutter .unwrap_or_default(), - GitGutter::TrackedFiles + GitGutterSetting::TrackedFiles ); if show_gutter { @@ -608,7 +611,7 @@ impl EditorElement { layout: &mut LayoutState, cx: &mut ViewContext, ) { - let diff_style = &cx.global::().theme.editor.diff.clone(); + let diff_style = &theme::current(cx).editor.diff.clone(); let line_height = layout.position_map.line_height; let scroll_position = layout.position_map.snapshot.scroll_position(); @@ -708,6 +711,7 @@ impl EditorElement { let scroll_left = scroll_position.x() * max_glyph_width; let content_origin = bounds.origin() + vec2f(layout.gutter_margin, 0.); let line_end_overshoot = 0.15 * layout.position_map.line_height; + let whitespace_setting = editor.buffer.read(cx).settings_at(0, cx).show_whitespaces; scene.push_layer(Some(bounds)); @@ -882,9 +886,10 @@ impl EditorElement { content_origin, scroll_left, visible_text_bounds, - cx, + whitespace_setting, &invisible_display_ranges, visible_bounds, + cx, ) } } @@ -1046,7 +1051,7 @@ impl EditorElement { ..Default::default() }); - let diff_style = cx.global::().theme.editor.diff.clone(); + let diff_style = theme::current(cx).editor.diff.clone(); for hunk in layout .position_map .snapshot @@ -1457,7 +1462,7 @@ impl EditorElement { editor: &mut Editor, cx: &mut LayoutContext, ) -> (f32, Vec) { - let tooltip_style = cx.global::().theme.tooltip.clone(); + let tooltip_style = theme::current(cx).tooltip.clone(); let scroll_x = snapshot.scroll_anchor.offset.x(); let (fixed_blocks, non_fixed_blocks) = snapshot .blocks_in_range(rows.clone()) @@ -1783,9 +1788,10 @@ impl LineWithInvisibles { content_origin: Vector2F, scroll_left: f32, visible_text_bounds: RectF, - cx: &mut ViewContext, + whitespace_setting: ShowWhitespaceSetting, selection_ranges: &[Range], visible_bounds: RectF, + cx: &mut ViewContext, ) { let line_height = layout.position_map.line_height; let line_y = row as f32 * line_height - scroll_top; @@ -1799,7 +1805,6 @@ impl LineWithInvisibles { ); self.draw_invisibles( - cx, &selection_ranges, layout, content_origin, @@ -1809,12 +1814,13 @@ impl LineWithInvisibles { scene, visible_bounds, line_height, + whitespace_setting, + cx, ); } fn draw_invisibles( &self, - cx: &mut ViewContext, selection_ranges: &[Range], layout: &LayoutState, content_origin: Vector2F, @@ -1824,17 +1830,13 @@ impl LineWithInvisibles { scene: &mut SceneBuilder, visible_bounds: RectF, line_height: f32, + whitespace_setting: ShowWhitespaceSetting, + cx: &mut ViewContext, ) { - let settings = cx.global::(); - let allowed_invisibles_regions = match settings - .editor_overrides - .show_whitespaces - .or(settings.editor_defaults.show_whitespaces) - .unwrap_or_default() - { - ShowWhitespaces::None => return, - ShowWhitespaces::Selection => Some(selection_ranges), - ShowWhitespaces::All => None, + let allowed_invisibles_regions = match whitespace_setting { + ShowWhitespaceSetting::None => return, + ShowWhitespaceSetting::Selection => Some(selection_ranges), + ShowWhitespaceSetting::All => None, }; for invisible in &self.invisibles { @@ -1979,11 +1981,11 @@ impl Element for EditorElement { let is_singleton = editor.is_singleton(cx); let highlighted_rows = editor.highlighted_rows(); - let theme = cx.global::().theme.as_ref(); + let theme = theme::current(cx); let highlighted_ranges = editor.background_highlights_in_range( start_anchor..end_anchor, &snapshot.display_snapshot, - theme, + theme.as_ref(), ); fold_ranges.extend( @@ -2058,13 +2060,13 @@ impl Element for EditorElement { )); } - let show_scrollbars = match cx.global::().show_scrollbars { - settings::ShowScrollbars::Auto => { + let show_scrollbars = match settings::get::(cx).show_scrollbars { + ShowScrollbars::Auto => { snapshot.has_scrollbar_info() || editor.scroll_manager.scrollbars_visible() } - settings::ShowScrollbars::System => editor.scroll_manager.scrollbars_visible(), - settings::ShowScrollbars::Always => true, - settings::ShowScrollbars::Never => false, + ShowScrollbars::System => editor.scroll_manager.scrollbars_visible(), + ShowScrollbars::Always => true, + ShowScrollbars::Never => false, }; let include_root = editor @@ -2826,17 +2828,19 @@ mod tests { use super::*; use crate::{ display_map::{BlockDisposition, BlockProperties}, + editor_tests::{init_test, update_test_settings}, Editor, MultiBuffer, }; use gpui::TestAppContext; + use language::language_settings; use log::info; - use settings::Settings; use std::{num::NonZeroU32, sync::Arc}; use util::test::sample_text; #[gpui::test] fn test_layout_line_numbers(cx: &mut TestAppContext) { - cx.update(|cx| cx.set_global(Settings::test(cx))); + init_test(cx, |_| {}); + let (_, editor) = cx.add_window(|cx| { let buffer = MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx); Editor::new(EditorMode::Full, buffer, None, None, cx) @@ -2854,7 +2858,8 @@ mod tests { #[gpui::test] fn test_layout_with_placeholder_text_and_blocks(cx: &mut TestAppContext) { - cx.update(|cx| cx.set_global(Settings::test(cx))); + init_test(cx, |_| {}); + let (_, editor) = cx.add_window(|cx| { let buffer = MultiBuffer::build_simple("", cx); Editor::new(EditorMode::Full, buffer, None, None, cx) @@ -2914,26 +2919,27 @@ mod tests { #[gpui::test] fn test_all_invisibles_drawing(cx: &mut TestAppContext) { - let tab_size = 4; + const TAB_SIZE: u32 = 4; + let input_text = "\t \t|\t| a b"; let expected_invisibles = vec![ Invisible::Tab { line_start_offset: 0, }, Invisible::Whitespace { - line_offset: tab_size as usize, + line_offset: TAB_SIZE as usize, }, Invisible::Tab { - line_start_offset: tab_size as usize + 1, + line_start_offset: TAB_SIZE as usize + 1, }, Invisible::Tab { - line_start_offset: tab_size as usize * 2 + 1, + line_start_offset: TAB_SIZE as usize * 2 + 1, }, Invisible::Whitespace { - line_offset: tab_size as usize * 3 + 1, + line_offset: TAB_SIZE as usize * 3 + 1, }, Invisible::Whitespace { - line_offset: tab_size as usize * 3 + 3, + line_offset: TAB_SIZE as usize * 3 + 3, }, ]; assert_eq!( @@ -2945,12 +2951,11 @@ mod tests { "Hardcoded expected invisibles differ from the actual ones in '{input_text}'" ); - cx.update(|cx| { - let mut test_settings = Settings::test(cx); - test_settings.editor_defaults.show_whitespaces = Some(ShowWhitespaces::All); - test_settings.editor_defaults.tab_size = Some(NonZeroU32::new(tab_size).unwrap()); - cx.set_global(test_settings); + init_test(cx, |s| { + s.defaults.show_whitespaces = Some(ShowWhitespaceSetting::All); + s.defaults.tab_size = NonZeroU32::new(TAB_SIZE); }); + let actual_invisibles = collect_invisibles_from_new_editor(cx, EditorMode::Full, &input_text, 500.0); @@ -2959,11 +2964,9 @@ mod tests { #[gpui::test] fn test_invisibles_dont_appear_in_certain_editors(cx: &mut TestAppContext) { - cx.update(|cx| { - let mut test_settings = Settings::test(cx); - test_settings.editor_defaults.show_whitespaces = Some(ShowWhitespaces::All); - test_settings.editor_defaults.tab_size = Some(NonZeroU32::new(4).unwrap()); - cx.set_global(test_settings); + init_test(cx, |s| { + s.defaults.show_whitespaces = Some(ShowWhitespaceSetting::All); + s.defaults.tab_size = NonZeroU32::new(4); }); for editor_mode_without_invisibles in [ @@ -3014,19 +3017,18 @@ mod tests { ); info!("Expected invisibles: {expected_invisibles:?}"); + init_test(cx, |_| {}); + // Put the same string with repeating whitespace pattern into editors of various size, // take deliberately small steps during resizing, to put all whitespace kinds near the wrap point. let resize_step = 10.0; let mut editor_width = 200.0; while editor_width <= 1000.0 { - cx.update(|cx| { - let mut test_settings = Settings::test(cx); - test_settings.editor_defaults.tab_size = Some(NonZeroU32::new(tab_size).unwrap()); - test_settings.editor_defaults.show_whitespaces = Some(ShowWhitespaces::All); - test_settings.editor_defaults.preferred_line_length = Some(editor_width as u32); - test_settings.editor_defaults.soft_wrap = - Some(settings::SoftWrap::PreferredLineLength); - cx.set_global(test_settings); + update_test_settings(cx, |s| { + s.defaults.tab_size = NonZeroU32::new(tab_size); + s.defaults.show_whitespaces = Some(ShowWhitespaceSetting::All); + s.defaults.preferred_line_length = Some(editor_width as u32); + s.defaults.soft_wrap = Some(language_settings::SoftWrap::PreferredLineLength); }); let actual_invisibles = @@ -3074,7 +3076,7 @@ mod tests { let mut element = EditorElement::new(editor.read_with(cx, |editor, cx| editor.style(cx))); let (_, layout_state) = editor.update(cx, |editor, cx| { - editor.set_soft_wrap_mode(settings::SoftWrap::EditorWidth, cx); + editor.set_soft_wrap_mode(language_settings::SoftWrap::EditorWidth, cx); editor.set_wrap_width(Some(editor_width), cx); let mut new_parents = Default::default(); diff --git a/crates/editor/src/highlight_matching_bracket.rs b/crates/editor/src/highlight_matching_bracket.rs index ce3864f56a2cf228284092719dda7408ecd574f8..a0baf6882fba4952488701ad8b36a25e5566332a 100644 --- a/crates/editor/src/highlight_matching_bracket.rs +++ b/crates/editor/src/highlight_matching_bracket.rs @@ -33,12 +33,14 @@ pub fn refresh_matching_bracket_highlights(editor: &mut Editor, cx: &mut ViewCon #[cfg(test)] mod tests { use super::*; - use crate::test::editor_lsp_test_context::EditorLspTestContext; + use crate::{editor_tests::init_test, test::editor_lsp_test_context::EditorLspTestContext}; use indoc::indoc; use language::{BracketPair, BracketPairConfig, Language, LanguageConfig}; #[gpui::test] async fn test_matching_bracket_highlights(cx: &mut gpui::TestAppContext) { + init_test(cx, |_| {}); + let mut cx = EditorLspTestContext::new( Language::new( LanguageConfig { diff --git a/crates/editor/src/hover_popover.rs b/crates/editor/src/hover_popover.rs index 85edb97da46cba1837727244f36e83b9dd90d28f..9192dc75e13997dcd77f10da172ce7084b479a77 100644 --- a/crates/editor/src/hover_popover.rs +++ b/crates/editor/src/hover_popover.rs @@ -1,6 +1,6 @@ use crate::{ - display_map::ToDisplayPoint, Anchor, AnchorRangeExt, DisplayPoint, Editor, EditorSnapshot, - EditorStyle, RangeToAnchorExt, + display_map::ToDisplayPoint, Anchor, AnchorRangeExt, DisplayPoint, Editor, EditorSettings, + EditorSnapshot, EditorStyle, RangeToAnchorExt, }; use futures::FutureExt; use gpui::{ @@ -12,7 +12,6 @@ use gpui::{ }; use language::{Bias, DiagnosticEntry, DiagnosticSeverity, Language, LanguageRegistry}; use project::{HoverBlock, HoverBlockKind, Project}; -use settings::Settings; use std::{ops::Range, sync::Arc, time::Duration}; use util::TryFutureExt; @@ -38,7 +37,7 @@ pub fn hover(editor: &mut Editor, _: &Hover, cx: &mut ViewContext) { /// The internal hover action dispatches between `show_hover` or `hide_hover` /// depending on whether a point to hover over is provided. pub fn hover_at(editor: &mut Editor, point: Option, cx: &mut ViewContext) { - if cx.global::().hover_popover_enabled { + if settings::get::(cx).hover_popover_enabled { if let Some(point) = point { show_hover(editor, point, false, cx); } else { @@ -654,7 +653,7 @@ impl DiagnosticPopover { _ => style.hover_popover.container, }; - let tooltip_style = cx.global::().theme.tooltip.clone(); + let tooltip_style = theme::current(cx).tooltip.clone(); MouseEventHandler::::new(0, cx, |_, _| { text.with_soft_wrap(true) @@ -694,7 +693,7 @@ impl DiagnosticPopover { #[cfg(test)] mod tests { use super::*; - use crate::test::editor_lsp_test_context::EditorLspTestContext; + use crate::{editor_tests::init_test, test::editor_lsp_test_context::EditorLspTestContext}; use gpui::fonts::Weight; use indoc::indoc; use language::{Diagnostic, DiagnosticSet}; @@ -706,6 +705,8 @@ mod tests { #[gpui::test] async fn test_mouse_hover_info_popover(cx: &mut gpui::TestAppContext) { + init_test(cx, |_| {}); + let mut cx = EditorLspTestContext::new_rust( lsp::ServerCapabilities { hover_provider: Some(lsp::HoverProviderCapability::Simple(true)), @@ -773,6 +774,8 @@ mod tests { #[gpui::test] async fn test_keyboard_hover_info_popover(cx: &mut gpui::TestAppContext) { + init_test(cx, |_| {}); + let mut cx = EditorLspTestContext::new_rust( lsp::ServerCapabilities { hover_provider: Some(lsp::HoverProviderCapability::Simple(true)), @@ -816,6 +819,8 @@ mod tests { #[gpui::test] async fn test_hover_diagnostic_and_info_popovers(cx: &mut gpui::TestAppContext) { + init_test(cx, |_| {}); + let mut cx = EditorLspTestContext::new_rust( lsp::ServerCapabilities { hover_provider: Some(lsp::HoverProviderCapability::Simple(true)), @@ -882,7 +887,8 @@ mod tests { #[gpui::test] fn test_render_blocks(cx: &mut gpui::TestAppContext) { - Settings::test_async(cx); + init_test(cx, |_| {}); + cx.add_window(|cx| { let editor = Editor::single_line(None, cx); let style = editor.style(cx); diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs index 9e122cc63d31ce5d79246a37050ffa0c76fbd907..988f263337e16f78af76fad7d3fd4150057cc75d 100644 --- a/crates/editor/src/items.rs +++ b/crates/editor/src/items.rs @@ -16,7 +16,6 @@ use language::{ }; use project::{FormatTrigger, Item as _, Project, ProjectPath}; use rpc::proto::{self, update_view}; -use settings::Settings; use smallvec::SmallVec; use std::{ borrow::Cow, @@ -1116,7 +1115,7 @@ impl View for CursorPosition { fn render(&mut self, cx: &mut ViewContext) -> AnyElement { if let Some(position) = self.position { - let theme = &cx.global::().theme.workspace.status_bar; + let theme = &theme::current(cx).workspace.status_bar; let mut text = format!( "{}{FILE_ROW_COLUMN_DELIMITER}{}", position.row + 1, diff --git a/crates/editor/src/link_go_to_definition.rs b/crates/editor/src/link_go_to_definition.rs index b2105c1c8138fd884985a1a4365eb834aee1ba6b..a52647fb5504b419a1597879de2db585182326bb 100644 --- a/crates/editor/src/link_go_to_definition.rs +++ b/crates/editor/src/link_go_to_definition.rs @@ -1,10 +1,8 @@ -use std::ops::Range; - use crate::{Anchor, DisplayPoint, Editor, EditorSnapshot, SelectPhase}; use gpui::{Task, ViewContext}; use language::{Bias, ToOffset}; use project::LocationLink; -use settings::Settings; +use std::ops::Range; use util::TryFutureExt; #[derive(Debug, Default)] @@ -211,7 +209,7 @@ pub fn show_link_definition( }); // Highlight symbol using theme link definition highlight style - let style = cx.global::().theme.editor.link_definition; + let style = theme::current(cx).editor.link_definition; this.highlight_text::( vec![highlight_range], style, @@ -297,6 +295,8 @@ fn go_to_fetched_definition_of_kind( #[cfg(test)] mod tests { + use super::*; + use crate::{editor_tests::init_test, test::editor_lsp_test_context::EditorLspTestContext}; use futures::StreamExt; use gpui::{ platform::{self, Modifiers, ModifiersChangedEvent}, @@ -305,12 +305,10 @@ mod tests { use indoc::indoc; use lsp::request::{GotoDefinition, GotoTypeDefinition}; - use crate::test::editor_lsp_test_context::EditorLspTestContext; - - use super::*; - #[gpui::test] async fn test_link_go_to_type_definition(cx: &mut gpui::TestAppContext) { + init_test(cx, |_| {}); + let mut cx = EditorLspTestContext::new_rust( lsp::ServerCapabilities { hover_provider: Some(lsp::HoverProviderCapability::Simple(true)), @@ -417,6 +415,8 @@ mod tests { #[gpui::test] async fn test_link_go_to_definition(cx: &mut gpui::TestAppContext) { + init_test(cx, |_| {}); + let mut cx = EditorLspTestContext::new_rust( lsp::ServerCapabilities { hover_provider: Some(lsp::HoverProviderCapability::Simple(true)), diff --git a/crates/editor/src/mouse_context_menu.rs b/crates/editor/src/mouse_context_menu.rs index b892fffbcac3cd0a1e76966a5a8190a89fcf8d2a..8dfdcdff53b77b8bb1dcb41c71104b9901406b94 100644 --- a/crates/editor/src/mouse_context_menu.rs +++ b/crates/editor/src/mouse_context_menu.rs @@ -57,13 +57,14 @@ pub fn deploy_context_menu( #[cfg(test)] mod tests { - use crate::test::editor_lsp_test_context::EditorLspTestContext; - use super::*; + use crate::{editor_tests::init_test, test::editor_lsp_test_context::EditorLspTestContext}; use indoc::indoc; #[gpui::test] async fn test_mouse_context_menu(cx: &mut gpui::TestAppContext) { + init_test(cx, |_| {}); + let mut cx = EditorLspTestContext::new_rust( lsp::ServerCapabilities { hover_provider: Some(lsp::HoverProviderCapability::Simple(true)), diff --git a/crates/editor/src/movement.rs b/crates/editor/src/movement.rs index 284f0c94bc4e7d080b9ae756ba62747c443d4b41..6c9bd6cb4fe1554d9a04a840db78ff8edc88b4c9 100644 --- a/crates/editor/src/movement.rs +++ b/crates/editor/src/movement.rs @@ -369,11 +369,12 @@ pub fn split_display_range_by_lines( mod tests { use super::*; use crate::{test::marked_display_snapshot, Buffer, DisplayMap, ExcerptRange, MultiBuffer}; - use settings::Settings; + use settings::SettingsStore; #[gpui::test] fn test_previous_word_start(cx: &mut gpui::AppContext) { - cx.set_global(Settings::test(cx)); + init_test(cx); + fn assert(marked_text: &str, cx: &mut gpui::AppContext) { let (snapshot, display_points) = marked_display_snapshot(marked_text, cx); assert_eq!( @@ -400,7 +401,8 @@ mod tests { #[gpui::test] fn test_previous_subword_start(cx: &mut gpui::AppContext) { - cx.set_global(Settings::test(cx)); + init_test(cx); + fn assert(marked_text: &str, cx: &mut gpui::AppContext) { let (snapshot, display_points) = marked_display_snapshot(marked_text, cx); assert_eq!( @@ -434,7 +436,8 @@ mod tests { #[gpui::test] fn test_find_preceding_boundary(cx: &mut gpui::AppContext) { - cx.set_global(Settings::test(cx)); + init_test(cx); + fn assert( marked_text: &str, cx: &mut gpui::AppContext, @@ -466,7 +469,8 @@ mod tests { #[gpui::test] fn test_next_word_end(cx: &mut gpui::AppContext) { - cx.set_global(Settings::test(cx)); + init_test(cx); + fn assert(marked_text: &str, cx: &mut gpui::AppContext) { let (snapshot, display_points) = marked_display_snapshot(marked_text, cx); assert_eq!( @@ -490,7 +494,8 @@ mod tests { #[gpui::test] fn test_next_subword_end(cx: &mut gpui::AppContext) { - cx.set_global(Settings::test(cx)); + init_test(cx); + fn assert(marked_text: &str, cx: &mut gpui::AppContext) { let (snapshot, display_points) = marked_display_snapshot(marked_text, cx); assert_eq!( @@ -523,7 +528,8 @@ mod tests { #[gpui::test] fn test_find_boundary(cx: &mut gpui::AppContext) { - cx.set_global(Settings::test(cx)); + init_test(cx); + fn assert( marked_text: &str, cx: &mut gpui::AppContext, @@ -555,7 +561,8 @@ mod tests { #[gpui::test] fn test_surrounding_word(cx: &mut gpui::AppContext) { - cx.set_global(Settings::test(cx)); + init_test(cx); + fn assert(marked_text: &str, cx: &mut gpui::AppContext) { let (snapshot, display_points) = marked_display_snapshot(marked_text, cx); assert_eq!( @@ -576,7 +583,8 @@ mod tests { #[gpui::test] fn test_move_up_and_down_with_excerpts(cx: &mut gpui::AppContext) { - cx.set_global(Settings::test(cx)); + init_test(cx); + let family_id = cx .font_cache() .load_family(&["Helvetica"], &Default::default()) @@ -691,4 +699,11 @@ mod tests { (DisplayPoint::new(7, 2), SelectionGoal::Column(2)), ); } + + fn init_test(cx: &mut gpui::AppContext) { + cx.set_global(SettingsStore::test(cx)); + theme::init((), cx); + language::init(cx); + crate::init(cx); + } } diff --git a/crates/editor/src/multi_buffer.rs b/crates/editor/src/multi_buffer.rs index a2160b47e5d342122b57f964f2070cdc8010684a..eb69e8e7c18b87bf87b7ce4608f7f865968be9eb 100644 --- a/crates/editor/src/multi_buffer.rs +++ b/crates/editor/src/multi_buffer.rs @@ -9,7 +9,9 @@ use git::diff::DiffHunk; use gpui::{AppContext, Entity, ModelContext, ModelHandle, Task}; pub use language::Completion; use language::{ - char_kind, AutoindentMode, Buffer, BufferChunks, BufferSnapshot, CharKind, Chunk, CursorShape, + char_kind, + language_settings::{language_settings, LanguageSettings}, + AutoindentMode, Buffer, BufferChunks, BufferSnapshot, CharKind, Chunk, CursorShape, DiagnosticEntry, File, IndentSize, Language, LanguageScope, OffsetRangeExt, OffsetUtf16, Outline, OutlineItem, Point, PointUtf16, Selection, TextDimension, ToOffset as _, ToOffsetUtf16 as _, ToPoint as _, ToPointUtf16 as _, TransactionId, Unclipped, @@ -1372,6 +1374,15 @@ impl MultiBuffer { .and_then(|(buffer, offset)| buffer.read(cx).language_at(offset)) } + pub fn settings_at<'a, T: ToOffset>( + &self, + point: T, + cx: &'a AppContext, + ) -> &'a LanguageSettings { + let language = self.language_at(point, cx); + language_settings(language.map(|l| l.name()).as_deref(), cx) + } + pub fn for_each_buffer(&self, mut f: impl FnMut(&ModelHandle)) { self.buffers .borrow() @@ -2764,6 +2775,16 @@ impl MultiBufferSnapshot { .and_then(|(buffer, offset)| buffer.language_at(offset)) } + pub fn settings_at<'a, T: ToOffset>( + &'a self, + point: T, + cx: &'a AppContext, + ) -> &'a LanguageSettings { + self.point_to_buffer_offset(point) + .map(|(buffer, offset)| buffer.settings_at(offset, cx)) + .unwrap_or_else(|| language_settings(None, cx)) + } + pub fn language_scope_at<'a, T: ToOffset>(&'a self, point: T) -> Option { self.point_to_buffer_offset(point) .and_then(|(buffer, offset)| buffer.language_scope_at(offset)) @@ -3785,10 +3806,9 @@ mod tests { use gpui::{AppContext, TestAppContext}; use language::{Buffer, Rope}; use rand::prelude::*; - use settings::Settings; + use settings::SettingsStore; use std::{env, rc::Rc}; use unindent::Unindent; - use util::test::sample_text; #[gpui::test] @@ -5034,7 +5054,8 @@ mod tests { #[gpui::test] fn test_history(cx: &mut AppContext) { - cx.set_global(Settings::test(cx)); + cx.set_global(SettingsStore::test(cx)); + let buffer_1 = cx.add_model(|cx| Buffer::new(0, "1234", cx)); let buffer_2 = cx.add_model(|cx| Buffer::new(0, "5678", cx)); let multibuffer = cx.add_model(|_| MultiBuffer::new(0)); diff --git a/crates/editor/src/test/editor_lsp_test_context.rs b/crates/editor/src/test/editor_lsp_test_context.rs index fe9a7909b8538bd7df4d91a97ff058d19cd1b3da..0fe49d4d0462216b28ea9273e9490e96a7bc3a9d 100644 --- a/crates/editor/src/test/editor_lsp_test_context.rs +++ b/crates/editor/src/test/editor_lsp_test_context.rs @@ -34,13 +34,17 @@ impl<'a> EditorLspTestContext<'a> { ) -> EditorLspTestContext<'a> { use json::json; + let app_state = cx.update(AppState::test); + cx.update(|cx| { + theme::init((), cx); + language::init(cx); crate::init(cx); pane::init(cx); + Project::init_settings(cx); + workspace::init_settings(cx); }); - let app_state = cx.update(AppState::test); - let file_name = format!( "file.{}", language diff --git a/crates/editor/src/test/editor_test_context.rs b/crates/editor/src/test/editor_test_context.rs index 269a2f4f3cbb0d9b26654429ad0ae7b0f7a1c3b7..ced99a3f23407524df3baae4ed4b0f25fa84b3fd 100644 --- a/crates/editor/src/test/editor_test_context.rs +++ b/crates/editor/src/test/editor_test_context.rs @@ -1,19 +1,16 @@ -use std::{ - any::TypeId, - ops::{Deref, DerefMut, Range}, -}; - -use futures::Future; -use indoc::indoc; - use crate::{ display_map::ToDisplayPoint, AnchorRangeExt, Autoscroll, DisplayPoint, Editor, MultiBuffer, }; +use futures::Future; use gpui::{ keymap_matcher::Keystroke, AppContext, ContextHandle, ModelContext, ViewContext, ViewHandle, }; +use indoc::indoc; use language::{Buffer, BufferSnapshot}; -use settings::Settings; +use std::{ + any::TypeId, + ops::{Deref, DerefMut, Range}, +}; use util::{ assert_set_eq, test::{generate_marked_text, marked_text_ranges}, @@ -30,15 +27,10 @@ pub struct EditorTestContext<'a> { impl<'a> EditorTestContext<'a> { pub fn new(cx: &'a mut gpui::TestAppContext) -> EditorTestContext<'a> { let (window_id, editor) = cx.update(|cx| { - cx.set_global(Settings::test(cx)); - crate::init(cx); - - let (window_id, editor) = cx.add_window(Default::default(), |cx| { + cx.add_window(Default::default(), |cx| { cx.focus_self(); build_editor(MultiBuffer::build_simple("", cx), cx) - }); - - (window_id, editor) + }) }); Self { diff --git a/crates/feedback/src/deploy_feedback_button.rs b/crates/feedback/src/deploy_feedback_button.rs index b464d0088707e73be0f71a1c2a333823ce76f13d..9133174475145ee3cc159695f14af99e562de43a 100644 --- a/crates/feedback/src/deploy_feedback_button.rs +++ b/crates/feedback/src/deploy_feedback_button.rs @@ -3,7 +3,6 @@ use gpui::{ platform::{CursorStyle, MouseButton}, Entity, View, ViewContext, WeakViewHandle, }; -use settings::Settings; use workspace::{item::ItemHandle, StatusItemView, Workspace}; use crate::feedback_editor::{FeedbackEditor, GiveFeedback}; @@ -33,7 +32,7 @@ impl View for DeployFeedbackButton { fn render(&mut self, cx: &mut ViewContext) -> AnyElement { let active = self.active; - let theme = cx.global::().theme.clone(); + let theme = theme::current(cx).clone(); Stack::new() .with_child( MouseEventHandler::::new(0, cx, |state, _| { diff --git a/crates/feedback/src/feedback_info_text.rs b/crates/feedback/src/feedback_info_text.rs index 9aee4e0e68b6c2ec4f1d590373559eb97b4b8412..5852cd9a6184449b08eb7844bf7ac6d11b3ca6ee 100644 --- a/crates/feedback/src/feedback_info_text.rs +++ b/crates/feedback/src/feedback_info_text.rs @@ -3,7 +3,6 @@ use gpui::{ platform::{CursorStyle, MouseButton}, AnyElement, Element, Entity, View, ViewContext, ViewHandle, }; -use settings::Settings; use workspace::{item::ItemHandle, ToolbarItemLocation, ToolbarItemView}; use crate::{feedback_editor::FeedbackEditor, open_zed_community_repo, OpenZedCommunityRepo}; @@ -30,7 +29,7 @@ impl View for FeedbackInfoText { } fn render(&mut self, cx: &mut ViewContext) -> AnyElement { - let theme = cx.global::().theme.clone(); + let theme = theme::current(cx).clone(); Flex::row() .with_child( diff --git a/crates/feedback/src/submit_feedback_button.rs b/crates/feedback/src/submit_feedback_button.rs index ccd58c3dc97dbcdad2920722a4e4fb0882edbb2f..56bc235570742d90850886b2460322035fa3c2fa 100644 --- a/crates/feedback/src/submit_feedback_button.rs +++ b/crates/feedback/src/submit_feedback_button.rs @@ -5,7 +5,6 @@ use gpui::{ platform::{CursorStyle, MouseButton}, AnyElement, AppContext, Element, Entity, Task, View, ViewContext, ViewHandle, }; -use settings::Settings; use workspace::{item::ItemHandle, ToolbarItemLocation, ToolbarItemView}; pub fn init(cx: &mut AppContext) { @@ -46,7 +45,7 @@ impl View for SubmitFeedbackButton { } fn render(&mut self, cx: &mut ViewContext) -> AnyElement { - let theme = cx.global::().theme.clone(); + let theme = theme::current(cx).clone(); enum SubmitFeedbackButton {} MouseEventHandler::::new(0, cx, |state, _| { let style = theme.feedback.submit_button.style_for(state, false); diff --git a/crates/file_finder/Cargo.toml b/crates/file_finder/Cargo.toml index 8b99cc58560b858642300899b1fd7f6077da5437..0349d26408b9888e54e2b6e6e4a9769218306593 100644 --- a/crates/file_finder/Cargo.toml +++ b/crates/file_finder/Cargo.toml @@ -24,7 +24,9 @@ postage.workspace = true [dev-dependencies] gpui = { path = "../gpui", features = ["test-support"] } -serde_json.workspace = true +language = { path = "../language", features = ["test-support"] } workspace = { path = "../workspace", features = ["test-support"] } + +serde_json.workspace = true ctor.workspace = true env_logger.workspace = true diff --git a/crates/file_finder/src/file_finder.rs b/crates/file_finder/src/file_finder.rs index 063067891a902538e5b861642d6497054cf43ca0..b318f1d1677ab5a82c4288e945737aa875601629 100644 --- a/crates/file_finder/src/file_finder.rs +++ b/crates/file_finder/src/file_finder.rs @@ -5,7 +5,6 @@ use gpui::{ }; use picker::{Picker, PickerDelegate}; use project::{PathMatchCandidateSet, Project, ProjectPath, WorktreeId}; -use settings::Settings; use std::{ path::Path, sync::{ @@ -324,8 +323,8 @@ impl PickerDelegate for FileFinderDelegate { cx: &AppContext, ) -> AnyElement> { let path_match = &self.matches[ix]; - let settings = cx.global::(); - let style = settings.theme.picker.item.style_for(mouse_state, selected); + let theme = theme::current(cx); + let style = theme.picker.item.style_for(mouse_state, selected); let (file_name, file_name_positions, full_path, full_path_positions) = self.labels_for_match(path_match); Flex::column() @@ -344,9 +343,11 @@ impl PickerDelegate for FileFinderDelegate { #[cfg(test)] mod tests { + use std::time::Duration; + use super::*; use editor::Editor; - use gpui::executor::Deterministic; + use gpui::TestAppContext; use menu::{Confirm, SelectNext}; use serde_json::json; use workspace::{AppState, Workspace}; @@ -359,13 +360,8 @@ mod tests { } #[gpui::test] - async fn test_matching_paths(cx: &mut gpui::TestAppContext) { - let app_state = cx.update(|cx| { - super::init(cx); - editor::init(cx); - AppState::test(cx) - }); - + async fn test_matching_paths(cx: &mut TestAppContext) { + let app_state = init_test(cx); app_state .fs .as_fake() @@ -415,15 +411,8 @@ mod tests { } #[gpui::test] - async fn test_row_column_numbers_query_inside_file( - deterministic: Arc, - cx: &mut gpui::TestAppContext, - ) { - let app_state = cx.update(|cx| { - super::init(cx); - editor::init(cx); - AppState::test(cx) - }); + async fn test_row_column_numbers_query_inside_file(cx: &mut TestAppContext) { + let app_state = init_test(cx); let first_file_name = "first.rs"; let first_file_contents = "// First Rust file"; @@ -484,9 +473,9 @@ mod tests { let active_item = active_pane.read(cx).active_item().unwrap(); active_item.downcast::().unwrap() }); - deterministic.advance_clock(std::time::Duration::from_secs(2)); - deterministic.start_waiting(); - deterministic.finish_waiting(); + cx.foreground().advance_clock(Duration::from_secs(2)); + cx.foreground().start_waiting(); + cx.foreground().finish_waiting(); editor.update(cx, |editor, cx| { let all_selections = editor.selections.all_adjusted(cx); assert_eq!( @@ -505,15 +494,8 @@ mod tests { } #[gpui::test] - async fn test_row_column_numbers_query_outside_file( - deterministic: Arc, - cx: &mut gpui::TestAppContext, - ) { - let app_state = cx.update(|cx| { - super::init(cx); - editor::init(cx); - AppState::test(cx) - }); + async fn test_row_column_numbers_query_outside_file(cx: &mut TestAppContext) { + let app_state = init_test(cx); let first_file_name = "first.rs"; let first_file_contents = "// First Rust file"; @@ -574,9 +556,9 @@ mod tests { let active_item = active_pane.read(cx).active_item().unwrap(); active_item.downcast::().unwrap() }); - deterministic.advance_clock(std::time::Duration::from_secs(2)); - deterministic.start_waiting(); - deterministic.finish_waiting(); + cx.foreground().advance_clock(Duration::from_secs(2)); + cx.foreground().start_waiting(); + cx.foreground().finish_waiting(); editor.update(cx, |editor, cx| { let all_selections = editor.selections.all_adjusted(cx); assert_eq!( @@ -595,8 +577,8 @@ mod tests { } #[gpui::test] - async fn test_matching_cancellation(cx: &mut gpui::TestAppContext) { - let app_state = cx.update(AppState::test); + async fn test_matching_cancellation(cx: &mut TestAppContext) { + let app_state = init_test(cx); app_state .fs .as_fake() @@ -664,8 +646,8 @@ mod tests { } #[gpui::test] - async fn test_ignored_files(cx: &mut gpui::TestAppContext) { - let app_state = cx.update(AppState::test); + async fn test_ignored_files(cx: &mut TestAppContext) { + let app_state = init_test(cx); app_state .fs .as_fake() @@ -720,8 +702,8 @@ mod tests { } #[gpui::test] - async fn test_single_file_worktrees(cx: &mut gpui::TestAppContext) { - let app_state = cx.update(AppState::test); + async fn test_single_file_worktrees(cx: &mut TestAppContext) { + let app_state = init_test(cx); app_state .fs .as_fake() @@ -778,10 +760,8 @@ mod tests { } #[gpui::test] - async fn test_multiple_matches_with_same_relative_path(cx: &mut gpui::TestAppContext) { - cx.foreground().forbid_parking(); - - let app_state = cx.update(AppState::test); + async fn test_multiple_matches_with_same_relative_path(cx: &mut TestAppContext) { + let app_state = init_test(cx); app_state .fs .as_fake() @@ -834,10 +814,8 @@ mod tests { } #[gpui::test] - async fn test_path_distance_ordering(cx: &mut gpui::TestAppContext) { - cx.foreground().forbid_parking(); - - let app_state = cx.update(AppState::test); + async fn test_path_distance_ordering(cx: &mut TestAppContext) { + let app_state = init_test(cx); app_state .fs .as_fake() @@ -886,8 +864,8 @@ mod tests { } #[gpui::test] - async fn test_search_worktree_without_files(cx: &mut gpui::TestAppContext) { - let app_state = cx.update(AppState::test); + async fn test_search_worktree_without_files(cx: &mut TestAppContext) { + let app_state = init_test(cx); app_state .fs .as_fake() @@ -926,6 +904,19 @@ mod tests { }); } + fn init_test(cx: &mut TestAppContext) -> Arc { + cx.foreground().forbid_parking(); + cx.update(|cx| { + let state = AppState::test(cx); + theme::init((), cx); + language::init(cx); + super::init(cx); + editor::init(cx); + workspace::init_settings(cx); + state + }) + } + fn test_path_like(test_str: &str) -> PathLikeWithPosition { PathLikeWithPosition::parse_str(test_str, |path_like_str| { Ok::<_, std::convert::Infallible>(FileSearchQuery { diff --git a/crates/go_to_line/Cargo.toml b/crates/go_to_line/Cargo.toml index 8f99aa366cbad598d00d5cbe24a6c84ede2deaa5..441f7ef7e40fbc5ea4b7ac6c1965c33090b45b5d 100644 --- a/crates/go_to_line/Cargo.toml +++ b/crates/go_to_line/Cargo.toml @@ -16,4 +16,5 @@ settings = { path = "../settings" } text = { path = "../text" } workspace = { path = "../workspace" } postage.workspace = true +theme = { path = "../theme" } util = { path = "../util" } diff --git a/crates/go_to_line/src/go_to_line.rs b/crates/go_to_line/src/go_to_line.rs index 967f17b79457596cf58967bd2258bebf97282d59..0b41ee6dca3630450668a7eac69aeea2e99d3400 100644 --- a/crates/go_to_line/src/go_to_line.rs +++ b/crates/go_to_line/src/go_to_line.rs @@ -6,7 +6,6 @@ use gpui::{ View, ViewContext, ViewHandle, }; use menu::{Cancel, Confirm}; -use settings::Settings; use text::{Bias, Point}; use util::paths::FILE_ROW_COLUMN_DELIMITER; use workspace::{Modal, Workspace}; @@ -151,7 +150,7 @@ impl View for GoToLine { } fn render(&mut self, cx: &mut ViewContext) -> AnyElement { - let theme = &cx.global::().theme.picker; + let theme = &theme::current(cx).picker; let label = format!( "{}{FILE_ROW_COLUMN_DELIMITER}{} of {} lines", diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index d5a7a2f3ab158fc3b91ece1a558231627026399d..5def0bed9d37ec24360cb2fe2f5cba3c6ed2bbcf 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -1174,7 +1174,7 @@ impl AppContext { this.notify_global(type_id); result } else { - panic!("No global added for {}", std::any::type_name::()); + panic!("no global added for {}", std::any::type_name::()); } } @@ -1182,6 +1182,15 @@ impl AppContext { self.globals.clear(); } + pub fn remove_global(&mut self) -> T { + *self + .globals + .remove(&TypeId::of::()) + .unwrap_or_else(|| panic!("no global added for {}", std::any::type_name::())) + .downcast() + .unwrap() + } + pub fn add_model(&mut self, build_model: F) -> ModelHandle where T: Entity, diff --git a/crates/gpui/src/executor.rs b/crates/gpui/src/executor.rs index 7baffab2d94caa72478d5fc28051fa927b2ff93d..028656a027db012e38041cfa5ed7b5806bf5b432 100644 --- a/crates/gpui/src/executor.rs +++ b/crates/gpui/src/executor.rs @@ -477,6 +477,14 @@ impl Deterministic { state.rng = StdRng::seed_from_u64(state.seed); } + pub fn allow_parking(&self) { + use rand::prelude::*; + + let mut state = self.state.lock(); + state.forbid_parking = false; + state.rng = StdRng::seed_from_u64(state.seed); + } + pub async fn simulate_random_delay(&self) { use rand::prelude::*; use smol::future::yield_now; @@ -698,6 +706,14 @@ impl Foreground { } } + #[cfg(any(test, feature = "test-support"))] + pub fn allow_parking(&self) { + match self { + Self::Deterministic { executor, .. } => executor.allow_parking(), + _ => panic!("this method can only be called on a deterministic executor"), + } + } + #[cfg(any(test, feature = "test-support"))] pub fn advance_clock(&self, duration: Duration) { match self { diff --git a/crates/journal/Cargo.toml b/crates/journal/Cargo.toml index b88e3e093a7df054ccc261e64ca6b5ff17e26e42..c1d9bde89e19bccc1404b8a292543f29185eb87d 100644 --- a/crates/journal/Cargo.toml +++ b/crates/journal/Cargo.toml @@ -13,9 +13,12 @@ editor = { path = "../editor" } gpui = { path = "../gpui" } util = { path = "../util" } workspace = { path = "../workspace" } +settings = { path = "../settings" } + anyhow.workspace = true chrono = "0.4" dirs = "4.0" +serde.workspace = true +schemars.workspace = true log.workspace = true -settings = { path = "../settings" } shellexpand = "2.1.0" diff --git a/crates/journal/src/journal.rs b/crates/journal/src/journal.rs index 4b9622ece9c38089fd1c6c2b83f138279eef94ac..99fe997dc5eeb4c428c8331958a1b8ef048eb357 100644 --- a/crates/journal/src/journal.rs +++ b/crates/journal/src/journal.rs @@ -1,7 +1,9 @@ +use anyhow::Result; use chrono::{Datelike, Local, NaiveTime, Timelike}; use editor::{scroll::autoscroll::Autoscroll, Editor}; use gpui::{actions, AppContext}; -use settings::{HourFormat, Settings}; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; use std::{ fs::OpenOptions, path::{Path, PathBuf}, @@ -11,13 +13,48 @@ use workspace::AppState; actions!(journal, [NewJournalEntry]); +#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)] +pub struct JournalSettings { + pub path: Option, + pub hour_format: Option, +} + +impl Default for JournalSettings { + fn default() -> Self { + Self { + path: Some("~".into()), + hour_format: Some(Default::default()), + } + } +} + +#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum HourFormat { + #[default] + Hour12, + Hour24, +} + +impl settings::Setting for JournalSettings { + const KEY: Option<&'static str> = Some("journal"); + + type FileContent = Self; + + fn load(default_value: &Self, user_values: &[&Self], _: &AppContext) -> Result { + Self::load_via_json_merge(default_value, user_values) + } +} + pub fn init(app_state: Arc, cx: &mut AppContext) { + settings::register::(cx); + cx.add_global_action(move |_: &NewJournalEntry, cx| new_journal_entry(app_state.clone(), cx)); } pub fn new_journal_entry(app_state: Arc, cx: &mut AppContext) { - let settings = cx.global::(); - let journal_dir = match journal_dir(&settings) { + let settings = settings::get::(cx); + let journal_dir = match journal_dir(settings.path.as_ref().unwrap()) { Some(journal_dir) => journal_dir, None => { log::error!("Can't determine journal directory"); @@ -31,8 +68,7 @@ pub fn new_journal_entry(app_state: Arc, cx: &mut AppContext) { .join(format!("{:02}", now.month())); let entry_path = month_dir.join(format!("{:02}.md", now.day())); let now = now.time(); - let hour_format = &settings.journal_overrides.hour_format; - let entry_heading = heading_entry(now, &hour_format); + let entry_heading = heading_entry(now, &settings.hour_format); let create_entry = cx.background().spawn(async move { std::fs::create_dir_all(month_dir)?; @@ -76,14 +112,8 @@ pub fn new_journal_entry(app_state: Arc, cx: &mut AppContext) { .detach_and_log_err(cx); } -fn journal_dir(settings: &Settings) -> Option { - let journal_dir = settings - .journal_overrides - .path - .as_ref() - .unwrap_or(settings.journal_defaults.path.as_ref()?); - - let expanded_journal_dir = shellexpand::full(&journal_dir) //TODO handle this better +fn journal_dir(path: &str) -> Option { + let expanded_journal_dir = shellexpand::full(path) //TODO handle this better .ok() .map(|dir| Path::new(&dir.to_string()).to_path_buf().join("journal")); diff --git a/crates/language/Cargo.toml b/crates/language/Cargo.toml index 6e03a07bc3b0d1a392cb3d9bd8cc9299510fce1e..5a7644d98e6220d77bda66bcd6d6f35f895b67a6 100644 --- a/crates/language/Cargo.toml +++ b/crates/language/Cargo.toml @@ -36,16 +36,19 @@ sum_tree = { path = "../sum_tree" } text = { path = "../text" } theme = { path = "../theme" } util = { path = "../util" } + anyhow.workspace = true async-broadcast = "0.4" async-trait.workspace = true futures.workspace = true +glob.workspace = true lazy_static.workspace = true log.workspace = true parking_lot.workspace = true postage.workspace = true rand = { workspace = true, optional = true } regex.workspace = true +schemars.workspace = true serde.workspace = true serde_derive.workspace = true serde_json.workspace = true diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index 43766192eb871ade7c912bf098b525520a973d65..aee646091a407d327579e7a8497d588e17583ede 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -5,6 +5,7 @@ pub use crate::{ }; use crate::{ diagnostic_set::{DiagnosticEntry, DiagnosticGroup}, + language_settings::{language_settings, LanguageSettings}, outline::OutlineItem, syntax_map::{ SyntaxMap, SyntaxMapCapture, SyntaxMapCaptures, SyntaxSnapshot, ToTreeSitterPoint, @@ -18,7 +19,6 @@ use futures::FutureExt as _; use gpui::{fonts::HighlightStyle, AppContext, Entity, ModelContext, Task}; use lsp::LanguageServerId; use parking_lot::Mutex; -use settings::Settings; use similar::{ChangeTag, TextDiff}; use smallvec::SmallVec; use smol::future::yield_now; @@ -1827,11 +1827,11 @@ impl BufferSnapshot { pub fn language_indent_size_at(&self, position: T, cx: &AppContext) -> IndentSize { let language_name = self.language_at(position).map(|language| language.name()); - let settings = cx.global::(); - if settings.hard_tabs(language_name.as_deref()) { + let settings = language_settings(language_name.as_deref(), cx); + if settings.hard_tabs { IndentSize::tab() } else { - IndentSize::spaces(settings.tab_size(language_name.as_deref()).get()) + IndentSize::spaces(settings.tab_size.get()) } } @@ -2146,6 +2146,15 @@ impl BufferSnapshot { .or(self.language.as_ref()) } + pub fn settings_at<'a, D: ToOffset>( + &self, + position: D, + cx: &'a AppContext, + ) -> &'a LanguageSettings { + let language = self.language_at(position); + language_settings(language.map(|l| l.name()).as_deref(), cx) + } + pub fn language_scope_at(&self, position: D) -> Option { let offset = position.to_offset(self); diff --git a/crates/language/src/buffer_tests.rs b/crates/language/src/buffer_tests.rs index eeac1a48186b3c13bf063ab0cdc4eb1ffbe19cdc..be573aa8956e3dc28e7074d17f85160fe5f2d1e9 100644 --- a/crates/language/src/buffer_tests.rs +++ b/crates/language/src/buffer_tests.rs @@ -1,3 +1,7 @@ +use crate::language_settings::{ + AllLanguageSettings, AllLanguageSettingsContent, LanguageSettingsContent, +}; + use super::*; use clock::ReplicaId; use collections::BTreeMap; @@ -7,7 +11,7 @@ use indoc::indoc; use proto::deserialize_operation; use rand::prelude::*; use regex::RegexBuilder; -use settings::Settings; +use settings::SettingsStore; use std::{ cell::RefCell, env, @@ -36,7 +40,8 @@ fn init_logger() { #[gpui::test] fn test_line_endings(cx: &mut gpui::AppContext) { - cx.set_global(Settings::test(cx)); + init_settings(cx, |_| {}); + cx.add_model(|cx| { let mut buffer = Buffer::new(0, "one\r\ntwo\rthree", cx).with_language(Arc::new(rust_lang()), cx); @@ -862,8 +867,7 @@ fn test_range_for_syntax_ancestor(cx: &mut AppContext) { #[gpui::test] fn test_autoindent_with_soft_tabs(cx: &mut AppContext) { - let settings = Settings::test(cx); - cx.set_global(settings); + init_settings(cx, |_| {}); cx.add_model(|cx| { let text = "fn a() {}"; @@ -903,9 +907,9 @@ fn test_autoindent_with_soft_tabs(cx: &mut AppContext) { #[gpui::test] fn test_autoindent_with_hard_tabs(cx: &mut AppContext) { - let mut settings = Settings::test(cx); - settings.editor_overrides.hard_tabs = Some(true); - cx.set_global(settings); + init_settings(cx, |settings| { + settings.defaults.hard_tabs = Some(true); + }); cx.add_model(|cx| { let text = "fn a() {}"; @@ -945,8 +949,7 @@ fn test_autoindent_with_hard_tabs(cx: &mut AppContext) { #[gpui::test] fn test_autoindent_does_not_adjust_lines_with_unchanged_suggestion(cx: &mut AppContext) { - let settings = Settings::test(cx); - cx.set_global(settings); + init_settings(cx, |_| {}); cx.add_model(|cx| { let mut buffer = Buffer::new( @@ -1082,8 +1085,7 @@ fn test_autoindent_does_not_adjust_lines_with_unchanged_suggestion(cx: &mut AppC #[gpui::test] fn test_autoindent_does_not_adjust_lines_within_newly_created_errors(cx: &mut AppContext) { - let settings = Settings::test(cx); - cx.set_global(settings); + init_settings(cx, |_| {}); cx.add_model(|cx| { let mut buffer = Buffer::new( @@ -1145,7 +1147,8 @@ fn test_autoindent_does_not_adjust_lines_within_newly_created_errors(cx: &mut Ap #[gpui::test] fn test_autoindent_adjusts_lines_when_only_text_changes(cx: &mut AppContext) { - cx.set_global(Settings::test(cx)); + init_settings(cx, |_| {}); + cx.add_model(|cx| { let mut buffer = Buffer::new( 0, @@ -1201,7 +1204,8 @@ fn test_autoindent_adjusts_lines_when_only_text_changes(cx: &mut AppContext) { #[gpui::test] fn test_autoindent_with_edit_at_end_of_buffer(cx: &mut AppContext) { - cx.set_global(Settings::test(cx)); + init_settings(cx, |_| {}); + cx.add_model(|cx| { let text = "a\nb"; let mut buffer = Buffer::new(0, text, cx).with_language(Arc::new(rust_lang()), cx); @@ -1217,7 +1221,8 @@ fn test_autoindent_with_edit_at_end_of_buffer(cx: &mut AppContext) { #[gpui::test] fn test_autoindent_multi_line_insertion(cx: &mut AppContext) { - cx.set_global(Settings::test(cx)); + init_settings(cx, |_| {}); + cx.add_model(|cx| { let text = " const a: usize = 1; @@ -1257,7 +1262,8 @@ fn test_autoindent_multi_line_insertion(cx: &mut AppContext) { #[gpui::test] fn test_autoindent_block_mode(cx: &mut AppContext) { - cx.set_global(Settings::test(cx)); + init_settings(cx, |_| {}); + cx.add_model(|cx| { let text = r#" fn a() { @@ -1339,7 +1345,8 @@ fn test_autoindent_block_mode(cx: &mut AppContext) { #[gpui::test] fn test_autoindent_block_mode_without_original_indent_columns(cx: &mut AppContext) { - cx.set_global(Settings::test(cx)); + init_settings(cx, |_| {}); + cx.add_model(|cx| { let text = r#" fn a() { @@ -1417,7 +1424,8 @@ fn test_autoindent_block_mode_without_original_indent_columns(cx: &mut AppContex #[gpui::test] fn test_autoindent_language_without_indents_query(cx: &mut AppContext) { - cx.set_global(Settings::test(cx)); + init_settings(cx, |_| {}); + cx.add_model(|cx| { let text = " * one @@ -1460,25 +1468,23 @@ fn test_autoindent_language_without_indents_query(cx: &mut AppContext) { #[gpui::test] fn test_autoindent_with_injected_languages(cx: &mut AppContext) { - cx.set_global({ - let mut settings = Settings::test(cx); - settings.language_overrides.extend([ + init_settings(cx, |settings| { + settings.languages.extend([ ( "HTML".into(), - settings::EditorSettings { + LanguageSettingsContent { tab_size: Some(2.try_into().unwrap()), ..Default::default() }, ), ( "JavaScript".into(), - settings::EditorSettings { + LanguageSettingsContent { tab_size: Some(8.try_into().unwrap()), ..Default::default() }, ), - ]); - settings + ]) }); let html_language = Arc::new( @@ -1574,9 +1580,10 @@ fn test_autoindent_with_injected_languages(cx: &mut AppContext) { #[gpui::test] fn test_autoindent_query_with_outdent_captures(cx: &mut AppContext) { - let mut settings = Settings::test(cx); - settings.editor_defaults.tab_size = Some(2.try_into().unwrap()); - cx.set_global(settings); + init_settings(cx, |settings| { + settings.defaults.tab_size = Some(2.try_into().unwrap()); + }); + cx.add_model(|cx| { let mut buffer = Buffer::new(0, "", cx).with_language(Arc::new(ruby_lang()), cx); @@ -1617,7 +1624,8 @@ fn test_autoindent_query_with_outdent_captures(cx: &mut AppContext) { #[gpui::test] fn test_language_config_at(cx: &mut AppContext) { - cx.set_global(Settings::test(cx)); + init_settings(cx, |_| {}); + cx.add_model(|cx| { let language = Language::new( LanguageConfig { @@ -2199,7 +2207,6 @@ fn assert_bracket_pairs( language: Language, cx: &mut AppContext, ) { - cx.set_global(Settings::test(cx)); let (expected_text, selection_ranges) = marked_text_ranges(selection_text, false); let buffer = cx.add_model(|cx| { Buffer::new(0, expected_text.clone(), cx).with_language(Arc::new(language), cx) @@ -2222,3 +2229,11 @@ fn assert_bracket_pairs( bracket_pairs ); } + +fn init_settings(cx: &mut AppContext, f: fn(&mut AllLanguageSettingsContent)) { + cx.set_global(SettingsStore::test(cx)); + crate::init(cx); + cx.update_global::(|settings, cx| { + settings.update_user_settings::(cx, f); + }); +} diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index 85c90899521bf521fca7a7eb3bc2176a23b52301..87e4880b99186527acacd24c0a7ff609b0a015db 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -1,6 +1,7 @@ mod buffer; mod diagnostic_set; mod highlight_map; +pub mod language_settings; mod outline; pub mod proto; mod syntax_map; @@ -58,6 +59,10 @@ pub use lsp::LanguageServerId; pub use outline::{Outline, OutlineItem}; pub use tree_sitter::{Parser, Tree}; +pub fn init(cx: &mut AppContext) { + language_settings::init(cx); +} + thread_local! { static PARSER: RefCell = RefCell::new(Parser::new()); } diff --git a/crates/language/src/language_settings.rs b/crates/language/src/language_settings.rs new file mode 100644 index 0000000000000000000000000000000000000000..b47982819a5a59dbc79791c04ec4918c7f99a00a --- /dev/null +++ b/crates/language/src/language_settings.rs @@ -0,0 +1,337 @@ +use anyhow::Result; +use collections::HashMap; +use gpui::AppContext; +use schemars::{ + schema::{InstanceType, ObjectValidation, Schema, SchemaObject}, + JsonSchema, +}; +use serde::{Deserialize, Serialize}; +use std::{num::NonZeroU32, path::Path, sync::Arc}; + +pub fn init(cx: &mut AppContext) { + settings::register::(cx); +} + +pub fn language_settings<'a>(language: Option<&str>, cx: &'a AppContext) -> &'a LanguageSettings { + settings::get::(cx).language(language) +} + +pub fn all_language_settings<'a>(cx: &'a AppContext) -> &'a AllLanguageSettings { + settings::get::(cx) +} + +#[derive(Debug, Clone)] +pub struct AllLanguageSettings { + pub copilot: CopilotSettings, + defaults: LanguageSettings, + languages: HashMap, LanguageSettings>, +} + +#[derive(Debug, Clone, Deserialize)] +pub struct LanguageSettings { + pub tab_size: NonZeroU32, + pub hard_tabs: bool, + pub soft_wrap: SoftWrap, + pub preferred_line_length: u32, + pub format_on_save: FormatOnSave, + pub remove_trailing_whitespace_on_save: bool, + pub ensure_final_newline_on_save: bool, + pub formatter: Formatter, + pub enable_language_server: bool, + pub show_copilot_suggestions: bool, + pub show_whitespaces: ShowWhitespaceSetting, +} + +#[derive(Clone, Debug, Default)] +pub struct CopilotSettings { + pub feature_enabled: bool, + pub disabled_globs: Vec, +} + +#[derive(Clone, Serialize, Deserialize, JsonSchema)] +pub struct AllLanguageSettingsContent { + #[serde(default)] + pub features: Option, + #[serde(default)] + pub copilot: Option, + #[serde(flatten)] + pub defaults: LanguageSettingsContent, + #[serde(default, alias = "language_overrides")] + pub languages: HashMap, LanguageSettingsContent>, +} + +#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)] +pub struct LanguageSettingsContent { + #[serde(default)] + pub tab_size: Option, + #[serde(default)] + pub hard_tabs: Option, + #[serde(default)] + pub soft_wrap: Option, + #[serde(default)] + pub preferred_line_length: Option, + #[serde(default)] + pub format_on_save: Option, + #[serde(default)] + pub remove_trailing_whitespace_on_save: Option, + #[serde(default)] + pub ensure_final_newline_on_save: Option, + #[serde(default)] + pub formatter: Option, + #[serde(default)] + pub enable_language_server: Option, + #[serde(default)] + pub show_copilot_suggestions: Option, + #[serde(default)] + pub show_whitespaces: Option, +} + +#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)] +pub struct CopilotSettingsContent { + #[serde(default)] + pub disabled_globs: Option>, +} + +#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub struct FeaturesContent { + pub copilot: Option, +} + +#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum SoftWrap { + None, + EditorWidth, + PreferredLineLength, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum FormatOnSave { + On, + Off, + LanguageServer, + External { + command: Arc, + arguments: Arc<[String]>, + }, +} + +#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum ShowWhitespaceSetting { + Selection, + None, + All, +} + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum Formatter { + LanguageServer, + External { + command: Arc, + arguments: Arc<[String]>, + }, +} + +impl AllLanguageSettings { + pub fn language<'a>(&'a self, language_name: Option<&str>) -> &'a LanguageSettings { + if let Some(name) = language_name { + if let Some(overrides) = self.languages.get(name) { + return overrides; + } + } + &self.defaults + } + + pub fn copilot_enabled_for_path(&self, path: &Path) -> bool { + !self + .copilot + .disabled_globs + .iter() + .any(|glob| glob.matches_path(path)) + } + + pub fn copilot_enabled(&self, language_name: Option<&str>, path: Option<&Path>) -> bool { + if !self.copilot.feature_enabled { + return false; + } + + if let Some(path) = path { + if !self.copilot_enabled_for_path(path) { + return false; + } + } + + self.language(language_name).show_copilot_suggestions + } +} + +impl settings::Setting for AllLanguageSettings { + const KEY: Option<&'static str> = None; + + type FileContent = AllLanguageSettingsContent; + + fn load( + default_value: &Self::FileContent, + user_settings: &[&Self::FileContent], + _: &AppContext, + ) -> Result { + // A default is provided for all settings. + let mut defaults: LanguageSettings = + serde_json::from_value(serde_json::to_value(&default_value.defaults)?)?; + + let mut languages = HashMap::default(); + for (language_name, settings) in &default_value.languages { + let mut language_settings = defaults.clone(); + merge_settings(&mut language_settings, &settings); + languages.insert(language_name.clone(), language_settings); + } + + let mut copilot_enabled = default_value + .features + .as_ref() + .and_then(|f| f.copilot) + .ok_or_else(Self::missing_default)?; + let mut copilot_globs = default_value + .copilot + .as_ref() + .and_then(|c| c.disabled_globs.as_ref()) + .ok_or_else(Self::missing_default)?; + + for user_settings in user_settings { + if let Some(copilot) = user_settings.features.as_ref().and_then(|f| f.copilot) { + copilot_enabled = copilot; + } + if let Some(globs) = user_settings + .copilot + .as_ref() + .and_then(|f| f.disabled_globs.as_ref()) + { + copilot_globs = globs; + } + + // A user's global settings override the default global settings and + // all default language-specific settings. + merge_settings(&mut defaults, &user_settings.defaults); + for language_settings in languages.values_mut() { + merge_settings(language_settings, &user_settings.defaults); + } + + // A user's language-specific settings override default language-specific settings. + for (language_name, user_language_settings) in &user_settings.languages { + merge_settings( + languages + .entry(language_name.clone()) + .or_insert_with(|| defaults.clone()), + &user_language_settings, + ); + } + } + + Ok(Self { + copilot: CopilotSettings { + feature_enabled: copilot_enabled, + disabled_globs: copilot_globs + .iter() + .filter_map(|pattern| glob::Pattern::new(pattern).ok()) + .collect(), + }, + defaults, + languages, + }) + } + + fn json_schema( + generator: &mut schemars::gen::SchemaGenerator, + params: &settings::SettingsJsonSchemaParams, + _: &AppContext, + ) -> schemars::schema::RootSchema { + let mut root_schema = generator.root_schema_for::(); + + // Create a schema for a 'languages overrides' object, associating editor + // settings with specific langauges. + assert!(root_schema + .definitions + .contains_key("LanguageSettingsContent")); + + let languages_object_schema = SchemaObject { + instance_type: Some(InstanceType::Object.into()), + object: Some(Box::new(ObjectValidation { + properties: params + .language_names + .iter() + .map(|name| { + ( + name.clone(), + Schema::new_ref("#/definitions/LanguageSettingsContent".into()), + ) + }) + .collect(), + ..Default::default() + })), + ..Default::default() + }; + + root_schema + .definitions + .extend([("Languages".into(), languages_object_schema.into())]); + + root_schema + .schema + .object + .as_mut() + .unwrap() + .properties + .extend([ + ( + "languages".to_owned(), + Schema::new_ref("#/definitions/Languages".into()), + ), + // For backward compatibility + ( + "language_overrides".to_owned(), + Schema::new_ref("#/definitions/Languages".into()), + ), + ]); + + root_schema + } +} + +fn merge_settings(settings: &mut LanguageSettings, src: &LanguageSettingsContent) { + merge(&mut settings.tab_size, src.tab_size); + merge(&mut settings.hard_tabs, src.hard_tabs); + merge(&mut settings.soft_wrap, src.soft_wrap); + merge( + &mut settings.preferred_line_length, + src.preferred_line_length, + ); + merge(&mut settings.formatter, src.formatter.clone()); + merge(&mut settings.format_on_save, src.format_on_save.clone()); + merge( + &mut settings.remove_trailing_whitespace_on_save, + src.remove_trailing_whitespace_on_save, + ); + merge( + &mut settings.ensure_final_newline_on_save, + src.ensure_final_newline_on_save, + ); + merge( + &mut settings.enable_language_server, + src.enable_language_server, + ); + merge( + &mut settings.show_copilot_suggestions, + src.show_copilot_suggestions, + ); + merge(&mut settings.show_whitespaces, src.show_whitespaces); + + fn merge(target: &mut T, value: Option) { + if let Some(value) = value { + *target = value; + } + } +} diff --git a/crates/language_selector/src/active_buffer_language.rs b/crates/language_selector/src/active_buffer_language.rs index 425f4c8dd7f80ea4edc4fa88338a238d8db2c194..2c78b89f31c61fd7193aaf15eb1b90c15b39fdd4 100644 --- a/crates/language_selector/src/active_buffer_language.rs +++ b/crates/language_selector/src/active_buffer_language.rs @@ -4,7 +4,6 @@ use gpui::{ platform::{CursorStyle, MouseButton}, Entity, Subscription, View, ViewContext, ViewHandle, WeakViewHandle, }; -use settings::Settings; use std::sync::Arc; use workspace::{item::ItemHandle, StatusItemView, Workspace}; @@ -55,7 +54,7 @@ impl View for ActiveBufferLanguage { }; MouseEventHandler::::new(0, cx, |state, cx| { - let theme = &cx.global::().theme.workspace.status_bar; + let theme = &theme::current(cx).workspace.status_bar; let style = theme.active_language.style_for(state, false); Label::new(active_language_text, style.text.clone()) .contained() diff --git a/crates/language_selector/src/language_selector.rs b/crates/language_selector/src/language_selector.rs index fd43111443373773593573d6189ec407ec165e73..817901cd3a6a3f480a19ba5fb34d11e6c20074ae 100644 --- a/crates/language_selector/src/language_selector.rs +++ b/crates/language_selector/src/language_selector.rs @@ -8,7 +8,6 @@ use gpui::{actions, elements::*, AppContext, ModelHandle, MouseState, ViewContex use language::{Buffer, LanguageRegistry}; use picker::{Picker, PickerDelegate, PickerEvent}; use project::Project; -use settings::Settings; use std::sync::Arc; use util::ResultExt; use workspace::Workspace; @@ -179,8 +178,7 @@ impl PickerDelegate for LanguageSelectorDelegate { selected: bool, cx: &AppContext, ) -> AnyElement> { - let settings = cx.global::(); - let theme = &settings.theme; + let theme = theme::current(cx); let mat = &self.matches[ix]; let style = theme.picker.item.style_for(mouse_state, selected); let buffer_language_name = self.buffer.read(cx).language().map(|l| l.name()); diff --git a/crates/lsp_log/src/lsp_log.rs b/crates/lsp_log/src/lsp_log.rs index 0efcd3afc83d773c15bdb484c6d0f50b7876f106..db41c6ff4d475bf870f28a2fb366ff9dcd199783 100644 --- a/crates/lsp_log/src/lsp_log.rs +++ b/crates/lsp_log/src/lsp_log.rs @@ -13,7 +13,6 @@ use gpui::{ }; use language::{Buffer, LanguageServerId, LanguageServerName}; use project::{Project, WorktreeId}; -use settings::Settings; use std::{borrow::Cow, sync::Arc}; use theme::{ui, Theme}; use workspace::{ @@ -304,7 +303,7 @@ impl View for LspLogToolbarItemView { } fn render(&mut self, cx: &mut ViewContext) -> AnyElement { - let theme = cx.global::().theme.clone(); + let theme = theme::current(cx).clone(); let Some(log_view) = self.log_view.as_ref() else { return Empty::new().into_any() }; let project = self.project.read(cx); let log_view = log_view.read(cx); diff --git a/crates/outline/Cargo.toml b/crates/outline/Cargo.toml index 91e5011b2a93082185d7110778f46f2e48b02419..95272b063e798c6cc8a2ea9aa3f7278ea9c1b755 100644 --- a/crates/outline/Cargo.toml +++ b/crates/outline/Cargo.toml @@ -16,7 +16,9 @@ language = { path = "../language" } picker = { path = "../picker" } settings = { path = "../settings" } text = { path = "../text" } +theme = { path = "../theme" } workspace = { path = "../workspace" } + ordered-float.workspace = true postage.workspace = true smol.workspace = true diff --git a/crates/outline/src/outline.rs b/crates/outline/src/outline.rs index 6ecaf370e40b1c846a1775bb27c421468fcf5066..1e364f5fc8f6d9198fba71b6d11429e11e13d3e0 100644 --- a/crates/outline/src/outline.rs +++ b/crates/outline/src/outline.rs @@ -10,7 +10,6 @@ use gpui::{ use language::Outline; use ordered_float::OrderedFloat; use picker::{Picker, PickerDelegate, PickerEvent}; -use settings::Settings; use std::{ cmp::{self, Reverse}, sync::Arc, @@ -34,7 +33,7 @@ pub fn toggle(workspace: &mut Workspace, _: &Toggle, cx: &mut ViewContext().theme.editor.syntax.as_ref())); + .outline(Some(theme::current(cx).editor.syntax.as_ref())); if let Some(outline) = outline { workspace.toggle_modal(cx, |_, cx| { cx.add_view(|cx| { @@ -204,9 +203,9 @@ impl PickerDelegate for OutlineViewDelegate { selected: bool, cx: &AppContext, ) -> AnyElement> { - let settings = cx.global::(); + let theme = theme::current(cx); + let style = theme.picker.item.style_for(mouse_state, selected); let string_match = &self.matches[ix]; - let style = settings.theme.picker.item.style_for(mouse_state, selected); let outline_item = &self.outline.items[string_match.candidate_id]; Text::new(outline_item.text.clone(), style.label.text.clone()) diff --git a/crates/picker/src/picker.rs b/crates/picker/src/picker.rs index 01749ccf84d72f5cc5907bf66f7d9c75c4d3d06f..69f16e494933ed0a47bc8bf45c08e046b3e55372 100644 --- a/crates/picker/src/picker.rs +++ b/crates/picker/src/picker.rs @@ -57,7 +57,7 @@ impl View for Picker { } fn render(&mut self, cx: &mut ViewContext) -> AnyElement { - let theme = (self.theme.lock())(&cx.global::().theme); + let theme = (self.theme.lock())(theme::current(cx).as_ref()); let query = self.query(cx); let match_count = self.delegate.match_count(); diff --git a/crates/project/Cargo.toml b/crates/project/Cargo.toml index 85a302bdd7f3d6ed0050505bd4547122f3891ee4..190f1d96a8539ad41e224f346be35957547f570f 100644 --- a/crates/project/Cargo.toml +++ b/crates/project/Cargo.toml @@ -50,6 +50,7 @@ parking_lot.workspace = true postage.workspace = true rand.workspace = true regex.workspace = true +schemars.workspace = true serde.workspace = true serde_derive.workspace = true serde_json.workspace = true @@ -57,7 +58,7 @@ sha2 = "0.10" similar = "1.3" smol.workspace = true thiserror.workspace = true -toml = "0.5" +toml.workspace = true itertools = "0.10" [dev-dependencies] diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 7c513fb6e0e3055d8bb9b1c4dc516af2cbddebe2..13809622f9e3191cf4b12eaaa0347c3f98e0f050 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -1,6 +1,7 @@ mod ignore; mod lsp_command; mod lsp_glob_set; +mod project_settings; pub mod search; pub mod terminals; pub mod worktree; @@ -23,6 +24,7 @@ use gpui::{ ModelHandle, Task, WeakModelHandle, }; use language::{ + language_settings::{all_language_settings, language_settings, FormatOnSave, Formatter}, point_to_lsp, proto::{ deserialize_anchor, deserialize_fingerprint, deserialize_line_ending, deserialize_version, @@ -41,10 +43,11 @@ use lsp::{ use lsp_command::*; use lsp_glob_set::LspGlobSet; use postage::watch; +use project_settings::ProjectSettings; use rand::prelude::*; use search::SearchQuery; use serde::Serialize; -use settings::{FormatOnSave, Formatter, Settings}; +use settings::SettingsStore; use sha2::{Digest, Sha256}; use similar::{ChangeTag, TextDiff}; use std::{ @@ -64,9 +67,7 @@ use std::{ }, time::{Duration, Instant, SystemTime}, }; - use terminals::Terminals; - use util::{debug_panic, defer, merge_json_value_into, post_inc, ResultExt, TryFutureExt as _}; pub use fs::*; @@ -388,7 +389,13 @@ impl FormatTrigger { } impl Project { - pub fn init(client: &Arc) { + pub fn init_settings(cx: &mut AppContext) { + settings::register::(cx); + } + + pub fn init(client: &Arc, cx: &mut AppContext) { + Self::init_settings(cx); + client.add_model_message_handler(Self::handle_add_collaborator); client.add_model_message_handler(Self::handle_update_project_collaborator); client.add_model_message_handler(Self::handle_remove_collaborator); @@ -458,7 +465,9 @@ impl Project { client_state: None, opened_buffer: watch::channel(), client_subscriptions: Vec::new(), - _subscriptions: vec![cx.observe_global::(Self::on_settings_changed)], + _subscriptions: vec![ + cx.observe_global::(Self::on_settings_changed) + ], _maintain_buffer_languages: Self::maintain_buffer_languages(&languages, cx), _maintain_workspace_config: Self::maintain_workspace_config(languages.clone(), cx), active_entry: None, @@ -601,12 +610,6 @@ impl Project { root_paths: impl IntoIterator, cx: &mut gpui::TestAppContext, ) -> ModelHandle { - if !cx.read(|cx| cx.has_global::()) { - cx.update(|cx| { - cx.set_global(Settings::test(cx)); - }); - } - let mut languages = LanguageRegistry::test(); languages.set_executor(cx.background()); let http_client = util::http::FakeHttpClient::with_404_response(); @@ -628,7 +631,7 @@ impl Project { } fn on_settings_changed(&mut self, cx: &mut ModelContext) { - let settings = cx.global::(); + let settings = all_language_settings(cx); let mut language_servers_to_start = Vec::new(); for buffer in self.opened_buffers.values() { @@ -636,7 +639,10 @@ impl Project { let buffer = buffer.read(cx); if let Some((file, language)) = File::from_dyn(buffer.file()).zip(buffer.language()) { - if settings.enable_language_server(Some(&language.name())) { + if settings + .language(Some(&language.name())) + .enable_language_server + { let worktree = file.worktree.read(cx); language_servers_to_start.push(( worktree.id(), @@ -651,7 +657,10 @@ impl Project { let mut language_servers_to_stop = Vec::new(); for language in self.languages.to_vec() { for lsp_adapter in language.lsp_adapters() { - if !settings.enable_language_server(Some(&language.name())) { + if !settings + .language(Some(&language.name())) + .enable_language_server + { let lsp_name = &lsp_adapter.name; for (worktree_id, started_lsp_name) in self.language_server_ids.keys() { if lsp_name == started_lsp_name { @@ -2122,7 +2131,7 @@ impl Project { let (mut settings_changed_tx, mut settings_changed_rx) = watch::channel(); let _ = postage::stream::Stream::try_recv(&mut settings_changed_rx); - let settings_observation = cx.observe_global::(move |_, _| { + let settings_observation = cx.observe_global::(move |_, _| { *settings_changed_tx.borrow_mut() = (); }); cx.spawn_weak(|this, mut cx| async move { @@ -2199,10 +2208,7 @@ impl Project { language: Arc, cx: &mut ModelContext, ) { - if !cx - .global::() - .enable_language_server(Some(&language.name())) - { + if !language_settings(Some(&language.name()), cx).enable_language_server { return; } @@ -2223,7 +2229,9 @@ impl Project { None => continue, }; - let lsp = &cx.global::().lsp.get(&adapter.name.0); + let lsp = settings::get::(cx) + .lsp + .get(&adapter.name.0); let override_options = lsp.map(|s| s.initialization_options.clone()).flatten(); let mut initialization_options = adapter.initialization_options.clone(); @@ -3249,24 +3257,17 @@ impl Project { let mut project_transaction = ProjectTransaction::default(); for (buffer, buffer_abs_path, language_server) in &buffers_with_paths_and_servers { - let ( - format_on_save, - remove_trailing_whitespace, - ensure_final_newline, - formatter, - tab_size, - ) = buffer.read_with(&cx, |buffer, cx| { - let settings = cx.global::(); + let settings = buffer.read_with(&cx, |buffer, cx| { let language_name = buffer.language().map(|language| language.name()); - ( - settings.format_on_save(language_name.as_deref()), - settings.remove_trailing_whitespace_on_save(language_name.as_deref()), - settings.ensure_final_newline_on_save(language_name.as_deref()), - settings.formatter(language_name.as_deref()), - settings.tab_size(language_name.as_deref()), - ) + language_settings(language_name.as_deref(), cx).clone() }); + let remove_trailing_whitespace = settings.remove_trailing_whitespace_on_save; + let ensure_final_newline = settings.ensure_final_newline_on_save; + let format_on_save = settings.format_on_save.clone(); + let formatter = settings.formatter.clone(); + let tab_size = settings.tab_size; + // First, format buffer's whitespace according to the settings. let trailing_whitespace_diff = if remove_trailing_whitespace { Some( diff --git a/crates/project/src/project_settings.rs b/crates/project/src/project_settings.rs new file mode 100644 index 0000000000000000000000000000000000000000..92e8cfcca79fc59f17a879c5c7ade0264a6b40bf --- /dev/null +++ b/crates/project/src/project_settings.rs @@ -0,0 +1,31 @@ +use collections::HashMap; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use settings::Setting; +use std::sync::Arc; + +#[derive(Clone, Serialize, Deserialize, JsonSchema)] +pub struct ProjectSettings { + #[serde(default)] + pub lsp: HashMap, LspSettings>, +} + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub struct LspSettings { + pub initialization_options: Option, +} + +impl Setting for ProjectSettings { + const KEY: Option<&'static str> = None; + + type FileContent = Self; + + fn load( + default_value: &Self::FileContent, + user_values: &[&Self::FileContent], + _: &gpui::AppContext, + ) -> anyhow::Result { + Self::load_via_json_merge(default_value, user_values) + } +} diff --git a/crates/project/src/project_tests.rs b/crates/project/src/project_tests.rs index 894b27f2ee94d9081f19a68d2385e352a9eee8a9..e7b1a84924eb2f5b25b519177ba9bcd5039eb000 100644 --- a/crates/project/src/project_tests.rs +++ b/crates/project/src/project_tests.rs @@ -1,10 +1,9 @@ use crate::{worktree::WorktreeHandle, Event, *}; -use fs::LineEnding; -use fs::{FakeFs, RealFs}; +use fs::{FakeFs, LineEnding, RealFs}; use futures::{future, StreamExt}; -use gpui::AppContext; -use gpui::{executor::Deterministic, test::subscribe}; +use gpui::{executor::Deterministic, test::subscribe, AppContext}; use language::{ + language_settings::{AllLanguageSettings, LanguageSettingsContent}, tree_sitter_rust, tree_sitter_typescript, Diagnostic, FakeLspAdapter, LanguageConfig, OffsetRangeExt, Point, ToPoint, }; @@ -26,6 +25,9 @@ fn init_logger() { #[gpui::test] async fn test_symlinks(cx: &mut gpui::TestAppContext) { + init_test(cx); + cx.foreground().allow_parking(); + let dir = temp_tree(json!({ "root": { "apple": "", @@ -65,7 +67,7 @@ async fn test_managing_language_servers( deterministic: Arc, cx: &mut gpui::TestAppContext, ) { - cx.foreground().forbid_parking(); + init_test(cx); let mut rust_language = Language::new( LanguageConfig { @@ -451,7 +453,7 @@ async fn test_managing_language_servers( #[gpui::test] async fn test_reporting_fs_changes_to_language_servers(cx: &mut gpui::TestAppContext) { - cx.foreground().forbid_parking(); + init_test(cx); let mut language = Language::new( LanguageConfig { @@ -556,7 +558,7 @@ async fn test_reporting_fs_changes_to_language_servers(cx: &mut gpui::TestAppCon #[gpui::test] async fn test_single_file_worktrees_diagnostics(cx: &mut gpui::TestAppContext) { - cx.foreground().forbid_parking(); + init_test(cx); let fs = FakeFs::new(cx.background()); fs.insert_tree( @@ -648,7 +650,7 @@ async fn test_single_file_worktrees_diagnostics(cx: &mut gpui::TestAppContext) { #[gpui::test] async fn test_hidden_worktrees_diagnostics(cx: &mut gpui::TestAppContext) { - cx.foreground().forbid_parking(); + init_test(cx); let fs = FakeFs::new(cx.background()); fs.insert_tree( @@ -719,7 +721,7 @@ async fn test_hidden_worktrees_diagnostics(cx: &mut gpui::TestAppContext) { #[gpui::test] async fn test_disk_based_diagnostics_progress(cx: &mut gpui::TestAppContext) { - cx.foreground().forbid_parking(); + init_test(cx); let progress_token = "the-progress-token"; let mut language = Language::new( @@ -847,7 +849,7 @@ async fn test_disk_based_diagnostics_progress(cx: &mut gpui::TestAppContext) { #[gpui::test] async fn test_restarting_server_with_diagnostics_running(cx: &mut gpui::TestAppContext) { - cx.foreground().forbid_parking(); + init_test(cx); let progress_token = "the-progress-token"; let mut language = Language::new( @@ -925,7 +927,7 @@ async fn test_restarting_server_with_diagnostics_running(cx: &mut gpui::TestAppC #[gpui::test] async fn test_restarted_server_reporting_invalid_buffer_version(cx: &mut gpui::TestAppContext) { - cx.foreground().forbid_parking(); + init_test(cx); let mut language = Language::new( LanguageConfig { @@ -973,11 +975,8 @@ async fn test_restarted_server_reporting_invalid_buffer_version(cx: &mut gpui::T } #[gpui::test] -async fn test_toggling_enable_language_server( - deterministic: Arc, - cx: &mut gpui::TestAppContext, -) { - deterministic.forbid_parking(); +async fn test_toggling_enable_language_server(cx: &mut gpui::TestAppContext) { + init_test(cx); let mut rust = Language::new( LanguageConfig { @@ -1051,14 +1050,16 @@ async fn test_toggling_enable_language_server( // Disable Rust language server, ensuring only that server gets stopped. cx.update(|cx| { - cx.update_global(|settings: &mut Settings, _| { - settings.language_overrides.insert( - Arc::from("Rust"), - settings::EditorSettings { - enable_language_server: Some(false), - ..Default::default() - }, - ); + cx.update_global(|settings: &mut SettingsStore, cx| { + settings.update_user_settings::(cx, |settings| { + settings.languages.insert( + Arc::from("Rust"), + LanguageSettingsContent { + enable_language_server: Some(false), + ..Default::default() + }, + ); + }); }) }); fake_rust_server_1 @@ -1068,21 +1069,23 @@ async fn test_toggling_enable_language_server( // Enable Rust and disable JavaScript language servers, ensuring that the // former gets started again and that the latter stops. cx.update(|cx| { - cx.update_global(|settings: &mut Settings, _| { - settings.language_overrides.insert( - Arc::from("Rust"), - settings::EditorSettings { - enable_language_server: Some(true), - ..Default::default() - }, - ); - settings.language_overrides.insert( - Arc::from("JavaScript"), - settings::EditorSettings { - enable_language_server: Some(false), - ..Default::default() - }, - ); + cx.update_global(|settings: &mut SettingsStore, cx| { + settings.update_user_settings::(cx, |settings| { + settings.languages.insert( + Arc::from("Rust"), + LanguageSettingsContent { + enable_language_server: Some(true), + ..Default::default() + }, + ); + settings.languages.insert( + Arc::from("JavaScript"), + LanguageSettingsContent { + enable_language_server: Some(false), + ..Default::default() + }, + ); + }); }) }); let mut fake_rust_server_2 = fake_rust_servers.next().await.unwrap(); @@ -1102,7 +1105,7 @@ async fn test_toggling_enable_language_server( #[gpui::test] async fn test_transforming_diagnostics(cx: &mut gpui::TestAppContext) { - cx.foreground().forbid_parking(); + init_test(cx); let mut language = Language::new( LanguageConfig { @@ -1388,7 +1391,7 @@ async fn test_transforming_diagnostics(cx: &mut gpui::TestAppContext) { #[gpui::test] async fn test_empty_diagnostic_ranges(cx: &mut gpui::TestAppContext) { - cx.foreground().forbid_parking(); + init_test(cx); let text = concat!( "let one = ;\n", // @@ -1457,9 +1460,7 @@ async fn test_empty_diagnostic_ranges(cx: &mut gpui::TestAppContext) { #[gpui::test] async fn test_diagnostics_from_multiple_language_servers(cx: &mut gpui::TestAppContext) { - println!("hello from stdout"); - eprintln!("hello from stderr"); - cx.foreground().forbid_parking(); + init_test(cx); let fs = FakeFs::new(cx.background()); fs.insert_tree("/dir", json!({ "a.rs": "one two three" })) @@ -1515,7 +1516,7 @@ async fn test_diagnostics_from_multiple_language_servers(cx: &mut gpui::TestAppC #[gpui::test] async fn test_edits_from_lsp_with_past_version(cx: &mut gpui::TestAppContext) { - cx.foreground().forbid_parking(); + init_test(cx); let mut language = Language::new( LanguageConfig { @@ -1673,7 +1674,7 @@ async fn test_edits_from_lsp_with_past_version(cx: &mut gpui::TestAppContext) { #[gpui::test] async fn test_edits_from_lsp_with_edits_on_adjacent_lines(cx: &mut gpui::TestAppContext) { - cx.foreground().forbid_parking(); + init_test(cx); let text = " use a::b; @@ -1781,7 +1782,7 @@ async fn test_edits_from_lsp_with_edits_on_adjacent_lines(cx: &mut gpui::TestApp #[gpui::test] async fn test_invalid_edits_from_lsp(cx: &mut gpui::TestAppContext) { - cx.foreground().forbid_parking(); + init_test(cx); let text = " use a::b; @@ -1902,6 +1903,8 @@ fn chunks_with_diagnostics( #[gpui::test(iterations = 10)] async fn test_definition(cx: &mut gpui::TestAppContext) { + init_test(cx); + let mut language = Language::new( LanguageConfig { name: "Rust".into(), @@ -2001,6 +2004,8 @@ async fn test_definition(cx: &mut gpui::TestAppContext) { #[gpui::test] async fn test_completions_without_edit_ranges(cx: &mut gpui::TestAppContext) { + init_test(cx); + let mut language = Language::new( LanguageConfig { name: "TypeScript".into(), @@ -2085,6 +2090,8 @@ async fn test_completions_without_edit_ranges(cx: &mut gpui::TestAppContext) { #[gpui::test] async fn test_completions_with_carriage_returns(cx: &mut gpui::TestAppContext) { + init_test(cx); + let mut language = Language::new( LanguageConfig { name: "TypeScript".into(), @@ -2138,6 +2145,8 @@ async fn test_completions_with_carriage_returns(cx: &mut gpui::TestAppContext) { #[gpui::test(iterations = 10)] async fn test_apply_code_actions_with_commands(cx: &mut gpui::TestAppContext) { + init_test(cx); + let mut language = Language::new( LanguageConfig { name: "TypeScript".into(), @@ -2254,6 +2263,8 @@ async fn test_apply_code_actions_with_commands(cx: &mut gpui::TestAppContext) { #[gpui::test(iterations = 10)] async fn test_save_file(cx: &mut gpui::TestAppContext) { + init_test(cx); + let fs = FakeFs::new(cx.background()); fs.insert_tree( "/dir", @@ -2284,6 +2295,8 @@ async fn test_save_file(cx: &mut gpui::TestAppContext) { #[gpui::test] async fn test_save_in_single_file_worktree(cx: &mut gpui::TestAppContext) { + init_test(cx); + let fs = FakeFs::new(cx.background()); fs.insert_tree( "/dir", @@ -2313,6 +2326,8 @@ async fn test_save_in_single_file_worktree(cx: &mut gpui::TestAppContext) { #[gpui::test] async fn test_save_as(cx: &mut gpui::TestAppContext) { + init_test(cx); + let fs = FakeFs::new(cx.background()); fs.insert_tree("/dir", json!({})).await; @@ -2373,6 +2388,9 @@ async fn test_rescan_and_remote_updates( deterministic: Arc, cx: &mut gpui::TestAppContext, ) { + init_test(cx); + cx.foreground().allow_parking(); + let dir = temp_tree(json!({ "a": { "file1": "", @@ -2529,6 +2547,8 @@ async fn test_buffer_identity_across_renames( deterministic: Arc, cx: &mut gpui::TestAppContext, ) { + init_test(cx); + let fs = FakeFs::new(cx.background()); fs.insert_tree( "/dir", @@ -2577,6 +2597,8 @@ async fn test_buffer_identity_across_renames( #[gpui::test] async fn test_buffer_deduping(cx: &mut gpui::TestAppContext) { + init_test(cx); + let fs = FakeFs::new(cx.background()); fs.insert_tree( "/dir", @@ -2621,6 +2643,8 @@ async fn test_buffer_deduping(cx: &mut gpui::TestAppContext) { #[gpui::test] async fn test_buffer_is_dirty(cx: &mut gpui::TestAppContext) { + init_test(cx); + let fs = FakeFs::new(cx.background()); fs.insert_tree( "/dir", @@ -2765,6 +2789,8 @@ async fn test_buffer_is_dirty(cx: &mut gpui::TestAppContext) { #[gpui::test] async fn test_buffer_file_changes_on_disk(cx: &mut gpui::TestAppContext) { + init_test(cx); + let initial_contents = "aaa\nbbbbb\nc\n"; let fs = FakeFs::new(cx.background()); fs.insert_tree( @@ -2844,6 +2870,8 @@ async fn test_buffer_file_changes_on_disk(cx: &mut gpui::TestAppContext) { #[gpui::test] async fn test_buffer_line_endings(cx: &mut gpui::TestAppContext) { + init_test(cx); + let fs = FakeFs::new(cx.background()); fs.insert_tree( "/dir", @@ -2904,7 +2932,7 @@ async fn test_buffer_line_endings(cx: &mut gpui::TestAppContext) { #[gpui::test] async fn test_grouped_diagnostics(cx: &mut gpui::TestAppContext) { - cx.foreground().forbid_parking(); + init_test(cx); let fs = FakeFs::new(cx.background()); fs.insert_tree( @@ -3146,7 +3174,7 @@ async fn test_grouped_diagnostics(cx: &mut gpui::TestAppContext) { #[gpui::test] async fn test_rename(cx: &mut gpui::TestAppContext) { - cx.foreground().forbid_parking(); + init_test(cx); let mut language = Language::new( LanguageConfig { @@ -3284,6 +3312,8 @@ async fn test_rename(cx: &mut gpui::TestAppContext) { #[gpui::test] async fn test_search(cx: &mut gpui::TestAppContext) { + init_test(cx); + let fs = FakeFs::new(cx.background()); fs.insert_tree( "/dir", @@ -3339,6 +3369,8 @@ async fn test_search(cx: &mut gpui::TestAppContext) { #[gpui::test] async fn test_search_with_inclusions(cx: &mut gpui::TestAppContext) { + init_test(cx); + let search_query = "file"; let fs = FakeFs::new(cx.background()); @@ -3447,6 +3479,8 @@ async fn test_search_with_inclusions(cx: &mut gpui::TestAppContext) { #[gpui::test] async fn test_search_with_exclusions(cx: &mut gpui::TestAppContext) { + init_test(cx); + let search_query = "file"; let fs = FakeFs::new(cx.background()); @@ -3554,6 +3588,8 @@ async fn test_search_with_exclusions(cx: &mut gpui::TestAppContext) { #[gpui::test] async fn test_search_with_exclusions_and_inclusions(cx: &mut gpui::TestAppContext) { + init_test(cx); + let search_query = "file"; let fs = FakeFs::new(cx.background()); @@ -3680,3 +3716,13 @@ async fn search( }) .collect()) } + +fn init_test(cx: &mut gpui::TestAppContext) { + cx.foreground().forbid_parking(); + + cx.update(|cx| { + cx.set_global(SettingsStore::test(cx)); + language::init(cx); + Project::init_settings(cx); + }); +} diff --git a/crates/project/src/terminals.rs b/crates/project/src/terminals.rs index 0f3092ca41d757437963a0658c502c87f913d262..7bd9ce8aecb011e287005803e5e186573b35899c 100644 --- a/crates/project/src/terminals.rs +++ b/crates/project/src/terminals.rs @@ -1,10 +1,7 @@ -use std::path::PathBuf; - -use gpui::{ModelContext, ModelHandle, WeakModelHandle}; -use settings::Settings; -use terminal::{Terminal, TerminalBuilder}; - use crate::Project; +use gpui::{ModelContext, ModelHandle, WeakModelHandle}; +use std::path::PathBuf; +use terminal::{Terminal, TerminalBuilder, TerminalSettings}; pub struct Terminals { pub(crate) local_handles: Vec>, @@ -22,17 +19,14 @@ impl Project { "creating terminals as a guest is not supported yet" )); } else { - let settings = cx.global::(); - let shell = settings.terminal_shell(); - let envs = settings.terminal_env(); - let scroll = settings.terminal_scroll(); + let settings = settings::get::(cx); let terminal = TerminalBuilder::new( working_directory.clone(), - shell, - envs, - settings.terminal_overrides.blinking.clone(), - scroll, + settings.shell.clone(), + settings.env.clone(), + Some(settings.blinking.clone()), + settings.alternate_scroll, window_id, ) .map(|builder| { diff --git a/crates/project_panel/Cargo.toml b/crates/project_panel/Cargo.toml index d3b28766752e13e4a28c47363ba4dcbcf5f24ccc..6fcdf06d2c736c63ea19cc5bd5ebdd79e7c36061 100644 --- a/crates/project_panel/Cargo.toml +++ b/crates/project_panel/Cargo.toml @@ -24,6 +24,8 @@ futures.workspace = true unicase = "2.6" [dev-dependencies] +client = { path = "../client", features = ["test-support"] } +language = { path = "../language", features = ["test-support"] } editor = { path = "../editor", features = ["test-support"] } gpui = { path = "../gpui", features = ["test-support"] } workspace = { path = "../workspace", features = ["test-support"] } diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index bb7f97fbf8df918d7a96bbf65015eedf78369e97..683ce8ad06671e2346628bcac46494dbfbfc3d7f 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -20,7 +20,6 @@ use project::{ repository::GitFileStatus, Entry, EntryKind, Project, ProjectEntryId, ProjectPath, Worktree, WorktreeId, }; -use settings::Settings; use std::{ cmp::Ordering, collections::{hash_map, HashMap}, @@ -1113,7 +1112,7 @@ impl ProjectPanel { ComponentHost::new(FileName::new( details.filename.clone(), details.git_status, - FileName::style(style.text.clone(), &cx.global::().theme), + FileName::style(style.text.clone(), &theme::current(cx)), )) .contained() .with_margin_left(style.icon_spacing) @@ -1223,7 +1222,7 @@ impl ProjectPanel { let row_container_style = theme.dragged_entry.container; move |_, cx: &mut ViewContext| { - let theme = cx.global::().theme.clone(); + let theme = theme::current(cx).clone(); Self::render_entry_visual_element( &details, None, @@ -1246,7 +1245,7 @@ impl View for ProjectPanel { fn render(&mut self, cx: &mut gpui::ViewContext) -> gpui::AnyElement { enum ProjectPanel {} - let theme = &cx.global::().theme.project_panel; + let theme = &theme::current(cx).project_panel; let mut container_style = theme.container; let padding = std::mem::take(&mut container_style.padding); let last_worktree_root_id = self.last_worktree_root_id; @@ -1265,7 +1264,7 @@ impl View for ProjectPanel { .sum(), cx, move |this, range, items, cx| { - let theme = cx.global::().theme.clone(); + let theme = theme::current(cx).clone(); let mut dragged_entry_destination = this.dragged_entry_destination.clone(); this.for_each_visible_entry(range, cx, |id, details, cx| { @@ -1302,8 +1301,7 @@ impl View for ProjectPanel { .with_child( MouseEventHandler::::new(2, cx, { let button_style = theme.open_project_button.clone(); - let context_menu_item_style = - cx.global::().theme.context_menu.item.clone(); + let context_menu_item_style = theme::current(cx).context_menu.item.clone(); move |state, cx| { let button_style = button_style.style_for(state, false).clone(); let context_menu_item = @@ -1378,15 +1376,12 @@ mod tests { use gpui::{TestAppContext, ViewHandle}; use project::FakeFs; use serde_json::json; + use settings::SettingsStore; use std::{collections::HashSet, path::Path}; #[gpui::test] async fn test_visible_list(cx: &mut gpui::TestAppContext) { - cx.foreground().forbid_parking(); - cx.update(|cx| { - let settings = Settings::test(cx); - cx.set_global(settings); - }); + init_test(cx); let fs = FakeFs::new(cx.background()); fs.insert_tree( @@ -1474,11 +1469,7 @@ mod tests { #[gpui::test(iterations = 30)] async fn test_editing_files(cx: &mut gpui::TestAppContext) { - cx.foreground().forbid_parking(); - cx.update(|cx| { - let settings = Settings::test(cx); - cx.set_global(settings); - }); + init_test(cx); let fs = FakeFs::new(cx.background()); fs.insert_tree( @@ -1794,11 +1785,7 @@ mod tests { #[gpui::test] async fn test_copy_paste(cx: &mut gpui::TestAppContext) { - cx.foreground().forbid_parking(); - cx.update(|cx| { - let settings = Settings::test(cx); - cx.set_global(settings); - }); + init_test(cx); let fs = FakeFs::new(cx.background()); fs.insert_tree( @@ -1958,4 +1945,15 @@ mod tests { result } + + fn init_test(cx: &mut TestAppContext) { + cx.foreground().forbid_parking(); + cx.update(|cx| { + cx.set_global(SettingsStore::test(cx)); + theme::init((), cx); + language::init(cx); + editor::init_settings(cx); + workspace::init_settings(cx); + }); + } } diff --git a/crates/project_symbols/Cargo.toml b/crates/project_symbols/Cargo.toml index 1c0d3a61658f0e2a36fa773e5f828598620edc6a..7e23e42b2633c4af743d2586fea0db3ebafcbe7b 100644 --- a/crates/project_symbols/Cargo.toml +++ b/crates/project_symbols/Cargo.toml @@ -17,7 +17,9 @@ project = { path = "../project" } text = { path = "../text" } settings = { path = "../settings" } workspace = { path = "../workspace" } +theme = { path = "../theme" } util = { path = "../util" } + anyhow.workspace = true ordered-float.workspace = true postage.workspace = true @@ -30,3 +32,5 @@ gpui = { path = "../gpui", features = ["test-support"] } language = { path = "../language", features = ["test-support"] } lsp = { path = "../lsp", features = ["test-support"] } project = { path = "../project", features = ["test-support"] } +theme = { path = "../theme", features = ["test-support"] } +workspace = { path = "../workspace", features = ["test-support"] } diff --git a/crates/project_symbols/src/project_symbols.rs b/crates/project_symbols/src/project_symbols.rs index 25828f17cac959659d89770d5f353c0ba93ab81b..992283df011cdb7eee4f2317e9904cd006216481 100644 --- a/crates/project_symbols/src/project_symbols.rs +++ b/crates/project_symbols/src/project_symbols.rs @@ -9,7 +9,6 @@ use gpui::{ use ordered_float::OrderedFloat; use picker::{Picker, PickerDelegate, PickerEvent}; use project::{Project, Symbol}; -use settings::Settings; use std::{borrow::Cow, cmp::Reverse, sync::Arc}; use util::ResultExt; use workspace::Workspace; @@ -195,12 +194,13 @@ impl PickerDelegate for ProjectSymbolsDelegate { selected: bool, cx: &AppContext, ) -> AnyElement> { - let string_match = &self.matches[ix]; - let settings = cx.global::(); - let style = &settings.theme.picker.item; + let theme = theme::current(cx); + let style = &theme.picker.item; let current_style = style.style_for(mouse_state, selected); + + let string_match = &self.matches[ix]; let symbol = &self.symbols[string_match.candidate_id]; - let syntax_runs = styled_runs_for_code_label(&symbol.label, &settings.theme.editor.syntax); + let syntax_runs = styled_runs_for_code_label(&symbol.label, &theme.editor.syntax); let mut path = symbol.path.path.to_string_lossy(); if self.show_worktree_root_name { @@ -244,12 +244,12 @@ mod tests { use gpui::{serde_json::json, TestAppContext}; use language::{FakeLspAdapter, Language, LanguageConfig}; use project::FakeFs; + use settings::SettingsStore; use std::{path::Path, sync::Arc}; #[gpui::test] async fn test_project_symbols(cx: &mut TestAppContext) { - cx.foreground().forbid_parking(); - cx.update(|cx| cx.set_global(Settings::test(cx))); + init_test(cx); let mut language = Language::new( LanguageConfig { @@ -368,6 +368,17 @@ mod tests { }); } + fn init_test(cx: &mut TestAppContext) { + cx.foreground().forbid_parking(); + cx.update(|cx| { + cx.set_global(SettingsStore::test(cx)); + theme::init((), cx); + language::init(cx); + Project::init_settings(cx); + workspace::init_settings(cx); + }); + } + fn symbol(name: &str, path: impl AsRef) -> lsp::SymbolInformation { #[allow(deprecated)] lsp::SymbolInformation { diff --git a/crates/recent_projects/Cargo.toml b/crates/recent_projects/Cargo.toml index 968ae8e9a4d8d2a753fd8ae9af76b9aa11761885..d9e7546f34464a3b611b1f4457a26efad9286e1a 100644 --- a/crates/recent_projects/Cargo.toml +++ b/crates/recent_projects/Cargo.toml @@ -18,6 +18,7 @@ picker = { path = "../picker" } settings = { path = "../settings" } text = { path = "../text" } util = { path = "../util"} +theme = { path = "../theme" } workspace = { path = "../workspace" } ordered-float.workspace = true diff --git a/crates/recent_projects/src/recent_projects.rs b/crates/recent_projects/src/recent_projects.rs index 644e74d87887ffd82124fff90fc7d4eccdd9bac0..a1dc8982c79fcedd36447f5bf116f068701b238f 100644 --- a/crates/recent_projects/src/recent_projects.rs +++ b/crates/recent_projects/src/recent_projects.rs @@ -10,7 +10,6 @@ use gpui::{ use highlighted_workspace_location::HighlightedWorkspaceLocation; use ordered_float::OrderedFloat; use picker::{Picker, PickerDelegate, PickerEvent}; -use settings::Settings; use std::sync::Arc; use workspace::{ notifications::simple_message_notification::MessageNotification, Workspace, WorkspaceLocation, @@ -173,9 +172,10 @@ impl PickerDelegate for RecentProjectsDelegate { selected: bool, cx: &gpui::AppContext, ) -> AnyElement> { - let settings = cx.global::(); + let theme = theme::current(cx); + let style = theme.picker.item.style_for(mouse_state, selected); + let string_match = &self.matches[ix]; - let style = settings.theme.picker.item.style_for(mouse_state, selected); let highlighted_location = HighlightedWorkspaceLocation::new( &string_match, diff --git a/crates/search/Cargo.toml b/crates/search/Cargo.toml index ab3c35c1fe4174626a70a71b3f97c4a11796b243..14e658e8f843bd71a915e774f9e6fc745c0b1e31 100644 --- a/crates/search/Cargo.toml +++ b/crates/search/Cargo.toml @@ -30,6 +30,7 @@ smol.workspace = true glob.workspace = true [dev-dependencies] +client = { path = "../client", features = ["test-support"] } editor = { path = "../editor", features = ["test-support"] } gpui = { path = "../gpui", features = ["test-support"] } serde_json.workspace = true diff --git a/crates/search/src/buffer_search.rs b/crates/search/src/buffer_search.rs index b0af51379d02e7dbae0cbb037cd3b62e5e925b5a..87a8b265fb2f774135e5db267a269c3890eaae21 100644 --- a/crates/search/src/buffer_search.rs +++ b/crates/search/src/buffer_search.rs @@ -13,7 +13,6 @@ use gpui::{ }; use project::search::SearchQuery; use serde::Deserialize; -use settings::Settings; use std::{any::Any, sync::Arc}; use util::ResultExt; use workspace::{ @@ -93,7 +92,7 @@ impl View for BufferSearchBar { } fn render(&mut self, cx: &mut ViewContext) -> AnyElement { - let theme = cx.global::().theme.clone(); + let theme = theme::current(cx).clone(); let editor_container = if self.query_contains_error { theme.search.invalid_editor } else { @@ -324,16 +323,12 @@ impl BufferSearchBar { return None; } - let tooltip_style = cx.global::().theme.tooltip.clone(); + let tooltip_style = theme::current(cx).tooltip.clone(); let is_active = self.is_search_option_enabled(option); Some( MouseEventHandler::::new(option as usize, cx, |state, cx| { - let style = cx - .global::() - .theme - .search - .option_button - .style_for(state, is_active); + let theme = theme::current(cx); + let style = theme.search.option_button.style_for(state, is_active); Label::new(icon, style.text.clone()) .contained() .with_style(style.container) @@ -371,16 +366,12 @@ impl BufferSearchBar { tooltip = "Select Next Match"; } }; - let tooltip_style = cx.global::().theme.tooltip.clone(); + let tooltip_style = theme::current(cx).tooltip.clone(); enum NavButton {} MouseEventHandler::::new(direction as usize, cx, |state, cx| { - let style = cx - .global::() - .theme - .search - .option_button - .style_for(state, false); + let theme = theme::current(cx); + let style = theme.search.option_button.style_for(state, false); Label::new(icon, style.text.clone()) .contained() .with_style(style.container) @@ -408,7 +399,7 @@ impl BufferSearchBar { cx: &mut ViewContext, ) -> AnyElement { let tooltip = "Dismiss Buffer Search"; - let tooltip_style = cx.global::().theme.tooltip.clone(); + let tooltip_style = theme::current(cx).tooltip.clone(); enum CloseButton {} MouseEventHandler::::new(0, cx, |state, _| { @@ -655,19 +646,11 @@ mod tests { use editor::{DisplayPoint, Editor}; use gpui::{color::Color, test::EmptyView, TestAppContext}; use language::Buffer; - use std::sync::Arc; use unindent::Unindent as _; #[gpui::test] async fn test_search_simple(cx: &mut TestAppContext) { - let fonts = cx.font_cache(); - let mut theme = gpui::fonts::with_font_cache(fonts.clone(), theme::Theme::default); - theme.search.match_background = Color::red(); - cx.update(|cx| { - let mut settings = Settings::test(cx); - settings.theme = Arc::new(theme); - cx.set_global(settings) - }); + crate::project_search::tests::init_test(cx); let buffer = cx.add_model(|cx| { Buffer::new( diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index 05d27b824c7bed5a0eb010c60e57d936af735880..17f86c153c9b51edba847f4fafb99a8efda9cbbb 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -17,7 +17,6 @@ use gpui::{ }; use menu::Confirm; use project::{search::SearchQuery, Project}; -use settings::Settings; use smallvec::SmallVec; use std::{ any::{Any, TypeId}, @@ -195,7 +194,7 @@ impl View for ProjectSearchView { if model.match_ranges.is_empty() { enum Status {} - let theme = cx.global::().theme.clone(); + let theme = theme::current(cx).clone(); let text = if self.query_editor.read(cx).text(cx).is_empty() { "" } else if model.pending_search.is_some() { @@ -903,16 +902,12 @@ impl ProjectSearchBar { tooltip = "Select Next Match"; } }; - let tooltip_style = cx.global::().theme.tooltip.clone(); + let tooltip_style = theme::current(cx).tooltip.clone(); enum NavButton {} MouseEventHandler::::new(direction as usize, cx, |state, cx| { - let style = &cx - .global::() - .theme - .search - .option_button - .style_for(state, false); + let theme = theme::current(cx); + let style = theme.search.option_button.style_for(state, false); Label::new(icon, style.text.clone()) .contained() .with_style(style.container) @@ -939,15 +934,11 @@ impl ProjectSearchBar { option: SearchOption, cx: &mut ViewContext, ) -> AnyElement { - let tooltip_style = cx.global::().theme.tooltip.clone(); + let tooltip_style = theme::current(cx).tooltip.clone(); let is_active = self.is_option_enabled(option, cx); MouseEventHandler::::new(option as usize, cx, |state, cx| { - let style = &cx - .global::() - .theme - .search - .option_button - .style_for(state, is_active); + let theme = theme::current(cx); + let style = theme.search.option_button.style_for(state, is_active); Label::new(icon, style.text.clone()) .contained() .with_style(style.container) @@ -992,7 +983,7 @@ impl View for ProjectSearchBar { fn render(&mut self, cx: &mut ViewContext) -> AnyElement { if let Some(search) = self.active_project_search.as_ref() { let search = search.read(cx); - let theme = cx.global::().theme.clone(); + let theme = theme::current(cx).clone(); let query_container_style = if search.panels_with_errors.contains(&InputPanel::Query) { theme.search.invalid_editor } else { @@ -1146,25 +1137,19 @@ impl ToolbarItemView for ProjectSearchBar { } #[cfg(test)] -mod tests { +pub mod tests { use super::*; use editor::DisplayPoint; use gpui::{color::Color, executor::Deterministic, TestAppContext}; use project::FakeFs; use serde_json::json; + use settings::SettingsStore; use std::sync::Arc; + use theme::ThemeSettings; #[gpui::test] async fn test_project_search(deterministic: Arc, cx: &mut TestAppContext) { - let fonts = cx.font_cache(); - let mut theme = gpui::fonts::with_font_cache(fonts.clone(), theme::Theme::default); - theme.search.match_background = Color::red(); - cx.update(|cx| { - let mut settings = Settings::test(cx); - settings.theme = Arc::new(theme); - cx.set_global(settings); - cx.set_global(ActiveSearches::default()); - }); + init_test(cx); let fs = FakeFs::new(cx.background()); fs.insert_tree( @@ -1279,4 +1264,27 @@ mod tests { ); }); } + + pub fn init_test(cx: &mut TestAppContext) { + let fonts = cx.font_cache(); + let mut theme = gpui::fonts::with_font_cache(fonts.clone(), theme::Theme::default); + theme.search.match_background = Color::red(); + + cx.update(|cx| { + cx.set_global(SettingsStore::test(cx)); + cx.set_global(ActiveSearches::default()); + + theme::init((), cx); + cx.update_global::(|store, _| { + let mut settings = store.get::(None).clone(); + settings.theme = Arc::new(theme); + store.override_global(settings) + }); + + language::init(cx); + client::init_settings(cx); + editor::init_settings(cx); + workspace::init_settings(cx); + }); + } } diff --git a/crates/settings/Cargo.toml b/crates/settings/Cargo.toml index 95661039608cd36cc531ecdd346fd96f15e9a277..2cb6637eadc876f375313f502d97ff89aaa3ee4f 100644 --- a/crates/settings/Cargo.toml +++ b/crates/settings/Cargo.toml @@ -9,7 +9,7 @@ path = "src/settings.rs" doctest = false [features] -test-support = [] +test-support = ["gpui/test-support", "fs/test-support"] [dependencies] assets = { path = "../assets" } @@ -17,21 +17,21 @@ collections = { path = "../collections" } gpui = { path = "../gpui" } sqlez = { path = "../sqlez" } fs = { path = "../fs" } -anyhow.workspace = true -futures.workspace = true -theme = { path = "../theme" } staff_mode = { path = "../staff_mode" } util = { path = "../util" } +anyhow.workspace = true +futures.workspace = true glob.workspace = true json_comments = "0.2" lazy_static.workspace = true postage.workspace = true -schemars = "0.8" +schemars.workspace = true serde.workspace = true serde_derive.workspace = true serde_json.workspace = true -toml = "0.5" +smallvec.workspace = true +toml.workspace = true tree-sitter = "*" tree-sitter-json = "*" diff --git a/crates/settings/src/keymap_file.rs b/crates/settings/src/keymap_file.rs index a45a53145edfca3371af9b135a882664e191ec6e..0b638da9242b8dbfdfe504cce5fa478a14c53fb0 100644 --- a/crates/settings/src/keymap_file.rs +++ b/crates/settings/src/keymap_file.rs @@ -1,4 +1,4 @@ -use crate::{parse_json_with_comments, Settings}; +use crate::settings_store::parse_json_with_comments; use anyhow::{Context, Result}; use assets::Assets; use collections::BTreeMap; @@ -41,20 +41,14 @@ impl JsonSchema for KeymapAction { struct ActionWithData(Box, Box); impl KeymapFileContent { - pub fn load_defaults(cx: &mut AppContext) { - for path in ["keymaps/default.json", "keymaps/vim.json"] { - Self::load(path, cx).unwrap(); - } - - if let Some(asset_path) = cx.global::().base_keymap.asset_path() { - Self::load(asset_path, cx).log_err(); - } - } - - pub fn load(asset_path: &str, cx: &mut AppContext) -> Result<()> { + pub fn load_asset(asset_path: &str, cx: &mut AppContext) -> Result<()> { let content = Assets::get(asset_path).unwrap().data; let content_str = std::str::from_utf8(content.as_ref()).unwrap(); - parse_json_with_comments::(content_str)?.add_to_cx(cx) + Self::parse(content_str)?.add_to_cx(cx) + } + + pub fn parse(content: &str) -> Result { + parse_json_with_comments::(content) } pub fn add_to_cx(self, cx: &mut AppContext) -> Result<()> { diff --git a/crates/settings/src/settings.rs b/crates/settings/src/settings.rs index 55598845c7b1daa17f524f35b1e6aff9a3ba4053..840797c6ad644cc851c298712e7db080a0650d78 100644 --- a/crates/settings/src/settings.rs +++ b/crates/settings/src/settings.rs @@ -1,1614 +1,19 @@ mod keymap_file; -pub mod settings_file; -pub mod watched_json; - -use anyhow::{bail, Result}; -use gpui::{ - font_cache::{FamilyId, FontCache}, - fonts, AssetSource, -}; -use lazy_static::lazy_static; -use schemars::{ - gen::{SchemaGenerator, SchemaSettings}, - schema::{InstanceType, ObjectValidation, Schema, SchemaObject, SingleOrVec}, - JsonSchema, -}; -use serde::{de::DeserializeOwned, Deserialize, Serialize}; -use serde_json::Value; -use sqlez::{ - bindable::{Bind, Column, StaticColumnCount}, - statement::Statement, -}; -use std::{ - borrow::Cow, collections::HashMap, num::NonZeroU32, ops::Range, path::Path, str, sync::Arc, -}; -use theme::{Theme, ThemeRegistry}; -use tree_sitter::{Query, Tree}; -use util::{RangeExt, ResultExt as _}; +mod settings_file; +mod settings_store; +use gpui::AssetSource; pub use keymap_file::{keymap_file_json_schema, KeymapFileContent}; -pub use watched_json::watch_files; +pub use settings_file::*; +pub use settings_store::{Setting, SettingsJsonSchemaParams, SettingsStore}; +use std::{borrow::Cow, str}; pub const DEFAULT_SETTINGS_ASSET_PATH: &str = "settings/default.json"; pub const INITIAL_USER_SETTINGS_ASSET_PATH: &str = "settings/initial_user_settings.json"; -#[derive(Clone)] -pub struct Settings { - pub features: Features, - pub buffer_font_family_name: String, - pub buffer_font_features: fonts::Features, - pub buffer_font_family: FamilyId, - pub default_buffer_font_size: f32, - pub buffer_font_size: f32, - pub active_pane_magnification: f32, - pub cursor_blink: bool, - pub confirm_quit: bool, - pub hover_popover_enabled: bool, - pub show_completions_on_input: bool, - pub show_call_status_icon: bool, - pub show_scrollbars: ShowScrollbars, - pub vim_mode: bool, - pub autosave: Autosave, - pub default_dock_anchor: DockAnchor, - pub editor_defaults: EditorSettings, - pub editor_overrides: EditorSettings, - pub git: GitSettings, - pub git_overrides: GitSettings, - pub copilot: CopilotSettings, - pub journal_defaults: JournalSettings, - pub journal_overrides: JournalSettings, - pub terminal_defaults: TerminalSettings, - pub terminal_overrides: TerminalSettings, - pub language_defaults: HashMap, EditorSettings>, - pub language_overrides: HashMap, EditorSettings>, - pub lsp: HashMap, LspSettings>, - pub theme: Arc, - pub telemetry_defaults: TelemetrySettings, - pub telemetry_overrides: TelemetrySettings, - pub auto_update: bool, - pub base_keymap: BaseKeymap, -} - -#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq, Default)] -#[serde(rename_all = "snake_case")] -pub enum ShowScrollbars { - #[default] - Auto, - System, - Always, - Never, -} - -#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq, Default)] -pub enum BaseKeymap { - #[default] - VSCode, - JetBrains, - SublimeText, - Atom, - TextMate, -} - -impl BaseKeymap { - pub const OPTIONS: [(&'static str, Self); 5] = [ - ("VSCode (Default)", Self::VSCode), - ("Atom", Self::Atom), - ("JetBrains", Self::JetBrains), - ("Sublime Text", Self::SublimeText), - ("TextMate", Self::TextMate), - ]; - - pub fn asset_path(&self) -> Option<&'static str> { - match self { - BaseKeymap::JetBrains => Some("keymaps/jetbrains.json"), - BaseKeymap::SublimeText => Some("keymaps/sublime_text.json"), - BaseKeymap::Atom => Some("keymaps/atom.json"), - BaseKeymap::TextMate => Some("keymaps/textmate.json"), - BaseKeymap::VSCode => None, - } - } - - pub fn names() -> impl Iterator { - Self::OPTIONS.iter().map(|(name, _)| *name) - } - - pub fn from_names(option: &str) -> BaseKeymap { - Self::OPTIONS - .iter() - .copied() - .find_map(|(name, value)| (name == option).then(|| value)) - .unwrap_or_default() - } -} - -#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, JsonSchema)] -pub struct TelemetrySettings { - diagnostics: Option, - metrics: Option, -} - -impl TelemetrySettings { - pub fn metrics(&self) -> bool { - self.metrics.unwrap() - } - - pub fn diagnostics(&self) -> bool { - self.diagnostics.unwrap() - } - - pub fn set_metrics(&mut self, value: bool) { - self.metrics = Some(value); - } - - pub fn set_diagnostics(&mut self, value: bool) { - self.diagnostics = Some(value); - } -} - -#[derive(Clone, Debug, Default)] -pub struct CopilotSettings { - pub disabled_globs: Vec, -} - -#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)] -pub struct CopilotSettingsContent { - #[serde(default)] - pub disabled_globs: Option>, -} - -#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, JsonSchema)] -pub struct GitSettings { - pub git_gutter: Option, - pub gutter_debounce: Option, -} - -#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum GitGutter { - #[default] - TrackedFiles, - Hide, -} - -pub struct GitGutterConfig {} - -#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)] -pub struct EditorSettings { - pub tab_size: Option, - pub hard_tabs: Option, - pub soft_wrap: Option, - pub preferred_line_length: Option, - pub format_on_save: Option, - pub remove_trailing_whitespace_on_save: Option, - pub ensure_final_newline_on_save: Option, - pub formatter: Option, - pub enable_language_server: Option, - pub show_copilot_suggestions: Option, - pub show_whitespaces: Option, -} - -#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum SoftWrap { - None, - EditorWidth, - PreferredLineLength, -} -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum FormatOnSave { - On, - Off, - LanguageServer, - External { - command: String, - arguments: Vec, - }, -} - -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum Formatter { - LanguageServer, - External { - command: String, - arguments: Vec, - }, -} - -#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum Autosave { - Off, - AfterDelay { milliseconds: u64 }, - OnFocusChange, - OnWindowChange, -} - -#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)] -pub struct JournalSettings { - pub path: Option, - pub hour_format: Option, -} - -impl Default for JournalSettings { - fn default() -> Self { - Self { - path: Some("~".into()), - hour_format: Some(Default::default()), - } - } -} - -#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum HourFormat { - Hour12, - Hour24, -} - -impl Default for HourFormat { - fn default() -> Self { - Self::Hour12 - } -} - -#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)] -pub struct TerminalSettings { - pub shell: Option, - pub working_directory: Option, - pub font_size: Option, - pub font_family: Option, - pub line_height: Option, - pub font_features: Option, - pub env: Option>, - pub blinking: Option, - pub alternate_scroll: Option, - pub option_as_meta: Option, - pub copy_on_select: Option, -} - -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema, Default)] -#[serde(rename_all = "snake_case")] -pub enum TerminalLineHeight { - #[default] - Comfortable, - Standard, - Custom(f32), -} - -impl TerminalLineHeight { - fn value(&self) -> f32 { - match self { - TerminalLineHeight::Comfortable => 1.618, - TerminalLineHeight::Standard => 1.3, - TerminalLineHeight::Custom(line_height) => *line_height, - } - } -} - -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum TerminalBlink { - Off, - TerminalControlled, - On, -} - -impl Default for TerminalBlink { - fn default() -> Self { - TerminalBlink::TerminalControlled - } -} - -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum Shell { - System, - Program(String), - WithArguments { program: String, args: Vec }, -} - -impl Default for Shell { - fn default() -> Self { - Shell::System - } -} - -#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum AlternateScroll { - On, - Off, -} - -impl Default for AlternateScroll { - fn default() -> Self { - AlternateScroll::On - } -} - -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum WorkingDirectory { - CurrentProjectDirectory, - FirstProjectDirectory, - AlwaysHome, - Always { directory: String }, -} - -impl Default for WorkingDirectory { - fn default() -> Self { - Self::CurrentProjectDirectory - } -} - -impl TerminalSettings { - fn line_height(&self) -> Option { - self.line_height - .to_owned() - .map(|line_height| line_height.value()) - } -} - -#[derive(PartialEq, Eq, Debug, Default, Copy, Clone, Hash, Serialize, Deserialize, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum DockAnchor { - #[default] - Bottom, - Right, - Expanded, -} - -impl StaticColumnCount for DockAnchor {} -impl Bind for DockAnchor { - fn bind(&self, statement: &Statement, start_index: i32) -> anyhow::Result { - match self { - DockAnchor::Bottom => "Bottom", - DockAnchor::Right => "Right", - DockAnchor::Expanded => "Expanded", - } - .bind(statement, start_index) - } -} - -impl Column for DockAnchor { - fn column(statement: &mut Statement, start_index: i32) -> anyhow::Result<(Self, i32)> { - String::column(statement, start_index).and_then(|(anchor_text, next_index)| { - Ok(( - match anchor_text.as_ref() { - "Bottom" => DockAnchor::Bottom, - "Right" => DockAnchor::Right, - "Expanded" => DockAnchor::Expanded, - _ => bail!("Stored dock anchor is incorrect"), - }, - next_index, - )) - }) - } -} - -#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)] -pub struct SettingsFileContent { - #[serde(default)] - pub buffer_font_family: Option, - #[serde(default)] - pub buffer_font_size: Option, - #[serde(default)] - pub buffer_font_features: Option, - #[serde(default)] - pub copilot: Option, - #[serde(default)] - pub active_pane_magnification: Option, - #[serde(default)] - pub show_scrollbars: Option, - #[serde(default)] - pub cursor_blink: Option, - #[serde(default)] - pub confirm_quit: Option, - #[serde(default)] - pub hover_popover_enabled: Option, - #[serde(default)] - pub show_completions_on_input: Option, - #[serde(default)] - pub show_call_status_icon: Option, - #[serde(default)] - pub vim_mode: Option, - #[serde(default)] - pub autosave: Option, - #[serde(default)] - pub default_dock_anchor: Option, - #[serde(flatten)] - pub editor: EditorSettings, - #[serde(default)] - pub journal: JournalSettings, - #[serde(default)] - pub terminal: TerminalSettings, - #[serde(default)] - pub git: Option, - #[serde(default)] - #[serde(alias = "language_overrides")] - pub languages: HashMap, EditorSettings>, - #[serde(default)] - pub lsp: HashMap, LspSettings>, - #[serde(default)] - pub theme: Option, - #[serde(default)] - pub telemetry: TelemetrySettings, - #[serde(default)] - pub auto_update: Option, - #[serde(default)] - pub base_keymap: Option, - #[serde(default)] - pub features: FeaturesContent, -} - -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub struct LspSettings { - pub initialization_options: Option, -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct Features { - pub copilot: bool, -} - -#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub struct FeaturesContent { - pub copilot: Option, -} - -#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum ShowWhitespaces { - #[default] - Selection, - None, - All, -} - -impl Settings { - pub fn initial_user_settings_content(assets: &'static impl AssetSource) -> Cow<'static, str> { - match assets.load(INITIAL_USER_SETTINGS_ASSET_PATH).unwrap() { - Cow::Borrowed(s) => Cow::Borrowed(str::from_utf8(s).unwrap()), - Cow::Owned(s) => Cow::Owned(String::from_utf8(s).unwrap()), - } - } - - /// Fill out the settings corresponding to the default.json file, overrides will be set later - pub fn defaults( - assets: impl AssetSource, - font_cache: &FontCache, - themes: &ThemeRegistry, - ) -> Self { - #[track_caller] - fn required(value: Option) -> Option { - assert!(value.is_some(), "missing default setting value"); - value - } - - let defaults: SettingsFileContent = parse_json_with_comments( - str::from_utf8(assets.load(DEFAULT_SETTINGS_ASSET_PATH).unwrap().as_ref()).unwrap(), - ) - .unwrap(); - - let buffer_font_features = defaults.buffer_font_features.unwrap(); - Self { - buffer_font_family: font_cache - .load_family( - &[defaults.buffer_font_family.as_ref().unwrap()], - &buffer_font_features, - ) - .unwrap(), - buffer_font_family_name: defaults.buffer_font_family.unwrap(), - buffer_font_features, - buffer_font_size: defaults.buffer_font_size.unwrap(), - active_pane_magnification: defaults.active_pane_magnification.unwrap(), - default_buffer_font_size: defaults.buffer_font_size.unwrap(), - confirm_quit: defaults.confirm_quit.unwrap(), - cursor_blink: defaults.cursor_blink.unwrap(), - hover_popover_enabled: defaults.hover_popover_enabled.unwrap(), - show_completions_on_input: defaults.show_completions_on_input.unwrap(), - show_call_status_icon: defaults.show_call_status_icon.unwrap(), - vim_mode: defaults.vim_mode.unwrap(), - autosave: defaults.autosave.unwrap(), - default_dock_anchor: defaults.default_dock_anchor.unwrap(), - editor_defaults: EditorSettings { - tab_size: required(defaults.editor.tab_size), - hard_tabs: required(defaults.editor.hard_tabs), - soft_wrap: required(defaults.editor.soft_wrap), - preferred_line_length: required(defaults.editor.preferred_line_length), - remove_trailing_whitespace_on_save: required( - defaults.editor.remove_trailing_whitespace_on_save, - ), - ensure_final_newline_on_save: required( - defaults.editor.ensure_final_newline_on_save, - ), - format_on_save: required(defaults.editor.format_on_save), - formatter: required(defaults.editor.formatter), - enable_language_server: required(defaults.editor.enable_language_server), - show_copilot_suggestions: required(defaults.editor.show_copilot_suggestions), - show_whitespaces: required(defaults.editor.show_whitespaces), - }, - editor_overrides: Default::default(), - copilot: CopilotSettings { - disabled_globs: defaults - .copilot - .unwrap() - .disabled_globs - .unwrap() - .into_iter() - .map(|s| glob::Pattern::new(&s).unwrap()) - .collect(), - }, - git: defaults.git.unwrap(), - git_overrides: Default::default(), - journal_defaults: defaults.journal, - journal_overrides: Default::default(), - terminal_defaults: defaults.terminal, - terminal_overrides: Default::default(), - language_defaults: defaults.languages, - language_overrides: Default::default(), - lsp: defaults.lsp.clone(), - theme: themes.get(&defaults.theme.unwrap()).unwrap(), - telemetry_defaults: defaults.telemetry, - telemetry_overrides: Default::default(), - auto_update: defaults.auto_update.unwrap(), - base_keymap: Default::default(), - features: Features { - copilot: defaults.features.copilot.unwrap(), - }, - show_scrollbars: defaults.show_scrollbars.unwrap(), - } - } - - // Fill out the overrride and etc. settings from the user's settings.json - pub fn set_user_settings( - &mut self, - data: SettingsFileContent, - theme_registry: &ThemeRegistry, - font_cache: &FontCache, - ) { - let mut family_changed = false; - if let Some(value) = data.buffer_font_family { - self.buffer_font_family_name = value; - family_changed = true; - } - if let Some(value) = data.buffer_font_features { - self.buffer_font_features = value; - family_changed = true; - } - if family_changed { - if let Some(id) = font_cache - .load_family(&[&self.buffer_font_family_name], &self.buffer_font_features) - .log_err() - { - self.buffer_font_family = id; - } - } - - if let Some(value) = &data.theme { - if let Some(theme) = theme_registry.get(value).log_err() { - self.theme = theme; - } - } - - merge(&mut self.buffer_font_size, data.buffer_font_size); - merge( - &mut self.active_pane_magnification, - data.active_pane_magnification, - ); - merge(&mut self.default_buffer_font_size, data.buffer_font_size); - merge(&mut self.cursor_blink, data.cursor_blink); - merge(&mut self.confirm_quit, data.confirm_quit); - merge(&mut self.hover_popover_enabled, data.hover_popover_enabled); - merge( - &mut self.show_completions_on_input, - data.show_completions_on_input, - ); - merge(&mut self.vim_mode, data.vim_mode); - merge(&mut self.autosave, data.autosave); - merge(&mut self.default_dock_anchor, data.default_dock_anchor); - merge(&mut self.base_keymap, data.base_keymap); - merge(&mut self.show_scrollbars, data.show_scrollbars); - merge(&mut self.features.copilot, data.features.copilot); - - if let Some(copilot) = data.copilot { - if let Some(disabled_globs) = copilot.disabled_globs { - self.copilot.disabled_globs = disabled_globs - .into_iter() - .filter_map(|s| glob::Pattern::new(&s).ok()) - .collect() - } - } - self.editor_overrides = data.editor; - self.git_overrides = data.git.unwrap_or_default(); - self.journal_overrides = data.journal; - self.terminal_defaults.font_size = data.terminal.font_size; - self.terminal_overrides.copy_on_select = data.terminal.copy_on_select; - self.terminal_overrides = data.terminal; - self.language_overrides = data.languages; - self.telemetry_overrides = data.telemetry; - self.lsp = data.lsp; - merge(&mut self.auto_update, data.auto_update); - } - - pub fn with_language_defaults( - mut self, - language_name: impl Into>, - overrides: EditorSettings, - ) -> Self { - self.language_defaults - .insert(language_name.into(), overrides); - self - } - - pub fn features(&self) -> &Features { - &self.features - } - - pub fn show_copilot_suggestions(&self, language: Option<&str>, path: Option<&Path>) -> bool { - if !self.features.copilot { - return false; - } - - if !self.copilot_enabled_for_language(language) { - return false; - } - - if let Some(path) = path { - if !self.copilot_enabled_for_path(path) { - return false; - } - } - - true - } - - pub fn copilot_enabled_for_path(&self, path: &Path) -> bool { - !self - .copilot - .disabled_globs - .iter() - .any(|glob| glob.matches_path(path)) - } - - pub fn copilot_enabled_for_language(&self, language: Option<&str>) -> bool { - self.language_setting(language, |settings| settings.show_copilot_suggestions) - } - - pub fn tab_size(&self, language: Option<&str>) -> NonZeroU32 { - self.language_setting(language, |settings| settings.tab_size) - } - - pub fn show_whitespaces(&self, language: Option<&str>) -> ShowWhitespaces { - self.language_setting(language, |settings| settings.show_whitespaces) - } - - pub fn hard_tabs(&self, language: Option<&str>) -> bool { - self.language_setting(language, |settings| settings.hard_tabs) - } - - pub fn soft_wrap(&self, language: Option<&str>) -> SoftWrap { - self.language_setting(language, |settings| settings.soft_wrap) - } - - pub fn preferred_line_length(&self, language: Option<&str>) -> u32 { - self.language_setting(language, |settings| settings.preferred_line_length) - } - - pub fn remove_trailing_whitespace_on_save(&self, language: Option<&str>) -> bool { - self.language_setting(language, |settings| { - settings.remove_trailing_whitespace_on_save.clone() - }) - } - - pub fn ensure_final_newline_on_save(&self, language: Option<&str>) -> bool { - self.language_setting(language, |settings| { - settings.ensure_final_newline_on_save.clone() - }) - } - - pub fn format_on_save(&self, language: Option<&str>) -> FormatOnSave { - self.language_setting(language, |settings| settings.format_on_save.clone()) - } - - pub fn formatter(&self, language: Option<&str>) -> Formatter { - self.language_setting(language, |settings| settings.formatter.clone()) - } - - pub fn enable_language_server(&self, language: Option<&str>) -> bool { - self.language_setting(language, |settings| settings.enable_language_server) - } - - fn language_setting(&self, language: Option<&str>, f: F) -> R - where - F: Fn(&EditorSettings) -> Option, - { - None.or_else(|| language.and_then(|l| self.language_overrides.get(l).and_then(&f))) - .or_else(|| f(&self.editor_overrides)) - .or_else(|| language.and_then(|l| self.language_defaults.get(l).and_then(&f))) - .or_else(|| f(&self.editor_defaults)) - .expect("missing default") - } - - pub fn git_gutter(&self) -> GitGutter { - self.git_overrides.git_gutter.unwrap_or_else(|| { - self.git - .git_gutter - .expect("git_gutter should be some by setting setup") - }) - } - - pub fn telemetry(&self) -> TelemetrySettings { - TelemetrySettings { - diagnostics: Some(self.telemetry_diagnostics()), - metrics: Some(self.telemetry_metrics()), - } - } - - pub fn telemetry_diagnostics(&self) -> bool { - self.telemetry_overrides - .diagnostics - .or(self.telemetry_defaults.diagnostics) - .expect("missing default") - } - - pub fn telemetry_metrics(&self) -> bool { - self.telemetry_overrides - .metrics - .or(self.telemetry_defaults.metrics) - .expect("missing default") - } - - fn terminal_setting(&self, f: F) -> R - where - F: Fn(&TerminalSettings) -> Option, - { - None.or_else(|| f(&self.terminal_overrides)) - .or_else(|| f(&self.terminal_defaults)) - .expect("missing default") - } - - pub fn terminal_line_height(&self) -> f32 { - self.terminal_setting(|terminal_setting| terminal_setting.line_height()) - } - - pub fn terminal_scroll(&self) -> AlternateScroll { - self.terminal_setting(|terminal_setting| terminal_setting.alternate_scroll.to_owned()) - } - - pub fn terminal_shell(&self) -> Shell { - self.terminal_setting(|terminal_setting| terminal_setting.shell.to_owned()) - } - - pub fn terminal_env(&self) -> HashMap { - self.terminal_setting(|terminal_setting| terminal_setting.env.to_owned()) - } - - pub fn terminal_strategy(&self) -> WorkingDirectory { - self.terminal_setting(|terminal_setting| terminal_setting.working_directory.to_owned()) - } - - #[cfg(any(test, feature = "test-support"))] - pub fn test(cx: &gpui::AppContext) -> Settings { - Settings { - buffer_font_family_name: "Monaco".to_string(), - buffer_font_features: Default::default(), - buffer_font_family: cx - .font_cache() - .load_family(&["Monaco"], &Default::default()) - .unwrap(), - buffer_font_size: 14., - active_pane_magnification: 1., - default_buffer_font_size: 14., - confirm_quit: false, - cursor_blink: true, - hover_popover_enabled: true, - show_completions_on_input: true, - show_call_status_icon: true, - vim_mode: false, - autosave: Autosave::Off, - default_dock_anchor: DockAnchor::Bottom, - editor_defaults: EditorSettings { - tab_size: Some(4.try_into().unwrap()), - hard_tabs: Some(false), - soft_wrap: Some(SoftWrap::None), - preferred_line_length: Some(80), - remove_trailing_whitespace_on_save: Some(true), - ensure_final_newline_on_save: Some(true), - format_on_save: Some(FormatOnSave::On), - formatter: Some(Formatter::LanguageServer), - enable_language_server: Some(true), - show_copilot_suggestions: Some(true), - show_whitespaces: Some(ShowWhitespaces::None), - }, - editor_overrides: Default::default(), - copilot: Default::default(), - journal_defaults: Default::default(), - journal_overrides: Default::default(), - terminal_defaults: Default::default(), - terminal_overrides: Default::default(), - git: Default::default(), - git_overrides: Default::default(), - language_defaults: Default::default(), - language_overrides: Default::default(), - lsp: Default::default(), - theme: gpui::fonts::with_font_cache(cx.font_cache().clone(), Default::default), - telemetry_defaults: TelemetrySettings { - diagnostics: Some(true), - metrics: Some(true), - }, - telemetry_overrides: Default::default(), - auto_update: true, - base_keymap: Default::default(), - features: Features { copilot: true }, - show_scrollbars: Default::default(), - } - } - - #[cfg(any(test, feature = "test-support"))] - pub fn test_async(cx: &mut gpui::TestAppContext) { - cx.update(|cx| { - let settings = Self::test(cx); - cx.set_global(settings); - }); - } -} - -pub fn settings_file_json_schema( - theme_names: Vec, - language_names: &[String], -) -> serde_json::Value { - let settings = SchemaSettings::draft07().with(|settings| { - settings.option_add_null_type = false; - }); - let generator = SchemaGenerator::new(settings); - - let mut root_schema = generator.into_root_schema_for::(); - - // Create a schema for a theme name. - let theme_name_schema = SchemaObject { - instance_type: Some(SingleOrVec::Single(Box::new(InstanceType::String))), - enum_values: Some(theme_names.into_iter().map(Value::String).collect()), - ..Default::default() - }; - - // Create a schema for a 'languages overrides' object, associating editor - // settings with specific langauges. - assert!(root_schema.definitions.contains_key("EditorSettings")); - - let languages_object_schema = SchemaObject { - instance_type: Some(SingleOrVec::Single(Box::new(InstanceType::Object))), - object: Some(Box::new(ObjectValidation { - properties: language_names - .iter() - .map(|name| { - ( - name.clone(), - Schema::new_ref("#/definitions/EditorSettings".into()), - ) - }) - .collect(), - ..Default::default() - })), - ..Default::default() - }; - - // Add these new schemas as definitions, and modify properties of the root - // schema to reference them. - root_schema.definitions.extend([ - ("ThemeName".into(), theme_name_schema.into()), - ("Languages".into(), languages_object_schema.into()), - ]); - let root_schema_object = &mut root_schema.schema.object.as_mut().unwrap(); - - root_schema_object.properties.extend([ - ( - "theme".to_owned(), - Schema::new_ref("#/definitions/ThemeName".into()), - ), - ( - "languages".to_owned(), - Schema::new_ref("#/definitions/Languages".into()), - ), - // For backward compatibility - ( - "language_overrides".to_owned(), - Schema::new_ref("#/definitions/Languages".into()), - ), - ]); - - serde_json::to_value(root_schema).unwrap() -} - -fn merge(target: &mut T, value: Option) { - if let Some(value) = value { - *target = value; - } -} - -pub fn parse_json_with_comments(content: &str) -> Result { - Ok(serde_json::from_reader( - json_comments::CommentSettings::c_style().strip_comments(content.as_bytes()), - )?) -} - -lazy_static! { - static ref PAIR_QUERY: Query = Query::new( - tree_sitter_json::language(), - " - (pair - key: (string) @key - value: (_) @value) - ", - ) - .unwrap(); -} - -fn update_object_in_settings_file<'a>( - old_object: &'a serde_json::Map, - new_object: &'a serde_json::Map, - text: &str, - syntax_tree: &Tree, - tab_size: usize, - key_path: &mut Vec<&'a str>, - edits: &mut Vec<(Range, String)>, -) { - for (key, old_value) in old_object.iter() { - key_path.push(key); - let new_value = new_object.get(key).unwrap_or(&Value::Null); - - // If the old and new values are both objects, then compare them key by key, - // preserving the comments and formatting of the unchanged parts. Otherwise, - // replace the old value with the new value. - if let (Value::Object(old_sub_object), Value::Object(new_sub_object)) = - (old_value, new_value) - { - update_object_in_settings_file( - old_sub_object, - new_sub_object, - text, - syntax_tree, - tab_size, - key_path, - edits, - ) - } else if old_value != new_value { - let (range, replacement) = - update_key_in_settings_file(text, syntax_tree, &key_path, tab_size, &new_value); - edits.push((range, replacement)); - } - - key_path.pop(); - } -} - -fn update_key_in_settings_file( - text: &str, - syntax_tree: &Tree, - key_path: &[&str], - tab_size: usize, - new_value: impl Serialize, -) -> (Range, String) { - const LANGUAGE_OVERRIDES: &'static str = "language_overrides"; - const LANGUAGES: &'static str = "languages"; - - let mut cursor = tree_sitter::QueryCursor::new(); - - let has_language_overrides = text.contains(LANGUAGE_OVERRIDES); - - let mut depth = 0; - let mut last_value_range = 0..0; - let mut first_key_start = None; - let mut existing_value_range = 0..text.len(); - let matches = cursor.matches(&PAIR_QUERY, syntax_tree.root_node(), text.as_bytes()); - for mat in matches { - if mat.captures.len() != 2 { - continue; - } - - let key_range = mat.captures[0].node.byte_range(); - let value_range = mat.captures[1].node.byte_range(); - - // Don't enter sub objects until we find an exact - // match for the current keypath - if last_value_range.contains_inclusive(&value_range) { - continue; - } - - last_value_range = value_range.clone(); - - if key_range.start > existing_value_range.end { - break; - } - - first_key_start.get_or_insert_with(|| key_range.start); - - let found_key = text - .get(key_range.clone()) - .map(|key_text| { - if key_path[depth] == LANGUAGES && has_language_overrides { - return key_text == format!("\"{}\"", LANGUAGE_OVERRIDES); - } else { - return key_text == format!("\"{}\"", key_path[depth]); - } - }) - .unwrap_or(false); - - if found_key { - existing_value_range = value_range; - // Reset last value range when increasing in depth - last_value_range = existing_value_range.start..existing_value_range.start; - depth += 1; - - if depth == key_path.len() { - break; - } else { - first_key_start = None; - } - } - } - - // We found the exact key we want, insert the new value - if depth == key_path.len() { - let new_val = to_pretty_json(&new_value, tab_size, tab_size * depth); - (existing_value_range, new_val) - } else { - // We have key paths, construct the sub objects - let new_key = if has_language_overrides && key_path[depth] == LANGUAGES { - LANGUAGE_OVERRIDES - } else { - key_path[depth] - }; - - // We don't have the key, construct the nested objects - let mut new_value = serde_json::to_value(new_value).unwrap(); - for key in key_path[(depth + 1)..].iter().rev() { - if has_language_overrides && key == &LANGUAGES { - new_value = serde_json::json!({ LANGUAGE_OVERRIDES.to_string(): new_value }); - } else { - new_value = serde_json::json!({ key.to_string(): new_value }); - } - } - - if let Some(first_key_start) = first_key_start { - let mut row = 0; - let mut column = 0; - for (ix, char) in text.char_indices() { - if ix == first_key_start { - break; - } - if char == '\n' { - row += 1; - column = 0; - } else { - column += char.len_utf8(); - } - } - - if row > 0 { - // depth is 0 based, but division needs to be 1 based. - let new_val = to_pretty_json(&new_value, column / (depth + 1), column); - let space = ' '; - let content = format!("\"{new_key}\": {new_val},\n{space:width$}", width = column); - (first_key_start..first_key_start, content) - } else { - let new_val = serde_json::to_string(&new_value).unwrap(); - let mut content = format!(r#""{new_key}": {new_val},"#); - content.push(' '); - (first_key_start..first_key_start, content) - } - } else { - new_value = serde_json::json!({ new_key.to_string(): new_value }); - let indent_prefix_len = 4 * depth; - let mut new_val = to_pretty_json(&new_value, 4, indent_prefix_len); - if depth == 0 { - new_val.push('\n'); - } - - (existing_value_range, new_val) - } - } -} - -fn to_pretty_json(value: &impl Serialize, indent_size: usize, indent_prefix_len: usize) -> String { - const SPACES: [u8; 32] = [b' '; 32]; - - debug_assert!(indent_size <= SPACES.len()); - debug_assert!(indent_prefix_len <= SPACES.len()); - - let mut output = Vec::new(); - let mut ser = serde_json::Serializer::with_formatter( - &mut output, - serde_json::ser::PrettyFormatter::with_indent(&SPACES[0..indent_size.min(SPACES.len())]), - ); - - value.serialize(&mut ser).unwrap(); - let text = String::from_utf8(output).unwrap(); - - let mut adjusted_text = String::new(); - for (i, line) in text.split('\n').enumerate() { - if i > 0 { - adjusted_text.push_str(str::from_utf8(&SPACES[0..indent_prefix_len]).unwrap()); - } - adjusted_text.push_str(line); - adjusted_text.push('\n'); - } - adjusted_text.pop(); - adjusted_text -} - -/// Update the settings file with the given callback. -/// -/// Returns a new JSON string and the offset where the first edit occurred. -fn update_settings_file( - text: &str, - mut old_file_content: SettingsFileContent, - tab_size: NonZeroU32, - update: impl FnOnce(&mut SettingsFileContent), -) -> Vec<(Range, String)> { - let mut new_file_content = old_file_content.clone(); - update(&mut new_file_content); - - if new_file_content.languages.len() != old_file_content.languages.len() { - for language in new_file_content.languages.keys() { - old_file_content - .languages - .entry(language.clone()) - .or_default(); - } - for language in old_file_content.languages.keys() { - new_file_content - .languages - .entry(language.clone()) - .or_default(); - } - } - - let mut parser = tree_sitter::Parser::new(); - parser.set_language(tree_sitter_json::language()).unwrap(); - let tree = parser.parse(text, None).unwrap(); - - let old_object = to_json_object(old_file_content); - let new_object = to_json_object(new_file_content); - let mut key_path = Vec::new(); - let mut edits = Vec::new(); - update_object_in_settings_file( - &old_object, - &new_object, - &text, - &tree, - tab_size.get() as usize, - &mut key_path, - &mut edits, - ); - edits.sort_unstable_by_key(|e| e.0.start); - return edits; -} - -fn to_json_object(settings_file: SettingsFileContent) -> serde_json::Map { - let tmp = serde_json::to_value(settings_file).unwrap(); - match tmp { - Value::Object(map) => map, - _ => unreachable!("SettingsFileContent represents a JSON map"), - } -} - -#[cfg(test)] -mod tests { - use super::*; - use unindent::Unindent; - - fn assert_new_settings( - old_json: String, - update: fn(&mut SettingsFileContent), - expected_new_json: String, - ) { - let old_content: SettingsFileContent = serde_json::from_str(&old_json).unwrap_or_default(); - let edits = update_settings_file(&old_json, old_content, 4.try_into().unwrap(), update); - let mut new_json = old_json; - for (range, replacement) in edits.into_iter().rev() { - new_json.replace_range(range, &replacement); - } - pretty_assertions::assert_eq!(new_json, expected_new_json); - } - - #[test] - fn test_update_language_overrides_copilot() { - assert_new_settings( - r#" - { - "language_overrides": { - "JSON": { - "show_copilot_suggestions": false - } - } - } - "# - .unindent(), - |settings| { - settings.languages.insert( - "Rust".into(), - EditorSettings { - show_copilot_suggestions: Some(true), - ..Default::default() - }, - ); - }, - r#" - { - "language_overrides": { - "Rust": { - "show_copilot_suggestions": true - }, - "JSON": { - "show_copilot_suggestions": false - } - } - } - "# - .unindent(), - ); - } - - #[test] - fn test_update_copilot_globs() { - assert_new_settings( - r#" - { - } - "# - .unindent(), - |settings| { - settings.copilot = Some(CopilotSettingsContent { - disabled_globs: Some(vec![]), - }); - }, - r#" - { - "copilot": { - "disabled_globs": [] - } - } - "# - .unindent(), - ); - - assert_new_settings( - r#" - { - "copilot": { - "disabled_globs": [ - "**/*.json" - ] - } - } - "# - .unindent(), - |settings| { - settings - .copilot - .get_or_insert(Default::default()) - .disabled_globs - .as_mut() - .unwrap() - .push(".env".into()); - }, - r#" - { - "copilot": { - "disabled_globs": [ - "**/*.json", - ".env" - ] - } - } - "# - .unindent(), - ); - } - - #[test] - fn test_update_copilot() { - assert_new_settings( - r#" - { - "languages": { - "JSON": { - "show_copilot_suggestions": false - } - } - } - "# - .unindent(), - |settings| { - settings.editor.show_copilot_suggestions = Some(true); - }, - r#" - { - "show_copilot_suggestions": true, - "languages": { - "JSON": { - "show_copilot_suggestions": false - } - } - } - "# - .unindent(), - ); - } - - #[test] - fn test_update_language_copilot() { - assert_new_settings( - r#" - { - "languages": { - "JSON": { - "show_copilot_suggestions": false - } - } - } - "# - .unindent(), - |settings| { - settings.languages.insert( - "Rust".into(), - EditorSettings { - show_copilot_suggestions: Some(true), - ..Default::default() - }, - ); - }, - r#" - { - "languages": { - "Rust": { - "show_copilot_suggestions": true - }, - "JSON": { - "show_copilot_suggestions": false - } - } - } - "# - .unindent(), - ); - } - - #[test] - fn test_update_telemetry_setting_multiple_fields() { - assert_new_settings( - r#" - { - "telemetry": { - "metrics": false, - "diagnostics": false - } - } - "# - .unindent(), - |settings| { - settings.telemetry.set_diagnostics(true); - settings.telemetry.set_metrics(true); - }, - r#" - { - "telemetry": { - "metrics": true, - "diagnostics": true - } - } - "# - .unindent(), - ); - } - - #[test] - fn test_update_telemetry_setting_weird_formatting() { - assert_new_settings( - r#"{ - "telemetry": { "metrics": false, "diagnostics": true } - }"# - .unindent(), - |settings| settings.telemetry.set_diagnostics(false), - r#"{ - "telemetry": { "metrics": false, "diagnostics": false } - }"# - .unindent(), - ); - } - - #[test] - fn test_update_telemetry_setting_other_fields() { - assert_new_settings( - r#" - { - "telemetry": { - "metrics": false, - "diagnostics": true - } - } - "# - .unindent(), - |settings| settings.telemetry.set_diagnostics(false), - r#" - { - "telemetry": { - "metrics": false, - "diagnostics": false - } - } - "# - .unindent(), - ); - } - - #[test] - fn test_update_telemetry_setting_empty_telemetry() { - assert_new_settings( - r#" - { - "telemetry": {} - } - "# - .unindent(), - |settings| settings.telemetry.set_diagnostics(false), - r#" - { - "telemetry": { - "diagnostics": false - } - } - "# - .unindent(), - ); - } - - #[test] - fn test_update_telemetry_setting_pre_existing() { - assert_new_settings( - r#" - { - "telemetry": { - "diagnostics": true - } - } - "# - .unindent(), - |settings| settings.telemetry.set_diagnostics(false), - r#" - { - "telemetry": { - "diagnostics": false - } - } - "# - .unindent(), - ); - } - - #[test] - fn test_update_telemetry_setting() { - assert_new_settings( - "{}".into(), - |settings| settings.telemetry.set_diagnostics(true), - r#" - { - "telemetry": { - "diagnostics": true - } - } - "# - .unindent(), - ); - } - - #[test] - fn test_update_object_empty_doc() { - assert_new_settings( - "".into(), - |settings| settings.telemetry.set_diagnostics(true), - r#" - { - "telemetry": { - "diagnostics": true - } - } - "# - .unindent(), - ); - } - - #[test] - fn test_write_theme_into_settings_with_theme() { - assert_new_settings( - r#" - { - "theme": "One Dark" - } - "# - .unindent(), - |settings| settings.theme = Some("summerfruit-light".to_string()), - r#" - { - "theme": "summerfruit-light" - } - "# - .unindent(), - ); - } - - #[test] - fn test_write_theme_into_empty_settings() { - assert_new_settings( - r#" - { - } - "# - .unindent(), - |settings| settings.theme = Some("summerfruit-light".to_string()), - r#" - { - "theme": "summerfruit-light" - } - "# - .unindent(), - ); - } - - #[test] - fn write_key_no_document() { - assert_new_settings( - "".to_string(), - |settings| settings.theme = Some("summerfruit-light".to_string()), - r#" - { - "theme": "summerfruit-light" - } - "# - .unindent(), - ); - } - - #[test] - fn test_write_theme_into_single_line_settings_without_theme() { - assert_new_settings( - r#"{ "a": "", "ok": true }"#.to_string(), - |settings| settings.theme = Some("summerfruit-light".to_string()), - r#"{ "theme": "summerfruit-light", "a": "", "ok": true }"#.to_string(), - ); - } - - #[test] - fn test_write_theme_pre_object_whitespace() { - assert_new_settings( - r#" { "a": "", "ok": true }"#.to_string(), - |settings| settings.theme = Some("summerfruit-light".to_string()), - r#" { "theme": "summerfruit-light", "a": "", "ok": true }"#.unindent(), - ); - } - - #[test] - fn test_write_theme_into_multi_line_settings_without_theme() { - assert_new_settings( - r#" - { - "a": "b" - } - "# - .unindent(), - |settings| settings.theme = Some("summerfruit-light".to_string()), - r#" - { - "theme": "summerfruit-light", - "a": "b" - } - "# - .unindent(), - ); +pub fn initial_user_settings_content(assets: &'static impl AssetSource) -> Cow<'static, str> { + match assets.load(INITIAL_USER_SETTINGS_ASSET_PATH).unwrap() { + Cow::Borrowed(s) => Cow::Borrowed(str::from_utf8(s).unwrap()), + Cow::Owned(s) => Cow::Owned(String::from_utf8(s).unwrap()), } } diff --git a/crates/settings/src/settings_file.rs b/crates/settings/src/settings_file.rs index 6402a07f5e713f7e4d0ad821ab1120a684bd00a5..30848713d9ce4bff41ca06ecc965e26594efe887 100644 --- a/crates/settings/src/settings_file.rs +++ b/crates/settings/src/settings_file.rs @@ -1,367 +1,138 @@ -use crate::{update_settings_file, watched_json::WatchedJsonFile, Settings, SettingsFileContent}; +use crate::{settings_store::SettingsStore, Setting, DEFAULT_SETTINGS_ASSET_PATH}; use anyhow::Result; use assets::Assets; use fs::Fs; -use gpui::AppContext; -use std::{io::ErrorKind, ops::Range, path::Path, sync::Arc}; - -// TODO: Switch SettingsFile to open a worktree and buffer for synchronization -// And instant updates in the Zed editor -#[derive(Clone)] -pub struct SettingsFile { - path: &'static Path, - settings_file_content: WatchedJsonFile, - fs: Arc, +use futures::{channel::mpsc, StreamExt}; +use gpui::{executor::Background, AppContext, AssetSource}; +use std::{borrow::Cow, io::ErrorKind, path::PathBuf, str, sync::Arc, time::Duration}; +use util::{paths, ResultExt}; + +pub fn register(cx: &mut AppContext) { + cx.update_global::(|store, cx| { + store.register_setting::(cx); + }); } -impl SettingsFile { - pub fn new( - path: &'static Path, - settings_file_content: WatchedJsonFile, - fs: Arc, - ) -> Self { - SettingsFile { - path, - settings_file_content, - fs, - } - } - - async fn load_settings(path: &Path, fs: &Arc) -> Result { - match fs.load(path).await { - result @ Ok(_) => result, - Err(err) => { - if let Some(e) = err.downcast_ref::() { - if e.kind() == ErrorKind::NotFound { - return Ok(Settings::initial_user_settings_content(&Assets).to_string()); - } - } - return Err(err); - } - } - } - - pub fn update_unsaved( - text: &str, - cx: &AppContext, - update: impl FnOnce(&mut SettingsFileContent), - ) -> Vec<(Range, String)> { - let this = cx.global::(); - let tab_size = cx.global::().tab_size(Some("JSON")); - let current_file_content = this.settings_file_content.current(); - update_settings_file(&text, current_file_content, tab_size, update) - } - - pub fn update( - cx: &mut AppContext, - update: impl 'static + Send + FnOnce(&mut SettingsFileContent), - ) { - let this = cx.global::(); - let tab_size = cx.global::().tab_size(Some("JSON")); - let current_file_content = this.settings_file_content.current(); - let fs = this.fs.clone(); - let path = this.path.clone(); - - cx.background() - .spawn(async move { - let old_text = SettingsFile::load_settings(path, &fs).await?; - let edits = update_settings_file(&old_text, current_file_content, tab_size, update); - let mut new_text = old_text; - for (range, replacement) in edits.into_iter().rev() { - new_text.replace_range(range, &replacement); - } - fs.atomic_write(path.to_path_buf(), new_text).await?; - anyhow::Ok(()) - }) - .detach_and_log_err(cx) - } +pub fn get<'a, T: Setting>(cx: &'a AppContext) -> &'a T { + cx.global::().get(None) } -#[cfg(test)] -mod tests { - use super::*; - use crate::{ - watch_files, watched_json::watch_settings_file, EditorSettings, Settings, SoftWrap, - }; - use fs::FakeFs; - use gpui::{actions, elements::*, Action, Entity, TestAppContext, View, ViewContext}; - use theme::ThemeRegistry; - - struct TestView; - - impl Entity for TestView { - type Event = (); +pub fn default_settings() -> Cow<'static, str> { + match Assets.load(DEFAULT_SETTINGS_ASSET_PATH).unwrap() { + Cow::Borrowed(s) => Cow::Borrowed(str::from_utf8(s).unwrap()), + Cow::Owned(s) => Cow::Owned(String::from_utf8(s).unwrap()), } +} - impl View for TestView { - fn ui_name() -> &'static str { - "TestView" - } - - fn render(&mut self, _: &mut ViewContext) -> AnyElement { - Empty::new().into_any() - } - } - - #[gpui::test] - async fn test_base_keymap(cx: &mut gpui::TestAppContext) { - let executor = cx.background(); - let fs = FakeFs::new(executor.clone()); - let font_cache = cx.font_cache(); - - actions!(test, [A, B]); - // From the Atom keymap - actions!(workspace, [ActivatePreviousPane]); - // From the JetBrains keymap - actions!(pane, [ActivatePrevItem]); - - fs.save( - "/settings.json".as_ref(), - &r#" - { - "base_keymap": "Atom" - } - "# - .into(), - Default::default(), - ) - .await - .unwrap(); +#[cfg(any(test, feature = "test-support"))] +pub const EMPTY_THEME_NAME: &'static str = "empty-theme"; + +#[cfg(any(test, feature = "test-support"))] +pub fn test_settings() -> String { + let mut value = crate::settings_store::parse_json_with_comments::( + default_settings().as_ref(), + ) + .unwrap(); + util::merge_non_null_json_value_into( + serde_json::json!({ + "buffer_font_family": "Courier", + "buffer_font_features": {}, + "buffer_font_size": 14, + "theme": EMPTY_THEME_NAME, + }), + &mut value, + ); + value.as_object_mut().unwrap().remove("languages"); + serde_json::to_string(&value).unwrap() +} - fs.save( - "/keymap.json".as_ref(), - &r#" - [ - { - "bindings": { - "backspace": "test::A" +pub fn watch_config_file( + executor: Arc, + fs: Arc, + path: PathBuf, +) -> mpsc::UnboundedReceiver { + let (tx, rx) = mpsc::unbounded(); + executor + .spawn(async move { + let events = fs.watch(&path, Duration::from_millis(100)).await; + futures::pin_mut!(events); + loop { + if let Ok(contents) = fs.load(&path).await { + if !tx.unbounded_send(contents).is_ok() { + break; } } - ] - "# - .into(), - Default::default(), - ) - .await - .unwrap(); - - let settings_file = - WatchedJsonFile::new(fs.clone(), &executor, "/settings.json".as_ref()).await; - let keymaps_file = - WatchedJsonFile::new(fs.clone(), &executor, "/keymap.json".as_ref()).await; - - let default_settings = cx.read(Settings::test); - - cx.update(|cx| { - cx.add_global_action(|_: &A, _cx| {}); - cx.add_global_action(|_: &B, _cx| {}); - cx.add_global_action(|_: &ActivatePreviousPane, _cx| {}); - cx.add_global_action(|_: &ActivatePrevItem, _cx| {}); - watch_files( - default_settings, - settings_file, - ThemeRegistry::new((), font_cache), - keymaps_file, - cx, - ) - }); - - cx.foreground().run_until_parked(); - - let (window_id, _view) = cx.add_window(|_| TestView); - - // Test loading the keymap base at all - assert_key_bindings_for( - window_id, - cx, - vec![("backspace", &A), ("k", &ActivatePreviousPane)], - line!(), - ); - - // Test modifying the users keymap, while retaining the base keymap - fs.save( - "/keymap.json".as_ref(), - &r#" - [ - { - "bindings": { - "backspace": "test::B" - } + if events.next().await.is_none() { + break; } - ] - "# - .into(), - Default::default(), - ) - .await - .unwrap(); - - cx.foreground().run_until_parked(); - - assert_key_bindings_for( - window_id, - cx, - vec![("backspace", &B), ("k", &ActivatePreviousPane)], - line!(), - ); - - // Test modifying the base, while retaining the users keymap - fs.save( - "/settings.json".as_ref(), - &r#" - { - "base_keymap": "JetBrains" } - "# - .into(), - Default::default(), - ) - .await - .unwrap(); - - cx.foreground().run_until_parked(); - - assert_key_bindings_for( - window_id, - cx, - vec![("backspace", &B), ("[", &ActivatePrevItem)], - line!(), - ); - } + }) + .detach(); + rx +} - fn assert_key_bindings_for<'a>( - window_id: usize, - cx: &TestAppContext, - actions: Vec<(&'static str, &'a dyn Action)>, - line: u32, - ) { - for (key, action) in actions { - // assert that... - assert!( - cx.available_actions(window_id, 0) - .into_iter() - .any(|(_, bound_action, b)| { - // action names match... - bound_action.name() == action.name() - && bound_action.namespace() == action.namespace() - // and key strokes contain the given key - && b.iter() - .any(|binding| binding.keystrokes().iter().any(|k| k.key == key)) - }), - "On {} Failed to find {} with key binding {}", - line, - action.name(), - key - ); +pub fn handle_settings_file_changes( + mut user_settings_file_rx: mpsc::UnboundedReceiver, + cx: &mut AppContext, +) { + let user_settings_content = cx.background().block(user_settings_file_rx.next()).unwrap(); + cx.update_global::(|store, cx| { + store + .set_user_settings(&user_settings_content, cx) + .log_err(); + }); + cx.spawn(move |mut cx| async move { + while let Some(user_settings_content) = user_settings_file_rx.next().await { + cx.update(|cx| { + cx.update_global::(|store, cx| { + store + .set_user_settings(&user_settings_content, cx) + .log_err(); + }); + cx.refresh_windows(); + }); } - } - - #[gpui::test] - async fn test_watch_settings_files(cx: &mut gpui::TestAppContext) { - let executor = cx.background(); - let fs = FakeFs::new(executor.clone()); - let font_cache = cx.font_cache(); + }) + .detach(); +} - fs.save( - "/settings.json".as_ref(), - &r#" - { - "buffer_font_size": 24, - "soft_wrap": "editor_width", - "tab_size": 8, - "language_overrides": { - "Markdown": { - "tab_size": 2, - "preferred_line_length": 100, - "soft_wrap": "preferred_line_length" - } +async fn load_settings(fs: &Arc) -> Result { + match fs.load(&paths::SETTINGS).await { + result @ Ok(_) => result, + Err(err) => { + if let Some(e) = err.downcast_ref::() { + if e.kind() == ErrorKind::NotFound { + return Ok(crate::initial_user_settings_content(&Assets).to_string()); } } - "# - .into(), - Default::default(), - ) - .await - .unwrap(); + return Err(err); + } + } +} - let source = WatchedJsonFile::new(fs.clone(), &executor, "/settings.json".as_ref()).await; +pub fn update_settings_file( + fs: Arc, + cx: &mut AppContext, + update: impl 'static + Send + FnOnce(&mut T::FileContent), +) { + cx.spawn(|cx| async move { + let old_text = cx + .background() + .spawn({ + let fs = fs.clone(); + async move { load_settings(&fs).await } + }) + .await?; - let default_settings = cx.read(Settings::test).with_language_defaults( - "JavaScript", - EditorSettings { - tab_size: Some(2.try_into().unwrap()), - ..Default::default() - }, - ); - cx.update(|cx| { - watch_settings_file( - default_settings.clone(), - source, - ThemeRegistry::new((), font_cache), - cx, - ) + let new_text = cx.read(|cx| { + cx.global::() + .new_text_for_update::(old_text, update) }); - cx.foreground().run_until_parked(); - let settings = cx.read(|cx| cx.global::().clone()); - assert_eq!(settings.buffer_font_size, 24.0); - - assert_eq!(settings.soft_wrap(None), SoftWrap::EditorWidth); - assert_eq!( - settings.soft_wrap(Some("Markdown")), - SoftWrap::PreferredLineLength - ); - assert_eq!( - settings.soft_wrap(Some("JavaScript")), - SoftWrap::EditorWidth - ); - - assert_eq!(settings.preferred_line_length(None), 80); - assert_eq!(settings.preferred_line_length(Some("Markdown")), 100); - assert_eq!(settings.preferred_line_length(Some("JavaScript")), 80); - - assert_eq!(settings.tab_size(None).get(), 8); - assert_eq!(settings.tab_size(Some("Markdown")).get(), 2); - assert_eq!(settings.tab_size(Some("JavaScript")).get(), 8); - - fs.save( - "/settings.json".as_ref(), - &"(garbage)".into(), - Default::default(), - ) - .await - .unwrap(); - // fs.remove_file("/settings.json".as_ref(), Default::default()) - // .await - // .unwrap(); - - cx.foreground().run_until_parked(); - let settings = cx.read(|cx| cx.global::().clone()); - assert_eq!(settings.buffer_font_size, 24.0); - - assert_eq!(settings.soft_wrap(None), SoftWrap::EditorWidth); - assert_eq!( - settings.soft_wrap(Some("Markdown")), - SoftWrap::PreferredLineLength - ); - assert_eq!( - settings.soft_wrap(Some("JavaScript")), - SoftWrap::EditorWidth - ); - - assert_eq!(settings.preferred_line_length(None), 80); - assert_eq!(settings.preferred_line_length(Some("Markdown")), 100); - assert_eq!(settings.preferred_line_length(Some("JavaScript")), 80); - - assert_eq!(settings.tab_size(None).get(), 8); - assert_eq!(settings.tab_size(Some("Markdown")).get(), 2); - assert_eq!(settings.tab_size(Some("JavaScript")).get(), 8); - - fs.remove_file("/settings.json".as_ref(), Default::default()) - .await - .unwrap(); - cx.foreground().run_until_parked(); - let settings = cx.read(|cx| cx.global::().clone()); - assert_eq!(settings.buffer_font_size, default_settings.buffer_font_size); - } + cx.background() + .spawn(async move { fs.atomic_write(paths::SETTINGS.clone(), new_text).await }) + .await?; + anyhow::Ok(()) + }) + .detach_and_log_err(cx); } diff --git a/crates/settings/src/settings_store.rs b/crates/settings/src/settings_store.rs new file mode 100644 index 0000000000000000000000000000000000000000..dd81b05434af71680f85f18b644839d7a6b47875 --- /dev/null +++ b/crates/settings/src/settings_store.rs @@ -0,0 +1,1246 @@ +use anyhow::Result; +use collections::{btree_map, hash_map, BTreeMap, HashMap}; +use gpui::AppContext; +use lazy_static::lazy_static; +use schemars::{gen::SchemaGenerator, schema::RootSchema, JsonSchema}; +use serde::{de::DeserializeOwned, Deserialize as _, Serialize}; +use smallvec::SmallVec; +use std::{ + any::{type_name, Any, TypeId}, + fmt::Debug, + ops::Range, + path::Path, + str, + sync::Arc, +}; +use util::{merge_non_null_json_value_into, RangeExt, ResultExt as _}; + +/// A value that can be defined as a user setting. +/// +/// Settings can be loaded from a combination of multiple JSON files. +pub trait Setting: 'static { + /// The name of a key within the JSON file from which this setting should + /// be deserialized. If this is `None`, then the setting will be deserialized + /// from the root object. + const KEY: Option<&'static str>; + + /// The type that is stored in an individual JSON file. + type FileContent: Clone + Serialize + DeserializeOwned + JsonSchema; + + /// The logic for combining together values from one or more JSON files into the + /// final value for this setting. + /// + /// The user values are ordered from least specific (the global settings file) + /// to most specific (the innermost local settings file). + fn load( + default_value: &Self::FileContent, + user_values: &[&Self::FileContent], + cx: &AppContext, + ) -> Result + where + Self: Sized; + + fn json_schema( + generator: &mut SchemaGenerator, + _: &SettingsJsonSchemaParams, + _: &AppContext, + ) -> RootSchema { + generator.root_schema_for::() + } + + fn json_merge( + default_value: &Self::FileContent, + user_values: &[&Self::FileContent], + ) -> Result { + let mut merged = serde_json::Value::Null; + for value in [default_value].iter().chain(user_values) { + merge_non_null_json_value_into(serde_json::to_value(value).unwrap(), &mut merged); + } + Ok(serde_json::from_value(merged)?) + } + + fn load_via_json_merge( + default_value: &Self::FileContent, + user_values: &[&Self::FileContent], + ) -> Result + where + Self: DeserializeOwned, + { + let mut merged = serde_json::Value::Null; + for value in [default_value].iter().chain(user_values) { + merge_non_null_json_value_into(serde_json::to_value(value).unwrap(), &mut merged); + } + Ok(serde_json::from_value(merged)?) + } + + fn missing_default() -> anyhow::Error { + anyhow::anyhow!("missing default") + } +} + +pub struct SettingsJsonSchemaParams<'a> { + pub staff_mode: bool, + pub language_names: &'a [String], +} + +/// A set of strongly-typed setting values defined via multiple JSON files. +#[derive(Default)] +pub struct SettingsStore { + setting_values: HashMap>, + default_deserialized_settings: Option, + user_deserialized_settings: Option, + local_deserialized_settings: BTreeMap, serde_json::Value>, + tab_size_callback: Option<(TypeId, Box Option>)>, +} + +#[derive(Debug)] +struct SettingValue { + global_value: Option, + local_values: Vec<(Arc, T)>, +} + +trait AnySettingValue { + fn key(&self) -> Option<&'static str>; + fn setting_type_name(&self) -> &'static str; + fn deserialize_setting(&self, json: &serde_json::Value) -> Result; + fn load_setting( + &self, + default_value: &DeserializedSetting, + custom: &[DeserializedSetting], + cx: &AppContext, + ) -> Result>; + fn value_for_path(&self, path: Option<&Path>) -> &dyn Any; + fn set_global_value(&mut self, value: Box); + fn set_local_value(&mut self, path: Arc, value: Box); + fn json_schema( + &self, + generator: &mut SchemaGenerator, + _: &SettingsJsonSchemaParams, + cx: &AppContext, + ) -> RootSchema; +} + +struct DeserializedSetting(Box); + +impl SettingsStore { + /// Add a new type of setting to the store. + pub fn register_setting(&mut self, cx: &AppContext) { + let setting_type_id = TypeId::of::(); + let entry = self.setting_values.entry(setting_type_id); + if matches!(entry, hash_map::Entry::Occupied(_)) { + return; + } + + let setting_value = entry.or_insert(Box::new(SettingValue:: { + global_value: None, + local_values: Vec::new(), + })); + + if let Some(default_settings) = &self.default_deserialized_settings { + if let Some(default_settings) = setting_value + .deserialize_setting(default_settings) + .log_err() + { + let mut user_values_stack = Vec::new(); + + if let Some(user_settings) = &self.user_deserialized_settings { + if let Some(user_settings) = + setting_value.deserialize_setting(user_settings).log_err() + { + user_values_stack = vec![user_settings]; + } + } + + if let Some(setting) = setting_value + .load_setting(&default_settings, &user_values_stack, cx) + .log_err() + { + setting_value.set_global_value(setting); + } + } + } + } + + /// Get the value of a setting. + /// + /// Panics if the given setting type has not been registered, or if there is no + /// value for this setting. + pub fn get(&self, path: Option<&Path>) -> &T { + self.setting_values + .get(&TypeId::of::()) + .unwrap_or_else(|| panic!("unregistered setting type {}", type_name::())) + .value_for_path(path) + .downcast_ref::() + .expect("no default value for setting type") + } + + /// Override the global value for a setting. + /// + /// The given value will be overwritten if the user settings file changes. + pub fn override_global(&mut self, value: T) { + self.setting_values + .get_mut(&TypeId::of::()) + .unwrap_or_else(|| panic!("unregistered setting type {}", type_name::())) + .set_global_value(Box::new(value)) + } + + /// Get the user's settings as a raw JSON value. + /// + /// This is only for debugging and reporting. For user-facing functionality, + /// use the typed setting interface. + pub fn untyped_user_settings(&self) -> &serde_json::Value { + self.user_deserialized_settings + .as_ref() + .unwrap_or(&serde_json::Value::Null) + } + + #[cfg(any(test, feature = "test-support"))] + pub fn test(cx: &AppContext) -> Self { + let mut this = Self::default(); + this.set_default_settings(&crate::test_settings(), cx) + .unwrap(); + this.set_user_settings("{}", cx).unwrap(); + this + } + + /// Update the value of a setting in the user's global configuration. + /// + /// This is only for tests. Normally, settings are only loaded from + /// JSON files. + #[cfg(any(test, feature = "test-support"))] + pub fn update_user_settings( + &mut self, + cx: &AppContext, + update: impl FnOnce(&mut T::FileContent), + ) { + if self.user_deserialized_settings.is_none() { + self.set_user_settings("{}", cx).unwrap(); + } + let old_text = + serde_json::to_string(self.user_deserialized_settings.as_ref().unwrap()).unwrap(); + let new_text = self.new_text_for_update::(old_text, update); + self.set_user_settings(&new_text, cx).unwrap(); + } + + /// Update the value of a setting in a JSON file, returning the new text + /// for that JSON file. + pub fn new_text_for_update( + &self, + old_text: String, + update: impl FnOnce(&mut T::FileContent), + ) -> String { + let edits = self.edits_for_update::(&old_text, update); + let mut new_text = old_text; + for (range, replacement) in edits.into_iter() { + new_text.replace_range(range, &replacement); + } + new_text + } + + /// Update the value of a setting in a JSON file, returning a list + /// of edits to apply to the JSON file. + pub fn edits_for_update( + &self, + text: &str, + update: impl FnOnce(&mut T::FileContent), + ) -> Vec<(Range, String)> { + let setting_type_id = TypeId::of::(); + + let old_content = self + .setting_values + .get(&setting_type_id) + .unwrap_or_else(|| panic!("unregistered setting type {}", type_name::())) + .deserialize_setting( + self.user_deserialized_settings + .as_ref() + .expect("no user settings loaded"), + ) + .unwrap_or_else(|e| { + panic!( + "could not deserialize setting type {} from user settings: {}", + type_name::(), + e + ) + }) + .0 + .downcast::() + .unwrap(); + let mut new_content = old_content.clone(); + update(&mut new_content); + + let old_value = &serde_json::to_value(&old_content).unwrap(); + let new_value = serde_json::to_value(new_content).unwrap(); + + let mut key_path = Vec::new(); + if let Some(key) = T::KEY { + key_path.push(key); + } + + let mut edits = Vec::new(); + let tab_size = self.json_tab_size(); + let mut text = text.to_string(); + update_value_in_json_text( + &mut text, + &mut key_path, + tab_size, + &old_value, + &new_value, + &mut edits, + ); + return edits; + } + + /// Configure the tab sized when updating JSON files. + pub fn set_json_tab_size_callback( + &mut self, + get_tab_size: fn(&T) -> Option, + ) { + self.tab_size_callback = Some(( + TypeId::of::(), + Box::new(move |value| get_tab_size(value.downcast_ref::().unwrap())), + )); + } + + fn json_tab_size(&self) -> usize { + const DEFAULT_JSON_TAB_SIZE: usize = 2; + + if let Some((setting_type_id, callback)) = &self.tab_size_callback { + let setting_value = self.setting_values.get(setting_type_id).unwrap(); + let value = setting_value.value_for_path(None); + if let Some(value) = callback(value) { + return value; + } + } + + DEFAULT_JSON_TAB_SIZE + } + + /// Set the default settings via a JSON string. + /// + /// The string should contain a JSON object with a default value for every setting. + pub fn set_default_settings( + &mut self, + default_settings_content: &str, + cx: &AppContext, + ) -> Result<()> { + self.default_deserialized_settings = + Some(parse_json_with_comments(default_settings_content)?); + self.recompute_values(None, cx)?; + Ok(()) + } + + /// Set the user settings via a JSON string. + pub fn set_user_settings( + &mut self, + user_settings_content: &str, + cx: &AppContext, + ) -> Result<()> { + self.user_deserialized_settings = Some(parse_json_with_comments(user_settings_content)?); + self.recompute_values(None, cx)?; + Ok(()) + } + + /// Add or remove a set of local settings via a JSON string. + pub fn set_local_settings( + &mut self, + path: Arc, + settings_content: Option<&str>, + cx: &AppContext, + ) -> Result<()> { + if let Some(content) = settings_content { + self.local_deserialized_settings + .insert(path.clone(), parse_json_with_comments(content)?); + } else { + self.local_deserialized_settings.remove(&path); + } + self.recompute_values(Some(&path), cx)?; + Ok(()) + } + + pub fn json_schema( + &self, + schema_params: &SettingsJsonSchemaParams, + cx: &AppContext, + ) -> serde_json::Value { + use schemars::{ + gen::SchemaSettings, + schema::{Schema, SchemaObject}, + }; + + let settings = SchemaSettings::draft07().with(|settings| { + settings.option_add_null_type = false; + }); + let mut generator = SchemaGenerator::new(settings); + let mut combined_schema = RootSchema::default(); + + for setting_value in self.setting_values.values() { + let setting_schema = setting_value.json_schema(&mut generator, schema_params, cx); + combined_schema + .definitions + .extend(setting_schema.definitions); + + let target_schema = if let Some(key) = setting_value.key() { + let key_schema = combined_schema + .schema + .object() + .properties + .entry(key.to_string()) + .or_insert_with(|| Schema::Object(SchemaObject::default())); + if let Schema::Object(key_schema) = key_schema { + key_schema + } else { + continue; + } + } else { + &mut combined_schema.schema + }; + + merge_schema(target_schema, setting_schema.schema); + } + + fn merge_schema(target: &mut SchemaObject, source: SchemaObject) { + if let Some(source) = source.object { + let target_properties = &mut target.object().properties; + for (key, value) in source.properties { + match target_properties.entry(key) { + btree_map::Entry::Vacant(e) => { + e.insert(value); + } + btree_map::Entry::Occupied(e) => { + if let (Schema::Object(target), Schema::Object(src)) = + (e.into_mut(), value) + { + merge_schema(target, src); + } + } + } + } + } + + overwrite(&mut target.instance_type, source.instance_type); + overwrite(&mut target.string, source.string); + overwrite(&mut target.number, source.number); + overwrite(&mut target.reference, source.reference); + overwrite(&mut target.array, source.array); + overwrite(&mut target.enum_values, source.enum_values); + + fn overwrite(target: &mut Option, source: Option) { + if let Some(source) = source { + *target = Some(source); + } + } + } + + serde_json::to_value(&combined_schema).unwrap() + } + + fn recompute_values( + &mut self, + changed_local_path: Option<&Path>, + cx: &AppContext, + ) -> Result<()> { + // Reload the global and local values for every setting. + let mut user_settings_stack = Vec::::new(); + let mut paths_stack = Vec::>::new(); + for setting_value in self.setting_values.values_mut() { + if let Some(default_settings) = &self.default_deserialized_settings { + let default_settings = setting_value.deserialize_setting(default_settings)?; + + user_settings_stack.clear(); + paths_stack.clear(); + + if let Some(user_settings) = &self.user_deserialized_settings { + if let Some(user_settings) = + setting_value.deserialize_setting(user_settings).log_err() + { + user_settings_stack.push(user_settings); + paths_stack.push(None); + } + } + + // If the global settings file changed, reload the global value for the field. + if changed_local_path.is_none() { + setting_value.set_global_value(setting_value.load_setting( + &default_settings, + &user_settings_stack, + cx, + )?); + } + + // Reload the local values for the setting. + for (path, local_settings) in &self.local_deserialized_settings { + // Build a stack of all of the local values for that setting. + while let Some(prev_path) = paths_stack.last() { + if let Some(prev_path) = prev_path { + if !path.starts_with(prev_path) { + paths_stack.pop(); + user_settings_stack.pop(); + continue; + } + } + break; + } + + if let Some(local_settings) = + setting_value.deserialize_setting(&local_settings).log_err() + { + paths_stack.push(Some(path.as_ref())); + user_settings_stack.push(local_settings); + + // If a local settings file changed, then avoid recomputing local + // settings for any path outside of that directory. + if changed_local_path.map_or(false, |changed_local_path| { + !path.starts_with(changed_local_path) + }) { + continue; + } + + setting_value.set_local_value( + path.clone(), + setting_value.load_setting( + &default_settings, + &user_settings_stack, + cx, + )?, + ); + } + } + } + } + Ok(()) + } +} + +impl AnySettingValue for SettingValue { + fn key(&self) -> Option<&'static str> { + T::KEY + } + + fn setting_type_name(&self) -> &'static str { + type_name::() + } + + fn load_setting( + &self, + default_value: &DeserializedSetting, + user_values: &[DeserializedSetting], + cx: &AppContext, + ) -> Result> { + let default_value = default_value.0.downcast_ref::().unwrap(); + let values: SmallVec<[&T::FileContent; 6]> = user_values + .iter() + .map(|value| value.0.downcast_ref().unwrap()) + .collect(); + Ok(Box::new(T::load(default_value, &values, cx)?)) + } + + fn deserialize_setting(&self, mut json: &serde_json::Value) -> Result { + if let Some(key) = T::KEY { + json = json.get(key).unwrap_or(&serde_json::Value::Null); + } + let value = T::FileContent::deserialize(json)?; + Ok(DeserializedSetting(Box::new(value))) + } + + fn value_for_path(&self, path: Option<&Path>) -> &dyn Any { + if let Some(path) = path { + for (settings_path, value) in self.local_values.iter().rev() { + if path.starts_with(&settings_path) { + return value; + } + } + } + self.global_value + .as_ref() + .unwrap_or_else(|| panic!("no default value for setting {}", self.setting_type_name())) + } + + fn set_global_value(&mut self, value: Box) { + self.global_value = Some(*value.downcast().unwrap()); + } + + fn set_local_value(&mut self, path: Arc, value: Box) { + let value = *value.downcast().unwrap(); + match self.local_values.binary_search_by_key(&&path, |e| &e.0) { + Ok(ix) => self.local_values[ix].1 = value, + Err(ix) => self.local_values.insert(ix, (path, value)), + } + } + + fn json_schema( + &self, + generator: &mut SchemaGenerator, + params: &SettingsJsonSchemaParams, + cx: &AppContext, + ) -> RootSchema { + T::json_schema(generator, params, cx) + } +} + +// impl Debug for SettingsStore { +// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +// return f +// .debug_struct("SettingsStore") +// .field( +// "setting_value_sets_by_type", +// &self +// .setting_values +// .values() +// .map(|set| (set.setting_type_name(), set)) +// .collect::>(), +// ) +// .finish_non_exhaustive(); +// } +// } + +fn update_value_in_json_text<'a>( + text: &mut String, + key_path: &mut Vec<&'a str>, + tab_size: usize, + old_value: &'a serde_json::Value, + new_value: &'a serde_json::Value, + edits: &mut Vec<(Range, String)>, +) { + // If the old and new values are both objects, then compare them key by key, + // preserving the comments and formatting of the unchanged parts. Otherwise, + // replace the old value with the new value. + if let (serde_json::Value::Object(old_object), serde_json::Value::Object(new_object)) = + (old_value, new_value) + { + for (key, old_sub_value) in old_object.iter() { + key_path.push(key); + let new_sub_value = new_object.get(key).unwrap_or(&serde_json::Value::Null); + update_value_in_json_text( + text, + key_path, + tab_size, + old_sub_value, + new_sub_value, + edits, + ); + key_path.pop(); + } + for (key, new_sub_value) in new_object.iter() { + key_path.push(key); + if !old_object.contains_key(key) { + update_value_in_json_text( + text, + key_path, + tab_size, + &serde_json::Value::Null, + new_sub_value, + edits, + ); + } + key_path.pop(); + } + } else if old_value != new_value { + let (range, replacement) = + replace_value_in_json_text(text, &key_path, tab_size, &new_value); + text.replace_range(range.clone(), &replacement); + edits.push((range, replacement)); + } +} + +fn replace_value_in_json_text( + text: &str, + key_path: &[&str], + tab_size: usize, + new_value: impl Serialize, +) -> (Range, String) { + const LANGUAGE_OVERRIDES: &'static str = "language_overrides"; + const LANGUAGES: &'static str = "languages"; + + lazy_static! { + static ref PAIR_QUERY: tree_sitter::Query = tree_sitter::Query::new( + tree_sitter_json::language(), + "(pair key: (string) @key value: (_) @value)", + ) + .unwrap(); + } + + let mut parser = tree_sitter::Parser::new(); + parser.set_language(tree_sitter_json::language()).unwrap(); + let syntax_tree = parser.parse(text, None).unwrap(); + + let mut cursor = tree_sitter::QueryCursor::new(); + + let has_language_overrides = text.contains(LANGUAGE_OVERRIDES); + + let mut depth = 0; + let mut last_value_range = 0..0; + let mut first_key_start = None; + let mut existing_value_range = 0..text.len(); + let matches = cursor.matches(&PAIR_QUERY, syntax_tree.root_node(), text.as_bytes()); + for mat in matches { + if mat.captures.len() != 2 { + continue; + } + + let key_range = mat.captures[0].node.byte_range(); + let value_range = mat.captures[1].node.byte_range(); + + // Don't enter sub objects until we find an exact + // match for the current keypath + if last_value_range.contains_inclusive(&value_range) { + continue; + } + + last_value_range = value_range.clone(); + + if key_range.start > existing_value_range.end { + break; + } + + first_key_start.get_or_insert_with(|| key_range.start); + + let found_key = text + .get(key_range.clone()) + .map(|key_text| { + if key_path[depth] == LANGUAGES && has_language_overrides { + return key_text == format!("\"{}\"", LANGUAGE_OVERRIDES); + } else { + return key_text == format!("\"{}\"", key_path[depth]); + } + }) + .unwrap_or(false); + + if found_key { + existing_value_range = value_range; + // Reset last value range when increasing in depth + last_value_range = existing_value_range.start..existing_value_range.start; + depth += 1; + + if depth == key_path.len() { + break; + } else { + first_key_start = None; + } + } + } + + // We found the exact key we want, insert the new value + if depth == key_path.len() { + let new_val = to_pretty_json(&new_value, tab_size, tab_size * depth); + (existing_value_range, new_val) + } else { + // We have key paths, construct the sub objects + let new_key = if has_language_overrides && key_path[depth] == LANGUAGES { + LANGUAGE_OVERRIDES + } else { + key_path[depth] + }; + + // We don't have the key, construct the nested objects + let mut new_value = serde_json::to_value(new_value).unwrap(); + for key in key_path[(depth + 1)..].iter().rev() { + if has_language_overrides && key == &LANGUAGES { + new_value = serde_json::json!({ LANGUAGE_OVERRIDES.to_string(): new_value }); + } else { + new_value = serde_json::json!({ key.to_string(): new_value }); + } + } + + if let Some(first_key_start) = first_key_start { + let mut row = 0; + let mut column = 0; + for (ix, char) in text.char_indices() { + if ix == first_key_start { + break; + } + if char == '\n' { + row += 1; + column = 0; + } else { + column += char.len_utf8(); + } + } + + if row > 0 { + // depth is 0 based, but division needs to be 1 based. + let new_val = to_pretty_json(&new_value, column / (depth + 1), column); + let space = ' '; + let content = format!("\"{new_key}\": {new_val},\n{space:width$}", width = column); + (first_key_start..first_key_start, content) + } else { + let new_val = serde_json::to_string(&new_value).unwrap(); + let mut content = format!(r#""{new_key}": {new_val},"#); + content.push(' '); + (first_key_start..first_key_start, content) + } + } else { + new_value = serde_json::json!({ new_key.to_string(): new_value }); + let indent_prefix_len = 4 * depth; + let mut new_val = to_pretty_json(&new_value, 4, indent_prefix_len); + if depth == 0 { + new_val.push('\n'); + } + + (existing_value_range, new_val) + } + } +} + +fn to_pretty_json(value: &impl Serialize, indent_size: usize, indent_prefix_len: usize) -> String { + const SPACES: [u8; 32] = [b' '; 32]; + + debug_assert!(indent_size <= SPACES.len()); + debug_assert!(indent_prefix_len <= SPACES.len()); + + let mut output = Vec::new(); + let mut ser = serde_json::Serializer::with_formatter( + &mut output, + serde_json::ser::PrettyFormatter::with_indent(&SPACES[0..indent_size.min(SPACES.len())]), + ); + + value.serialize(&mut ser).unwrap(); + let text = String::from_utf8(output).unwrap(); + + let mut adjusted_text = String::new(); + for (i, line) in text.split('\n').enumerate() { + if i > 0 { + adjusted_text.push_str(str::from_utf8(&SPACES[0..indent_prefix_len]).unwrap()); + } + adjusted_text.push_str(line); + adjusted_text.push('\n'); + } + adjusted_text.pop(); + adjusted_text +} + +pub fn parse_json_with_comments(content: &str) -> Result { + Ok(serde_json::from_reader( + json_comments::CommentSettings::c_style().strip_comments(content.as_bytes()), + )?) +} + +#[cfg(test)] +mod tests { + use super::*; + use serde_derive::Deserialize; + use unindent::Unindent; + + #[gpui::test] + fn test_settings_store_basic(cx: &mut AppContext) { + let mut store = SettingsStore::default(); + store.register_setting::(cx); + store.register_setting::(cx); + store.register_setting::(cx); + + // error - missing required field in default settings + store + .set_default_settings( + r#"{ + "user": { + "name": "John Doe", + "age": 30, + "staff": false + } + }"#, + cx, + ) + .unwrap_err(); + + // error - type error in default settings + store + .set_default_settings( + r#"{ + "turbo": "the-wrong-type", + "user": { + "name": "John Doe", + "age": 30, + "staff": false + } + }"#, + cx, + ) + .unwrap_err(); + + // valid default settings. + store + .set_default_settings( + r#"{ + "turbo": false, + "user": { + "name": "John Doe", + "age": 30, + "staff": false + } + }"#, + cx, + ) + .unwrap(); + + assert_eq!(store.get::(None), &TurboSetting(false)); + assert_eq!( + store.get::(None), + &UserSettings { + name: "John Doe".to_string(), + age: 30, + staff: false, + } + ); + assert_eq!( + store.get::(None), + &MultiKeySettings { + key1: String::new(), + key2: String::new(), + } + ); + + store + .set_user_settings( + r#"{ + "turbo": true, + "user": { "age": 31 }, + "key1": "a" + }"#, + cx, + ) + .unwrap(); + + assert_eq!(store.get::(None), &TurboSetting(true)); + assert_eq!( + store.get::(None), + &UserSettings { + name: "John Doe".to_string(), + age: 31, + staff: false + } + ); + + store + .set_local_settings( + Path::new("/root1").into(), + Some(r#"{ "user": { "staff": true } }"#), + cx, + ) + .unwrap(); + store + .set_local_settings( + Path::new("/root1/subdir").into(), + Some(r#"{ "user": { "name": "Jane Doe" } }"#), + cx, + ) + .unwrap(); + + store + .set_local_settings( + Path::new("/root2").into(), + Some(r#"{ "user": { "age": 42 }, "key2": "b" }"#), + cx, + ) + .unwrap(); + + assert_eq!( + store.get::(Some(Path::new("/root1/something"))), + &UserSettings { + name: "John Doe".to_string(), + age: 31, + staff: true + } + ); + assert_eq!( + store.get::(Some(Path::new("/root1/subdir/something"))), + &UserSettings { + name: "Jane Doe".to_string(), + age: 31, + staff: true + } + ); + assert_eq!( + store.get::(Some(Path::new("/root2/something"))), + &UserSettings { + name: "John Doe".to_string(), + age: 42, + staff: false + } + ); + assert_eq!( + store.get::(Some(Path::new("/root2/something"))), + &MultiKeySettings { + key1: "a".to_string(), + key2: "b".to_string(), + } + ); + } + + #[gpui::test] + fn test_setting_store_assign_json_before_register(cx: &mut AppContext) { + let mut store = SettingsStore::default(); + store + .set_default_settings( + r#"{ + "turbo": true, + "user": { + "name": "John Doe", + "age": 30, + "staff": false + }, + "key1": "x" + }"#, + cx, + ) + .unwrap(); + store + .set_user_settings(r#"{ "turbo": false }"#, cx) + .unwrap(); + store.register_setting::(cx); + store.register_setting::(cx); + + assert_eq!(store.get::(None), &TurboSetting(false)); + assert_eq!( + store.get::(None), + &UserSettings { + name: "John Doe".to_string(), + age: 30, + staff: false, + } + ); + + store.register_setting::(cx); + assert_eq!( + store.get::(None), + &MultiKeySettings { + key1: "x".into(), + key2: String::new(), + } + ); + } + + #[gpui::test] + fn test_setting_store_update(cx: &mut AppContext) { + let mut store = SettingsStore::default(); + store.register_setting::(cx); + store.register_setting::(cx); + store.register_setting::(cx); + + // entries added and updated + check_settings_update::( + &mut store, + r#"{ + "languages": { + "JSON": { + "is_enabled": true + } + } + }"# + .unindent(), + |settings| { + settings.languages.get_mut("JSON").unwrap().is_enabled = false; + settings + .languages + .insert("Rust".into(), LanguageSettingEntry { is_enabled: true }); + }, + r#"{ + "languages": { + "Rust": { + "is_enabled": true + }, + "JSON": { + "is_enabled": false + } + } + }"# + .unindent(), + cx, + ); + + // weird formatting + check_settings_update::( + &mut store, + r#"{ + "user": { "age": 36, "name": "Max", "staff": true } + }"# + .unindent(), + |settings| settings.age = Some(37), + r#"{ + "user": { "age": 37, "name": "Max", "staff": true } + }"# + .unindent(), + cx, + ); + + // single-line formatting, other keys + check_settings_update::( + &mut store, + r#"{ "one": 1, "two": 2 }"#.unindent(), + |settings| settings.key1 = Some("x".into()), + r#"{ "key1": "x", "one": 1, "two": 2 }"#.unindent(), + cx, + ); + + // empty object + check_settings_update::( + &mut store, + r#"{ + "user": {} + }"# + .unindent(), + |settings| settings.age = Some(37), + r#"{ + "user": { + "age": 37 + } + }"# + .unindent(), + cx, + ); + + // no content + check_settings_update::( + &mut store, + r#""#.unindent(), + |settings| settings.age = Some(37), + r#"{ + "user": { + "age": 37 + } + } + "# + .unindent(), + cx, + ); + } + + fn check_settings_update( + store: &mut SettingsStore, + old_json: String, + update: fn(&mut T::FileContent), + expected_new_json: String, + cx: &mut AppContext, + ) { + store.set_user_settings(&old_json, cx).ok(); + let edits = store.edits_for_update::(&old_json, update); + let mut new_json = old_json; + for (range, replacement) in edits.into_iter() { + new_json.replace_range(range, &replacement); + } + pretty_assertions::assert_eq!(new_json, expected_new_json); + } + + #[derive(Debug, PartialEq, Deserialize)] + struct UserSettings { + name: String, + age: u32, + staff: bool, + } + + #[derive(Clone, Serialize, Deserialize, JsonSchema)] + struct UserSettingsJson { + name: Option, + age: Option, + staff: Option, + } + + impl Setting for UserSettings { + const KEY: Option<&'static str> = Some("user"); + type FileContent = UserSettingsJson; + + fn load( + default_value: &UserSettingsJson, + user_values: &[&UserSettingsJson], + _: &AppContext, + ) -> Result { + Self::load_via_json_merge(default_value, user_values) + } + } + + #[derive(Debug, Deserialize, PartialEq)] + struct TurboSetting(bool); + + impl Setting for TurboSetting { + const KEY: Option<&'static str> = Some("turbo"); + type FileContent = Option; + + fn load( + default_value: &Option, + user_values: &[&Option], + _: &AppContext, + ) -> Result { + Self::load_via_json_merge(default_value, user_values) + } + } + + #[derive(Clone, Debug, PartialEq, Deserialize)] + struct MultiKeySettings { + #[serde(default)] + key1: String, + #[serde(default)] + key2: String, + } + + #[derive(Clone, Serialize, Deserialize, JsonSchema)] + struct MultiKeySettingsJson { + key1: Option, + key2: Option, + } + + impl Setting for MultiKeySettings { + const KEY: Option<&'static str> = None; + + type FileContent = MultiKeySettingsJson; + + fn load( + default_value: &MultiKeySettingsJson, + user_values: &[&MultiKeySettingsJson], + _: &AppContext, + ) -> Result { + Self::load_via_json_merge(default_value, user_values) + } + } + + #[derive(Debug, Deserialize)] + struct JournalSettings { + pub path: String, + pub hour_format: HourFormat, + } + + #[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)] + #[serde(rename_all = "snake_case")] + enum HourFormat { + Hour12, + Hour24, + } + + #[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)] + struct JournalSettingsJson { + pub path: Option, + pub hour_format: Option, + } + + impl Setting for JournalSettings { + const KEY: Option<&'static str> = Some("journal"); + + type FileContent = JournalSettingsJson; + + fn load( + default_value: &JournalSettingsJson, + user_values: &[&JournalSettingsJson], + _: &AppContext, + ) -> Result { + Self::load_via_json_merge(default_value, user_values) + } + } + + #[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)] + struct LanguageSettings { + #[serde(default)] + languages: HashMap, + } + + #[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)] + struct LanguageSettingEntry { + is_enabled: bool, + } + + impl Setting for LanguageSettings { + const KEY: Option<&'static str> = None; + + type FileContent = Self; + + fn load(default_value: &Self, user_values: &[&Self], _: &AppContext) -> Result { + Self::load_via_json_merge(default_value, user_values) + } + } +} diff --git a/crates/settings/src/watched_json.rs b/crates/settings/src/watched_json.rs deleted file mode 100644 index 16be82fa354e45cb9b6039b07b4fe37408b731a6..0000000000000000000000000000000000000000 --- a/crates/settings/src/watched_json.rs +++ /dev/null @@ -1,126 +0,0 @@ -use fs::Fs; -use futures::StreamExt; -use gpui::{executor, AppContext}; -use postage::sink::Sink as _; -use postage::{prelude::Stream, watch}; -use serde::Deserialize; - -use std::{path::Path, sync::Arc, time::Duration}; -use theme::ThemeRegistry; -use util::ResultExt; - -use crate::{parse_json_with_comments, KeymapFileContent, Settings, SettingsFileContent}; - -#[derive(Clone)] -pub struct WatchedJsonFile(pub watch::Receiver); - -impl WatchedJsonFile -where - T: 'static + for<'de> Deserialize<'de> + Clone + Default + Send + Sync, -{ - pub async fn new( - fs: Arc, - executor: &executor::Background, - path: impl Into>, - ) -> Self { - let path = path.into(); - let settings = Self::load(fs.clone(), &path).await.unwrap_or_default(); - let mut events = fs.watch(&path, Duration::from_millis(500)).await; - let (mut tx, rx) = watch::channel_with(settings); - executor - .spawn(async move { - while events.next().await.is_some() { - if let Some(settings) = Self::load(fs.clone(), &path).await { - if tx.send(settings).await.is_err() { - break; - } - } - } - }) - .detach(); - Self(rx) - } - - ///Loads the given watched JSON file. In the special case that the file is - ///empty (ignoring whitespace) or is not a file, this will return T::default() - async fn load(fs: Arc, path: &Path) -> Option { - if !fs.is_file(path).await { - return Some(T::default()); - } - - fs.load(path).await.log_err().and_then(|data| { - if data.trim().is_empty() { - Some(T::default()) - } else { - parse_json_with_comments(&data).log_err() - } - }) - } - - pub fn current(&self) -> T { - self.0.borrow().clone() - } -} - -pub fn watch_files( - defaults: Settings, - settings_file: WatchedJsonFile, - theme_registry: Arc, - keymap_file: WatchedJsonFile, - cx: &mut AppContext, -) { - watch_settings_file(defaults, settings_file, theme_registry, cx); - watch_keymap_file(keymap_file, cx); -} - -pub(crate) fn watch_settings_file( - defaults: Settings, - mut file: WatchedJsonFile, - theme_registry: Arc, - cx: &mut AppContext, -) { - settings_updated(&defaults, file.0.borrow().clone(), &theme_registry, cx); - cx.spawn(|mut cx| async move { - while let Some(content) = file.0.recv().await { - cx.update(|cx| settings_updated(&defaults, content, &theme_registry, cx)); - } - }) - .detach(); -} - -fn keymap_updated(content: KeymapFileContent, cx: &mut AppContext) { - cx.clear_bindings(); - KeymapFileContent::load_defaults(cx); - content.add_to_cx(cx).log_err(); -} - -fn settings_updated( - defaults: &Settings, - content: SettingsFileContent, - theme_registry: &Arc, - cx: &mut AppContext, -) { - let mut settings = defaults.clone(); - settings.set_user_settings(content, theme_registry, cx.font_cache()); - cx.set_global(settings); - cx.refresh_windows(); -} - -fn watch_keymap_file(mut file: WatchedJsonFile, cx: &mut AppContext) { - cx.spawn(|mut cx| async move { - let mut settings_subscription = None; - while let Some(content) = file.0.recv().await { - cx.update(|cx| { - let old_base_keymap = cx.global::().base_keymap; - keymap_updated(content.clone(), cx); - settings_subscription = Some(cx.observe_global::(move |cx| { - let settings = cx.global::(); - if settings.base_keymap != old_base_keymap { - keymap_updated(content.clone(), cx); - } - })); - }); - } - }) - .detach(); -} diff --git a/crates/terminal/Cargo.toml b/crates/terminal/Cargo.toml index 725b102c04591daa5b7ed2abde3f73cc7fb0e723..a2902234c5fc2909b9974de0beba72b2bc973d64 100644 --- a/crates/terminal/Cargo.toml +++ b/crates/terminal/Cargo.toml @@ -15,6 +15,7 @@ settings = { path = "../settings" } db = { path = "../db" } theme = { path = "../theme" } util = { path = "../util" } + alacritty_terminal = { git = "https://github.com/zed-industries/alacritty", rev = "a51dbe25d67e84d6ed4261e640d3954fbdd9be45" } procinfo = { git = "https://github.com/zed-industries/wezterm", rev = "5cd757e5f2eb039ed0c6bb6512223e69d5efc64d", default-features = false } smallvec.workspace = true @@ -27,6 +28,7 @@ dirs = "4.0.0" shellexpand = "2.1.0" libc = "0.2" anyhow.workspace = true +schemars.workspace = true thiserror.workspace = true lazy_static.workspace = true serde.workspace = true diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index 25852875c3b75e8fdc468dd3ec27ed1d6959fefd..98d85d00e1c51922347f690a6700a64819b51c87 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -31,8 +31,8 @@ use mappings::mouse::{ }; use procinfo::LocalProcessInfo; +use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use settings::{AlternateScroll, Settings, Shell, TerminalBlink}; use util::truncate_and_trailoff; use std::{ @@ -48,11 +48,12 @@ use std::{ use thiserror::Error; use gpui::{ + fonts, geometry::vector::{vec2f, Vector2F}, keymap_matcher::Keystroke, platform::{MouseButton, MouseMovedEvent, TouchPhase}, scene::{MouseDown, MouseDrag, MouseScrollWheel, MouseUp}, - ClipboardItem, Entity, ModelContext, Task, + AppContext, ClipboardItem, Entity, ModelContext, Task, }; use crate::mappings::{ @@ -114,6 +115,112 @@ impl EventListener for ZedListener { } } +pub fn init(cx: &mut AppContext) { + settings::register::(cx); +} + +#[derive(Deserialize)] +pub struct TerminalSettings { + pub shell: Shell, + pub working_directory: WorkingDirectory, + font_size: Option, + pub font_family: Option, + pub line_height: TerminalLineHeight, + pub font_features: Option, + pub env: HashMap, + pub blinking: TerminalBlink, + pub alternate_scroll: AlternateScroll, + pub option_as_meta: bool, + pub copy_on_select: bool, +} + +#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)] +pub struct TerminalSettingsContent { + pub shell: Option, + pub working_directory: Option, + pub font_size: Option, + pub font_family: Option, + pub line_height: Option, + pub font_features: Option, + pub env: Option>, + pub blinking: Option, + pub alternate_scroll: Option, + pub option_as_meta: Option, + pub copy_on_select: Option, +} + +impl TerminalSettings { + pub fn font_size(&self, cx: &AppContext) -> Option { + self.font_size + .map(|size| theme::adjusted_font_size(size, cx)) + } +} + +impl settings::Setting for TerminalSettings { + const KEY: Option<&'static str> = Some("terminal"); + + type FileContent = TerminalSettingsContent; + + fn load( + default_value: &Self::FileContent, + user_values: &[&Self::FileContent], + _: &AppContext, + ) -> Result { + Self::load_via_json_merge(default_value, user_values) + } +} + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema, Default)] +#[serde(rename_all = "snake_case")] +pub enum TerminalLineHeight { + #[default] + Comfortable, + Standard, + Custom(f32), +} + +impl TerminalLineHeight { + pub fn value(&self) -> f32 { + match self { + TerminalLineHeight::Comfortable => 1.618, + TerminalLineHeight::Standard => 1.3, + TerminalLineHeight::Custom(line_height) => *line_height, + } + } +} + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum TerminalBlink { + Off, + TerminalControlled, + On, +} + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum Shell { + System, + Program(String), + WithArguments { program: String, args: Vec }, +} + +#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum AlternateScroll { + On, + Off, +} + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum WorkingDirectory { + CurrentProjectDirectory, + FirstProjectDirectory, + AlwaysHome, + Always { directory: String }, +} + #[derive(Clone, Copy, Debug, Serialize, Deserialize)] pub struct TerminalSize { pub cell_width: f32, @@ -599,7 +706,7 @@ impl Terminal { match event { InternalEvent::ColorRequest(index, format) => { let color = term.colors()[*index].unwrap_or_else(|| { - let term_style = &cx.global::().theme.terminal; + let term_style = &theme::current(cx).terminal; to_alac_rgb(get_color_at_index(index, &term_style)) }); self.write_to_pty(format(color)) @@ -1049,16 +1156,7 @@ impl Terminal { } pub fn mouse_up(&mut self, e: &MouseUp, origin: Vector2F, cx: &mut ModelContext) { - let settings = cx.global::(); - let copy_on_select = settings - .terminal_overrides - .copy_on_select - .unwrap_or_else(|| { - settings - .terminal_defaults - .copy_on_select - .expect("Should be set in defaults") - }); + let setting = settings::get::(cx); let position = e.position.sub(origin); if self.mouse_mode(e.shift) { @@ -1072,7 +1170,7 @@ impl Terminal { self.pty_tx.notify(bytes); } } else { - if e.button == MouseButton::Left && copy_on_select { + if e.button == MouseButton::Left && setting.copy_on_select { self.copy(); } diff --git a/crates/terminal_view/src/terminal_button.rs b/crates/terminal_view/src/terminal_button.rs index a92f7285b51d4bad489af8ef929cb8a88ef91074..fcb5e7feb3ae8d5eaa1db8c87229b8c5c31c2e0e 100644 --- a/crates/terminal_view/src/terminal_button.rs +++ b/crates/terminal_view/src/terminal_button.rs @@ -5,7 +5,6 @@ use gpui::{ platform::{CursorStyle, MouseButton}, AnyElement, Element, Entity, View, ViewContext, ViewHandle, WeakViewHandle, }; -use settings::Settings; use std::any::TypeId; use workspace::{ dock::{Dock, FocusDock}, @@ -43,7 +42,7 @@ impl View for TerminalButton { let has_terminals = !project.local_terminal_handles().is_empty(); let terminal_count = project.local_terminal_handles().len() as i32; - let theme = cx.global::().theme.clone(); + let theme = theme::current(cx).clone(); Stack::new() .with_child( diff --git a/crates/terminal_view/src/terminal_element.rs b/crates/terminal_view/src/terminal_element.rs index ae2342cd97359aa267adf0154fa0e0c800858992..18c85db980ba14aab27ad91254ee022db57b1a0f 100644 --- a/crates/terminal_view/src/terminal_element.rs +++ b/crates/terminal_view/src/terminal_element.rs @@ -16,7 +16,6 @@ use gpui::{ use itertools::Itertools; use language::CursorShape; use ordered_float::OrderedFloat; -use settings::Settings; use terminal::{ alacritty_terminal::{ ansi::{Color as AnsiColor, Color::Named, CursorShape as AlacCursorShape, NamedColor}, @@ -25,9 +24,9 @@ use terminal::{ term::{cell::Flags, TermMode}, }, mappings::colors::convert_color, - IndexedCell, Terminal, TerminalContent, TerminalSize, + IndexedCell, Terminal, TerminalContent, TerminalSettings, TerminalSize, }; -use theme::TerminalStyle; +use theme::{TerminalStyle, ThemeSettings}; use util::ResultExt; use std::{fmt::Debug, ops::RangeInclusive}; @@ -510,38 +509,47 @@ impl TerminalElement { scene.push_mouse_region(region); } +} + +impl Element for TerminalElement { + type LayoutState = LayoutState; + type PaintState = (); + + fn layout( + &mut self, + constraint: gpui::SizeConstraint, + view: &mut TerminalView, + cx: &mut LayoutContext, + ) -> (gpui::geometry::vector::Vector2F, Self::LayoutState) { + let settings = settings::get::(cx); + let terminal_settings = settings::get::(cx); + + //Setup layout information + let terminal_theme = settings.theme.terminal.clone(); //TODO: Try to minimize this clone. + let link_style = settings.theme.editor.link_definition; + let tooltip_style = settings.theme.tooltip.clone(); - ///Configures a text style from the current settings. - pub fn make_text_style(font_cache: &FontCache, settings: &Settings) -> TextStyle { - let font_family_name = settings - .terminal_overrides + let font_cache = cx.font_cache(); + let font_size = terminal_settings + .font_size(cx) + .unwrap_or(settings.buffer_font_size(cx)); + let font_family_name = terminal_settings .font_family .as_ref() - .or(settings.terminal_defaults.font_family.as_ref()) .unwrap_or(&settings.buffer_font_family_name); - let font_features = settings - .terminal_overrides + let font_features = terminal_settings .font_features .as_ref() - .or(settings.terminal_defaults.font_features.as_ref()) .unwrap_or(&settings.buffer_font_features); - let family_id = font_cache .load_family(&[font_family_name], &font_features) .log_err() .unwrap_or(settings.buffer_font_family); - - let font_size = settings - .terminal_overrides - .font_size - .or(settings.terminal_defaults.font_size) - .unwrap_or(settings.buffer_font_size); - let font_id = font_cache .select_font(family_id, &Default::default()) .unwrap(); - TextStyle { + let text_style = TextStyle { color: settings.theme.editor.text_color, font_family_id: family_id, font_family_name: font_cache.family_name(family_id).unwrap(), @@ -549,34 +557,12 @@ impl TerminalElement { font_size, font_properties: Default::default(), underline: Default::default(), - } - } -} - -impl Element for TerminalElement { - type LayoutState = LayoutState; - type PaintState = (); - - fn layout( - &mut self, - constraint: gpui::SizeConstraint, - view: &mut TerminalView, - cx: &mut LayoutContext, - ) -> (gpui::geometry::vector::Vector2F, Self::LayoutState) { - let settings = cx.global::(); - let font_cache = cx.font_cache(); - - //Setup layout information - let terminal_theme = settings.theme.terminal.clone(); //TODO: Try to minimize this clone. - let link_style = settings.theme.editor.link_definition; - let tooltip_style = settings.theme.tooltip.clone(); - - let text_style = TerminalElement::make_text_style(font_cache, settings); + }; let selection_color = settings.theme.editor.selection.selection; let match_color = settings.theme.search.match_background; let gutter; let dimensions = { - let line_height = text_style.font_size * settings.terminal_line_height(); + let line_height = text_style.font_size * terminal_settings.line_height.value(); let cell_width = font_cache.em_advance(text_style.font_id, text_style.font_size); gutter = cell_width; diff --git a/crates/terminal_view/src/terminal_view.rs b/crates/terminal_view/src/terminal_view.rs index dfb2334dc554d6dabc1ea7b5ac0d71d6726daf86..0a7a69bf7392aa5dfc28e6175d8a51ae1c0590ee 100644 --- a/crates/terminal_view/src/terminal_view.rs +++ b/crates/terminal_view/src/terminal_view.rs @@ -2,6 +2,7 @@ mod persistence; pub mod terminal_button; pub mod terminal_element; +use crate::{persistence::TERMINAL_DB, terminal_element::TerminalElement}; use context_menu::{ContextMenu, ContextMenuItem}; use dirs::home_dir; use gpui::{ @@ -16,7 +17,6 @@ use gpui::{ }; use project::{LocalWorktree, Project}; use serde::Deserialize; -use settings::{Settings, TerminalBlink, WorkingDirectory}; use smallvec::{smallvec, SmallVec}; use smol::Timer; use std::{ @@ -30,7 +30,7 @@ use terminal::{ index::Point, term::{search::RegexSearch, TermMode}, }, - Event, Terminal, + Event, Terminal, TerminalBlink, WorkingDirectory, }; use util::ResultExt; use workspace::{ @@ -41,7 +41,7 @@ use workspace::{ Pane, ToolbarItemLocation, Workspace, WorkspaceId, }; -use crate::{persistence::TERMINAL_DB, terminal_element::TerminalElement}; +pub use terminal::TerminalSettings; const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500); @@ -63,6 +63,8 @@ actions!( impl_actions!(terminal, [SendText, SendKeystroke]); pub fn init(cx: &mut AppContext) { + terminal::init(cx); + cx.add_action(TerminalView::deploy); register_deserializable_item::(cx); @@ -101,9 +103,9 @@ impl TerminalView { _: &workspace::NewTerminal, cx: &mut ViewContext, ) { - let strategy = cx.global::().terminal_strategy(); - - let working_directory = get_working_directory(workspace, cx, strategy); + let strategy = settings::get::(cx); + let working_directory = + get_working_directory(workspace, cx, strategy.working_directory.clone()); let window_id = cx.window_id(); let terminal = workspace @@ -215,10 +217,7 @@ impl TerminalView { self.terminal.update(cx, |term, cx| { term.try_keystroke( &Keystroke::parse("ctrl-cmd-space").unwrap(), - cx.global::() - .terminal_overrides - .option_as_meta - .unwrap_or(false), + settings::get::(cx).option_as_meta, ) }); } @@ -244,16 +243,7 @@ impl TerminalView { return true; } - let setting = { - let settings = cx.global::(); - settings - .terminal_overrides - .blinking - .clone() - .unwrap_or(TerminalBlink::TerminalControlled) - }; - - match setting { + match settings::get::(cx).blinking { //If the user requested to never blink, don't blink it. TerminalBlink::Off => true, //If the terminal is controlling it, check terminal mode @@ -346,10 +336,7 @@ impl TerminalView { self.terminal.update(cx, |term, cx| { term.try_keystroke( &keystroke, - cx.global::() - .terminal_overrides - .option_as_meta - .unwrap_or(false), + settings::get::(cx).option_as_meta, ); }); } @@ -412,10 +399,7 @@ impl View for TerminalView { self.terminal.update(cx, |term, cx| { term.try_keystroke( &event.keystroke, - cx.global::() - .terminal_overrides - .option_as_meta - .unwrap_or(false), + settings::get::(cx).option_as_meta, ) }) } @@ -617,7 +601,9 @@ impl Item for TerminalView { .flatten() .or_else(|| { cx.read(|cx| { - let strategy = cx.global::().terminal_strategy(); + let strategy = settings::get::(cx) + .working_directory + .clone(); workspace .upgrade(cx) .map(|workspace| { @@ -801,22 +787,18 @@ fn get_path_from_wt(wt: &LocalWorktree) -> Option { #[cfg(test)] mod tests { - use super::*; use gpui::TestAppContext; use project::{Entry, Project, ProjectPath, Worktree}; - use workspace::AppState; - use std::path::Path; + use workspace::AppState; - ///Working directory calculation tests + // Working directory calculation tests - ///No Worktrees in project -> home_dir() + // No Worktrees in project -> home_dir() #[gpui::test] async fn no_worktree(cx: &mut TestAppContext) { - //Setup variables - let (project, workspace) = blank_workspace(cx).await; - //Test + let (project, workspace) = init_test(cx).await; cx.read(|cx| { let workspace = workspace.read(cx); let active_entry = project.read(cx).active_entry(); @@ -832,14 +814,12 @@ mod tests { }); } - ///No active entry, but a worktree, worktree is a file -> home_dir() + // No active entry, but a worktree, worktree is a file -> home_dir() #[gpui::test] async fn no_active_entry_worktree_is_file(cx: &mut TestAppContext) { - //Setup variables + let (project, workspace) = init_test(cx).await; - let (project, workspace) = blank_workspace(cx).await; create_file_wt(project.clone(), "/root.txt", cx).await; - cx.read(|cx| { let workspace = workspace.read(cx); let active_entry = project.read(cx).active_entry(); @@ -855,14 +835,12 @@ mod tests { }); } - //No active entry, but a worktree, worktree is a folder -> worktree_folder + // No active entry, but a worktree, worktree is a folder -> worktree_folder #[gpui::test] async fn no_active_entry_worktree_is_dir(cx: &mut TestAppContext) { - //Setup variables - let (project, workspace) = blank_workspace(cx).await; - let (_wt, _entry) = create_folder_wt(project.clone(), "/root/", cx).await; + let (project, workspace) = init_test(cx).await; - //Test + let (_wt, _entry) = create_folder_wt(project.clone(), "/root/", cx).await; cx.update(|cx| { let workspace = workspace.read(cx); let active_entry = project.read(cx).active_entry(); @@ -877,17 +855,15 @@ mod tests { }); } - //Active entry with a work tree, worktree is a file -> home_dir() + // Active entry with a work tree, worktree is a file -> home_dir() #[gpui::test] async fn active_entry_worktree_is_file(cx: &mut TestAppContext) { - //Setup variables + let (project, workspace) = init_test(cx).await; - let (project, workspace) = blank_workspace(cx).await; let (_wt, _entry) = create_folder_wt(project.clone(), "/root1/", cx).await; let (wt2, entry2) = create_file_wt(project.clone(), "/root2.txt", cx).await; insert_active_entry_for(wt2, entry2, project.clone(), cx); - //Test cx.update(|cx| { let workspace = workspace.read(cx); let active_entry = project.read(cx).active_entry(); @@ -901,16 +877,15 @@ mod tests { }); } - //Active entry, with a worktree, worktree is a folder -> worktree_folder + // Active entry, with a worktree, worktree is a folder -> worktree_folder #[gpui::test] async fn active_entry_worktree_is_dir(cx: &mut TestAppContext) { - //Setup variables - let (project, workspace) = blank_workspace(cx).await; + let (project, workspace) = init_test(cx).await; + let (_wt, _entry) = create_folder_wt(project.clone(), "/root1/", cx).await; let (wt2, entry2) = create_folder_wt(project.clone(), "/root2/", cx).await; insert_active_entry_for(wt2, entry2, project.clone(), cx); - //Test cx.update(|cx| { let workspace = workspace.read(cx); let active_entry = project.read(cx).active_entry(); @@ -924,11 +899,12 @@ mod tests { }); } - ///Creates a worktree with 1 file: /root.txt - pub async fn blank_workspace( + /// Creates a worktree with 1 file: /root.txt + pub async fn init_test( cx: &mut TestAppContext, ) -> (ModelHandle, ViewHandle) { let params = cx.update(AppState::test); + cx.update(|cx| theme::init((), cx)); let project = Project::test(params.fs.clone(), [], cx).await; let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); @@ -936,7 +912,7 @@ mod tests { (project, workspace) } - ///Creates a worktree with 1 folder: /root{suffix}/ + /// Creates a worktree with 1 folder: /root{suffix}/ async fn create_folder_wt( project: ModelHandle, path: impl AsRef, @@ -945,7 +921,7 @@ mod tests { create_wt(project, true, path, cx).await } - ///Creates a worktree with 1 file: /root{suffix}.txt + /// Creates a worktree with 1 file: /root{suffix}.txt async fn create_file_wt( project: ModelHandle, path: impl AsRef, diff --git a/crates/theme/Cargo.toml b/crates/theme/Cargo.toml index c7dc2938ed17d9b051650e1a34b76cc2681822ca..b213cc9c1e2ece98c09791f0a005cf3d2b1cf752 100644 --- a/crates/theme/Cargo.toml +++ b/crates/theme/Cargo.toml @@ -4,6 +4,13 @@ version = "0.1.0" edition = "2021" publish = false +[features] +test-support = [ + "gpui/test-support", + "fs/test-support", + "settings/test-support" +] + [lib] path = "src/theme.rs" doctest = false @@ -11,10 +18,19 @@ doctest = false [dependencies] gpui = { path = "../gpui" } fs = { path = "../fs" } +settings = { path = "../settings" } +util = { path = "../util" } + anyhow.workspace = true indexmap = "1.6.2" parking_lot.workspace = true +schemars.workspace = true serde.workspace = true serde_derive.workspace = true serde_json.workspace = true -toml = "0.5" +toml.workspace = true + +[dev-dependencies] +gpui = { path = "../gpui", features = ["test-support"] } +fs = { path = "../fs", features = ["test-support"] } +settings = { path = "../settings", features = ["test-support"] } diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index 8760ea54ea842776017ef664d94ecf344507c130..eb404cdaad6ad5a8d5e7fa5c572e0ad709e6c4e4 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -1,19 +1,40 @@ mod theme_registry; +mod theme_settings; +pub mod ui; use gpui::{ color::Color, elements::{ContainerStyle, ImageStyle, LabelStyle, Shadow, TooltipStyle}, fonts::{HighlightStyle, TextStyle}, - platform, Border, MouseState, + platform, AppContext, AssetSource, Border, MouseState, }; use serde::{de::DeserializeOwned, Deserialize}; use serde_json::Value; +use settings::SettingsStore; use std::{collections::HashMap, sync::Arc}; use ui::{ButtonStyle, CheckboxStyle, IconStyle, ModalStyle, SvgStyle}; -pub mod ui; - pub use theme_registry::*; +pub use theme_settings::*; + +pub fn current(cx: &AppContext) -> Arc { + settings::get::(cx).theme.clone() +} + +pub fn init(source: impl AssetSource, cx: &mut AppContext) { + cx.set_global(ThemeRegistry::new(source, cx.font_cache().clone())); + settings::register::(cx); + + let mut prev_buffer_font_size = settings::get::(cx).buffer_font_size; + cx.observe_global::(move |cx| { + let buffer_font_size = settings::get::(cx).buffer_font_size; + if buffer_font_size != prev_buffer_font_size { + prev_buffer_font_size = buffer_font_size; + reset_font_size(cx); + } + }) + .detach(); +} #[derive(Deserialize, Default)] pub struct Theme { diff --git a/crates/theme/src/theme_registry.rs b/crates/theme/src/theme_registry.rs index f9f89b7adcf6ac1c251ebc6fe3c360230a03b212..12ccaf3d519c754d9d8fafee79aafe05300c1d82 100644 --- a/crates/theme/src/theme_registry.rs +++ b/crates/theme/src/theme_registry.rs @@ -22,13 +22,26 @@ pub struct ThemeRegistry { impl ThemeRegistry { pub fn new(source: impl AssetSource, font_cache: Arc) -> Arc { - Arc::new(Self { + let this = Arc::new(Self { assets: Box::new(source), themes: Default::default(), theme_data: Default::default(), next_theme_id: Default::default(), font_cache, - }) + }); + + #[cfg(any(test, feature = "test-support"))] + this.themes.lock().insert( + settings::EMPTY_THEME_NAME.to_string(), + gpui::fonts::with_font_cache(this.font_cache.clone(), || { + let mut theme = Theme::default(); + theme.meta.id = this.next_theme_id.fetch_add(1, SeqCst); + theme.meta.name = settings::EMPTY_THEME_NAME.into(); + Arc::new(theme) + }), + ); + + this } pub fn list(&self, staff: bool) -> impl Iterator + '_ { diff --git a/crates/theme/src/theme_settings.rs b/crates/theme/src/theme_settings.rs new file mode 100644 index 0000000000000000000000000000000000000000..f86d3fd8dd38477993f490d1824aab6ee232d646 --- /dev/null +++ b/crates/theme/src/theme_settings.rs @@ -0,0 +1,184 @@ +use crate::{Theme, ThemeRegistry}; +use anyhow::Result; +use gpui::{font_cache::FamilyId, fonts, AppContext}; +use schemars::{ + gen::SchemaGenerator, + schema::{InstanceType, Schema, SchemaObject}, + JsonSchema, +}; +use serde::{Deserialize, Serialize}; +use serde_json::Value; +use settings::SettingsJsonSchemaParams; +use std::sync::Arc; +use util::ResultExt as _; + +const MIN_FONT_SIZE: f32 = 6.0; + +#[derive(Clone)] +pub struct ThemeSettings { + pub buffer_font_family_name: String, + pub buffer_font_features: fonts::Features, + pub buffer_font_family: FamilyId, + pub(crate) buffer_font_size: f32, + pub theme: Arc, +} + +pub struct AdjustedBufferFontSize(pub f32); + +#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)] +pub struct ThemeSettingsContent { + #[serde(default)] + pub buffer_font_family: Option, + #[serde(default)] + pub buffer_font_size: Option, + #[serde(default)] + pub buffer_font_features: Option, + #[serde(default)] + pub theme: Option, +} + +impl ThemeSettings { + pub fn buffer_font_size(&self, cx: &AppContext) -> f32 { + if cx.has_global::() { + cx.global::().0 + } else { + self.buffer_font_size + } + .max(MIN_FONT_SIZE) + } +} + +pub fn adjusted_font_size(size: f32, cx: &AppContext) -> f32 { + if cx.has_global::() { + let buffer_font_size = settings::get::(cx).buffer_font_size; + let delta = cx.global::().0 - buffer_font_size; + size + delta + } else { + size + } + .max(MIN_FONT_SIZE) +} + +pub fn adjust_font_size(cx: &mut AppContext, f: fn(&mut f32)) { + if !cx.has_global::() { + let buffer_font_size = settings::get::(cx).buffer_font_size; + cx.set_global(AdjustedBufferFontSize(buffer_font_size)); + } + + cx.update_global::(|delta, cx| { + f(&mut delta.0); + delta.0 = delta + .0 + .max(MIN_FONT_SIZE - settings::get::(cx).buffer_font_size); + }); + cx.refresh_windows(); +} + +pub fn reset_font_size(cx: &mut AppContext) { + if cx.has_global::() { + cx.remove_global::(); + cx.refresh_windows(); + } +} + +impl settings::Setting for ThemeSettings { + const KEY: Option<&'static str> = None; + + type FileContent = ThemeSettingsContent; + + fn load( + defaults: &Self::FileContent, + user_values: &[&Self::FileContent], + cx: &AppContext, + ) -> Result { + let buffer_font_features = defaults.buffer_font_features.clone().unwrap(); + let themes = cx.global::>(); + + let mut this = Self { + buffer_font_family: cx + .font_cache() + .load_family( + &[defaults.buffer_font_family.as_ref().unwrap()], + &buffer_font_features, + ) + .unwrap(), + buffer_font_family_name: defaults.buffer_font_family.clone().unwrap(), + buffer_font_features, + buffer_font_size: defaults.buffer_font_size.unwrap(), + theme: themes.get(defaults.theme.as_ref().unwrap()).unwrap(), + }; + + for value in user_values.into_iter().copied().cloned() { + let font_cache = cx.font_cache(); + let mut family_changed = false; + if let Some(value) = value.buffer_font_family { + this.buffer_font_family_name = value; + family_changed = true; + } + if let Some(value) = value.buffer_font_features { + this.buffer_font_features = value; + family_changed = true; + } + if family_changed { + if let Some(id) = font_cache + .load_family(&[&this.buffer_font_family_name], &this.buffer_font_features) + .log_err() + { + this.buffer_font_family = id; + } + } + + if let Some(value) = &value.theme { + if let Some(theme) = themes.get(value).log_err() { + this.theme = theme; + } + } + + merge(&mut this.buffer_font_size, value.buffer_font_size); + } + + Ok(this) + } + + fn json_schema( + generator: &mut SchemaGenerator, + params: &SettingsJsonSchemaParams, + cx: &AppContext, + ) -> schemars::schema::RootSchema { + let mut root_schema = generator.root_schema_for::(); + let theme_names = cx + .global::>() + .list(params.staff_mode) + .map(|theme| Value::String(theme.name.clone())) + .collect(); + + let theme_name_schema = SchemaObject { + instance_type: Some(InstanceType::String.into()), + enum_values: Some(theme_names), + ..Default::default() + }; + + root_schema + .definitions + .extend([("ThemeName".into(), theme_name_schema.into())]); + + root_schema + .schema + .object + .as_mut() + .unwrap() + .properties + .extend([( + "theme".to_owned(), + Schema::new_ref("#/definitions/ThemeName".into()), + )]); + + root_schema + } +} + +fn merge(target: &mut T, value: Option) { + if let Some(value) = value { + *target = value; + } +} diff --git a/crates/theme_selector/Cargo.toml b/crates/theme_selector/Cargo.toml index a404e43f2927e274e6efe76f4079e5e3513728e1..ac3a85d89a48a777cd852366b588bb8d05fa45a5 100644 --- a/crates/theme_selector/Cargo.toml +++ b/crates/theme_selector/Cargo.toml @@ -11,6 +11,7 @@ doctest = false [dependencies] editor = { path = "../editor" } fuzzy = { path = "../fuzzy" } +fs = { path = "../fs" } gpui = { path = "../gpui" } picker = { path = "../picker" } theme = { path = "../theme" } diff --git a/crates/theme_selector/src/theme_selector.rs b/crates/theme_selector/src/theme_selector.rs index 21332114e22bf81bf9147568c471bc0e3d77316b..a6c84d1d91a6b149b3ed4950c2127f194040fc4a 100644 --- a/crates/theme_selector/src/theme_selector.rs +++ b/crates/theme_selector/src/theme_selector.rs @@ -1,10 +1,11 @@ +use fs::Fs; use fuzzy::{match_strings, StringMatch, StringMatchCandidate}; use gpui::{actions, elements::*, AnyElement, AppContext, Element, MouseState, ViewContext}; use picker::{Picker, PickerDelegate, PickerEvent}; -use settings::{settings_file::SettingsFile, Settings}; +use settings::{update_settings_file, SettingsStore}; use staff_mode::StaffMode; use std::sync::Arc; -use theme::{Theme, ThemeMeta, ThemeRegistry}; +use theme::{Theme, ThemeMeta, ThemeRegistry, ThemeSettings}; use util::ResultExt; use workspace::Workspace; @@ -17,16 +18,17 @@ pub fn init(cx: &mut AppContext) { pub fn toggle(workspace: &mut Workspace, _: &Toggle, cx: &mut ViewContext) { workspace.toggle_modal(cx, |workspace, cx| { - let themes = workspace.app_state().themes.clone(); - cx.add_view(|cx| ThemeSelector::new(ThemeSelectorDelegate::new(themes, cx), cx)) + let fs = workspace.app_state().fs.clone(); + cx.add_view(|cx| ThemeSelector::new(ThemeSelectorDelegate::new(fs, cx), cx)) }); } #[cfg(debug_assertions)] -pub fn reload(themes: Arc, cx: &mut AppContext) { - let current_theme_name = cx.global::().theme.meta.name.clone(); - themes.clear(); - match themes.get(¤t_theme_name) { +pub fn reload(cx: &mut AppContext) { + let current_theme_name = theme::current(cx).meta.name.clone(); + let registry = cx.global::>(); + registry.clear(); + match registry.get(¤t_theme_name) { Ok(theme) => { ThemeSelectorDelegate::set_theme(theme, cx); log::info!("reloaded theme {}", current_theme_name); @@ -40,7 +42,7 @@ pub fn reload(themes: Arc, cx: &mut AppContext) { pub type ThemeSelector = Picker; pub struct ThemeSelectorDelegate { - registry: Arc, + fs: Arc, theme_data: Vec, matches: Vec, original_theme: Arc, @@ -49,14 +51,12 @@ pub struct ThemeSelectorDelegate { } impl ThemeSelectorDelegate { - fn new(registry: Arc, cx: &mut ViewContext) -> Self { - let settings = cx.global::(); + fn new(fs: Arc, cx: &mut ViewContext) -> Self { + let original_theme = theme::current(cx).clone(); - let original_theme = settings.theme.clone(); - - let mut theme_names = registry - .list(**cx.default_global::()) - .collect::>(); + let staff_mode = **cx.default_global::(); + let registry = cx.global::>(); + let mut theme_names = registry.list(staff_mode).collect::>(); theme_names.sort_unstable_by(|a, b| a.is_light.cmp(&b.is_light).then(a.name.cmp(&b.name))); let matches = theme_names .iter() @@ -68,7 +68,7 @@ impl ThemeSelectorDelegate { }) .collect(); let mut this = Self { - registry, + fs, theme_data: theme_names, matches, original_theme: original_theme.clone(), @@ -81,7 +81,8 @@ impl ThemeSelectorDelegate { fn show_selected_theme(&mut self, cx: &mut ViewContext) { if let Some(mat) = self.matches.get(self.selected_index) { - match self.registry.get(&mat.string) { + let registry = cx.global::>(); + match registry.get(&mat.string) { Ok(theme) => { Self::set_theme(theme, cx); } @@ -101,8 +102,10 @@ impl ThemeSelectorDelegate { } fn set_theme(theme: Arc, cx: &mut AppContext) { - cx.update_global::(|settings, cx| { - settings.theme = theme; + cx.update_global::(|store, cx| { + let mut theme_settings = store.get::(None).clone(); + theme_settings.theme = theme; + store.override_global(theme_settings); cx.refresh_windows(); }); } @@ -120,9 +123,9 @@ impl PickerDelegate for ThemeSelectorDelegate { fn confirm(&mut self, cx: &mut ViewContext) { self.selection_completed = true; - let theme_name = cx.global::().theme.meta.name.clone(); - SettingsFile::update(cx, |settings_content| { - settings_content.theme = Some(theme_name); + let theme_name = theme::current(cx).meta.name.clone(); + update_settings_file::(self.fs.clone(), cx, |settings| { + settings.theme = Some(theme_name); }); cx.emit(PickerEvent::Dismiss); @@ -204,11 +207,10 @@ impl PickerDelegate for ThemeSelectorDelegate { selected: bool, cx: &AppContext, ) -> AnyElement> { - let settings = cx.global::(); - let theme = &settings.theme; - let theme_match = &self.matches[ix]; + let theme = theme::current(cx); let style = theme.picker.item.style_for(mouse_state, selected); + let theme_match = &self.matches[ix]; Label::new(theme_match.string.clone(), style.label.clone()) .with_highlights(theme_match.positions.clone()) .contained() diff --git a/crates/theme_testbench/src/theme_testbench.rs b/crates/theme_testbench/src/theme_testbench.rs index c18a580d07294ad06bfe8cecf7b367fb27079f8d..258249b59932c8c087d5e27a85cb75dea7dbf2f2 100644 --- a/crates/theme_testbench/src/theme_testbench.rs +++ b/crates/theme_testbench/src/theme_testbench.rs @@ -10,8 +10,7 @@ use gpui::{ WeakViewHandle, }; use project::Project; -use settings::Settings; -use theme::{ColorScheme, Layer, Style, StyleSet}; +use theme::{ColorScheme, Layer, Style, StyleSet, ThemeSettings}; use workspace::{item::Item, register_deserializable_item, Pane, Workspace}; actions!(theme, [DeployThemeTestbench]); @@ -220,10 +219,10 @@ impl ThemeTestbench { } fn render_label(text: String, style: &Style, cx: &mut ViewContext) -> Label { - let settings = cx.global::(); + let settings = settings::get::(cx); let font_cache = cx.font_cache(); let family_id = settings.buffer_font_family; - let font_size = settings.buffer_font_size; + let font_size = settings.buffer_font_size(cx); let font_id = font_cache .select_font(family_id, &Default::default()) .unwrap(); @@ -252,7 +251,7 @@ impl View for ThemeTestbench { } fn render(&mut self, cx: &mut gpui::ViewContext) -> AnyElement { - let color_scheme = &cx.global::().theme.clone().color_scheme; + let color_scheme = &theme::current(cx).clone().color_scheme; Flex::row() .with_child( diff --git a/crates/util/src/util.rs b/crates/util/src/util.rs index 63b2d5f279045fb3cfa170eaee36c11cce6d4278..9d787e1389a9742c44e505a96ddf44ee71d04671 100644 --- a/crates/util/src/util.rs +++ b/crates/util/src/util.rs @@ -95,6 +95,27 @@ pub fn merge_json_value_into(source: serde_json::Value, target: &mut serde_json: } } +pub fn merge_non_null_json_value_into(source: serde_json::Value, target: &mut serde_json::Value) { + use serde_json::Value; + if let Value::Object(source_object) = source { + let target_object = if let Value::Object(target) = target { + target + } else { + *target = Value::Object(Default::default()); + target.as_object_mut().unwrap() + }; + for (key, value) in source_object { + if let Some(target) = target_object.get_mut(&key) { + merge_non_null_json_value_into(value, target); + } else if !value.is_null() { + target_object.insert(key.clone(), value); + } + } + } else if !source.is_null() { + *target = source + } +} + pub trait ResultExt { type Ok; diff --git a/crates/vim/Cargo.toml b/crates/vim/Cargo.toml index 5f7cf0a5a33c534bfeeaddb74dd5dc0d8b2377df..c34a5b469b40e73cb13bbf84803576c6ba48b643 100644 --- a/crates/vim/Cargo.toml +++ b/crates/vim/Cargo.toml @@ -12,6 +12,7 @@ doctest = false neovim = ["nvim-rs", "async-compat", "async-trait", "tokio"] [dependencies] +anyhow.workspace = true serde.workspace = true serde_derive.workspace = true itertools = "0.10" diff --git a/crates/vim/src/test/vim_test_context.rs b/crates/vim/src/test/vim_test_context.rs index 69227a0e45e2cbb8c0d67b5bb2e4de9634cb7a5b..531fbf0bba110847fa4474fbb8b8bde025865a98 100644 --- a/crates/vim/src/test/vim_test_context.rs +++ b/crates/vim/src/test/vim_test_context.rs @@ -17,14 +17,17 @@ pub struct VimTestContext<'a> { impl<'a> VimTestContext<'a> { pub async fn new(cx: &'a mut gpui::TestAppContext, enabled: bool) -> VimTestContext<'a> { let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await; + cx.update(|cx| { - cx.update_global(|settings: &mut Settings, _| { - settings.vim_mode = enabled; - }); search::init(cx); crate::init(cx); + }); - settings::KeymapFileContent::load("keymaps/vim.json", cx).unwrap(); + cx.update(|cx| { + cx.update_global(|store: &mut SettingsStore, cx| { + store.update_user_settings::(cx, |s| *s = Some(enabled)); + }); + settings::KeymapFileContent::load_asset("keymaps/vim.json", cx).unwrap(); }); // Setup search toolbars and keypress hook @@ -52,16 +55,16 @@ impl<'a> VimTestContext<'a> { pub fn enable_vim(&mut self) { self.cx.update(|cx| { - cx.update_global(|settings: &mut Settings, _| { - settings.vim_mode = true; + cx.update_global(|store: &mut SettingsStore, cx| { + store.update_user_settings::(cx, |s| *s = Some(true)); }); }) } pub fn disable_vim(&mut self) { self.cx.update(|cx| { - cx.update_global(|settings: &mut Settings, _| { - settings.vim_mode = false; + cx.update_global(|store: &mut SettingsStore, cx| { + store.update_user_settings::(cx, |s| *s = Some(false)); }); }) } diff --git a/crates/vim/src/vim.rs b/crates/vim/src/vim.rs index cc686f851f21c7019c76a43840cea70a8d6f32de..d10ec5e824b7a720b8136a4b3d694d3c42d0b62e 100644 --- a/crates/vim/src/vim.rs +++ b/crates/vim/src/vim.rs @@ -10,8 +10,7 @@ mod state; mod utils; mod visual; -use std::sync::Arc; - +use anyhow::Result; use collections::CommandPaletteFilter; use editor::{Bias, Cancel, Editor, EditorMode, Event}; use gpui::{ @@ -22,11 +21,14 @@ use language::CursorShape; use motion::Motion; use normal::normal_replace; use serde::Deserialize; -use settings::Settings; +use settings::{Setting, SettingsStore}; use state::{Mode, Operator, VimState}; +use std::sync::Arc; use visual::visual_replace; use workspace::{self, Workspace}; +struct VimModeSetting(bool); + #[derive(Clone, Deserialize, PartialEq)] pub struct SwitchMode(pub Mode); @@ -40,6 +42,8 @@ actions!(vim, [Tab, Enter]); impl_actions!(vim, [Number, SwitchMode, PushOperator]); pub fn init(cx: &mut AppContext) { + settings::register::(cx); + editor_events::init(cx); normal::init(cx); visual::init(cx); @@ -91,11 +95,11 @@ pub fn init(cx: &mut AppContext) { filter.filtered_namespaces.insert("vim"); }); cx.update_default_global(|vim: &mut Vim, cx: &mut AppContext| { - vim.set_enabled(cx.global::().vim_mode, cx) + vim.set_enabled(settings::get::(cx).0, cx) }); - cx.observe_global::(|cx| { + cx.observe_global::(|cx| { cx.update_default_global(|vim: &mut Vim, cx: &mut AppContext| { - vim.set_enabled(cx.global::().vim_mode, cx) + vim.set_enabled(settings::get::(cx).0, cx) }); }) .detach(); @@ -330,6 +334,22 @@ impl Vim { } } +impl Setting for VimModeSetting { + const KEY: Option<&'static str> = Some("vim_mode"); + + type FileContent = Option; + + fn load( + default_value: &Self::FileContent, + user_values: &[&Self::FileContent], + _: &AppContext, + ) -> Result { + Ok(Self(user_values.iter().rev().find_map(|v| **v).unwrap_or( + default_value.ok_or_else(Self::missing_default)?, + ))) + } +} + fn local_selections_changed(newest_empty: bool, cx: &mut WindowContext) { Vim::update(cx, |vim, cx| { if vim.enabled && vim.state.mode == Mode::Normal && !newest_empty { diff --git a/crates/welcome/Cargo.toml b/crates/welcome/Cargo.toml index d35cced642322c09bda9a933329434b71c03dfe4..65f5151584bea831d1c4744c150b994ea86fa72e 100644 --- a/crates/welcome/Cargo.toml +++ b/crates/welcome/Cargo.toml @@ -11,9 +11,9 @@ path = "src/welcome.rs" test-support = [] [dependencies] -anyhow.workspace = true -log.workspace = true +client = { path = "../client" } editor = { path = "../editor" } +fs = { path = "../fs" } fuzzy = { path = "../fuzzy" } gpui = { path = "../gpui" } db = { path = "../db" } @@ -25,3 +25,8 @@ theme_selector = { path = "../theme_selector" } util = { path = "../util" } picker = { path = "../picker" } workspace = { path = "../workspace" } + +anyhow.workspace = true +log.workspace = true +schemars.workspace = true +serde.workspace = true diff --git a/crates/welcome/src/base_keymap_picker.rs b/crates/welcome/src/base_keymap_picker.rs index 260c279e181a8677f08124024ac3f7db537e8eed..e44b391d84b3f46d96e84c8e97589b6ecbde699e 100644 --- a/crates/welcome/src/base_keymap_picker.rs +++ b/crates/welcome/src/base_keymap_picker.rs @@ -1,5 +1,4 @@ -use std::sync::Arc; - +use super::base_keymap_setting::BaseKeymap; use fuzzy::{match_strings, StringMatch, StringMatchCandidate}; use gpui::{ actions, @@ -7,7 +6,9 @@ use gpui::{ AppContext, Task, ViewContext, }; use picker::{Picker, PickerDelegate, PickerEvent}; -use settings::{settings_file::SettingsFile, BaseKeymap, Settings}; +use project::Fs; +use settings::update_settings_file; +use std::sync::Arc; use util::ResultExt; use workspace::Workspace; @@ -23,8 +24,9 @@ pub fn toggle( _: &ToggleBaseKeymapSelector, cx: &mut ViewContext, ) { - workspace.toggle_modal(cx, |_, cx| { - cx.add_view(|cx| BaseKeymapSelector::new(BaseKeymapSelectorDelegate::new(cx), cx)) + workspace.toggle_modal(cx, |workspace, cx| { + let fs = workspace.app_state().fs.clone(); + cx.add_view(|cx| BaseKeymapSelector::new(BaseKeymapSelectorDelegate::new(fs, cx), cx)) }); } @@ -33,18 +35,20 @@ pub type BaseKeymapSelector = Picker; pub struct BaseKeymapSelectorDelegate { matches: Vec, selected_index: usize, + fs: Arc, } impl BaseKeymapSelectorDelegate { - fn new(cx: &mut ViewContext) -> Self { - let base = cx.global::().base_keymap; + fn new(fs: Arc, cx: &mut ViewContext) -> Self { + let base = settings::get::(cx); let selected_index = BaseKeymap::OPTIONS .iter() - .position(|(_, value)| *value == base) + .position(|(_, value)| value == base) .unwrap_or(0); Self { matches: Vec::new(), selected_index, + fs, } } } @@ -119,7 +123,9 @@ impl PickerDelegate for BaseKeymapSelectorDelegate { fn confirm(&mut self, cx: &mut ViewContext) { if let Some(selection) = self.matches.get(self.selected_index) { let base_keymap = BaseKeymap::from_names(&selection.string); - SettingsFile::update(cx, move |settings| settings.base_keymap = Some(base_keymap)); + update_settings_file::(self.fs.clone(), cx, move |setting| { + *setting = Some(base_keymap) + }); } cx.emit(PickerEvent::Dismiss); } @@ -133,7 +139,7 @@ impl PickerDelegate for BaseKeymapSelectorDelegate { selected: bool, cx: &gpui::AppContext, ) -> gpui::AnyElement> { - let theme = &cx.global::().theme; + let theme = &theme::current(cx); let keymap_match = &self.matches[ix]; let style = theme.picker.item.style_for(mouse_state, selected); diff --git a/crates/welcome/src/base_keymap_setting.rs b/crates/welcome/src/base_keymap_setting.rs new file mode 100644 index 0000000000000000000000000000000000000000..c5b6171f9b4fb809670d7e17f800f5d23699da61 --- /dev/null +++ b/crates/welcome/src/base_keymap_setting.rs @@ -0,0 +1,65 @@ +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use settings::Setting; + +#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq, Default)] +pub enum BaseKeymap { + #[default] + VSCode, + JetBrains, + SublimeText, + Atom, + TextMate, +} + +impl BaseKeymap { + pub const OPTIONS: [(&'static str, Self); 5] = [ + ("VSCode (Default)", Self::VSCode), + ("Atom", Self::Atom), + ("JetBrains", Self::JetBrains), + ("Sublime Text", Self::SublimeText), + ("TextMate", Self::TextMate), + ]; + + pub fn asset_path(&self) -> Option<&'static str> { + match self { + BaseKeymap::JetBrains => Some("keymaps/jetbrains.json"), + BaseKeymap::SublimeText => Some("keymaps/sublime_text.json"), + BaseKeymap::Atom => Some("keymaps/atom.json"), + BaseKeymap::TextMate => Some("keymaps/textmate.json"), + BaseKeymap::VSCode => None, + } + } + + pub fn names() -> impl Iterator { + Self::OPTIONS.iter().map(|(name, _)| *name) + } + + pub fn from_names(option: &str) -> BaseKeymap { + Self::OPTIONS + .iter() + .copied() + .find_map(|(name, value)| (name == option).then(|| value)) + .unwrap_or_default() + } +} + +impl Setting for BaseKeymap { + const KEY: Option<&'static str> = Some("base_keymap"); + + type FileContent = Option; + + fn load( + default_value: &Self::FileContent, + user_values: &[&Self::FileContent], + _: &gpui::AppContext, + ) -> anyhow::Result + where + Self: Sized, + { + Ok(user_values + .first() + .and_then(|v| **v) + .unwrap_or(default_value.unwrap())) + } +} diff --git a/crates/welcome/src/welcome.rs b/crates/welcome/src/welcome.rs index a3d91adc910f658ab2bfd3b9af063ae4bc81142b..000c2fd8d97fbddadeda4ca7cb884f697a083e0b 100644 --- a/crates/welcome/src/welcome.rs +++ b/crates/welcome/src/welcome.rs @@ -1,24 +1,27 @@ mod base_keymap_picker; +mod base_keymap_setting; -use std::{borrow::Cow, sync::Arc}; - +use crate::base_keymap_picker::ToggleBaseKeymapSelector; +use client::TelemetrySettings; use db::kvp::KEY_VALUE_STORE; use gpui::{ elements::{Flex, Label, ParentElement}, AnyElement, AppContext, Element, Entity, Subscription, View, ViewContext, WeakViewHandle, }; -use settings::{settings_file::SettingsFile, Settings}; - +use settings::{update_settings_file, SettingsStore}; +use std::{borrow::Cow, sync::Arc}; use workspace::{ item::Item, open_new, sidebar::SidebarSide, AppState, PaneBackdrop, Welcome, Workspace, WorkspaceId, }; -use crate::base_keymap_picker::ToggleBaseKeymapSelector; +pub use base_keymap_setting::BaseKeymap; pub const FIRST_OPEN: &str = "first_open"; pub fn init(cx: &mut AppContext) { + settings::register::(cx); + cx.add_action(|workspace: &mut Workspace, _: &Welcome, cx| { let welcome_page = cx.add_view(|cx| WelcomePage::new(workspace, cx)); workspace.add_item(Box::new(welcome_page), cx) @@ -58,15 +61,10 @@ impl View for WelcomePage { fn render(&mut self, cx: &mut gpui::ViewContext) -> AnyElement { let self_handle = cx.handle(); - let settings = cx.global::(); - let theme = settings.theme.clone(); - + let theme = theme::current(cx); let width = theme.welcome.page_width; - let (diagnostics, metrics) = { - let telemetry = settings.telemetry(); - (telemetry.diagnostics(), telemetry.metrics()) - }; + let telemetry_settings = *settings::get::(cx); enum Metrics {} enum Diagnostics {} @@ -166,13 +164,18 @@ impl View for WelcomePage { .with_style(theme.welcome.usage_note.container), ), &theme.welcome.checkbox, - metrics, + telemetry_settings.metrics, 0, cx, - |_, checked, cx| { - SettingsFile::update(cx, move |file| { - file.telemetry.set_metrics(checked) - }) + |this, checked, cx| { + if let Some(workspace) = this.workspace.upgrade(cx) { + let fs = workspace.read(cx).app_state().fs.clone(); + update_settings_file::( + fs, + cx, + move |setting| setting.metrics = Some(checked), + ) + } }, ) .contained() @@ -182,13 +185,18 @@ impl View for WelcomePage { theme::ui::checkbox::( "Send crash reports", &theme.welcome.checkbox, - diagnostics, + telemetry_settings.diagnostics, 0, cx, - |_, checked, cx| { - SettingsFile::update(cx, move |file| { - file.telemetry.set_diagnostics(checked) - }) + |this, checked, cx| { + if let Some(workspace) = this.workspace.upgrade(cx) { + let fs = workspace.read(cx).app_state().fs.clone(); + update_settings_file::( + fs, + cx, + move |setting| setting.diagnostics = Some(checked), + ) + } }, ) .contained() @@ -214,7 +222,7 @@ impl WelcomePage { pub fn new(workspace: &Workspace, cx: &mut ViewContext) -> Self { WelcomePage { workspace: workspace.weak_handle(), - _settings_subscription: cx.observe_global::(move |_, cx| cx.notify()), + _settings_subscription: cx.observe_global::(move |_, cx| cx.notify()), } } } @@ -250,7 +258,7 @@ impl Item for WelcomePage { ) -> Option { Some(WelcomePage { workspace: self.workspace.clone(), - _settings_subscription: cx.observe_global::(move |_, cx| cx.notify()), + _settings_subscription: cx.observe_global::(move |_, cx| cx.notify()), }) } } diff --git a/crates/workspace/Cargo.toml b/crates/workspace/Cargo.toml index 177dc0a292f387fefee9aa5b68c1d0e6e07ac8c3..26797e8d6c3a1a3635936e481229e4705d640270 100644 --- a/crates/workspace/Cargo.toml +++ b/crates/workspace/Cargo.toml @@ -45,6 +45,7 @@ lazy_static.workspace = true log.workspace = true parking_lot.workspace = true postage.workspace = true +schemars.workspace = true serde.workspace = true serde_derive.workspace = true serde_json.workspace = true diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index a1fce13df452623c2bf4cf1a66887bf74e21d403..d1ec80de9598c8caa08c866c7b751ff987749419 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -1,5 +1,9 @@ mod toggle_dock_button; +use crate::{ + sidebar::SidebarSide, BackgroundActions, DockAnchor, ItemHandle, Pane, Workspace, + WorkspaceSettings, +}; use collections::HashMap; use gpui::{ actions, @@ -8,10 +12,7 @@ use gpui::{ platform::{CursorStyle, MouseButton}, AnyElement, AppContext, Border, Element, SizeConstraint, ViewContext, ViewHandle, }; -use settings::{DockAnchor, Settings}; use theme::Theme; - -use crate::{sidebar::SidebarSide, BackgroundActions, ItemHandle, Pane, Workspace}; pub use toggle_dock_button::ToggleDockButton; actions!( @@ -171,7 +172,8 @@ impl Dock { background_actions: BackgroundActions, cx: &mut ViewContext, ) -> Self { - let position = DockPosition::Hidden(cx.global::().default_dock_anchor); + let position = + DockPosition::Hidden(settings::get::(cx).default_dock_anchor); let workspace = cx.weak_handle(); let pane = cx.add_view(|cx| Pane::new(workspace, Some(position.anchor()), background_actions, cx)); @@ -405,8 +407,6 @@ mod tests { use gpui::{AppContext, BorrowWindowContext, TestAppContext, ViewContext, WindowContext}; use project::{FakeFs, Project}; - use settings::Settings; - use theme::ThemeRegistry; use super::*; use crate::{ @@ -417,6 +417,7 @@ mod tests { }, register_deserializable_item, sidebar::Sidebar, + tests::init_test, AppState, ItemHandle, Workspace, }; @@ -429,8 +430,7 @@ mod tests { #[gpui::test] async fn test_dock_workspace_infinite_loop(cx: &mut TestAppContext) { - cx.foreground().forbid_parking(); - Settings::test_async(cx); + init_test(cx); cx.update(|cx| { register_deserializable_item::(cx); @@ -466,7 +466,6 @@ mod tests { project.clone(), Arc::new(AppState { languages: project.read(cx).languages().clone(), - themes: ThemeRegistry::new((), cx.font_cache().clone()), client: project.read(cx).client(), user_store: project.read(cx).user_store(), fs: project.read(cx).fs().clone(), @@ -602,7 +601,7 @@ mod tests { impl<'a> DockTestContext<'a> { pub async fn new(cx: &'a mut TestAppContext) -> DockTestContext<'a> { - Settings::test_async(cx); + init_test(cx); let fs = FakeFs::new(cx.background()); cx.update(|cx| init(cx)); @@ -613,7 +612,6 @@ mod tests { project.clone(), Arc::new(AppState { languages: project.read(cx).languages().clone(), - themes: ThemeRegistry::new((), cx.font_cache().clone()), client: project.read(cx).client(), user_store: project.read(cx).user_store(), fs: project.read(cx).fs().clone(), diff --git a/crates/workspace/src/dock/toggle_dock_button.rs b/crates/workspace/src/dock/toggle_dock_button.rs index 1fda55b7834967207a0f4463e548c8ad219bf84e..9ab7a8996bc18868c1f1f0f06847aebc06183f56 100644 --- a/crates/workspace/src/dock/toggle_dock_button.rs +++ b/crates/workspace/src/dock/toggle_dock_button.rs @@ -6,7 +6,6 @@ use gpui::{ platform::MouseButton, AnyElement, Element, Entity, View, ViewContext, ViewHandle, WeakViewHandle, }; -use settings::Settings; pub struct ToggleDockButton { workspace: WeakViewHandle, @@ -43,7 +42,7 @@ impl View for ToggleDockButton { let dock_position = workspace.read(cx).dock.position; let dock_pane = workspace.read(cx).dock_pane().clone(); - let theme = cx.global::().theme.clone(); + let theme = theme::current(cx).clone(); let button = MouseEventHandler::::new(0, cx, { let theme = theme.clone(); diff --git a/crates/workspace/src/item.rs b/crates/workspace/src/item.rs index 43c4544611e9f174db5d8d2350501020eaa6d84e..16905849a9d5e923f3574b476cb949c4bc0312e1 100644 --- a/crates/workspace/src/item.rs +++ b/crates/workspace/src/item.rs @@ -3,6 +3,7 @@ use crate::{ FollowableItemBuilders, ItemNavHistory, Pane, ToolbarItemLocation, ViewId, Workspace, WorkspaceId, }; +use crate::{AutosaveSetting, WorkspaceSettings}; use anyhow::Result; use client::{proto, Client}; use gpui::{ @@ -10,7 +11,6 @@ use gpui::{ ViewContext, ViewHandle, WeakViewHandle, WindowContext, }; use project::{Project, ProjectEntryId, ProjectPath}; -use settings::{Autosave, Settings}; use smallvec::SmallVec; use std::{ any::{Any, TypeId}, @@ -450,8 +450,11 @@ impl ItemHandle for ViewHandle { } ItemEvent::Edit => { - if let Autosave::AfterDelay { milliseconds } = - cx.global::().autosave + let settings = settings::get::(cx); + let debounce_delay = settings.git.gutter_debounce; + + if let AutosaveSetting::AfterDelay { milliseconds } = + settings.autosave { let delay = Duration::from_millis(milliseconds); let item = item.clone(); @@ -460,9 +463,6 @@ impl ItemHandle for ViewHandle { }); } - let settings = cx.global::(); - let debounce_delay = settings.git_overrides.gutter_debounce; - let item = item.clone(); if let Some(delay) = debounce_delay { @@ -500,7 +500,10 @@ impl ItemHandle for ViewHandle { })); cx.observe_focus(self, move |workspace, item, focused, cx| { - if !focused && cx.global::().autosave == Autosave::OnFocusChange { + if !focused + && settings::get::(cx).autosave + == AutosaveSetting::OnFocusChange + { Pane::autosave_item(&item, workspace.project.clone(), cx) .detach_and_log_err(cx); } diff --git a/crates/workspace/src/notifications.rs b/crates/workspace/src/notifications.rs index 7881603bbc6c47bc97edc931633d957965949181..21b3be09d06ea6781e70dd12fa91a1c2ace2e152 100644 --- a/crates/workspace/src/notifications.rs +++ b/crates/workspace/src/notifications.rs @@ -149,6 +149,8 @@ impl Workspace { } pub mod simple_message_notification { + use super::Notification; + use crate::Workspace; use gpui::{ actions, elements::{Flex, MouseEventHandler, Padding, ParentElement, Svg, Text}, @@ -158,13 +160,8 @@ pub mod simple_message_notification { }; use menu::Cancel; use serde::Deserialize; - use settings::Settings; use std::{borrow::Cow, sync::Arc}; - use crate::Workspace; - - use super::Notification; - actions!(message_notifications, [CancelMessageNotification]); #[derive(Clone, Default, Deserialize, PartialEq)] @@ -240,7 +237,7 @@ pub mod simple_message_notification { } fn render(&mut self, cx: &mut gpui::ViewContext) -> gpui::AnyElement { - let theme = cx.global::().theme.clone(); + let theme = theme::current(cx).clone(); let theme = &theme.simple_message_notification; enum MessageNotificationTag {} diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 96320a4baf5c5460030e73efd8276f26c7c6f804..200f83700b6c899d793d551f01dcf830cb9f6235 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -5,7 +5,8 @@ use crate::{ dock::{icon_for_dock_anchor, AnchorDockBottom, AnchorDockRight, Dock, ExpandDock}, item::WeakItemHandle, toolbar::Toolbar, - Item, NewFile, NewSearch, NewTerminal, Workspace, + AutosaveSetting, DockAnchor, Item, NewFile, NewSearch, NewTerminal, Workspace, + WorkspaceSettings, }; use anyhow::{anyhow, Result}; use collections::{HashMap, HashSet, VecDeque}; @@ -29,7 +30,6 @@ use gpui::{ }; use project::{Project, ProjectEntryId, ProjectPath}; use serde::Deserialize; -use settings::{Autosave, DockAnchor, Settings}; use std::{any::Any, cell::RefCell, cmp, mem, path::Path, rc::Rc}; use theme::Theme; use util::ResultExt; @@ -1024,8 +1024,8 @@ impl Pane { } else if is_dirty && (can_save || is_singleton) { let will_autosave = cx.read(|cx| { matches!( - cx.global::().autosave, - Autosave::OnFocusChange | Autosave::OnWindowChange + settings::get::(cx).autosave, + AutosaveSetting::OnFocusChange | AutosaveSetting::OnWindowChange ) && Self::can_autosave_item(&*item, cx) }); let should_save = if should_prompt_for_save && !will_autosave { @@ -1296,7 +1296,7 @@ impl Pane { } fn render_tabs(&mut self, cx: &mut ViewContext) -> impl Element { - let theme = cx.global::().theme.clone(); + let theme = theme::current(cx).clone(); let pane = cx.handle().downgrade(); let autoscroll = if mem::take(&mut self.autoscroll) { @@ -1327,7 +1327,7 @@ impl Pane { let pane = pane.clone(); let detail = detail.clone(); - let theme = cx.global::().theme.clone(); + let theme = theme::current(cx).clone(); let mut tooltip_theme = theme.tooltip.clone(); tooltip_theme.max_text_width = None; let tab_tooltip_text = item.tab_tooltip_text(cx).map(|a| a.to_string()); @@ -1405,7 +1405,7 @@ impl Pane { pane: pane.clone(), }, { - let theme = cx.global::().theme.clone(); + let theme = theme::current(cx).clone(); let detail = detail.clone(); move |dragged_item: &DraggedItem, cx: &mut ViewContext| { @@ -1698,7 +1698,7 @@ impl View for Pane { if let Some(active_item) = self.active_item() { Flex::column() .with_child({ - let theme = cx.global::().theme.clone(); + let theme = theme::current(cx).clone(); let mut stack = Stack::new(); @@ -1764,7 +1764,7 @@ impl View for Pane { .into_any() } else { enum EmptyPane {} - let theme = cx.global::().theme.clone(); + let theme = theme::current(cx).clone(); dragged_item_receiver::(0, 0, false, None, cx, |_, cx| { self.render_blank_pane(&theme, cx) @@ -1861,7 +1861,7 @@ fn render_tab_bar_button)>( Stack::new() .with_child( MouseEventHandler::::new(index, cx, |mouse_state, cx| { - let theme = &cx.global::().theme.workspace.tab_bar; + let theme = &theme::current(cx).workspace.tab_bar; let style = theme.pane_button.style_for(mouse_state, false); Svg::new(icon) .with_color(style.color) @@ -2023,7 +2023,7 @@ impl Element for PaneBackdrop { view: &mut V, cx: &mut ViewContext, ) -> Self::PaintState { - let background = cx.global::().theme.editor.background; + let background = theme::current(cx).editor.background; let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default(); @@ -2087,10 +2087,11 @@ mod tests { use crate::item::test::{TestItem, TestProjectItem}; use gpui::{executor::Deterministic, TestAppContext}; use project::FakeFs; + use settings::SettingsStore; #[gpui::test] async fn test_remove_active_empty(cx: &mut TestAppContext) { - Settings::test_async(cx); + init_test(cx); let fs = FakeFs::new(cx.background()); let project = Project::test(fs, None, cx).await; @@ -2104,7 +2105,7 @@ mod tests { #[gpui::test] async fn test_add_item_with_new_item(cx: &mut TestAppContext) { cx.foreground().forbid_parking(); - Settings::test_async(cx); + init_test(cx); let fs = FakeFs::new(cx.background()); let project = Project::test(fs, None, cx).await; @@ -2192,7 +2193,7 @@ mod tests { #[gpui::test] async fn test_add_item_with_existing_item(cx: &mut TestAppContext) { cx.foreground().forbid_parking(); - Settings::test_async(cx); + init_test(cx); let fs = FakeFs::new(cx.background()); let project = Project::test(fs, None, cx).await; @@ -2268,7 +2269,7 @@ mod tests { #[gpui::test] async fn test_add_item_with_same_project_entries(cx: &mut TestAppContext) { cx.foreground().forbid_parking(); - Settings::test_async(cx); + init_test(cx); let fs = FakeFs::new(cx.background()); let project = Project::test(fs, None, cx).await; @@ -2377,7 +2378,7 @@ mod tests { #[gpui::test] async fn test_remove_item_ordering(deterministic: Arc, cx: &mut TestAppContext) { - Settings::test_async(cx); + init_test(cx); let fs = FakeFs::new(cx.background()); let project = Project::test(fs, None, cx).await; @@ -2424,7 +2425,7 @@ mod tests { #[gpui::test] async fn test_close_inactive_items(deterministic: Arc, cx: &mut TestAppContext) { - Settings::test_async(cx); + init_test(cx); let fs = FakeFs::new(cx.background()); let project = Project::test(fs, None, cx).await; @@ -2443,7 +2444,7 @@ mod tests { #[gpui::test] async fn test_close_clean_items(deterministic: Arc, cx: &mut TestAppContext) { - Settings::test_async(cx); + init_test(cx); let fs = FakeFs::new(cx.background()); let project = Project::test(fs, None, cx).await; @@ -2470,7 +2471,7 @@ mod tests { deterministic: Arc, cx: &mut TestAppContext, ) { - Settings::test_async(cx); + init_test(cx); let fs = FakeFs::new(cx.background()); let project = Project::test(fs, None, cx).await; @@ -2492,7 +2493,7 @@ mod tests { deterministic: Arc, cx: &mut TestAppContext, ) { - Settings::test_async(cx); + init_test(cx); let fs = FakeFs::new(cx.background()); let project = Project::test(fs, None, cx).await; @@ -2511,7 +2512,7 @@ mod tests { #[gpui::test] async fn test_close_all_items(deterministic: Arc, cx: &mut TestAppContext) { - Settings::test_async(cx); + init_test(cx); let fs = FakeFs::new(cx.background()); let project = Project::test(fs, None, cx).await; @@ -2531,6 +2532,14 @@ mod tests { assert_item_labels(&pane, [], cx); } + fn init_test(cx: &mut TestAppContext) { + cx.update(|cx| { + cx.set_global(SettingsStore::test(cx)); + theme::init((), cx); + crate::init_settings(cx); + }); + } + fn add_labeled_item( workspace: &ViewHandle, pane: &ViewHandle, diff --git a/crates/workspace/src/pane/dragged_item_receiver.rs b/crates/workspace/src/pane/dragged_item_receiver.rs index 961205b9ee72e67a32e152bb18cbad58a7c7a64f..532e6bff5c03e6e9565e86f333603c1eacd5d299 100644 --- a/crates/workspace/src/pane/dragged_item_receiver.rs +++ b/crates/workspace/src/pane/dragged_item_receiver.rs @@ -1,3 +1,5 @@ +use super::DraggedItem; +use crate::{Pane, SplitDirection, Workspace}; use drag_and_drop::DragAndDrop; use gpui::{ color::Color, @@ -8,11 +10,6 @@ use gpui::{ AppContext, Element, EventContext, MouseState, Quad, View, ViewContext, WeakViewHandle, }; use project::ProjectEntryId; -use settings::Settings; - -use crate::{Pane, SplitDirection, Workspace}; - -use super::DraggedItem; pub fn dragged_item_receiver( region_id: usize, @@ -225,8 +222,5 @@ fn drop_split_direction( } fn overlay_color(cx: &AppContext) -> Color { - cx.global::() - .theme - .workspace - .drop_target_overlay_color + theme::current(cx).workspace.drop_target_overlay_color } diff --git a/crates/workspace/src/pane_group.rs b/crates/workspace/src/pane_group.rs index 55032b4bc1e79bf40e75c5be5a0a4b7e47f3e836..6e7580a103f3bf7a27217cdb0b246b789caffc44 100644 --- a/crates/workspace/src/pane_group.rs +++ b/crates/workspace/src/pane_group.rs @@ -1,6 +1,6 @@ use std::sync::Arc; -use crate::{AppState, FollowerStatesByLeader, Pane, Workspace}; +use crate::{AppState, FollowerStatesByLeader, Pane, Workspace, WorkspaceSettings}; use anyhow::{anyhow, Result}; use call::{ActiveCall, ParticipantLocation}; use gpui::{ @@ -11,7 +11,6 @@ use gpui::{ }; use project::Project; use serde::Deserialize; -use settings::Settings; use theme::Theme; #[derive(Clone, Debug, Eq, PartialEq)] @@ -380,7 +379,7 @@ impl PaneAxis { .with_children(self.members.iter().enumerate().map(|(ix, member)| { let mut flex = 1.0; if member.contains(active_pane) { - flex = cx.global::().active_pane_magnification; + flex = settings::get::(cx).active_pane_magnification; } let mut member = member.render( diff --git a/crates/workspace/src/persistence.rs b/crates/workspace/src/persistence.rs index d5d79c3ddd3368ed5960747908a6c88d4edd8094..4ffae0d7e3cacaefbd244ff1af8c80439f44b6f8 100644 --- a/crates/workspace/src/persistence.rs +++ b/crates/workspace/src/persistence.rs @@ -497,13 +497,10 @@ impl WorkspaceDb { #[cfg(test)] mod tests { - - use std::sync::Arc; - - use db::open_test_db; - use settings::DockAnchor; - use super::*; + use crate::DockAnchor; + use db::open_test_db; + use std::sync::Arc; #[gpui::test] async fn test_next_id_stability() { diff --git a/crates/workspace/src/persistence/model.rs b/crates/workspace/src/persistence/model.rs index dd81109d8c220c2876010be3c8b6917ef94b69ad..b73dfa495d4fbb2d3c60f09bede945227d506da6 100644 --- a/crates/workspace/src/persistence/model.rs +++ b/crates/workspace/src/persistence/model.rs @@ -1,6 +1,6 @@ use crate::{ - dock::DockPosition, item::ItemHandle, ItemDeserializers, Member, Pane, PaneAxis, Workspace, - WorkspaceId, + dock::DockPosition, item::ItemHandle, DockAnchor, ItemDeserializers, Member, Pane, PaneAxis, + Workspace, WorkspaceId, }; use anyhow::{anyhow, Context, Result}; use async_recursion::async_recursion; @@ -12,7 +12,6 @@ use gpui::{ platform::WindowBounds, AsyncAppContext, Axis, ModelHandle, Task, ViewHandle, WeakViewHandle, }; use project::Project; -use settings::DockAnchor; use std::{ path::{Path, PathBuf}, sync::Arc, @@ -316,10 +315,9 @@ impl Column for DockPosition { #[cfg(test)] mod tests { - use db::sqlez::connection::Connection; - use settings::DockAnchor; - use super::WorkspaceLocation; + use crate::DockAnchor; + use db::sqlez::connection::Connection; #[test] fn test_workspace_round_trips() { diff --git a/crates/workspace/src/shared_screen.rs b/crates/workspace/src/shared_screen.rs index 5cc54a6a7f4244f09a1a40fa0cea7c592703331d..9a2e0bc5d2ec98e216e18f22bfd0bf709a755473 100644 --- a/crates/workspace/src/shared_screen.rs +++ b/crates/workspace/src/shared_screen.rs @@ -12,7 +12,6 @@ use gpui::{ platform::MouseButton, AppContext, Entity, Task, View, ViewContext, }; -use settings::Settings; use smallvec::SmallVec; use std::{ borrow::Cow, @@ -88,7 +87,7 @@ impl View for SharedScreen { } }) .contained() - .with_style(cx.global::().theme.shared_screen) + .with_style(theme::current(cx).shared_screen) }) .on_down(MouseButton::Left, |_, _, cx| cx.focus_parent()) .into_any() diff --git a/crates/workspace/src/sidebar.rs b/crates/workspace/src/sidebar.rs index 6463ab7d24b9ea6e44ababa2c3768dc7d36b663a..50148fa211dad3a415e59a7381c44f55573c6b18 100644 --- a/crates/workspace/src/sidebar.rs +++ b/crates/workspace/src/sidebar.rs @@ -4,7 +4,6 @@ use gpui::{ AppContext, Entity, Subscription, View, ViewContext, ViewHandle, WeakViewHandle, WindowContext, }; use serde::Deserialize; -use settings::Settings; use std::rc::Rc; pub trait SidebarItem: View { @@ -192,7 +191,7 @@ impl View for Sidebar { fn render(&mut self, cx: &mut ViewContext) -> AnyElement { if let Some(active_item) = self.active_item() { enum ResizeHandleTag {} - let style = &cx.global::().theme.workspace.sidebar; + let style = &theme::current(cx).workspace.sidebar; ChildView::new(active_item.as_any(), cx) .contained() .with_style(style.container) @@ -231,7 +230,7 @@ impl View for SidebarButtons { } fn render(&mut self, cx: &mut ViewContext) -> AnyElement { - let theme = &cx.global::().theme; + let theme = &theme::current(cx); let tooltip_style = theme.tooltip.clone(); let theme = &theme.workspace.status_bar.sidebar_buttons; let sidebar = self.sidebar.read(cx); diff --git a/crates/workspace/src/status_bar.rs b/crates/workspace/src/status_bar.rs index b4de6b3575d392497ccba3c18274820bd51fa371..6fc1467566bec3a3c93ff6807a15bf1fec988e67 100644 --- a/crates/workspace/src/status_bar.rs +++ b/crates/workspace/src/status_bar.rs @@ -11,7 +11,6 @@ use gpui::{ AnyElement, AnyViewHandle, Entity, LayoutContext, SceneBuilder, SizeConstraint, Subscription, View, ViewContext, ViewHandle, WindowContext, }; -use settings::Settings; pub trait StatusItemView: View { fn set_active_pane_item( @@ -47,7 +46,7 @@ impl View for StatusBar { } fn render(&mut self, cx: &mut ViewContext) -> AnyElement { - let theme = &cx.global::().theme.workspace.status_bar; + let theme = &theme::current(cx).workspace.status_bar; StatusBarElement { left: Flex::row() diff --git a/crates/workspace/src/toolbar.rs b/crates/workspace/src/toolbar.rs index b2832aa1e810ff7e5fad7e3dc5802de6cf49a8b5..30890ed5d2cefa4ebdadece37e94733087f5112f 100644 --- a/crates/workspace/src/toolbar.rs +++ b/crates/workspace/src/toolbar.rs @@ -3,7 +3,6 @@ use gpui::{ elements::*, platform::CursorStyle, platform::MouseButton, Action, AnyElement, AnyViewHandle, AppContext, Entity, View, ViewContext, ViewHandle, WeakViewHandle, WindowContext, }; -use settings::Settings; pub trait ToolbarItemView: View { fn set_active_pane_item( @@ -68,7 +67,7 @@ impl View for Toolbar { } fn render(&mut self, cx: &mut ViewContext) -> AnyElement { - let theme = &cx.global::().theme.workspace.toolbar; + let theme = &theme::current(cx).workspace.toolbar; let mut primary_left_items = Vec::new(); let mut primary_right_items = Vec::new(); @@ -131,7 +130,7 @@ impl View for Toolbar { let height = theme.height * primary_items_row_count as f32; let nav_button_height = theme.height; let button_style = theme.nav_button; - let tooltip_style = cx.global::().theme.tooltip.clone(); + let tooltip_style = theme::current(cx).tooltip.clone(); Flex::column() .with_child( diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 4bdaaedc6451ead2ce484b79ad81245010d6ae90..8ca6358f9aeafc8397bcc3c24aa8a157503db638 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -13,6 +13,7 @@ pub mod shared_screen; pub mod sidebar; mod status_bar; mod toolbar; +mod workspace_settings; use anyhow::{anyhow, Context, Result}; use assets::Assets; @@ -75,14 +76,14 @@ pub use persistence::{ use postage::prelude::Stream; use project::{Project, ProjectEntryId, ProjectPath, Worktree, WorktreeId}; use serde::Deserialize; -use settings::{Autosave, DockAnchor, Settings}; use shared_screen::SharedScreen; use sidebar::{Sidebar, SidebarButtons, SidebarSide, ToggleSidebarItem}; use status_bar::StatusBar; pub use status_bar::StatusItemView; -use theme::{Theme, ThemeRegistry}; +use theme::Theme; pub use toolbar::{ToolbarItemLocation, ToolbarItemView}; use util::{async_iife, paths, ResultExt}; +pub use workspace_settings::{AutosaveSetting, DockAnchor, GitGutterSetting, WorkspaceSettings}; lazy_static! { static ref ZED_WINDOW_SIZE: Option = env::var("ZED_WINDOW_SIZE") @@ -183,7 +184,12 @@ pub type WorkspaceId = i64; impl_actions!(workspace, [ActivatePane]); +pub fn init_settings(cx: &mut AppContext) { + settings::register::(cx); +} + pub fn init(app_state: Arc, cx: &mut AppContext) { + init_settings(cx); pane::init(cx); dock::init(cx); notifications::init(cx); @@ -269,7 +275,7 @@ pub fn init(app_state: Arc, cx: &mut AppContext) { cx.add_action( move |_: &mut Workspace, _: &OpenSettings, cx: &mut ViewContext| { create_and_open_local_file(&paths::SETTINGS, cx, || { - Settings::initial_user_settings_content(&Assets) + settings::initial_user_settings_content(&Assets) .as_ref() .into() }) @@ -354,7 +360,6 @@ pub fn register_deserializable_item(cx: &mut AppContext) { pub struct AppState { pub languages: Arc, - pub themes: Arc, pub client: Arc, pub user_store: ModelHandle, pub fs: Arc, @@ -368,18 +373,24 @@ pub struct AppState { impl AppState { #[cfg(any(test, feature = "test-support"))] pub fn test(cx: &mut AppContext) -> Arc { - let settings = Settings::test(cx); - cx.set_global(settings); + use settings::SettingsStore; + + if !cx.has_global::() { + cx.set_global(SettingsStore::test(cx)); + } let fs = fs::FakeFs::new(cx.background().clone()); let languages = Arc::new(LanguageRegistry::test()); let http_client = util::http::FakeHttpClient::with_404_response(); let client = Client::new(http_client.clone(), cx); let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http_client, cx)); - let themes = ThemeRegistry::new((), cx.font_cache().clone()); + + theme::init((), cx); + client::init(&client, cx); + crate::init_settings(cx); + Arc::new(Self { client, - themes, fs, languages, user_store, @@ -1977,7 +1988,7 @@ impl Workspace { enum DisconnectedOverlay {} Some( MouseEventHandler::::new(0, cx, |_, cx| { - let theme = &cx.global::().theme; + let theme = &theme::current(cx); Label::new( "Your connection to the remote project has been lost.", theme.workspace.disconnected_overlay.text.clone(), @@ -2348,8 +2359,8 @@ impl Workspace { item.workspace_deactivated(cx); } if matches!( - cx.global::().autosave, - Autosave::OnWindowChange | Autosave::OnFocusChange + settings::get::(cx).autosave, + AutosaveSetting::OnWindowChange | AutosaveSetting::OnFocusChange ) { for item in pane.items() { Pane::autosave_item(item.as_ref(), self.project.clone(), cx) @@ -2615,7 +2626,6 @@ impl Workspace { pub fn test_new(project: ModelHandle, cx: &mut ViewContext) -> Self { let app_state = Arc::new(AppState { languages: project.read(cx).languages().clone(), - themes: ThemeRegistry::new((), cx.font_cache().clone()), client: project.read(cx).client(), user_store: project.read(cx).user_store(), fs: project.read(cx).fs().clone(), @@ -2761,7 +2771,7 @@ impl View for Workspace { } fn render(&mut self, cx: &mut ViewContext) -> AnyElement { - let theme = cx.global::().theme.clone(); + let theme = theme::current(cx).clone(); Stack::new() .with_child( Flex::column() @@ -3130,7 +3140,7 @@ pub fn join_remote_project( } pub fn restart(_: &Restart, cx: &mut AppContext) { - let should_confirm = cx.global::().confirm_quit; + let should_confirm = settings::get::(cx).confirm_quit; cx.spawn(|mut cx| async move { let mut workspaces = cx .window_ids() @@ -3191,20 +3201,18 @@ fn parse_pixel_position_env_var(value: &str) -> Option { #[cfg(test)] mod tests { - use std::{cell::RefCell, rc::Rc}; - - use crate::item::test::{TestItem, TestItemEvent, TestProjectItem}; - use super::*; + use crate::item::test::{TestItem, TestItemEvent, TestProjectItem}; use fs::FakeFs; use gpui::{executor::Deterministic, TestAppContext}; use project::{Project, ProjectEntryId}; use serde_json::json; + use settings::SettingsStore; + use std::{cell::RefCell, rc::Rc}; #[gpui::test] async fn test_tab_disambiguation(cx: &mut TestAppContext) { - cx.foreground().forbid_parking(); - Settings::test_async(cx); + init_test(cx); let fs = FakeFs::new(cx.background()); let project = Project::test(fs, [], cx).await; @@ -3252,8 +3260,8 @@ mod tests { #[gpui::test] async fn test_tracking_active_path(cx: &mut TestAppContext) { - cx.foreground().forbid_parking(); - Settings::test_async(cx); + init_test(cx); + let fs = FakeFs::new(cx.background()); fs.insert_tree( "/root1", @@ -3356,8 +3364,8 @@ mod tests { #[gpui::test] async fn test_close_window(cx: &mut TestAppContext) { - cx.foreground().forbid_parking(); - Settings::test_async(cx); + init_test(cx); + let fs = FakeFs::new(cx.background()); fs.insert_tree("/root", json!({ "one": "" })).await; @@ -3392,8 +3400,8 @@ mod tests { #[gpui::test] async fn test_close_pane_items(cx: &mut TestAppContext) { - cx.foreground().forbid_parking(); - Settings::test_async(cx); + init_test(cx); + let fs = FakeFs::new(cx.background()); let project = Project::test(fs, None, cx).await; @@ -3499,8 +3507,8 @@ mod tests { #[gpui::test] async fn test_prompting_to_save_only_on_last_item_for_entry(cx: &mut TestAppContext) { - cx.foreground().forbid_parking(); - Settings::test_async(cx); + init_test(cx); + let fs = FakeFs::new(cx.background()); let project = Project::test(fs, [], cx).await; @@ -3605,9 +3613,8 @@ mod tests { #[gpui::test] async fn test_autosave(deterministic: Arc, cx: &mut gpui::TestAppContext) { - deterministic.forbid_parking(); + init_test(cx); - Settings::test_async(cx); let fs = FakeFs::new(cx.background()); let project = Project::test(fs, [], cx).await; @@ -3623,8 +3630,10 @@ mod tests { // Autosave on window change. item.update(cx, |item, cx| { - cx.update_global(|settings: &mut Settings, _| { - settings.autosave = Autosave::OnWindowChange; + cx.update_global(|settings: &mut SettingsStore, cx| { + settings.update_user_settings::(cx, |settings| { + settings.autosave = Some(AutosaveSetting::OnWindowChange); + }) }); item.is_dirty = true; }); @@ -3637,8 +3646,10 @@ mod tests { // Autosave on focus change. item.update(cx, |item, cx| { cx.focus_self(); - cx.update_global(|settings: &mut Settings, _| { - settings.autosave = Autosave::OnFocusChange; + cx.update_global(|settings: &mut SettingsStore, cx| { + settings.update_user_settings::(cx, |settings| { + settings.autosave = Some(AutosaveSetting::OnFocusChange); + }) }); item.is_dirty = true; }); @@ -3661,8 +3672,10 @@ mod tests { // Autosave after delay. item.update(cx, |item, cx| { - cx.update_global(|settings: &mut Settings, _| { - settings.autosave = Autosave::AfterDelay { milliseconds: 500 }; + cx.update_global(|settings: &mut SettingsStore, cx| { + settings.update_user_settings::(cx, |settings| { + settings.autosave = Some(AutosaveSetting::AfterDelay { milliseconds: 500 }); + }) }); item.is_dirty = true; cx.emit(TestItemEvent::Edit); @@ -3678,8 +3691,10 @@ mod tests { // Autosave on focus change, ensuring closing the tab counts as such. item.update(cx, |item, cx| { - cx.update_global(|settings: &mut Settings, _| { - settings.autosave = Autosave::OnFocusChange; + cx.update_global(|settings: &mut SettingsStore, cx| { + settings.update_user_settings::(cx, |settings| { + settings.autosave = Some(AutosaveSetting::OnFocusChange); + }) }); item.is_dirty = true; }); @@ -3719,12 +3734,9 @@ mod tests { } #[gpui::test] - async fn test_pane_navigation( - deterministic: Arc, - cx: &mut gpui::TestAppContext, - ) { - deterministic.forbid_parking(); - Settings::test_async(cx); + async fn test_pane_navigation(cx: &mut gpui::TestAppContext) { + init_test(cx); + let fs = FakeFs::new(cx.background()); let project = Project::test(fs, [], cx).await; @@ -3776,4 +3788,14 @@ mod tests { assert!(pane.can_navigate_forward()); }); } + + pub fn init_test(cx: &mut TestAppContext) { + cx.foreground().forbid_parking(); + cx.update(|cx| { + cx.set_global(SettingsStore::test(cx)); + theme::init((), cx); + language::init(cx); + crate::init_settings(cx); + }); + } } diff --git a/crates/workspace/src/workspace_settings.rs b/crates/workspace/src/workspace_settings.rs new file mode 100644 index 0000000000000000000000000000000000000000..41e47964910575a06fb0891c5e75d6ced13eefd7 --- /dev/null +++ b/crates/workspace/src/workspace_settings.rs @@ -0,0 +1,103 @@ +use anyhow::bail; +use db::sqlez::{ + bindable::{Bind, Column, StaticColumnCount}, + statement::Statement, +}; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use settings::Setting; + +#[derive(Deserialize)] +pub struct WorkspaceSettings { + pub active_pane_magnification: f32, + pub confirm_quit: bool, + pub show_call_status_icon: bool, + pub autosave: AutosaveSetting, + pub default_dock_anchor: DockAnchor, + pub git: GitSettings, +} + +#[derive(Clone, Serialize, Deserialize, JsonSchema)] +pub struct WorkspaceSettingsContent { + pub active_pane_magnification: Option, + pub confirm_quit: Option, + pub show_call_status_icon: Option, + pub autosave: Option, + pub default_dock_anchor: Option, + pub git: Option, +} + +#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum AutosaveSetting { + Off, + AfterDelay { milliseconds: u64 }, + OnFocusChange, + OnWindowChange, +} + +#[derive(PartialEq, Eq, Debug, Default, Copy, Clone, Hash, Serialize, Deserialize, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum DockAnchor { + #[default] + Bottom, + Right, + Expanded, +} + +#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, JsonSchema)] +pub struct GitSettings { + pub git_gutter: Option, + pub gutter_debounce: Option, +} + +#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum GitGutterSetting { + #[default] + TrackedFiles, + Hide, +} + +impl StaticColumnCount for DockAnchor {} + +impl Bind for DockAnchor { + fn bind(&self, statement: &Statement, start_index: i32) -> anyhow::Result { + match self { + DockAnchor::Bottom => "Bottom", + DockAnchor::Right => "Right", + DockAnchor::Expanded => "Expanded", + } + .bind(statement, start_index) + } +} + +impl Column for DockAnchor { + fn column(statement: &mut Statement, start_index: i32) -> anyhow::Result<(Self, i32)> { + String::column(statement, start_index).and_then(|(anchor_text, next_index)| { + Ok(( + match anchor_text.as_ref() { + "Bottom" => DockAnchor::Bottom, + "Right" => DockAnchor::Right, + "Expanded" => DockAnchor::Expanded, + _ => bail!("Stored dock anchor is incorrect"), + }, + next_index, + )) + }) + } +} + +impl Setting for WorkspaceSettings { + const KEY: Option<&'static str> = None; + + type FileContent = WorkspaceSettingsContent; + + fn load( + default_value: &Self::FileContent, + user_values: &[&Self::FileContent], + _: &gpui::AppContext, + ) -> anyhow::Result { + Self::load_via_json_merge(default_value, user_values) + } +} diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index ea9ee23971f15cf14ced07ecbaf135679d9e7ec3..90dced65f574018fdcfe1ca54ac7d1a773d2708c 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -101,7 +101,7 @@ smol.workspace = true tempdir.workspace = true thiserror.workspace = true tiny_http = "0.8" -toml = "0.5" +toml.workspace = true tree-sitter = "0.20" tree-sitter-c = "0.20.1" tree-sitter-cpp = "0.20.0" diff --git a/crates/zed/src/languages.rs b/crates/zed/src/languages.rs index 91b58f634b855743e8dd0421b317cbc5efe6b725..1f2b359af1523212d97de8dfd43cf4bccaae6dfb 100644 --- a/crates/zed/src/languages.rs +++ b/crates/zed/src/languages.rs @@ -3,7 +3,6 @@ pub use language::*; use node_runtime::NodeRuntime; use rust_embed::RustEmbed; use std::{borrow::Cow, str, sync::Arc}; -use theme::ThemeRegistry; mod c; mod elixir; @@ -32,11 +31,7 @@ mod yaml; #[exclude = "*.rs"] struct LanguageDir; -pub fn init( - languages: Arc, - themes: Arc, - node_runtime: Arc, -) { +pub fn init(languages: Arc, node_runtime: Arc) { fn adapter_arc(adapter: impl LspAdapter) -> Arc { Arc::new(adapter) } @@ -69,7 +64,6 @@ pub fn init( vec![adapter_arc(json::JsonLspAdapter::new( node_runtime.clone(), languages.clone(), - themes.clone(), ))], ), ("markdown", tree_sitter_markdown::language(), vec![]), diff --git a/crates/zed/src/languages/c.rs b/crates/zed/src/languages/c.rs index 84c5798b07d53bbcbe9dbb930cedb6f80db2c4ad..7e4ddcef19189cb0a5704d43ca78a6164186d1de 100644 --- a/crates/zed/src/languages/c.rs +++ b/crates/zed/src/languages/c.rs @@ -249,16 +249,21 @@ impl super::LspAdapter for CLspAdapter { #[cfg(test)] mod tests { use gpui::TestAppContext; - use language::{AutoindentMode, Buffer}; - use settings::Settings; + use language::{language_settings::AllLanguageSettings, AutoindentMode, Buffer}; + use settings::SettingsStore; + use std::num::NonZeroU32; #[gpui::test] async fn test_c_autoindent(cx: &mut TestAppContext) { cx.foreground().set_block_on_ticks(usize::MAX..=usize::MAX); cx.update(|cx| { - let mut settings = Settings::test(cx); - settings.editor_overrides.tab_size = Some(2.try_into().unwrap()); - cx.set_global(settings); + cx.set_global(SettingsStore::test(cx)); + language::init(cx); + cx.update_global::(|store, cx| { + store.update_user_settings::(cx, |s| { + s.defaults.tab_size = NonZeroU32::new(2); + }); + }); }); let language = crate::languages::language("c", tree_sitter_c::language(), None).await; diff --git a/crates/zed/src/languages/json.rs b/crates/zed/src/languages/json.rs index d87d36abfef7139033d8f9526e877d2ba4efb826..1fb1a5a941c78dbe7378dce83e9bfe7aeb4707d4 100644 --- a/crates/zed/src/languages/json.rs +++ b/crates/zed/src/languages/json.rs @@ -6,7 +6,7 @@ use gpui::AppContext; use language::{LanguageRegistry, LanguageServerBinary, LanguageServerName, LspAdapter}; use node_runtime::NodeRuntime; use serde_json::json; -use settings::{keymap_file_json_schema, settings_file_json_schema}; +use settings::{keymap_file_json_schema, SettingsJsonSchemaParams, SettingsStore}; use smol::fs; use staff_mode::StaffMode; use std::{ @@ -16,7 +16,6 @@ use std::{ path::{Path, PathBuf}, sync::Arc, }; -use theme::ThemeRegistry; use util::http::HttpClient; use util::{paths, ResultExt}; @@ -30,20 +29,11 @@ fn server_binary_arguments(server_path: &Path) -> Vec { pub struct JsonLspAdapter { node: Arc, languages: Arc, - themes: Arc, } impl JsonLspAdapter { - pub fn new( - node: Arc, - languages: Arc, - themes: Arc, - ) -> Self { - JsonLspAdapter { - node, - languages, - themes, - } + pub fn new(node: Arc, languages: Arc) -> Self { + JsonLspAdapter { node, languages } } } @@ -128,12 +118,15 @@ impl LspAdapter for JsonLspAdapter { cx: &mut AppContext, ) -> Option> { let action_names = cx.all_action_names().collect::>(); - let theme_names = self - .themes - .list(**cx.default_global::()) - .map(|meta| meta.name) - .collect(); - let language_names = self.languages.language_names(); + let staff_mode = cx.global::().0; + let language_names = &self.languages.language_names(); + let settings_schema = cx.global::().json_schema( + &SettingsJsonSchemaParams { + language_names, + staff_mode, + }, + cx, + ); Some( future::ready(serde_json::json!({ "json": { @@ -143,7 +136,7 @@ impl LspAdapter for JsonLspAdapter { "schemas": [ { "fileMatch": [schema_file_match(&paths::SETTINGS)], - "schema": settings_file_json_schema(theme_names, &language_names), + "schema": settings_schema, }, { "fileMatch": [schema_file_match(&paths::KEYMAP)], diff --git a/crates/zed/src/languages/python.rs b/crates/zed/src/languages/python.rs index acd31e82059b0d04aa476340e30e87b4c1867a9d..7aaddf5fe8a72d5731322e911b5a5c87473dfdbe 100644 --- a/crates/zed/src/languages/python.rs +++ b/crates/zed/src/languages/python.rs @@ -170,8 +170,9 @@ impl LspAdapter for PythonLspAdapter { #[cfg(test)] mod tests { use gpui::{ModelContext, TestAppContext}; - use language::{AutoindentMode, Buffer}; - use settings::Settings; + use language::{language_settings::AllLanguageSettings, AutoindentMode, Buffer}; + use settings::SettingsStore; + use std::num::NonZeroU32; #[gpui::test] async fn test_python_autoindent(cx: &mut TestAppContext) { @@ -179,9 +180,13 @@ mod tests { let language = crate::languages::language("python", tree_sitter_python::language(), None).await; cx.update(|cx| { - let mut settings = Settings::test(cx); - settings.editor_overrides.tab_size = Some(2.try_into().unwrap()); - cx.set_global(settings); + cx.set_global(SettingsStore::test(cx)); + language::init(cx); + cx.update_global::(|store, cx| { + store.update_user_settings::(cx, |s| { + s.defaults.tab_size = NonZeroU32::new(2); + }); + }); }); cx.add_model(|cx| { diff --git a/crates/zed/src/languages/rust.rs b/crates/zed/src/languages/rust.rs index 92fb5bc3b2739cf8a63a2d3717c23e7642e50963..15700ec80a1bc798d44210ed47d600cc319a1241 100644 --- a/crates/zed/src/languages/rust.rs +++ b/crates/zed/src/languages/rust.rs @@ -253,10 +253,13 @@ impl LspAdapter for RustLspAdapter { #[cfg(test)] mod tests { + use std::num::NonZeroU32; + use super::*; use crate::languages::language; use gpui::{color::Color, TestAppContext}; - use settings::Settings; + use language::language_settings::AllLanguageSettings; + use settings::SettingsStore; use theme::SyntaxTheme; #[gpui::test] @@ -435,9 +438,13 @@ mod tests { async fn test_rust_autoindent(cx: &mut TestAppContext) { cx.foreground().set_block_on_ticks(usize::MAX..=usize::MAX); cx.update(|cx| { - let mut settings = Settings::test(cx); - settings.editor_overrides.tab_size = Some(2.try_into().unwrap()); - cx.set_global(settings); + cx.set_global(SettingsStore::test(cx)); + language::init(cx); + cx.update_global::(|store, cx| { + store.update_user_settings::(cx, |s| { + s.defaults.tab_size = NonZeroU32::new(2); + }); + }); }); let language = crate::languages::language("rust", tree_sitter_rust::language(), None).await; diff --git a/crates/zed/src/languages/yaml.rs b/crates/zed/src/languages/yaml.rs index fed76cd5b9e399bdfed5a4756617395eb746852b..bd5f2b4c021e7d8239d76ea29d8ef88ddcf8015b 100644 --- a/crates/zed/src/languages/yaml.rs +++ b/crates/zed/src/languages/yaml.rs @@ -2,10 +2,11 @@ use anyhow::{anyhow, Result}; use async_trait::async_trait; use futures::{future::BoxFuture, FutureExt, StreamExt}; use gpui::AppContext; -use language::{LanguageServerBinary, LanguageServerName, LspAdapter}; +use language::{ + language_settings::language_settings, LanguageServerBinary, LanguageServerName, LspAdapter, +}; use node_runtime::NodeRuntime; use serde_json::Value; -use settings::Settings; use smol::fs; use std::{ any::Any, @@ -100,14 +101,13 @@ impl LspAdapter for YamlLspAdapter { } fn workspace_configuration(&self, cx: &mut AppContext) -> Option> { - let settings = cx.global::(); Some( future::ready(serde_json::json!({ "yaml": { "keyOrdering": false }, "[yaml]": { - "editor.tabSize": settings.tab_size(Some("YAML")) + "editor.tabSize": language_settings(Some("YAML"), cx).tab_size, } })) .boxed(), diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 1947095bf5598990304687d891e170d697bc951a..299e77efce6758d8851374632cb737bbdc010f6c 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -8,7 +8,7 @@ use cli::{ ipc::{self, IpcSender}, CliRequest, CliResponse, IpcHandshake, FORCE_CLI_MODE_ENV_VAR_NAME, }; -use client::{self, UserStore, ZED_APP_VERSION, ZED_SECRET_CLIENT_TOKEN}; +use client::{self, TelemetrySettings, UserStore, ZED_APP_VERSION, ZED_SECRET_CLIENT_TOKEN}; use db::kvp::KEY_VALUE_STORE; use editor::{scroll::autoscroll::Autoscroll, Editor}; use futures::{ @@ -17,16 +17,13 @@ use futures::{ }; use gpui::{Action, App, AppContext, AssetSource, AsyncAppContext, Task, ViewContext}; use isahc::{config::Configurable, Request}; -use language::LanguageRegistry; +use language::{LanguageRegistry, Point}; use log::LevelFilter; use node_runtime::NodeRuntime; use parking_lot::Mutex; use project::Fs; use serde::{Deserialize, Serialize}; -use settings::{ - self, settings_file::SettingsFile, KeymapFileContent, Settings, SettingsFileContent, - WorkingDirectory, -}; +use settings::{default_settings, handle_settings_file_changes, watch_config_file, SettingsStore}; use simplelog::ConfigBuilder; use smol::process::Command; use std::{ @@ -38,6 +35,7 @@ use std::{ os::unix::prelude::OsStrExt, panic, path::{Path, PathBuf}, + str, sync::{ atomic::{AtomicBool, Ordering}, Arc, Weak, @@ -46,8 +44,7 @@ use std::{ time::Duration, }; use sum_tree::Bias; -use terminal_view::{get_working_directory, TerminalView}; -use text::Point; +use terminal_view::{get_working_directory, TerminalSettings, TerminalView}; use util::{ http::{self, HttpClient}, paths::PathLikeWithPosition, @@ -55,16 +52,16 @@ use util::{ use welcome::{show_welcome_experience, FIRST_OPEN}; use fs::RealFs; -use settings::watched_json::WatchedJsonFile; #[cfg(debug_assertions)] use staff_mode::StaffMode; -use theme::ThemeRegistry; use util::{channel::RELEASE_CHANNEL, paths, ResultExt, TryFutureExt}; use workspace::{ dock::FocusDock, item::ItemHandle, notifications::NotifyResultExt, AppState, OpenSettings, Workspace, }; -use zed::{self, build_window_options, initialize_workspace, languages, menus}; +use zed::{ + self, build_window_options, handle_keymap_file_changes, initialize_workspace, languages, menus, +}; fn main() { let http = http::client(); @@ -84,10 +81,10 @@ fn main() { load_embedded_fonts(&app); let fs = Arc::new(RealFs); - - let themes = ThemeRegistry::new(Assets, app.font_cache()); - let default_settings = Settings::defaults(Assets, &app.font_cache(), &themes); - let config_files = load_config_files(&app, fs.clone()); + let user_settings_file_rx = + watch_config_file(app.background(), fs.clone(), paths::SETTINGS.clone()); + let user_keymap_file_rx = + watch_config_file(app.background(), fs.clone(), paths::KEYMAP.clone()); let login_shell_env_loaded = if stdout_is_a_pty() { Task::ready(()) @@ -127,22 +124,13 @@ fn main() { #[cfg(debug_assertions)] cx.set_global(StaffMode(true)); - let (settings_file_content, keymap_file) = cx.background().block(config_files).unwrap(); - - //Setup settings global before binding actions - cx.set_global(SettingsFile::new( - &paths::SETTINGS, - settings_file_content.clone(), - fs.clone(), - )); - - settings::watch_files( - default_settings, - settings_file_content, - themes.clone(), - keymap_file, - cx, - ); + let mut store = SettingsStore::default(); + store + .set_default_settings(default_settings().as_ref(), cx) + .unwrap(); + cx.set_global(store); + handle_settings_file_changes(user_settings_file_rx, cx); + handle_keymap_file_changes(user_keymap_file_rx, cx); if !stdout_is_a_pty() { upload_previous_panics(http.clone(), cx); @@ -155,15 +143,17 @@ fn main() { let languages = Arc::new(languages); let node_runtime = NodeRuntime::new(http.clone(), cx.background().to_owned()); - languages::init(languages.clone(), themes.clone(), node_runtime.clone()); + languages::init(languages.clone(), node_runtime.clone()); let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http.clone(), cx)); cx.set_global(client.clone()); + theme::init(Assets, cx); context_menu::init(cx); - project::Project::init(&client); - client::init(client.clone(), cx); + project::Project::init(&client, cx); + client::init(&client, cx); command_palette::init(cx); + language::init(cx); editor::init(cx); go_to_line::init(cx); file_finder::init(cx); @@ -177,13 +167,12 @@ fn main() { theme_testbench::init(cx); copilot::init(http.clone(), node_runtime, cx); - cx.spawn(|cx| watch_themes(fs.clone(), themes.clone(), cx)) - .detach(); + cx.spawn(|cx| watch_themes(fs.clone(), cx)).detach(); - languages.set_theme(cx.global::().theme.clone()); - cx.observe_global::({ + languages.set_theme(theme::current(cx).clone()); + cx.observe_global::({ let languages = languages.clone(); - move |cx| languages.set_theme(cx.global::().theme.clone()) + move |cx| languages.set_theme(theme::current(cx).clone()) }) .detach(); @@ -191,12 +180,11 @@ fn main() { client.telemetry().report_mixpanel_event( "start app", Default::default(), - cx.global::().telemetry(), + *settings::get::(cx), ); let app_state = Arc::new(AppState { languages, - themes, client: client.clone(), user_store, fs, @@ -214,10 +202,13 @@ fn main() { journal::init(app_state.clone(), cx); language_selector::init(cx); theme_selector::init(cx); - zed::init(&app_state, cx); + activity_indicator::init(cx); + lsp_log::init(cx); + call::init(app_state.client.clone(), app_state.user_store.clone(), cx); collab_ui::init(&app_state, cx); feedback::init(cx); welcome::init(cx); + zed::init(&app_state, cx); cx.set_menus(menus::menus()); @@ -450,7 +441,7 @@ fn init_panic_hook(app_version: String) { } fn upload_previous_panics(http: Arc, cx: &mut AppContext) { - let diagnostics_telemetry = cx.global::().telemetry_diagnostics(); + let telemetry_settings = *settings::get::(cx); cx.background() .spawn({ @@ -480,7 +471,7 @@ fn upload_previous_panics(http: Arc, cx: &mut AppContext) { continue; }; - if diagnostics_telemetry { + if telemetry_settings.diagnostics { let panic_data_text = smol::fs::read_to_string(&child_path) .await .context("error reading panic file")?; @@ -590,11 +581,7 @@ fn load_embedded_fonts(app: &App) { } #[cfg(debug_assertions)] -async fn watch_themes( - fs: Arc, - themes: Arc, - mut cx: AsyncAppContext, -) -> Option<()> { +async fn watch_themes(fs: Arc, mut cx: AsyncAppContext) -> Option<()> { let mut events = fs .watch("styles/src".as_ref(), Duration::from_millis(100)) .await; @@ -606,7 +593,7 @@ async fn watch_themes( .await .log_err()?; if output.status.success() { - cx.update(|cx| theme_selector::reload(themes.clone(), cx)) + cx.update(|cx| theme_selector::reload(cx)) } else { eprintln!( "build script failed {}", @@ -626,27 +613,6 @@ async fn watch_themes( None } -fn load_config_files( - app: &App, - fs: Arc, -) -> oneshot::Receiver<( - WatchedJsonFile, - WatchedJsonFile, -)> { - let executor = app.background(); - let (tx, rx) = oneshot::channel(); - executor - .clone() - .spawn(async move { - let settings_file = - WatchedJsonFile::new(fs.clone(), &executor, paths::SETTINGS.clone()).await; - let keymap_file = WatchedJsonFile::new(fs, &executor, paths::KEYMAP.clone()).await; - tx.send((settings_file, keymap_file)).ok() - }) - .detach(); - rx -} - fn connect_to_cli( server_name: &str, ) -> Result<(mpsc::Receiver, IpcSender)> { @@ -834,13 +800,9 @@ pub fn dock_default_item_factory( workspace: &mut Workspace, cx: &mut ViewContext, ) -> Option> { - let strategy = cx - .global::() - .terminal_overrides + let strategy = settings::get::(cx) .working_directory - .clone() - .unwrap_or(WorkingDirectory::CurrentProjectDirectory); - + .clone(); let working_directory = get_working_directory(workspace, cx, strategy); let window_id = cx.window_id(); diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index f687237bd22dd599a3d313bf3d79bdfa0aa64399..376ecfc06bd43aeb2753345aa2301b8bd7d60cf7 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -15,7 +15,7 @@ use anyhow::anyhow; use feedback::{ feedback_info_text::FeedbackInfoText, submit_feedback_button::SubmitFeedbackButton, }; -use futures::StreamExt; +use futures::{channel::mpsc, StreamExt}; use gpui::{ actions, geometry::vector::vec2f, @@ -29,15 +29,16 @@ use project_panel::ProjectPanel; use search::{BufferSearchBar, ProjectSearchBar}; use serde::Deserialize; use serde_json::to_string_pretty; -use settings::{Settings, DEFAULT_SETTINGS_ASSET_PATH}; +use settings::{KeymapFileContent, SettingsStore, DEFAULT_SETTINGS_ASSET_PATH}; use std::{borrow::Cow, str, sync::Arc}; use terminal_view::terminal_button::TerminalButton; use util::{channel::ReleaseChannel, paths, ResultExt}; use uuid::Uuid; +use welcome::BaseKeymap; pub use workspace; use workspace::{ create_and_open_local_file, open_new, sidebar::SidebarSide, AppState, NewFile, NewWindow, - Workspace, + Workspace, WorkspaceSettings, }; #[derive(Deserialize, Clone, PartialEq)] @@ -72,8 +73,6 @@ actions!( ] ); -const MIN_FONT_SIZE: f32 = 6.0; - pub fn init(app_state: &Arc, cx: &mut gpui::AppContext) { cx.add_action(about); cx.add_global_action(|_: &Hide, cx: &mut gpui::AppContext| { @@ -117,30 +116,12 @@ pub fn init(app_state: &Arc, cx: &mut gpui::AppContext) { cx.add_global_action(quit); cx.add_global_action(move |action: &OpenBrowser, cx| cx.platform().open_url(&action.url)); cx.add_global_action(move |_: &IncreaseBufferFontSize, cx| { - cx.update_global::(|settings, cx| { - settings.buffer_font_size = (settings.buffer_font_size + 1.0).max(MIN_FONT_SIZE); - if let Some(terminal_font_size) = settings.terminal_overrides.font_size.as_mut() { - *terminal_font_size = (*terminal_font_size + 1.0).max(MIN_FONT_SIZE); - } - cx.refresh_windows(); - }); + theme::adjust_font_size(cx, |size| *size += 1.0) }); cx.add_global_action(move |_: &DecreaseBufferFontSize, cx| { - cx.update_global::(|settings, cx| { - settings.buffer_font_size = (settings.buffer_font_size - 1.0).max(MIN_FONT_SIZE); - if let Some(terminal_font_size) = settings.terminal_overrides.font_size.as_mut() { - *terminal_font_size = (*terminal_font_size - 1.0).max(MIN_FONT_SIZE); - } - cx.refresh_windows(); - }); - }); - cx.add_global_action(move |_: &ResetBufferFontSize, cx| { - cx.update_global::(|settings, cx| { - settings.buffer_font_size = settings.default_buffer_font_size; - settings.terminal_overrides.font_size = settings.terminal_defaults.font_size; - cx.refresh_windows(); - }); + theme::adjust_font_size(cx, |size| *size -= 1.0) }); + cx.add_global_action(move |_: &ResetBufferFontSize, cx| theme::reset_font_size(cx)); cx.add_global_action(move |_: &install_cli::Install, cx| { cx.spawn(|cx| async move { install_cli::install_cli(&cx) @@ -267,10 +248,7 @@ pub fn init(app_state: &Arc, cx: &mut gpui::AppContext) { } } }); - activity_indicator::init(cx); - lsp_log::init(cx); - call::init(app_state.client.clone(), app_state.user_store.clone(), cx); - settings::KeymapFileContent::load_defaults(cx); + load_default_keymap(cx); } pub fn initialize_workspace( @@ -323,7 +301,7 @@ pub fn initialize_workspace( }); let toggle_terminal = cx.add_view(|cx| TerminalButton::new(workspace_handle.clone(), cx)); - let copilot = cx.add_view(|cx| copilot_button::CopilotButton::new(cx)); + let copilot = cx.add_view(|cx| copilot_button::CopilotButton::new(app_state.fs.clone(), cx)); let diagnostic_summary = cx.add_view(|cx| diagnostics::items::DiagnosticIndicator::new(workspace, cx)); let activity_indicator = @@ -379,7 +357,7 @@ pub fn build_window_options( } fn quit(_: &Quit, cx: &mut gpui::AppContext) { - let should_confirm = cx.global::().confirm_quit; + let should_confirm = settings::get::(cx).confirm_quit; cx.spawn(|mut cx| async move { let mut workspaces = cx .window_ids() @@ -490,6 +468,51 @@ fn open_log_file(workspace: &mut Workspace, cx: &mut ViewContext) { .detach(); } +pub fn load_default_keymap(cx: &mut AppContext) { + for path in ["keymaps/default.json", "keymaps/vim.json"] { + KeymapFileContent::load_asset(path, cx).unwrap(); + } + + if let Some(asset_path) = settings::get::(cx).asset_path() { + KeymapFileContent::load_asset(asset_path, cx).unwrap(); + } +} + +pub fn handle_keymap_file_changes( + mut user_keymap_file_rx: mpsc::UnboundedReceiver, + cx: &mut AppContext, +) { + cx.spawn(move |mut cx| async move { + let mut settings_subscription = None; + while let Some(user_keymap_content) = user_keymap_file_rx.next().await { + if let Ok(keymap_content) = KeymapFileContent::parse(&user_keymap_content) { + cx.update(|cx| { + cx.clear_bindings(); + load_default_keymap(cx); + keymap_content.clone().add_to_cx(cx).log_err(); + }); + + let mut old_base_keymap = cx.read(|cx| *settings::get::(cx)); + drop(settings_subscription); + settings_subscription = Some(cx.update(|cx| { + cx.observe_global::(move |cx| { + let new_base_keymap = *settings::get::(cx); + if new_base_keymap != old_base_keymap { + old_base_keymap = new_base_keymap.clone(); + + cx.clear_bindings(); + load_default_keymap(cx); + keymap_content.clone().add_to_cx(cx).log_err(); + } + }) + .detach(); + })); + } + } + }) + .detach(); +} + fn open_telemetry_log_file(workspace: &mut Workspace, cx: &mut ViewContext) { workspace.with_local_workspace(cx, move |workspace, cx| { let app_state = workspace.app_state().clone(); @@ -591,16 +614,21 @@ mod tests { use super::*; use assets::Assets; use editor::{scroll::autoscroll::Autoscroll, DisplayPoint, Editor}; - use gpui::{executor::Deterministic, AppContext, AssetSource, TestAppContext, ViewHandle}; + use fs::{FakeFs, Fs}; + use gpui::{ + elements::Empty, executor::Deterministic, Action, AnyElement, AppContext, AssetSource, + Element, Entity, TestAppContext, View, ViewHandle, + }; use language::LanguageRegistry; use node_runtime::NodeRuntime; use project::{Project, ProjectPath}; use serde_json::json; + use settings::{handle_settings_file_changes, watch_config_file, SettingsStore}; use std::{ collections::HashSet, path::{Path, PathBuf}, }; - use theme::ThemeRegistry; + use theme::{ThemeRegistry, ThemeSettings}; use util::http::FakeHttpClient; use workspace::{ item::{Item, ItemHandle}, @@ -609,7 +637,7 @@ mod tests { #[gpui::test] async fn test_open_paths_action(cx: &mut TestAppContext) { - let app_state = init(cx); + let app_state = init_test(cx); app_state .fs .as_fake() @@ -709,7 +737,7 @@ mod tests { #[gpui::test] async fn test_window_edit_state(executor: Arc, cx: &mut TestAppContext) { - let app_state = init(cx); + let app_state = init_test(cx); app_state .fs .as_fake() @@ -789,7 +817,7 @@ mod tests { #[gpui::test] async fn test_new_empty_workspace(cx: &mut TestAppContext) { - let app_state = init(cx); + let app_state = init_test(cx); cx.update(|cx| { open_new(&app_state, cx, |workspace, cx| { Editor::new_file(workspace, &Default::default(), cx) @@ -828,7 +856,7 @@ mod tests { #[gpui::test] async fn test_open_entry(cx: &mut TestAppContext) { - let app_state = init(cx); + let app_state = init_test(cx); app_state .fs .as_fake() @@ -941,7 +969,7 @@ mod tests { #[gpui::test] async fn test_open_paths(cx: &mut TestAppContext) { - let app_state = init(cx); + let app_state = init_test(cx); app_state .fs @@ -1111,7 +1139,7 @@ mod tests { #[gpui::test] async fn test_save_conflicting_item(cx: &mut TestAppContext) { - let app_state = init(cx); + let app_state = init_test(cx); app_state .fs .as_fake() @@ -1155,7 +1183,7 @@ mod tests { #[gpui::test] async fn test_open_and_save_new_file(cx: &mut TestAppContext) { - let app_state = init(cx); + let app_state = init_test(cx); app_state.fs.create_dir(Path::new("/root")).await.unwrap(); let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await; @@ -1244,7 +1272,7 @@ mod tests { #[gpui::test] async fn test_setting_language_when_saving_as_single_file_worktree(cx: &mut TestAppContext) { - let app_state = init(cx); + let app_state = init_test(cx); app_state.fs.create_dir(Path::new("/root")).await.unwrap(); let project = Project::test(app_state.fs.clone(), [], cx).await; @@ -1283,9 +1311,7 @@ mod tests { #[gpui::test] async fn test_pane_actions(cx: &mut TestAppContext) { - init(cx); - - let app_state = cx.update(AppState::test); + let app_state = init_test(cx); app_state .fs .as_fake() @@ -1359,7 +1385,7 @@ mod tests { #[gpui::test] async fn test_navigation(cx: &mut TestAppContext) { - let app_state = init(cx); + let app_state = init_test(cx); app_state .fs .as_fake() @@ -1636,7 +1662,7 @@ mod tests { #[gpui::test] async fn test_reopening_closed_items(cx: &mut TestAppContext) { - let app_state = init(cx); + let app_state = init_test(cx); app_state .fs .as_fake() @@ -1811,6 +1837,175 @@ mod tests { } } + #[gpui::test] + async fn test_base_keymap(cx: &mut gpui::TestAppContext) { + struct TestView; + + impl Entity for TestView { + type Event = (); + } + + impl View for TestView { + fn ui_name() -> &'static str { + "TestView" + } + + fn render(&mut self, _: &mut ViewContext) -> AnyElement { + Empty::new().into_any() + } + } + + let executor = cx.background(); + let fs = FakeFs::new(executor.clone()); + + actions!(test, [A, B]); + // From the Atom keymap + actions!(workspace, [ActivatePreviousPane]); + // From the JetBrains keymap + actions!(pane, [ActivatePrevItem]); + + fs.save( + "/settings.json".as_ref(), + &r#" + { + "base_keymap": "Atom" + } + "# + .into(), + Default::default(), + ) + .await + .unwrap(); + + fs.save( + "/keymap.json".as_ref(), + &r#" + [ + { + "bindings": { + "backspace": "test::A" + } + } + ] + "# + .into(), + Default::default(), + ) + .await + .unwrap(); + + cx.update(|cx| { + cx.set_global(SettingsStore::test(cx)); + theme::init(Assets, cx); + welcome::init(cx); + + cx.add_global_action(|_: &A, _cx| {}); + cx.add_global_action(|_: &B, _cx| {}); + cx.add_global_action(|_: &ActivatePreviousPane, _cx| {}); + cx.add_global_action(|_: &ActivatePrevItem, _cx| {}); + + let settings_rx = watch_config_file( + executor.clone(), + fs.clone(), + PathBuf::from("/settings.json"), + ); + let keymap_rx = + watch_config_file(executor.clone(), fs.clone(), PathBuf::from("/keymap.json")); + + handle_keymap_file_changes(keymap_rx, cx); + handle_settings_file_changes(settings_rx, cx); + }); + + cx.foreground().run_until_parked(); + + let (window_id, _view) = cx.add_window(|_| TestView); + + // Test loading the keymap base at all + assert_key_bindings_for( + window_id, + cx, + vec![("backspace", &A), ("k", &ActivatePreviousPane)], + line!(), + ); + + // Test modifying the users keymap, while retaining the base keymap + fs.save( + "/keymap.json".as_ref(), + &r#" + [ + { + "bindings": { + "backspace": "test::B" + } + } + ] + "# + .into(), + Default::default(), + ) + .await + .unwrap(); + + cx.foreground().run_until_parked(); + + assert_key_bindings_for( + window_id, + cx, + vec![("backspace", &B), ("k", &ActivatePreviousPane)], + line!(), + ); + + // Test modifying the base, while retaining the users keymap + fs.save( + "/settings.json".as_ref(), + &r#" + { + "base_keymap": "JetBrains" + } + "# + .into(), + Default::default(), + ) + .await + .unwrap(); + + cx.foreground().run_until_parked(); + + assert_key_bindings_for( + window_id, + cx, + vec![("backspace", &B), ("[", &ActivatePrevItem)], + line!(), + ); + + fn assert_key_bindings_for<'a>( + window_id: usize, + cx: &TestAppContext, + actions: Vec<(&'static str, &'a dyn Action)>, + line: u32, + ) { + for (key, action) in actions { + // assert that... + assert!( + cx.available_actions(window_id, 0) + .into_iter() + .any(|(_, bound_action, b)| { + // action names match... + bound_action.name() == action.name() + && bound_action.namespace() == action.namespace() + // and key strokes contain the given key + && b.iter() + .any(|binding| binding.keystrokes().iter().any(|k| k.key == key)) + }), + "On {} Failed to find {} with key binding {}", + line, + action.name(), + key + ); + } + } + } + #[gpui::test] fn test_bundled_settings_and_themes(cx: &mut AppContext) { cx.platform() @@ -1829,15 +2024,20 @@ mod tests { ]) .unwrap(); let themes = ThemeRegistry::new(Assets, cx.font_cache().clone()); - let settings = Settings::defaults(Assets, cx.font_cache(), &themes); + let mut settings = SettingsStore::default(); + settings + .set_default_settings(&settings::default_settings(), cx) + .unwrap(); + cx.set_global(settings); + theme::init(Assets, cx); let mut has_default_theme = false; for theme_name in themes.list(false).map(|meta| meta.name) { let theme = themes.get(&theme_name).unwrap(); - if theme.meta.name == settings.theme.meta.name { + assert_eq!(theme.meta.name, theme_name); + if theme.meta.name == settings::get::(cx).theme.meta.name { has_default_theme = true; } - assert_eq!(theme.meta.name, theme_name); } assert!(has_default_theme); } @@ -1847,25 +2047,26 @@ mod tests { let mut languages = LanguageRegistry::test(); languages.set_executor(cx.background().clone()); let languages = Arc::new(languages); - let themes = ThemeRegistry::new((), cx.font_cache().clone()); let http = FakeHttpClient::with_404_response(); let node_runtime = NodeRuntime::new(http, cx.background().to_owned()); - languages::init(languages.clone(), themes, node_runtime); + languages::init(languages.clone(), node_runtime); for name in languages.language_names() { languages.language_for_name(&name); } cx.foreground().run_until_parked(); } - fn init(cx: &mut TestAppContext) -> Arc { + fn init_test(cx: &mut TestAppContext) -> Arc { cx.foreground().forbid_parking(); cx.update(|cx| { let mut app_state = AppState::test(cx); let state = Arc::get_mut(&mut app_state).unwrap(); state.initialize_workspace = initialize_workspace; state.build_window_options = build_window_options; + theme::init((), cx); call::init(app_state.client.clone(), app_state.user_store.clone(), cx); workspace::init(app_state.clone(), cx); + language::init(cx); editor::init(cx); pane::init(cx); app_state