diff --git a/Cargo.lock b/Cargo.lock
index f534a4fe7d..623ebd982f 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -362,7 +362,7 @@ dependencies = [
"async-lock",
"async-task",
"concurrent-queue",
- "fastrand",
+ "fastrand 1.9.0",
"futures-lite",
"slab",
]
@@ -481,7 +481,7 @@ checksum = "0e97ce7de6cf12de5d7226c73f5ba9811622f4db3a5b91b55c53e987e5f91cba"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.26",
+ "syn 2.0.27",
]
[[package]]
@@ -529,7 +529,7 @@ checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.26",
+ "syn 2.0.27",
]
[[package]]
@@ -566,13 +566,13 @@ dependencies = [
[[package]]
name = "async-trait"
-version = "0.1.71"
+version = "0.1.72"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a564d521dd56509c4c47480d00b80ee55f7e385ae48db5744c67ad50c92d2ebf"
+checksum = "cc6dde6e4ed435a4c1ee4e73592f5ba9da2151af10076cc04858746af9352d09"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.26",
+ "syn 2.0.27",
]
[[package]]
@@ -830,7 +830,7 @@ dependencies = [
"regex",
"rustc-hash",
"shlex",
- "syn 2.0.26",
+ "syn 2.0.27",
"which",
]
@@ -907,7 +907,7 @@ dependencies = [
"async-lock",
"async-task",
"atomic-waker",
- "fastrand",
+ "fastrand 1.9.0",
"futures-lite",
"log",
]
@@ -1070,6 +1070,10 @@ dependencies = [
"media",
"postage",
"project",
+ "schemars",
+ "serde",
+ "serde_derive",
+ "serde_json",
"settings",
"util",
]
@@ -1243,9 +1247,9 @@ dependencies = [
[[package]]
name = "clap"
-version = "4.3.15"
+version = "4.3.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8f644d0dac522c8b05ddc39aaaccc5b136d5dc4ff216610c5641e3be5becf56c"
+checksum = "5fd304a20bff958a57f04c4e96a2e7594cc4490a0e809cbd48bb6437edaa452d"
dependencies = [
"clap_builder",
"clap_derive 4.3.12",
@@ -1254,9 +1258,9 @@ dependencies = [
[[package]]
name = "clap_builder"
-version = "4.3.15"
+version = "4.3.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "af410122b9778e024f9e0fb35682cc09cc3f85cad5e8d3ba8f47a9702df6e73d"
+checksum = "01c6a3f08f1fe5662a35cfe393aec09c4df95f60ee93b7556505260f75eee9e1"
dependencies = [
"anstream",
"anstyle",
@@ -1286,7 +1290,7 @@ dependencies = [
"heck 0.4.1",
"proc-macro2",
"quote",
- "syn 2.0.26",
+ "syn 2.0.27",
]
[[package]]
@@ -1970,9 +1974,9 @@ dependencies = [
[[package]]
name = "curl-sys"
-version = "0.4.63+curl-8.1.2"
+version = "0.4.64+curl-8.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "aeb0fef7046022a1e2ad67a004978f0e3cacb9e3123dc62ce768f92197b771dc"
+checksum = "f96069f0b1cb1241c838740659a771ef143363f52772a9ce1bd9c04c75eee0dc"
dependencies = [
"cc",
"libc",
@@ -2262,9 +2266,9 @@ dependencies = [
[[package]]
name = "either"
-version = "1.8.1"
+version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91"
+checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07"
[[package]]
name = "encoding_rs"
@@ -2413,6 +2417,12 @@ dependencies = [
"instant",
]
+[[package]]
+name = "fastrand"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6999dc1837253364c2ebb0704ba97994bd874e8f195d665c50b7548f6ea92764"
+
[[package]]
name = "feedback"
version = "0.1.0"
@@ -2771,7 +2781,7 @@ version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce"
dependencies = [
- "fastrand",
+ "fastrand 1.9.0",
"futures-core",
"futures-io",
"memchr",
@@ -2788,7 +2798,7 @@ checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.26",
+ "syn 2.0.27",
]
[[package]]
@@ -3250,9 +3260,9 @@ dependencies = [
[[package]]
name = "http-range-header"
-version = "0.3.0"
+version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0bfe8eed0a9285ef776bb792479ea3834e8b94e13d615c2f66d03dd50a435a29"
+checksum = "add0ab9360ddbd88cfeb3bd9574a1d85cfdfa14db10b3e21d3700dbc4328758f"
[[package]]
name = "httparse"
@@ -3893,9 +3903,9 @@ dependencies = [
[[package]]
name = "libz-sys"
-version = "1.1.9"
+version = "1.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "56ee889ecc9568871456d42f603d6a0ce59ff328d291063a45cbdf0036baf6db"
+checksum = "d97137b25e321a73eef1418d1d5d2eda4d77e12813f8e6dead84bc52c5870a7b"
dependencies = [
"cc",
"libc",
@@ -4561,9 +4571,9 @@ dependencies = [
[[package]]
name = "num-traits"
-version = "0.2.15"
+version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd"
+checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2"
dependencies = [
"autocfg",
"libm",
@@ -4722,7 +4732,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.26",
+ "syn 2.0.27",
]
[[package]]
@@ -5010,7 +5020,7 @@ checksum = "ec2e072ecce94ec471b13398d5402c188e76ac03cf74dd1a975161b23a3f6d9c"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.26",
+ "syn 2.0.27",
]
[[package]]
@@ -5158,12 +5168,12 @@ dependencies = [
[[package]]
name = "prettyplease"
-version = "0.2.10"
+version = "0.2.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "92139198957b410250d43fad93e630d956499a625c527eda65175c8680f83387"
+checksum = "6c64d9ba0963cdcea2e1b2230fbae2bab30eb25a174be395c41e764bfb65dd62"
dependencies = [
"proc-macro2",
- "syn 2.0.26",
+ "syn 2.0.27",
]
[[package]]
@@ -5288,6 +5298,7 @@ version = "0.1.0"
dependencies = [
"anyhow",
"client",
+ "collections",
"context_menu",
"db",
"drag_and_drop",
@@ -5491,9 +5502,9 @@ dependencies = [
[[package]]
name = "quote"
-version = "1.0.31"
+version = "1.0.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5fe8a65d69dd0808184ebb5f836ab526bb259db23c657efa38711b1072ee47f0"
+checksum = "50f3b39ccfb720540debaa0164757101c08ecb8d326b15358ce76a62c7e85965"
dependencies = [
"proc-macro2",
]
@@ -5895,9 +5906,9 @@ dependencies = [
[[package]]
name = "rmp"
-version = "0.8.11"
+version = "0.8.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "44519172358fd6d58656c86ab8e7fbc9e1490c3e8f14d35ed78ca0dd07403c9f"
+checksum = "7f9860a6cc38ed1da53456442089b4dfa35e7cedaa326df63017af88385e6b20"
dependencies = [
"byteorder",
"num-traits",
@@ -5906,9 +5917,9 @@ dependencies = [
[[package]]
name = "rmpv"
-version = "1.0.0"
+version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "de8813b3a2f95c5138fe5925bfb8784175d88d6bff059ba8ce090aa891319754"
+checksum = "2e0e0214a4a2b444ecce41a4025792fc31f77c7bb89c46d253953ea8c65701ec"
dependencies = [
"num-traits",
"rmp",
@@ -6034,7 +6045,7 @@ dependencies = [
"proc-macro2",
"quote",
"rust-embed-utils",
- "syn 2.0.26",
+ "syn 2.0.27",
"walkdir",
]
@@ -6419,6 +6430,7 @@ name = "search"
version = "0.1.0"
dependencies = [
"anyhow",
+ "bitflags 1.3.2",
"client",
"collections",
"editor",
@@ -6445,9 +6457,9 @@ dependencies = [
[[package]]
name = "security-framework"
-version = "2.9.1"
+version = "2.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1fc758eb7bffce5b308734e9b0c1468893cae9ff70ebf13e7090be8dcbcc83a8"
+checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de"
dependencies = [
"bitflags 1.3.2",
"core-foundation",
@@ -6458,9 +6470,9 @@ dependencies = [
[[package]]
name = "security-framework-sys"
-version = "2.9.0"
+version = "2.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f51d0c0d83bec45f16480d0ce0058397a69e48fcdc52d1dc8855fb68acbd31a7"
+checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a"
dependencies = [
"core-foundation-sys 0.8.3",
"libc",
@@ -6538,22 +6550,22 @@ checksum = "5a9f47faea3cad316faa914d013d24f471cd90bfca1a0c70f05a3f42c6441e99"
[[package]]
name = "serde"
-version = "1.0.171"
+version = "1.0.175"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "30e27d1e4fd7659406c492fd6cfaf2066ba8773de45ca75e855590f856dc34a9"
+checksum = "5d25439cd7397d044e2748a6fe2432b5e85db703d6d097bd014b3c0ad1ebff0b"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
-version = "1.0.171"
+version = "1.0.175"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "389894603bd18c46fa56231694f8d827779c0951a667087194cf9de94ed24682"
+checksum = "b23f7ade6f110613c0d63858ddb8b94c1041f550eab58a16b371bdf2c9c80ab4"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.26",
+ "syn 2.0.27",
]
[[package]]
@@ -6602,13 +6614,13 @@ dependencies = [
[[package]]
name = "serde_repr"
-version = "0.1.14"
+version = "0.1.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1d89a8107374290037607734c0b73a85db7ed80cae314b3c5791f192a496e731"
+checksum = "e168eaaf71e8f9bd6037feb05190485708e019f4fd87d161b3c0a0d37daf85e5"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.26",
+ "syn 2.0.27",
]
[[package]]
@@ -6749,9 +6761,9 @@ checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3"
[[package]]
name = "signal-hook"
-version = "0.3.16"
+version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b824b6e687aff278cdbf3b36f07aa52d4bd4099699324d5da86a2ebce3aa00b3"
+checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801"
dependencies = [
"libc",
"signal-hook-registry",
@@ -7276,9 +7288,9 @@ dependencies = [
[[package]]
name = "syn"
-version = "2.0.26"
+version = "2.0.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "45c3457aacde3c65315de5031ec191ce46604304d2446e803d71ade03308d970"
+checksum = "b60f673f44a8255b9c8c657daf66a596d435f2da81a555b06dc644d080ba45e0"
dependencies = [
"proc-macro2",
"quote",
@@ -7346,9 +7358,9 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
[[package]]
name = "target-lexicon"
-version = "0.12.9"
+version = "0.12.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "df8e77cb757a61f51b947ec4a7e3646efd825b73561db1c232a8ccb639e611a0"
+checksum = "1d2faeef5759ab89935255b1a4cd98e0baf99d1085e37d36599c625dac49ae8e"
[[package]]
name = "tempdir"
@@ -7362,15 +7374,14 @@ dependencies = [
[[package]]
name = "tempfile"
-version = "3.6.0"
+version = "3.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "31c0432476357e58790aaa47a8efb0c5138f137343f3b5f23bd36a27e3b0a6d6"
+checksum = "5486094ee78b2e5038a6382ed7645bc084dc2ec433426ca4c3cb61e2007b8998"
dependencies = [
- "autocfg",
"cfg-if 1.0.0",
- "fastrand",
+ "fastrand 2.0.0",
"redox_syscall 0.3.5",
- "rustix 0.37.23",
+ "rustix 0.38.4",
"windows-sys",
]
@@ -7517,22 +7528,22 @@ dependencies = [
[[package]]
name = "thiserror"
-version = "1.0.43"
+version = "1.0.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a35fc5b8971143ca348fa6df4f024d4d55264f3468c71ad1c2f365b0a4d58c42"
+checksum = "611040a08a0439f8248d1990b111c95baa9c704c805fa1f62104b39655fd7f90"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
-version = "1.0.43"
+version = "1.0.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "463fe12d7993d3b327787537ce8dd4dfa058de32fc2b195ef3cde03dc4771e8f"
+checksum = "090198534930841fab3a5d1bb637cde49e339654e606195f8d9c76eeb081dc96"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.26",
+ "syn 2.0.27",
]
[[package]]
@@ -7721,7 +7732,7 @@ checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.26",
+ "syn 2.0.27",
]
[[package]]
@@ -7926,7 +7937,7 @@ checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.26",
+ "syn 2.0.27",
]
[[package]]
@@ -8000,11 +8011,20 @@ dependencies = [
"regex",
]
+[[package]]
+name = "tree-sitter-bash"
+version = "0.19.0"
+source = "git+https://github.com/tree-sitter/tree-sitter-bash?rev=1b0321ee85701d5036c334a6f04761cdc672e64c#1b0321ee85701d5036c334a6f04761cdc672e64c"
+dependencies = [
+ "cc",
+ "tree-sitter",
+]
+
[[package]]
name = "tree-sitter-c"
-version = "0.20.2"
+version = "0.20.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cca211f4827d4b4dc79f388bf67b6fa3bc8a8cfa642161ef24f99f371ba34c7b"
+checksum = "fa1bb73a4101c88775e4fefcd0543ee25e192034484a5bd45cb99eefb997dca9"
dependencies = [
"cc",
"tree-sitter",
@@ -8012,9 +8032,9 @@ dependencies = [
[[package]]
name = "tree-sitter-cpp"
-version = "0.20.0"
+version = "0.20.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8a869e3c5cef4e5db4e9ab16a8dc84d73010e60ada14cdc60d2f6d8aed17779d"
+checksum = "0dbedbf4066bfab725b3f9e2a21530507419a7d2f98621d3c13213502b734ec0"
dependencies = [
"cc",
"tree-sitter",
@@ -8048,6 +8068,16 @@ dependencies = [
"tree-sitter",
]
+[[package]]
+name = "tree-sitter-elm"
+version = "5.6.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "95236155fa1cd5fcf92123e7e6aa7b6e8c6756b54b5d39afd792a23bd6c9eb7b"
+dependencies = [
+ "cc",
+ "tree-sitter",
+]
+
[[package]]
name = "tree-sitter-embedded-template"
version = "0.20.0"
@@ -8058,6 +8088,15 @@ dependencies = [
"tree-sitter",
]
+[[package]]
+name = "tree-sitter-glsl"
+version = "0.1.4"
+source = "git+https://github.com/theHamsta/tree-sitter-glsl?rev=2a56fb7bc8bb03a1892b4741279dd0a8758b7fb3#2a56fb7bc8bb03a1892b4741279dd0a8758b7fb3"
+dependencies = [
+ "cc",
+ "tree-sitter",
+]
+
[[package]]
name = "tree-sitter-go"
version = "0.19.1"
@@ -8135,9 +8174,9 @@ dependencies = [
[[package]]
name = "tree-sitter-python"
-version = "0.20.2"
+version = "0.20.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dda114f58048f5059dcf158aff691dffb8e113e6d2b50d94263fd68711975287"
+checksum = "f47ebd9cac632764b2f4389b08517bf2ef895431dd163eb562e3d2062cc23a14"
dependencies = [
"cc",
"tree-sitter",
@@ -8409,9 +8448,9 @@ dependencies = [
[[package]]
name = "urlencoding"
-version = "2.1.2"
+version = "2.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e8db7427f936968176eaa7cdf81b7f98b980b18495ec28f1b5791ac3bfe3eea9"
+checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da"
[[package]]
name = "usvg"
@@ -8571,6 +8610,7 @@ dependencies = [
"indoc",
"itertools",
"language",
+ "language_selector",
"log",
"nvim-rs",
"parking_lot 0.11.2",
@@ -8580,6 +8620,7 @@ dependencies = [
"serde_derive",
"serde_json",
"settings",
+ "theme",
"tokio",
"util",
"workspace",
@@ -8711,7 +8752,7 @@ dependencies = [
"once_cell",
"proc-macro2",
"quote",
- "syn 2.0.26",
+ "syn 2.0.27",
"wasm-bindgen-shared",
]
@@ -8745,7 +8786,7 @@ checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.26",
+ "syn 2.0.27",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
@@ -9328,9 +9369,9 @@ checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a"
[[package]]
name = "winnow"
-version = "0.5.0"
+version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "81fac9742fd1ad1bd9643b991319f72dd031016d44b77039a26977eb667141e7"
+checksum = "25b5872fa2e10bd067ae946f927e726d7d603eaeb6e02fa6a350e0722d2b8c11"
dependencies = [
"memchr",
]
@@ -9460,7 +9501,7 @@ name = "xtask"
version = "0.1.0"
dependencies = [
"anyhow",
- "clap 4.3.15",
+ "clap 4.3.19",
"schemars",
"serde_json",
"theme",
@@ -9495,7 +9536,7 @@ dependencies = [
[[package]]
name = "zed"
-version = "0.96.0"
+version = "0.97.0"
dependencies = [
"activity_indicator",
"ai",
@@ -9580,11 +9621,14 @@ dependencies = [
"tiny_http",
"toml",
"tree-sitter",
+ "tree-sitter-bash",
"tree-sitter-c",
"tree-sitter-cpp",
"tree-sitter-css",
"tree-sitter-elixir 0.1.0 (git+https://github.com/elixir-lang/tree-sitter-elixir?rev=4ba9dab6e2602960d95b2b625f3386c27e08084e)",
+ "tree-sitter-elm",
"tree-sitter-embedded-template",
+ "tree-sitter-glsl",
"tree-sitter-go",
"tree-sitter-heex",
"tree-sitter-html",
@@ -9636,7 +9680,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.26",
+ "syn 2.0.27",
]
[[package]]
diff --git a/Cargo.toml b/Cargo.toml
index 04f2147431..fc021bee79 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -107,11 +107,14 @@ tree-sitter = "0.20"
unindent = { version = "0.1.7" }
pretty_assertions = "1.3.0"
+tree-sitter-bash = { git = "https://github.com/tree-sitter/tree-sitter-bash", rev = "1b0321ee85701d5036c334a6f04761cdc672e64c" }
tree-sitter-c = "0.20.1"
tree-sitter-cpp = "0.20.0"
tree-sitter-css = { git = "https://github.com/tree-sitter/tree-sitter-css", rev = "769203d0f9abe1a9a691ac2b9fe4bb4397a73c51" }
tree-sitter-elixir = { git = "https://github.com/elixir-lang/tree-sitter-elixir", rev = "4ba9dab6e2602960d95b2b625f3386c27e08084e" }
+tree-sitter-elm = "5.6.4"
tree-sitter-embedded-template = "0.20.0"
+tree-sitter-glsl = { git = "https://github.com/theHamsta/tree-sitter-glsl", rev = "2a56fb7bc8bb03a1892b4741279dd0a8758b7fb3" }
tree-sitter-go = { git = "https://github.com/tree-sitter/tree-sitter-go", rev = "aeb2f33b366fd78d5789ff104956ce23508b85db" }
tree-sitter-heex = { git = "https://github.com/phoenixframework/tree-sitter-heex", rev = "2e1348c3cf2c9323e87c2744796cf3f3868aa82a" }
tree-sitter-json = { git = "https://github.com/tree-sitter/tree-sitter-json", rev = "40a81c01a40ac48744e0c8ccabbaba1920441199" }
diff --git a/assets/icons/file_icons/archive.svg b/assets/icons/file_icons/archive.svg
new file mode 100644
index 0000000000..35e3dc59bd
--- /dev/null
+++ b/assets/icons/file_icons/archive.svg
@@ -0,0 +1,5 @@
+
diff --git a/assets/icons/file_icons/audio.svg b/assets/icons/file_icons/audio.svg
new file mode 100644
index 0000000000..c2275efb63
--- /dev/null
+++ b/assets/icons/file_icons/audio.svg
@@ -0,0 +1,6 @@
+
diff --git a/assets/icons/file_icons/book.svg b/assets/icons/file_icons/book.svg
new file mode 100644
index 0000000000..c9aa764d72
--- /dev/null
+++ b/assets/icons/file_icons/book.svg
@@ -0,0 +1,6 @@
+
diff --git a/assets/icons/file_icons/camera.svg b/assets/icons/file_icons/camera.svg
new file mode 100644
index 0000000000..bc1993ad63
--- /dev/null
+++ b/assets/icons/file_icons/camera.svg
@@ -0,0 +1,4 @@
+
diff --git a/assets/icons/file_icons/chevron_down.svg b/assets/icons/file_icons/chevron_down.svg
new file mode 100644
index 0000000000..b971555cfa
--- /dev/null
+++ b/assets/icons/file_icons/chevron_down.svg
@@ -0,0 +1,3 @@
+
diff --git a/assets/icons/file_icons/chevron_left.svg b/assets/icons/file_icons/chevron_left.svg
new file mode 100644
index 0000000000..8e61beed5d
--- /dev/null
+++ b/assets/icons/file_icons/chevron_left.svg
@@ -0,0 +1,3 @@
+
diff --git a/assets/icons/file_icons/chevron_right.svg b/assets/icons/file_icons/chevron_right.svg
new file mode 100644
index 0000000000..fcd9d83fc2
--- /dev/null
+++ b/assets/icons/file_icons/chevron_right.svg
@@ -0,0 +1,3 @@
+
diff --git a/assets/icons/file_icons/chevron_up.svg b/assets/icons/file_icons/chevron_up.svg
new file mode 100644
index 0000000000..171cdd61c0
--- /dev/null
+++ b/assets/icons/file_icons/chevron_up.svg
@@ -0,0 +1,3 @@
+
diff --git a/assets/icons/file_icons/code.svg b/assets/icons/file_icons/code.svg
new file mode 100644
index 0000000000..5e59cbe58f
--- /dev/null
+++ b/assets/icons/file_icons/code.svg
@@ -0,0 +1,4 @@
+
diff --git a/assets/icons/file_icons/database.svg b/assets/icons/file_icons/database.svg
new file mode 100644
index 0000000000..812d147717
--- /dev/null
+++ b/assets/icons/file_icons/database.svg
@@ -0,0 +1,5 @@
+
diff --git a/assets/icons/file_icons/eslint.svg b/assets/icons/file_icons/eslint.svg
new file mode 100644
index 0000000000..14ac83df96
--- /dev/null
+++ b/assets/icons/file_icons/eslint.svg
@@ -0,0 +1,4 @@
+
diff --git a/assets/icons/file_icons/file.svg b/assets/icons/file_icons/file.svg
new file mode 100644
index 0000000000..bfffe03684
--- /dev/null
+++ b/assets/icons/file_icons/file.svg
@@ -0,0 +1,5 @@
+
diff --git a/assets/icons/file_icons/file_types.json b/assets/icons/file_icons/file_types.json
new file mode 100644
index 0000000000..0ccf9c2bb7
--- /dev/null
+++ b/assets/icons/file_icons/file_types.json
@@ -0,0 +1,159 @@
+{
+ "suffixes": {
+ "aac": "audio",
+ "bash": "terminal",
+ "bmp": "image",
+ "c": "code",
+ "conf": "settings",
+ "cpp": "code",
+ "cc": "code",
+ "css": "code",
+ "doc": "document",
+ "docx": "document",
+ "eslintrc": "eslint",
+ "eslintrc.js": "eslint",
+ "eslintrc.json": "eslint",
+ "flac": "audio",
+ "fish": "terminal",
+ "gitattributes": "vcs",
+ "gitignore": "vcs",
+ "gitmodules": "vcs",
+ "gif": "image",
+ "go": "code",
+ "h": "code",
+ "handlebars": "code",
+ "hbs": "template",
+ "htm": "template",
+ "html": "template",
+ "svelte": "template",
+ "hpp": "code",
+ "ico": "image",
+ "ini": "settings",
+ "java": "code",
+ "jpeg": "image",
+ "jpg": "image",
+ "js": "code",
+ "json": "storage",
+ "lock": "lock",
+ "log": "log",
+ "md": "document",
+ "mdx": "document",
+ "mp3": "audio",
+ "mp4": "video",
+ "ods": "document",
+ "odp": "document",
+ "odt": "document",
+ "ogg": "video",
+ "pdf": "document",
+ "php": "code",
+ "png": "image",
+ "ppt": "document",
+ "pptx": "document",
+ "prettierrc": "prettier",
+ "prettierignore": "prettier",
+ "ps1": "terminal",
+ "psd": "image",
+ "py": "code",
+ "rb": "code",
+ "rkt": "code",
+ "rs": "rust",
+ "rtf": "document",
+ "scm": "code",
+ "sh": "terminal",
+ "bashrc": "terminal",
+ "bash_profile": "terminal",
+ "bash_aliases": "terminal",
+ "bash_logout": "terminal",
+ "profile": "terminal",
+ "zshrc": "terminal",
+ "zshenv": "terminal",
+ "zsh_profile": "terminal",
+ "zsh_aliases": "terminal",
+ "zsh_histfile": "terminal",
+ "zlogin": "terminal",
+ "sql": "code",
+ "svg": "image",
+ "swift": "code",
+ "tiff": "image",
+ "toml": "toml",
+ "ts": "typescript",
+ "tsx": "code",
+ "txt": "document",
+ "wav": "audio",
+ "webm": "video",
+ "xls": "document",
+ "xlsx": "document",
+ "xml": "template",
+ "yaml": "settings",
+ "yml": "settings",
+ "zsh": "terminal"
+ },
+ "types": {
+ "audio": {
+ "icon": "icons/file_icons/audio.svg"
+ },
+ "code": {
+ "icon": "icons/file_icons/code.svg"
+ },
+ "collapsed_chevron": {
+ "icon": "icons/file_icons/chevron_right.svg"
+ },
+ "collapsed_folder": {
+ "icon": "icons/file_icons/folder.svg"
+ },
+ "default": {
+ "icon": "icons/file_icons/file.svg"
+ },
+ "document": {
+ "icon": "icons/file_icons/book.svg"
+ },
+ "eslint": {
+ "icon": "icons/file_icons/eslint.svg"
+ },
+ "expanded_chevron": {
+ "icon": "icons/file_icons/chevron_down.svg"
+ },
+ "expanded_folder": {
+ "icon": "icons/file_icons/folder_open.svg"
+ },
+ "image": {
+ "icon": "icons/file_icons/image.svg"
+ },
+ "lock": {
+ "icon": "icons/file_icons/lock.svg"
+ },
+ "log": {
+ "icon": "icons/file_icons/info.svg"
+ },
+ "prettier": {
+ "icon": "icons/file_icons/prettier.svg"
+ },
+ "rust": {
+ "icon": "icons/file_icons/rust.svg"
+ },
+ "settings": {
+ "icon": "icons/file_icons/settings.svg"
+ },
+ "storage": {
+ "icon": "icons/file_icons/database.svg"
+ },
+ "template": {
+ "icon": "icons/file_icons/html.svg"
+ },
+ "terminal": {
+ "icon": "icons/file_icons/terminal.svg"
+ },
+ "toml": {
+ "icon": "icons/file_icons/toml.svg"
+ },
+ "typescript": {
+ "icon": "icons/file_icons/typescript.svg"
+ },
+ "vcs": {
+ "icon": "icons/file_icons/git.svg"
+ },
+ "video": {
+ "icon": "icons/file_icons/video.svg"
+ }
+ }
+}
diff --git a/assets/icons/file_icons/folder.svg b/assets/icons/file_icons/folder.svg
new file mode 100644
index 0000000000..fd45ab1c44
--- /dev/null
+++ b/assets/icons/file_icons/folder.svg
@@ -0,0 +1,5 @@
+
diff --git a/assets/icons/file_icons/folder_open.svg b/assets/icons/file_icons/folder_open.svg
new file mode 100644
index 0000000000..55c7d51649
--- /dev/null
+++ b/assets/icons/file_icons/folder_open.svg
@@ -0,0 +1,5 @@
+
diff --git a/assets/icons/file_icons/git.svg b/assets/icons/file_icons/git.svg
new file mode 100644
index 0000000000..a30b47fb86
--- /dev/null
+++ b/assets/icons/file_icons/git.svg
@@ -0,0 +1,6 @@
+
diff --git a/assets/icons/file_icons/hash.svg b/assets/icons/file_icons/hash.svg
new file mode 100644
index 0000000000..edd0462678
--- /dev/null
+++ b/assets/icons/file_icons/hash.svg
@@ -0,0 +1,6 @@
+
diff --git a/assets/icons/file_icons/html.svg b/assets/icons/file_icons/html.svg
new file mode 100644
index 0000000000..ba9ec14299
--- /dev/null
+++ b/assets/icons/file_icons/html.svg
@@ -0,0 +1,5 @@
+
diff --git a/assets/icons/file_icons/image.svg b/assets/icons/file_icons/image.svg
new file mode 100644
index 0000000000..d9d5b82af1
--- /dev/null
+++ b/assets/icons/file_icons/image.svg
@@ -0,0 +1,7 @@
+
diff --git a/assets/icons/file_icons/info.svg b/assets/icons/file_icons/info.svg
new file mode 100644
index 0000000000..e84ae7c628
--- /dev/null
+++ b/assets/icons/file_icons/info.svg
@@ -0,0 +1,5 @@
+
diff --git a/assets/icons/file_icons/lock.svg b/assets/icons/file_icons/lock.svg
new file mode 100644
index 0000000000..14fed3941a
--- /dev/null
+++ b/assets/icons/file_icons/lock.svg
@@ -0,0 +1,6 @@
+
diff --git a/assets/icons/file_icons/notebook.svg b/assets/icons/file_icons/notebook.svg
new file mode 100644
index 0000000000..4f55ceac58
--- /dev/null
+++ b/assets/icons/file_icons/notebook.svg
@@ -0,0 +1,8 @@
+
diff --git a/assets/icons/file_icons/package.svg b/assets/icons/file_icons/package.svg
new file mode 100644
index 0000000000..a46126e3e9
--- /dev/null
+++ b/assets/icons/file_icons/package.svg
@@ -0,0 +1,4 @@
+
diff --git a/assets/icons/file_icons/prettier.svg b/assets/icons/file_icons/prettier.svg
new file mode 100644
index 0000000000..23cefe0efc
--- /dev/null
+++ b/assets/icons/file_icons/prettier.svg
@@ -0,0 +1,12 @@
+
diff --git a/assets/icons/file_icons/rust.svg b/assets/icons/file_icons/rust.svg
new file mode 100644
index 0000000000..91982b3eeb
--- /dev/null
+++ b/assets/icons/file_icons/rust.svg
@@ -0,0 +1,4 @@
+
diff --git a/assets/icons/file_icons/settings.svg b/assets/icons/file_icons/settings.svg
new file mode 100644
index 0000000000..35af7e1899
--- /dev/null
+++ b/assets/icons/file_icons/settings.svg
@@ -0,0 +1,4 @@
+
diff --git a/assets/icons/file_icons/terminal.svg b/assets/icons/file_icons/terminal.svg
new file mode 100644
index 0000000000..15dd705b0b
--- /dev/null
+++ b/assets/icons/file_icons/terminal.svg
@@ -0,0 +1,5 @@
+
diff --git a/assets/icons/file_icons/toml.svg b/assets/icons/file_icons/toml.svg
new file mode 100644
index 0000000000..496c41e755
--- /dev/null
+++ b/assets/icons/file_icons/toml.svg
@@ -0,0 +1,5 @@
+
diff --git a/assets/icons/file_icons/typescript.svg b/assets/icons/file_icons/typescript.svg
new file mode 100644
index 0000000000..f7748a86c4
--- /dev/null
+++ b/assets/icons/file_icons/typescript.svg
@@ -0,0 +1,5 @@
+
diff --git a/assets/icons/file_icons/video.svg b/assets/icons/file_icons/video.svg
new file mode 100644
index 0000000000..c7ebf98af6
--- /dev/null
+++ b/assets/icons/file_icons/video.svg
@@ -0,0 +1,4 @@
+
diff --git a/assets/keymaps/default.json b/assets/keymaps/default.json
index 1a13d8cdb3..7553c19925 100644
--- a/assets/keymaps/default.json
+++ b/assets/keymaps/default.json
@@ -195,8 +195,8 @@
{
"context": "Editor && mode == auto_height",
"bindings": {
- "shift-enter": "editor::Newline",
- "cmd-shift-enter": "editor::NewlineBelow"
+ "ctrl-enter": "editor::Newline",
+ "ctrl-shift-enter": "editor::NewlineBelow"
}
},
{
@@ -406,6 +406,7 @@
"cmd-b": "workspace::ToggleLeftDock",
"cmd-r": "workspace::ToggleRightDock",
"cmd-j": "workspace::ToggleBottomDock",
+ "alt-cmd-y": "workspace::CloseAllDocks",
"cmd-shift-f": "workspace::NewSearch",
"cmd-k cmd-t": "theme_selector::Toggle",
"cmd-k cmd-s": "zed::OpenKeymap",
@@ -446,8 +447,22 @@
},
{
"bindings": {
- "cmd-k cmd-left": "workspace::ActivatePreviousPane",
- "cmd-k cmd-right": "workspace::ActivateNextPane"
+ "cmd-k cmd-left": [
+ "workspace::ActivatePaneInDirection",
+ "Left"
+ ],
+ "cmd-k cmd-right": [
+ "workspace::ActivatePaneInDirection",
+ "Right"
+ ],
+ "cmd-k cmd-up": [
+ "workspace::ActivatePaneInDirection",
+ "Up"
+ ],
+ "cmd-k cmd-down": [
+ "workspace::ActivatePaneInDirection",
+ "Down"
+ ]
}
},
// Bindings from Atom
@@ -513,8 +528,11 @@
"cmd-alt-c": "project_panel::CopyPath",
"alt-cmd-shift-c": "project_panel::CopyRelativePath",
"f2": "project_panel::Rename",
+ "enter": "project_panel::Rename",
+ "space": "project_panel::Open",
"backspace": "project_panel::Delete",
- "alt-cmd-r": "project_panel::RevealInFinder"
+ "alt-cmd-r": "project_panel::RevealInFinder",
+ "alt-shift-f": "project_panel::NewSearchInDirectory"
}
},
{
diff --git a/assets/keymaps/vim.json b/assets/keymaps/vim.json
index e6421114ec..94a271f037 100644
--- a/assets/keymaps/vim.json
+++ b/assets/keymaps/vim.json
@@ -2,12 +2,6 @@
{
"context": "Editor && VimControl && !VimWaiting && !menu",
"bindings": {
- "g": [
- "vim::PushOperator",
- {
- "Namespace": "G"
- }
- ],
"i": [
"vim::PushOperator",
{
@@ -30,6 +24,8 @@
"j": "vim::Down",
"down": "vim::Down",
"enter": "vim::NextLineStart",
+ "tab": "vim::Tab",
+ "shift-tab": "vim::Tab",
"k": "vim::Up",
"up": "vim::Up",
"l": "vim::Right",
@@ -60,6 +56,8 @@
"ignorePunctuation": true
}
],
+ "n": "search::SelectNextMatch",
+ "shift-n": "search::SelectPrevMatch",
"%": "vim::Matching",
"f": [
"vim::PushOperator",
@@ -103,7 +101,35 @@
"vim::SwitchMode",
"Normal"
],
+ "*": "vim::MoveToNext",
+ "#": "vim::MoveToPrev",
"0": "vim::StartOfLine", // When no number operator present, use start of line motion
+ // "g" commands
+ "g g": "vim::StartOfDocument",
+ "g h": "editor::Hover",
+ "g t": "pane::ActivateNextItem",
+ "g shift-t": "pane::ActivatePrevItem",
+ "g d": "editor::GoToDefinition",
+ "g shift-d": "editor::GoToTypeDefinition",
+ "g .": "editor::ToggleCodeActions", // zed specific
+ "g shift-a": "editor::FindAllReferences", // zed specific
+ "g *": [
+ "vim::MoveToNext",
+ {
+ "partialWord": true
+ }
+ ],
+ "g #": [
+ "vim::MoveToPrev",
+ {
+ "partialWord": true
+ }
+ ],
+ // z commands
+ "z t": "editor::ScrollCursorTop",
+ "z z": "editor::ScrollCursorCenter",
+ "z b": "editor::ScrollCursorBottom",
+ // Count support
"1": [
"vim::Number",
1
@@ -139,7 +165,75 @@
"9": [
"vim::Number",
9
- ]
+ ],
+ // window related commands (ctrl-w X)
+ "ctrl-w left": [
+ "workspace::ActivatePaneInDirection",
+ "Left"
+ ],
+ "ctrl-w right": [
+ "workspace::ActivatePaneInDirection",
+ "Right"
+ ],
+ "ctrl-w up": [
+ "workspace::ActivatePaneInDirection",
+ "Up"
+ ],
+ "ctrl-w down": [
+ "workspace::ActivatePaneInDirection",
+ "Down"
+ ],
+ "ctrl-w h": [
+ "workspace::ActivatePaneInDirection",
+ "Left"
+ ],
+ "ctrl-w l": [
+ "workspace::ActivatePaneInDirection",
+ "Right"
+ ],
+ "ctrl-w k": [
+ "workspace::ActivatePaneInDirection",
+ "Up"
+ ],
+ "ctrl-w j": [
+ "workspace::ActivatePaneInDirection",
+ "Down"
+ ],
+ "ctrl-w ctrl-h": [
+ "workspace::ActivatePaneInDirection",
+ "Left"
+ ],
+ "ctrl-w ctrl-l": [
+ "workspace::ActivatePaneInDirection",
+ "Right"
+ ],
+ "ctrl-w ctrl-k": [
+ "workspace::ActivatePaneInDirection",
+ "Up"
+ ],
+ "ctrl-w ctrl-j": [
+ "workspace::ActivatePaneInDirection",
+ "Down"
+ ],
+ "ctrl-w g t": "pane::ActivateNextItem",
+ "ctrl-w ctrl-g t": "pane::ActivateNextItem",
+ "ctrl-w g shift-t": "pane::ActivatePrevItem",
+ "ctrl-w ctrl-g shift-t": "pane::ActivatePrevItem",
+ "ctrl-w w": "workspace::ActivateNextPane",
+ "ctrl-w ctrl-w": "workspace::ActivateNextPane",
+ "ctrl-w p": "workspace::ActivatePreviousPane",
+ "ctrl-w ctrl-p": "workspace::ActivatePreviousPane",
+ "ctrl-w shift-w": "workspace::ActivatePreviousPane",
+ "ctrl-w ctrl-shift-w": "workspace::ActivatePreviousPane",
+ "ctrl-w v": "pane::SplitLeft",
+ "ctrl-w ctrl-v": "pane::SplitLeft",
+ "ctrl-w s": "pane::SplitUp",
+ "ctrl-w shift-s": "pane::SplitUp",
+ "ctrl-w ctrl-s": "pane::SplitUp",
+ "ctrl-w c": "pane::CloseAllItems",
+ "ctrl-w ctrl-c": "pane::CloseAllItems",
+ "ctrl-w q": "pane::CloseAllItems",
+ "ctrl-w ctrl-q": "pane::CloseAllItems"
}
},
{
@@ -160,12 +254,6 @@
"vim::PushOperator",
"Yank"
],
- "z": [
- "vim::PushOperator",
- {
- "Namespace": "Z"
- }
- ],
"i": [
"vim::SwitchMode",
"Insert"
@@ -197,10 +285,18 @@
"p": "vim::Paste",
"u": "editor::Undo",
"ctrl-r": "editor::Redo",
- "/": [
- "buffer_search::Deploy",
+ "/": "vim::Search",
+ "?": [
+ "vim::Search",
{
- "focus": true
+ "backwards": true
+ }
+ ],
+ ";": "vim::RepeatFind",
+ ",": [
+ "vim::RepeatFind",
+ {
+ "backwards": true
}
],
"ctrl-f": "vim::PageDown",
@@ -231,20 +327,11 @@
]
}
},
- {
- "context": "Editor && vim_operator == g",
- "bindings": {
- "g": "vim::StartOfDocument",
- "h": "editor::Hover",
- "t": "pane::ActivateNextItem",
- "shift-t": "pane::ActivatePrevItem",
- "d": "editor::GoToDefinition"
- }
- },
{
"context": "Editor && vim_operator == c",
"bindings": {
- "c": "vim::CurrentLine"
+ "c": "vim::CurrentLine",
+ "d": "editor::Rename" // zed specific
}
},
{
@@ -259,14 +346,6 @@
"y": "vim::CurrentLine"
}
},
- {
- "context": "Editor && vim_operator == z",
- "bindings": {
- "t": "editor::ScrollCursorTop",
- "z": "editor::ScrollCursorCenter",
- "b": "editor::ScrollCursorBottom",
- }
- },
{
"context": "Editor && VimObject",
"bindings": {
@@ -310,8 +389,8 @@
"vim::SwitchMode",
"Normal"
],
- "> >": "editor::Indent",
- "< <": "editor::Outdent"
+ ">": "editor::Indent",
+ "<": "editor::Outdent"
}
},
{
@@ -319,7 +398,7 @@
"bindings": {
"escape": "vim::NormalBefore",
"ctrl-c": "vim::NormalBefore",
- "ctrl-[": "vim::NormalBefore",
+ "ctrl-[": "vim::NormalBefore"
}
},
{
@@ -336,5 +415,12 @@
"Normal"
]
}
+ },
+ {
+ "context": "BufferSearchBar > VimEnabled",
+ "bindings": {
+ "enter": "vim::SearchSubmit",
+ "escape": "buffer_search::Dismiss"
+ }
}
]
diff --git a/assets/settings/default.json b/assets/settings/default.json
index ce272b32e8..397dac0961 100644
--- a/assets/settings/default.json
+++ b/assets/settings/default.json
@@ -50,6 +50,13 @@
// Whether to pop the completions menu while typing in an editor without
// explicitly requesting it.
"show_completions_on_input": true,
+ // Whether to show wrap guides in the editor. Setting this to true will
+ // show a guide at the 'preferred_line_length' value if softwrap is set to
+ // 'preferred_line_length', and will show any additional guides as specified
+ // by the 'wrap_guides' setting.
+ "show_wrap_guides": true,
+ // Character counts at which to show wrap guides in the editor.
+ "wrap_guides": [],
// Whether to use additional LSP queries to format (and amend) the code after
// every "trigger" symbol input, defined by LSP server capabilities.
"use_on_type_format": true,
@@ -66,6 +73,11 @@
// 3. Draw all invisible symbols:
// "all"
"show_whitespaces": "selection",
+ // Settings related to calls in Zed
+ "calls": {
+ // Join calls with the microphone muted by default
+ "mute_on_join": true
+ },
// Scrollbar related settings
"scrollbar": {
// When to show the scrollbar in the editor.
@@ -97,12 +109,18 @@
"show_other_hints": true
},
"project_panel": {
- // Whether to show the git status in the project panel.
- "git_status": true,
+ // Default width of the project panel.
+ "default_width": 240,
// Where to dock project panel. Can be 'left' or 'right'.
"dock": "left",
- // Default width of the project panel.
- "default_width": 240
+ // Whether to show file icons in the project panel.
+ "file_icons": true,
+ // Whether to show folder icons or chevrons for directories in the project panel.
+ "folder_icons": true,
+ // Whether to show the git status in the project panel.
+ "git_status": true,
+ // Amount of indentation for nested items.
+ "indent_size": 20
},
"assistant": {
// Where to dock the assistant. Can be 'left', 'right' or 'bottom'.
@@ -196,9 +214,7 @@
"copilot": {
// The set of glob patterns for which copilot should be disabled
// in any matching file.
- "disabled_globs": [
- ".env"
- ]
+ "disabled_globs": [".env"]
},
// Settings specific to journaling
"journal": {
@@ -347,12 +363,6 @@
// LSP Specific settings.
"lsp": {
// Specify the LSP name as a key here.
- // As of 8/10/22, supported LSPs are:
- // pyright
- // gopls
- // rust-analyzer
- // typescript-language-server
- // vscode-json-languageserver
// "rust-analyzer": {
// //These initialization options are merged into Zed's defaults
// "initialization_options": {
diff --git a/crates/ai/src/assistant.rs b/crates/ai/src/assistant.rs
index 35c88486f7..8a4c04d338 100644
--- a/crates/ai/src/assistant.rs
+++ b/crates/ai/src/assistant.rs
@@ -298,12 +298,22 @@ impl AssistantPanel {
}
fn deploy(&mut self, action: &search::buffer_search::Deploy, cx: &mut ViewContext) {
+ let mut propagate_action = true;
if let Some(search_bar) = self.toolbar.read(cx).item_of_type::() {
- if search_bar.update(cx, |search_bar, cx| search_bar.show(action.focus, true, cx)) {
- return;
- }
+ search_bar.update(cx, |search_bar, cx| {
+ if search_bar.show(cx) {
+ search_bar.search_suggested(cx);
+ if action.focus {
+ search_bar.select_query(cx);
+ cx.focus_self();
+ }
+ propagate_action = false
+ }
+ });
+ }
+ if propagate_action {
+ cx.propagate_action();
}
- cx.propagate_action();
}
fn handle_editor_cancel(&mut self, _: &editor::Cancel, cx: &mut ViewContext) {
@@ -320,13 +330,13 @@ impl AssistantPanel {
fn select_next_match(&mut self, _: &search::SelectNextMatch, cx: &mut ViewContext) {
if let Some(search_bar) = self.toolbar.read(cx).item_of_type::() {
- search_bar.update(cx, |bar, cx| bar.select_match(Direction::Next, cx));
+ search_bar.update(cx, |bar, cx| bar.select_match(Direction::Next, 1, cx));
}
}
fn select_prev_match(&mut self, _: &search::SelectPrevMatch, cx: &mut ViewContext) {
if let Some(search_bar) = self.toolbar.read(cx).item_of_type::() {
- search_bar.update(cx, |bar, cx| bar.select_match(Direction::Prev, cx));
+ search_bar.update(cx, |bar, cx| bar.select_match(Direction::Prev, 1, cx));
}
}
diff --git a/crates/call/Cargo.toml b/crates/call/Cargo.toml
index 61f3593247..eb448d8d8d 100644
--- a/crates/call/Cargo.toml
+++ b/crates/call/Cargo.toml
@@ -36,6 +36,10 @@ anyhow.workspace = true
async-broadcast = "0.4"
futures.workspace = true
postage.workspace = true
+schemars.workspace = true
+serde.workspace = true
+serde_json.workspace = true
+serde_derive.workspace = true
[dev-dependencies]
client = { path = "../client", features = ["test-support"] }
diff --git a/crates/call/src/call.rs b/crates/call/src/call.rs
index cf6dd1799c..2defd6b40f 100644
--- a/crates/call/src/call.rs
+++ b/crates/call/src/call.rs
@@ -1,9 +1,11 @@
+pub mod call_settings;
pub mod participant;
pub mod room;
use std::sync::Arc;
use anyhow::{anyhow, Result};
+use call_settings::CallSettings;
use client::{proto, ClickhouseEvent, Client, TelemetrySettings, TypedEnvelope, User, UserStore};
use collections::HashSet;
use futures::{future::Shared, FutureExt};
@@ -19,6 +21,8 @@ pub use participant::ParticipantLocation;
pub use room::Room;
pub fn init(client: Arc, user_store: ModelHandle, cx: &mut AppContext) {
+ settings::register::(cx);
+
let active_call = cx.add_model(|cx| ActiveCall::new(client, user_store, cx));
cx.set_global(active_call);
}
@@ -280,21 +284,6 @@ 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,
diff --git a/crates/call/src/call_settings.rs b/crates/call/src/call_settings.rs
new file mode 100644
index 0000000000..2808a99617
--- /dev/null
+++ b/crates/call/src/call_settings.rs
@@ -0,0 +1,27 @@
+use schemars::JsonSchema;
+use serde_derive::{Deserialize, Serialize};
+use settings::Setting;
+
+#[derive(Deserialize, Debug)]
+pub struct CallSettings {
+ pub mute_on_join: bool,
+}
+
+#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug)]
+pub struct CallSettingsContent {
+ pub mute_on_join: Option,
+}
+
+impl Setting for CallSettings {
+ const KEY: Option<&'static str> = Some("calls");
+
+ type FileContent = CallSettingsContent;
+
+ 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/call/src/room.rs b/crates/call/src/room.rs
index 87e6faf988..328a94506c 100644
--- a/crates/call/src/room.rs
+++ b/crates/call/src/room.rs
@@ -1,4 +1,5 @@
use crate::{
+ call_settings::CallSettings,
participant::{LocalParticipant, ParticipantLocation, RemoteParticipant, RemoteVideoTrack},
IncomingCall,
};
@@ -153,8 +154,10 @@ impl Room {
cx.spawn(|this, mut cx| async move {
connect.await?;
- this.update(&mut cx, |this, cx| this.share_microphone(cx))
- .await?;
+ if !cx.read(|cx| settings::get::(cx).mute_on_join) {
+ this.update(&mut cx, |this, cx| this.share_microphone(cx))
+ .await?;
+ }
anyhow::Ok(())
})
@@ -656,7 +659,7 @@ impl Room {
peer_id,
projects: participant.projects,
location,
- muted: false,
+ muted: true,
speaking: false,
video_tracks: Default::default(),
audio_tracks: Default::default(),
@@ -670,6 +673,10 @@ impl Room {
live_kit.room.remote_video_tracks(&user.id.to_string());
let audio_tracks =
live_kit.room.remote_audio_tracks(&user.id.to_string());
+ let publications = live_kit
+ .room
+ .remote_audio_track_publications(&user.id.to_string());
+
for track in video_tracks {
this.remote_video_track_updated(
RemoteVideoTrackUpdate::Subscribed(track),
@@ -677,9 +684,15 @@ impl Room {
)
.log_err();
}
- for track in audio_tracks {
+
+ for (track, publication) in
+ audio_tracks.iter().zip(publications.iter())
+ {
this.remote_audio_track_updated(
- RemoteAudioTrackUpdate::Subscribed(track),
+ RemoteAudioTrackUpdate::Subscribed(
+ track.clone(),
+ publication.clone(),
+ ),
cx,
)
.log_err();
@@ -819,8 +832,8 @@ impl Room {
cx.notify();
}
RemoteAudioTrackUpdate::MuteChanged { track_id, muted } => {
+ let mut found = false;
for participant in &mut self.remote_participants.values_mut() {
- let mut found = false;
for track in participant.audio_tracks.values() {
if track.sid() == track_id {
found = true;
@@ -832,16 +845,20 @@ impl Room {
break;
}
}
+
cx.notify();
}
- RemoteAudioTrackUpdate::Subscribed(track) => {
+ RemoteAudioTrackUpdate::Subscribed(track, publication) => {
let user_id = track.publisher_id().parse()?;
let track_id = track.sid().to_string();
let participant = self
.remote_participants
.get_mut(&user_id)
.ok_or_else(|| anyhow!("subscribed to track by unknown participant"))?;
+
participant.audio_tracks.insert(track_id.clone(), track);
+ participant.muted = publication.is_muted();
+
cx.emit(Event::RemoteAudioTracksChanged {
participant_id: participant.peer_id,
});
@@ -1053,7 +1070,7 @@ impl Room {
self.live_kit
.as_ref()
.and_then(|live_kit| match &live_kit.microphone_track {
- LocalTrack::None => None,
+ LocalTrack::None => Some(true),
LocalTrack::Pending { muted, .. } => Some(*muted),
LocalTrack::Published { muted, .. } => Some(*muted),
})
@@ -1070,6 +1087,7 @@ impl Room {
self.live_kit.as_ref().map(|live_kit| live_kit.deafened)
}
+ #[track_caller]
pub fn share_microphone(&mut self, cx: &mut ModelContext) -> Task> {
if self.status.is_offline() {
return Task::ready(Err(anyhow!("room is offline")));
@@ -1244,6 +1262,10 @@ impl Room {
pub fn toggle_mute(&mut self, cx: &mut ModelContext) -> Result>> {
let should_mute = !self.is_muted();
if let Some(live_kit) = self.live_kit.as_mut() {
+ if matches!(live_kit.microphone_track, LocalTrack::None) {
+ return Ok(self.share_microphone(cx));
+ }
+
let (ret_task, old_muted) = live_kit.set_mute(should_mute, cx)?;
live_kit.muted_by_user = should_mute;
diff --git a/crates/client/src/telemetry.rs b/crates/client/src/telemetry.rs
index 959f4cc783..dc5154d96f 100644
--- a/crates/client/src/telemetry.rs
+++ b/crates/client/src/telemetry.rs
@@ -40,6 +40,7 @@ lazy_static! {
struct ClickhouseEventRequestBody {
token: &'static str,
installation_id: Option>,
+ is_staff: Option,
app_version: Option>,
os_name: &'static str,
os_version: Option>,
@@ -224,6 +225,7 @@ impl Telemetry {
&ClickhouseEventRequestBody {
token: ZED_SECRET_CLIENT_TOKEN,
installation_id: state.installation_id.clone(),
+ is_staff: state.is_staff.clone(),
app_version: state.app_version.clone(),
os_name: state.os_name,
os_version: state.os_version.clone(),
diff --git a/crates/collab_ui/src/collab_titlebar_item.rs b/crates/collab_ui/src/collab_titlebar_item.rs
index 6cfc9d8e30..ce8d10d655 100644
--- a/crates/collab_ui/src/collab_titlebar_item.rs
+++ b/crates/collab_ui/src/collab_titlebar_item.rs
@@ -652,10 +652,10 @@ impl CollabTitlebarItem {
let is_muted = room.read(cx).is_muted();
if is_muted {
icon = "icons/radix/mic-mute.svg";
- tooltip = "Unmute microphone\nRight click for options";
+ tooltip = "Unmute microphone";
} else {
icon = "icons/radix/mic.svg";
- tooltip = "Mute microphone\nRight click for options";
+ tooltip = "Mute microphone";
}
let titlebar = &theme.titlebar;
@@ -705,10 +705,10 @@ impl CollabTitlebarItem {
let is_deafened = room.read(cx).is_deafened().unwrap_or(false);
if is_deafened {
icon = "icons/radix/speaker-off.svg";
- tooltip = "Unmute speakers\nRight click for options";
+ tooltip = "Unmute speakers";
} else {
icon = "icons/radix/speaker-loud.svg";
- tooltip = "Mute speakers\nRight click for options";
+ tooltip = "Mute speakers";
}
let titlebar = &theme.titlebar;
diff --git a/crates/collab_ui/src/collab_ui.rs b/crates/collab_ui/src/collab_ui.rs
index 7608fdbfee..df4b502391 100644
--- a/crates/collab_ui/src/collab_ui.rs
+++ b/crates/collab_ui/src/collab_ui.rs
@@ -18,13 +18,7 @@ use workspace::AppState;
actions!(
collab,
- [
- ToggleScreenSharing,
- ToggleMute,
- ToggleDeafen,
- LeaveCall,
- ShareMicrophone
- ]
+ [ToggleScreenSharing, ToggleMute, ToggleDeafen, LeaveCall]
);
pub fn init(app_state: &Arc, cx: &mut AppContext) {
@@ -40,7 +34,6 @@ pub fn init(app_state: &Arc, cx: &mut AppContext) {
cx.add_global_action(toggle_screen_sharing);
cx.add_global_action(toggle_mute);
cx.add_global_action(toggle_deafen);
- cx.add_global_action(share_microphone);
}
pub fn toggle_screen_sharing(_: &ToggleScreenSharing, cx: &mut AppContext) {
@@ -71,10 +64,24 @@ pub fn toggle_screen_sharing(_: &ToggleScreenSharing, cx: &mut AppContext) {
}
pub fn toggle_mute(_: &ToggleMute, cx: &mut AppContext) {
- if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() {
- room.update(cx, Room::toggle_mute)
- .map(|task| task.detach_and_log_err(cx))
- .log_err();
+ let call = ActiveCall::global(cx).read(cx);
+ if let Some(room) = call.room().cloned() {
+ let client = call.client();
+ room.update(cx, |room, cx| {
+ if room.is_muted() {
+ ActiveCall::report_call_event_for_room("enable microphone", room.id(), &client, cx);
+ } else {
+ ActiveCall::report_call_event_for_room(
+ "disable microphone",
+ room.id(),
+ &client,
+ cx,
+ );
+ }
+ room.toggle_mute(cx)
+ })
+ .map(|task| task.detach_and_log_err(cx))
+ .log_err();
}
}
@@ -85,10 +92,3 @@ pub fn toggle_deafen(_: &ToggleDeafen, cx: &mut AppContext) {
.log_err();
}
}
-
-pub fn share_microphone(_: &ShareMicrophone, cx: &mut AppContext) {
- if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() {
- room.update(cx, Room::share_microphone)
- .detach_and_log_err(cx)
- }
-}
diff --git a/crates/editor/Cargo.toml b/crates/editor/Cargo.toml
index 087ce81c26..bc1c904404 100644
--- a/crates/editor/Cargo.toml
+++ b/crates/editor/Cargo.toml
@@ -10,7 +10,6 @@ doctest = false
[features]
test-support = [
- "rand",
"copilot/test-support",
"text/test-support",
"language/test-support",
@@ -62,8 +61,8 @@ serde.workspace = true
serde_derive.workspace = true
smallvec.workspace = true
smol.workspace = true
+rand.workspace = true
-rand = { workspace = true, optional = true }
tree-sitter-rust = { workspace = true, optional = true }
tree-sitter-html = { workspace = true, optional = true }
tree-sitter-typescript = { workspace = true, optional = true }
diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs
index b8a7853a93..e05837740d 100644
--- a/crates/editor/src/editor.rs
+++ b/crates/editor/src/editor.rs
@@ -74,6 +74,7 @@ pub use multi_buffer::{
};
use ordered_float::OrderedFloat;
use project::{FormatTrigger, Location, LocationLink, Project, ProjectPath, ProjectTransaction};
+use rand::{seq::SliceRandom, thread_rng};
use scroll::{
autoscroll::Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, ScrollbarAutoHide,
};
@@ -226,6 +227,10 @@ actions!(
MoveLineUp,
MoveLineDown,
JoinLines,
+ SortLinesCaseSensitive,
+ SortLinesCaseInsensitive,
+ ReverseLines,
+ ShuffleLines,
Transpose,
Cut,
Copy,
@@ -344,6 +349,10 @@ pub fn init(cx: &mut AppContext) {
cx.add_action(Editor::outdent);
cx.add_action(Editor::delete_line);
cx.add_action(Editor::join_lines);
+ cx.add_action(Editor::sort_lines_case_sensitive);
+ cx.add_action(Editor::sort_lines_case_insensitive);
+ cx.add_action(Editor::reverse_lines);
+ cx.add_action(Editor::shuffle_lines);
cx.add_action(Editor::delete_to_previous_word_start);
cx.add_action(Editor::delete_to_previous_subword_start);
cx.add_action(Editor::delete_to_next_word_end);
@@ -549,6 +558,7 @@ pub struct Editor {
pending_rename: Option,
searchable: bool,
cursor_shape: CursorShape,
+ collapse_matches: bool,
workspace: Option<(WeakViewHandle, i64)>,
keymap_context_layers: BTreeMap,
input_enabled: bool,
@@ -562,6 +572,7 @@ pub struct Editor {
inlay_hint_cache: InlayHintCache,
next_inlay_id: usize,
_subscriptions: Vec,
+ pixel_position_of_newest_cursor: Option,
}
pub struct EditorSnapshot {
@@ -1381,6 +1392,7 @@ impl Editor {
searchable: true,
override_text_style: None,
cursor_shape: Default::default(),
+ collapse_matches: false,
workspace: None,
keymap_context_layers: Default::default(),
input_enabled: true,
@@ -1392,6 +1404,7 @@ impl Editor {
copilot_state: Default::default(),
inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
gutter_hovered: false,
+ pixel_position_of_newest_cursor: None,
_subscriptions: vec![
cx.observe(&buffer, Self::on_buffer_changed),
cx.subscribe(&buffer, Self::on_buffer_event),
@@ -1520,6 +1533,17 @@ impl Editor {
cx.notify();
}
+ pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
+ self.collapse_matches = collapse_matches;
+ }
+
+ fn range_for_match(&self, range: &Range) -> Range {
+ if self.collapse_matches {
+ return range.start..range.start;
+ }
+ range.clone()
+ }
+
pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut ViewContext) {
if self.display_map.read(cx).clip_at_line_ends != clip {
self.display_map
@@ -2659,11 +2683,16 @@ impl Editor {
InlayRefreshReason::RefreshRequested => (InvalidationStrategy::RefreshRequested, None),
};
- self.inlay_hint_cache.refresh_inlay_hints(
+ if let Some(InlaySplice {
+ to_remove,
+ to_insert,
+ }) = self.inlay_hint_cache.spawn_hint_refresh(
self.excerpt_visible_offsets(required_languages.as_ref(), cx),
invalidate_cache,
cx,
- )
+ ) {
+ self.splice_inlay_hints(to_remove, to_insert, cx);
+ }
}
fn visible_inlay_hints(&self, cx: &ViewContext<'_, '_, Editor>) -> Vec {
@@ -4185,6 +4214,96 @@ impl Editor {
});
}
+ pub fn sort_lines_case_sensitive(
+ &mut self,
+ _: &SortLinesCaseSensitive,
+ cx: &mut ViewContext,
+ ) {
+ self.manipulate_lines(cx, |text| text.sort())
+ }
+
+ pub fn sort_lines_case_insensitive(
+ &mut self,
+ _: &SortLinesCaseInsensitive,
+ cx: &mut ViewContext,
+ ) {
+ self.manipulate_lines(cx, |text| text.sort_by_key(|line| line.to_lowercase()))
+ }
+
+ pub fn reverse_lines(&mut self, _: &ReverseLines, cx: &mut ViewContext) {
+ self.manipulate_lines(cx, |lines| lines.reverse())
+ }
+
+ pub fn shuffle_lines(&mut self, _: &ShuffleLines, cx: &mut ViewContext) {
+ self.manipulate_lines(cx, |lines| lines.shuffle(&mut thread_rng()))
+ }
+
+ fn manipulate_lines(&mut self, cx: &mut ViewContext, mut callback: Fn)
+ where
+ Fn: FnMut(&mut [&str]),
+ {
+ let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
+ let buffer = self.buffer.read(cx).snapshot(cx);
+
+ let mut edits = Vec::new();
+
+ let selections = self.selections.all::(cx);
+ let mut selections = selections.iter().peekable();
+ let mut contiguous_row_selections = Vec::new();
+ let mut new_selections = Vec::new();
+
+ while let Some(selection) = selections.next() {
+ let (start_row, end_row) = consume_contiguous_rows(
+ &mut contiguous_row_selections,
+ selection,
+ &display_map,
+ &mut selections,
+ );
+
+ let start_point = Point::new(start_row, 0);
+ let end_point = Point::new(end_row - 1, buffer.line_len(end_row - 1));
+ let text = buffer
+ .text_for_range(start_point..end_point)
+ .collect::();
+ let mut text = text.split("\n").collect_vec();
+
+ let text_len = text.len();
+ callback(&mut text);
+
+ // This is a current limitation with selections.
+ // If we wanted to support removing or adding lines, we'd need to fix the logic associated with selections.
+ debug_assert!(
+ text.len() == text_len,
+ "callback should not change the number of lines"
+ );
+
+ edits.push((start_point..end_point, text.join("\n")));
+ let start_anchor = buffer.anchor_after(start_point);
+ let end_anchor = buffer.anchor_before(end_point);
+
+ // Make selection and push
+ new_selections.push(Selection {
+ id: selection.id,
+ start: start_anchor.to_offset(&buffer),
+ end: end_anchor.to_offset(&buffer),
+ goal: SelectionGoal::None,
+ reversed: selection.reversed,
+ });
+ }
+
+ self.transact(cx, |this, cx| {
+ this.buffer.update(cx, |buffer, cx| {
+ buffer.edit(edits, None, cx);
+ });
+
+ this.change_selections(Some(Autoscroll::fit()), cx, |s| {
+ s.select(new_selections);
+ });
+
+ this.request_autoscroll(Autoscroll::fit(), cx);
+ });
+ }
+
pub fn duplicate_line(&mut self, _: &DuplicateLine, cx: &mut ViewContext) {
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
let buffer = &display_map.buffer_snapshot;
@@ -5278,7 +5397,7 @@ impl Editor {
pub fn select_all(&mut self, _: &SelectAll, cx: &mut ViewContext) {
let end = self.buffer.read(cx).read(cx).len();
- self.change_selections(Some(Autoscroll::fit()), cx, |s| {
+ self.change_selections(None, cx, |s| {
s.select_ranges(vec![0..end]);
});
}
@@ -6256,6 +6375,7 @@ impl Editor {
.to_offset(definition.target.buffer.read(cx));
if Some(&definition.target.buffer) == self.buffer.read(cx).as_singleton().as_ref() {
+ let range = self.range_for_match(&range);
self.change_selections(Some(Autoscroll::fit()), cx, |s| {
s.select_ranges([range]);
});
@@ -6272,6 +6392,7 @@ impl Editor {
// When selecting a definition in a different buffer, disable the nav history
// to avoid creating a history entry at the previous cursor location.
pane.update(cx, |pane, _| pane.disable_history());
+ let range = target_editor.range_for_match(&range);
target_editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
s.select_ranges([range]);
});
@@ -7064,6 +7185,20 @@ impl Editor {
.text()
}
+ pub fn wrap_guides(&self, cx: &AppContext) -> SmallVec<[(usize, bool); 2]> {
+ let mut wrap_guides = smallvec::smallvec![];
+
+ let settings = self.buffer.read(cx).settings_at(0, cx);
+ if settings.show_wrap_guides {
+ if let SoftWrap::Column(soft_wrap) = self.soft_wrap_mode(cx) {
+ wrap_guides.push((soft_wrap as usize, true));
+ }
+ wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
+ }
+
+ wrap_guides
+ }
+
pub fn soft_wrap_mode(&self, cx: &AppContext) -> SoftWrap {
let settings = self.buffer.read(cx).settings_at(0, cx);
let mode = self
diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs
index 247a7b021d..eb03d2bdc0 100644
--- a/crates/editor/src/editor_tests.rs
+++ b/crates/editor/src/editor_tests.rs
@@ -2500,6 +2500,156 @@ fn test_join_lines_with_multi_selection(cx: &mut TestAppContext) {
});
}
+#[gpui::test]
+async fn test_manipulate_lines_with_single_selection(cx: &mut TestAppContext) {
+ init_test(cx, |_| {});
+
+ let mut cx = EditorTestContext::new(cx).await;
+
+ // Test sort_lines_case_insensitive()
+ cx.set_state(indoc! {"
+ «z
+ y
+ x
+ Z
+ Y
+ Xˇ»
+ "});
+ cx.update_editor(|e, cx| e.sort_lines_case_insensitive(&SortLinesCaseInsensitive, cx));
+ cx.assert_editor_state(indoc! {"
+ «x
+ X
+ y
+ Y
+ z
+ Zˇ»
+ "});
+
+ // Test reverse_lines()
+ cx.set_state(indoc! {"
+ «5
+ 4
+ 3
+ 2
+ 1ˇ»
+ "});
+ cx.update_editor(|e, cx| e.reverse_lines(&ReverseLines, cx));
+ cx.assert_editor_state(indoc! {"
+ «1
+ 2
+ 3
+ 4
+ 5ˇ»
+ "});
+
+ // Skip testing shuffle_line()
+
+ // From here on out, test more complex cases of manipulate_lines() with a single driver method: sort_lines_case_sensitive()
+ // Since all methods calling manipulate_lines() are doing the exact same general thing (reordering lines)
+
+ // Don't manipulate when cursor is on single line, but expand the selection
+ cx.set_state(indoc! {"
+ ddˇdd
+ ccc
+ bb
+ a
+ "});
+ cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
+ cx.assert_editor_state(indoc! {"
+ «ddddˇ»
+ ccc
+ bb
+ a
+ "});
+
+ // Basic manipulate case
+ // Start selection moves to column 0
+ // End of selection shrinks to fit shorter line
+ cx.set_state(indoc! {"
+ dd«d
+ ccc
+ bb
+ aaaaaˇ»
+ "});
+ cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
+ cx.assert_editor_state(indoc! {"
+ «aaaaa
+ bb
+ ccc
+ dddˇ»
+ "});
+
+ // Manipulate case with newlines
+ cx.set_state(indoc! {"
+ dd«d
+ ccc
+
+ bb
+ aaaaa
+
+ ˇ»
+ "});
+ cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
+ cx.assert_editor_state(indoc! {"
+ «
+
+ aaaaa
+ bb
+ ccc
+ dddˇ»
+
+ "});
+}
+
+#[gpui::test]
+async fn test_manipulate_lines_with_multi_selection(cx: &mut TestAppContext) {
+ init_test(cx, |_| {});
+
+ let mut cx = EditorTestContext::new(cx).await;
+
+ // Manipulate with multiple selections on a single line
+ cx.set_state(indoc! {"
+ dd«dd
+ cˇ»c«c
+ bb
+ aaaˇ»aa
+ "});
+ cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
+ cx.assert_editor_state(indoc! {"
+ «aaaaa
+ bb
+ ccc
+ ddddˇ»
+ "});
+
+ // Manipulate with multiple disjoin selections
+ cx.set_state(indoc! {"
+ 5«
+ 4
+ 3
+ 2
+ 1ˇ»
+
+ dd«dd
+ ccc
+ bb
+ aaaˇ»aa
+ "});
+ cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
+ cx.assert_editor_state(indoc! {"
+ «1
+ 2
+ 3
+ 4
+ 5ˇ»
+
+ «aaaaa
+ bb
+ ccc
+ ddddˇ»
+ "});
+}
+
#[gpui::test]
fn test_duplicate_line(cx: &mut TestAppContext) {
init_test(cx, |_| {});
diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs
index 4f4aa7477d..b48fa5b56d 100644
--- a/crates/editor/src/element.rs
+++ b/crates/editor/src/element.rs
@@ -61,6 +61,7 @@ enum FoldMarkers {}
struct SelectionLayout {
head: DisplayPoint,
cursor_shape: CursorShape,
+ is_newest: bool,
range: Range,
}
@@ -70,6 +71,7 @@ impl SelectionLayout {
line_mode: bool,
cursor_shape: CursorShape,
map: &DisplaySnapshot,
+ is_newest: bool,
) -> Self {
if line_mode {
let selection = selection.map(|p| p.to_point(&map.buffer_snapshot));
@@ -77,6 +79,7 @@ impl SelectionLayout {
Self {
head: selection.head().to_display_point(map),
cursor_shape,
+ is_newest,
range: point_range.start.to_display_point(map)
..point_range.end.to_display_point(map),
}
@@ -85,6 +88,7 @@ impl SelectionLayout {
Self {
head: selection.head(),
cursor_shape,
+ is_newest,
range: selection.range(),
}
}
@@ -537,6 +541,24 @@ impl EditorElement {
corner_radius: 0.,
});
}
+
+ for (wrap_position, active) in layout.wrap_guides.iter() {
+ let x = text_bounds.origin_x() + wrap_position + layout.position_map.em_width / 2.;
+ let color = if *active {
+ self.style.active_wrap_guide
+ } else {
+ self.style.wrap_guide
+ };
+ scene.push_quad(Quad {
+ bounds: RectF::new(
+ vec2f(x, text_bounds.origin_y()),
+ vec2f(1., text_bounds.height()),
+ ),
+ background: Some(color),
+ border: Border::new(0., Color::transparent_black()),
+ corner_radius: 0.,
+ });
+ }
}
}
@@ -864,6 +886,12 @@ impl EditorElement {
let x = cursor_character_x - scroll_left;
let y = cursor_position.row() as f32 * layout.position_map.line_height
- scroll_top;
+ if selection.is_newest {
+ editor.pixel_position_of_newest_cursor = Some(vec2f(
+ bounds.origin_x() + x + block_width / 2.,
+ bounds.origin_y() + y + layout.position_map.line_height / 2.,
+ ));
+ }
cursors.push(Cursor {
color: selection_style.cursor,
block_width,
@@ -1310,16 +1338,15 @@ impl EditorElement {
}
}
- fn max_line_number_width(&self, snapshot: &EditorSnapshot, cx: &ViewContext) -> f32 {
- let digit_count = (snapshot.max_buffer_row() as f32).log10().floor() as usize + 1;
+ fn column_pixels(&self, column: usize, cx: &ViewContext) -> f32 {
let style = &self.style;
cx.text_layout_cache()
.layout_str(
- "1".repeat(digit_count).as_str(),
+ " ".repeat(column).as_str(),
style.text.font_size,
&[(
- digit_count,
+ column,
RunStyle {
font_id: style.text.font_id,
color: Color::black(),
@@ -1330,6 +1357,11 @@ impl EditorElement {
.width()
}
+ fn max_line_number_width(&self, snapshot: &EditorSnapshot, cx: &ViewContext) -> f32 {
+ let digit_count = (snapshot.max_buffer_row() as f32 + 1.).log10().floor() as usize + 1;
+ self.column_pixels(digit_count, cx)
+ }
+
//Folds contained in a hunk are ignored apart from shrinking visual size
//If a fold contains any hunks then that fold line is marked as modified
fn layout_git_gutters(
@@ -1977,6 +2009,7 @@ impl Element for EditorElement {
let snapshot = editor.snapshot(cx);
let style = self.style.clone();
+
let line_height = (style.text.font_size * style.line_height_scalar).round();
let gutter_padding;
@@ -2014,6 +2047,12 @@ impl Element for EditorElement {
}
};
+ let wrap_guides = editor
+ .wrap_guides(cx)
+ .iter()
+ .map(|(guide, active)| (self.column_pixels(*guide, cx), *active))
+ .collect();
+
let scroll_height = (snapshot.max_point().row() + 1) as f32 * line_height;
if let EditorMode::AutoHeight { max_lines } = snapshot.mode {
size.set_y(
@@ -2108,6 +2147,7 @@ impl Element for EditorElement {
line_mode,
cursor_shape,
&snapshot.display_snapshot,
+ false,
));
}
selections.extend(remote_selections);
@@ -2117,6 +2157,7 @@ impl Element for EditorElement {
.selections
.disjoint_in_range(start_anchor..end_anchor, cx);
local_selections.extend(editor.selections.pending(cx));
+ let newest = editor.selections.newest(cx);
for selection in &local_selections {
let is_empty = selection.start == selection.end;
let selection_start = snapshot.prev_line_boundary(selection.start).1;
@@ -2139,11 +2180,13 @@ impl Element for EditorElement {
local_selections
.into_iter()
.map(|selection| {
+ let is_newest = selection == newest;
SelectionLayout::new(
selection,
editor.selections.line_mode,
editor.cursor_shape,
&snapshot.display_snapshot,
+ is_newest,
)
})
.collect(),
@@ -2370,6 +2413,7 @@ impl Element for EditorElement {
snapshot,
}),
visible_display_row_range: start_row..end_row,
+ wrap_guides,
gutter_size,
gutter_padding,
text_size,
@@ -2520,6 +2564,7 @@ pub struct LayoutState {
gutter_margin: f32,
text_size: Vector2F,
mode: EditorMode,
+ wrap_guides: SmallVec<[(f32, bool); 2]>,
visible_display_row_range: Range,
active_rows: BTreeMap,
highlighted_rows: Option>,
diff --git a/crates/editor/src/hover_popover.rs b/crates/editor/src/hover_popover.rs
index 7a203d54a9..92ed9ef77d 100644
--- a/crates/editor/src/hover_popover.rs
+++ b/crates/editor/src/hover_popover.rs
@@ -198,7 +198,7 @@ fn show_hover(
// Construct new hover popover from hover request
let hover_popover = hover_request.await.ok().flatten().and_then(|hover_result| {
- if hover_result.contents.is_empty() {
+ if hover_result.is_empty() {
return None;
}
@@ -420,7 +420,7 @@ fn render_blocks(
RenderedInfo {
theme_id,
- text,
+ text: text.trim().to_string(),
highlights,
region_ranges,
regions,
@@ -816,6 +816,118 @@ mod tests {
});
}
+ #[gpui::test]
+ async fn test_empty_hovers_filtered(cx: &mut gpui::TestAppContext) {
+ init_test(cx, |_| {});
+
+ let mut cx = EditorLspTestContext::new_rust(
+ lsp::ServerCapabilities {
+ hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
+ ..Default::default()
+ },
+ cx,
+ )
+ .await;
+
+ // Hover with keyboard has no delay
+ cx.set_state(indoc! {"
+ fˇn test() { println!(); }
+ "});
+ cx.update_editor(|editor, cx| hover(editor, &Hover, cx));
+ let symbol_range = cx.lsp_range(indoc! {"
+ «fn» test() { println!(); }
+ "});
+ cx.handle_request::(move |_, _, _| async move {
+ Ok(Some(lsp::Hover {
+ contents: lsp::HoverContents::Array(vec![
+ lsp::MarkedString::String("regular text for hover to show".to_string()),
+ lsp::MarkedString::String("".to_string()),
+ lsp::MarkedString::LanguageString(lsp::LanguageString {
+ language: "Rust".to_string(),
+ value: "".to_string(),
+ }),
+ ]),
+ range: Some(symbol_range),
+ }))
+ })
+ .next()
+ .await;
+
+ cx.condition(|editor, _| editor.hover_state.visible()).await;
+ cx.editor(|editor, _| {
+ assert_eq!(
+ editor.hover_state.info_popover.clone().unwrap().blocks,
+ vec![HoverBlock {
+ text: "regular text for hover to show".to_string(),
+ kind: HoverBlockKind::Markdown,
+ }],
+ "No empty string hovers should be shown"
+ );
+ });
+ }
+
+ #[gpui::test]
+ async fn test_line_ends_trimmed(cx: &mut gpui::TestAppContext) {
+ init_test(cx, |_| {});
+
+ let mut cx = EditorLspTestContext::new_rust(
+ lsp::ServerCapabilities {
+ hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
+ ..Default::default()
+ },
+ cx,
+ )
+ .await;
+
+ // Hover with keyboard has no delay
+ cx.set_state(indoc! {"
+ fˇn test() { println!(); }
+ "});
+ cx.update_editor(|editor, cx| hover(editor, &Hover, cx));
+ let symbol_range = cx.lsp_range(indoc! {"
+ «fn» test() { println!(); }
+ "});
+
+ let code_str = "\nlet hovered_point: Vector2F // size = 8, align = 0x4\n";
+ let markdown_string = format!("\n```rust\n{code_str}```");
+
+ let closure_markdown_string = markdown_string.clone();
+ cx.handle_request::(move |_, _, _| {
+ let future_markdown_string = closure_markdown_string.clone();
+ async move {
+ Ok(Some(lsp::Hover {
+ contents: lsp::HoverContents::Markup(lsp::MarkupContent {
+ kind: lsp::MarkupKind::Markdown,
+ value: future_markdown_string,
+ }),
+ range: Some(symbol_range),
+ }))
+ }
+ })
+ .next()
+ .await;
+
+ cx.condition(|editor, _| editor.hover_state.visible()).await;
+ cx.editor(|editor, cx| {
+ let blocks = editor.hover_state.info_popover.clone().unwrap().blocks;
+ assert_eq!(
+ blocks,
+ vec![HoverBlock {
+ text: markdown_string,
+ kind: HoverBlockKind::Markdown,
+ }],
+ );
+
+ let style = editor.style(cx);
+ let rendered = render_blocks(0, &blocks, &Default::default(), None, &style);
+ assert_eq!(
+ rendered.text,
+ code_str.trim(),
+ "Should not have extra line breaks at end of rendered hover"
+ );
+ });
+ }
+
#[gpui::test]
async fn test_hover_diagnostic_and_info_popovers(cx: &mut gpui::TestAppContext) {
init_test(cx, |_| {});
diff --git a/crates/editor/src/inlay_hint_cache.rs b/crates/editor/src/inlay_hint_cache.rs
index 52473f9971..63076ba234 100644
--- a/crates/editor/src/inlay_hint_cache.rs
+++ b/crates/editor/src/inlay_hint_cache.rs
@@ -195,20 +195,41 @@ impl InlayHintCache {
}
}
- pub fn refresh_inlay_hints(
+ pub fn spawn_hint_refresh(
&mut self,
mut excerpts_to_query: HashMap, Global, Range)>,
invalidate: InvalidationStrategy,
cx: &mut ViewContext,
- ) {
- if !self.enabled || excerpts_to_query.is_empty() {
- return;
+ ) -> Option {
+ if !self.enabled {
+ return None;
}
+
let update_tasks = &mut self.update_tasks;
+ let mut invalidated_hints = Vec::new();
if invalidate.should_invalidate() {
- update_tasks
- .retain(|task_excerpt_id, _| excerpts_to_query.contains_key(task_excerpt_id));
+ let mut changed = false;
+ update_tasks.retain(|task_excerpt_id, _| {
+ let retain = excerpts_to_query.contains_key(task_excerpt_id);
+ changed |= !retain;
+ retain
+ });
+ self.hints.retain(|cached_excerpt, cached_hints| {
+ let retain = excerpts_to_query.contains_key(cached_excerpt);
+ changed |= !retain;
+ if !retain {
+ invalidated_hints.extend(cached_hints.read().hints.iter().map(|&(id, _)| id));
+ }
+ retain
+ });
+ if changed {
+ self.version += 1;
+ }
}
+ if excerpts_to_query.is_empty() && invalidated_hints.is_empty() {
+ return None;
+ }
+
let cache_version = self.version;
excerpts_to_query.retain(|visible_excerpt_id, _| {
match update_tasks.entry(*visible_excerpt_id) {
@@ -229,6 +250,15 @@ impl InlayHintCache {
.ok();
})
.detach();
+
+ if invalidated_hints.is_empty() {
+ None
+ } else {
+ Some(InlaySplice {
+ to_remove: invalidated_hints,
+ to_insert: Vec::new(),
+ })
+ }
}
fn new_allowed_hint_kinds_splice(
@@ -684,7 +714,7 @@ async fn fetch_and_update_hints(
if query.invalidate.should_invalidate() {
let mut outdated_excerpt_caches = HashSet::default();
- for (excerpt_id, excerpt_hints) in editor.inlay_hint_cache().hints.iter() {
+ for (excerpt_id, excerpt_hints) in &editor.inlay_hint_cache().hints {
let excerpt_hints = excerpt_hints.read();
if excerpt_hints.buffer_id == query.buffer_id
&& excerpt_id != &query.excerpt_id
@@ -1022,9 +1052,9 @@ mod tests {
"Should get its first hints when opening the editor"
);
assert_eq!(expected_layers, visible_hint_labels(editor, cx));
- let inlay_cache = editor.inlay_hint_cache();
assert_eq!(
- inlay_cache.version, edits_made,
+ editor.inlay_hint_cache().version,
+ edits_made,
"The editor update the cache version after every cache/view change"
);
});
@@ -1053,9 +1083,9 @@ mod tests {
"Should not update hints while the work task is running"
);
assert_eq!(expected_layers, visible_hint_labels(editor, cx));
- let inlay_cache = editor.inlay_hint_cache();
assert_eq!(
- inlay_cache.version, edits_made,
+ editor.inlay_hint_cache().version,
+ edits_made,
"Should not update the cache while the work task is running"
);
});
@@ -1077,9 +1107,9 @@ mod tests {
"New hints should be queried after the work task is done"
);
assert_eq!(expected_layers, visible_hint_labels(editor, cx));
- let inlay_cache = editor.inlay_hint_cache();
assert_eq!(
- inlay_cache.version, edits_made,
+ editor.inlay_hint_cache().version,
+ edits_made,
"Cache version should udpate once after the work task is done"
);
});
@@ -1194,9 +1224,9 @@ mod tests {
"Should get its first hints when opening the editor"
);
assert_eq!(expected_layers, visible_hint_labels(editor, cx));
- let inlay_cache = editor.inlay_hint_cache();
assert_eq!(
- inlay_cache.version, 1,
+ editor.inlay_hint_cache().version,
+ 1,
"Rust editor update the cache version after every cache/view change"
);
});
@@ -1252,8 +1282,7 @@ mod tests {
"Markdown editor should have a separate verison, repeating Rust editor rules"
);
assert_eq!(expected_layers, visible_hint_labels(editor, cx));
- let inlay_cache = editor.inlay_hint_cache();
- assert_eq!(inlay_cache.version, 1);
+ assert_eq!(editor.inlay_hint_cache().version, 1);
});
rs_editor.update(cx, |editor, cx| {
@@ -1269,9 +1298,9 @@ mod tests {
"Rust inlay cache should change after the edit"
);
assert_eq!(expected_layers, visible_hint_labels(editor, cx));
- let inlay_cache = editor.inlay_hint_cache();
assert_eq!(
- inlay_cache.version, 2,
+ editor.inlay_hint_cache().version,
+ 2,
"Every time hint cache changes, cache version should be incremented"
);
});
@@ -1283,8 +1312,7 @@ mod tests {
"Markdown editor should not be affected by Rust editor changes"
);
assert_eq!(expected_layers, visible_hint_labels(editor, cx));
- let inlay_cache = editor.inlay_hint_cache();
- assert_eq!(inlay_cache.version, 1);
+ assert_eq!(editor.inlay_hint_cache().version, 1);
});
md_editor.update(cx, |editor, cx| {
@@ -1300,8 +1328,7 @@ mod tests {
"Rust editor should not be affected by Markdown editor changes"
);
assert_eq!(expected_layers, visible_hint_labels(editor, cx));
- let inlay_cache = editor.inlay_hint_cache();
- assert_eq!(inlay_cache.version, 2);
+ assert_eq!(editor.inlay_hint_cache().version, 2);
});
rs_editor.update(cx, |editor, cx| {
let expected_layers = vec!["1".to_string()];
@@ -1311,8 +1338,7 @@ mod tests {
"Markdown editor should also change independently"
);
assert_eq!(expected_layers, visible_hint_labels(editor, cx));
- let inlay_cache = editor.inlay_hint_cache();
- assert_eq!(inlay_cache.version, 2);
+ assert_eq!(editor.inlay_hint_cache().version, 2);
});
}
@@ -1433,9 +1459,9 @@ mod tests {
vec!["other hint".to_string(), "type hint".to_string()],
visible_hint_labels(editor, cx)
);
- let inlay_cache = editor.inlay_hint_cache();
assert_eq!(
- inlay_cache.version, edits_made,
+ editor.inlay_hint_cache().version,
+ edits_made,
"Should not update cache version due to new loaded hints being the same"
);
});
@@ -1568,9 +1594,8 @@ mod tests {
);
assert!(cached_hint_labels(editor).is_empty());
assert!(visible_hint_labels(editor, cx).is_empty());
- let inlay_cache = editor.inlay_hint_cache();
assert_eq!(
- inlay_cache.version, edits_made,
+ editor.inlay_hint_cache().version, edits_made,
"The editor should not update the cache version after /refresh query without updates"
);
});
@@ -1641,8 +1666,7 @@ mod tests {
vec!["parameter hint".to_string()],
visible_hint_labels(editor, cx),
);
- let inlay_cache = editor.inlay_hint_cache();
- assert_eq!(inlay_cache.version, edits_made);
+ assert_eq!(editor.inlay_hint_cache().version, edits_made);
});
}
@@ -1720,9 +1744,8 @@ mod tests {
"Should get hints from the last edit landed only"
);
assert_eq!(expected_hints, visible_hint_labels(editor, cx));
- let inlay_cache = editor.inlay_hint_cache();
assert_eq!(
- inlay_cache.version, 1,
+ editor.inlay_hint_cache().version, 1,
"Only one update should be registered in the cache after all cancellations"
);
});
@@ -1766,9 +1789,9 @@ mod tests {
"Should get hints from the last edit landed only"
);
assert_eq!(expected_hints, visible_hint_labels(editor, cx));
- let inlay_cache = editor.inlay_hint_cache();
assert_eq!(
- inlay_cache.version, 2,
+ editor.inlay_hint_cache().version,
+ 2,
"Should update the cache version once more, for the new change"
);
});
@@ -1886,9 +1909,8 @@ mod tests {
"Should have hints from both LSP requests made for a big file"
);
assert_eq!(expected_layers, visible_hint_labels(editor, cx));
- let inlay_cache = editor.inlay_hint_cache();
assert_eq!(
- inlay_cache.version, 2,
+ editor.inlay_hint_cache().version, 2,
"Both LSP queries should've bumped the cache version"
);
});
@@ -1918,8 +1940,7 @@ mod tests {
assert_eq!(expected_layers, cached_hint_labels(editor),
"Should have hints from the new LSP response after edit");
assert_eq!(expected_layers, visible_hint_labels(editor, cx));
- let inlay_cache = editor.inlay_hint_cache();
- assert_eq!(inlay_cache.version, 5, "Should update the cache for every LSP response with hints added");
+ assert_eq!(editor.inlay_hint_cache().version, 5, "Should update the cache for every LSP response with hints added");
});
}
@@ -2075,6 +2096,7 @@ mod tests {
panic!("unexpected uri: {:?}", params.text_document.uri);
};
+ // one hint per excerpt
let positions = [
lsp::Position::new(0, 2),
lsp::Position::new(4, 2),
@@ -2138,8 +2160,7 @@ mod tests {
"When scroll is at the edge of a multibuffer, its visible excerpts only should be queried for inlay hints"
);
assert_eq!(expected_layers, visible_hint_labels(editor, cx));
- let inlay_cache = editor.inlay_hint_cache();
- assert_eq!(inlay_cache.version, 4, "Every visible excerpt hints should bump the verison");
+ assert_eq!(editor.inlay_hint_cache().version, expected_layers.len(), "Every visible excerpt hints should bump the verison");
});
editor.update(cx, |editor, cx| {
@@ -2169,8 +2190,8 @@ mod tests {
assert_eq!(expected_layers, cached_hint_labels(editor),
"With more scrolls of the multibuffer, more hints should be added into the cache and nothing invalidated without edits");
assert_eq!(expected_layers, visible_hint_labels(editor, cx));
- let inlay_cache = editor.inlay_hint_cache();
- assert_eq!(inlay_cache.version, 9);
+ assert_eq!(editor.inlay_hint_cache().version, expected_layers.len(),
+ "Due to every excerpt having one hint, we update cache per new excerpt scrolled");
});
editor.update(cx, |editor, cx| {
@@ -2179,7 +2200,7 @@ mod tests {
});
});
cx.foreground().run_until_parked();
- editor.update(cx, |editor, cx| {
+ let last_scroll_update_version = editor.update(cx, |editor, cx| {
let expected_layers = vec![
"main hint #0".to_string(),
"main hint #1".to_string(),
@@ -2197,8 +2218,8 @@ mod tests {
assert_eq!(expected_layers, cached_hint_labels(editor),
"After multibuffer was scrolled to the end, all hints for all excerpts should be fetched");
assert_eq!(expected_layers, visible_hint_labels(editor, cx));
- let inlay_cache = editor.inlay_hint_cache();
- assert_eq!(inlay_cache.version, 12);
+ assert_eq!(editor.inlay_hint_cache().version, expected_layers.len());
+ expected_layers.len()
});
editor.update(cx, |editor, cx| {
@@ -2225,12 +2246,14 @@ mod tests {
assert_eq!(expected_layers, cached_hint_labels(editor),
"After multibuffer was scrolled to the end, further scrolls up should not bring more hints");
assert_eq!(expected_layers, visible_hint_labels(editor, cx));
- let inlay_cache = editor.inlay_hint_cache();
- assert_eq!(inlay_cache.version, 12, "No updates should happen during scrolling already scolled buffer");
+ assert_eq!(editor.inlay_hint_cache().version, last_scroll_update_version, "No updates should happen during scrolling already scolled buffer");
});
editor_edited.store(true, Ordering::Release);
editor.update(cx, |editor, cx| {
+ editor.change_selections(None, cx, |s| {
+ s.select_ranges([Point::new(56, 0)..Point::new(56, 0)])
+ });
editor.handle_input("++++more text++++", cx);
});
cx.foreground().run_until_parked();
@@ -2240,19 +2263,253 @@ mod tests {
"main hint(edited) #1".to_string(),
"main hint(edited) #2".to_string(),
"main hint(edited) #3".to_string(),
- "other hint #0".to_string(),
- "other hint #1".to_string(),
- "other hint #2".to_string(),
- "other hint #3".to_string(),
- "other hint #4".to_string(),
- "other hint #5".to_string(),
+ "main hint(edited) #4".to_string(),
+ "main hint(edited) #5".to_string(),
+ "other hint(edited) #0".to_string(),
+ "other hint(edited) #1".to_string(),
];
- assert_eq!(expected_layers, cached_hint_labels(editor),
- "After multibuffer was edited, hints for the edited buffer (1st) should be invalidated and requeried for all of its visible excerpts, \
-unedited (2nd) buffer should have the same hint");
+ assert_eq!(
+ expected_layers,
+ cached_hint_labels(editor),
+ "After multibuffer edit, editor gets scolled back to the last selection; \
+all hints should be invalidated and requeried for all of its visible excerpts"
+ );
assert_eq!(expected_layers, visible_hint_labels(editor, cx));
- let inlay_cache = editor.inlay_hint_cache();
- assert_eq!(inlay_cache.version, 16);
+ assert_eq!(
+ editor.inlay_hint_cache().version,
+ last_scroll_update_version + expected_layers.len() + 1,
+ "Due to every excerpt having one hint, cache should update per new excerpt received + 1 for outdated hints removal"
+ );
+ });
+ }
+
+ #[gpui::test]
+ async fn test_excerpts_removed(
+ deterministic: Arc,
+ cx: &mut gpui::TestAppContext,
+ ) {
+ init_test(cx, |settings| {
+ settings.defaults.inlay_hints = Some(InlayHintSettings {
+ enabled: true,
+ show_type_hints: false,
+ show_parameter_hints: false,
+ show_other_hints: false,
+ })
+ });
+
+ let mut language = Language::new(
+ LanguageConfig {
+ name: "Rust".into(),
+ path_suffixes: vec!["rs".to_string()],
+ ..Default::default()
+ },
+ Some(tree_sitter_rust::language()),
+ );
+ let mut fake_servers = language
+ .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
+ capabilities: lsp::ServerCapabilities {
+ inlay_hint_provider: Some(lsp::OneOf::Left(true)),
+ ..Default::default()
+ },
+ ..Default::default()
+ }))
+ .await;
+ let language = Arc::new(language);
+ let fs = FakeFs::new(cx.background());
+ fs.insert_tree(
+ "/a",
+ json!({
+ "main.rs": format!("fn main() {{\n{}\n}}", (0..501).map(|i| format!("let i = {i};\n")).collect::>().join("")),
+ "other.rs": format!("fn main() {{\n{}\n}}", (0..501).map(|j| format!("let j = {j};\n")).collect::>().join("")),
+ }),
+ )
+ .await;
+ let project = Project::test(fs, ["/a".as_ref()], cx).await;
+ project.update(cx, |project, _| {
+ project.languages().add(Arc::clone(&language))
+ });
+ let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+ let worktree_id = workspace.update(cx, |workspace, cx| {
+ workspace.project().read_with(cx, |project, cx| {
+ project.worktrees(cx).next().unwrap().read(cx).id()
+ })
+ });
+
+ let buffer_1 = project
+ .update(cx, |project, cx| {
+ project.open_buffer((worktree_id, "main.rs"), cx)
+ })
+ .await
+ .unwrap();
+ let buffer_2 = project
+ .update(cx, |project, cx| {
+ project.open_buffer((worktree_id, "other.rs"), cx)
+ })
+ .await
+ .unwrap();
+ let multibuffer = cx.add_model(|_| MultiBuffer::new(0));
+ let (buffer_1_excerpts, buffer_2_excerpts) = multibuffer.update(cx, |multibuffer, cx| {
+ let buffer_1_excerpts = multibuffer.push_excerpts(
+ buffer_1.clone(),
+ [ExcerptRange {
+ context: Point::new(0, 0)..Point::new(2, 0),
+ primary: None,
+ }],
+ cx,
+ );
+ let buffer_2_excerpts = multibuffer.push_excerpts(
+ buffer_2.clone(),
+ [ExcerptRange {
+ context: Point::new(0, 1)..Point::new(2, 1),
+ primary: None,
+ }],
+ cx,
+ );
+ (buffer_1_excerpts, buffer_2_excerpts)
+ });
+
+ assert!(!buffer_1_excerpts.is_empty());
+ assert!(!buffer_2_excerpts.is_empty());
+
+ deterministic.run_until_parked();
+ cx.foreground().run_until_parked();
+ let (_, editor) =
+ cx.add_window(|cx| Editor::for_multibuffer(multibuffer, Some(project.clone()), cx));
+ let editor_edited = Arc::new(AtomicBool::new(false));
+ let fake_server = fake_servers.next().await.unwrap();
+ let closure_editor_edited = Arc::clone(&editor_edited);
+ fake_server
+ .handle_request::(move |params, _| {
+ let task_editor_edited = Arc::clone(&closure_editor_edited);
+ async move {
+ let hint_text = if params.text_document.uri
+ == lsp::Url::from_file_path("/a/main.rs").unwrap()
+ {
+ "main hint"
+ } else if params.text_document.uri
+ == lsp::Url::from_file_path("/a/other.rs").unwrap()
+ {
+ "other hint"
+ } else {
+ panic!("unexpected uri: {:?}", params.text_document.uri);
+ };
+
+ let positions = [
+ lsp::Position::new(0, 2),
+ lsp::Position::new(4, 2),
+ lsp::Position::new(22, 2),
+ lsp::Position::new(44, 2),
+ lsp::Position::new(56, 2),
+ lsp::Position::new(67, 2),
+ ];
+ let out_of_range_hint = lsp::InlayHint {
+ position: lsp::Position::new(
+ params.range.start.line + 99,
+ params.range.start.character + 99,
+ ),
+ label: lsp::InlayHintLabel::String(
+ "out of excerpt range, should be ignored".to_string(),
+ ),
+ kind: None,
+ text_edits: None,
+ tooltip: None,
+ padding_left: None,
+ padding_right: None,
+ data: None,
+ };
+
+ let edited = task_editor_edited.load(Ordering::Acquire);
+ Ok(Some(
+ std::iter::once(out_of_range_hint)
+ .chain(positions.into_iter().enumerate().map(|(i, position)| {
+ lsp::InlayHint {
+ position,
+ label: lsp::InlayHintLabel::String(format!(
+ "{hint_text}{} #{i}",
+ if edited { "(edited)" } else { "" },
+ )),
+ kind: None,
+ text_edits: None,
+ tooltip: None,
+ padding_left: None,
+ padding_right: None,
+ data: None,
+ }
+ }))
+ .collect(),
+ ))
+ }
+ })
+ .next()
+ .await;
+ cx.foreground().run_until_parked();
+
+ editor.update(cx, |editor, cx| {
+ assert_eq!(
+ vec!["main hint #0".to_string(), "other hint #0".to_string()],
+ cached_hint_labels(editor),
+ "Cache should update for both excerpts despite hints display was disabled"
+ );
+ assert!(
+ visible_hint_labels(editor, cx).is_empty(),
+ "All hints are disabled and should not be shown despite being present in the cache"
+ );
+ assert_eq!(
+ editor.inlay_hint_cache().version,
+ 2,
+ "Cache should update once per excerpt query"
+ );
+ });
+
+ editor.update(cx, |editor, cx| {
+ editor.buffer().update(cx, |multibuffer, cx| {
+ multibuffer.remove_excerpts(buffer_2_excerpts, cx)
+ })
+ });
+ cx.foreground().run_until_parked();
+ editor.update(cx, |editor, cx| {
+ assert_eq!(
+ vec!["main hint #0".to_string()],
+ cached_hint_labels(editor),
+ "For the removed excerpt, should clean corresponding cached hints"
+ );
+ assert!(
+ visible_hint_labels(editor, cx).is_empty(),
+ "All hints are disabled and should not be shown despite being present in the cache"
+ );
+ assert_eq!(
+ editor.inlay_hint_cache().version,
+ 3,
+ "Excerpt removal should trigger cache update"
+ );
+ });
+
+ update_test_language_settings(cx, |settings| {
+ settings.defaults.inlay_hints = Some(InlayHintSettings {
+ enabled: true,
+ show_type_hints: true,
+ show_parameter_hints: true,
+ show_other_hints: true,
+ })
+ });
+ cx.foreground().run_until_parked();
+ editor.update(cx, |editor, cx| {
+ let expected_hints = vec!["main hint #0".to_string()];
+ assert_eq!(
+ expected_hints,
+ cached_hint_labels(editor),
+ "Hint display settings change should not change the cache"
+ );
+ assert_eq!(
+ expected_hints,
+ visible_hint_labels(editor, cx),
+ "Settings change should make cached hints visible"
+ );
+ assert_eq!(
+ editor.inlay_hint_cache().version,
+ 4,
+ "Settings change should trigger cache update"
+ );
});
}
diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs
index 9498be1844..7c8fe12aa0 100644
--- a/crates/editor/src/items.rs
+++ b/crates/editor/src/items.rs
@@ -7,8 +7,10 @@ use anyhow::{Context, Result};
use collections::HashSet;
use futures::future::try_join_all;
use gpui::{
- elements::*, geometry::vector::vec2f, AppContext, AsyncAppContext, Entity, ModelHandle,
- Subscription, Task, View, ViewContext, ViewHandle, WeakViewHandle,
+ elements::*,
+ geometry::vector::{vec2f, Vector2F},
+ AppContext, AsyncAppContext, Entity, ModelHandle, Subscription, Task, View, ViewContext,
+ ViewHandle, WeakViewHandle,
};
use language::{
proto::serialize_anchor as serialize_text_anchor, Bias, Buffer, OffsetRangeExt, Point,
@@ -750,6 +752,10 @@ impl Item for Editor {
Some(Box::new(handle.clone()))
}
+ fn pixel_position_of_cursor(&self) -> Option {
+ self.pixel_position_of_newest_cursor
+ }
+
fn breadcrumb_location(&self) -> ToolbarItemLocation {
ToolbarItemLocation::PrimaryLeft { flex: None }
}
@@ -946,21 +952,27 @@ impl SearchableItem for Editor {
cx: &mut ViewContext,
) {
self.unfold_ranges([matches[index].clone()], false, true, cx);
+ let range = self.range_for_match(&matches[index]);
self.change_selections(Some(Autoscroll::fit()), cx, |s| {
- s.select_ranges([matches[index].clone()])
- });
+ s.select_ranges([range]);
+ })
}
fn select_matches(&mut self, matches: Vec, cx: &mut ViewContext) {
self.unfold_ranges(matches.clone(), false, false, cx);
- self.change_selections(None, cx, |s| s.select_ranges(matches));
+ let mut ranges = Vec::new();
+ for m in &matches {
+ ranges.push(self.range_for_match(&m))
+ }
+ self.change_selections(None, cx, |s| s.select_ranges(ranges));
}
fn match_index_for_direction(
&mut self,
matches: &Vec>,
- mut current_index: usize,
+ current_index: usize,
direction: Direction,
+ count: usize,
cx: &mut ViewContext,
) -> usize {
let buffer = self.buffer().read(cx).snapshot(cx);
@@ -969,40 +981,39 @@ impl SearchableItem for Editor {
} else {
matches[current_index].start
};
- if matches[current_index]
- .start
- .cmp(¤t_index_position, &buffer)
- .is_gt()
- {
- if direction == Direction::Prev {
- if current_index == 0 {
- current_index = matches.len() - 1;
+
+ let mut count = count % matches.len();
+ if count == 0 {
+ return current_index;
+ }
+ match direction {
+ Direction::Next => {
+ if matches[current_index]
+ .start
+ .cmp(¤t_index_position, &buffer)
+ .is_gt()
+ {
+ count = count - 1
+ }
+
+ (current_index + count) % matches.len()
+ }
+ Direction::Prev => {
+ if matches[current_index]
+ .end
+ .cmp(¤t_index_position, &buffer)
+ .is_lt()
+ {
+ count = count - 1;
+ }
+
+ if current_index >= count {
+ current_index - count
} else {
- current_index -= 1;
+ matches.len() - (count - current_index)
}
}
- } else if matches[current_index]
- .end
- .cmp(¤t_index_position, &buffer)
- .is_lt()
- {
- if direction == Direction::Next {
- current_index = 0;
- }
- } else if direction == Direction::Prev {
- if current_index == 0 {
- current_index = matches.len() - 1;
- } else {
- current_index -= 1;
- }
- } else if direction == Direction::Next {
- if current_index == matches.len() - 1 {
- current_index = 0
- } else {
- current_index += 1;
- }
- };
- current_index
+ }
}
fn find_matches(
diff --git a/crates/editor/src/selections_collection.rs b/crates/editor/src/selections_collection.rs
index a22506f751..1921bc0738 100644
--- a/crates/editor/src/selections_collection.rs
+++ b/crates/editor/src/selections_collection.rs
@@ -138,7 +138,7 @@ impl SelectionsCollection {
.collect()
}
- // Returns all of the selections, adjusted to take into account the selection line_mode
+ /// Returns all of the selections, adjusted to take into account the selection line_mode
pub fn all_adjusted(&self, cx: &mut AppContext) -> Vec> {
let mut selections = self.all::(cx);
if self.line_mode {
diff --git a/crates/fs/src/repository.rs b/crates/fs/src/repository.rs
index 3826dae2aa..2b2aebe679 100644
--- a/crates/fs/src/repository.rs
+++ b/crates/fs/src/repository.rs
@@ -1,6 +1,6 @@
use anyhow::Result;
use collections::HashMap;
-use git2::{BranchType, ErrorCode};
+use git2::{BranchType, StatusShow};
use parking_lot::Mutex;
use rpc::proto;
use serde_derive::{Deserialize, Serialize};
@@ -10,6 +10,7 @@ use std::{
os::unix::prelude::OsStrExt,
path::{Component, Path, PathBuf},
sync::Arc,
+ time::SystemTime,
};
use sum_tree::{MapSeekTarget, TreeMap};
use util::ResultExt;
@@ -25,24 +26,30 @@ pub struct Branch {
#[async_trait::async_trait]
pub trait GitRepository: Send {
fn reload_index(&self);
-
fn load_index_text(&self, relative_file_path: &Path) -> Option;
-
fn branch_name(&self) -> Option;
- fn statuses(&self) -> Option>;
+ /// Get the statuses of all of the files in the index that start with the given
+ /// path and have changes with resepect to the HEAD commit. This is fast because
+ /// the index stores hashes of trees, so that unchanged directories can be skipped.
+ fn staged_statuses(&self, path_prefix: &Path) -> TreeMap;
- fn status(&self, path: &RepoPath) -> Result