Merge branch 'main' into kvark-linux

Mikayla created

Change summary

Cargo.lock                                                    |  20 
Cargo.toml                                                    |   5 
crates/assistant/src/codegen.rs                               |   8 
crates/assistant/src/prompts.rs                               |  12 
crates/collab/src/tests/editor_tests.rs                       |  42 
crates/collab/src/tests/integration_tests.rs                  |  62 
crates/collab/src/tests/random_project_collaboration_tests.rs |   9 
crates/editor/src/display_map.rs                              |  17 
crates/editor/src/editor_tests.rs                             |  38 
crates/editor/src/highlight_matching_bracket.rs               |   7 
crates/editor/src/inlay_hint_cache.rs                         |  32 
crates/editor/src/test/editor_lsp_test_context.rs             |  14 
crates/extension/Cargo.toml                                   |  28 
crates/extension/LICENSE-GPL                                  |   1 
crates/extension/src/extension_store.rs                       | 344 ++++
crates/extension/src/extension_store_test.rs                  | 295 ++++
crates/gpui/src/shared_string.rs                              |   2 
crates/language/Cargo.toml                                    |   1 
crates/language/src/buffer_tests.rs                           |  30 
crates/language/src/language.rs                               | 346 +++-
crates/language/src/syntax_map/syntax_map_tests.rs            |  37 
crates/language_tools/src/lsp_log_tests.rs                    |   9 
crates/project/src/project_tests.rs                           |  90 +
crates/project_symbols/src/project_symbols.rs                 |   7 
crates/semantic_index/src/semantic_index_tests.rs             |  47 
crates/theme/src/registry.rs                                  |  14 
crates/util/src/arc_cow.rs                                    |  13 
crates/util/src/paths.rs                                      |   5 
crates/zed/Cargo.toml                                         |   1 
crates/zed/src/languages.rs                                   | 279 +--
crates/zed/src/languages/bash/config.toml                     |   1 
crates/zed/src/languages/beancount/config.toml                |   1 
crates/zed/src/languages/c/config.toml                        |   1 
crates/zed/src/languages/cpp/config.toml                      |   1 
crates/zed/src/languages/csharp/config.toml                   |   1 
crates/zed/src/languages/css/config.toml                      |   1 
crates/zed/src/languages/elixir/config.toml                   |   1 
crates/zed/src/languages/elm/config.toml                      |   1 
crates/zed/src/languages/erb/config.toml                      |   1 
crates/zed/src/languages/erlang/config.toml                   |   1 
crates/zed/src/languages/gitcommit/config.toml                |   1 
crates/zed/src/languages/gleam/config.toml                    |   1 
crates/zed/src/languages/glsl/config.toml                     |   1 
crates/zed/src/languages/go/config.toml                       |   1 
crates/zed/src/languages/gomod/config.toml                    |   1 
crates/zed/src/languages/gowork/config.toml                   |   1 
crates/zed/src/languages/haskell/config.toml                  |   1 
crates/zed/src/languages/hcl/config.toml                      |   1 
crates/zed/src/languages/heex/config.toml                     |   1 
crates/zed/src/languages/html/config.toml                     |   1 
crates/zed/src/languages/javascript/config.toml               |   1 
crates/zed/src/languages/json/config.toml                     |   1 
crates/zed/src/languages/lua/config.toml                      |   1 
crates/zed/src/languages/markdown/config.toml                 |   2 
crates/zed/src/languages/nix/config.toml                      |   1 
crates/zed/src/languages/nu/config.toml                       |   1 
crates/zed/src/languages/ocaml-interface/config.toml          |   3 
crates/zed/src/languages/ocaml/config.toml                    |   3 
crates/zed/src/languages/php/config.toml                      |   1 
crates/zed/src/languages/proto/config.toml                    |   1 
crates/zed/src/languages/purescript/config.toml               |   1 
crates/zed/src/languages/python/config.toml                   |   1 
crates/zed/src/languages/racket/config.toml                   |   1 
crates/zed/src/languages/ruby/config.toml                     |   1 
crates/zed/src/languages/rust/config.toml                     |   1 
crates/zed/src/languages/scheme/config.toml                   |   1 
crates/zed/src/languages/svelte/config.toml                   |   1 
crates/zed/src/languages/terraform/config.toml                |   1 
crates/zed/src/languages/toml/config.toml                     |   1 
crates/zed/src/languages/tsx/config.toml                      |   1 
crates/zed/src/languages/typescript/config.toml               |   1 
crates/zed/src/languages/uiua/config.toml                     |   1 
crates/zed/src/languages/vue/config.toml                      |   1 
crates/zed/src/languages/yaml/config.toml                     |   1 
crates/zed/src/languages/zig/config.toml                      |   1 
crates/zed/src/main.rs                                        |  22 
crates/zed/src/zed.rs                                         |   7 
docs/src/languages/ocaml.md                                   |   2 
78 files changed, 1,509 insertions(+), 387 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -2649,6 +2649,24 @@ version = "2.5.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0"
 
+[[package]]
+name = "extension"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "collections",
+ "fs",
+ "futures 0.3.28",
+ "gpui",
+ "language",
+ "parking_lot 0.11.2",
+ "serde",
+ "serde_json",
+ "theme",
+ "toml",
+ "util",
+]
+
 [[package]]
 name = "fallible-iterator"
 version = "0.2.0"
