Merge branch 'main' into v0.173.x

Joseph T. Lyons created

Change summary

Cargo.lock                                              | 212 +++++-----
Cargo.toml                                              |  12 
crates/editor/Cargo.toml                                |   1 
crates/editor/src/editor.rs                             |  36 +
crates/editor/src/editor_tests.rs                       |   2 
crates/editor/src/element.rs                            | 198 ++++------
crates/extension_host/src/wasm_host/wit/since_v0_0_1.rs |   2 
crates/extension_host/src/wasm_host/wit/since_v0_0_4.rs |   2 
crates/extension_host/src/wasm_host/wit/since_v0_0_6.rs |   2 
crates/extension_host/src/wasm_host/wit/since_v0_1_0.rs |   6 
crates/extension_host/src/wasm_host/wit/since_v0_2_0.rs |   8 
crates/gpui/Cargo.toml                                  |   2 
crates/language/Cargo.toml                              |   1 
crates/language/src/outline.rs                          |  38 +
crates/language/src/syntax_map.rs                       |   9 
crates/migrator/Cargo.toml                              |   2 
crates/migrator/src/migrator.rs                         |  21 
crates/semantic_index/Cargo.toml                        |   1 
crates/semantic_index/src/chunking.rs                   |   3 
crates/settings/Cargo.toml                              |   2 
crates/settings/src/keymap_file.rs                      |  58 ++
crates/settings/src/settings.rs                         |   4 
crates/settings/src/settings_store.rs                   |   5 
crates/ui/src/components/context_menu.rs                |  12 
24 files changed, 363 insertions(+), 276 deletions(-)

Detailed changes

Cargo.lock πŸ”—

