diff --git a/.github/workflows/release_actions.yml b/.github/workflows/release_actions.yml index 4ccab09cbe..71909ae177 100644 --- a/.github/workflows/release_actions.yml +++ b/.github/workflows/release_actions.yml @@ -16,8 +16,4 @@ jobs: Restart your Zed or head to https://zed.dev/releases/stable/latest to grab it. - ```md - # Changelog - ${{ github.event.release.body }} - ``` diff --git a/Cargo.lock b/Cargo.lock index cd92d0003a..fe6a104ef2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -318,9 +318,9 @@ dependencies = [ [[package]] name = "async-channel" -version = "1.8.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf46fee83e5ccffc220104713af3292ff9bc7c64c7de289f66dae8e38d826833" +checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35" dependencies = [ "concurrent-queue", "event-listener", @@ -374,7 +374,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "279cf904654eeebfa37ac9bb1598880884924aab82e290aa65c9e77a0e142e06" dependencies = [ "async-lock", - "autocfg 1.1.0", + "autocfg", "blocking", "futures-lite", ] @@ -401,7 +401,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fc5b45d93ef0529756f812ca52e44c221b35341892d3dcc34132ac02f3dd2af" dependencies = [ "async-lock", - "autocfg 1.1.0", + "autocfg", "cfg-if 1.0.0", "concurrent-queue", "futures-lite", @@ -430,7 +430,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4051e67316bc7eff608fe723df5d32ed639946adcd69e07df41fd42a7b411f1f" dependencies = [ "async-io", - "autocfg 1.1.0", + "autocfg", "blocking", "futures-lite", ] @@ -452,7 +452,7 @@ checksum = "7a9d28b1d97e08915212e2e45310d47854eafa69600756fc735fb788f75199c9" dependencies = [ "async-io", "async-lock", - "autocfg 1.1.0", + "autocfg", "blocking", "cfg-if 1.0.0", "event-listener", @@ -481,7 +481,7 @@ checksum = "0e97ce7de6cf12de5d7226c73f5ba9811622f4db3a5b91b55c53e987e5f91cba" dependencies = [ "proc-macro2", "quote", - "syn 2.0.23", + "syn 2.0.25", ] [[package]] @@ -529,7 +529,7 @@ checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" dependencies = [ "proc-macro2", "quote", - "syn 2.0.23", + "syn 2.0.25", ] [[package]] @@ -572,7 +572,7 @@ checksum = "a564d521dd56509c4c47480d00b80ee55f7e385ae48db5744c67ad50c92d2ebf" dependencies = [ "proc-macro2", "quote", - "syn 2.0.23", + "syn 2.0.25", ] [[package]] @@ -658,15 +658,6 @@ dependencies = [ "workspace", ] -[[package]] -name = "autocfg" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dde43e75fd43e8a1bf86103336bc699aa8d17ad1be60c76c0bdfd4828e19b78" -dependencies = [ - "autocfg 1.1.0", -] - [[package]] name = "autocfg" version = "1.1.0" @@ -839,7 +830,7 @@ dependencies = [ "regex", "rustc-hash", "shlex", - "syn 2.0.23", + "syn 2.0.25", "which", ] @@ -1001,7 +992,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6798148dccfbff0fae41c7574d2fa8f1ef3492fba0face179de5d8d447d67b05" dependencies = [ "memchr", - "regex-automata 0.3.1", + "regex-automata 0.3.2", "serde", ] @@ -1295,7 +1286,7 @@ dependencies = [ "heck 0.4.1", "proc-macro2", "quote", - "syn 2.0.23", + "syn 2.0.25", ] [[package]] @@ -1362,7 +1353,7 @@ dependencies = [ "sum_tree", "tempfile", "thiserror", - "time 0.3.22", + "time 0.3.23", "tiny_http", "url", "util", @@ -1464,7 +1455,7 @@ dependencies = [ "sha-1 0.9.8", "sqlx", "theme", - "time 0.3.22", + "time 0.3.23", "tokio", "tokio-tungstenite", "toml", @@ -1506,6 +1497,7 @@ dependencies = [ "theme", "theme_selector", "util", + "vcs_menu", "workspace", "zed-actions", ] @@ -1905,7 +1897,7 @@ version = "0.9.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae211234986c545741a7dc064309f67ee1e5ad243d0e48335adc0484d960bcc7" dependencies = [ - "autocfg 1.1.0", + "autocfg", "cfg-if 1.0.0", "crossbeam-utils", "memoffset 0.9.0", @@ -1994,12 +1986,12 @@ dependencies = [ [[package]] name = "dashmap" -version = "5.4.0" +version = "5.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "907076dfda823b0b36d2a1bb5f90c96660a5bbcd7729e10727f07858f22c4edc" +checksum = "6943ae99c34386c84a470c499d3414f66502a41340aa895406e0d2e4a207b91d" dependencies = [ "cfg-if 1.0.0", - "hashbrown 0.12.3", + "hashbrown 0.14.0", "lock_api", "once_cell", "parking_lot_core 0.9.8", @@ -2322,9 +2314,9 @@ dependencies = [ [[package]] name = "equivalent" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88bffebc5d80432c9b140ee17875ff173a8ab62faad5b257da912bd2f6c1c0a1" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "erased-serde" @@ -2648,7 +2640,7 @@ dependencies = [ "smol", "sum_tree", "tempfile", - "time 0.3.22", + "time 0.3.23", "util", ] @@ -2798,7 +2790,7 @@ checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" dependencies = [ "proc-macro2", "quote", - "syn 2.0.23", + "syn 2.0.25", ] [[package]] @@ -3038,7 +3030,7 @@ dependencies = [ "smol", "sqlez", "sum_tree", - "time 0.3.22", + "time 0.3.23", "tiny-skia", "usvg", "util", @@ -3412,7 +3404,7 @@ version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" dependencies = [ - "autocfg 1.1.0", + "autocfg", "hashbrown 0.12.3", "serde", ] @@ -3537,7 +3529,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" dependencies = [ "hermit-abi 0.3.2", - "rustix 0.38.3", + "rustix 0.38.4", "windows-sys", ] @@ -4023,7 +4015,7 @@ version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16" dependencies = [ - "autocfg 1.1.0", + "autocfg", "scopeguard", ] @@ -4127,7 +4119,7 @@ version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "090126dc04f95dc0d1c1c91f61bdd474b3930ca064c1edc8a849da2c6cbe1e77" dependencies = [ - "autocfg 1.1.0", + "autocfg", "rawpointer", ] @@ -4190,7 +4182,7 @@ version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" dependencies = [ - "autocfg 1.1.0", + "autocfg", ] [[package]] @@ -4199,7 +4191,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" dependencies = [ - "autocfg 1.1.0", + "autocfg", ] [[package]] @@ -4251,7 +4243,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b" dependencies = [ "adler", - "autocfg 1.1.0", + "autocfg", ] [[package]] @@ -4513,18 +4505,17 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f" dependencies = [ - "autocfg 1.1.0", + "autocfg", "num-integer", "num-traits", ] [[package]] name = "num-bigint-dig" -version = "0.7.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4547ee5541c18742396ae2c895d0717d0f886d8823b8399cdaf7b07d63ad0480" +checksum = "f9bc3e36fd683e004fd59c64a425e0e991616f5a8b617c3b9a933a93c168facc" dependencies = [ - "autocfg 0.1.8", "byteorder", "lazy_static", "libm", @@ -4553,7 +4544,7 @@ version = "0.1.45" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" dependencies = [ - "autocfg 1.1.0", + "autocfg", "num-traits", ] @@ -4563,7 +4554,7 @@ version = "0.1.43" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" dependencies = [ - "autocfg 1.1.0", + "autocfg", "num-integer", "num-traits", ] @@ -4574,7 +4565,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "12ac428b1cb17fce6f731001d307d351ec70a6d202fc2e60f7d4c5e42d8f4f07" dependencies = [ - "autocfg 1.1.0", + "autocfg", "num-integer", "num-traits", ] @@ -4585,7 +4576,7 @@ version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" dependencies = [ - "autocfg 1.1.0", + "autocfg", "libm", ] @@ -4742,7 +4733,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.23", + "syn 2.0.25", ] [[package]] @@ -5030,7 +5021,7 @@ checksum = "ec2e072ecce94ec471b13398d5402c188e76ac03cf74dd1a975161b23a3f6d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.23", + "syn 2.0.25", ] [[package]] @@ -5059,16 +5050,16 @@ checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" [[package]] name = "plist" -version = "1.4.3" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bd9647b268a3d3e14ff09c23201133a62589c658db02bb7388c7246aafe0590" +checksum = "bdc0001cfea3db57a2e24bc0d818e9e20e554b5f97fabb9bc231dc240269ae06" dependencies = [ "base64 0.21.2", "indexmap 1.9.3", "line-wrap", "quick-xml", "serde", - "time 0.3.22", + "time 0.3.23", ] [[package]] @@ -5127,7 +5118,7 @@ version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce" dependencies = [ - "autocfg 1.1.0", + "autocfg", "bitflags 1.3.2", "cfg-if 1.0.0", "concurrent-queue", @@ -5183,7 +5174,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92139198957b410250d43fad93e630d956499a625c527eda65175c8680f83387" dependencies = [ "proc-macro2", - "syn 2.0.23", + "syn 2.0.25", ] [[package]] @@ -5231,9 +5222,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.63" +version = "1.0.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b368fba921b0dce7e60f5e04ec15e565b3303972b42bcfde1d0713b881959eb" +checksum = "78803b62cbf1f46fde80d7c0e803111524b9877184cfe7c3033659490ac7a7da" dependencies = [ "unicode-ident", ] @@ -5502,9 +5493,9 @@ dependencies = [ [[package]] name = "quick-xml" -version = "0.28.2" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ce5e73202a820a31f8a0ee32ada5e21029c81fd9e3ebf668a40832e4219d9d1" +checksum = "81b9228215d82c7b61490fec1de287136b5de6f5700f6e58ea9ad61a7964ca51" dependencies = [ "memchr", ] @@ -5742,8 +5733,8 @@ checksum = "b2eae68fc220f7cf2532e4494aded17545fce192d59cd996e0fe7887f4ceb575" dependencies = [ "aho-corasick 1.0.2", "memchr", - "regex-automata 0.3.1", - "regex-syntax 0.7.3", + "regex-automata 0.3.2", + "regex-syntax 0.7.4", ] [[package]] @@ -5757,13 +5748,13 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9aaecc05d5c4b5f7da074b9a0d1a0867e71fd36e7fc0482d8bcfe8e8fc56290" +checksum = "83d3daa6976cffb758ec878f108ba0e062a45b2d6ca3a2cca965338855476caf" dependencies = [ "aho-corasick 1.0.2", "memchr", - "regex-syntax 0.7.3", + "regex-syntax 0.7.4", ] [[package]] @@ -5774,9 +5765,9 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "regex-syntax" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ab07dc67230e4a4718e70fd5c20055a4334b121f1f9db8fe63ef39ce9b8c846" +checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2" [[package]] name = "region" @@ -6054,7 +6045,7 @@ dependencies = [ "proc-macro2", "quote", "rust-embed-utils", - "syn 2.0.23", + "syn 2.0.25", "walkdir", ] @@ -6140,9 +6131,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.3" +version = "0.38.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac5ffa1efe7548069688cd7028f32591853cd7b5b756d41bcffd2353e4fc75b4" +checksum = "0a962918ea88d644592894bc6dc55acc6c0956488adcebbfb6e273506b7fd6e5" dependencies = [ "bitflags 2.3.3", "errno 0.3.1", @@ -6346,7 +6337,7 @@ dependencies = [ "serde_json", "sqlx", "thiserror", - "time 0.3.22", + "time 0.3.23", "tracing", "url", "uuid 1.4.0", @@ -6374,7 +6365,7 @@ dependencies = [ "rust_decimal", "sea-query-derive", "serde_json", - "time 0.3.22", + "time 0.3.23", "uuid 1.4.0", ] @@ -6389,7 +6380,7 @@ dependencies = [ "sea-query", "serde_json", "sqlx", - "time 0.3.22", + "time 0.3.23", "uuid 1.4.0", ] @@ -6511,22 +6502,22 @@ checksum = "5a9f47faea3cad316faa914d013d24f471cd90bfca1a0c70f05a3f42c6441e99" [[package]] name = "serde" -version = "1.0.167" +version = "1.0.171" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7daf513456463b42aa1d94cff7e0c24d682b429f020b9afa4f5ba5c40a22b237" +checksum = "30e27d1e4fd7659406c492fd6cfaf2066ba8773de45ca75e855590f856dc34a9" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.167" +version = "1.0.171" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b69b106b68bc8054f0e974e70d19984040f8a5cf9215ca82626ea4853f82c4b9" +checksum = "389894603bd18c46fa56231694f8d827779c0951a667087194cf9de94ed24682" dependencies = [ "proc-macro2", "quote", - "syn 2.0.23", + "syn 2.0.25", ] [[package]] @@ -6581,7 +6572,7 @@ checksum = "1d89a8107374290037607734c0b73a85db7ed80cae314b3c5791f192a496e731" dependencies = [ "proc-macro2", "quote", - "syn 2.0.23", + "syn 2.0.25", ] [[package]] @@ -6807,7 +6798,7 @@ version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" dependencies = [ - "autocfg 1.1.0", + "autocfg", ] [[package]] @@ -7000,7 +6991,7 @@ dependencies = [ "sqlx-rt", "stringprep", "thiserror", - "time 0.3.22", + "time 0.3.23", "tokio-stream", "url", "uuid 1.4.0", @@ -7249,9 +7240,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.23" +version = "2.0.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59fb7d6d8281a51045d62b8eb3a7d1ce347b76f312af50cd3dc0af39c87c1737" +checksum = "15e3fc8c0c74267e2df136e5e5fb656a464158aa57624053375eb9c8c6e25ae2" dependencies = [ "proc-macro2", "quote", @@ -7319,9 +7310,9 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "target-lexicon" -version = "0.12.8" +version = "0.12.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b1c7f239eb94671427157bd93b3694320f3668d4e1eff08c7285366fd777fac" +checksum = "df8e77cb757a61f51b947ec4a7e3646efd825b73561db1c232a8ccb639e611a0" [[package]] name = "tempdir" @@ -7339,7 +7330,7 @@ version = "3.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "31c0432476357e58790aaa47a8efb0c5138f137343f3b5f23bd36a27e3b0a6d6" dependencies = [ - "autocfg 1.1.0", + "autocfg", "cfg-if 1.0.0", "fastrand", "redox_syscall 0.3.5", @@ -7505,7 +7496,7 @@ checksum = "463fe12d7993d3b327787537ce8dd4dfa058de32fc2b195ef3cde03dc4771e8f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.23", + "syn 2.0.25", ] [[package]] @@ -7578,9 +7569,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.22" +version = "0.3.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea9e1b3cf1243ae005d9e74085d4d542f3125458f3a81af210d901dcd7411efd" +checksum = "59e399c068f43a5d116fedaf73b203fa4f9c519f17e2b34f63221d3792f81446" dependencies = [ "itoa 1.0.8", "serde", @@ -7596,9 +7587,9 @@ checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb" [[package]] name = "time-macros" -version = "0.2.9" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "372950940a5f07bf38dbe211d7283c9e6d7327df53794992d293e534c733d09b" +checksum = "96ba15a897f3c86766b757e5ac7221554c6750054d74d5b28844fce5fb36a6c4" dependencies = [ "time-core", ] @@ -7651,7 +7642,7 @@ version = "1.29.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "532826ff75199d5833b9d2c5fe410f29235e25704ee5f0ef599fb51c21f4a4da" dependencies = [ - "autocfg 1.1.0", + "autocfg", "backtrace", "bytes 1.4.0", "libc", @@ -7694,7 +7685,7 @@ checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.23", + "syn 2.0.25", ] [[package]] @@ -7899,7 +7890,7 @@ checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab" dependencies = [ "proc-macro2", "quote", - "syn 2.0.23", + "syn 2.0.25", ] [[package]] @@ -8483,6 +8474,19 @@ version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" +[[package]] +name = "vcs_menu" +version = "0.1.0" +dependencies = [ + "anyhow", + "fuzzy", + "gpui", + "picker", + "theme", + "util", + "workspace", +] + [[package]] name = "vector_store" version = "0.1.0" @@ -8678,7 +8682,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.23", + "syn 2.0.25", "wasm-bindgen-shared", ] @@ -8712,7 +8716,7 @@ checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.23", + "syn 2.0.25", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -8725,9 +8729,9 @@ checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" [[package]] name = "wasm-encoder" -version = "0.29.0" +version = "0.30.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18c41dbd92eaebf3612a39be316540b8377c871cb9bde6b064af962984912881" +checksum = "b2f8e9778e04cbf44f58acc301372577375a666b966c50b03ef46144f80436a8" dependencies = [ "leb128", ] @@ -8949,9 +8953,9 @@ dependencies = [ [[package]] name = "wast" -version = "60.0.0" +version = "61.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd06cc744b536e30387e72a48fdd492105b9c938bb4f415c39c616a7a0a697ad" +checksum = "dc6b347851b52fd500657d301155c79e8c67595501d179cef87b6f04ebd25ac4" dependencies = [ "leb128", "memchr", @@ -8961,11 +8965,11 @@ dependencies = [ [[package]] name = "wat" -version = "1.0.66" +version = "1.0.67" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5abe520f0ab205366e9ac7d3e6b2fc71de44e32a2b58f2ec871b6b575bdcea3b" +checksum = "459e764d27c3ab7beba1ebd617cc025c7e76dea6e7c5ce3189989a970aea3491" dependencies = [ - "wast 60.0.0", + "wast 61.0.0", ] [[package]] @@ -9295,9 +9299,9 @@ checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" [[package]] name = "winnow" -version = "0.4.8" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9482fe6ceabdf32f3966bfdd350ba69256a97c30253dc616fe0005af24f164e" +checksum = "81a2094c43cc94775293eaa0e499fbc30048a6d824ac82c0351a8c0bf9112529" dependencies = [ "memchr", ] @@ -9601,7 +9605,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.23", + "syn 2.0.25", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 2c82120780..983f91081a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -65,6 +65,7 @@ members = [ "crates/util", "crates/vector_store", "crates/vim", + "crates/vcs_menu", "crates/workspace", "crates/welcome", "crates/xtask", diff --git a/assets/icons/radix/maximize.svg b/assets/icons/radix/maximize.svg new file mode 100644 index 0000000000..f37f6a2087 --- /dev/null +++ b/assets/icons/radix/maximize.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/icons/radix/minimize.svg b/assets/icons/radix/minimize.svg new file mode 100644 index 0000000000..ec78f152e1 --- /dev/null +++ b/assets/icons/radix/minimize.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/keymaps/default.json b/assets/keymaps/default.json index 6fc06198fe..8c3a1f407c 100644 --- a/assets/keymaps/default.json +++ b/assets/keymaps/default.json @@ -39,6 +39,7 @@ "cmd-shift-n": "workspace::NewWindow", "cmd-o": "workspace::Open", "alt-cmd-o": "projects::OpenRecent", + "alt-cmd-b": "branches::OpenRecent", "ctrl-~": "workspace::NewTerminal", "ctrl-`": "terminal_panel::ToggleFocus", "shift-escape": "workspace::ToggleZoom" diff --git a/assets/keymaps/textmate.json b/assets/keymaps/textmate.json index 591d6e443f..1f28c05158 100644 --- a/assets/keymaps/textmate.json +++ b/assets/keymaps/textmate.json @@ -2,6 +2,7 @@ { "bindings": { "cmd-shift-o": "projects::OpenRecent", + "cmd-shift-b": "branches::OpenRecent", "cmd-alt-tab": "project_panel::ToggleFocus" } }, diff --git a/assets/keymaps/vim.json b/assets/keymaps/vim.json index afee6fcd2e..639daef614 100644 --- a/assets/keymaps/vim.json +++ b/assets/keymaps/vim.json @@ -35,8 +35,11 @@ "l": "vim::Right", "right": "vim::Right", "$": "vim::EndOfLine", + "^": "vim::FirstNonWhitespace", "shift-g": "vim::EndOfDocument", "w": "vim::NextWordStart", + "{": "vim::StartOfParagraph", + "}": "vim::EndOfParagraph", "shift-w": [ "vim::NextWordStart", { @@ -92,7 +95,10 @@ ], "ctrl-o": "pane::GoBack", "ctrl-]": "editor::GoToDefinition", - "escape": "editor::Cancel", + "escape": [ + "vim::SwitchMode", + "Normal" + ], "0": "vim::StartOfLine", // When no number operator present, use start of line motion "1": [ "vim::Number", @@ -165,7 +171,6 @@ "shift-a": "vim::InsertEndOfLine", "x": "vim::DeleteRight", "shift-x": "vim::DeleteLeft", - "^": "vim::FirstNonWhitespace", "o": "vim::InsertLineBelow", "shift-o": "vim::InsertLineAbove", "~": "vim::ChangeCase", @@ -305,6 +310,10 @@ "vim::PushOperator", "Replace" ], + "ctrl-c": [ + "vim::SwitchMode", + "Normal" + ], "> >": "editor::Indent", "< <": "editor::Outdent" } @@ -321,7 +330,10 @@ "bindings": { "tab": "vim::Tab", "enter": "vim::Enter", - "escape": "editor::Cancel" + "escape": [ + "vim::SwitchMode", + "Normal" + ] } } ] diff --git a/crates/ai/src/assistant.rs b/crates/ai/src/assistant.rs index 4d300230e1..35c88486f7 100644 --- a/crates/ai/src/assistant.rs +++ b/crates/ai/src/assistant.rs @@ -2061,6 +2061,8 @@ impl ConversationEditor { let remaining_tokens = self.conversation.read(cx).remaining_tokens()?; let remaining_tokens_style = if remaining_tokens <= 0 { &style.no_remaining_tokens + } else if remaining_tokens <= 500 { + &style.low_remaining_tokens } else { &style.remaining_tokens }; diff --git a/crates/call/src/call.rs b/crates/call/src/call.rs index 0a8f150194..ed5e560218 100644 --- a/crates/call/src/call.rs +++ b/crates/call/src/call.rs @@ -4,7 +4,7 @@ pub mod room; use std::sync::Arc; use anyhow::{anyhow, Result}; -use client::{proto, Client, TypedEnvelope, User, UserStore}; +use client::{proto, ClickhouseEvent, Client, TelemetrySettings, TypedEnvelope, User, UserStore}; use collections::HashSet; use futures::{future::Shared, FutureExt}; use postage::watch; @@ -198,6 +198,7 @@ impl ActiveCall { let result = invite.await; this.update(&mut cx, |this, cx| { this.pending_invites.remove(&called_user_id); + this.report_call_event("invite", cx); cx.notify(); }); result @@ -243,21 +244,26 @@ impl ActiveCall { }; let join = Room::join(&call, self.client.clone(), self.user_store.clone(), cx); + cx.spawn(|this, mut cx| async move { let room = join.await?; this.update(&mut cx, |this, cx| this.set_room(Some(room.clone()), cx)) .await?; + this.update(&mut cx, |this, cx| { + this.report_call_event("accept incoming", cx) + }); Ok(()) }) } - pub fn decline_incoming(&mut self) -> Result<()> { + pub fn decline_incoming(&mut self, cx: &mut ModelContext) -> Result<()> { let call = self .incoming_call .0 .borrow_mut() .take() .ok_or_else(|| anyhow!("no incoming call"))?; + self.report_call_event_for_room("decline incoming", call.room_id, cx); self.client.send(proto::DeclineCall { room_id: call.room_id, })?; @@ -266,6 +272,7 @@ impl ActiveCall { pub fn hang_up(&mut self, cx: &mut ModelContext) -> Task> { cx.notify(); + self.report_call_event("hang up", cx); if let Some((room, _)) = self.room.take() { room.update(cx, |room, cx| room.leave(cx)) } else { @@ -273,12 +280,28 @@ impl ActiveCall { } } + pub fn toggle_screen_sharing(&self, cx: &mut AppContext) { + if let Some(room) = self.room().cloned() { + let toggle_screen_sharing = room.update(cx, |room, cx| { + if room.is_screen_sharing() { + self.report_call_event("disable screen share", cx); + Task::ready(room.unshare_screen(cx)) + } else { + self.report_call_event("enable screen share", cx); + room.share_screen(cx) + } + }); + toggle_screen_sharing.detach_and_log_err(cx); + } + } + pub fn share_project( &mut self, project: ModelHandle, cx: &mut ModelContext, ) -> Task> { if let Some((room, _)) = self.room.as_ref() { + self.report_call_event("share project", cx); room.update(cx, |room, cx| room.share_project(project, cx)) } else { Task::ready(Err(anyhow!("no active call"))) @@ -291,6 +314,7 @@ impl ActiveCall { cx: &mut ModelContext, ) -> Result<()> { if let Some((room, _)) = self.room.as_ref() { + self.report_call_event("unshare project", cx); room.update(cx, |room, cx| room.unshare_project(project, cx)) } else { Err(anyhow!("no active call")) @@ -352,4 +376,19 @@ impl ActiveCall { pub fn pending_invites(&self) -> &HashSet { &self.pending_invites } + + fn report_call_event(&self, operation: &'static str, cx: &AppContext) { + if let Some(room) = self.room() { + self.report_call_event_for_room(operation, room.read(cx).id(), cx) + } + } + + fn report_call_event_for_room(&self, operation: &'static str, room_id: u64, cx: &AppContext) { + let telemetry = self.client.telemetry(); + let telemetry_settings = *settings::get::(cx); + + let event = ClickhouseEvent::Call { operation, room_id }; + + telemetry.report_clickhouse_event(event, telemetry_settings); + } } diff --git a/crates/cli/src/main.rs b/crates/cli/src/main.rs index bdf677512c..2f742814a8 100644 --- a/crates/cli/src/main.rs +++ b/crates/cli/src/main.rs @@ -201,6 +201,7 @@ impl Bundle { self.zed_version_string() ); } + Self::LocalPath { executable, .. } => { let executable_parent = executable .parent() diff --git a/crates/client/src/telemetry.rs b/crates/client/src/telemetry.rs index 9c4e187dbc..959f4cc783 100644 --- a/crates/client/src/telemetry.rs +++ b/crates/client/src/telemetry.rs @@ -70,6 +70,10 @@ pub enum ClickhouseEvent { suggestion_accepted: bool, file_extension: Option, }, + Call { + operation: &'static str, + room_id: u64, + }, } #[cfg(debug_assertions)] diff --git a/crates/collab/src/tests/integration_tests.rs b/crates/collab/src/tests/integration_tests.rs index 66dc19d690..c32129818f 100644 --- a/crates/collab/src/tests/integration_tests.rs +++ b/crates/collab/src/tests/integration_tests.rs @@ -157,7 +157,7 @@ async fn test_basic_calls( // User C receives the call, but declines it. let call_c = incoming_call_c.next().await.unwrap().unwrap(); assert_eq!(call_c.calling_user.github_login, "user_b"); - active_call_c.update(cx_c, |call, _| call.decline_incoming().unwrap()); + active_call_c.update(cx_c, |call, cx| call.decline_incoming(cx).unwrap()); assert!(incoming_call_c.next().await.unwrap().is_none()); deterministic.run_until_parked(); @@ -1080,7 +1080,7 @@ async fn test_calls_on_multiple_connections( // User B declines the call on one of the two connections, causing both connections // to stop ringing. - active_call_b2.update(cx_b2, |call, _| call.decline_incoming().unwrap()); + active_call_b2.update(cx_b2, |call, cx| call.decline_incoming(cx).unwrap()); deterministic.run_until_parked(); assert!(incoming_call_b1.next().await.unwrap().is_none()); assert!(incoming_call_b2.next().await.unwrap().is_none()); @@ -5945,7 +5945,7 @@ async fn test_contacts( [("user_b".to_string(), "online", "busy")] ); - active_call_b.update(cx_b, |call, _| call.decline_incoming().unwrap()); + active_call_b.update(cx_b, |call, cx| call.decline_incoming(cx).unwrap()); deterministic.run_until_parked(); assert_eq!( contacts(&client_a, cx_a), diff --git a/crates/collab/src/tests/randomized_integration_tests.rs b/crates/collab/src/tests/randomized_integration_tests.rs index f5dfe17d6f..8062a12b83 100644 --- a/crates/collab/src/tests/randomized_integration_tests.rs +++ b/crates/collab/src/tests/randomized_integration_tests.rs @@ -365,7 +365,7 @@ async fn apply_client_operation( } log::info!("{}: declining incoming call", client.username); - active_call.update(cx, |call, _| call.decline_incoming())?; + active_call.update(cx, |call, cx| call.decline_incoming(cx))?; } ClientOperation::LeaveCall => { diff --git a/crates/collab_ui/Cargo.toml b/crates/collab_ui/Cargo.toml index f81885c07a..4a38c2691c 100644 --- a/crates/collab_ui/Cargo.toml +++ b/crates/collab_ui/Cargo.toml @@ -39,6 +39,7 @@ recent_projects = {path = "../recent_projects"} settings = { path = "../settings" } theme = { path = "../theme" } theme_selector = { path = "../theme_selector" } +vcs_menu = { path = "../vcs_menu" } util = { path = "../util" } workspace = { path = "../workspace" } zed-actions = {path = "../zed-actions"} diff --git a/crates/collab_ui/src/collab_titlebar_item.rs b/crates/collab_ui/src/collab_titlebar_item.rs index 73450e7c7d..6cfc9d8e30 100644 --- a/crates/collab_ui/src/collab_titlebar_item.rs +++ b/crates/collab_ui/src/collab_titlebar_item.rs @@ -1,8 +1,5 @@ use crate::{ - branch_list::{build_branch_list, BranchList}, - contact_notification::ContactNotification, - contacts_popover, - face_pile::FacePile, + contact_notification::ContactNotification, contacts_popover, face_pile::FacePile, toggle_deafen, toggle_mute, toggle_screen_sharing, LeaveCall, ToggleDeafen, ToggleMute, ToggleScreenSharing, }; @@ -27,6 +24,7 @@ use recent_projects::{build_recent_projects, RecentProjects}; use std::{ops::Range, sync::Arc}; use theme::{AvatarStyle, Theme}; use util::ResultExt; +use vcs_menu::{build_branch_list, BranchList, OpenRecent as ToggleVcsMenu}; use workspace::{FollowNextCollaborator, Workspace, WORKSPACE_DB}; const MAX_PROJECT_NAME_LENGTH: usize = 40; @@ -37,7 +35,6 @@ actions!( [ ToggleContactsMenu, ToggleUserMenu, - ToggleVcsMenu, ToggleProjectMenu, SwitchBranch, ShareProject, @@ -229,15 +226,23 @@ impl CollabTitlebarItem { let mut ret = Flex::row().with_child( Stack::new() .with_child( - MouseEventHandler::::new(0, cx, |mouse_state, _| { + MouseEventHandler::::new(0, cx, |mouse_state, cx| { let style = project_style .in_state(self.project_popover.is_some()) .style_for(mouse_state); + enum RecentProjectsTooltip {} Label::new(name, style.text.clone()) .contained() .with_style(style.container) .aligned() .left() + .with_tooltip::( + 0, + "Recent projects".into(), + Some(Box::new(recent_projects::OpenRecent)), + theme.tooltip.clone(), + cx, + ) .into_any_named("title-project-name") }) .with_cursor_style(CursorStyle::PointingHand) @@ -264,7 +269,8 @@ impl CollabTitlebarItem { MouseEventHandler::::new( 0, cx, - |mouse_state, _| { + |mouse_state, cx| { + enum BranchPopoverTooltip {} let style = git_style .in_state(self.branch_popover.is_some()) .style_for(mouse_state); @@ -274,6 +280,13 @@ impl CollabTitlebarItem { .with_margin_right(item_spacing) .aligned() .left() + .with_tooltip::( + 0, + "Recent branches".into(), + Some(Box::new(ToggleVcsMenu)), + theme.tooltip.clone(), + cx, + ) .into_any_named("title-project-branch") }, ) diff --git a/crates/collab_ui/src/collab_ui.rs b/crates/collab_ui/src/collab_ui.rs index 26d9c70a43..3f5ca17a20 100644 --- a/crates/collab_ui/src/collab_ui.rs +++ b/crates/collab_ui/src/collab_ui.rs @@ -1,4 +1,3 @@ -mod branch_list; mod collab_titlebar_item; mod contact_finder; mod contact_list; @@ -12,7 +11,7 @@ mod sharing_status_indicator; use call::{ActiveCall, Room}; pub use collab_titlebar_item::{CollabTitlebarItem, ToggleContactsMenu}; -use gpui::{actions, AppContext, Task}; +use gpui::{actions, AppContext}; use std::sync::Arc; use util::ResultExt; use workspace::AppState; @@ -29,7 +28,7 @@ actions!( ); pub fn init(app_state: &Arc, cx: &mut AppContext) { - branch_list::init(cx); + vcs_menu::init(cx); collab_titlebar_item::init(cx); contact_list::init(cx); contact_finder::init(cx); @@ -45,16 +44,9 @@ pub fn init(app_state: &Arc, cx: &mut AppContext) { } pub fn toggle_screen_sharing(_: &ToggleScreenSharing, cx: &mut AppContext) { - if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() { - let toggle_screen_sharing = room.update(cx, |room, cx| { - if room.is_screen_sharing() { - Task::ready(room.unshare_screen(cx)) - } else { - room.share_screen(cx) - } - }); - toggle_screen_sharing.detach_and_log_err(cx); - } + ActiveCall::global(cx).update(cx, |call, cx| { + call.toggle_screen_sharing(cx); + }); } pub fn toggle_mute(_: &ToggleMute, cx: &mut AppContext) { diff --git a/crates/collab_ui/src/incoming_call_notification.rs b/crates/collab_ui/src/incoming_call_notification.rs index 12fad467e3..4066b5b229 100644 --- a/crates/collab_ui/src/incoming_call_notification.rs +++ b/crates/collab_ui/src/incoming_call_notification.rs @@ -99,8 +99,8 @@ impl IncomingCallNotification { }) .detach_and_log_err(cx); } else { - active_call.update(cx, |active_call, _| { - active_call.decline_incoming().log_err(); + active_call.update(cx, |active_call, cx| { + active_call.decline_incoming(cx).log_err(); }); } } diff --git a/crates/db/src/db.rs b/crates/db/src/db.rs index 798dfbc17f..7b4aa74a80 100644 --- a/crates/db/src/db.rs +++ b/crates/db/src/db.rs @@ -41,8 +41,7 @@ const FALLBACK_DB_NAME: &'static str = "FALLBACK_MEMORY_DB"; const DB_FILE_NAME: &'static str = "db.sqlite"; lazy_static::lazy_static! { - // !!!!!!! CHANGE BACK TO DEFAULT FALSE BEFORE SHIPPING - static ref ZED_STATELESS: bool = std::env::var("ZED_STATELESS").map_or(false, |v| !v.is_empty()); + pub static ref ZED_STATELESS: bool = std::env::var("ZED_STATELESS").map_or(false, |v| !v.is_empty()); pub static ref BACKUP_DB_PATH: RwLock> = RwLock::new(None); pub static ref ALL_FILE_DB_FAILED: AtomicBool = AtomicBool::new(false); } diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index e979bd9c1e..85a428d801 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -5123,7 +5123,7 @@ impl Editor { self.change_selections(Some(Autoscroll::fit()), cx, |s| { s.move_with(|map, selection| { selection.collapse_to( - movement::start_of_paragraph(map, selection.head()), + movement::start_of_paragraph(map, selection.head(), 1), SelectionGoal::None, ) }); @@ -5143,7 +5143,7 @@ impl Editor { self.change_selections(Some(Autoscroll::fit()), cx, |s| { s.move_with(|map, selection| { selection.collapse_to( - movement::end_of_paragraph(map, selection.head()), + movement::end_of_paragraph(map, selection.head(), 1), SelectionGoal::None, ) }); @@ -5162,7 +5162,10 @@ impl Editor { self.change_selections(Some(Autoscroll::fit()), cx, |s| { s.move_heads_with(|map, head, _| { - (movement::start_of_paragraph(map, head), SelectionGoal::None) + ( + movement::start_of_paragraph(map, head, 1), + SelectionGoal::None, + ) }); }) } @@ -5179,7 +5182,10 @@ impl Editor { self.change_selections(Some(Autoscroll::fit()), cx, |s| { s.move_heads_with(|map, head, _| { - (movement::end_of_paragraph(map, head), SelectionGoal::None) + ( + movement::end_of_paragraph(map, head, 1), + SelectionGoal::None, + ) }); }) } @@ -7216,6 +7222,47 @@ impl Editor { } results } + pub fn background_highlights_in_range_for( + &self, + search_range: Range, + display_snapshot: &DisplaySnapshot, + theme: &Theme, + ) -> Vec<(Range, Color)> { + let mut results = Vec::new(); + let buffer = &display_snapshot.buffer_snapshot; + let Some((color_fetcher, ranges)) = self.background_highlights + .get(&TypeId::of::()) else { + return vec![]; + }; + + let color = color_fetcher(theme); + let start_ix = match ranges.binary_search_by(|probe| { + let cmp = probe.end.cmp(&search_range.start, buffer); + if cmp.is_gt() { + Ordering::Greater + } else { + Ordering::Less + } + }) { + Ok(i) | Err(i) => i, + }; + for range in &ranges[start_ix..] { + if range.start.cmp(&search_range.end, buffer).is_ge() { + break; + } + let start = range + .start + .to_point(buffer) + .to_display_point(display_snapshot); + let end = range + .end + .to_point(buffer) + .to_display_point(display_snapshot); + results.push((start..end, color)) + } + + results + } pub fn highlight_text( &mut self, @@ -7518,7 +7565,7 @@ impl Editor { fn report_editor_event( &self, - name: &'static str, + operation: &'static str, file_extension: Option, cx: &AppContext, ) { @@ -7555,7 +7602,7 @@ impl Editor { let event = ClickhouseEvent::Editor { file_extension, vim_mode, - operation: name, + operation, copilot_enabled, copilot_enabled_for_language, }; diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index 9e726d6cc4..7b36287dca 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -22,7 +22,10 @@ use language::{ BracketPairConfig, FakeLspAdapter, LanguageConfig, LanguageRegistry, Point, }; use parking_lot::Mutex; +use project::project_settings::{LspSettings, ProjectSettings}; use project::FakeFs; +use std::sync::atomic; +use std::sync::atomic::AtomicUsize; use std::{cell::RefCell, future::Future, rc::Rc, time::Instant}; use unindent::Unindent; use util::{ @@ -1796,7 +1799,7 @@ async fn test_newline_comments(cx: &mut gpui::TestAppContext) { "}); } // Ensure that comment continuations can be disabled. - update_test_settings(cx, |settings| { + update_test_language_settings(cx, |settings| { settings.defaults.extend_comment_on_newline = Some(false); }); let mut cx = EditorTestContext::new(cx).await; @@ -4546,7 +4549,7 @@ 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 overridden tabsize is sent to language server - update_test_settings(cx, |settings| { + update_test_language_settings(cx, |settings| { settings.languages.insert( "Rust".into(), LanguageSettingsContent { @@ -4660,7 +4663,7 @@ 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 overridden tabsize is sent to language server - update_test_settings(cx, |settings| { + update_test_language_settings(cx, |settings| { settings.languages.insert( "Rust".into(), LanguageSettingsContent { @@ -7084,6 +7087,142 @@ async fn test_on_type_formatting_not_triggered(cx: &mut gpui::TestAppContext) { }); } +#[gpui::test] +async fn test_language_server_restart_due_to_settings_change(cx: &mut gpui::TestAppContext) { + init_test(cx, |_| {}); + + let language_name: Arc = "Rust".into(); + let mut language = Language::new( + LanguageConfig { + name: Arc::clone(&language_name), + path_suffixes: vec!["rs".to_string()], + ..Default::default() + }, + Some(tree_sitter_rust::language()), + ); + + let server_restarts = Arc::new(AtomicUsize::new(0)); + let closure_restarts = Arc::clone(&server_restarts); + let language_server_name = "test language server"; + let mut fake_servers = language + .set_fake_lsp_adapter(Arc::new(FakeLspAdapter { + name: language_server_name, + initialization_options: Some(json!({ + "testOptionValue": true + })), + initializer: Some(Box::new(move |fake_server| { + let task_restarts = Arc::clone(&closure_restarts); + fake_server.handle_request::(move |_, _| { + task_restarts.fetch_add(1, atomic::Ordering::Release); + futures::future::ready(Ok(())) + }); + })), + ..Default::default() + })) + .await; + + let fs = FakeFs::new(cx.background()); + fs.insert_tree( + "/a", + json!({ + "main.rs": "fn main() { let a = 5; }", + "other.rs": "// Test file", + }), + ) + .await; + let project = Project::test(fs, ["/a".as_ref()], cx).await; + project.update(cx, |project, _| project.languages().add(Arc::new(language))); + let (_, _workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); + let _buffer = project + .update(cx, |project, cx| { + project.open_local_buffer("/a/main.rs", cx) + }) + .await + .unwrap(); + let _fake_server = fake_servers.next().await.unwrap(); + update_test_language_settings(cx, |language_settings| { + language_settings.languages.insert( + Arc::clone(&language_name), + LanguageSettingsContent { + tab_size: NonZeroU32::new(8), + ..Default::default() + }, + ); + }); + cx.foreground().run_until_parked(); + assert_eq!( + server_restarts.load(atomic::Ordering::Acquire), + 0, + "Should not restart LSP server on an unrelated change" + ); + + update_test_project_settings(cx, |project_settings| { + project_settings.lsp.insert( + "Some other server name".into(), + LspSettings { + initialization_options: Some(json!({ + "some other init value": false + })), + }, + ); + }); + cx.foreground().run_until_parked(); + assert_eq!( + server_restarts.load(atomic::Ordering::Acquire), + 0, + "Should not restart LSP server on an unrelated LSP settings change" + ); + + update_test_project_settings(cx, |project_settings| { + project_settings.lsp.insert( + language_server_name.into(), + LspSettings { + initialization_options: Some(json!({ + "anotherInitValue": false + })), + }, + ); + }); + cx.foreground().run_until_parked(); + assert_eq!( + server_restarts.load(atomic::Ordering::Acquire), + 1, + "Should restart LSP server on a related LSP settings change" + ); + + update_test_project_settings(cx, |project_settings| { + project_settings.lsp.insert( + language_server_name.into(), + LspSettings { + initialization_options: Some(json!({ + "anotherInitValue": false + })), + }, + ); + }); + cx.foreground().run_until_parked(); + assert_eq!( + server_restarts.load(atomic::Ordering::Acquire), + 1, + "Should not restart LSP server on a related LSP settings change that is the same" + ); + + update_test_project_settings(cx, |project_settings| { + project_settings.lsp.insert( + language_server_name.into(), + LspSettings { + initialization_options: None, + }, + ); + }); + cx.foreground().run_until_parked(); + assert_eq!( + server_restarts.load(atomic::Ordering::Acquire), + 2, + "Should restart LSP server on another related LSP settings change" + ); +} + fn empty_range(row: usize, column: usize) -> Range { let point = DisplayPoint::new(row as u32, column as u32); point..point @@ -7203,7 +7342,7 @@ fn handle_copilot_completion_request( }); } -pub(crate) fn update_test_settings( +pub(crate) fn update_test_language_settings( cx: &mut TestAppContext, f: impl Fn(&mut AllLanguageSettingsContent), ) { @@ -7214,6 +7353,17 @@ pub(crate) fn update_test_settings( }); } +pub(crate) fn update_test_project_settings( + cx: &mut TestAppContext, + f: impl Fn(&mut ProjectSettings), +) { + 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(); @@ -7227,5 +7377,5 @@ pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsC crate::init(cx); }); - update_test_settings(cx, f); + update_test_language_settings(cx, f); } diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index e96f1efe92..f0bae9533b 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -1086,11 +1086,13 @@ impl EditorElement { }) } }; - for (row, _) in &editor.background_highlights_in_range( - start_anchor..end_anchor, - &layout.position_map.snapshot, - &theme, - ) { + for (row, _) in &editor + .background_highlights_in_range_for::( + start_anchor..end_anchor, + &layout.position_map.snapshot, + &theme, + ) + { let start_display = row.start; let end_display = row.end; @@ -2149,6 +2151,9 @@ impl Element for EditorElement { ShowScrollbar::Auto => { // Git (is_singleton && scrollbar_settings.git_diff && snapshot.buffer_snapshot.has_git_diffs()) + || + // Selections + (is_singleton && scrollbar_settings.selections && !highlighted_ranges.is_empty()) // Scrollmanager || editor.scroll_manager.scrollbars_visible() } @@ -2911,7 +2916,7 @@ mod tests { use super::*; use crate::{ display_map::{BlockDisposition, BlockProperties}, - editor_tests::{init_test, update_test_settings}, + editor_tests::{init_test, update_test_language_settings}, Editor, MultiBuffer, }; use gpui::TestAppContext; @@ -3108,7 +3113,7 @@ mod tests { let resize_step = 10.0; let mut editor_width = 200.0; while editor_width <= 1000.0 { - update_test_settings(cx, |s| { + update_test_language_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); diff --git a/crates/editor/src/inlay_hint_cache.rs b/crates/editor/src/inlay_hint_cache.rs index 70fb372504..52473f9971 100644 --- a/crates/editor/src/inlay_hint_cache.rs +++ b/crates/editor/src/inlay_hint_cache.rs @@ -847,7 +847,7 @@ mod tests { use text::Point; use workspace::Workspace; - use crate::editor_tests::update_test_settings; + use crate::editor_tests::update_test_language_settings; use super::*; @@ -1476,7 +1476,7 @@ mod tests { ), ] { edits_made += 1; - update_test_settings(cx, |settings| { + update_test_language_settings(cx, |settings| { settings.defaults.inlay_hints = Some(InlayHintSettings { enabled: true, show_type_hints: new_allowed_hint_kinds.contains(&Some(InlayHintKind::Type)), @@ -1520,7 +1520,7 @@ mod tests { edits_made += 1; let another_allowed_hint_kinds = HashSet::from_iter([Some(InlayHintKind::Type)]); - update_test_settings(cx, |settings| { + update_test_language_settings(cx, |settings| { settings.defaults.inlay_hints = Some(InlayHintSettings { enabled: false, show_type_hints: another_allowed_hint_kinds.contains(&Some(InlayHintKind::Type)), @@ -1577,7 +1577,7 @@ mod tests { let final_allowed_hint_kinds = HashSet::from_iter([Some(InlayHintKind::Parameter)]); edits_made += 1; - update_test_settings(cx, |settings| { + update_test_language_settings(cx, |settings| { settings.defaults.inlay_hints = Some(InlayHintSettings { enabled: true, show_type_hints: final_allowed_hint_kinds.contains(&Some(InlayHintKind::Type)), @@ -2269,7 +2269,7 @@ unedited (2nd) buffer should have the same hint"); crate::init(cx); }); - update_test_settings(cx, f); + update_test_language_settings(cx, f); } async fn prepare_test_objects( diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs index 74b8e0ddb6..431ccf0bfe 100644 --- a/crates/editor/src/items.rs +++ b/crates/editor/src/items.rs @@ -883,7 +883,7 @@ impl ProjectItem for Editor { } } -enum BufferSearchHighlights {} +pub(crate) enum BufferSearchHighlights {} impl SearchableItem for Editor { type Match = Range; diff --git a/crates/editor/src/movement.rs b/crates/editor/src/movement.rs index e50d7d8306..1bd37da52f 100644 --- a/crates/editor/src/movement.rs +++ b/crates/editor/src/movement.rs @@ -193,7 +193,11 @@ pub fn next_subword_end(map: &DisplaySnapshot, point: DisplayPoint) -> DisplayPo }) } -pub fn start_of_paragraph(map: &DisplaySnapshot, display_point: DisplayPoint) -> DisplayPoint { +pub fn start_of_paragraph( + map: &DisplaySnapshot, + display_point: DisplayPoint, + mut count: usize, +) -> DisplayPoint { let point = display_point.to_point(map); if point.row == 0 { return map.max_point(); @@ -203,7 +207,11 @@ pub fn start_of_paragraph(map: &DisplaySnapshot, display_point: DisplayPoint) -> for row in (0..point.row + 1).rev() { let blank = map.buffer_snapshot.is_line_blank(row); if found_non_blank_line && blank { - return Point::new(row, 0).to_display_point(map); + if count <= 1 { + return Point::new(row, 0).to_display_point(map); + } + count -= 1; + found_non_blank_line = false; } found_non_blank_line |= !blank; @@ -212,7 +220,11 @@ pub fn start_of_paragraph(map: &DisplaySnapshot, display_point: DisplayPoint) -> DisplayPoint::zero() } -pub fn end_of_paragraph(map: &DisplaySnapshot, display_point: DisplayPoint) -> DisplayPoint { +pub fn end_of_paragraph( + map: &DisplaySnapshot, + display_point: DisplayPoint, + mut count: usize, +) -> DisplayPoint { let point = display_point.to_point(map); if point.row == map.max_buffer_row() { return DisplayPoint::zero(); @@ -222,7 +234,11 @@ pub fn end_of_paragraph(map: &DisplaySnapshot, display_point: DisplayPoint) -> D for row in point.row..map.max_buffer_row() + 1 { let blank = map.buffer_snapshot.is_line_blank(row); if found_non_blank_line && blank { - return Point::new(row, 0).to_display_point(map); + if count <= 1 { + return Point::new(row, 0).to_display_point(map); + } + count -= 1; + found_non_blank_line = false; } found_non_blank_line |= !blank; diff --git a/crates/editor/src/test/editor_test_context.rs b/crates/editor/src/test/editor_test_context.rs index 95da7ff297..bac70f139a 100644 --- a/crates/editor/src/test/editor_test_context.rs +++ b/crates/editor/src/test/editor_test_context.rs @@ -210,6 +210,10 @@ impl<'a> EditorTestContext<'a> { self.assert_selections(expected_selections, marked_text.to_string()) } + pub fn editor_state(&mut self) -> String { + generate_marked_text(self.buffer_text().as_str(), &self.editor_selections(), true) + } + #[track_caller] pub fn assert_editor_background_highlights(&mut self, marked_text: &str) { let expected_ranges = self.ranges(marked_text); @@ -248,14 +252,8 @@ impl<'a> EditorTestContext<'a> { self.assert_selections(expected_selections, expected_marked_text) } - #[track_caller] - fn assert_selections( - &mut self, - expected_selections: Vec>, - expected_marked_text: String, - ) { - let actual_selections = self - .editor + fn editor_selections(&self) -> Vec> { + self.editor .read_with(self.cx, |editor, cx| editor.selections.all::(cx)) .into_iter() .map(|s| { @@ -265,12 +263,22 @@ impl<'a> EditorTestContext<'a> { s.start..s.end } }) - .collect::>(); + .collect::>() + } + + #[track_caller] + fn assert_selections( + &mut self, + expected_selections: Vec>, + expected_marked_text: String, + ) { + let actual_selections = self.editor_selections(); let actual_marked_text = generate_marked_text(&self.buffer_text(), &actual_selections, true); if expected_selections != actual_selections { panic!( indoc! {" + {}Editor has unexpected selections. Expected selections: diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index 4ef9d25894..ddc3b10515 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -90,7 +90,8 @@ pub struct LanguageServerName(pub Arc); /// once at startup, and caches the results. pub struct CachedLspAdapter { pub name: LanguageServerName, - pub initialization_options: Option, + initialization_options: Option, + initialization_overrides: Mutex>, pub disk_based_diagnostic_sources: Vec, pub disk_based_diagnostics_progress_token: Option, pub language_ids: HashMap, @@ -109,6 +110,7 @@ impl CachedLspAdapter { Arc::new(CachedLspAdapter { name, initialization_options, + initialization_overrides: Mutex::new(None), disk_based_diagnostic_sources, disk_based_diagnostics_progress_token, language_ids, @@ -208,6 +210,30 @@ impl CachedLspAdapter { ) -> Option { self.adapter.label_for_symbol(name, kind, language).await } + + pub fn update_initialization_overrides(&self, new: Option<&Value>) -> bool { + let mut current = self.initialization_overrides.lock(); + if current.as_ref() != new { + *current = new.cloned(); + true + } else { + false + } + } + + pub fn initialization_options(&self) -> Option { + let initialization_options = self.initialization_options.as_ref(); + let override_options = self.initialization_overrides.lock().clone(); + match (initialization_options, override_options) { + (None, override_options) => override_options, + (initialization_options, None) => initialization_options.cloned(), + (Some(initialization_options), Some(override_options)) => { + let mut initialization_options = initialization_options.clone(); + merge_json_value_into(override_options, &mut initialization_options); + Some(initialization_options) + } + } + } } pub trait LspAdapterDelegate: Send + Sync { @@ -428,6 +454,7 @@ fn deserialize_regex<'de, D: Deserializer<'de>>(d: D) -> Result, D #[cfg(any(test, feature = "test-support"))] pub struct FakeLspAdapter { pub name: &'static str, + pub initialization_options: Option, pub capabilities: lsp::ServerCapabilities, pub initializer: Option>, pub disk_based_diagnostics_progress_token: Option, @@ -1681,6 +1708,7 @@ impl Default for FakeLspAdapter { capabilities: lsp::LanguageServer::full_capabilities(), initializer: None, disk_based_diagnostics_progress_token: None, + initialization_options: None, disk_based_diagnostics_sources: Vec::new(), } } @@ -1730,6 +1758,10 @@ impl LspAdapter for Arc { async fn disk_based_diagnostics_progress_token(&self) -> Option { self.disk_based_diagnostics_progress_token.clone() } + + async fn initialization_options(&self) -> Option { + self.initialization_options.clone() + } } fn get_capture_indices(query: &Query, captures: &mut [(&str, &mut Option)]) { diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 5d62478b13..ed81b80e8d 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -78,8 +78,8 @@ use std::{ use terminals::Terminals; use text::Anchor; use util::{ - debug_panic, defer, http::HttpClient, merge_json_value_into, - paths::LOCAL_SETTINGS_RELATIVE_PATH, post_inc, ResultExt, TryFutureExt as _, + debug_panic, defer, http::HttpClient, paths::LOCAL_SETTINGS_RELATIVE_PATH, post_inc, ResultExt, + TryFutureExt as _, }; pub use fs::*; @@ -801,7 +801,7 @@ impl Project { .lsp .get(&adapter.name.0) .and_then(|s| s.initialization_options.as_ref()); - if adapter.initialization_options.as_ref() != new_lsp_settings { + if adapter.update_initialization_overrides(new_lsp_settings) { language_servers_to_restart.push((worktree, Arc::clone(language))); } } @@ -2546,20 +2546,13 @@ impl Project { let project_settings = settings::get::(cx); let lsp = project_settings.lsp.get(&adapter.name.0); let override_options = lsp.map(|s| s.initialization_options.clone()).flatten(); - - let mut initialization_options = adapter.initialization_options.clone(); - match (&mut initialization_options, override_options) { - (Some(initialization_options), Some(override_options)) => { - merge_json_value_into(override_options, initialization_options); - } - (None, override_options) => initialization_options = override_options, - _ => {} - } + adapter.update_initialization_overrides(override_options.as_ref()); let server_id = pending_server.server_id; let container_dir = pending_server.container_dir.clone(); let state = LanguageServerState::Starting({ let adapter = adapter.clone(); + let initialization_options = adapter.initialization_options(); let server_name = adapter.name.0.clone(); let languages = self.languages.clone(); let language = language.clone(); diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index 135194df6a..ebd504d02c 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -675,6 +675,9 @@ impl ProjectSearchView { if match_ranges.is_empty() { self.active_match_index = None; } else { + self.active_match_index = Some(0); + self.select_match(Direction::Next, cx); + self.update_match_index(cx); let prev_search_id = mem::replace(&mut self.search_id, self.model.read(cx).search_id); let is_new_search = self.search_id != prev_search_id; self.results_editor.update(cx, |editor, cx| { diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index 1949a5d9bb..fae7c470e3 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -1030,6 +1030,7 @@ pub struct AssistantStyle { pub system_sender: Interactive, pub model: Interactive, pub remaining_tokens: ContainedText, + pub low_remaining_tokens: ContainedText, pub no_remaining_tokens: ContainedText, pub error_icon: Icon, pub api_key_editor: FieldEditor, diff --git a/crates/vcs_menu/Cargo.toml b/crates/vcs_menu/Cargo.toml new file mode 100644 index 0000000000..4ddf1214d0 --- /dev/null +++ b/crates/vcs_menu/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "vcs_menu" +version = "0.1.0" +edition = "2021" +publish = false +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +fuzzy = {path = "../fuzzy"} +gpui = {path = "../gpui"} +picker = {path = "../picker"} +util = {path = "../util"} +theme = {path = "../theme"} +workspace = {path = "../workspace"} + +anyhow.workspace = true diff --git a/crates/collab_ui/src/branch_list.rs b/crates/vcs_menu/src/lib.rs similarity index 89% rename from crates/collab_ui/src/branch_list.rs rename to crates/vcs_menu/src/lib.rs index 16fefbd2eb..b5b1036b36 100644 --- a/crates/collab_ui/src/branch_list.rs +++ b/crates/vcs_menu/src/lib.rs @@ -1,15 +1,17 @@ -use anyhow::{anyhow, bail}; +use anyhow::{anyhow, bail, Result}; use fuzzy::{StringMatch, StringMatchCandidate}; -use gpui::{elements::*, AppContext, MouseState, Task, ViewContext, ViewHandle}; +use gpui::{actions, elements::*, AppContext, MouseState, Task, ViewContext, ViewHandle}; use picker::{Picker, PickerDelegate, PickerEvent}; use std::{ops::Not, sync::Arc}; use util::ResultExt; use workspace::{Toast, Workspace}; +actions!(branches, [OpenRecent]); + pub fn init(cx: &mut AppContext) { Picker::::init(cx); + cx.add_async_action(toggle); } - pub type BranchList = Picker; pub fn build_branch_list( @@ -28,6 +30,34 @@ pub fn build_branch_list( .with_theme(|theme| theme.picker.clone()) } +fn toggle( + _: &mut Workspace, + _: &OpenRecent, + cx: &mut ViewContext, +) -> Option>> { + Some(cx.spawn(|workspace, mut cx| async move { + workspace.update(&mut cx, |workspace, cx| { + workspace.toggle_modal(cx, |_, cx| { + let workspace = cx.handle(); + cx.add_view(|cx| { + Picker::new( + BranchListDelegate { + matches: vec![], + workspace, + selected_index: 0, + last_query: String::default(), + }, + cx, + ) + .with_theme(|theme| theme.picker.clone()) + .with_max_size(800., 1200.) + }) + }); + })?; + Ok(()) + })) +} + pub struct BranchListDelegate { matches: Vec, workspace: ViewHandle, diff --git a/crates/vim/src/motion.rs b/crates/vim/src/motion.rs index faf69d9473..07b095dd5e 100644 --- a/crates/vim/src/motion.rs +++ b/crates/vim/src/motion.rs @@ -31,6 +31,8 @@ pub enum Motion { CurrentLine, StartOfLine, EndOfLine, + StartOfParagraph, + EndOfParagraph, StartOfDocument, EndOfDocument, Matching, @@ -72,6 +74,8 @@ actions!( StartOfLine, EndOfLine, CurrentLine, + StartOfParagraph, + EndOfParagraph, StartOfDocument, EndOfDocument, Matching, @@ -92,6 +96,12 @@ pub fn init(cx: &mut AppContext) { cx.add_action(|_: &mut Workspace, _: &StartOfLine, cx: _| motion(Motion::StartOfLine, cx)); cx.add_action(|_: &mut Workspace, _: &EndOfLine, cx: _| motion(Motion::EndOfLine, cx)); cx.add_action(|_: &mut Workspace, _: &CurrentLine, cx: _| motion(Motion::CurrentLine, cx)); + cx.add_action(|_: &mut Workspace, _: &StartOfParagraph, cx: _| { + motion(Motion::StartOfParagraph, cx) + }); + cx.add_action(|_: &mut Workspace, _: &EndOfParagraph, cx: _| { + motion(Motion::EndOfParagraph, cx) + }); cx.add_action(|_: &mut Workspace, _: &StartOfDocument, cx: _| { motion(Motion::StartOfDocument, cx) }); @@ -142,7 +152,8 @@ impl Motion { pub fn linewise(&self) -> bool { use Motion::*; match self { - Down | Up | StartOfDocument | EndOfDocument | CurrentLine | NextLineStart => true, + Down | Up | StartOfDocument | EndOfDocument | CurrentLine | NextLineStart + | StartOfParagraph | EndOfParagraph => true, EndOfLine | NextWordEnd { .. } | Matching @@ -172,6 +183,8 @@ impl Motion { | Backspace | Right | StartOfLine + | StartOfParagraph + | EndOfParagraph | NextWordStart { .. } | PreviousWordStart { .. } | FirstNonWhitespace @@ -197,6 +210,8 @@ impl Motion { | Backspace | Right | StartOfLine + | StartOfParagraph + | EndOfParagraph | NextWordStart { .. } | PreviousWordStart { .. } | FirstNonWhitespace @@ -235,6 +250,14 @@ impl Motion { FirstNonWhitespace => (first_non_whitespace(map, point), SelectionGoal::None), StartOfLine => (start_of_line(map, point), SelectionGoal::None), EndOfLine => (end_of_line(map, point), SelectionGoal::None), + StartOfParagraph => ( + movement::start_of_paragraph(map, point, times), + SelectionGoal::None, + ), + EndOfParagraph => ( + map.clip_at_line_end(movement::end_of_paragraph(map, point, times)), + SelectionGoal::None, + ), CurrentLine => (end_of_line(map, point), SelectionGoal::None), StartOfDocument => (start_of_document(map, point, times), SelectionGoal::None), EndOfDocument => ( @@ -502,10 +525,13 @@ fn matching(map: &DisplaySnapshot, display_point: DisplayPoint) -> DisplayPoint if line_end == point { line_end = map.max_point().to_point(map); } - line_end.column = line_end.column.saturating_sub(1); let line_range = map.prev_line_boundary(point).0..line_end; - let ranges = map.buffer_snapshot.bracket_ranges(line_range.clone()); + let visible_line_range = + line_range.start..Point::new(line_range.end.row, line_range.end.column.saturating_sub(1)); + let ranges = map + .buffer_snapshot + .bracket_ranges(visible_line_range.clone()); if let Some(ranges) = ranges { let line_range = line_range.start.to_offset(&map.buffer_snapshot) ..line_range.end.to_offset(&map.buffer_snapshot); @@ -590,3 +616,131 @@ fn next_line_start(map: &DisplaySnapshot, point: DisplayPoint, times: usize) -> let new_row = (point.row() + times as u32).min(map.max_buffer_row()); map.clip_point(DisplayPoint::new(new_row, 0), Bias::Left) } + +#[cfg(test)] + +mod test { + + use crate::test::NeovimBackedTestContext; + use indoc::indoc; + + #[gpui::test] + async fn test_start_end_of_paragraph(cx: &mut gpui::TestAppContext) { + let mut cx = NeovimBackedTestContext::new(cx).await; + + let initial_state = indoc! {r"ˇabc + def + + paragraph + the second + + + + third and + final"}; + + // goes down once + cx.set_shared_state(initial_state).await; + cx.simulate_shared_keystrokes(["}"]).await; + cx.assert_shared_state(indoc! {r"abc + def + ˇ + paragraph + the second + + + + third and + final"}) + .await; + + // goes up once + cx.simulate_shared_keystrokes(["{"]).await; + cx.assert_shared_state(initial_state).await; + + // goes down twice + cx.simulate_shared_keystrokes(["2", "}"]).await; + cx.assert_shared_state(indoc! {r"abc + def + + paragraph + the second + ˇ + + + third and + final"}) + .await; + + // goes down over multiple blanks + cx.simulate_shared_keystrokes(["}"]).await; + cx.assert_shared_state(indoc! {r"abc + def + + paragraph + the second + + + + third and + finaˇl"}) + .await; + + // goes up twice + cx.simulate_shared_keystrokes(["2", "{"]).await; + cx.assert_shared_state(indoc! {r"abc + def + ˇ + paragraph + the second + + + + third and + final"}) + .await + } + + #[gpui::test] + async fn test_matching(cx: &mut gpui::TestAppContext) { + let mut cx = NeovimBackedTestContext::new(cx).await; + + cx.set_shared_state(indoc! {r"func ˇ(a string) { + do(something(with.and_arrays[0, 2])) + }"}) + .await; + cx.simulate_shared_keystrokes(["%"]).await; + cx.assert_shared_state(indoc! {r"func (a stringˇ) { + do(something(with.and_arrays[0, 2])) + }"}) + .await; + + // test it works on the last character of the line + cx.set_shared_state(indoc! {r"func (a string) ˇ{ + do(something(with.and_arrays[0, 2])) + }"}) + .await; + cx.simulate_shared_keystrokes(["%"]).await; + cx.assert_shared_state(indoc! {r"func (a string) { + do(something(with.and_arrays[0, 2])) + ˇ}"}) + .await; + + // test it works on immediate nesting + cx.set_shared_state("ˇ{()}").await; + cx.simulate_shared_keystrokes(["%"]).await; + cx.assert_shared_state("{()ˇ}").await; + cx.simulate_shared_keystrokes(["%"]).await; + cx.assert_shared_state("ˇ{()}").await; + + // test it works on immediate nesting inside braces + cx.set_shared_state("{\n ˇ{()}\n}").await; + cx.simulate_shared_keystrokes(["%"]).await; + cx.assert_shared_state("{\n {()ˇ}\n}").await; + + // test it jumps to the next paren on a line + cx.set_shared_state("func ˇboop() {\n}").await; + cx.simulate_shared_keystrokes(["%"]).await; + cx.assert_shared_state("func boop(ˇ) {\n}").await; + } +} diff --git a/crates/vim/src/normal/case.rs b/crates/vim/src/normal/case.rs index ba527af0bb..b3e101262d 100644 --- a/crates/vim/src/normal/case.rs +++ b/crates/vim/src/normal/case.rs @@ -1,29 +1,51 @@ +use editor::scroll::autoscroll::Autoscroll; use gpui::ViewContext; -use language::Point; +use language::{Bias, Point}; use workspace::Workspace; -use crate::{motion::Motion, normal::ChangeCase, Vim}; +use crate::{normal::ChangeCase, state::Mode, Vim}; pub fn change_case(_: &mut Workspace, _: &ChangeCase, cx: &mut ViewContext) { Vim::update(cx, |vim, cx| { - let count = vim.pop_number_operator(cx); + let count = vim.pop_number_operator(cx).unwrap_or(1) as u32; vim.update_active_editor(cx, |editor, cx| { - editor.set_clip_at_line_ends(false, cx); - editor.transact(cx, |editor, cx| { - editor.change_selections(None, cx, |s| { - s.move_with(|map, selection| { - if selection.start == selection.end { - Motion::Right.expand_selection(map, selection, count, true); + let mut ranges = Vec::new(); + let mut cursor_positions = Vec::new(); + let snapshot = editor.buffer().read(cx).snapshot(cx); + for selection in editor.selections.all::(cx) { + match vim.state.mode { + Mode::Visual { line: true } => { + let start = Point::new(selection.start.row, 0); + let end = + Point::new(selection.end.row, snapshot.line_len(selection.end.row)); + ranges.push(start..end); + cursor_positions.push(start..start); + } + Mode::Visual { line: false } => { + ranges.push(selection.start..selection.end); + cursor_positions.push(selection.start..selection.start); + } + Mode::Insert | Mode::Normal => { + let start = selection.start; + let mut end = start; + for _ in 0..count { + end = snapshot.clip_point(end + Point::new(0, 1), Bias::Right); } - }) - }); - let selections = editor.selections.all::(cx); - for selection in selections.into_iter().rev() { + ranges.push(start..end); + + if end.column == snapshot.line_len(end.row) { + end = snapshot.clip_point(end - Point::new(0, 1), Bias::Left); + } + cursor_positions.push(end..end) + } + } + } + editor.transact(cx, |editor, cx| { + for range in ranges.into_iter().rev() { let snapshot = editor.buffer().read(cx).snapshot(cx); editor.buffer().update(cx, |buffer, cx| { - let range = selection.start..selection.end; let text = snapshot - .text_for_range(selection.start..selection.end) + .text_for_range(range.start..range.end) .flat_map(|s| s.chars()) .flat_map(|c| { if c.is_lowercase() { @@ -37,28 +59,46 @@ pub fn change_case(_: &mut Workspace, _: &ChangeCase, cx: &mut ViewContext", ">"]); cx.assert_editor_state("aa\n b«b\n cˇ»c"); } + +#[gpui::test] +async fn test_escape_command_palette(cx: &mut gpui::TestAppContext) { + let mut cx = VimTestContext::new(cx, true).await; + + cx.set_state("aˇbc\n", Mode::Normal); + cx.simulate_keystrokes(["i", "cmd-shift-p"]); + + assert!(cx.workspace(|workspace, _| workspace.modal::().is_some())); + cx.simulate_keystroke("escape"); + assert!(!cx.workspace(|workspace, _| workspace.modal::().is_some())); + cx.assert_state("aˇbc\n", Mode::Insert); +} diff --git a/crates/vim/src/test/neovim_backed_test_context.rs b/crates/vim/src/test/neovim_backed_test_context.rs index 9b6bf976ca..7f9a84b666 100644 --- a/crates/vim/src/test/neovim_backed_test_context.rs +++ b/crates/vim/src/test/neovim_backed_test_context.rs @@ -1,9 +1,10 @@ -use std::ops::{Deref, DerefMut}; +use indoc::indoc; +use std::ops::{Deref, DerefMut, Range}; use collections::{HashMap, HashSet}; use gpui::ContextHandle; use language::OffsetRangeExt; -use util::test::marked_text_offsets; +use util::test::{generate_marked_text, marked_text_offsets}; use super::{neovim_connection::NeovimConnection, NeovimBackedBindingTestContext, VimTestContext}; use crate::state::Mode; @@ -112,6 +113,43 @@ impl<'a> NeovimBackedTestContext<'a> { context_handle } + pub async fn assert_shared_state(&mut self, marked_text: &str) { + let neovim = self.neovim_state().await; + if neovim != marked_text { + panic!( + indoc! {"Test is incorrect (currently expected != neovim state) + + # currently expected: + {} + # neovim state: + {} + # zed state: + {}"}, + marked_text, + neovim, + self.editor_state(), + ) + } + self.assert_editor_state(marked_text) + } + + pub async fn neovim_state(&mut self) -> String { + generate_marked_text( + self.neovim.text().await.as_str(), + &vec![self.neovim_selection().await], + true, + ) + } + + async fn neovim_selection(&mut self) -> Range { + let mut neovim_selection = self.neovim.selection().await; + // Zed selections adjust themselves to make the end point visually make sense + if neovim_selection.start > neovim_selection.end { + neovim_selection.start.column += 1; + } + neovim_selection.to_offset(&self.buffer_snapshot()) + } + pub async fn assert_state_matches(&mut self) { assert_eq!( self.neovim.text().await, @@ -120,13 +158,8 @@ impl<'a> NeovimBackedTestContext<'a> { self.assertion_context() ); - let mut neovim_selection = self.neovim.selection().await; - // Zed selections adjust themselves to make the end point visually make sense - if neovim_selection.start > neovim_selection.end { - neovim_selection.start.column += 1; - } - let neovim_selection = neovim_selection.to_offset(&self.buffer_snapshot()); - self.assert_editor_selections(vec![neovim_selection]); + let selections = vec![self.neovim_selection().await]; + self.assert_editor_selections(selections); if let Some(neovim_mode) = self.neovim.mode().await { assert_eq!(neovim_mode, self.mode(), "{}", self.assertion_context(),); diff --git a/crates/vim/src/test/neovim_connection.rs b/crates/vim/src/test/neovim_connection.rs index aa14e4a065..5bfae4e673 100644 --- a/crates/vim/src/test/neovim_connection.rs +++ b/crates/vim/src/test/neovim_connection.rs @@ -167,15 +167,25 @@ impl NeovimConnection { .await .expect("Could not get neovim window"); - if !selection.is_empty() { - panic!("Setting neovim state with non empty selection not yet supported"); - } let cursor = selection.start; nvim_window .set_cursor((cursor.row as i64 + 1, cursor.column as i64)) .await .expect("Could not set nvim cursor position"); + if !selection.is_empty() { + self.nvim + .input("v") + .await + .expect("could not enter visual mode"); + + let cursor = selection.end; + nvim_window + .set_cursor((cursor.row as i64 + 1, cursor.column as i64)) + .await + .expect("Could not set nvim cursor position"); + } + if let Some(NeovimData::Get { mode, state }) = self.data.back() { if *mode == Some(Mode::Normal) && *state == marked_text { return; diff --git a/crates/vim/src/test/vim_test_context.rs b/crates/vim/src/test/vim_test_context.rs index 3e66d6bb1c..f9ba577231 100644 --- a/crates/vim/src/test/vim_test_context.rs +++ b/crates/vim/src/test/vim_test_context.rs @@ -21,12 +21,14 @@ impl<'a> VimTestContext<'a> { cx.update(|cx| { search::init(cx); crate::init(cx); + command_palette::init(cx); }); cx.update(|cx| { cx.update_global(|store: &mut SettingsStore, cx| { store.update_user_settings::(cx, |s| *s = Some(enabled)); }); + settings::KeymapFile::load_asset("keymaps/default.json", cx).unwrap(); settings::KeymapFile::load_asset("keymaps/vim.json", cx).unwrap(); }); diff --git a/crates/vim/src/vim.rs b/crates/vim/src/vim.rs index eae8643cf3..2bcc2254ee 100644 --- a/crates/vim/src/vim.rs +++ b/crates/vim/src/vim.rs @@ -12,7 +12,7 @@ mod visual; use anyhow::Result; use collections::CommandPaletteFilter; -use editor::{Bias, Cancel, Editor, EditorMode, Event}; +use editor::{Bias, Editor, EditorMode, Event}; use gpui::{ actions, impl_actions, AppContext, Subscription, ViewContext, ViewHandle, WeakViewHandle, WindowContext, @@ -64,22 +64,6 @@ pub fn init(cx: &mut AppContext) { Vim::update(cx, |vim, cx| vim.push_number(n, cx)); }); - // Editor Actions - cx.add_action(|_: &mut Editor, _: &Cancel, cx| { - // If we are in aren't in normal mode or have an active operator, swap to normal mode - // Otherwise forward cancel on to the editor - let vim = Vim::read(cx); - if vim.state.mode != Mode::Normal || vim.active_operator().is_some() { - WindowContext::defer(cx, |cx| { - Vim::update(cx, |state, cx| { - state.switch_mode(Mode::Normal, false, cx); - }); - }); - } else { - cx.propagate_action(); - } - }); - cx.add_action(|_: &mut Workspace, _: &Tab, cx| { Vim::active_editor_input_ignored(" ".into(), cx) }); @@ -109,10 +93,7 @@ pub fn observe_keystrokes(cx: &mut WindowContext) { cx.observe_keystrokes(|_keystroke, _result, handled_by, cx| { if let Some(handled_by) = handled_by { // Keystroke is handled by the vim system, so continue forward - // Also short circuit if it is the special cancel action - if handled_by.namespace() == "vim" - || (handled_by.namespace() == "editor" && handled_by.name() == "Cancel") - { + if handled_by.namespace() == "vim" { return true; } } diff --git a/crates/vim/test_data/test_change_case.json b/crates/vim/test_data/test_change_case.json new file mode 100644 index 0000000000..1c0cad0b93 --- /dev/null +++ b/crates/vim/test_data/test_change_case.json @@ -0,0 +1,18 @@ +{"Put":{"state":"ˇabC\n"}} +{"Key":"~"} +{"Get":{"state":"AˇbC\n","mode":"Normal"}} +{"Key":"2"} +{"Key":"~"} +{"Get":{"state":"ABˇc\n","mode":"Normal"}} +{"Put":{"state":"a😀C«dÉ1*fˇ»\n"}} +{"Key":"~"} +{"Get":{"state":"a😀CˇDé1*F\n","mode":"Normal"}} +{"Key":"~"} +{"Put":{"state":"aˇC😀é1*F\n"}} +{"Key":"4"} +{"Key":"~"} +{"Get":{"state":"ac😀É1ˇ*F\n","mode":"Normal"}} +{"Put":{"state":"abˇC\n"}} +{"Key":"shift-v"} +{"Key":"~"} +{"Get":{"state":"ˇABc\n","mode":"Normal"}} diff --git a/crates/vim/test_data/test_matching.json b/crates/vim/test_data/test_matching.json new file mode 100644 index 0000000000..5c8d7529b9 --- /dev/null +++ b/crates/vim/test_data/test_matching.json @@ -0,0 +1,17 @@ +{"Put":{"state":"func ˇ(a string) {\n do(something(with.and_arrays[0, 2]))\n}"}} +{"Key":"%"} +{"Get":{"state":"func (a stringˇ) {\n do(something(with.and_arrays[0, 2]))\n}","mode":"Normal"}} +{"Put":{"state":"func (a string) ˇ{\ndo(something(with.and_arrays[0, 2]))\n}"}} +{"Key":"%"} +{"Get":{"state":"func (a string) {\ndo(something(with.and_arrays[0, 2]))\nˇ}","mode":"Normal"}} +{"Put":{"state":"ˇ{()}"}} +{"Key":"%"} +{"Get":{"state":"{()ˇ}","mode":"Normal"}} +{"Key":"%"} +{"Get":{"state":"ˇ{()}","mode":"Normal"}} +{"Put":{"state":"{\n ˇ{()}\n}"}} +{"Key":"%"} +{"Get":{"state":"{\n {()ˇ}\n}","mode":"Normal"}} +{"Put":{"state":"func ˇboop() {\n}"}} +{"Key":"%"} +{"Get":{"state":"func boop(ˇ) {\n}","mode":"Normal"}} diff --git a/crates/vim/test_data/test_start_end_of_paragraph.json b/crates/vim/test_data/test_start_end_of_paragraph.json new file mode 100644 index 0000000000..0de4d84f50 --- /dev/null +++ b/crates/vim/test_data/test_start_end_of_paragraph.json @@ -0,0 +1,13 @@ +{"Put":{"state":"ˇabc\ndef\n\nparagraph\nthe second\n\n\n\nthird and\nfinal"}} +{"Key":"}"} +{"Get":{"state":"abc\ndef\nˇ\nparagraph\nthe second\n\n\n\nthird and\nfinal","mode":"Normal"}} +{"Key":"{"} +{"Get":{"state":"ˇabc\ndef\n\nparagraph\nthe second\n\n\n\nthird and\nfinal","mode":"Normal"}} +{"Key":"2"} +{"Key":"}"} +{"Get":{"state":"abc\ndef\n\nparagraph\nthe second\nˇ\n\n\nthird and\nfinal","mode":"Normal"}} +{"Key":"}"} +{"Get":{"state":"abc\ndef\n\nparagraph\nthe second\n\n\n\nthird and\nfinaˇl","mode":"Normal"}} +{"Key":"2"} +{"Key":"{"} +{"Get":{"state":"abc\ndef\nˇ\nparagraph\nthe second\n\n\n\nthird and\nfinal","mode":"Normal"}} diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index db5e0e547d..4c75d370d5 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -57,8 +57,9 @@ use staff_mode::StaffMode; use util::{channel::RELEASE_CHANNEL, paths, ResultExt, TryFutureExt}; use workspace::{item::ItemHandle, notifications::NotifyResultExt, AppState, Workspace}; use zed::{ - assets::Assets, build_window_options, handle_keymap_file_changes, initialize_workspace, - languages, menus, + assets::Assets, + build_window_options, handle_keymap_file_changes, initialize_workspace, languages, menus, + only_instance::{ensure_only_instance, IsOnlyInstance}, }; fn main() { @@ -66,6 +67,10 @@ fn main() { init_paths(); init_logger(); + if ensure_only_instance() != IsOnlyInstance::Yes { + return; + } + log::info!("========== starting zed =========="); let mut app = gpui::App::new(Assets).unwrap(); diff --git a/crates/zed/src/only_instance.rs b/crates/zed/src/only_instance.rs new file mode 100644 index 0000000000..a8c4b30816 --- /dev/null +++ b/crates/zed/src/only_instance.rs @@ -0,0 +1,103 @@ +use std::{ + io::{Read, Write}, + net::{Ipv4Addr, SocketAddr, SocketAddrV4, TcpListener, TcpStream}, + thread, + time::Duration, +}; + +use util::channel::ReleaseChannel; + +const LOCALHOST: Ipv4Addr = Ipv4Addr::new(127, 0, 0, 1); +const CONNECT_TIMEOUT: Duration = Duration::from_millis(10); +const RECEIVE_TIMEOUT: Duration = Duration::from_millis(35); +const SEND_TIMEOUT: Duration = Duration::from_millis(20); + +fn address() -> SocketAddr { + let port = match *util::channel::RELEASE_CHANNEL { + ReleaseChannel::Dev => 43737, + ReleaseChannel::Preview => 43738, + ReleaseChannel::Stable => 43739, + }; + + SocketAddr::V4(SocketAddrV4::new(LOCALHOST, port)) +} + +fn instance_handshake() -> &'static str { + match *util::channel::RELEASE_CHANNEL { + ReleaseChannel::Dev => "Zed Editor Dev Instance Running", + ReleaseChannel::Preview => "Zed Editor Preview Instance Running", + ReleaseChannel::Stable => "Zed Editor Stable Instance Running", + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum IsOnlyInstance { + Yes, + No, +} + +pub fn ensure_only_instance() -> IsOnlyInstance { + if *db::ZED_STATELESS { + return IsOnlyInstance::Yes; + } + + if check_got_handshake() { + return IsOnlyInstance::No; + } + + let listener = match TcpListener::bind(address()) { + Ok(listener) => listener, + + Err(err) => { + log::warn!("Error binding to single instance port: {err}"); + if check_got_handshake() { + return IsOnlyInstance::No; + } + + // Avoid failing to start when some other application by chance already has + // a claim on the port. This is sub-par as any other instance that gets launched + // will be unable to communicate with this instance and will duplicate + log::warn!("Backup handshake request failed, continuing without handshake"); + return IsOnlyInstance::Yes; + } + }; + + thread::spawn(move || { + for stream in listener.incoming() { + let mut stream = match stream { + Ok(stream) => stream, + Err(_) => return, + }; + + _ = stream.set_nodelay(true); + _ = stream.set_read_timeout(Some(SEND_TIMEOUT)); + _ = stream.write_all(instance_handshake().as_bytes()); + } + }); + + IsOnlyInstance::Yes +} + +fn check_got_handshake() -> bool { + match TcpStream::connect_timeout(&address(), CONNECT_TIMEOUT) { + Ok(mut stream) => { + let mut buf = vec![0u8; instance_handshake().len()]; + + stream.set_read_timeout(Some(RECEIVE_TIMEOUT)).unwrap(); + if let Err(err) = stream.read_exact(&mut buf) { + log::warn!("Connected to single instance port but failed to read: {err}"); + return false; + } + + if buf == instance_handshake().as_bytes() { + log::info!("Got instance handshake"); + return true; + } + + log::warn!("Got wrong instance handshake value"); + false + } + + Err(_) => false, + } +} diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 0df16f4bab..09bdbf65be 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -1,6 +1,7 @@ pub mod assets; pub mod languages; pub mod menus; +pub mod only_instance; #[cfg(any(test, feature = "test-support"))] pub mod test; diff --git a/styles/src/component/tab_bar_button.ts b/styles/src/component/tab_bar_button.ts new file mode 100644 index 0000000000..0c43e7010e --- /dev/null +++ b/styles/src/component/tab_bar_button.ts @@ -0,0 +1,55 @@ +import { Theme, StyleSets } from "../common" +import { interactive } from "../element" +import { InteractiveState } from "../element/interactive" +import { background, foreground } from "../style_tree/components" + +interface TabBarButtonOptions { + icon: string + color?: StyleSets +} + +type TabBarButtonProps = TabBarButtonOptions & { + state?: Partial>> +} + +export function tab_bar_button(theme: Theme, { icon, color = "base" }: TabBarButtonProps) { + const button_spacing = 8 + + return ( + interactive({ + base: { + icon: { + color: foreground(theme.middle, color), + asset: icon, + dimensions: { + width: 15, + height: 15, + }, + }, + container: { + corner_radius: 4, + padding: { + top: 4, bottom: 4, left: 4, right: 4 + }, + margin: { + left: button_spacing / 2, + right: button_spacing / 2, + }, + }, + }, + state: { + hovered: { + container: { + background: background(theme.middle, color, "hovered"), + + } + }, + clicked: { + container: { + background: background(theme.middle, color, "pressed"), + } + }, + }, + }) + ) +} diff --git a/styles/src/style_tree/assistant.ts b/styles/src/style_tree/assistant.ts index 6df02a0e33..cfc1f8d813 100644 --- a/styles/src/style_tree/assistant.ts +++ b/styles/src/style_tree/assistant.ts @@ -1,233 +1,133 @@ -import { text, border, background, foreground } from "./components" -import { interactive } from "../element" -import { useTheme } from "../theme" +import { text, border, background, foreground, TextStyle } from "./components" +import { Interactive, interactive } from "../element" +import { tab_bar_button } from "../component/tab_bar_button" +import { StyleSets, useTheme } from "../theme" + +type RoleCycleButton = TextStyle & { + background?: string +} +// TODO: Replace these with zed types +type RemainingTokens = TextStyle & { + background: string, + margin: { top: number, right: number }, + padding: { + right: number, + left: number, + top: number, + bottom: number, + }, + corner_radius: number, +} export default function assistant(): any { const theme = useTheme() + const interactive_role = (color: StyleSets): Interactive => { + return ( + interactive({ + base: { + ...text(theme.highest, "sans", color, { size: "sm" }), + }, + state: { + hovered: { + ...text(theme.highest, "sans", color, { size: "sm" }), + background: background(theme.highest, color, "hovered"), + }, + clicked: { + ...text(theme.highest, "sans", color, { size: "sm" }), + background: background(theme.highest, color, "pressed"), + } + }, + }) + ) + } + + const tokens_remaining = (color: StyleSets): RemainingTokens => { + return ( + { + ...text(theme.highest, "mono", color, { size: "xs" }), + background: background(theme.highest, "on", "default"), + margin: { top: 12, right: 20 }, + padding: { right: 4, left: 4, top: 1, bottom: 1 }, + corner_radius: 6, + } + ) + } + return { container: { background: background(theme.highest), padding: { left: 12 }, }, message_header: { - margin: { bottom: 6, top: 6 }, + margin: { bottom: 4, top: 4 }, background: background(theme.highest), }, - hamburger_button: interactive({ - base: { - icon: { - color: foreground(theme.highest, "variant"), - asset: "icons/hamburger_15.svg", - dimensions: { - width: 15, - height: 15, - }, - }, - container: { - padding: { left: 12, right: 8.5 }, - }, - }, - state: { - hovered: { - icon: { - color: foreground(theme.highest, "hovered"), - }, - }, - }, + hamburger_button: tab_bar_button(theme, { + icon: "icons/hamburger_15.svg", }), - split_button: interactive({ - base: { - icon: { - color: foreground(theme.highest, "variant"), - asset: "icons/split_message_15.svg", - dimensions: { - width: 15, - height: 15, - }, - }, - container: { - padding: { left: 8.5, right: 8.5 }, - }, - }, - state: { - hovered: { - icon: { - color: foreground(theme.highest, "hovered"), - }, - }, - }, + + split_button: tab_bar_button(theme, { + icon: "icons/split_message_15.svg", }), - quote_button: interactive({ - base: { - icon: { - color: foreground(theme.highest, "variant"), - asset: "icons/quote_15.svg", - dimensions: { - width: 15, - height: 15, - }, - }, - container: { - padding: { left: 8.5, right: 8.5 }, - }, - }, - state: { - hovered: { - icon: { - color: foreground(theme.highest, "hovered"), - }, - }, - }, + quote_button: tab_bar_button(theme, { + icon: "icons/radix/quote.svg", }), - assist_button: interactive({ - base: { - icon: { - color: foreground(theme.highest, "variant"), - asset: "icons/assist_15.svg", - dimensions: { - width: 15, - height: 15, - }, - }, - container: { - padding: { left: 8.5, right: 8.5 }, - }, - }, - state: { - hovered: { - icon: { - color: foreground(theme.highest, "hovered"), - }, - }, - }, + assist_button: tab_bar_button(theme, { + icon: "icons/radix/magic-wand.svg", }), - zoom_in_button: interactive({ - base: { - icon: { - color: foreground(theme.highest, "variant"), - asset: "icons/maximize_8.svg", - dimensions: { - width: 12, - height: 12, - }, - }, - container: { - padding: { left: 10, right: 10 }, - }, - }, - state: { - hovered: { - icon: { - color: foreground(theme.highest, "hovered"), - }, - }, - }, + zoom_in_button: tab_bar_button(theme, { + icon: "icons/radix/maximize.svg", }), - zoom_out_button: interactive({ - base: { - icon: { - color: foreground(theme.highest, "variant"), - asset: "icons/minimize_8.svg", - dimensions: { - width: 12, - height: 12, - }, - }, - container: { - padding: { left: 10, right: 10 }, - }, - }, - state: { - hovered: { - icon: { - color: foreground(theme.highest, "hovered"), - }, - }, - }, + zoom_out_button: tab_bar_button(theme, { + icon: "icons/radix/minimize.svg", }), - plus_button: interactive({ - base: { - icon: { - color: foreground(theme.highest, "variant"), - asset: "icons/plus_12.svg", - dimensions: { - width: 12, - height: 12, - }, - }, - container: { - padding: { left: 10, right: 10 }, - }, - }, - state: { - hovered: { - icon: { - color: foreground(theme.highest, "hovered"), - }, - }, - }, + plus_button: tab_bar_button(theme, { + icon: "icons/radix/plus.svg", }), title: { - ...text(theme.highest, "sans", "default", { size: "sm" }), + ...text(theme.highest, "sans", "default", { size: "xs" }), }, saved_conversation: { container: interactive({ base: { - background: background(theme.highest, "on"), + background: background(theme.middle), padding: { top: 4, bottom: 4 }, + border: border(theme.middle, "default", { top: true, overlay: true }), }, state: { hovered: { - background: background(theme.highest, "on", "hovered"), + background: background(theme.middle, "hovered"), }, + clicked: { + background: background(theme.middle, "pressed"), + } }, }), saved_at: { margin: { left: 8 }, - ...text(theme.highest, "sans", "default", { size: "xs" }), + ...text(theme.highest, "sans", "variant", { size: "xs" }), }, title: { - margin: { left: 16 }, + margin: { left: 12 }, ...text(theme.highest, "sans", "default", { size: "sm", weight: "bold", }), }, }, - user_sender: { - default: { - ...text(theme.highest, "sans", "default", { - size: "sm", - weight: "bold", - }), - }, - }, - assistant_sender: { - default: { - ...text(theme.highest, "sans", "accent", { - size: "sm", - weight: "bold", - }), - }, - }, - system_sender: { - default: { - ...text(theme.highest, "sans", "variant", { - size: "sm", - weight: "bold", - }), - }, - }, + user_sender: interactive_role("base"), + assistant_sender: interactive_role("accent"), + system_sender: interactive_role("warning"), sent_at: { margin: { top: 2, left: 8 }, - ...text(theme.highest, "sans", "default", { size: "2xs" }), + ...text(theme.highest, "sans", "variant", { size: "2xs" }), }, model: interactive({ base: { - background: background(theme.highest, "on"), - margin: { left: 12, right: 12, top: 12 }, - padding: 4, + background: background(theme.highest), + margin: { left: 12, right: 4, top: 12 }, + padding: { right: 4, left: 4, top: 1, bottom: 1 }, corner_radius: 4, ...text(theme.highest, "sans", "default", { size: "xs" }), }, @@ -238,20 +138,9 @@ export default function assistant(): any { }, }, }), - remaining_tokens: { - background: background(theme.highest, "on"), - margin: { top: 12, right: 24 }, - padding: 4, - corner_radius: 4, - ...text(theme.highest, "sans", "positive", { size: "xs" }), - }, - no_remaining_tokens: { - background: background(theme.highest, "on"), - margin: { top: 12, right: 24 }, - padding: 4, - corner_radius: 4, - ...text(theme.highest, "sans", "negative", { size: "xs" }), - }, + remaining_tokens: tokens_remaining("positive"), + low_remaining_tokens: tokens_remaining("warning"), + no_remaining_tokens: tokens_remaining("negative"), error_icon: { margin: { left: 8 }, color: foreground(theme.highest, "negative"), @@ -259,7 +148,7 @@ export default function assistant(): any { }, api_key_editor: { background: background(theme.highest, "on"), - corner_radius: 6, + corner_radius: 4, text: text(theme.highest, "mono", "on"), placeholder_text: text(theme.highest, "mono", "on", "disabled", { size: "xs", diff --git a/styles/src/theme/create_theme.ts b/styles/src/theme/create_theme.ts index dff4c3dbc4..d2701f8341 100644 --- a/styles/src/theme/create_theme.ts +++ b/styles/src/theme/create_theme.ts @@ -12,8 +12,17 @@ export interface Theme { name: string is_light: boolean + /** + * App background, other elements that should sit directly on top of the background. + */ lowest: Layer + /** + * Panels, tabs, other UI surfaces that sit on top of the background. + */ middle: Layer + /** + * Editors like code buffers, conversation editors, etc. + */ highest: Layer ramps: RampSet