@@ -4138,6 +4156,7 @@ dependencies = [
  "sum_tree",
  "text",
  "theme",
+ "toml",
  "tree-sitter",
  "tree-sitter-elixir",
  "tree-sitter-embedded-template",
@@ -10589,6 +10608,7 @@ dependencies = [
  "diagnostics",
  "editor",
  "env_logger",
+ "extension",
  "feature_flags",
  "feedback",
  "file_finder",

Cargo.toml 🔗

@@ -21,6 +21,7 @@ members = [
     "crates/db",
     "crates/diagnostics",
     "crates/editor",
+    "crates/extension",
     "crates/feature_flags",
     "crates/feedback",
     "crates/file_finder",
@@ -30,12 +31,9 @@ members = [
     "crates/git",
     "crates/go_to_line",
     "crates/gpui",
-    "crates/gpui",
-    "crates/gpui_macros",
     "crates/gpui_macros",
     "crates/install_cli",
     "crates/journal",
-    "crates/journal",
     "crates/language",
     "crates/language_selector",
     "crates/language_tools",
@@ -114,6 +112,7 @@ copilot_ui = { path = "crates/copilot_ui" }
 db = { path = "crates/db" }
 diagnostics = { path = "crates/diagnostics" }
 editor = { path = "crates/editor" }
+extension = { path = "crates/extension" }
 feature_flags = { path = "crates/feature_flags" }
 feedback = { path = "crates/feedback" }
 file_finder = { path = "crates/file_finder" }

crates/assistant/src/codegen.rs 🔗

@@ -366,7 +366,8 @@ mod tests {
     use gpui::{Context, TestAppContext};
     use indoc::indoc;
     use language::{
-        language_settings, tree_sitter_rust, Buffer, BufferId, Language, LanguageConfig, Point,
+        language_settings, tree_sitter_rust, Buffer, BufferId, Language, LanguageConfig,
+        LanguageMatcher, Point,
     };
     use rand::prelude::*;
     use serde::Serialize;
@@ -675,7 +676,10 @@ mod tests {
         Language::new(
             LanguageConfig {
                 name: "Rust".into(),
-                path_suffixes: vec!["rs".to_string()],
+                matcher: LanguageMatcher {
+                    path_suffixes: vec!["rs".to_string()],
+                    ..Default::default()
+                },
                 ..Default::default()
             },
             Some(tree_sitter_rust::language()),

crates/assistant/src/prompts.rs 🔗

@@ -172,22 +172,24 @@ pub fn generate_content_prompt(
 
 #[cfg(test)]
 pub(crate) mod tests {
-
     use super::*;
-    use std::sync::Arc;
-
     use gpui::{AppContext, Context};
     use indoc::indoc;
     use language::{
-        language_settings, tree_sitter_rust, Buffer, BufferId, Language, LanguageConfig, Point,
+        language_settings, tree_sitter_rust, Buffer, BufferId, Language, LanguageConfig,
+        LanguageMatcher, Point,
     };
     use settings::SettingsStore;
+    use std::sync::Arc;
 
     pub(crate) fn rust_lang() -> Language {
         Language::new(
             LanguageConfig {
                 name: "Rust".into(),
-                path_suffixes: vec!["rs".to_string()],
+                matcher: LanguageMatcher {
+                    path_suffixes: vec!["rs".to_string()],
+                    ..Default::default()
+                },
                 ..Default::default()
             },
             Some(tree_sitter_rust::language()),

crates/collab/src/tests/editor_tests.rs 🔗

@@ -19,7 +19,7 @@ use gpui::{TestAppContext, VisualContext, VisualTestContext};
 use indoc::indoc;
 use language::{
     language_settings::{AllLanguageSettings, InlayHintSettings},
-    tree_sitter_rust, FakeLspAdapter, Language, LanguageConfig,
+    tree_sitter_rust, FakeLspAdapter, Language, LanguageConfig, LanguageMatcher,
 };
 use rpc::RECEIVE_TIMEOUT;
 use serde_json::json;
@@ -269,7 +269,10 @@ async fn test_collaborating_with_completion(cx_a: &mut TestAppContext, cx_b: &mu
     let mut language = Language::new(
         LanguageConfig {
             name: "Rust".into(),
-            path_suffixes: vec!["rs".to_string()],
+            matcher: LanguageMatcher {
+                path_suffixes: vec!["rs".to_string()],
+                ..Default::default()
+            },
             ..Default::default()
         },
         Some(tree_sitter_rust::language()),
@@ -455,7 +458,10 @@ async fn test_collaborating_with_code_actions(
     let mut language = Language::new(
         LanguageConfig {
             name: "Rust".into(),
-            path_suffixes: vec!["rs".to_string()],
+            matcher: LanguageMatcher {
+                path_suffixes: vec!["rs".to_string()],
+                ..Default::default()
+            },
             ..Default::default()
         },
         Some(tree_sitter_rust::language()),
@@ -668,7 +674,10 @@ async fn test_collaborating_with_renames(cx_a: &mut TestAppContext, cx_b: &mut T
     let mut language = Language::new(
         LanguageConfig {
             name: "Rust".into(),
-            path_suffixes: vec!["rs".to_string()],
+            matcher: LanguageMatcher {
+                path_suffixes: vec!["rs".to_string()],
+                ..Default::default()
+            },
             ..Default::default()
         },
         Some(tree_sitter_rust::language()),
@@ -853,7 +862,10 @@ async fn test_language_server_statuses(cx_a: &mut TestAppContext, cx_b: &mut Tes
     let mut language = Language::new(
         LanguageConfig {
             name: "Rust".into(),
-            path_suffixes: vec!["rs".to_string()],
+            matcher: LanguageMatcher {
+                path_suffixes: vec!["rs".to_string()],
+                ..Default::default()
+            },
             ..Default::default()
         },
         Some(tree_sitter_rust::language()),
@@ -1144,7 +1156,10 @@ async fn test_on_input_format_from_host_to_guest(
     let mut language = Language::new(
         LanguageConfig {
             name: "Rust".into(),
-            path_suffixes: vec!["rs".to_string()],
+            matcher: LanguageMatcher {
+                path_suffixes: vec!["rs".to_string()],
+                ..Default::default()
+            },
             ..Default::default()
         },
         Some(tree_sitter_rust::language()),
@@ -1272,7 +1287,10 @@ async fn test_on_input_format_from_guest_to_host(
     let mut language = Language::new(
         LanguageConfig {
             name: "Rust".into(),
-            path_suffixes: vec!["rs".to_string()],
+            matcher: LanguageMatcher {
+                path_suffixes: vec!["rs".to_string()],
+                ..Default::default()
+            },
             ..Default::default()
         },
         Some(tree_sitter_rust::language()),
@@ -1431,7 +1449,10 @@ async fn test_mutual_editor_inlay_hint_cache_update(
     let mut language = Language::new(
         LanguageConfig {
             name: "Rust".into(),
-            path_suffixes: vec!["rs".to_string()],
+            matcher: LanguageMatcher {
+                path_suffixes: vec!["rs".to_string()],
+                ..Default::default()
+            },
             ..Default::default()
         },
         Some(tree_sitter_rust::language()),
@@ -1697,7 +1718,10 @@ async fn test_inlay_hint_refresh_is_forwarded(
     let mut language = Language::new(
         LanguageConfig {
             name: "Rust".into(),
-            path_suffixes: vec!["rs".to_string()],
+            matcher: LanguageMatcher {
+                path_suffixes: vec!["rs".to_string()],
+                ..Default::default()
+            },
             ..Default::default()
         },
         Some(tree_sitter_rust::language()),

crates/collab/src/tests/integration_tests.rs 🔗

@@ -14,7 +14,7 @@ use gpui::{
 use language::{
     language_settings::{AllLanguageSettings, Formatter},
     tree_sitter_rust, Diagnostic, DiagnosticEntry, FakeLspAdapter, Language, LanguageConfig,
-    LineEnding, OffsetRangeExt, Point, Rope,
+    LanguageMatcher, LineEnding, OffsetRangeExt, Point, Rope,
 };
 use live_kit_client::MacOSDisplay;
 use lsp::LanguageServerId;
@@ -2246,7 +2246,10 @@ async fn test_propagate_saves_and_fs_changes(
     let rust = Arc::new(Language::new(
         LanguageConfig {
             name: "Rust".into(),
-            path_suffixes: vec!["rs".to_string()],
+            matcher: LanguageMatcher {
+                path_suffixes: vec!["rs".to_string()],
+                ..Default::default()
+            },
             ..Default::default()
         },
         Some(tree_sitter_rust::language()),
@@ -2254,7 +2257,10 @@ async fn test_propagate_saves_and_fs_changes(
     let javascript = Arc::new(Language::new(
         LanguageConfig {
             name: "JavaScript".into(),
-            path_suffixes: vec!["js".to_string()],
+            matcher: LanguageMatcher {
+                path_suffixes: vec!["js".to_string()],
+                ..Default::default()
+            },
             ..Default::default()
         },
         Some(tree_sitter_rust::language()),
@@ -3783,7 +3789,10 @@ async fn test_collaborating_with_diagnostics(
     let mut language = Language::new(
         LanguageConfig {
             name: "Rust".into(),
-            path_suffixes: vec!["rs".to_string()],
+            matcher: LanguageMatcher {
+                path_suffixes: vec!["rs".to_string()],
+                ..Default::default()
+            },
             ..Default::default()
         },
         Some(tree_sitter_rust::language()),
@@ -4061,7 +4070,10 @@ async fn test_collaborating_with_lsp_progress_updates_and_diagnostics_ordering(
     let mut language = Language::new(
         LanguageConfig {
             name: "Rust".into(),
-            path_suffixes: vec!["rs".to_string()],
+            matcher: LanguageMatcher {
+                path_suffixes: vec!["rs".to_string()],
+                ..Default::default()
+            },
             ..Default::default()
         },
         Some(tree_sitter_rust::language()),
@@ -4290,7 +4302,10 @@ async fn test_formatting_buffer(
     let mut language = Language::new(
         LanguageConfig {
             name: "Rust".into(),
-            path_suffixes: vec!["rs".to_string()],
+            matcher: LanguageMatcher {
+                path_suffixes: vec!["rs".to_string()],
+                ..Default::default()
+            },
             ..Default::default()
         },
         Some(tree_sitter_rust::language()),
@@ -4395,7 +4410,10 @@ async fn test_prettier_formatting_buffer(
     let mut language = Language::new(
         LanguageConfig {
             name: "Rust".into(),
-            path_suffixes: vec!["rs".to_string()],
+            matcher: LanguageMatcher {
+                path_suffixes: vec!["rs".to_string()],
+                ..Default::default()
+            },
             prettier_parser_name: Some("test_parser".to_string()),
             ..Default::default()
         },
@@ -4511,7 +4529,10 @@ async fn test_definition(
     let mut language = Language::new(
         LanguageConfig {
             name: "Rust".into(),
-            path_suffixes: vec!["rs".to_string()],
+            matcher: LanguageMatcher {
+                path_suffixes: vec!["rs".to_string()],
+                ..Default::default()
+            },
             ..Default::default()
         },
         Some(tree_sitter_rust::language()),
@@ -4655,7 +4676,10 @@ async fn test_references(
     let mut language = Language::new(
         LanguageConfig {
             name: "Rust".into(),
-            path_suffixes: vec!["rs".to_string()],
+            matcher: LanguageMatcher {
+                path_suffixes: vec!["rs".to_string()],
+                ..Default::default()
+            },
             ..Default::default()
         },
         Some(tree_sitter_rust::language()),
@@ -4852,7 +4876,10 @@ async fn test_document_highlights(
     let mut language = Language::new(
         LanguageConfig {
             name: "Rust".into(),
-            path_suffixes: vec!["rs".to_string()],
+            matcher: LanguageMatcher {
+                path_suffixes: vec!["rs".to_string()],
+                ..Default::default()
+            },
             ..Default::default()
         },
         Some(tree_sitter_rust::language()),
@@ -4955,7 +4982,10 @@ async fn test_lsp_hover(
     let mut language = Language::new(
         LanguageConfig {
             name: "Rust".into(),
-            path_suffixes: vec!["rs".to_string()],
+            matcher: LanguageMatcher {
+                path_suffixes: vec!["rs".to_string()],
+                ..Default::default()
+            },
             ..Default::default()
         },
         Some(tree_sitter_rust::language()),
@@ -5051,7 +5081,10 @@ async fn test_project_symbols(
     let mut language = Language::new(
         LanguageConfig {
             name: "Rust".into(),
-            path_suffixes: vec!["rs".to_string()],
+            matcher: LanguageMatcher {
+                path_suffixes: vec!["rs".to_string()],
+                ..Default::default()
+            },
             ..Default::default()
         },
         Some(tree_sitter_rust::language()),
@@ -5160,7 +5193,10 @@ async fn test_open_buffer_while_getting_definition_pointing_to_it(
     let mut language = Language::new(
         LanguageConfig {
             name: "Rust".into(),
-            path_suffixes: vec!["rs".to_string()],
+            matcher: LanguageMatcher {
+                path_suffixes: vec!["rs".to_string()],
+                ..Default::default()
+            },
             ..Default::default()
         },
         Some(tree_sitter_rust::language()),

crates/collab/src/tests/random_project_collaboration_tests.rs 🔗

@@ -8,7 +8,9 @@ use editor::Bias;
 use fs::{repository::GitFileStatus, FakeFs, Fs as _};
 use futures::StreamExt;
 use gpui::{BackgroundExecutor, Model, TestAppContext};
-use language::{range_to_lsp, FakeLspAdapter, Language, LanguageConfig, PointUtf16};
+use language::{
+    range_to_lsp, FakeLspAdapter, Language, LanguageConfig, LanguageMatcher, PointUtf16,
+};
 use lsp::FakeLanguageServer;
 use pretty_assertions::assert_eq;
 use project::{search::SearchQuery, Project, ProjectPath};
@@ -1022,7 +1024,10 @@ impl RandomizedTest for ProjectCollaborationTest {
         let mut language = Language::new(
             LanguageConfig {
                 name: "Rust".into(),
-                path_suffixes: vec!["rs".to_string()],
+                matcher: LanguageMatcher {
+                    path_suffixes: vec!["rs".to_string()],
+                    ..Default::default()
+                },
                 ..Default::default()
             },
             None,

crates/editor/src/display_map.rs 🔗

@@ -1003,7 +1003,7 @@ pub mod tests {
     use gpui::{div, font, observe, px, AppContext, Context, Element, Hsla};
     use language::{
         language_settings::{AllLanguageSettings, AllLanguageSettingsContent},
-        Buffer, Language, LanguageConfig, SelectionGoal,
+        Buffer, Language, LanguageConfig, LanguageMatcher, SelectionGoal,
     };
     use project::Project;
     use rand::{prelude::*, Rng};
@@ -1453,7 +1453,10 @@ pub mod tests {
             Language::new(
                 LanguageConfig {
                     name: "Test".into(),
-                    path_suffixes: vec![".test".to_string()],
+                    matcher: LanguageMatcher {
+                        path_suffixes: vec![".test".to_string()],
+                        ..Default::default()
+                    },
                     ..Default::default()
                 },
                 Some(tree_sitter_rust::language()),
@@ -1540,7 +1543,10 @@ pub mod tests {
             Language::new(
                 LanguageConfig {
                     name: "Test".into(),
-                    path_suffixes: vec![".test".to_string()],
+                    matcher: LanguageMatcher {
+                        path_suffixes: vec![".test".to_string()],
+                        ..Default::default()
+                    },
                     ..Default::default()
                 },
                 Some(tree_sitter_rust::language()),
@@ -1608,7 +1614,10 @@ pub mod tests {
             Language::new(
                 LanguageConfig {
                     name: "Test".into(),
-                    path_suffixes: vec![".test".to_string()],
+                    matcher: LanguageMatcher {
+                        path_suffixes: vec![".test".to_string()],
+                        ..Default::default()
+                    },
                     ..Default::default()
                 },
                 Some(tree_sitter_rust::language()),

crates/editor/src/editor_tests.rs 🔗

@@ -15,7 +15,8 @@ use language::{
     language_settings::{AllLanguageSettings, AllLanguageSettingsContent, LanguageSettingsContent},
     BracketPairConfig,
     Capability::ReadWrite,
-    FakeLspAdapter, LanguageConfig, LanguageConfigOverride, LanguageRegistry, Override, Point,
+    FakeLspAdapter, LanguageConfig, LanguageConfigOverride, LanguageMatcher, LanguageRegistry,
+    Override, Point,
 };
 use parking_lot::Mutex;
 use project::project_settings::{LspSettings, ProjectSettings};
@@ -5077,7 +5078,10 @@ async fn test_document_format_during_save(cx: &mut gpui::TestAppContext) {
     let mut language = Language::new(
         LanguageConfig {
             name: "Rust".into(),
-            path_suffixes: vec!["rs".to_string()],
+            matcher: LanguageMatcher {
+                path_suffixes: vec!["rs".to_string()],
+                ..Default::default()
+            },
             ..Default::default()
         },
         Some(tree_sitter_rust::language()),
@@ -5196,7 +5200,10 @@ async fn test_range_format_during_save(cx: &mut gpui::TestAppContext) {
     let mut language = Language::new(
         LanguageConfig {
             name: "Rust".into(),
-            path_suffixes: vec!["rs".to_string()],
+            matcher: LanguageMatcher {
+                path_suffixes: vec!["rs".to_string()],
+                ..Default::default()
+            },
             ..Default::default()
         },
         Some(tree_sitter_rust::language()),
@@ -5318,7 +5325,10 @@ async fn test_document_format_manual_trigger(cx: &mut gpui::TestAppContext) {
     let mut language = Language::new(
         LanguageConfig {
             name: "Rust".into(),
-            path_suffixes: vec!["rs".to_string()],
+            matcher: LanguageMatcher {
+                path_suffixes: vec!["rs".to_string()],
+                ..Default::default()
+            },
             // Enable Prettier formatting for the same buffer, and ensure
             // LSP is called instead of Prettier.
             prettier_parser_name: Some("test_parser".to_string()),
@@ -7747,7 +7757,10 @@ async fn test_on_type_formatting_not_triggered(cx: &mut gpui::TestAppContext) {
     let mut language = Language::new(
         LanguageConfig {
             name: "Rust".into(),
-            path_suffixes: vec!["rs".to_string()],
+            matcher: LanguageMatcher {
+                path_suffixes: vec!["rs".to_string()],
+                ..Default::default()
+            },
             brackets: BracketPairConfig {
                 pairs: vec![BracketPair {
                     start: "{".to_string(),
@@ -7859,7 +7872,10 @@ async fn test_language_server_restart_due_to_settings_change(cx: &mut gpui::Test
     let mut language = Language::new(
         LanguageConfig {
             name: Arc::clone(&language_name),
-            path_suffixes: vec!["rs".to_string()],
+            matcher: LanguageMatcher {
+                path_suffixes: vec!["rs".to_string()],
+                ..Default::default()
+            },
             ..Default::default()
         },
         Some(tree_sitter_rust::language()),
@@ -8086,7 +8102,10 @@ async fn test_completions_in_languages_with_extra_word_characters(cx: &mut gpui:
     let mut cx = EditorLspTestContext::new(
         Language::new(
             LanguageConfig {
-                path_suffixes: vec!["jsx".into()],
+                matcher: LanguageMatcher {
+                    path_suffixes: vec!["jsx".into()],
+                    ..Default::default()
+                },
                 overrides: [(
                     "element".into(),
                     LanguageConfigOverride {
@@ -8187,7 +8206,10 @@ async fn test_document_format_with_prettier(cx: &mut gpui::TestAppContext) {
     let mut language = Language::new(
         LanguageConfig {
             name: "Rust".into(),
-            path_suffixes: vec!["rs".to_string()],
+            matcher: LanguageMatcher {
+                path_suffixes: vec!["rs".to_string()],
+                ..Default::default()
+            },
             prettier_parser_name: Some("test_parser".to_string()),
             ..Default::default()
         },

crates/editor/src/highlight_matching_bracket.rs 🔗

@@ -35,7 +35,7 @@ mod tests {
     use super::*;
     use crate::{editor_tests::init_test, test::editor_lsp_test_context::EditorLspTestContext};
     use indoc::indoc;
-    use language::{BracketPair, BracketPairConfig, Language, LanguageConfig};
+    use language::{BracketPair, BracketPairConfig, Language, LanguageConfig, LanguageMatcher};
 
     #[gpui::test]
     async fn test_matching_bracket_highlights(cx: &mut gpui::TestAppContext) {
@@ -45,7 +45,10 @@ mod tests {
             Language::new(
                 LanguageConfig {
                     name: "Rust".into(),
-                    path_suffixes: vec!["rs".to_string()],
+                    matcher: LanguageMatcher {
+                        path_suffixes: vec!["rs".to_string()],
+                        ..Default::default()
+                    },
                     brackets: BracketPairConfig {
                         pairs: vec![
                             BracketPair {

crates/editor/src/inlay_hint_cache.rs 🔗

@@ -1240,7 +1240,7 @@ pub mod tests {
     use itertools::Itertools;
     use language::{
         language_settings::AllLanguageSettingsContent, Capability, FakeLspAdapter, Language,
-        LanguageConfig,
+        LanguageConfig, LanguageMatcher,
     };
     use lsp::FakeLanguageServer;
     use parking_lot::Mutex;
@@ -1529,7 +1529,10 @@ pub mod tests {
             let mut language = Language::new(
                 LanguageConfig {
                     name: name.into(),
-                    path_suffixes: vec![path_suffix.to_string()],
+                    matcher: LanguageMatcher {
+                        path_suffixes: vec![path_suffix.to_string()],
+                        ..Default::default()
+                    },
                     ..Default::default()
                 },
                 Some(tree_sitter_rust::language()),
@@ -2209,7 +2212,10 @@ pub mod tests {
         let mut language = Language::new(
             LanguageConfig {
                 name: "Rust".into(),
-                path_suffixes: vec!["rs".to_string()],
+                matcher: LanguageMatcher {
+                    path_suffixes: vec!["rs".to_string()],
+                    ..Default::default()
+                },
                 ..Default::default()
             },
             Some(tree_sitter_rust::language()),
@@ -2500,7 +2506,10 @@ pub mod tests {
         let mut language = Language::new(
             LanguageConfig {
                 name: "Rust".into(),
-                path_suffixes: vec!["rs".to_string()],
+                matcher: LanguageMatcher {
+                    path_suffixes: vec!["rs".to_string()],
+                    ..Default::default()
+                },
                 ..Default::default()
             },
             Some(tree_sitter_rust::language()),
@@ -2848,7 +2857,10 @@ pub mod tests {
         let mut language = Language::new(
             LanguageConfig {
                 name: "Rust".into(),
-                path_suffixes: vec!["rs".to_string()],
+                matcher: LanguageMatcher {
+                    path_suffixes: vec!["rs".to_string()],
+                    ..Default::default()
+                },
                 ..Default::default()
             },
             Some(tree_sitter_rust::language()),
@@ -3079,7 +3091,10 @@ pub mod tests {
         let mut language = Language::new(
             LanguageConfig {
                 name: "Rust".into(),
-                path_suffixes: vec!["rs".to_string()],
+                matcher: LanguageMatcher {
+                    path_suffixes: vec!["rs".to_string()],
+                    ..Default::default()
+                },
                 ..Default::default()
             },
             Some(tree_sitter_rust::language()),
@@ -3319,7 +3334,10 @@ pub mod tests {
         let mut language = Language::new(
             LanguageConfig {
                 name: "Rust".into(),
-                path_suffixes: vec!["rs".to_string()],
+                matcher: LanguageMatcher {
+                    path_suffixes: vec!["rs".to_string()],
+                    ..Default::default()
+                },
                 ..Default::default()
             },
             Some(tree_sitter_rust::language()),

crates/editor/src/test/editor_lsp_test_context.rs 🔗

@@ -12,7 +12,9 @@ use collections::HashSet;
 use futures::Future;
 use gpui::{View, ViewContext, VisualTestContext};
 use indoc::indoc;
-use language::{point_to_lsp, FakeLspAdapter, Language, LanguageConfig, LanguageQueries};
+use language::{
+    point_to_lsp, FakeLspAdapter, Language, LanguageConfig, LanguageMatcher, LanguageQueries,
+};
 use lsp::{notification, request};
 use multi_buffer::ToPointUtf16;
 use project::Project;
@@ -115,7 +117,10 @@ impl EditorLspTestContext {
         let language = Language::new(
             LanguageConfig {
                 name: "Rust".into(),
-                path_suffixes: vec!["rs".to_string()],
+                matcher: LanguageMatcher {
+                    path_suffixes: vec!["rs".to_string()],
+                    ..Default::default()
+                },
                 ..Default::default()
             },
             Some(tree_sitter_rust::language()),
@@ -160,7 +165,10 @@ impl EditorLspTestContext {
         let language = Language::new(
             LanguageConfig {
                 name: "Typescript".into(),
-                path_suffixes: vec!["ts".to_string()],
+                matcher: LanguageMatcher {
+                    path_suffixes: vec!["ts".to_string()],
+                    ..Default::default()
+                },
                 brackets: language::BracketPairConfig {
                     pairs: vec![language::BracketPair {
                         start: "{".to_string(),

crates/extension/Cargo.toml 🔗

@@ -0,0 +1,28 @@
+[package]
+name = "extension"
+version = "0.1.0"
+edition = "2021"
+publish = false
+license = "GPL-3.0-or-later"
+
+[lib]
+path = "src/extension_store.rs"
+
+[dependencies]
+anyhow.workspace = true
+collections.workspace = true
+fs.workspace = true
+futures.workspace = true
+gpui.workspace = true
+language.workspace = true
+parking_lot.workspace = true
+serde.workspace = true
+serde_json.workspace = true
+theme.workspace = true
+toml.workspace = true
+util.workspace = true
+
+[dev-dependencies]
+fs = { workspace = true, features = ["test-support"] }
+gpui = { workspace = true, features = ["test-support"] }
+language = { workspace = true, features = ["test-support"] }

crates/extension/src/extension_store.rs 🔗

@@ -0,0 +1,344 @@
+use anyhow::Result;
+use collections::{HashMap, HashSet};
+use fs::Fs;
+use futures::StreamExt as _;
+use gpui::{actions, AppContext, Context, Global, Model, ModelContext, Task};
+use language::{
+    LanguageConfig, LanguageMatcher, LanguageQueries, LanguageRegistry, QUERY_FILENAME_PREFIXES,
+};
+use parking_lot::RwLock;
+use serde::{Deserialize, Serialize};
+use std::{
+    ffi::OsStr,
+    path::{Path, PathBuf},
+    sync::Arc,
+    time::Duration,
+};
+use theme::ThemeRegistry;
+use util::{paths::EXTENSIONS_DIR, ResultExt};
+
+#[cfg(test)]
+mod extension_store_test;
+
+pub struct ExtensionStore {
+    manifest: Arc<RwLock<Manifest>>,
+    fs: Arc<dyn Fs>,
+    extensions_dir: PathBuf,
+    manifest_path: PathBuf,
+    language_registry: Arc<LanguageRegistry>,
+    theme_registry: Arc<ThemeRegistry>,
+    _watch_extensions_dir: Task<()>,
+}
+
+struct GlobalExtensionStore(Model<ExtensionStore>);
+
+impl Global for GlobalExtensionStore {}
+
+#[derive(Deserialize, Serialize, Default)]
+pub struct Manifest {
+    pub grammars: HashMap<String, GrammarManifestEntry>,
+    pub languages: HashMap<Arc<str>, LanguageManifestEntry>,
+    pub themes: HashMap<String, ThemeManifestEntry>,
+}
+
+#[derive(PartialEq, Eq, Debug, PartialOrd, Ord, Deserialize, Serialize)]
+pub struct GrammarManifestEntry {
+    extension: String,
+    path: PathBuf,
+}
+
+#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Deserialize, Serialize)]
+pub struct LanguageManifestEntry {
+    extension: String,
+    path: PathBuf,
+    matcher: LanguageMatcher,
+}
+
+#[derive(Clone, PartialEq, Debug, Deserialize, Serialize)]
+pub struct ThemeManifestEntry {
+    extension: String,
+    path: PathBuf,
+}
+
+actions!(zed, [ReloadExtensions]);
+
+pub fn init(
+    fs: Arc<fs::RealFs>,
+    language_registry: Arc<LanguageRegistry>,
+    theme_registry: Arc<ThemeRegistry>,
+    cx: &mut AppContext,
+) {
+    let store = cx.new_model(|cx| {
+        ExtensionStore::new(
+            EXTENSIONS_DIR.clone(),
+            fs.clone(),
+            language_registry.clone(),
+            theme_registry,
+            cx,
+        )
+    });
+
+    cx.on_action(|_: &ReloadExtensions, cx| {
+        let store = cx.global::<GlobalExtensionStore>().0.clone();
+        store
+            .update(cx, |store, cx| store.reload(cx))
+            .detach_and_log_err(cx);
+    });
+
+    cx.set_global(GlobalExtensionStore(store));
+}
+
+impl ExtensionStore {
+    pub fn new(
+        extensions_dir: PathBuf,
+        fs: Arc<dyn Fs>,
+        language_registry: Arc<LanguageRegistry>,
+        theme_registry: Arc<ThemeRegistry>,
+        cx: &mut ModelContext<Self>,
+    ) -> Self {
+        let mut this = Self {
+            manifest: Default::default(),
+            extensions_dir: extensions_dir.join("installed"),
+            manifest_path: extensions_dir.join("manifest.json"),
+            fs,
+            language_registry,
+            theme_registry,
+            _watch_extensions_dir: Task::ready(()),
+        };
+        this._watch_extensions_dir = this.watch_extensions_dir(cx);
+        this.load(cx);
+        this
+    }
+
+    pub fn load(&mut self, cx: &mut ModelContext<Self>) {
+        let (manifest_content, manifest_metadata, extensions_metadata) =
+            cx.background_executor().block(async {
+                futures::join!(
+                    self.fs.load(&self.manifest_path),
+                    self.fs.metadata(&self.manifest_path),
+                    self.fs.metadata(&self.extensions_dir),
+                )
+            });
+
+        if let Some(manifest_content) = manifest_content.log_err() {
+            if let Some(manifest) = serde_json::from_str(&manifest_content).log_err() {
+                self.manifest_updated(manifest, cx);
+            }
+        }
+
+        let should_reload = if let (Ok(Some(manifest_metadata)), Ok(Some(extensions_metadata))) =
+            (manifest_metadata, extensions_metadata)
+        {
+            extensions_metadata.mtime > manifest_metadata.mtime
+        } else {
+            true
+        };
+
+        if should_reload {
+            self.reload(cx).detach_and_log_err(cx);
+        }
+    }
+
+    fn manifest_updated(&mut self, manifest: Manifest, cx: &mut ModelContext<Self>) {
+        for (grammar_name, grammar) in &manifest.grammars {
+            let mut grammar_path = self.extensions_dir.clone();
+            grammar_path.extend([grammar.extension.as_ref(), grammar.path.as_path()]);
+            self.language_registry
+                .register_grammar(grammar_name.clone(), grammar_path);
+        }
+        for (language_name, language) in &manifest.languages {
+            let mut language_path = self.extensions_dir.clone();
+            language_path.extend([language.extension.as_ref(), language.path.as_path()]);
+            self.language_registry.register_extension(
+                language_path.into(),
+                language_name.clone(),
+                language.matcher.clone(),
+                load_plugin_queries,
+            );
+        }
+        let fs = self.fs.clone();
+        let root_dir = self.extensions_dir.clone();
+        let theme_registry = self.theme_registry.clone();
+        let themes = manifest.themes.clone();
+        cx.background_executor()
+            .spawn(async move {
+                for theme in themes.values() {
+                    let mut theme_path = root_dir.clone();
+                    theme_path.extend([theme.extension.as_ref(), theme.path.as_path()]);
+
+                    theme_registry
+                        .load_user_theme(&theme_path, fs.clone())
+                        .await
+                        .log_err();
+                }
+            })
+            .detach();
+        *self.manifest.write() = manifest;
+    }
+
+    fn watch_extensions_dir(&self, cx: &mut ModelContext<Self>) -> Task<()> {
+        let manifest = self.manifest.clone();
+        let fs = self.fs.clone();
+        let language_registry = self.language_registry.clone();
+        let extensions_dir = self.extensions_dir.clone();
+        cx.background_executor().spawn(async move {
+            let mut changed_languages = HashSet::default();
+            let mut events = fs.watch(&extensions_dir, Duration::from_millis(250)).await;
+            while let Some(events) = events.next().await {
+                changed_languages.clear();
+                let manifest = manifest.read();
+                for event in events {
+                    for (language_name, language) in &manifest.languages {
+                        let mut language_path = extensions_dir.clone();
+                        language_path
+                            .extend([language.extension.as_ref(), language.path.as_path()]);
+                        if event.path.starts_with(&language_path) || event.path == language_path {
+                            changed_languages.insert(language_name.clone());
+                        }
+                    }
+                }
+                language_registry.reload_languages(&changed_languages);
+            }
+        })
+    }
+
+    pub fn reload(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
+        let fs = self.fs.clone();
+        let extensions_dir = self.extensions_dir.clone();
+        let manifest_path = self.manifest_path.clone();
+        cx.spawn(|this, mut cx| async move {
+            let manifest = cx
+                .background_executor()
+                .spawn(async move {
+                    let mut manifest = Manifest::default();
+
+                    let mut extension_paths = fs.read_dir(&extensions_dir).await?;
+                    while let Some(extension_dir) = extension_paths.next().await {
+                        let extension_dir = extension_dir?;
+                        let Some(extension_name) =
+                            extension_dir.file_name().and_then(OsStr::to_str)
+                        else {
+                            continue;
+                        };
+
+                        if let Ok(mut grammar_paths) =
+                            fs.read_dir(&extension_dir.join("grammars")).await
+                        {
+                            while let Some(grammar_path) = grammar_paths.next().await {
+                                let grammar_path = grammar_path?;
+                                let Ok(relative_path) = grammar_path.strip_prefix(&extension_dir)
+                                else {
+                                    continue;
+                                };
+                                let Some(grammar_name) =
+                                    grammar_path.file_stem().and_then(OsStr::to_str)
+                                else {
+                                    continue;
+                                };
+
+                                manifest.grammars.insert(
+                                    grammar_name.into(),
+                                    GrammarManifestEntry {
+                                        extension: extension_name.into(),
+                                        path: relative_path.into(),
+                                    },
+                                );
+                            }
+                        }
+
+                        if let Ok(mut language_paths) =
+                            fs.read_dir(&extension_dir.join("languages")).await
+                        {
+                            while let Some(language_path) = language_paths.next().await {
+                                let language_path = language_path?;
+                                let Ok(relative_path) = language_path.strip_prefix(&extension_dir)
+                                else {
+                                    continue;
+                                };
+                                let config = fs.load(&language_path.join("config.toml")).await?;
+                                let config = ::toml::from_str::<LanguageConfig>(&config)?;
+
+                                manifest.languages.insert(
+                                    config.name.clone(),
+                                    LanguageManifestEntry {
+                                        extension: extension_name.into(),
+                                        path: relative_path.into(),
+                                        matcher: config.matcher,
+                                    },
+                                );
+                            }
+                        }
+
+                        if let Ok(mut theme_paths) =
+                            fs.read_dir(&extension_dir.join("themes")).await
+                        {
+                            while let Some(theme_path) = theme_paths.next().await {
+                                let theme_path = theme_path?;
+                                let Ok(relative_path) = theme_path.strip_prefix(&extension_dir)
+                                else {
+                                    continue;
+                                };
+
+                                let Some(theme_family) =
+                                    ThemeRegistry::read_user_theme(&theme_path, fs.clone())
+                                        .await
+                                        .log_err()
+                                else {
+                                    continue;
+                                };
+
+                                for theme in theme_family.themes {
+                                    let location = ThemeManifestEntry {
+                                        extension: extension_name.into(),
+                                        path: relative_path.into(),
+                                    };
+
+                                    manifest.themes.insert(theme.name, location);
+                                }
+                            }
+                        }
+                    }
+
+                    fs.save(
+                        &manifest_path,
+                        &serde_json::to_string_pretty(&manifest)?.as_str().into(),
+                        Default::default(),
+                    )
+                    .await?;
+
+                    anyhow::Ok(manifest)
+                })
+                .await?;
+            this.update(&mut cx, |this, cx| this.manifest_updated(manifest, cx))
+        })
+    }
+}
+
+fn load_plugin_queries(root_path: &Path) -> LanguageQueries {
+    let mut result = LanguageQueries::default();
+    if let Some(entries) = std::fs::read_dir(root_path).log_err() {
+        for entry in entries {
+            let Some(entry) = entry.log_err() else {
+                continue;
+            };
+            let path = entry.path();
+            if let Some(remainder) = path.strip_prefix(root_path).ok().and_then(|p| p.to_str()) {
+                if !remainder.ends_with(".scm") {
+                    continue;
+                }
+                for (name, query) in QUERY_FILENAME_PREFIXES {
+                    if remainder.starts_with(name) {
+                        if let Some(contents) = std::fs::read_to_string(&path).log_err() {
+                            match query(&mut result) {
+                                None => *query(&mut result) = Some(contents.into()),
+                                Some(r) => r.to_mut().push_str(contents.as_ref()),
+                            }
+                        }
+                        break;
+                    }
+                }
+            }
+        }
+    }
+    result
+}

crates/extension/src/extension_store_test.rs 🔗

@@ -0,0 +1,295 @@
+use crate::{
+    ExtensionStore, GrammarManifestEntry, LanguageManifestEntry, Manifest, ThemeManifestEntry,
+};
+use fs::FakeFs;
+use gpui::{Context, TestAppContext};
+use language::{LanguageMatcher, LanguageRegistry};
+use serde_json::json;
+use std::{path::PathBuf, sync::Arc};
+use theme::ThemeRegistry;
+
+#[gpui::test]
+async fn test_extension_store(cx: &mut TestAppContext) {
+    let fs = FakeFs::new(cx.executor());
+
+    fs.insert_tree(
+        "/the-extension-dir",
+        json!({
+            "installed": {
+                "zed-monokai": {
+                    "themes": {
+                        "monokai.json": r#"{
+                            "name": "Monokai",
+                            "author": "Someone",
+                            "themes": [
+                                {
+                                    "name": "Monokai Dark",
+                                    "appearance": "dark",
+                                    "style": {}
+                                },
+                                {
+                                    "name": "Monokai Light",
+                                    "appearance": "light",
+                                    "style": {}
+                                }
+                            ]
+                        }"#,
+                        "monokai-pro.json": r#"{
+                            "name": "Monokai Pro",
+                            "author": "Someone",
+                            "themes": [
+                                {
+                                    "name": "Monokai Pro Dark",
+                                    "appearance": "dark",
+                                    "style": {}
+                                },
+                                {
+                                    "name": "Monokai Pro Light",
+                                    "appearance": "light",
+                                    "style": {}
+                                }
+                            ]
+                        }"#,
+                    }
+                },
+                "zed-ruby": {
+                    "grammars": {
+                        "ruby.wasm": "",
+                        "embedded_template.wasm": "",
+                    },
+                    "languages": {
+                        "ruby": {
+                            "config.toml": r#"
+                                name = "Ruby"
+                                grammar = "ruby"
+                                path_suffixes = ["rb"]
+                            "#,
+                            "highlights.scm": "",
+                        },
+                        "erb": {
+                            "config.toml": r#"
+                                name = "ERB"
+                                grammar = "embedded_template"
+                                path_suffixes = ["erb"]
+                            "#,
+                            "highlights.scm": "",
+                        }
+                    },
+                }
+            }
+        }),
+    )
+    .await;
+
+    let mut expected_manifest = Manifest {
+        grammars: [
+            (
+                "embedded_template".into(),
+                GrammarManifestEntry {
+                    extension: "zed-ruby".into(),
+                    path: "grammars/embedded_template.wasm".into(),
+                },
+            ),
+            (
+                "ruby".into(),
+                GrammarManifestEntry {
+                    extension: "zed-ruby".into(),
+                    path: "grammars/ruby.wasm".into(),
+                },
+            ),
+        ]
+        .into_iter()
+        .collect(),
+        languages: [
+            (
+                "ERB".into(),
+                LanguageManifestEntry {
+                    extension: "zed-ruby".into(),
+                    path: "languages/erb".into(),
+                    matcher: LanguageMatcher {
+                        path_suffixes: vec!["erb".into()],
+                        first_line_pattern: None,
+                    },
+                },
+            ),
+            (
+                "Ruby".into(),
+                LanguageManifestEntry {
+                    extension: "zed-ruby".into(),
+                    path: "languages/ruby".into(),
+                    matcher: LanguageMatcher {
+                        path_suffixes: vec!["rb".into()],
+                        first_line_pattern: None,
+                    },
+                },
+            ),
+        ]
+        .into_iter()
+        .collect(),
+        themes: [
+            (
+                "Monokai Dark".into(),
+                ThemeManifestEntry {
+                    extension: "zed-monokai".into(),
+                    path: "themes/monokai.json".into(),
+                },
+            ),
+            (
+                "Monokai Light".into(),
+                ThemeManifestEntry {
+                    extension: "zed-monokai".into(),
+                    path: "themes/monokai.json".into(),
+                },
+            ),
+            (
+                "Monokai Pro Dark".into(),
+                ThemeManifestEntry {
+                    extension: "zed-monokai".into(),
+                    path: "themes/monokai-pro.json".into(),
+                },
+            ),
+            (
+                "Monokai Pro Light".into(),
+                ThemeManifestEntry {
+                    extension: "zed-monokai".into(),
+                    path: "themes/monokai-pro.json".into(),
+                },
+            ),
+        ]
+        .into_iter()
+        .collect(),
+    };
+
+    let language_registry = Arc::new(LanguageRegistry::test());
+    let theme_registry = Arc::new(ThemeRegistry::new(Box::new(())));
+
+    let store = cx.new_model(|cx| {
+        ExtensionStore::new(
+            PathBuf::from("/the-extension-dir"),
+            fs.clone(),
+            language_registry.clone(),
+            theme_registry.clone(),
+            cx,
+        )
+    });
+
+    cx.executor().run_until_parked();
+    store.read_with(cx, |store, _| {
+        let manifest = store.manifest.read();
+        assert_eq!(manifest.grammars, expected_manifest.grammars);
+        assert_eq!(manifest.languages, expected_manifest.languages);
+        assert_eq!(manifest.themes, expected_manifest.themes);
+
+        assert_eq!(
+            language_registry.language_names(),
+            ["ERB", "Plain Text", "Ruby"]
+        );
+        assert_eq!(
+            theme_registry.list_names(false),
+            [
+                "Monokai Dark",
+                "Monokai Light",
+                "Monokai Pro Dark",
+                "Monokai Pro Light",
+                "One Dark",
+            ]
+        );
+    });
+
+    fs.insert_tree(
+        "/the-extension-dir/installed/zed-gruvbox",
+        json!({
+            "themes": {
+                "gruvbox.json": r#"{
+                    "name": "Gruvbox",
+                    "author": "Someone Else",
+                    "themes": [
+                        {
+                            "name": "Gruvbox",
+                            "appearance": "dark",
+                            "style": {}
+                        }
+                    ]
+                }"#,
+            }
+        }),
+    )
+    .await;
+
+    expected_manifest.themes.insert(
+        "Gruvbox".into(),
+        ThemeManifestEntry {
+            extension: "zed-gruvbox".into(),
+            path: "themes/gruvbox.json".into(),
+        },
+    );
+
+    store
+        .update(cx, |store, cx| store.reload(cx))
+        .await
+        .unwrap();
+
+    cx.executor().run_until_parked();
+    store.read_with(cx, |store, _| {
+        let manifest = store.manifest.read();
+        assert_eq!(manifest.grammars, expected_manifest.grammars);
+        assert_eq!(manifest.languages, expected_manifest.languages);
+        assert_eq!(manifest.themes, expected_manifest.themes);
+
+        assert_eq!(
+            theme_registry.list_names(false),
+            [
+                "Gruvbox",
+                "Monokai Dark",
+                "Monokai Light",
+                "Monokai Pro Dark",
+                "Monokai Pro Light",
+                "One Dark",
+            ]
+        );
+    });
+
+    let prev_fs_metadata_call_count = fs.metadata_call_count();
+    let prev_fs_read_dir_call_count = fs.read_dir_call_count();
+
+    // Create new extension store, as if Zed were restarting.
+    drop(store);
+    let store = cx.new_model(|cx| {
+        ExtensionStore::new(
+            PathBuf::from("/the-extension-dir"),
+            fs.clone(),
+            language_registry.clone(),
+            theme_registry.clone(),
+            cx,
+        )
+    });
+
+    cx.executor().run_until_parked();
+    store.read_with(cx, |store, _| {
+        let manifest = store.manifest.read();
+        assert_eq!(manifest.grammars, expected_manifest.grammars);
+        assert_eq!(manifest.languages, expected_manifest.languages);
+        assert_eq!(manifest.themes, expected_manifest.themes);
+
+        assert_eq!(
+            language_registry.language_names(),
+            ["ERB", "Plain Text", "Ruby"]
+        );
+        assert_eq!(
+            theme_registry.list_names(false),
+            [
+                "Gruvbox",
+                "Monokai Dark",
+                "Monokai Light",
+                "Monokai Pro Dark",
+                "Monokai Pro Light",
+                "One Dark",
+            ]
+        );
+
+        // The on-disk manifest limits the number of FS calls that need to be made
+        // on startup.
+        assert_eq!(fs.read_dir_call_count(), prev_fs_read_dir_call_count);
+        assert_eq!(fs.metadata_call_count(), prev_fs_metadata_call_count + 2);
+    });
+}

crates/gpui/src/shared_string.rs 🔗

@@ -5,7 +5,7 @@ use util::arc_cow::ArcCow;
 
 /// A shared string is an immutable string that can be cheaply cloned in GPUI
 /// tasks. Essentially an abstraction over an `Arc<str>` and `&'static str`,
-#[derive(Deref, DerefMut, Eq, PartialEq, Hash, Clone)]
+#[derive(Deref, DerefMut, Eq, PartialEq, PartialOrd, Ord, Hash, Clone)]
 pub struct SharedString(ArcCow<'static, str>);
 
 impl Default for SharedString {

crates/language/Cargo.toml 🔗

@@ -52,6 +52,7 @@ smol.workspace = true
 sum_tree.workspace = true
 text.workspace = true
 theme.workspace = true
+toml.workspace = true
 tree-sitter-rust = { workspace = true, optional = true }
 tree-sitter-typescript = { workspace = true, optional = true }
 pulldown-cmark.workspace = true

crates/language/src/buffer_tests.rs 🔗

@@ -73,7 +73,10 @@ fn test_select_language() {
     registry.add(Arc::new(Language::new(
         LanguageConfig {
             name: "Rust".into(),
-            path_suffixes: vec!["rs".to_string()],
+            matcher: LanguageMatcher {
+                path_suffixes: vec!["rs".to_string()],
+                ..Default::default()
+            },
             ..Default::default()
         },
         Some(tree_sitter_rust::language()),
@@ -81,7 +84,10 @@ fn test_select_language() {
     registry.add(Arc::new(Language::new(
         LanguageConfig {
             name: "Make".into(),
-            path_suffixes: vec!["Makefile".to_string(), "mk".to_string()],
+            matcher: LanguageMatcher {
+                path_suffixes: vec!["Makefile".to_string(), "mk".to_string()],
+                ..Default::default()
+            },
             ..Default::default()
         },
         Some(tree_sitter_rust::language()),
@@ -2322,7 +2328,10 @@ fn ruby_lang() -> Language {
     Language::new(
         LanguageConfig {
             name: "Ruby".into(),
-            path_suffixes: vec!["rb".to_string()],
+            matcher: LanguageMatcher {
+                path_suffixes: vec!["rb".to_string()],
+                ..Default::default()
+            },
             line_comments: vec!["# ".into()],
             ..Default::default()
         },
@@ -2370,7 +2379,10 @@ fn erb_lang() -> Language {
     Language::new(
         LanguageConfig {
             name: "ERB".into(),
-            path_suffixes: vec!["erb".to_string()],
+            matcher: LanguageMatcher {
+                path_suffixes: vec!["erb".to_string()],
+                ..Default::default()
+            },
             block_comment: Some(("<%#".into(), "%>".into())),
             ..Default::default()
         },
@@ -2398,7 +2410,10 @@ fn rust_lang() -> Language {
     Language::new(
         LanguageConfig {
             name: "Rust".into(),
-            path_suffixes: vec!["rs".to_string()],
+            matcher: LanguageMatcher {
+                path_suffixes: vec!["rs".to_string()],
+                ..Default::default()
+            },
             ..Default::default()
         },
         Some(tree_sitter_rust::language()),
@@ -2450,7 +2465,10 @@ fn json_lang() -> Language {
     Language::new(
         LanguageConfig {
             name: "Json".into(),
-            path_suffixes: vec!["js".to_string()],
+            matcher: LanguageMatcher {
+                path_suffixes: vec!["js".to_string()],
+                ..Default::default()
+            },
             ..Default::default()
         },
         Some(tree_sitter_json::language()),

crates/language/src/language.rs 🔗

@@ -20,7 +20,7 @@ pub mod markdown;
 
 use anyhow::{anyhow, Context, Result};
 use async_trait::async_trait;
-use collections::{HashMap, HashSet};
+use collections::{hash_map, HashMap, HashSet};
 use futures::{
     channel::{mpsc, oneshot},
     future::Shared,
@@ -33,12 +33,13 @@ use lsp::{CodeActionKind, LanguageServerBinary};
 use parking_lot::{Mutex, RwLock};
 use postage::watch;
 use regex::Regex;
-use serde::{de, Deserialize, Deserializer};
+use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
 use serde_json::Value;
 use std::{
     any::Any,
     borrow::Cow,
     cell::RefCell,
+    ffi::OsStr,
     fmt::Debug,
     hash::Hash,
     mem,
@@ -392,14 +393,13 @@ pub struct LanguageConfig {
     /// Human-readable name of the language.
     pub name: Arc<str>,
     // The name of the grammar in a WASM bundle (experimental).
-    pub grammar_name: Option<Arc<str>>,
-    /// Given a list of `LanguageConfig`'s, the language of a file can be determined based on the path extension matching any of the `path_suffixes`.
-    pub path_suffixes: Vec<String>,
+    pub grammar: Option<Arc<str>>,
+    /// The criteria for matching this language to a given file.
+    #[serde(flatten)]
+    pub matcher: LanguageMatcher,
     /// List of bracket types in a language.
+    #[serde(default)]
     pub brackets: BracketPairConfig,
-    /// A regex pattern that determines whether the language should be assigned to a file or not.
-    #[serde(default, deserialize_with = "deserialize_regex")]
-    pub first_line_pattern: Option<Regex>,
     /// If set to true, auto indentation uses last non empty line to determine
     /// the indentation level for a new line.
     #[serde(default = "auto_indent_using_last_non_empty_line_default")]
@@ -443,6 +443,34 @@ pub struct LanguageConfig {
     pub prettier_parser_name: Option<String>,
 }
 
+#[derive(Clone, Debug, Serialize, Deserialize, Default)]
+pub struct LanguageMatcher {
+    /// Given a list of `LanguageConfig`'s, the language of a file can be determined based on the path extension matching any of the `path_suffixes`.
+    #[serde(default)]
+    pub path_suffixes: Vec<String>,
+    /// A regex pattern that determines whether the language should be assigned to a file or not.
+    #[serde(
+        default,
+        serialize_with = "serialize_regex",
+        deserialize_with = "deserialize_regex"
+    )]
+    pub first_line_pattern: Option<Regex>,
+}
+
+pub const QUERY_FILENAME_PREFIXES: &[(
+    &str,
+    fn(&mut LanguageQueries) -> &mut Option<Cow<'static, str>>,
+)] = &[
+    ("highlights", |q| &mut q.highlights),
+    ("brackets", |q| &mut q.brackets),
+    ("outline", |q| &mut q.outline),
+    ("indents", |q| &mut q.indents),
+    ("embedding", |q| &mut q.embedding),
+    ("injections", |q| &mut q.injections),
+    ("overrides", |q| &mut q.overrides),
+    ("redactions", |q| &mut q.redactions),
+];
+
 /// Tree-sitter language queries for a given language.
 #[derive(Debug, Default)]
 pub struct LanguageQueries {
@@ -506,11 +534,10 @@ impl Default for LanguageConfig {
     fn default() -> Self {
         Self {
             name: "".into(),
-            grammar_name: None,
-            path_suffixes: Default::default(),
+            grammar: None,
+            matcher: LanguageMatcher::default(),
             brackets: Default::default(),
             auto_indent_using_last_non_empty_line: auto_indent_using_last_non_empty_line_default(),
-            first_line_pattern: Default::default(),
             increase_indent_pattern: Default::default(),
             decrease_indent_pattern: Default::default(),
             autoclose_before: Default::default(),
@@ -538,6 +565,16 @@ fn deserialize_regex<'de, D: Deserializer<'de>>(d: D) -> Result<Option<Regex>, D
     }
 }
 
+fn serialize_regex<S>(regex: &Option<Regex>, serializer: S) -> Result<S::Ok, S::Error>
+where
+    S: Serializer,
+{
+    match regex {
+        Some(regex) => serializer.serialize_str(regex.as_str()),
+        None => serializer.serialize_none(),
+    }
+}
+
 #[doc(hidden)]
 #[cfg(any(test, feature = "test-support"))]
 pub struct FakeLspAdapter {
@@ -702,22 +739,29 @@ type AvailableLanguageId = usize;
 #[derive(Clone)]
 struct AvailableLanguage {
     id: AvailableLanguageId,
-    config: LanguageConfig,
-    grammar: AvailableGrammar,
+    name: Arc<str>,
+    source: AvailableLanguageSource,
     lsp_adapters: Vec<Arc<dyn LspAdapter>>,
     loaded: bool,
 }
 
-#[derive(Clone)]
 enum AvailableGrammar {
-    Native {
-        grammar: tree_sitter::Language,
+    Loaded(tree_sitter::Language),
+    Loading(Vec<oneshot::Sender<Result<tree_sitter::Language>>>),
+    Unloaded(PathBuf),
+}
+
+#[derive(Clone)]
+enum AvailableLanguageSource {
+    BuiltIn {
         asset_dir: &'static str,
         get_queries: fn(&str) -> LanguageQueries,
+        config: LanguageConfig,
     },
-    Wasm {
+    Extension {
         path: Arc<Path>,
         get_queries: fn(&Path) -> LanguageQueries,
+        matcher: LanguageMatcher,
     },
 }
 
@@ -737,6 +781,7 @@ struct LanguageRegistryState {
     next_language_server_id: usize,
     languages: Vec<Arc<Language>>,
     available_languages: Vec<AvailableLanguage>,
+    grammars: HashMap<String, AvailableGrammar>,
     next_available_language_id: AvailableLanguageId,
     loading_languages: HashMap<AvailableLanguageId, Vec<oneshot::Sender<Result<Arc<Language>>>>>,
     subscription: (watch::Sender<()>, watch::Receiver<()>),
@@ -758,6 +803,7 @@ impl LanguageRegistry {
                 next_language_server_id: 0,
                 languages: vec![PLAIN_TEXT.clone()],
                 available_languages: Default::default(),
+                grammars: Default::default(),
                 next_available_language_id: 0,
                 loading_languages: Default::default(),
                 subscription: watch::channel(),
@@ -787,20 +833,24 @@ impl LanguageRegistry {
         self.state.write().reload();
     }
 
+    /// Clear out the given languages and reload them from scratch.
+    pub fn reload_languages(&self, languages: &HashSet<Arc<str>>) {
+        self.state.write().reload_languages(languages);
+    }
+
     pub fn register(
         &self,
         asset_dir: &'static str,
         config: LanguageConfig,
-        grammar: tree_sitter::Language,
         lsp_adapters: Vec<Arc<dyn LspAdapter>>,
         get_queries: fn(&str) -> LanguageQueries,
     ) {
         let state = &mut *self.state.write();
         state.available_languages.push(AvailableLanguage {
             id: post_inc(&mut state.next_available_language_id),
-            config,
-            grammar: AvailableGrammar::Native {
-                grammar,
+            name: config.name.clone(),
+            source: AvailableLanguageSource::BuiltIn {
+                config,
                 get_queries,
                 asset_dir,
             },
@@ -809,28 +859,63 @@ impl LanguageRegistry {
         });
     }
 
-    pub fn register_wasm(
+    pub fn register_extension(
         &self,
         path: Arc<Path>,
-        config: LanguageConfig,
+        name: Arc<str>,
+        matcher: LanguageMatcher,
         get_queries: fn(&Path) -> LanguageQueries,
     ) {
         let state = &mut *self.state.write();
+        let source = AvailableLanguageSource::Extension {
+            path,
+            get_queries,
+            matcher,
+        };
+        for existing_language in &mut state.available_languages {
+            if existing_language.name == name
+                && matches!(
+                    existing_language.source,
+                    AvailableLanguageSource::Extension { .. }
+                )
+            {
+                existing_language.source = source;
+                return;
+            }
+        }
         state.available_languages.push(AvailableLanguage {
             id: post_inc(&mut state.next_available_language_id),
-            config,
-            grammar: AvailableGrammar::Wasm { path, get_queries },
+            name,
+            source,
             lsp_adapters: Vec::new(),
             loaded: false,
         });
     }
 
+    pub fn add_grammars(
+        &self,
+        grammars: impl IntoIterator<Item = (impl Into<String>, tree_sitter::Language)>,
+    ) {
+        self.state.write().grammars.extend(
+            grammars
+                .into_iter()
+                .map(|(name, grammar)| (name.into(), AvailableGrammar::Loaded(grammar))),
+        );
+    }
+
+    pub fn register_grammar(&self, name: String, path: PathBuf) {
+        self.state
+            .write()
+            .grammars
+            .insert(name, AvailableGrammar::Unloaded(path));
+    }
+
     pub fn language_names(&self) -> Vec<String> {
         let state = self.state.read();
         let mut result = state
             .available_languages
             .iter()
-            .filter_map(|l| l.loaded.not().then_some(l.config.name.to_string()))
+            .filter_map(|l| l.loaded.not().then_some(l.name.to_string()))
             .chain(state.languages.iter().map(|l| l.config.name.to_string()))
             .collect::<Vec<_>>();
         result.sort_unstable_by_key(|language_name| language_name.to_lowercase());
@@ -873,7 +958,7 @@ impl LanguageRegistry {
         name: &str,
     ) -> UnwrapFuture<oneshot::Receiver<Result<Arc<Language>>>> {
         let name = UniCase::new(name);
-        self.get_or_load_language(|config| UniCase::new(config.name.as_ref()) == name)
+        self.get_or_load_language(|language_name, _| UniCase::new(language_name) == name)
     }
 
     pub fn language_for_name_or_extension(
@@ -881,8 +966,8 @@ impl LanguageRegistry {
         string: &str,
     ) -> UnwrapFuture<oneshot::Receiver<Result<Arc<Language>>>> {
         let string = UniCase::new(string);
-        self.get_or_load_language(|config| {
-            UniCase::new(config.name.as_ref()) == string
+        self.get_or_load_language(|name, config| {
+            UniCase::new(name) == string
                 || config
                     .path_suffixes
                     .iter()
@@ -899,7 +984,7 @@ impl LanguageRegistry {
         let filename = path.file_name().and_then(|name| name.to_str());
         let extension = path.extension_or_hidden_file_name();
         let path_suffixes = [extension, filename];
-        self.get_or_load_language(|config| {
+        self.get_or_load_language(|_, config| {
             let path_matches = config
                 .path_suffixes
                 .iter()
@@ -919,7 +1004,7 @@ impl LanguageRegistry {
 
     fn get_or_load_language(
         self: &Arc<Self>,
-        callback: impl Fn(&LanguageConfig) -> bool,
+        callback: impl Fn(&str, &LanguageMatcher) -> bool,
     ) -> UnwrapFuture<oneshot::Receiver<Result<Arc<Language>>>> {
         let (tx, rx) = oneshot::channel();
 
@@ -927,52 +1012,60 @@ impl LanguageRegistry {
         if let Some(language) = state
             .languages
             .iter()
-            .find(|language| callback(&language.config))
+            .find(|language| callback(language.config.name.as_ref(), &language.config.matcher))
         {
             let _ = tx.send(Ok(language.clone()));
         } else if let Some(executor) = self.executor.clone() {
             if let Some(language) = state
                 .available_languages
                 .iter()
-                .find(|l| !l.loaded && callback(&l.config))
+                .rfind(|l| {
+                    !l.loaded
+                        && match &l.source {
+                            AvailableLanguageSource::BuiltIn { config, .. } => {
+                                callback(l.name.as_ref(), &config.matcher)
+                            }
+                            AvailableLanguageSource::Extension { matcher, .. } => {
+                                callback(l.name.as_ref(), &matcher)
+                            }
+                        }
+                })
                 .cloned()
             {
-                let txs = state
-                    .loading_languages
-                    .entry(language.id)
-                    .or_insert_with(|| {
+                match state.loading_languages.entry(language.id) {
+                    hash_map::Entry::Occupied(mut entry) => entry.get_mut().push(tx),
+                    hash_map::Entry::Vacant(entry) => {
                         let this = self.clone();
                         executor
                             .spawn(async move {
                                 let id = language.id;
-                                let name = language.config.name.clone();
+                                let name = language.name.clone();
                                 let language = async {
-                                    let (grammar, queries) = match language.grammar {
-                                        AvailableGrammar::Native {
-                                            grammar,
+                                    let (config, queries) = match language.source {
+                                        AvailableLanguageSource::BuiltIn {
                                             asset_dir,
                                             get_queries,
-                                        } => (grammar, (get_queries)(asset_dir)),
-                                        AvailableGrammar::Wasm { path, get_queries } => {
-                                            let grammar_name =
-                                                &language.config.grammar_name.as_ref().ok_or_else(
-                                                    || anyhow!("missing grammar name"),
-                                                )?;
-                                            let mut wasm_path = path.join(grammar_name.as_ref());
-                                            wasm_path.set_extension("wasm");
-                                            let wasm_bytes = std::fs::read(&wasm_path)?;
-                                            let grammar = PARSER.with(|parser| {
-                                                let mut parser = parser.borrow_mut();
-                                                let mut store = parser.take_wasm_store().unwrap();
-                                                let grammar =
-                                                    store.load_language(&grammar_name, &wasm_bytes);
-                                                parser.set_wasm_store(store).unwrap();
-                                                grammar
-                                            })?;
-                                            (grammar, get_queries(path.as_ref()))
+                                            config,
+                                        } => (config, (get_queries)(asset_dir)),
+                                        AvailableLanguageSource::Extension {
+                                            path,
+                                            get_queries,
+                                            ..
+                                        } => {
+                                            let config = std::fs::read(path.join("config.toml"));
+                                            let config: LanguageConfig =
+                                                ::toml::from_slice(&config?)?;
+                                            (config, get_queries(path.as_ref()))
                                         }
                                     };
-                                    Language::new(language.config, Some(grammar))
+
+                                    let grammar = if let Some(grammar) = config.grammar.clone() {
+                                        Some(this.get_or_load_grammar(grammar).await?)
+                                    } else {
+                                        None
+                                    };
+
+                                    Language::new(config, grammar)
                                         .with_lsp_adapters(language.lsp_adapters)
                                         .await
                                         .with_queries(queries)
@@ -1009,10 +1102,9 @@ impl LanguageRegistry {
                                 };
                             })
                             .detach();
-
-                        Vec::new()
-                    });
-                txs.push(tx);
+                        entry.insert(vec![tx]);
+                    }
+                }
             } else {
                 let _ = tx.send(Err(anyhow!("language not found")));
             }
@@ -1023,6 +1115,65 @@ impl LanguageRegistry {
         rx.unwrap()
     }
 
+    fn get_or_load_grammar(
+        self: &Arc<Self>,
+        name: Arc<str>,
+    ) -> UnwrapFuture<oneshot::Receiver<Result<tree_sitter::Language>>> {
+        let (tx, rx) = oneshot::channel();
+        let mut state = self.state.write();
+
+        if let Some(grammar) = state.grammars.get_mut(name.as_ref()) {
+            match grammar {
+                AvailableGrammar::Loaded(grammar) => {
+                    tx.send(Ok(grammar.clone())).ok();
+                }
+                AvailableGrammar::Loading(txs) => {
+                    txs.push(tx);
+                }
+                AvailableGrammar::Unloaded(wasm_path) => {
+                    if let Some(executor) = &self.executor {
+                        let this = self.clone();
+                        let wasm_path = wasm_path.clone();
+                        executor
+                            .spawn(async move {
+                                let wasm_bytes = std::fs::read(&wasm_path)?;
+                                let grammar_name = wasm_path
+                                    .file_stem()
+                                    .and_then(OsStr::to_str)
+                                    .ok_or_else(|| anyhow!("invalid grammar filename"))?;
+                                let grammar = PARSER.with(|parser| {
+                                    let mut parser = parser.borrow_mut();
+                                    let mut store = parser.take_wasm_store().unwrap();
+                                    let grammar = store.load_language(&grammar_name, &wasm_bytes);
+                                    parser.set_wasm_store(store).unwrap();
+                                    grammar
+                                })?;
+
+                                if let Some(AvailableGrammar::Loading(txs)) =
+                                    this.state.write().grammars.insert(
+                                        name.to_string(),
+                                        AvailableGrammar::Loaded(grammar.clone()),
+                                    )
+                                {
+                                    for tx in txs {
+                                        tx.send(Ok(grammar.clone())).ok();
+                                    }
+                                }
+
+                                anyhow::Ok(())
+                            })
+                            .detach();
+                        *grammar = AvailableGrammar::Loading(vec![tx]);
+                    }
+                }
+            }
+        } else {
+            tx.send(Err(anyhow!("no such grammar {}", name))).ok();
+        }
+
+        rx.unwrap()
+    }
+
     pub fn to_vec(&self) -> Vec<Arc<Language>> {
         self.state.read().languages.iter().cloned().collect()
     }
@@ -1206,6 +1357,19 @@ impl LanguageRegistryState {
         *self.subscription.0.borrow_mut() = ();
     }
 
+    fn reload_languages(&mut self, languages: &HashSet<Arc<str>>) {
+        self.languages
+            .retain(|language| !languages.contains(&language.config.name));
+        self.version += 1;
+        self.reload_count += 1;
+        for language in &mut self.available_languages {
+            if languages.contains(&language.name) {
+                language.loaded = false;
+            }
+        }
+        *self.subscription.0.borrow_mut() = ();
+    }
+
     /// Mark the given language a having been loaded, so that the
     /// language registry won't try to load it again.
     fn mark_language_loaded(&mut self, id: AvailableLanguageId) {
@@ -1720,7 +1884,7 @@ impl Language {
     }
 
     pub fn path_suffixes(&self) -> &[String] {
-        &self.config.path_suffixes
+        &self.config.matcher.path_suffixes
     }
 
     pub fn should_autoclose_before(&self, c: char) -> bool {
@@ -1911,6 +2075,33 @@ impl CodeLabel {
     }
 }
 
+impl Ord for LanguageMatcher {
+    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
+        self.path_suffixes.cmp(&other.path_suffixes).then_with(|| {
+            self.first_line_pattern
+                .as_ref()
+                .map(Regex::as_str)
+                .cmp(&other.first_line_pattern.as_ref().map(Regex::as_str))
+        })
+    }
+}
+
+impl PartialOrd for LanguageMatcher {
+    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
+        Some(self.cmp(other))
+    }
+}
+
+impl Eq for LanguageMatcher {}
+
+impl PartialEq for LanguageMatcher {
+    fn eq(&self, other: &Self) -> bool {
+        self.path_suffixes == other.path_suffixes
+            && self.first_line_pattern.as_ref().map(Regex::as_str)
+                == other.first_line_pattern.as_ref().map(Regex::as_str)
+    }
+}
+
 #[cfg(any(test, feature = "test-support"))]
 impl Default for FakeLspAdapter {
     fn default() -> Self {
@@ -2034,11 +2225,12 @@ mod tests {
             "/javascript",
             LanguageConfig {
                 name: "JavaScript".into(),
-                path_suffixes: vec!["js".into()],
-                first_line_pattern: Some(Regex::new(r"\bnode\b").unwrap()),
+                matcher: LanguageMatcher {
+                    path_suffixes: vec!["js".into()],
+                    first_line_pattern: Some(Regex::new(r"\bnode\b").unwrap()),
+                },
                 ..Default::default()
             },
-            tree_sitter_typescript::language_tsx(),
             vec![],
             |_| Default::default(),
         );
@@ -2067,14 +2259,21 @@ mod tests {
         let mut languages = LanguageRegistry::test();
         languages.set_executor(cx.executor());
         let languages = Arc::new(languages);
+        languages.add_grammars([
+            ("json", tree_sitter_json::language()),
+            ("rust", tree_sitter_rust::language()),
+        ]);
         languages.register(
             "/JSON",
             LanguageConfig {
                 name: "JSON".into(),
-                path_suffixes: vec!["json".into()],
+                grammar: Some("json".into()),
+                matcher: LanguageMatcher {
+                    path_suffixes: vec!["json".into()],
+                    ..Default::default()
+                },
                 ..Default::default()
             },
-            tree_sitter_json::language(),
             vec![],
             |_| Default::default(),
         );
@@ -2082,10 +2281,13 @@ mod tests {
             "/rust",
             LanguageConfig {
                 name: "Rust".into(),
-                path_suffixes: vec!["rs".into()],
+                grammar: Some("rust".into()),
+                matcher: LanguageMatcher {
+                    path_suffixes: vec!["rs".into()],
+                    ..Default::default()
+                },
                 ..Default::default()
             },
-            tree_sitter_rust::language(),
             vec![],
             |_| Default::default(),
         );

crates/language/src/syntax_map/syntax_map_tests.rs 🔗

@@ -1,5 +1,5 @@
 use super::*;
-use crate::LanguageConfig;
+use crate::{LanguageConfig, LanguageMatcher};
 use rand::rngs::StdRng;
 use std::{env, ops::Range, sync::Arc};
 use text::{Buffer, BufferId};
@@ -1092,7 +1092,10 @@ fn html_lang() -> Language {
     Language::new(
         LanguageConfig {
             name: "HTML".into(),
-            path_suffixes: vec!["html".to_string()],
+            matcher: LanguageMatcher {
+                path_suffixes: vec!["html".to_string()],
+                ..Default::default()
+            },
             ..Default::default()
         },
         Some(tree_sitter_html::language()),
@@ -1111,7 +1114,10 @@ fn ruby_lang() -> Language {
     Language::new(
         LanguageConfig {
             name: "Ruby".into(),
-            path_suffixes: vec!["rb".to_string()],
+            matcher: LanguageMatcher {
+                path_suffixes: vec!["rb".to_string()],
+                ..Default::default()
+            },
             ..Default::default()
         },
         Some(tree_sitter_ruby::language()),
@@ -1130,7 +1136,10 @@ fn erb_lang() -> Language {
     Language::new(
         LanguageConfig {
             name: "ERB".into(),
-            path_suffixes: vec!["erb".to_string()],
+            matcher: LanguageMatcher {
+                path_suffixes: vec!["erb".to_string()],
+                ..Default::default()
+            },
             ..Default::default()
         },
         Some(tree_sitter_embedded_template::language()),
@@ -1163,7 +1172,10 @@ fn rust_lang() -> Language {
     Language::new(
         LanguageConfig {
             name: "Rust".into(),
-            path_suffixes: vec!["rs".to_string()],
+            matcher: LanguageMatcher {
+                path_suffixes: vec!["rs".to_string()],
+                ..Default::default()
+            },
             ..Default::default()
         },
         Some(tree_sitter_rust::language()),
@@ -1189,7 +1201,10 @@ fn markdown_lang() -> Language {
     Language::new(
         LanguageConfig {
             name: "Markdown".into(),
-            path_suffixes: vec!["md".into()],
+            matcher: LanguageMatcher {
+                path_suffixes: vec!["md".into()],
+                ..Default::default()
+            },
             ..Default::default()
         },
         Some(tree_sitter_markdown::language()),
@@ -1209,7 +1224,10 @@ fn elixir_lang() -> Language {
     Language::new(
         LanguageConfig {
             name: "Elixir".into(),
-            path_suffixes: vec!["ex".into()],
+            matcher: LanguageMatcher {
+                path_suffixes: vec!["ex".into()],
+                ..Default::default()
+            },
             ..Default::default()
         },
         Some(tree_sitter_elixir::language()),
@@ -1226,7 +1244,10 @@ fn heex_lang() -> Language {
     Language::new(
         LanguageConfig {
             name: "HEEx".into(),
-            path_suffixes: vec!["heex".into()],
+            matcher: LanguageMatcher {
+                path_suffixes: vec!["heex".into()],
+                ..Default::default()
+            },
             ..Default::default()
         },
         Some(tree_sitter_heex::language()),

crates/language_tools/src/lsp_log_tests.rs 🔗

@@ -5,7 +5,9 @@ use crate::lsp_log::LogMenuItem;
 use super::*;
 use futures::StreamExt;
 use gpui::{Context, TestAppContext, VisualTestContext};
-use language::{tree_sitter_rust, FakeLspAdapter, Language, LanguageConfig, LanguageServerName};
+use language::{
+    tree_sitter_rust, FakeLspAdapter, Language, LanguageConfig, LanguageMatcher, LanguageServerName,
+};
 use project::{FakeFs, Project};
 use serde_json::json;
 use settings::SettingsStore;
@@ -21,7 +23,10 @@ async fn test_lsp_logs(cx: &mut TestAppContext) {
     let mut rust_language = Language::new(
         LanguageConfig {
             name: "Rust".into(),
-            path_suffixes: vec!["rs".to_string()],
+            matcher: LanguageMatcher {
+                path_suffixes: vec!["rs".to_string()],
+                ..Default::default()
+            },
             ..Default::default()
         },
         Some(tree_sitter_rust::language()),

crates/project/src/project_tests.rs 🔗

@@ -5,7 +5,7 @@ use gpui::AppContext;
 use language::{
     language_settings::{AllLanguageSettings, LanguageSettingsContent},
     tree_sitter_rust, tree_sitter_typescript, Diagnostic, FakeLspAdapter, LanguageConfig,
-    LineEnding, OffsetRangeExt, Point, ToPoint,
+    LanguageMatcher, LineEnding, OffsetRangeExt, Point, ToPoint,
 };
 use lsp::Url;
 use parking_lot::Mutex;
@@ -149,7 +149,10 @@ async fn test_managing_language_servers(cx: &mut gpui::TestAppContext) {
     let mut rust_language = Language::new(
         LanguageConfig {
             name: "Rust".into(),
-            path_suffixes: vec!["rs".to_string()],
+            matcher: LanguageMatcher {
+                path_suffixes: vec!["rs".to_string()],
+                ..Default::default()
+            },
             ..Default::default()
         },
         Some(tree_sitter_rust::language()),
@@ -157,7 +160,10 @@ async fn test_managing_language_servers(cx: &mut gpui::TestAppContext) {
     let mut json_language = Language::new(
         LanguageConfig {
             name: "JSON".into(),
-            path_suffixes: vec!["json".to_string()],
+            matcher: LanguageMatcher {
+                path_suffixes: vec!["json".to_string()],
+                ..Default::default()
+            },
             ..Default::default()
         },
         None,
@@ -535,7 +541,10 @@ async fn test_reporting_fs_changes_to_language_servers(cx: &mut gpui::TestAppCon
     let mut language = Language::new(
         LanguageConfig {
             name: "Rust".into(),
-            path_suffixes: vec!["rs".to_string()],
+            matcher: LanguageMatcher {
+                path_suffixes: vec!["rs".to_string()],
+                ..Default::default()
+            },
             ..Default::default()
         },
         Some(tree_sitter_rust::language()),
@@ -970,7 +979,10 @@ async fn test_disk_based_diagnostics_progress(cx: &mut gpui::TestAppContext) {
     let mut language = Language::new(
         LanguageConfig {
             name: "Rust".into(),
-            path_suffixes: vec!["rs".to_string()],
+            matcher: LanguageMatcher {
+                path_suffixes: vec!["rs".to_string()],
+                ..Default::default()
+            },
             ..Default::default()
         },
         Some(tree_sitter_rust::language()),
@@ -1102,7 +1114,10 @@ async fn test_restarting_server_with_diagnostics_running(cx: &mut gpui::TestAppC
     let progress_token = "the-progress-token";
     let mut language = Language::new(
         LanguageConfig {
-            path_suffixes: vec!["rs".to_string()],
+            matcher: LanguageMatcher {
+                path_suffixes: vec!["rs".to_string()],
+                ..Default::default()
+            },
             ..Default::default()
         },
         None,
@@ -1183,7 +1198,10 @@ async fn test_restarting_server_with_diagnostics_published(cx: &mut gpui::TestAp
 
     let mut language = Language::new(
         LanguageConfig {
-            path_suffixes: vec!["rs".to_string()],
+            matcher: LanguageMatcher {
+                path_suffixes: vec!["rs".to_string()],
+                ..Default::default()
+            },
             ..Default::default()
         },
         None,
@@ -1272,7 +1290,10 @@ async fn test_restarted_server_reporting_invalid_buffer_version(cx: &mut gpui::T
 
     let mut language = Language::new(
         LanguageConfig {
-            path_suffixes: vec!["rs".to_string()],
+            matcher: LanguageMatcher {
+                path_suffixes: vec!["rs".to_string()],
+                ..Default::default()
+            },
             ..Default::default()
         },
         None,
@@ -1322,7 +1343,10 @@ async fn test_toggling_enable_language_server(cx: &mut gpui::TestAppContext) {
     let mut rust = Language::new(
         LanguageConfig {
             name: Arc::from("Rust"),
-            path_suffixes: vec!["rs".to_string()],
+            matcher: LanguageMatcher {
+                path_suffixes: vec!["rs".to_string()],
+                ..Default::default()
+            },
             ..Default::default()
         },
         None,
@@ -1336,7 +1360,10 @@ async fn test_toggling_enable_language_server(cx: &mut gpui::TestAppContext) {
     let mut js = Language::new(
         LanguageConfig {
             name: Arc::from("JavaScript"),
-            path_suffixes: vec!["js".to_string()],
+            matcher: LanguageMatcher {
+                path_suffixes: vec!["js".to_string()],
+                ..Default::default()
+            },
             ..Default::default()
         },
         None,
@@ -1451,7 +1478,10 @@ async fn test_transforming_diagnostics(cx: &mut gpui::TestAppContext) {
     let mut language = Language::new(
         LanguageConfig {
             name: "Rust".into(),
-            path_suffixes: vec!["rs".to_string()],
+            matcher: LanguageMatcher {
+                path_suffixes: vec!["rs".to_string()],
+                ..Default::default()
+            },
             ..Default::default()
         },
         Some(tree_sitter_rust::language()),
@@ -1862,7 +1892,10 @@ async fn test_edits_from_lsp2_with_past_version(cx: &mut gpui::TestAppContext) {
     let mut language = Language::new(
         LanguageConfig {
             name: "Rust".into(),
-            path_suffixes: vec!["rs".to_string()],
+            matcher: LanguageMatcher {
+                path_suffixes: vec!["rs".to_string()],
+                ..Default::default()
+            },
             ..Default::default()
         },
         Some(tree_sitter_rust::language()),
@@ -2249,7 +2282,10 @@ async fn test_definition(cx: &mut gpui::TestAppContext) {
     let mut language = Language::new(
         LanguageConfig {
             name: "Rust".into(),
-            path_suffixes: vec!["rs".to_string()],
+            matcher: LanguageMatcher {
+                path_suffixes: vec!["rs".to_string()],
+                ..Default::default()
+            },
             ..Default::default()
         },
         Some(tree_sitter_rust::language()),
@@ -2350,7 +2386,10 @@ async fn test_completions_without_edit_ranges(cx: &mut gpui::TestAppContext) {
     let mut language = Language::new(
         LanguageConfig {
             name: "TypeScript".into(),
-            path_suffixes: vec!["ts".to_string()],
+            matcher: LanguageMatcher {
+                path_suffixes: vec!["ts".to_string()],
+                ..Default::default()
+            },
             ..Default::default()
         },
         Some(tree_sitter_typescript::language_typescript()),
@@ -2447,7 +2486,10 @@ async fn test_completions_with_carriage_returns(cx: &mut gpui::TestAppContext) {
     let mut language = Language::new(
         LanguageConfig {
             name: "TypeScript".into(),
-            path_suffixes: vec!["ts".to_string()],
+            matcher: LanguageMatcher {
+                path_suffixes: vec!["ts".to_string()],
+                ..Default::default()
+            },
             ..Default::default()
         },
         Some(tree_sitter_typescript::language_typescript()),
@@ -2513,7 +2555,10 @@ async fn test_apply_code_actions_with_commands(cx: &mut gpui::TestAppContext) {
     let mut language = Language::new(
         LanguageConfig {
             name: "TypeScript".into(),
-            path_suffixes: vec!["ts".to_string()],
+            matcher: LanguageMatcher {
+                path_suffixes: vec!["ts".to_string()],
+                ..Default::default()
+            },
             ..Default::default()
         },
         None,
@@ -2816,14 +2861,18 @@ async fn test_save_as(cx: &mut gpui::TestAppContext) {
     let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await;
 
     let languages = project.update(cx, |project, _| project.languages().clone());
+    languages.add_grammars([("rust", tree_sitter_rust::language())]);
     languages.register(
         "/some/path",
         LanguageConfig {
             name: "Rust".into(),
-            path_suffixes: vec!["rs".into()],
+            grammar: Some("rust".into()),
+            matcher: LanguageMatcher {
+                path_suffixes: vec!["rs".into()],
+                ..Default::default()
+            },
             ..Default::default()
         },
-        tree_sitter_rust::language(),
         vec![],
         |_| Default::default(),
     );
@@ -3649,7 +3698,10 @@ async fn test_rename(cx: &mut gpui::TestAppContext) {
     let mut language = Language::new(
         LanguageConfig {
             name: "Rust".into(),
-            path_suffixes: vec!["rs".to_string()],
+            matcher: LanguageMatcher {
+                path_suffixes: vec!["rs".to_string()],
+                ..Default::default()
+            },
             ..Default::default()
         },
         Some(tree_sitter_rust::language()),

crates/project_symbols/src/project_symbols.rs 🔗

@@ -260,7 +260,7 @@ mod tests {
     use super::*;
     use futures::StreamExt;
     use gpui::{TestAppContext, VisualContext};
-    use language::{FakeLspAdapter, Language, LanguageConfig};
+    use language::{FakeLspAdapter, Language, LanguageConfig, LanguageMatcher};
     use project::FakeFs;
     use serde_json::json;
     use settings::SettingsStore;
@@ -273,7 +273,10 @@ mod tests {
         let mut language = Language::new(
             LanguageConfig {
                 name: "Rust".into(),
-                path_suffixes: vec!["rs".to_string()],
+                matcher: LanguageMatcher {
+                    path_suffixes: vec!["rs".to_string()],
+                    ..Default::default()
+                },
                 ..Default::default()
             },
             None,

crates/semantic_index/src/semantic_index_tests.rs 🔗

@@ -7,7 +7,7 @@ use crate::{
 use ai::test::FakeEmbeddingProvider;
 
 use gpui::{Task, TestAppContext};
-use language::{Language, LanguageConfig, LanguageRegistry, ToOffset};
+use language::{Language, LanguageConfig, LanguageMatcher, LanguageRegistry, ToOffset};
 use parking_lot::Mutex;
 use pretty_assertions::assert_eq;
 use project::{project_settings::ProjectSettings, FakeFs, Fs, Project};
@@ -1251,7 +1251,10 @@ fn js_lang() -> Arc<Language> {
         Language::new(
             LanguageConfig {
                 name: "Javascript".into(),
-                path_suffixes: vec!["js".into()],
+                matcher: LanguageMatcher {
+                    path_suffixes: vec!["js".into()],
+                    ..Default::default()
+                },
                 ..Default::default()
             },
             Some(tree_sitter_typescript::language_tsx()),
@@ -1343,7 +1346,10 @@ fn rust_lang() -> Arc<Language> {
         Language::new(
             LanguageConfig {
                 name: "Rust".into(),
-                path_suffixes: vec!["rs".into()],
+                matcher: LanguageMatcher {
+                    path_suffixes: vec!["rs".into()],
+                    ..Default::default()
+                },
                 collapsed_placeholder: " /* ... */ ".to_string(),
                 ..Default::default()
             },
@@ -1393,7 +1399,10 @@ fn json_lang() -> Arc<Language> {
         Language::new(
             LanguageConfig {
                 name: "JSON".into(),
-                path_suffixes: vec!["json".into()],
+                matcher: LanguageMatcher {
+                    path_suffixes: vec!["json".into()],
+                    ..Default::default()
+                },
                 ..Default::default()
             },
             Some(tree_sitter_json::language()),
@@ -1421,7 +1430,10 @@ fn toml_lang() -> Arc<Language> {
     Arc::new(Language::new(
         LanguageConfig {
             name: "TOML".into(),
-            path_suffixes: vec!["toml".into()],
+            matcher: LanguageMatcher {
+                path_suffixes: vec!["toml".into()],
+                ..Default::default()
+            },
             ..Default::default()
         },
         Some(tree_sitter_toml::language()),
@@ -1433,7 +1445,10 @@ fn cpp_lang() -> Arc<Language> {
         Language::new(
             LanguageConfig {
                 name: "CPP".into(),
-                path_suffixes: vec!["cpp".into()],
+                matcher: LanguageMatcher {
+                    path_suffixes: vec!["cpp".into()],
+                    ..Default::default()
+                },
                 ..Default::default()
             },
             Some(tree_sitter_cpp::language()),
@@ -1513,7 +1528,10 @@ fn lua_lang() -> Arc<Language> {
         Language::new(
             LanguageConfig {
                 name: "Lua".into(),
-                path_suffixes: vec!["lua".into()],
+                matcher: LanguageMatcher {
+                    path_suffixes: vec!["lua".into()],
+                    ..Default::default()
+                },
                 collapsed_placeholder: "--[ ... ]--".to_string(),
                 ..Default::default()
             },
@@ -1542,7 +1560,10 @@ fn php_lang() -> Arc<Language> {
         Language::new(
             LanguageConfig {
                 name: "PHP".into(),
-                path_suffixes: vec!["php".into()],
+                matcher: LanguageMatcher {
+                    path_suffixes: vec!["php".into()],
+                    ..Default::default()
+                },
                 collapsed_placeholder: "/* ... */".into(),
                 ..Default::default()
             },
@@ -1597,7 +1618,10 @@ fn ruby_lang() -> Arc<Language> {
         Language::new(
             LanguageConfig {
                 name: "Ruby".into(),
-                path_suffixes: vec!["rb".into()],
+                matcher: LanguageMatcher {
+                    path_suffixes: vec!["rb".into()],
+                    ..Default::default()
+                },
                 collapsed_placeholder: "# ...".to_string(),
                 ..Default::default()
             },
@@ -1638,7 +1662,10 @@ fn elixir_lang() -> Arc<Language> {
         Language::new(
             LanguageConfig {
                 name: "Elixir".into(),
-                path_suffixes: vec!["rs".into()],
+                matcher: LanguageMatcher {
+                    path_suffixes: vec!["rs".into()],
+                    ..Default::default()
+                },
                 ..Default::default()
             },
             Some(tree_sitter_elixir::language()),

crates/theme/src/registry.rs 🔗

@@ -194,7 +194,9 @@ impl ThemeRegistry {
     }
 
     pub fn list_names(&self, _staff: bool) -> Vec<SharedString> {
-        self.state.read().themes.keys().cloned().collect()
+        let mut names = self.state.read().themes.keys().cloned().collect::<Vec<_>>();
+        names.sort();
+        names
     }
 
     pub fn list(&self, _staff: bool) -> Vec<ThemeMeta> {
@@ -263,11 +265,17 @@ impl ThemeRegistry {
         Ok(())
     }
 
-    /// Loads the user theme from the specified path and adds it to the registry.
-    pub async fn load_user_theme(&self, theme_path: &Path, fs: Arc<dyn Fs>) -> Result<()> {
+    pub async fn read_user_theme(theme_path: &Path, fs: Arc<dyn Fs>) -> Result<ThemeFamilyContent> {
         let reader = fs.open_sync(&theme_path).await?;
         let theme = serde_json_lenient::from_reader(reader)?;
 
+        Ok(theme)
+    }
+
+    /// Loads the user theme from the specified path and adds it to the registry.
+    pub async fn load_user_theme(&self, theme_path: &Path, fs: Arc<dyn Fs>) -> Result<()> {
+        let theme = Self::read_user_theme(theme_path, fs).await?;
+
         self.insert_user_theme_families([theme]);
 
         Ok(())

crates/util/src/arc_cow.rs 🔗

@@ -1,5 +1,6 @@
 use std::{
     borrow::Cow,
+    cmp::Ordering,
     fmt::{self, Debug},
     hash::{Hash, Hasher},
     sync::Arc,
@@ -18,6 +19,18 @@ impl<'a, T: ?Sized + PartialEq> PartialEq for ArcCow<'a, T> {
     }
 }
 
+impl<'a, T: ?Sized + PartialOrd> PartialOrd for ArcCow<'a, T> {
+    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
+        self.as_ref().partial_cmp(other.as_ref())
+    }
+}
+
+impl<'a, T: ?Sized + Ord> Ord for ArcCow<'a, T> {
+    fn cmp(&self, other: &Self) -> Ordering {
+        self.as_ref().cmp(other.as_ref())
+    }
+}
+
 impl<'a, T: ?Sized + Eq> Eq for ArcCow<'a, T> {}
 
 impl<'a, T: ?Sized + Hash> Hash for ArcCow<'a, T> {

crates/util/src/paths.rs 🔗

@@ -22,6 +22,11 @@ lazy_static::lazy_static! {
     } else {
         CONFIG_DIR.join("support")
     };
+    pub static ref EXTENSIONS_DIR: PathBuf = if cfg!(target_os="macos") {
+        HOME.join("Library/Application Support/Zed")
+    } else {
+        CONFIG_DIR.join("extensions")
+    };
     pub static ref PLUGINS_DIR: PathBuf = if cfg!(target_os="macos") {
         HOME.join("Library/Application Support/Zed/plugins")
     } else {

crates/zed/Cargo.toml 🔗

@@ -44,6 +44,7 @@ db.workspace = true
 diagnostics.workspace = true
 editor.workspace = true
 env_logger.workspace = true
+extension.workspace = true
 feature_flags.workspace = true
 feedback.workspace = true
 file_finder.workspace = true

crates/zed/src/languages.rs 🔗

@@ -4,8 +4,8 @@ pub use language::*;
 use node_runtime::NodeRuntime;
 use rust_embed::RustEmbed;
 use settings::Settings;
-use std::{borrow::Cow, fs, path::Path, str, sync::Arc};
-use util::{asset_str, paths::PLUGINS_DIR, ResultExt};
+use std::{str, sync::Arc};
+use util::asset_str;
 
 use self::{deno::DenoSettings, elixir::ElixirSettings};
 
@@ -62,30 +62,69 @@ pub fn init(
     ElixirSettings::register(cx);
     DenoSettings::register(cx);
 
-    let language = |name, grammar, adapters| {
-        languages.register(name, load_config(name), grammar, adapters, load_queries)
+    languages.add_grammars([
+        ("bash", tree_sitter_bash::language()),
+        ("beancount", tree_sitter_beancount::language()),
+        ("c", tree_sitter_c::language()),
+        ("c_sharp", tree_sitter_c_sharp::language()),
+        ("cpp", tree_sitter_cpp::language()),
+        ("css", tree_sitter_css::language()),
+        ("elixir", tree_sitter_elixir::language()),
+        ("elm", tree_sitter_elm::language()),
+        (
+            "embedded_template",
+            tree_sitter_embedded_template::language(),
+        ),
+        ("erlang", tree_sitter_erlang::language()),
+        ("gitcommit", tree_sitter_gitcommit::language()),
+        ("gleam", tree_sitter_gleam::language()),
+        ("glsl", tree_sitter_glsl::language()),
+        ("go", tree_sitter_go::language()),
+        ("gomod", tree_sitter_gomod::language()),
+        ("gowork", tree_sitter_gowork::language()),
+        ("haskell", tree_sitter_haskell::language()),
+        ("hcl", tree_sitter_hcl::language()),
+        ("heex", tree_sitter_heex::language()),
+        ("html", tree_sitter_html::language()),
+        ("json", tree_sitter_json::language()),
+        ("lua", tree_sitter_lua::language()),
+        ("markdown", tree_sitter_markdown::language()),
+        ("nix", tree_sitter_nix::language()),
+        ("nu", tree_sitter_nu::language()),
+        ("ocaml", tree_sitter_ocaml::language_ocaml()),
+        (
+            "ocaml_interface",
+            tree_sitter_ocaml::language_ocaml_interface(),
+        ),
+        ("php", tree_sitter_php::language_php()),
+        ("proto", tree_sitter_proto::language()),
+        ("purescript", tree_sitter_purescript::language()),
+        ("python", tree_sitter_python::language()),
+        ("racket", tree_sitter_racket::language()),
+        ("ruby", tree_sitter_ruby::language()),
+        ("rust", tree_sitter_rust::language()),
+        ("scheme", tree_sitter_scheme::language()),
+        ("svelte", tree_sitter_svelte::language()),
+        ("toml", tree_sitter_toml::language()),
+        ("tsx", tree_sitter_typescript::language_tsx()),
+        ("typescript", tree_sitter_typescript::language_typescript()),
+        ("uiua", tree_sitter_uiua::language()),
+        ("vue", tree_sitter_vue::language()),
+        ("yaml", tree_sitter_yaml::language()),
+        ("zig", tree_sitter_zig::language()),
+    ]);
+
+    let language = |name: &'static str, adapters| {
+        languages.register(name, load_config(name), adapters, load_queries)
     };
 
-    language("bash", tree_sitter_bash::language(), vec![]);
-    language("beancount", tree_sitter_beancount::language(), vec![]);
-    language(
-        "c",
-        tree_sitter_c::language(),
-        vec![Arc::new(c::CLspAdapter) as Arc<dyn LspAdapter>],
-    );
-    language(
-        "cpp",
-        tree_sitter_cpp::language(),
-        vec![Arc::new(c::CLspAdapter)],
-    );
-    language(
-        "csharp",
-        tree_sitter_c_sharp::language(),
-        vec![Arc::new(csharp::OmniSharpAdapter {})],
-    );
+    language("bash", vec![]);
+    language("beancount", vec![]);
+    language("c", vec![Arc::new(c::CLspAdapter) as Arc<dyn LspAdapter>]);
+    language("cpp", vec![Arc::new(c::CLspAdapter)]);
+    language("csharp", vec![Arc::new(csharp::OmniSharpAdapter {})]);
     language(
         "css",
-        tree_sitter_css::language(),
         vec![
             Arc::new(css::CssLspAdapter::new(node_runtime.clone())),
             Arc::new(tailwind::TailwindLspAdapter::new(node_runtime.clone())),
@@ -95,53 +134,32 @@ pub fn init(
     match &ElixirSettings::get(None, cx).lsp {
         elixir::ElixirLspSetting::ElixirLs => language(
             "elixir",
-            tree_sitter_elixir::language(),
             vec![
                 Arc::new(elixir::ElixirLspAdapter),
                 Arc::new(tailwind::TailwindLspAdapter::new(node_runtime.clone())),
             ],
         ),
-        elixir::ElixirLspSetting::NextLs => language(
-            "elixir",
-            tree_sitter_elixir::language(),
-            vec![Arc::new(elixir::NextLspAdapter)],
-        ),
+        elixir::ElixirLspSetting::NextLs => {
+            language("elixir", vec![Arc::new(elixir::NextLspAdapter)])
+        }
         elixir::ElixirLspSetting::Local { path, arguments } => language(
             "elixir",
-            tree_sitter_elixir::language(),
             vec![Arc::new(elixir::LocalLspAdapter {
                 path: path.clone(),
                 arguments: arguments.clone(),
             })],
         ),
     }
-    language("gitcommit", tree_sitter_gitcommit::language(), vec![]);
-    language(
-        "erlang",
-        tree_sitter_erlang::language(),
-        vec![Arc::new(erlang::ErlangLspAdapter)],
-    );
+    language("gitcommit", vec![]);
+    language("erlang", vec![Arc::new(erlang::ErlangLspAdapter)]);
 
-    language(
-        "gleam",
-        tree_sitter_gleam::language(),
-        vec![Arc::new(gleam::GleamLspAdapter)],
-    );
-    language(
-        "go",
-        tree_sitter_go::language(),
-        vec![Arc::new(go::GoLspAdapter)],
-    );
-    language("gomod", tree_sitter_gomod::language(), vec![]);
-    language("gowork", tree_sitter_gowork::language(), vec![]);
-    language(
-        "zig",
-        tree_sitter_zig::language(),
-        vec![Arc::new(zig::ZlsAdapter)],
-    );
+    language("gleam", vec![Arc::new(gleam::GleamLspAdapter)]);
+    language("go", vec![Arc::new(go::GoLspAdapter)]);
+    language("gomod", vec![]);
+    language("gowork", vec![]);
+    language("zig", vec![Arc::new(zig::ZlsAdapter)]);
     language(
         "heex",
-        tree_sitter_heex::language(),
         vec![
             Arc::new(elixir::ElixirLspAdapter),
             Arc::new(tailwind::TailwindLspAdapter::new(node_runtime.clone())),
@@ -149,48 +167,32 @@ pub fn init(
     );
     language(
         "json",
-        tree_sitter_json::language(),
         vec![Arc::new(json::JsonLspAdapter::new(
             node_runtime.clone(),
             languages.clone(),
         ))],
     );
-    language("markdown", tree_sitter_markdown::language(), vec![]);
+    language("markdown", vec![]);
     language(
         "python",
-        tree_sitter_python::language(),
         vec![Arc::new(python::PythonLspAdapter::new(
             node_runtime.clone(),
         ))],
     );
-    language(
-        "rust",
-        tree_sitter_rust::language(),
-        vec![Arc::new(rust::RustLspAdapter)],
-    );
-    language(
-        "toml",
-        tree_sitter_toml::language(),
-        vec![Arc::new(toml::TaploLspAdapter)],
-    );
+    language("rust", vec![Arc::new(rust::RustLspAdapter)]);
+    language("toml", vec![Arc::new(toml::TaploLspAdapter)]);
     match &DenoSettings::get(None, cx).enable {
         true => {
             language(
                 "tsx",
-                tree_sitter_typescript::language_tsx(),
                 vec![
                     Arc::new(deno::DenoLspAdapter::new()),
                     Arc::new(tailwind::TailwindLspAdapter::new(node_runtime.clone())),
                 ],
             );
-            language(
-                "typescript",
-                tree_sitter_typescript::language_typescript(),
-                vec![Arc::new(deno::DenoLspAdapter::new())],
-            );
+            language("typescript", vec![Arc::new(deno::DenoLspAdapter::new())]);
             language(
                 "javascript",
-                tree_sitter_typescript::language_tsx(),
                 vec![
                     Arc::new(deno::DenoLspAdapter::new()),
                     Arc::new(tailwind::TailwindLspAdapter::new(node_runtime.clone())),
@@ -200,7 +202,6 @@ pub fn init(
         false => {
             language(
                 "tsx",
-                tree_sitter_typescript::language_tsx(),
                 vec![
                     Arc::new(typescript::TypeScriptLspAdapter::new(node_runtime.clone())),
                     Arc::new(typescript::EsLintLspAdapter::new(node_runtime.clone())),
@@ -209,7 +210,6 @@ pub fn init(
             );
             language(
                 "typescript",
-                tree_sitter_typescript::language_typescript(),
                 vec![
                     Arc::new(typescript::TypeScriptLspAdapter::new(node_runtime.clone())),
                     Arc::new(typescript::EsLintLspAdapter::new(node_runtime.clone())),
@@ -217,7 +217,6 @@ pub fn init(
             );
             language(
                 "javascript",
-                tree_sitter_typescript::language_tsx(),
                 vec![
                     Arc::new(typescript::TypeScriptLspAdapter::new(node_runtime.clone())),
                     Arc::new(typescript::EsLintLspAdapter::new(node_runtime.clone())),
@@ -227,47 +226,31 @@ pub fn init(
         }
     }
 
-    language(
-        "haskell",
-        tree_sitter_haskell::language(),
-        vec![Arc::new(haskell::HaskellLanguageServer {})],
-    );
+    language("haskell", vec![Arc::new(haskell::HaskellLanguageServer {})]);
     language(
         "html",
-        tree_sitter_html::language(),
         vec![
             Arc::new(html::HtmlLspAdapter::new(node_runtime.clone())),
             Arc::new(tailwind::TailwindLspAdapter::new(node_runtime.clone())),
         ],
     );
-    language(
-        "ruby",
-        tree_sitter_ruby::language(),
-        vec![Arc::new(ruby::RubyLanguageServer)],
-    );
+    language("ruby", vec![Arc::new(ruby::RubyLanguageServer)]);
     language(
         "erb",
-        tree_sitter_embedded_template::language(),
         vec![
             Arc::new(ruby::RubyLanguageServer),
             Arc::new(tailwind::TailwindLspAdapter::new(node_runtime.clone())),
         ],
     );
-    language("scheme", tree_sitter_scheme::language(), vec![]);
-    language("racket", tree_sitter_racket::language(), vec![]);
-    language(
-        "lua",
-        tree_sitter_lua::language(),
-        vec![Arc::new(lua::LuaLspAdapter)],
-    );
+    language("scheme", vec![]);
+    language("racket", vec![]);
+    language("lua", vec![Arc::new(lua::LuaLspAdapter)]);
     language(
         "yaml",
-        tree_sitter_yaml::language(),
         vec![Arc::new(yaml::YamlLspAdapter::new(node_runtime.clone()))],
     );
     language(
         "svelte",
-        tree_sitter_svelte::language(),
         vec![
             Arc::new(svelte::SvelteLspAdapter::new(node_runtime.clone())),
             Arc::new(tailwind::TailwindLspAdapter::new(node_runtime.clone())),
@@ -275,7 +258,6 @@ pub fn init(
     );
     language(
         "php",
-        tree_sitter_php::language_php(),
         vec![
             Arc::new(php::IntelephenseLspAdapter::new(node_runtime.clone())),
             Arc::new(tailwind::TailwindLspAdapter::new(node_runtime.clone())),
@@ -287,62 +269,24 @@ pub fn init(
     #[cfg(not(target_os = "linux"))]
     language(
         "purescript",
-        tree_sitter_purescript::language(),
         vec![Arc::new(purescript::PurescriptLspAdapter::new(
             node_runtime.clone(),
         ))],
     );
     language(
         "elm",
-        tree_sitter_elm::language(),
         vec![Arc::new(elm::ElmLspAdapter::new(node_runtime.clone()))],
     );
-    language("glsl", tree_sitter_glsl::language(), vec![]);
-    language("nix", tree_sitter_nix::language(), vec![]);
-    language(
-        "nu",
-        tree_sitter_nu::language(),
-        vec![Arc::new(nu::NuLanguageServer {})],
-    );
-    language(
-        "ocaml",
-        tree_sitter_ocaml::language_ocaml(),
-        vec![Arc::new(ocaml::OCamlLspAdapter)],
-    );
-    language(
-        "ocaml-interface",
-        tree_sitter_ocaml::language_ocaml_interface(),
-        vec![Arc::new(ocaml::OCamlLspAdapter)],
-    );
-    language(
-        "vue",
-        tree_sitter_vue::language(),
-        vec![Arc::new(vue::VueLspAdapter::new(node_runtime))],
-    );
-    language(
-        "uiua",
-        tree_sitter_uiua::language(),
-        vec![Arc::new(uiua::UiuaLanguageServer {})],
-    );
-    language("proto", tree_sitter_proto::language(), vec![]);
-    language("terraform", tree_sitter_hcl::language(), vec![]);
-    language("hcl", tree_sitter_hcl::language(), vec![]);
-
-    if let Ok(children) = std::fs::read_dir(&*PLUGINS_DIR) {
-        for child in children {
-            if let Ok(child) = child {
-                let path = child.path();
-                let config_path = path.join("config.toml");
-                if let Ok(config) = std::fs::read(&config_path) {
-                    languages.register_wasm(
-                        path.into(),
-                        ::toml::from_slice(&config).unwrap(),
-                        load_plugin_queries,
-                    );
-                }
-            }
-        }
-    }
+    language("glsl", vec![]);
+    language("nix", vec![]);
+    language("nu", vec![Arc::new(nu::NuLanguageServer {})]);
+    language("ocaml", vec![Arc::new(ocaml::OCamlLspAdapter)]);
+    language("ocaml-interface", vec![Arc::new(ocaml::OCamlLspAdapter)]);
+    language("vue", vec![Arc::new(vue::VueLspAdapter::new(node_runtime))]);
+    language("uiua", vec![Arc::new(uiua::UiuaLanguageServer {})]);
+    language("proto", vec![]);
+    language("terraform", vec![]);
+    language("hcl", vec![]);
 }
 
 #[cfg(any(test, feature = "test-support"))]
@@ -370,20 +314,6 @@ fn load_config(name: &str) -> LanguageConfig {
     .unwrap()
 }
 
-const QUERY_FILENAME_PREFIXES: &[(
-    &str,
-    fn(&mut LanguageQueries) -> &mut Option<Cow<'static, str>>,
-)] = &[
-    ("highlights", |q| &mut q.highlights),
-    ("brackets", |q| &mut q.brackets),
-    ("outline", |q| &mut q.outline),
-    ("indents", |q| &mut q.indents),
-    ("embedding", |q| &mut q.embedding),
-    ("injections", |q| &mut q.injections),
-    ("overrides", |q| &mut q.overrides),
-    ("redactions", |q| &mut q.redactions),
-];
-
 fn load_queries(name: &str) -> LanguageQueries {
     let mut result = LanguageQueries::default();
     for path in LanguageDir::iter() {
@@ -404,32 +334,3 @@ fn load_queries(name: &str) -> LanguageQueries {
     }
     result
 }
-
-fn load_plugin_queries(root_path: &Path) -> LanguageQueries {
-    let mut result = LanguageQueries::default();
-    if let Some(entries) = fs::read_dir(root_path).log_err() {
-        for entry in entries {
-            let Some(entry) = entry.log_err() else {
-                continue;
-            };
-            let path = entry.path();
-            if let Some(remainder) = path.strip_prefix(root_path).ok().and_then(|p| p.to_str()) {
-                if !remainder.ends_with(".scm") {
-                    continue;
-                }
-                for (name, query) in QUERY_FILENAME_PREFIXES {
-                    if remainder.starts_with(name) {
-                        if let Some(contents) = fs::read_to_string(&path).log_err() {
-                            match query(&mut result) {
-                                None => *query(&mut result) = Some(contents.into()),
-                                Some(r) => r.to_mut().push_str(contents.as_ref()),
-                            }
-                        }
-                        break;
-                    }
-                }
-            }
-        }
-    }
-    result
-}

crates/zed/src/languages/bash/config.toml 🔗

@@ -1,4 +1,5 @@
 name = "Shell Script"
+grammar = "bash"
 path_suffixes = ["sh", "bash", "bashrc", "bash_profile", "bash_aliases", "bash_logout", "profile", "zsh", "zshrc", "zshenv", "zsh_profile", "zsh_aliases", "zsh_histfile", "zlogin", "zprofile", ".env"]
 line_comments = ["# "]
 first_line_pattern = "^#!.*\\b(?:ba|z)?sh\\b"

crates/zed/src/languages/erlang/config.toml 🔗

@@ -1,4 +1,5 @@
 name = "Erlang"
+grammar = "erlang"
 # TODO: support parsing rebar.config files
 # # https://github.com/WhatsApp/tree-sitter-erlang/issues/3
 path_suffixes = ["erl", "hrl", "app.src", "escript", "xrl", "yrl", "Emakefile", "rebar.config"]

crates/zed/src/languages/markdown/config.toml 🔗

@@ -1,5 +1,7 @@
 name = "Markdown"
+grammar = "markdown"
 path_suffixes = ["md", "mdx"]
+word_characters = ["-"]
 brackets = [
     { start = "{", end = "}", close = true, newline = true },
     { start = "[", end = "]", close = true, newline = true },

crates/zed/src/languages/ocaml-interface/config.toml 🔗

@@ -1,4 +1,5 @@
 name = "OCaml Interface"
+grammar = "ocaml_interface"
 path_suffixes = ["mli"]
 block_comment = ["(* ", "*)"]
 autoclose_before = ";,=)}"
@@ -8,6 +9,6 @@ brackets = [
   { start = "[", end = "]", close = true, newline = true },
   { start = "(", end = ")", close = true, newline = true },
   { start = "sig", end = " end", close = true, newline = true },
-  # HACK: For some reason `object` alone does not work  
+  # HACK: For some reason `object` alone does not work
   { start = "object ", end = "end", close = true, newline = true },
 ]

crates/zed/src/languages/ocaml/config.toml 🔗

@@ -1,8 +1,9 @@
 name = "OCaml"
+grammar = "ocaml"
 path_suffixes = ["ml"]
 block_comment = ["(* ", "*)"]
 autoclose_before = ";,=)}]"
-brackets = [ 
+brackets = [
   { start = "{", end = "}", close = true, newline = true },
   { start = "<", end = ">", close = true, newline = true },
   { start = "[", end = "]", close = true, newline = true },

crates/zed/src/main.rs 🔗

@@ -48,7 +48,7 @@ use theme::{ActiveTheme, SystemAppearance, ThemeRegistry, ThemeSettings};
 use util::{
     async_maybe,
     http::{self, HttpClient, ZedHttpClient},
-    paths::{self, CRASHES_DIR, CRASHES_RETIRED_DIR, PLUGINS_DIR},
+    paths::{self, CRASHES_DIR, CRASHES_RETIRED_DIR},
     ResultExt,
 };
 use uuid::Uuid;
@@ -174,6 +174,8 @@ fn main() {
         );
         assistant::init(cx);
 
+        extension::init(fs.clone(), languages.clone(), ThemeRegistry::global(cx), cx);
+
         load_user_themes_in_background(fs.clone(), cx);
         #[cfg(target_os = "macos")]
         watch_themes(fs.clone(), cx);
@@ -982,20 +984,13 @@ fn watch_themes(fs: Arc<dyn fs::Fs>, cx: &mut AppContext) {
     .detach()
 }
 
+#[cfg(debug_assertions)]
 async fn watch_languages(fs: Arc<dyn fs::Fs>, languages: Arc<LanguageRegistry>) {
     let reload_debounce = Duration::from_millis(250);
 
-    let mut events = fs.watch(PLUGINS_DIR.as_ref(), reload_debounce).await;
-
-    #[cfg(debug_assertions)]
-    {
-        events = futures::stream::select(
-            events,
-            fs.watch("crates/zed/src/languages".as_ref(), reload_debounce)
-                .await,
-        )
-        .boxed();
-    }
+    let mut events = fs
+        .watch("crates/zed/src/languages".as_ref(), reload_debounce)
+        .await;
 
     while (events.next().await).is_some() {
         languages.reload();
@@ -1025,3 +1020,6 @@ fn watch_file_types(fs: Arc<dyn fs::Fs>, cx: &mut AppContext) {
 
 #[cfg(not(debug_assertions))]
 fn watch_file_types(_fs: Arc<dyn fs::Fs>, _cx: &mut AppContext) {}
+
+#[cfg(not(debug_assertions))]
+async fn watch_languages(_fs: Arc<dyn fs::Fs>, _languages: Arc<LanguageRegistry>) {}

crates/zed/src/zed.rs 🔗

@@ -739,7 +739,7 @@ mod tests {
         actions, Action, AnyWindowHandle, AppContext, AssetSource, Entity, TestAppContext,
         VisualTestContext, WindowHandle,
     };
-    use language::LanguageRegistry;
+    use language::{LanguageMatcher, LanguageRegistry};
     use project::{project_settings::ProjectSettings, Project, ProjectPath};
     use serde_json::json;
     use settings::{handle_settings_file_changes, watch_config_file, SettingsStore};
@@ -2742,7 +2742,10 @@ mod tests {
         Arc::new(language::Language::new(
             language::LanguageConfig {
                 name: "Rust".into(),
-                path_suffixes: vec!["rs".to_string()],
+                matcher: LanguageMatcher {
+                    path_suffixes: vec!["rs".to_string()],
+                    ..Default::default()
+                },
                 ..Default::default()
             },
             Some(tree_sitter_rust::language()),

docs/src/languages/ocaml.md 🔗

@@ -7,7 +7,7 @@
 If you have the development environment already setup, you can skip to [Launching Zed](#launching-zed)
 
 ### Using OPAM
-Opam is the official package manager for OCaml and is highly recommended for getting started with OCaml. To get started using Opam, please follow the instructions provided [here](https://opam.ocaml.org/doc/Install.html). 
+Opam is the official package manager for OCaml and is highly recommended for getting started with OCaml. To get started using Opam, please follow the instructions provided [here](https://ocaml.org/install). 
 
 Once you install opam and setup a switch with your development environment as per the instructions, you can proceed.