@@ -2368,9 +2368,9 @@ dependencies = [
 
 [[package]]
 name = "cc"
-version = "1.2.3"
+version = "1.2.12"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "27f657647bcff5394bf56c7317665bbf790a137a50eaaa5c6bfbb9e27a518f2d"
+checksum = "755717a7de9ec452bf7f3f1a3099085deabd7f2962b861dae91ecd7a365903d2"
 dependencies = [
  "jobserver",
  "libc",
@@ -3308,18 +3308,18 @@ dependencies = [
 
 [[package]]
 name = "cranelift-bforest"
-version = "0.111.2"
+version = "0.112.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f823c6662ea77699089ec8b6b4b8a23c1e1a9c6526a6420ede7ac957274a7ab4"
+checksum = "69792bd40d21be8059f7c709f44200ded3bbd073df7eb3fa3c282b387c7ffa5b"
 dependencies = [
  "cranelift-entity",
 ]
 
 [[package]]
 name = "cranelift-bitset"
-version = "0.111.2"
+version = "0.112.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2fcbb4187005097204458a8e4309bb9e737933477e47b4609f81b07a5b4cdd25"
+checksum = "38da1eb6f7d8cdfa92f05acfae63c9a1d7a337e49ce7a2d0769c7fa03a2613a5"
 dependencies = [
  "serde",
  "serde_derive",
@@ -3327,9 +3327,9 @@ dependencies = [
 
 [[package]]
 name = "cranelift-codegen"
-version = "0.111.2"
+version = "0.112.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8cd1aaf8e88339f4f95afffd60d22033546ec7da4d79e805b85260a16668f78f"
+checksum = "709f5567a2bff9f06edf911a7cb5ebb091e4c81701714dc6ab574d08b4a69a0d"
 dependencies = [
  "bumpalo",
  "cranelift-bforest",
@@ -3343,40 +3343,40 @@ dependencies = [
  "hashbrown 0.14.5",
  "log",
  "regalloc2",
- "rustc-hash 1.1.0",
+ "rustc-hash 2.1.1",
  "smallvec",
  "target-lexicon",
 ]
 
 [[package]]
 name = "cranelift-codegen-meta"
-version = "0.111.2"
+version = "0.112.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8e541b0418bbba3ce82040a445bd9a83bf3e0da604a95178d9e949dc8a7840af"
+checksum = "72d39a6b194c069fd091ca1f17b9d86ff1a4627ccad8806095828f61989a691f"
 dependencies = [
  "cranelift-codegen-shared",
 ]
 
 [[package]]
 name = "cranelift-codegen-shared"
-version = "0.111.2"
+version = "0.112.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "91fc96a709a30be39d53ecf89dbfe4edcc5adba528d4b65f7e58dc867ba70fab"
+checksum = "18f81aefad1f80ed4132ae33f40b92779eeb57edeb1e28bb24424a4098c963a2"
 
 [[package]]
 name = "cranelift-control"
-version = "0.111.2"
+version = "0.112.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4c3bfcb035e0a501323896bb7ea3d7a5dd1fac3e92dda458ccd23960fde12c88"
+checksum = "6adbaac785ad4683c4f199686f9e15c1471f52ae2f4c013a3be039b4719db754"
 dependencies = [
  "arbitrary",
 ]
 
 [[package]]
 name = "cranelift-entity"
-version = "0.111.2"
+version = "0.112.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b2f00b4eba51d73a8c343c45cfdeeffa1f74f423bba0e6b8e290e646777c2b81"
+checksum = "70b85ed43567e13782cd1b25baf42a8167ee57169a60dfd3d7307c6ca3839da0"
 dependencies = [
  "cranelift-bitset",
  "serde",
@@ -3385,9 +3385,9 @@ dependencies = [
 
 [[package]]
 name = "cranelift-frontend"
-version = "0.111.2"
+version = "0.112.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "52d5e18bf04660bb716dacf45809e2d4c85e7111701e27dbdb75b4634504ad8f"
+checksum = "8349f71373bb69c6f73992c6c1606236a66c8134e7a60e04e03fbd64b1aa7dcf"
 dependencies = [
  "cranelift-codegen",
  "log",
@@ -3397,15 +3397,15 @@ dependencies = [
 
 [[package]]
 name = "cranelift-isle"
-version = "0.111.2"
+version = "0.112.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "31f9901807b6d0fde1205f0e4db9d96dcf7ddfc1894c69eb2ff93c47ebf2439f"
+checksum = "464a6b958ce05e0c237c8b25508012b6c644e8c37348213a8c786ba29e28cfdb"
 
 [[package]]
 name = "cranelift-native"
-version = "0.111.2"
+version = "0.112.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "967d65a4077726a9afc3f4694e037f34b992cbe2b6c48ce519b714a0b0558f97"
+checksum = "ffc4acaf6894ee323ff4e9ce786bec09f0ebbe49941e8012f1c1052f1d965034"
 dependencies = [
  "cranelift-codegen",
  "libc",
@@ -3414,9 +3414,9 @@ dependencies = [
 
 [[package]]
 name = "cranelift-wasm"
-version = "0.111.2"
+version = "0.112.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4899fd1ef6b1fe1df30f26ef864bd6e45040b8cf9f3cb3905d3e973c25698579"
+checksum = "b878860895cca97454ef8d8b12bfda9d0889dd49efee175dba78d54ff8363ec2"
 dependencies = [
  "cranelift-codegen",
  "cranelift-entity",
@@ -3424,7 +3424,7 @@ dependencies = [
  "itertools 0.12.1",
  "log",
  "smallvec",
- "wasmparser 0.215.0",
+ "wasmparser 0.217.1",
  "wasmtime-types",
 ]
 
@@ -4060,6 +4060,7 @@ dependencies = [
  "http_client",
  "indoc",
  "inline_completion",
+ "inventory",
  "itertools 0.14.0",
  "language",
  "linkify",
@@ -4447,8 +4448,8 @@ dependencies = [
  "serde_json",
  "toml 0.8.20",
  "util",
- "wasm-encoder 0.215.0",
- "wasmparser 0.215.0",
+ "wasm-encoder 0.217.1",
+ "wasmparser 0.217.1",
  "wit-component",
 ]
 
@@ -4517,7 +4518,7 @@ dependencies = [
  "toml 0.8.20",
  "url",
  "util",
- "wasmparser 0.215.0",
+ "wasmparser 0.217.1",
  "wasmtime",
  "wasmtime-wasi",
 ]
@@ -5699,15 +5700,6 @@ dependencies = [
  "ahash 0.7.8",
 ]
 
-[[package]]
-name = "hashbrown"
-version = "0.13.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e"
-dependencies = [
- "ahash 0.8.11",
-]
-
 [[package]]
 name = "hashbrown"
 version = "0.14.5"
@@ -6912,6 +6904,7 @@ dependencies = [
  "similar",
  "smallvec",
  "smol",
+ "streaming-iterator",
  "strsim",
  "sum_tree",
  "task",
@@ -7886,7 +7879,9 @@ version = "0.1.0"
 dependencies = [
  "collections",
  "convert_case 0.7.1",
+ "log",
  "pretty_assertions",
+ "streaming-iterator",
  "tree-sitter",
  "tree-sitter-json",
 ]
@@ -10781,13 +10776,13 @@ dependencies = [
 
 [[package]]
 name = "regalloc2"
-version = "0.9.3"
+version = "0.10.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ad156d539c879b7a24a363a2016d77961786e71f48f2e2fc8302a92abd2429a6"
+checksum = "12908dbeb234370af84d0579b9f68258a0f67e201412dd9a2814e6f45b2fc0f0"
 dependencies = [
- "hashbrown 0.13.2",
+ "hashbrown 0.14.5",
  "log",
- "rustc-hash 1.1.0",
+ "rustc-hash 2.1.1",
  "slice-group-by",
  "smallvec",
 ]
@@ -11858,6 +11853,7 @@ dependencies = [
  "settings",
  "sha2",
  "smol",
+ "streaming-iterator",
  "tempfile",
  "theme",
  "tree-sitter",
@@ -12037,6 +12033,7 @@ dependencies = [
  "futures 0.3.31",
  "gpui",
  "indoc",
+ "inventory",
  "log",
  "migrator",
  "paths",
@@ -12049,6 +12046,7 @@ dependencies = [
  "serde_json",
  "serde_json_lenient",
  "smallvec",
+ "streaming-iterator",
  "tree-sitter",
  "tree-sitter-json",
  "unindent",
@@ -12732,6 +12730,12 @@ dependencies = [
  "ui",
 ]
 
+[[package]]
+name = "streaming-iterator"
+version = "0.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2b2231b7c3057d5e4ad0156fb3dc807d900806020c5ffa3ee6ff2c8c76fb8520"
+
 [[package]]
 name = "streaming_diff"
 version = "0.1.0"
@@ -14031,13 +14035,14 @@ dependencies = [
 
 [[package]]
 name = "tree-sitter"
-version = "0.23.2"
+version = "0.24.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0203df02a3b6dd63575cc1d6e609edc2181c9a11867a271b25cfd2abff3ec5ca"
+checksum = "a5387dffa7ffc7d2dae12b50c6f7aab8ff79d6210147c6613561fc3d474c6f75"
 dependencies = [
  "cc",
  "regex",
  "regex-syntax 0.8.5",
+ "streaming-iterator",
  "tree-sitter-language",
  "wasmtime-c-api-impl",
 ]
@@ -14936,9 +14941,9 @@ dependencies = [
 
 [[package]]
 name = "wasm-encoder"
-version = "0.215.0"
+version = "0.217.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4fb56df3e06b8e6b77e37d2969a50ba51281029a9aeb3855e76b7f49b6418847"
+checksum = "10961fd76db420582926af70816dd205019d8152d9e51e1b939125dd1639f854"
 dependencies = [
  "leb128",
 ]
@@ -14985,9 +14990,9 @@ dependencies = [
 
 [[package]]
 name = "wasmparser"
-version = "0.215.0"
+version = "0.217.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "53fbde0881f24199b81cf49b6ff8f9c145ac8eb1b7fc439adb5c099734f7d90e"
+checksum = "65a5a0689975b9fd93c02f5400cfd9669858b99607e54e7b892c6080cba598bb"
 dependencies = [
  "ahash 0.8.11",
  "bitflags 2.8.0",
@@ -14999,20 +15004,20 @@ dependencies = [
 
 [[package]]
 name = "wasmprinter"
-version = "0.215.0"
+version = "0.217.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d8e9a325d85053408209b3d2ce5eaddd0dd6864d1cff7a007147ba073157defc"
+checksum = "324c6782d7b81c01625335d252653b26ea68e835ddb4aef4cb1ed3ea40ae3a49"
 dependencies = [
  "anyhow",
  "termcolor",
- "wasmparser 0.215.0",
+ "wasmparser 0.217.1",
 ]
 
 [[package]]
 name = "wasmtime"
-version = "24.0.2"
+version = "25.0.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e763074ccd6b251c78095fcd27707253b69cef961ea0a2ff76a8d246ddfadd1b"
+checksum = "f38dbf42dc56a6fe41ccd77211ea8ec90855de05e52cd00df5a0a3bca87d6147"
 dependencies = [
  "anyhow",
  "async-trait",
@@ -15040,7 +15045,7 @@ dependencies = [
  "smallvec",
  "sptr",
  "target-lexicon",
- "wasmparser 0.215.0",
+ "wasmparser 0.217.1",
  "wasmtime-asm-macros",
  "wasmtime-component-macro",
  "wasmtime-component-util",
@@ -15056,18 +15061,18 @@ dependencies = [
 
 [[package]]
 name = "wasmtime-asm-macros"
-version = "24.0.2"
+version = "25.0.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f45004b6fa5d12dd95b427474e69bde05a6d31d33b39bd56054f9cd68e824283"
+checksum = "30e0c7f9983c2d60109a939d9ab0e0df301901085c3608e1c22c27c98390a027"
 dependencies = [
  "cfg-if",
 ]
 
 [[package]]
 name = "wasmtime-c-api-impl"
-version = "24.0.2"
+version = "25.0.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4e038dd412700174019867608617127e7cc4f113f764dd10e7488dbf5f47b191"
+checksum = "ebfcdb4aa0f68020934099815cf6ef11dbbedaf070ef800b3f0a7f6ec7b7d005"
 dependencies = [
  "anyhow",
  "log",
@@ -15079,9 +15084,9 @@ dependencies = [
 
 [[package]]
 name = "wasmtime-c-api-macros"
-version = "24.0.2"
+version = "25.0.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bde0ca2263811d980ab676bcb2a190c990737f58969a908976101ad208149a17"
+checksum = "842c213ad4546fb0178735910b96ee7da303e1d745c3f42f4178b0de1da138b6"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -15089,9 +15094,9 @@ dependencies = [
 
 [[package]]
 name = "wasmtime-component-macro"
-version = "24.0.2"
+version = "25.0.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "74b72572d389586e429a9830ab68a5b3e2a567962b8a82f4249652ccc68ddab2"
+checksum = "0929ffffaca32dd8770b56848c94056036963ca05de25fb47cac644e20262168"
 dependencies = [
  "anyhow",
  "proc-macro2",
@@ -15099,20 +15104,20 @@ dependencies = [
  "syn 2.0.90",
  "wasmtime-component-util",
  "wasmtime-wit-bindgen",
- "wit-parser 0.215.0",
+ "wit-parser 0.217.1",
 ]
 
 [[package]]
 name = "wasmtime-component-util"
-version = "24.0.2"
+version = "25.0.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "eb3081af782040e8016373e603ee854496c82cdc0f32b13a6bc9700e15f582db"
+checksum = "fdc29d2b56629d66d2fd791d1b46471d0016e0d684ed2dc299e870d127082268"
 
 [[package]]
 name = "wasmtime-cranelift"
-version = "24.0.2"
+version = "25.0.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "42c18ca178eee0947cd53b27d3a101dd2f79afec86fc3ce657545519c6bf011a"
+checksum = "f8c8af1197703f4de556a274384adf5db36a146f9892bc9607bad16881e75c80"
 dependencies = [
  "anyhow",
  "cfg-if",
@@ -15125,18 +15130,19 @@ dependencies = [
  "gimli 0.29.0",
  "log",
  "object",
+ "smallvec",
  "target-lexicon",
  "thiserror 1.0.69",
- "wasmparser 0.215.0",
+ "wasmparser 0.217.1",
  "wasmtime-environ",
  "wasmtime-versioned-export-macros",
 ]
 
 [[package]]
 name = "wasmtime-environ"
-version = "24.0.2"
+version = "25.0.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e80da0784d4dd0788479ce390cd4a54a893d24f2937d4046145704777aa7a131"
+checksum = "3f1b5af7bac868c5bce3b78a366a10677caacf6e6467c156301297e36ed31f3e"
 dependencies = [
  "anyhow",
  "cpp_demangle",
@@ -15152,8 +15158,8 @@ dependencies = [
  "serde",
  "serde_derive",
  "target-lexicon",
- "wasm-encoder 0.215.0",
- "wasmparser 0.215.0",
+ "wasm-encoder 0.217.1",
+ "wasmparser 0.217.1",
  "wasmprinter",
  "wasmtime-component-util",
  "wasmtime-types",
@@ -15161,9 +15167,9 @@ dependencies = [
 
 [[package]]
 name = "wasmtime-fiber"
-version = "24.0.2"
+version = "25.0.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "57c3d366194ff87b8aeeb7348bb789d5dd9a9aca18b340b19dcf4ab96966e663"
+checksum = "665ccc1bb0f28496e6fa02e94c575ee9ad6e3202c7df8591e5dda78106d5aa4a"
 dependencies = [
  "anyhow",
  "cc",
@@ -15176,9 +15182,9 @@ dependencies = [
 
 [[package]]
 name = "wasmtime-jit-icache-coherence"
-version = "24.0.2"
+version = "25.0.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c543f7ee7b1ec8f2215f88197a40f9fa3452dc98c5902c5c700d8ec9e9ea7021"
+checksum = "5d7314e32c624f645ad7d6b9fc3ac89eb7d2b9aa06695d6445cec087958ec27d"
 dependencies = [
  "anyhow",
  "cfg-if",
@@ -15188,29 +15194,29 @@ dependencies = [
 
 [[package]]
 name = "wasmtime-slab"
-version = "24.0.2"
+version = "25.0.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bcf7ded4156c76cc1cb348e5728096087e2c432714d1b285044c6da6a1e3d01a"
+checksum = "f75cba1a8cc327839f493cfc3036c9de3d077d59ab76296bc710ee5f95be5391"
 
 [[package]]
 name = "wasmtime-types"
-version = "24.0.2"
+version = "25.0.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c92a6f3c2a8704a60ae0278ea2635c986539539ce1b80080b0fe8ea7bc83da81"
+checksum = "c6d83a7816947a4974e2380c311eacb1db009b8bad86081dc726b705603c93c7"
 dependencies = [
  "anyhow",
  "cranelift-entity",
  "serde",
  "serde_derive",
  "smallvec",
- "wasmparser 0.215.0",
+ "wasmparser 0.217.1",
 ]
 
 [[package]]
 name = "wasmtime-versioned-export-macros"
-version = "24.0.2"
+version = "25.0.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7a6e2f847c118d5b26f0cc01d12a6d72fa450e32c42a4a3ce5d33afb4729ed6a"
+checksum = "6879a8e168aef3fe07335343b7fbede12fa494215e83322e173d4018e124a846"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -15219,9 +15225,9 @@ dependencies = [
 
 [[package]]
 name = "wasmtime-wasi"
-version = "24.0.2"
+version = "25.0.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f88f94e393084426f5055d57ce7ae6346ae623783ee6792f411282d6b9e1e5c3"
+checksum = "d042ea66b2834fb03b8a6968ef1a99a4b537211b00f7502a4d6a37f4eb2049b2"
 dependencies = [
  "anyhow",
  "async-trait",
@@ -15250,16 +15256,16 @@ dependencies = [
 
 [[package]]
 name = "wasmtime-winch"
-version = "24.0.2"
+version = "25.0.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ee3640cd34c67f505e88cef0da11368806204a24c68c35d671a48a59bb37f908"
+checksum = "6baca2a919a288df653246069868b4de80f07e9679a8ef9b78ad79fc658ffd12"
 dependencies = [
  "anyhow",
  "cranelift-codegen",
  "gimli 0.29.0",
  "object",
  "target-lexicon",
- "wasmparser 0.215.0",
+ "wasmparser 0.217.1",
  "wasmtime-cranelift",
  "wasmtime-environ",
  "winch-codegen",
@@ -15267,14 +15273,14 @@ dependencies = [
 
 [[package]]
 name = "wasmtime-wit-bindgen"
-version = "24.0.2"
+version = "25.0.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c58b085b2d330e5057dddd31f3ca527569b90fcdd35f6d373420c304927a5190"
+checksum = "3f571f63ac1d532e986eb3973bbef3a45e4ae83de521a8d573b0fe0594dc9608"
 dependencies = [
  "anyhow",
  "heck 0.4.1",
  "indexmap",
- "wit-parser 0.215.0",
+ "wit-parser 0.217.1",
 ]
 
 [[package]]
@@ -15493,9 +15499,9 @@ dependencies = [
 
 [[package]]
 name = "wiggle"
-version = "24.0.2"
+version = "25.0.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c72a4c92952216582f55eab27819a1fe8d3c54b292b7b8e5f849b23bfed96e78"
+checksum = "4c8fdcd81702e0f46a8ab2ed28a5bf824aabf4a1af1673af496a020aacd0b6f9"
 dependencies = [
  "anyhow",
  "async-trait",
@@ -15508,9 +15514,9 @@ dependencies = [
 
 [[package]]
 name = "wiggle-generate"
-version = "24.0.2"
+version = "25.0.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cb744fb938a9fc38207838829b4a43831c1de499e3526eaea71deeff4d9cbb83"
+checksum = "14f745361f0a9071aaabd05de1bb2b782d9f0597f30d9c0f20326224902e64d5"
 dependencies = [
  "anyhow",
  "heck 0.4.1",
@@ -15523,9 +15529,9 @@ dependencies = [
 
 [[package]]
 name = "wiggle-macro"
-version = "24.0.2"
+version = "25.0.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7cef395fff17bf8f9c1dee6c0e12801a3ba24928139af0ecb5ccb82ff87bf9d2"
+checksum = "bfbdae3574621921ed3c13325edc910388487759d10fb330f656cfc69bee38db"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -15566,9 +15572,9 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
 
 [[package]]
 name = "winch-codegen"
-version = "0.22.2"
+version = "0.23.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "46d7fecc199486f048bb2d649dce68bf28712ae1183dd54fd4a0534989517b24"
+checksum = "01cd1dc56c5a45d509ff06e7ca8817eaa9ec3240096f07e71915d5d528658e8a"
 dependencies = [
  "anyhow",
  "cranelift-codegen",
@@ -15576,7 +15582,7 @@ dependencies = [
  "regalloc2",
  "smallvec",
  "target-lexicon",
- "wasmparser 0.215.0",
+ "wasmparser 0.217.1",
  "wasmtime-cranelift",
  "wasmtime-environ",
 ]
@@ -16128,9 +16134,9 @@ dependencies = [
 
 [[package]]
 name = "wit-parser"
-version = "0.215.0"
+version = "0.217.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "935a97eaffd57c3b413aa510f8f0b550a4a9fe7d59e79cd8b89a83dcb860321f"
+checksum = "e5aaf02882453eaeec4fe30f1e4263cfd8b8ea36dd00e1fe7d902d9cb498bccd"
 dependencies = [
  "anyhow",
  "id-arena",
@@ -16141,7 +16147,7 @@ dependencies = [
  "serde_derive",
  "serde_json",
  "unicode-xid",
- "wasmparser 0.215.0",
+ "wasmparser 0.217.1",
 ]
 
 [[package]]

Cargo.toml πŸ”—

@@ -423,6 +423,7 @@ ignore = "0.4.22"
 image = "0.25.1"
 indexmap = { version = "2.7.0", features = ["serde"] }
 indoc = "2"
+inventory = "0.3.19"
 itertools = "0.14.0"
 jsonwebtoken = "9.3"
 jupyter-protocol = { version = "0.6.0" }
@@ -502,6 +503,7 @@ simplelog = "0.12.2"
 smallvec = { version = "1.6", features = ["union"] }
 smol = "2.0"
 sqlformat = "0.2"
+streaming-iterator = "0.1"
 strsim = "0.11"
 strum = { version = "0.26.0", features = ["derive"] }
 subtle = "2.5.0"
@@ -523,7 +525,7 @@ tiny_http = "0.8"
 toml = "0.8"
 tokio = { version = "1" }
 tower-http = "0.4.4"
-tree-sitter = { version = "0.23", features = ["wasm"] }
+tree-sitter = { version = "0.24", features = ["wasm"] }
 tree-sitter-bash = "0.23"
 tree-sitter-c = "0.23"
 tree-sitter-cpp = "0.23"
@@ -552,16 +554,16 @@ unicode-segmentation = "1.10"
 unicode-script = "0.5.7"
 url = "2.2"
 uuid = { version = "1.1.2", features = ["v4", "v5", "v7", "serde"] }
-wasmparser = "0.215"
-wasm-encoder = "0.215"
-wasmtime = { version = "24", default-features = false, features = [
+wasmparser = "0.217"
+wasm-encoder = "0.217"
+wasmtime = { version = "25", default-features = false, features = [
     "async",
     "demangle",
     "runtime",
     "cranelift",
     "component-model",
 ] }
-wasmtime-wasi = "24"
+wasmtime-wasi = "25"
 which = "6.0.0"
 wit-component = "0.201"
 zed_llm_client = "0.4"

crates/editor/Cargo.toml πŸ”—

@@ -49,6 +49,7 @@ gpui.workspace = true
 http_client.workspace = true
 indoc.workspace = true
 inline_completion.workspace = true
+inventory.workspace = true
 itertools.workspace = true
 language.workspace = true
 linkify.workspace = true

crates/editor/src/editor.rs πŸ”—

@@ -62,10 +62,10 @@ pub use editor_settings::{
     CurrentLineHighlight, EditorSettings, ScrollBeyondLastLine, SearchSettings, ShowScrollbar,
 };
 pub use editor_settings_controls::*;
+use element::{AcceptEditPredictionBinding, LineWithInvisibles, PositionMap};
 pub use element::{
     CursorLayout, EditorElement, HighlightedRange, HighlightedRangeLine, PointForPosition,
 };
-use element::{LineWithInvisibles, PositionMap};
 use futures::{future, FutureExt};
 use fuzzy::StringMatchCandidate;
 
@@ -190,6 +190,9 @@ pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
 pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(2);
 pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
 
+pub(crate) const EDIT_PREDICTION_REQUIRES_MODIFIER_KEY_CONTEXT: &str =
+    "edit_prediction_requires_modifier";
+
 pub fn render_parsed_markdown(
     element_id: impl Into<ElementId>,
     parsed: &language::ParsedMarkdown,
@@ -1528,7 +1531,7 @@ impl Editor {
             key_context.add("edit_prediction");
 
             if showing_completions || self.edit_prediction_requires_modifier(cx) {
-                key_context.add("edit_prediction_requires_modifier");
+                key_context.add(EDIT_PREDICTION_REQUIRES_MODIFIER_KEY_CONTEXT);
             }
         }
 
@@ -5092,19 +5095,38 @@ impl Editor {
         has_completion && self.edit_prediction_requires_modifier(cx)
     }
 
-    fn update_inline_completion_preview(
+    fn handle_modifiers_changed(
         &mut self,
-        modifiers: &Modifiers,
+        modifiers: Modifiers,
+        position_map: &PositionMap,
         window: &mut Window,
         cx: &mut Context<Self>,
     ) {
         if !self.show_edit_predictions_in_menu(cx) {
+            let accept_binding =
+                AcceptEditPredictionBinding::resolve(self.focus_handle(cx), window);
+            if let Some(accept_keystroke) = accept_binding.keystroke() {
+                let was_previewing_inline_completion = self.previewing_inline_completion;
+                self.previewing_inline_completion = modifiers == accept_keystroke.modifiers
+                    && accept_keystroke.modifiers.modified();
+                if self.previewing_inline_completion != was_previewing_inline_completion {
+                    self.update_visible_inline_completion(window, cx);
+                }
+            }
+        }
+
+        let mouse_position = window.mouse_position();
+        if !position_map.text_hitbox.is_hovered(window) {
             return;
         }
 
-        self.previewing_inline_completion = modifiers.alt;
-        self.update_visible_inline_completion(window, cx);
-        cx.notify();
+        self.update_hovered_link(
+            position_map.point_for_position(mouse_position),
+            &position_map.snapshot,
+            modifiers,
+            window,
+            cx,
+        )
     }
 
     fn update_visible_inline_completion(

crates/editor/src/editor_tests.rs πŸ”—

@@ -9711,7 +9711,7 @@ async fn test_toggle_block_comment(cx: &mut gpui::TestAppContext) {
         &r#"
             <!-- Λ‡<script> -->
                 // Λ‡var x = new Y();
-            // Λ‡</script>
+            <!-- Λ‡</script> -->
         "#
         .unindent(),
     );

crates/editor/src/element.rs πŸ”—

@@ -15,13 +15,14 @@ use crate::{
     items::BufferSearchHighlights,
     mouse_context_menu::{self, MenuPosition, MouseContextMenu},
     scroll::{axis_pair, scroll_amount::ScrollAmount, AxisPair},
-    BlockId, ChunkReplacement, CursorShape, CustomBlockId, DisplayPoint, DisplayRow,
-    DocumentHighlightRead, DocumentHighlightWrite, EditDisplayMode, Editor, EditorMode,
+    AcceptEditPrediction, BlockId, ChunkReplacement, CursorShape, CustomBlockId, DisplayPoint,
+    DisplayRow, DocumentHighlightRead, DocumentHighlightWrite, EditDisplayMode, Editor, EditorMode,
     EditorSettings, EditorSnapshot, EditorStyle, ExpandExcerpts, FocusedBlock, GoToHunk,
     GoToPrevHunk, GutterDimensions, HalfPageDown, HalfPageUp, HandleInput, HoveredCursor,
     InlineCompletion, JumpData, LineDown, LineUp, OpenExcerpts, PageDown, PageUp, Point,
     RevertSelectedHunks, RowExt, RowRangeExt, SelectPhase, Selection, SoftWrap,
-    StickyHeaderExcerpt, ToPoint, ToggleFold, CURSORS_VISIBLE_FOR, FILE_HEADER_HEIGHT,
+    StickyHeaderExcerpt, ToPoint, ToggleFold, CURSORS_VISIBLE_FOR,
+    EDIT_PREDICTION_REQUIRES_MODIFIER_KEY_CONTEXT, FILE_HEADER_HEIGHT,
     GIT_BLAME_MAX_AUTHOR_CHARS_DISPLAYED, MAX_LINE_LEN, MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
 };
 use client::ParticipantIndex;
@@ -34,11 +35,11 @@ use gpui::{
     relative, size, svg, transparent_black, Action, AnyElement, App, AvailableSpace, Axis, Bounds,
     ClickEvent, ClipboardItem, ContentMask, Context, Corner, Corners, CursorStyle, DispatchPhase,
     Edges, Element, ElementInputHandler, Entity, FocusHandle, Focusable as _, FontId,
-    GlobalElementId, Hitbox, Hsla, InteractiveElement, IntoElement, Keystroke, Length,
-    ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, PaintQuad,
-    ParentElement, Pixels, ScrollDelta, ScrollWheelEvent, ShapedLine, SharedString, Size,
-    StatefulInteractiveElement, Style, Styled, Subscription, TextRun, TextStyleRefinement,
-    WeakEntity, Window,
+    GlobalElementId, Hitbox, Hsla, InteractiveElement, IntoElement, KeyBindingContextPredicate,
+    Keystroke, Length, ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMoveEvent,
+    MouseUpEvent, PaintQuad, ParentElement, Pixels, ScrollDelta, ScrollWheelEvent, ShapedLine,
+    SharedString, Size, StatefulInteractiveElement, Style, Styled, Subscription, TextRun,
+    TextStyleRefinement, WeakEntity, Window,
 };
 use itertools::Itertools;
 use language::{
@@ -54,7 +55,7 @@ use multi_buffer::{
     RowInfo, ToOffset,
 };
 use project::project_settings::{GitGutterSetting, ProjectSettings};
-use settings::Settings;
+use settings::{KeyBindingValidator, KeyBindingValidatorRegistration, Settings};
 use smallvec::{smallvec, SmallVec};
 use std::{
     any::TypeId,
@@ -74,7 +75,7 @@ use ui::{
     POPOVER_Y_PADDING,
 };
 use unicode_segmentation::UnicodeSegmentation;
-use util::{RangeExt, ResultExt};
+use util::{markdown::MarkdownString, RangeExt, ResultExt};
 use workspace::{item::Item, notifications::NotifyTaskExt, Workspace};
 
 const INLINE_BLAME_PADDING_EM_WIDTHS: f32 = 7.;
@@ -511,35 +512,12 @@ impl EditorElement {
                     if editor.hover_state.focused(window, cx) {
                         return;
                     }
-                    Self::modifiers_changed(editor, event, &position_map, window, cx)
+                    editor.handle_modifiers_changed(event.modifiers, &position_map, window, cx);
                 })
             }
         });
     }
 
-    fn modifiers_changed(
-        editor: &mut Editor,
-        event: &ModifiersChangedEvent,
-        position_map: &PositionMap,
-        window: &mut Window,
-        cx: &mut Context<Editor>,
-    ) {
-        editor.update_inline_completion_preview(&event.modifiers, window, cx);
-
-        let mouse_position = window.mouse_position();
-        if !position_map.text_hitbox.is_hovered(window) {
-            return;
-        }
-
-        editor.update_hovered_link(
-            position_map.point_for_position(mouse_position),
-            &position_map.snapshot,
-            event.modifiers,
-            window,
-            cx,
-        )
-    }
-
     fn mouse_left_down(
         editor: &mut Editor,
         event: &MouseDownEvent,
@@ -3190,49 +3168,8 @@ impl EditorElement {
                 );
 
                 let edit_prediction = if edit_prediction_popover_visible {
-                    let accept_keystroke: Option<Keystroke>;
-
-                    // TODO: load modifier from keymap.
-                    // `bindings_for_action_in` returns `None` in Linux, and is intermittent on macOS
-                    #[cfg(target_os = "macos")]
-                    {
-                        // let bindings = window.bindings_for_action_in(
-                        //     &crate::AcceptEditPrediction,
-                        //     &self.editor.focus_handle(cx),
-                        // );
-
-                        // let last_binding = bindings.last();
-
-                        // accept_keystroke = if let Some(binding) = last_binding {
-                        //     match &binding.keystrokes() {
-                        //         // TODO: no need to clone once this logic works on linux.
-                        //         [keystroke] => Some(keystroke.clone()),
-                        //         _ => None,
-                        //     }
-                        // } else {
-                        //     None
-                        // };
-                        accept_keystroke = Some(Keystroke {
-                            modifiers: gpui::Modifiers {
-                                alt: true,
-                                ..Default::default()
-                            },
-                            key: "tab".to_string(),
-                            key_char: None,
-                        });
-                    }
-
-                    #[cfg(not(target_os = "macos"))]
-                    {
-                        accept_keystroke = Some(Keystroke {
-                            modifiers: gpui::Modifiers {
-                                alt: true,
-                                ..Default::default()
-                            },
-                            key: "enter".to_string(),
-                            key_char: None,
-                        });
-                    }
+                    let accept_binding =
+                        AcceptEditPredictionBinding::resolve(self.editor.focus_handle(cx), window);
 
                     self.editor.update(cx, move |editor, cx| {
                         let mut element = editor.render_edit_prediction_cursor_popover(
@@ -3240,7 +3177,7 @@ impl EditorElement {
                             max_width,
                             cursor_point,
                             style,
-                            accept_keystroke.as_ref()?,
+                            accept_binding.keystroke()?,
                             window,
                             cx,
                         )?;
@@ -5739,48 +5676,12 @@ fn inline_completion_accept_indicator(
     label: impl Into<SharedString>,
     icon: Option<IconName>,
     previewing: bool,
-    focus_handle: FocusHandle,
+    editor_focus_handle: FocusHandle,
     window: &Window,
     cx: &App,
 ) -> Option<AnyElement> {
-    let use_hardcoded_linux_bindings;
-
-    #[cfg(target_os = "macos")]
-    {
-        use_hardcoded_linux_bindings = false;
-    }
-
-    #[cfg(not(target_os = "macos"))]
-    {
-        use_hardcoded_linux_bindings = true;
-    }
-
-    let accept_keystroke = if use_hardcoded_linux_bindings {
-        if previewing {
-            Keystroke {
-                modifiers: Default::default(),
-                key: "enter".to_string(),
-                key_char: None,
-            }
-        } else {
-            Keystroke {
-                modifiers: Default::default(),
-                key: "tab".to_string(),
-                key_char: None,
-            }
-        }
-    } else {
-        let bindings = window.bindings_for_action_in(&crate::AcceptEditPrediction, &focus_handle);
-        if let Some(keystroke) = bindings
-            .last()
-            .and_then(|binding| binding.keystrokes().first())
-        {
-            // TODO: clone unnecessary once `use_hardcoded_linux_bindings` is removed.
-            keystroke.clone()
-        } else {
-            return None;
-        }
-    };
+    let accept_binding = AcceptEditPredictionBinding::resolve(editor_focus_handle, window);
+    let accept_keystroke = accept_binding.keystroke()?;
 
     let accept_key = h_flex()
         .px_0p5()
@@ -5828,6 +5729,69 @@ fn inline_completion_accept_indicator(
     )
 }
 
+pub struct AcceptEditPredictionBinding(Option<gpui::KeyBinding>);
+
+impl AcceptEditPredictionBinding {
+    pub fn resolve(editor_focus_handle: FocusHandle, window: &Window) -> Self {
+        AcceptEditPredictionBinding(
+            window
+                .bindings_for_action_in(&AcceptEditPrediction, &editor_focus_handle)
+                .into_iter()
+                .next(),
+        )
+    }
+
+    pub fn keystroke(&self) -> Option<&Keystroke> {
+        if let Some(binding) = self.0.as_ref() {
+            match &binding.keystrokes() {
+                [keystroke] => Some(keystroke),
+                _ => None,
+            }
+        } else {
+            None
+        }
+    }
+}
+
+struct AcceptEditPredictionsBindingValidator;
+
+inventory::submit! { KeyBindingValidatorRegistration(|| Box::new(AcceptEditPredictionsBindingValidator)) }
+
+impl KeyBindingValidator for AcceptEditPredictionsBindingValidator {
+    fn action_type_id(&self) -> TypeId {
+        TypeId::of::<AcceptEditPrediction>()
+    }
+
+    fn validate(&self, binding: &gpui::KeyBinding) -> Result<(), MarkdownString> {
+        use KeyBindingContextPredicate::*;
+
+        if binding.keystrokes().len() == 1 && binding.keystrokes()[0].modifiers.modified() {
+            return Ok(());
+        }
+        let required_predicate =
+            Not(Identifier(EDIT_PREDICTION_REQUIRES_MODIFIER_KEY_CONTEXT.into()).into());
+        match binding.predicate() {
+            Some(predicate) if required_predicate.is_superset(&predicate) => {
+                return Ok(());
+            }
+            _ => {}
+        }
+        Err(MarkdownString(format!(
+            "{} can only be bound to a single keystroke with modifiers, so \
+            that holding down these modifiers can be used to preview \
+            completions inline when the completions menu is open.\n\n\
+            This restriction does not apply when the context requires {}, \
+            since these bindings will not be used when the completions menu \
+            is open.",
+            MarkdownString::inline_code(AcceptEditPrediction.name()),
+            MarkdownString::inline_code(&format!(
+                "!{}",
+                EDIT_PREDICTION_REQUIRES_MODIFIER_KEY_CONTEXT
+            )),
+        )))
+    }
+}
+
 #[allow(clippy::too_many_arguments)]
 fn prepaint_gutter_button(
     button: IconButton,

crates/extension_host/src/wasm_host/wit/since_v0_0_1.rs πŸ”—

@@ -84,7 +84,7 @@ impl HostWorktree for WasmState {
         latest::HostWorktree::which(self, delegate, binary_name).await
     }
 
-    fn drop(&mut self, _worktree: Resource<Worktree>) -> Result<()> {
+    async fn drop(&mut self, _worktree: Resource<Worktree>) -> Result<()> {
         Ok(())
     }
 }

crates/extension_host/src/wasm_host/wit/since_v0_0_4.rs πŸ”—

@@ -92,7 +92,7 @@ impl HostWorktree for WasmState {
         latest::HostWorktree::which(self, delegate, binary_name).await
     }
 
-    fn drop(&mut self, _worktree: Resource<Worktree>) -> Result<()> {
+    async fn drop(&mut self, _worktree: Resource<Worktree>) -> Result<()> {
         // We only ever hand out borrows of worktrees.
         Ok(())
     }

crates/extension_host/src/wasm_host/wit/since_v0_0_6.rs πŸ”—

@@ -147,7 +147,7 @@ impl HostWorktree for WasmState {
         latest::HostWorktree::which(self, delegate, binary_name).await
     }
 
-    fn drop(&mut self, _worktree: Resource<Worktree>) -> Result<()> {
+    async fn drop(&mut self, _worktree: Resource<Worktree>) -> Result<()> {
         // We only ever hand out borrows of worktrees.
         Ok(())
     }

crates/extension_host/src/wasm_host/wit/since_v0_1_0.rs πŸ”—

@@ -240,7 +240,7 @@ impl HostKeyValueStore for WasmState {
         kv_store.insert(key, value).await.to_wasmtime_result()
     }
 
-    fn drop(&mut self, _worktree: Resource<ExtensionKeyValueStore>) -> Result<()> {
+    async fn drop(&mut self, _worktree: Resource<ExtensionKeyValueStore>) -> Result<()> {
         // We only ever hand out borrows of key-value stores.
         Ok(())
     }
@@ -282,7 +282,7 @@ impl HostWorktree for WasmState {
         latest::HostWorktree::which(self, delegate, binary_name).await
     }
 
-    fn drop(&mut self, _worktree: Resource<Worktree>) -> Result<()> {
+    async fn drop(&mut self, _worktree: Resource<Worktree>) -> Result<()> {
         // We only ever hand out borrows of worktrees.
         Ok(())
     }
@@ -350,7 +350,7 @@ impl http_client::HostHttpResponseStream for WasmState {
         .to_wasmtime_result()
     }
 
-    fn drop(&mut self, _resource: Resource<ExtensionHttpResponseStream>) -> Result<()> {
+    async fn drop(&mut self, _resource: Resource<ExtensionHttpResponseStream>) -> Result<()> {
         Ok(())
     }
 }

crates/extension_host/src/wasm_host/wit/since_v0_2_0.rs πŸ”—

@@ -259,7 +259,7 @@ impl HostKeyValueStore for WasmState {
         kv_store.insert(key, value).await.to_wasmtime_result()
     }
 
-    fn drop(&mut self, _worktree: Resource<ExtensionKeyValueStore>) -> Result<()> {
+    async fn drop(&mut self, _worktree: Resource<ExtensionKeyValueStore>) -> Result<()> {
         // We only ever hand out borrows of key-value stores.
         Ok(())
     }
@@ -275,7 +275,7 @@ impl HostProject for WasmState {
         Ok(project.worktree_ids())
     }
 
-    fn drop(&mut self, _project: Resource<Project>) -> Result<()> {
+    async fn drop(&mut self, _project: Resource<Project>) -> Result<()> {
         // We only ever hand out borrows of projects.
         Ok(())
     }
@@ -325,7 +325,7 @@ impl HostWorktree for WasmState {
         Ok(delegate.which(binary_name).await)
     }
 
-    fn drop(&mut self, _worktree: Resource<Worktree>) -> Result<()> {
+    async fn drop(&mut self, _worktree: Resource<Worktree>) -> Result<()> {
         // We only ever hand out borrows of worktrees.
         Ok(())
     }
@@ -393,7 +393,7 @@ impl http_client::HostHttpResponseStream for WasmState {
         .to_wasmtime_result()
     }
 
-    fn drop(&mut self, _resource: Resource<ExtensionHttpResponseStream>) -> Result<()> {
+    async fn drop(&mut self, _resource: Resource<ExtensionHttpResponseStream>) -> Result<()> {
         Ok(())
     }
 }

crates/gpui/Cargo.toml πŸ”—

@@ -79,7 +79,7 @@ futures.workspace = true
 gpui_macros.workspace = true
 http_client = { optional = true, workspace = true }
 image = "0.25.1"
-inventory = "0.3.19"
+inventory.workspace = true
 itertools.workspace = true
 log.workspace = true
 num_cpus = "1.13"

crates/language/Cargo.toml πŸ”—

@@ -53,6 +53,7 @@ settings.workspace = true
 similar.workspace = true
 smallvec.workspace = true
 smol.workspace = true
+streaming-iterator.workspace = true
 strsim.workspace = true
 sum_tree.workspace = true
 task.workspace = true

crates/language/src/outline.rs πŸ”—

@@ -146,7 +146,9 @@ impl<T> Outline<T> {
                 }
             } else {
                 let mut name_ranges = outline_match.name_ranges.iter();
-                let mut name_range = name_ranges.next().unwrap();
+                let Some(mut name_range) = name_ranges.next() else {
+                    continue;
+                };
                 let mut preceding_ranges_len = 0;
                 for position in &mut string_match.positions {
                     while *position >= preceding_ranges_len + name_range.len() {
@@ -194,6 +196,40 @@ impl<T> Outline<T> {
 #[cfg(test)]
 mod tests {
     use super::*;
+    use gpui::TestAppContext;
+
+    #[gpui::test]
+    async fn test_entries_with_no_names(cx: &mut TestAppContext) {
+        let outline = Outline::new(vec![
+            OutlineItem {
+                depth: 0,
+                range: Point::new(0, 0)..Point::new(5, 0),
+                text: "class Foo".to_string(),
+                highlight_ranges: vec![],
+                name_ranges: vec![6..9],
+                body_range: None,
+                annotation_range: None,
+            },
+            OutlineItem {
+                depth: 0,
+                range: Point::new(2, 0)..Point::new(2, 7),
+                text: "private".to_string(),
+                highlight_ranges: vec![],
+                name_ranges: vec![],
+                body_range: None,
+                annotation_range: None,
+            },
+        ]);
+        assert_eq!(
+            outline
+                .search(" ", cx.executor())
+                .await
+                .into_iter()
+                .map(|mat| mat.string)
+                .collect::<Vec<String>>(),
+            vec!["class Foo".to_string()]
+        );
+    }
 
     #[test]
     fn test_find_most_similar_with_low_similarity() {

crates/language/src/syntax_map.rs πŸ”—

@@ -14,6 +14,7 @@ use std::{
     ops::{Deref, DerefMut, Range},
     sync::Arc,
 };
+use streaming_iterator::StreamingIterator;
 use sum_tree::{Bias, SeekTarget, SumTree};
 use text::{Anchor, BufferSnapshot, OffsetRangeExt, Point, Rope, ToOffset, ToPoint};
 use tree_sitter::{Node, Query, QueryCapture, QueryCaptures, QueryCursor, QueryMatches, Tree};
@@ -1143,7 +1144,7 @@ impl<'a> SyntaxMapMatches<'a> {
 
 impl<'a> SyntaxMapCapturesLayer<'a> {
     fn advance(&mut self) {
-        self.next_capture = self.captures.next().map(|(mat, ix)| mat.captures[ix]);
+        self.next_capture = self.captures.next().map(|(mat, ix)| mat.captures[*ix]);
     }
 
     fn sort_key(&self) -> (usize, Reverse<usize>, usize) {
@@ -1280,7 +1281,8 @@ fn get_injections(
 
     for query_range in changed_ranges {
         query_cursor.set_byte_range(query_range.start.saturating_sub(1)..query_range.end + 1);
-        for mat in query_cursor.matches(&config.query, node, TextProvider(text.as_rope())) {
+        let mut matches = query_cursor.matches(&config.query, node, TextProvider(text.as_rope()));
+        while let Some(mat) = matches.next() {
             let content_ranges = mat
                 .nodes_for_capture_index(config.content_capture_ix)
                 .map(|node| node.range())
@@ -1554,7 +1556,8 @@ impl<'a> SyntaxLayer<'a> {
         query_cursor.set_byte_range(offset.saturating_sub(1)..offset.saturating_add(1));
 
         let mut smallest_match: Option<(u32, Range<usize>)> = None;
-        for mat in query_cursor.matches(&config.query, self.node(), text) {
+        let mut matches = query_cursor.matches(&config.query, self.node(), text);
+        while let Some(mat) = matches.next() {
             for capture in mat.captures {
                 let Some(override_entry) = config.values.get(&capture.index) else {
                     continue;

crates/migrator/Cargo.toml πŸ”—

@@ -15,6 +15,8 @@ doctest = false
 [dependencies]
 collections.workspace = true
 convert_case.workspace = true
+log.workspace = true
+streaming-iterator.workspace = true
 tree-sitter-json.workspace = true
 tree-sitter.workspace = true
 

crates/migrator/src/migrator.rs πŸ”—

@@ -1,6 +1,7 @@
 use collections::HashMap;
 use convert_case::{Case, Casing};
 use std::{cmp::Reverse, ops::Range, sync::LazyLock};
+use streaming_iterator::StreamingIterator;
 use tree_sitter::{Query, QueryMatch};
 
 fn migrate(text: &str, patterns: MigrationPatterns, query: &Query) -> Option<String> {
@@ -11,10 +12,10 @@ fn migrate(text: &str, patterns: MigrationPatterns, query: &Query) -> Option<Str
     let syntax_tree = parser.parse(&text, None).unwrap();
 
     let mut cursor = tree_sitter::QueryCursor::new();
-    let matches = cursor.matches(query, syntax_tree.root_node(), text.as_bytes());
+    let mut matches = cursor.matches(query, syntax_tree.root_node(), text.as_bytes());
 
     let mut edits = vec![];
-    for mat in matches {
+    while let Some(mat) = matches.next() {
         if let Some((_, callback)) = patterns.get(mat.pattern_index) {
             edits.extend(callback(&text, &mat, query));
         }
@@ -28,11 +29,19 @@ fn migrate(text: &str, patterns: MigrationPatterns, query: &Query) -> Option<Str
     if edits.is_empty() {
         None
     } else {
-        let mut text = text.to_string();
-        for (range, replacement) in edits.into_iter().rev() {
-            text.replace_range(range, &replacement);
+        let mut new_text = text.to_string();
+        for (range, replacement) in edits.iter().rev() {
+            new_text.replace_range(range.clone(), replacement);
+        }
+        if new_text == text {
+            log::error!(
+                "Edits computed for configuration migration do not cause a change: {:?}",
+                edits
+            );
+            None
+        } else {
+            Some(new_text)
         }
-        Some(text)
     }
 }
 

crates/semantic_index/Cargo.toml πŸ”—

@@ -42,6 +42,7 @@ serde_json.workspace = true
 settings.workspace = true
 sha2.workspace = true
 smol.workspace = true
+streaming-iterator.workspace = true
 theme.workspace = true
 tree-sitter.workspace = true
 ui.workspace = true

crates/semantic_index/src/chunking.rs πŸ”—

@@ -7,6 +7,7 @@ use std::{
     path::Path,
     sync::Arc,
 };
+use streaming_iterator::StreamingIterator;
 use tree_sitter::QueryCapture;
 use util::ResultExt as _;
 
@@ -88,7 +89,7 @@ fn syntactic_ranges(
     let mut ranges = with_query_cursor(|cursor| {
         cursor
             .matches(&outline.query, tree.root_node(), text.as_bytes())
-            .filter_map(|mat| {
+            .filter_map_deref(|mat| {
                 mat.captures
                     .iter()
                     .find_map(|QueryCapture { node, index }| {

crates/settings/Cargo.toml πŸ”—

@@ -22,6 +22,7 @@ ec4rs.workspace = true
 fs.workspace = true
 futures.workspace = true
 gpui.workspace = true
+inventory.workspace = true
 log.workspace = true
 paths.workspace = true
 release_channel.workspace = true
@@ -32,6 +33,7 @@ serde_derive.workspace = true
 serde_json.workspace = true
 serde_json_lenient.workspace = true
 smallvec.workspace = true
+streaming-iterator.workspace = true
 tree-sitter-json.workspace = true
 tree-sitter.workspace = true
 util.workspace = true

crates/settings/src/keymap_file.rs πŸ”—

@@ -1,5 +1,5 @@
 use anyhow::{anyhow, Context as _, Result};
-use collections::{HashMap, IndexMap};
+use collections::{BTreeMap, HashMap, IndexMap};
 use fs::Fs;
 use gpui::{
     Action, ActionBuildError, App, InvalidKeystrokeError, KeyBinding, KeyBindingContextPredicate,
@@ -13,12 +13,30 @@ use schemars::{
 };
 use serde::{Deserialize, Serialize};
 use serde_json::Value;
-use std::rc::Rc;
-use std::{fmt::Write, sync::Arc};
+use std::{any::TypeId, fmt::Write, rc::Rc, sync::Arc, sync::LazyLock};
 use util::{asset_str, markdown::MarkdownString};
 
 use crate::{settings_store::parse_json_with_comments, SettingsAssets};
 
+pub trait KeyBindingValidator: Send + Sync {
+    fn action_type_id(&self) -> TypeId;
+    fn validate(&self, binding: &KeyBinding) -> Result<(), MarkdownString>;
+}
+
+pub struct KeyBindingValidatorRegistration(pub fn() -> Box<dyn KeyBindingValidator>);
+
+inventory::collect!(KeyBindingValidatorRegistration);
+
+pub(crate) static KEY_BINDING_VALIDATORS: LazyLock<BTreeMap<TypeId, Box<dyn KeyBindingValidator>>> =
+    LazyLock::new(|| {
+        let mut validators = BTreeMap::new();
+        for validator_registration in inventory::iter::<KeyBindingValidatorRegistration> {
+            let validator = validator_registration.0();
+            validators.insert(validator.action_type_id(), validator);
+        }
+        validators
+    });
+
 // Note that the doc comments on these are shown by json-language-server when editing the keymap, so
 // they should be considered user-facing documentation. Documentation is not handled well with
 // schemars-0.8 - when there are newlines, it is rendered as plaintext (see
@@ -255,9 +273,16 @@ impl KeymapFile {
                             key_bindings.push(key_binding);
                         }
                         Err(err) => {
+                            let mut lines = err.lines();
+                            let mut indented_err = lines.next().unwrap().to_string();
+                            for line in lines {
+                                indented_err.push_str("  ");
+                                indented_err.push_str(line);
+                                indented_err.push_str("\n");
+                            }
                             write!(
                                 section_errors,
-                                "\n\n - In binding {}, {err}",
+                                "\n\n- In binding {}, {indented_err}",
                                 inline_code_string(keystrokes),
                             )
                             .unwrap();
@@ -367,13 +392,24 @@ impl KeymapFile {
             },
         };
 
-        match KeyBinding::load(keystrokes, action, context, key_equivalents) {
-            Ok(binding) => Ok(binding),
-            Err(InvalidKeystrokeError { keystroke }) => Err(format!(
-                "invalid keystroke {}. {}",
-                inline_code_string(&keystroke),
-                KEYSTROKE_PARSE_EXPECTED_MESSAGE
-            )),
+        let key_binding = match KeyBinding::load(keystrokes, action, context, key_equivalents) {
+            Ok(key_binding) => key_binding,
+            Err(InvalidKeystrokeError { keystroke }) => {
+                return Err(format!(
+                    "invalid keystroke {}. {}",
+                    inline_code_string(&keystroke),
+                    KEYSTROKE_PARSE_EXPECTED_MESSAGE
+                ))
+            }
+        };
+
+        if let Some(validator) = KEY_BINDING_VALIDATORS.get(&key_binding.action().type_id()) {
+            match validator.validate(&key_binding) {
+                Ok(()) => Ok(key_binding),
+                Err(error) => Err(error.0),
+            }
+        } else {
+            Ok(key_binding)
         }
     }
 

crates/settings/src/settings.rs πŸ”—

@@ -13,7 +13,9 @@ use util::asset_str;
 pub use editable_setting_control::*;
 pub use json_schema::*;
 pub use key_equivalents::*;
-pub use keymap_file::{KeymapFile, KeymapFileLoadResult};
+pub use keymap_file::{
+    KeyBindingValidator, KeyBindingValidatorRegistration, KeymapFile, KeymapFileLoadResult,
+};
 pub use settings_file::*;
 pub use settings_store::{
     parse_json_with_comments, InvalidSettingsError, LocalSettingsKind, Settings, SettingsLocation,

crates/settings/src/settings_store.rs πŸ”—

@@ -17,6 +17,7 @@ use std::{
     str::{self, FromStr},
     sync::{Arc, LazyLock},
 };
+use streaming_iterator::StreamingIterator;
 use tree_sitter::Query;
 use util::RangeExt;
 
@@ -1262,8 +1263,8 @@ fn replace_value_in_json_text(
     let mut last_value_range = 0..0;
     let mut first_key_start = None;
     let mut existing_value_range = 0..text.len();
-    let matches = cursor.matches(&PAIR_QUERY, syntax_tree.root_node(), text.as_bytes());
-    for mat in matches {
+    let mut matches = cursor.matches(&PAIR_QUERY, syntax_tree.root_node(), text.as_bytes());
+    while let Some(mat) = matches.next() {
         if mat.captures.len() != 2 {
             continue;
         }

crates/ui/src/components/context_menu.rs πŸ”—

@@ -671,18 +671,16 @@ impl Render for ContextMenu {
                                                         .when_some(
                                                             *toggle,
                                                             |list_item, (position, toggled)| {
-                                                                let contents = if toggled {
+                                                                let contents =
                                                                     div().flex_none().child(
                                                                         Icon::new(IconName::Check)
                                                                             .color(Color::Accent)
                                                                             .size(*icon_size)
                                                                     )
-                                                                } else {
-                                                                    div().flex_none().child(
-                                                                        Icon::new(IconName::Check)
-                                                                            .size(*icon_size)
-                                                                    ).invisible()
-                                                                };
+                                                                    .when(!toggled, |contents|
+                                                                        contents.invisible()
+                                                                    );
+
                                                                 match position {
                                                                     IconPosition::Start => {
                                                                         list_item