From 6edeea7c8a3bdbcaaafa38e4740de0ac6927d6fb Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 7 Feb 2024 12:14:50 -0800 Subject: [PATCH] Add logic for managing language and theme extensions (#7467) This PR adds the initial support for loading extensions in Zed. ### Extensions Directory Extensions are loaded from the extensions directory. The extensions directory has the following structure: ``` extensions/ installed/ extension-a/ grammars/ languages/ extension-b/ themes/ manifest.json ``` The `manifest.json` file is used internally by Zed to keep track of which extensions are installed. This file should be maintained automatically, and shouldn't require any direct interaction with it. Extensions can provide Tree-sitter grammars, languages, and themes. Release Notes: - N/A --------- Co-authored-by: Marshall --- 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 +++- .../random_project_collaboration_tests.rs | 9 +- crates/editor/src/display_map.rs | 17 +- crates/editor/src/editor_tests.rs | 38 +- .../editor/src/highlight_matching_bracket.rs | 7 +- crates/editor/src/inlay_hint_cache.rs | 32 +- .../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 ++++++++++++++---- .../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 +- .../src/semantic_index_tests.rs | 47 ++- crates/theme/src/registry.rs | 14 +- crates/util/src/arc_cow.rs | 13 + crates/util/src/paths.rs | 2 +- crates/zed/Cargo.toml | 1 + crates/zed/src/languages.rs | 279 +++++--------- crates/zed/src/languages/bash/config.toml | 1 + .../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 + .../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 + .../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 | 1 + crates/zed/src/languages/nix/config.toml | 1 + crates/zed/src/languages/nu/config.toml | 1 + .../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 + .../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 + .../zed/src/languages/terraform/config.toml | 1 + crates/zed/src/languages/toml/config.toml | 1 + crates/zed/src/languages/tsx/config.toml | 1 + .../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 +- 77 files changed, 1503 insertions(+), 387 deletions(-) create mode 100644 crates/extension/Cargo.toml create mode 120000 crates/extension/LICENSE-GPL create mode 100644 crates/extension/src/extension_store.rs create mode 100644 crates/extension/src/extension_store_test.rs diff --git a/Cargo.lock b/Cargo.lock index 04f18936cd..2bbc846044 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2559,6 +2559,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" @@ -3969,6 +3987,7 @@ dependencies = [ "sum_tree", "text", "theme", + "toml", "tree-sitter", "tree-sitter-elixir", "tree-sitter-embedded-template", @@ -10327,6 +10346,7 @@ dependencies = [ "diagnostics", "editor", "env_logger", + "extension", "feature_flags", "feedback", "file_finder", diff --git a/Cargo.toml b/Cargo.toml index 4587e947c6..c7e40436f4 100644 --- a/Cargo.toml +++ b/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" } diff --git a/crates/assistant/src/codegen.rs b/crates/assistant/src/codegen.rs index d592ce88ae..c1a663d7ef 100644 --- a/crates/assistant/src/codegen.rs +++ b/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()), diff --git a/crates/assistant/src/prompts.rs b/crates/assistant/src/prompts.rs index 9042fda5be..f40a841f4c 100644 --- a/crates/assistant/src/prompts.rs +++ b/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()), diff --git a/crates/collab/src/tests/editor_tests.rs b/crates/collab/src/tests/editor_tests.rs index 0e11ec1684..458f347efb 100644 --- a/crates/collab/src/tests/editor_tests.rs +++ b/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()), diff --git a/crates/collab/src/tests/integration_tests.rs b/crates/collab/src/tests/integration_tests.rs index eb0beb6f66..746f5aeeaf 100644 --- a/crates/collab/src/tests/integration_tests.rs +++ b/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()), diff --git a/crates/collab/src/tests/random_project_collaboration_tests.rs b/crates/collab/src/tests/random_project_collaboration_tests.rs index 53d47eb6b5..37103a3382 100644 --- a/crates/collab/src/tests/random_project_collaboration_tests.rs +++ b/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, diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index 23507eab3e..f975a833c1 100644 --- a/crates/editor/src/display_map.rs +++ b/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()), diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index 6b23e13d15..865fcb1a5c 100644 --- a/crates/editor/src/editor_tests.rs +++ b/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() }, diff --git a/crates/editor/src/highlight_matching_bracket.rs b/crates/editor/src/highlight_matching_bracket.rs index 1ed7700f37..787be1999e 100644 --- a/crates/editor/src/highlight_matching_bracket.rs +++ b/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 { diff --git a/crates/editor/src/inlay_hint_cache.rs b/crates/editor/src/inlay_hint_cache.rs index bc90b6face..dc2616c6ef 100644 --- a/crates/editor/src/inlay_hint_cache.rs +++ b/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()), diff --git a/crates/editor/src/test/editor_lsp_test_context.rs b/crates/editor/src/test/editor_lsp_test_context.rs index 70c1699b83..b083e63890 100644 --- a/crates/editor/src/test/editor_lsp_test_context.rs +++ b/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(), diff --git a/crates/extension/Cargo.toml b/crates/extension/Cargo.toml new file mode 100644 index 0000000000..092f7b0bec --- /dev/null +++ b/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"] } diff --git a/crates/extension/LICENSE-GPL b/crates/extension/LICENSE-GPL new file mode 120000 index 0000000000..89e542f750 --- /dev/null +++ b/crates/extension/LICENSE-GPL @@ -0,0 +1 @@ +../../LICENSE-GPL \ No newline at end of file diff --git a/crates/extension/src/extension_store.rs b/crates/extension/src/extension_store.rs new file mode 100644 index 0000000000..9b9892641d --- /dev/null +++ b/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>, + fs: Arc, + extensions_dir: PathBuf, + manifest_path: PathBuf, + language_registry: Arc, + theme_registry: Arc, + _watch_extensions_dir: Task<()>, +} + +struct GlobalExtensionStore(Model); + +impl Global for GlobalExtensionStore {} + +#[derive(Deserialize, Serialize, Default)] +pub struct Manifest { + pub grammars: HashMap, + pub languages: HashMap, LanguageManifestEntry>, + pub themes: HashMap, +} + +#[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, + language_registry: Arc, + theme_registry: Arc, + 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::().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, + language_registry: Arc, + theme_registry: Arc, + cx: &mut ModelContext, + ) -> 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) { + 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) { + 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) -> 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) -> Task> { + 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::(&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 +} diff --git a/crates/extension/src/extension_store_test.rs b/crates/extension/src/extension_store_test.rs new file mode 100644 index 0000000000..e95496b529 --- /dev/null +++ b/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); + }); +} diff --git a/crates/gpui/src/shared_string.rs b/crates/gpui/src/shared_string.rs index 8c12c1c970..1aa1bcae95 100644 --- a/crates/gpui/src/shared_string.rs +++ b/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` 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 { diff --git a/crates/language/Cargo.toml b/crates/language/Cargo.toml index 821fa80840..328327a212 100644 --- a/crates/language/Cargo.toml +++ b/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 diff --git a/crates/language/src/buffer_tests.rs b/crates/language/src/buffer_tests.rs index 05cec88126..ccbab9d6d1 100644 --- a/crates/language/src/buffer_tests.rs +++ b/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()), diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index 70e85761d2..eee05ea8e2 100644 --- a/crates/language/src/language.rs +++ b/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, // The name of the grammar in a WASM bundle (experimental). - pub grammar_name: Option>, - /// 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, + pub grammar: Option>, + /// 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, /// 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, } +#[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, + /// 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, +} + +pub const QUERY_FILENAME_PREFIXES: &[( + &str, + fn(&mut LanguageQueries) -> &mut Option>, +)] = &[ + ("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, D } } +fn serialize_regex(regex: &Option, serializer: S) -> Result +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, + source: AvailableLanguageSource, lsp_adapters: Vec>, loaded: bool, } -#[derive(Clone)] enum AvailableGrammar { - Native { - grammar: tree_sitter::Language, + Loaded(tree_sitter::Language), + Loading(Vec>>), + Unloaded(PathBuf), +} + +#[derive(Clone)] +enum AvailableLanguageSource { + BuiltIn { asset_dir: &'static str, get_queries: fn(&str) -> LanguageQueries, + config: LanguageConfig, }, - Wasm { + Extension { path: Arc, get_queries: fn(&Path) -> LanguageQueries, + matcher: LanguageMatcher, }, } @@ -737,6 +781,7 @@ struct LanguageRegistryState { next_language_server_id: usize, languages: Vec>, available_languages: Vec, + grammars: HashMap, next_available_language_id: AvailableLanguageId, loading_languages: HashMap>>>>, 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>) { + self.state.write().reload_languages(languages); + } + pub fn register( &self, asset_dir: &'static str, config: LanguageConfig, - grammar: tree_sitter::Language, lsp_adapters: Vec>, 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, - config: LanguageConfig, + name: Arc, + 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, 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 { 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::>(); result.sort_unstable_by_key(|language_name| language_name.to_lowercase()); @@ -873,7 +958,7 @@ impl LanguageRegistry { name: &str, ) -> UnwrapFuture>>> { 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>>> { 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, - callback: impl Fn(&LanguageConfig) -> bool, + callback: impl Fn(&str, &LanguageMatcher) -> bool, ) -> UnwrapFuture>>> { 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, + name: Arc, + ) -> UnwrapFuture>> { + 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> { 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>) { + 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 { + 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(), ); diff --git a/crates/language/src/syntax_map/syntax_map_tests.rs b/crates/language/src/syntax_map/syntax_map_tests.rs index abf52c64e5..0601a9f3c1 100644 --- a/crates/language/src/syntax_map/syntax_map_tests.rs +++ b/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()), diff --git a/crates/language_tools/src/lsp_log_tests.rs b/crates/language_tools/src/lsp_log_tests.rs index 24b23df880..b00d1bb79a 100644 --- a/crates/language_tools/src/lsp_log_tests.rs +++ b/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()), diff --git a/crates/project/src/project_tests.rs b/crates/project/src/project_tests.rs index 2e222c24fb..4cb321f9c0 100644 --- a/crates/project/src/project_tests.rs +++ b/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()), diff --git a/crates/project_symbols/src/project_symbols.rs b/crates/project_symbols/src/project_symbols.rs index 8aa7fac359..bb09741f07 100644 --- a/crates/project_symbols/src/project_symbols.rs +++ b/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, diff --git a/crates/semantic_index/src/semantic_index_tests.rs b/crates/semantic_index/src/semantic_index_tests.rs index 979151d4b2..23ed45ff1d 100644 --- a/crates/semantic_index/src/semantic_index_tests.rs +++ b/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::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::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::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 { 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::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::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::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::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::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()), diff --git a/crates/theme/src/registry.rs b/crates/theme/src/registry.rs index 2cc4d902a2..5a86c877f9 100644 --- a/crates/theme/src/registry.rs +++ b/crates/theme/src/registry.rs @@ -194,7 +194,9 @@ impl ThemeRegistry { } pub fn list_names(&self, _staff: bool) -> Vec { - self.state.read().themes.keys().cloned().collect() + let mut names = self.state.read().themes.keys().cloned().collect::>(); + names.sort(); + names } pub fn list(&self, _staff: bool) -> Vec { @@ -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) -> Result<()> { + pub async fn read_user_theme(theme_path: &Path, fs: Arc) -> Result { 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) -> Result<()> { + let theme = Self::read_user_theme(theme_path, fs).await?; + self.insert_user_theme_families([theme]); Ok(()) diff --git a/crates/util/src/arc_cow.rs b/crates/util/src/arc_cow.rs index c6afabbbaa..02ad1fa1f0 100644 --- a/crates/util/src/arc_cow.rs +++ b/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 { + 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> { diff --git a/crates/util/src/paths.rs b/crates/util/src/paths.rs index cd839ae50e..dcf9270fb1 100644 --- a/crates/util/src/paths.rs +++ b/crates/util/src/paths.rs @@ -14,7 +14,7 @@ lazy_static::lazy_static! { pub static ref THEMES_DIR: PathBuf = HOME.join(".config/zed/themes"); pub static ref LOGS_DIR: PathBuf = HOME.join("Library/Logs/Zed"); pub static ref SUPPORT_DIR: PathBuf = HOME.join("Library/Application Support/Zed"); - pub static ref PLUGINS_DIR: PathBuf = HOME.join("Library/Application Support/Zed/plugins"); + pub static ref EXTENSIONS_DIR: PathBuf = HOME.join("Library/Application Support/Zed/extensions"); pub static ref LANGUAGES_DIR: PathBuf = HOME.join("Library/Application Support/Zed/languages"); pub static ref COPILOT_DIR: PathBuf = HOME.join("Library/Application Support/Zed/copilot"); pub static ref DEFAULT_PRETTIER_DIR: PathBuf = HOME.join("Library/Application Support/Zed/prettier"); diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index c023faf6f7..1e9a5ba31e 100644 --- a/crates/zed/Cargo.toml +++ b/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 diff --git a/crates/zed/src/languages.rs b/crates/zed/src/languages.rs index 1250aa92c7..2931d53763 100644 --- a/crates/zed/src/languages.rs +++ b/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], - ); - 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]); + 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())), @@ -284,62 +266,24 @@ pub fn init( 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"))] @@ -367,20 +311,6 @@ fn load_config(name: &str) -> LanguageConfig { .unwrap() } -const QUERY_FILENAME_PREFIXES: &[( - &str, - fn(&mut LanguageQueries) -> &mut Option>, -)] = &[ - ("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() { @@ -401,32 +331,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 -} diff --git a/crates/zed/src/languages/bash/config.toml b/crates/zed/src/languages/bash/config.toml index 8b86318ecd..abbb95bda5 100644 --- a/crates/zed/src/languages/bash/config.toml +++ b/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" diff --git a/crates/zed/src/languages/beancount/config.toml b/crates/zed/src/languages/beancount/config.toml index 722b8a39b7..fff6411e9d 100644 --- a/crates/zed/src/languages/beancount/config.toml +++ b/crates/zed/src/languages/beancount/config.toml @@ -1,3 +1,4 @@ name = "Beancount" +grammar = "beancount" path_suffixes = ["beancount"] brackets = [{ start = "\"", end = "\"", close = false, newline = false }] diff --git a/crates/zed/src/languages/c/config.toml b/crates/zed/src/languages/c/config.toml index f99c0416cd..b41f469bd5 100644 --- a/crates/zed/src/languages/c/config.toml +++ b/crates/zed/src/languages/c/config.toml @@ -1,4 +1,5 @@ name = "C" +grammar = "c" path_suffixes = ["c"] line_comments = ["// "] autoclose_before = ";:.,=}])>" diff --git a/crates/zed/src/languages/cpp/config.toml b/crates/zed/src/languages/cpp/config.toml index 7630f1dd79..eecb09bc20 100644 --- a/crates/zed/src/languages/cpp/config.toml +++ b/crates/zed/src/languages/cpp/config.toml @@ -1,4 +1,5 @@ name = "C++" +grammar = "cpp" path_suffixes = ["cc", "cpp", "h", "hpp", "cxx", "hxx", "inl"] line_comments = ["// "] autoclose_before = ";:.,=}])>" diff --git a/crates/zed/src/languages/csharp/config.toml b/crates/zed/src/languages/csharp/config.toml index 7835283df4..51a10c70e0 100644 --- a/crates/zed/src/languages/csharp/config.toml +++ b/crates/zed/src/languages/csharp/config.toml @@ -1,4 +1,5 @@ name = "CSharp" +grammar = "c_sharp" path_suffixes = ["cs"] line_comments = ["// "] autoclose_before = ";:.,=}])>" diff --git a/crates/zed/src/languages/css/config.toml b/crates/zed/src/languages/css/config.toml index 24a844c239..e22abe6d70 100644 --- a/crates/zed/src/languages/css/config.toml +++ b/crates/zed/src/languages/css/config.toml @@ -1,4 +1,5 @@ name = "CSS" +grammar = "css" path_suffixes = ["css"] autoclose_before = ";:.,=}])>" brackets = [ diff --git a/crates/zed/src/languages/elixir/config.toml b/crates/zed/src/languages/elixir/config.toml index a3aab0ee8a..81e92d45b0 100644 --- a/crates/zed/src/languages/elixir/config.toml +++ b/crates/zed/src/languages/elixir/config.toml @@ -1,4 +1,5 @@ name = "Elixir" +grammar = "elixir" path_suffixes = ["ex", "exs"] line_comments = ["# "] autoclose_before = ";:.,=}])>" diff --git a/crates/zed/src/languages/elm/config.toml b/crates/zed/src/languages/elm/config.toml index b95c85d444..96fb2989ad 100644 --- a/crates/zed/src/languages/elm/config.toml +++ b/crates/zed/src/languages/elm/config.toml @@ -1,4 +1,5 @@ name = "Elm" +grammar = "elm" path_suffixes = ["elm"] line_comments = ["-- "] block_comment = ["{- ", " -}"] diff --git a/crates/zed/src/languages/erb/config.toml b/crates/zed/src/languages/erb/config.toml index ebc45e9984..5ec987e139 100644 --- a/crates/zed/src/languages/erb/config.toml +++ b/crates/zed/src/languages/erb/config.toml @@ -1,4 +1,5 @@ name = "ERB" +grammar = "embedded_template" path_suffixes = ["erb"] autoclose_before = ">})" brackets = [ diff --git a/crates/zed/src/languages/erlang/config.toml b/crates/zed/src/languages/erlang/config.toml index 5f92c0fe27..01eba93ff6 100644 --- a/crates/zed/src/languages/erlang/config.toml +++ 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"] diff --git a/crates/zed/src/languages/gitcommit/config.toml b/crates/zed/src/languages/gitcommit/config.toml index 70c68d9385..0b4d84069e 100644 --- a/crates/zed/src/languages/gitcommit/config.toml +++ b/crates/zed/src/languages/gitcommit/config.toml @@ -1,4 +1,5 @@ name = "Git Commit" +grammar = "git_commit" path_suffixes = [ # Refer to https://github.com/neovim/neovim/blob/master/runtime/lua/vim/filetype.lua#L1286-L1290 "TAG_EDITMSG", diff --git a/crates/zed/src/languages/gleam/config.toml b/crates/zed/src/languages/gleam/config.toml index a841862709..0a472172ad 100644 --- a/crates/zed/src/languages/gleam/config.toml +++ b/crates/zed/src/languages/gleam/config.toml @@ -1,4 +1,5 @@ name = "Gleam" +grammar = "gleam" path_suffixes = ["gleam"] line_comments = ["// ", "/// "] autoclose_before = ";:.,=}])>" diff --git a/crates/zed/src/languages/glsl/config.toml b/crates/zed/src/languages/glsl/config.toml index 9b60179662..01d7c88214 100644 --- a/crates/zed/src/languages/glsl/config.toml +++ b/crates/zed/src/languages/glsl/config.toml @@ -1,4 +1,5 @@ name = "GLSL" +grammar = "glsl" path_suffixes = ["vert", "frag", "tesc", "tese", "geom", "comp"] line_comments = ["// "] block_comment = ["/* ", " */"] diff --git a/crates/zed/src/languages/go/config.toml b/crates/zed/src/languages/go/config.toml index d07c46731a..cf29bfbb23 100644 --- a/crates/zed/src/languages/go/config.toml +++ b/crates/zed/src/languages/go/config.toml @@ -1,4 +1,5 @@ name = "Go" +grammar = "go" path_suffixes = ["go"] line_comments = ["// "] autoclose_before = ";:.,=}])>" diff --git a/crates/zed/src/languages/gomod/config.toml b/crates/zed/src/languages/gomod/config.toml index 80c252949d..19c7b20e8c 100644 --- a/crates/zed/src/languages/gomod/config.toml +++ b/crates/zed/src/languages/gomod/config.toml @@ -1,4 +1,5 @@ name = "Go Mod" +grammar = "go" path_suffixes = ["mod"] line_comments = ["//"] autoclose_before = ")" diff --git a/crates/zed/src/languages/gowork/config.toml b/crates/zed/src/languages/gowork/config.toml index 919f1db512..79621ecdaf 100644 --- a/crates/zed/src/languages/gowork/config.toml +++ b/crates/zed/src/languages/gowork/config.toml @@ -1,4 +1,5 @@ name = "Go Work" +grammar = "go_work" path_suffixes = ["work"] line_comments = ["//"] autoclose_before = ")" diff --git a/crates/zed/src/languages/haskell/config.toml b/crates/zed/src/languages/haskell/config.toml index 7b1454431e..3d83ab907d 100644 --- a/crates/zed/src/languages/haskell/config.toml +++ b/crates/zed/src/languages/haskell/config.toml @@ -1,4 +1,5 @@ name = "Haskell" +grammar = "haskell" path_suffixes = ["hs"] autoclose_before = ",=)}]" line_comments = ["-- "] diff --git a/crates/zed/src/languages/hcl/config.toml b/crates/zed/src/languages/hcl/config.toml index c7c2ebf6ba..891b2f38d4 100644 --- a/crates/zed/src/languages/hcl/config.toml +++ b/crates/zed/src/languages/hcl/config.toml @@ -1,4 +1,5 @@ name = "HCL" +grammar = "hcl" path_suffixes = ["hcl"] line_comments = ["# ", "// "] block_comment = ["/*", "*/"] diff --git a/crates/zed/src/languages/heex/config.toml b/crates/zed/src/languages/heex/config.toml index 74cb5ac9ff..c28ffa16b0 100644 --- a/crates/zed/src/languages/heex/config.toml +++ b/crates/zed/src/languages/heex/config.toml @@ -1,4 +1,5 @@ name = "HEEX" +grammar = "heex" path_suffixes = ["heex"] autoclose_before = ">})" brackets = [ diff --git a/crates/zed/src/languages/html/config.toml b/crates/zed/src/languages/html/config.toml index 3c24490fc4..389020b89d 100644 --- a/crates/zed/src/languages/html/config.toml +++ b/crates/zed/src/languages/html/config.toml @@ -1,4 +1,5 @@ name = "HTML" +grammar = "html" path_suffixes = ["html", "htm", "shtml"] autoclose_before = ">})" block_comment = [""] diff --git a/crates/zed/src/languages/javascript/config.toml b/crates/zed/src/languages/javascript/config.toml index a19d2588ed..a3acd1aac0 100644 --- a/crates/zed/src/languages/javascript/config.toml +++ b/crates/zed/src/languages/javascript/config.toml @@ -1,4 +1,5 @@ name = "JavaScript" +grammar = "tsx" path_suffixes = ["js", "jsx", "mjs", "cjs"] first_line_pattern = '^#!.*\bnode\b' line_comments = ["// "] diff --git a/crates/zed/src/languages/json/config.toml b/crates/zed/src/languages/json/config.toml index eed86826d5..34b3bc8bc6 100644 --- a/crates/zed/src/languages/json/config.toml +++ b/crates/zed/src/languages/json/config.toml @@ -1,4 +1,5 @@ name = "JSON" +grammar = "json" path_suffixes = ["json"] line_comments = ["// "] autoclose_before = ",]}" diff --git a/crates/zed/src/languages/lua/config.toml b/crates/zed/src/languages/lua/config.toml index 31946f46e9..19577fed91 100644 --- a/crates/zed/src/languages/lua/config.toml +++ b/crates/zed/src/languages/lua/config.toml @@ -1,4 +1,5 @@ name = "Lua" +grammar = "lua" path_suffixes = ["lua"] line_comments = ["-- "] autoclose_before = ",]}" diff --git a/crates/zed/src/languages/markdown/config.toml b/crates/zed/src/languages/markdown/config.toml index 98691a4ef7..e44ba6ec1a 100644 --- a/crates/zed/src/languages/markdown/config.toml +++ b/crates/zed/src/languages/markdown/config.toml @@ -1,4 +1,5 @@ name = "Markdown" +grammar = "markdown" path_suffixes = ["md", "mdx"] word_characters = ["-"] brackets = [ diff --git a/crates/zed/src/languages/nix/config.toml b/crates/zed/src/languages/nix/config.toml index 20b3921271..b62ead99b3 100644 --- a/crates/zed/src/languages/nix/config.toml +++ b/crates/zed/src/languages/nix/config.toml @@ -1,4 +1,5 @@ name = "Nix" +grammar = "nix" path_suffixes = ["nix"] line_comments = ["# "] block_comment = ["/* ", " */"] diff --git a/crates/zed/src/languages/nu/config.toml b/crates/zed/src/languages/nu/config.toml index 63f5a61591..23f19cdcd2 100644 --- a/crates/zed/src/languages/nu/config.toml +++ b/crates/zed/src/languages/nu/config.toml @@ -1,4 +1,5 @@ name = "Nu" +grammar = "nu" path_suffixes = ["nu"] line_comments = ["# "] autoclose_before = ";:.,=}])>` \n\t\"" diff --git a/crates/zed/src/languages/ocaml-interface/config.toml b/crates/zed/src/languages/ocaml-interface/config.toml index f7401f774c..fdbf1aad81 100644 --- a/crates/zed/src/languages/ocaml-interface/config.toml +++ b/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 }, ] diff --git a/crates/zed/src/languages/ocaml/config.toml b/crates/zed/src/languages/ocaml/config.toml index 522db6dae1..313cbb46df 100644 --- a/crates/zed/src/languages/ocaml/config.toml +++ b/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 }, diff --git a/crates/zed/src/languages/php/config.toml b/crates/zed/src/languages/php/config.toml index 9c6e9b767c..db594f8a18 100644 --- a/crates/zed/src/languages/php/config.toml +++ b/crates/zed/src/languages/php/config.toml @@ -1,4 +1,5 @@ name = "PHP" +grammar = "php" path_suffixes = ["php"] first_line_pattern = '^#!.*php' line_comments = ["// ", "# "] diff --git a/crates/zed/src/languages/proto/config.toml b/crates/zed/src/languages/proto/config.toml index 81fe1bdfb4..b8bccfd39b 100644 --- a/crates/zed/src/languages/proto/config.toml +++ b/crates/zed/src/languages/proto/config.toml @@ -1,4 +1,5 @@ name = "proto" +grammar = "proto" path_suffixes = ["proto"] line_comments = ["// "] autoclose_before = ";:.,=}])>" diff --git a/crates/zed/src/languages/purescript/config.toml b/crates/zed/src/languages/purescript/config.toml index ee9810dbb7..350f476978 100644 --- a/crates/zed/src/languages/purescript/config.toml +++ b/crates/zed/src/languages/purescript/config.toml @@ -1,4 +1,5 @@ name = "PureScript" +grammar = "purescript" path_suffixes = ["purs"] autoclose_before = ",=)}]" line_comments = ["-- "] diff --git a/crates/zed/src/languages/python/config.toml b/crates/zed/src/languages/python/config.toml index 3496f845dc..d5254eac90 100644 --- a/crates/zed/src/languages/python/config.toml +++ b/crates/zed/src/languages/python/config.toml @@ -1,4 +1,5 @@ name = "Python" +grammar = "python" path_suffixes = ["py", "pyi", "mpy"] first_line_pattern = '^#!.*\bpython[0-9.]*\b' line_comments = ["# "] diff --git a/crates/zed/src/languages/racket/config.toml b/crates/zed/src/languages/racket/config.toml index aea5ec5fce..d5975a36e5 100644 --- a/crates/zed/src/languages/racket/config.toml +++ b/crates/zed/src/languages/racket/config.toml @@ -1,4 +1,5 @@ name = "Racket" +grammar = "racket" path_suffixes = ["rkt"] line_comments = ["; "] autoclose_before = "])" diff --git a/crates/zed/src/languages/ruby/config.toml b/crates/zed/src/languages/ruby/config.toml index 8f2f1fab97..d3285d48d2 100644 --- a/crates/zed/src/languages/ruby/config.toml +++ b/crates/zed/src/languages/ruby/config.toml @@ -1,4 +1,5 @@ name = "Ruby" +grammar = "ruby" path_suffixes = [ "rb", "Gemfile", diff --git a/crates/zed/src/languages/rust/config.toml b/crates/zed/src/languages/rust/config.toml index 9382c9d78b..d01f62e354 100644 --- a/crates/zed/src/languages/rust/config.toml +++ b/crates/zed/src/languages/rust/config.toml @@ -1,4 +1,5 @@ name = "Rust" +grammar = "rust" path_suffixes = ["rs"] line_comments = ["// ", "/// ", "//! "] autoclose_before = ";:.,=}])>" diff --git a/crates/zed/src/languages/scheme/config.toml b/crates/zed/src/languages/scheme/config.toml index c080f6d2ee..abe6d043e9 100644 --- a/crates/zed/src/languages/scheme/config.toml +++ b/crates/zed/src/languages/scheme/config.toml @@ -1,4 +1,5 @@ name = "Scheme" +grammar = "scheme" path_suffixes = ["scm", "ss"] line_comments = ["; "] autoclose_before = "])" diff --git a/crates/zed/src/languages/svelte/config.toml b/crates/zed/src/languages/svelte/config.toml index ff450f3f84..a05f4f9a4f 100644 --- a/crates/zed/src/languages/svelte/config.toml +++ b/crates/zed/src/languages/svelte/config.toml @@ -1,4 +1,5 @@ name = "Svelte" +grammar = "svelte" path_suffixes = ["svelte"] block_comment = [""] autoclose_before = ";:.,=}])>" diff --git a/crates/zed/src/languages/terraform/config.toml b/crates/zed/src/languages/terraform/config.toml index 9ea5896001..09e52621a9 100644 --- a/crates/zed/src/languages/terraform/config.toml +++ b/crates/zed/src/languages/terraform/config.toml @@ -1,4 +1,5 @@ name = "Terraform" +grammar = "terraform" path_suffixes = ["tf", "tfvars"] line_comments = ["# ", "// "] block_comment = ["/*", "*/"] diff --git a/crates/zed/src/languages/toml/config.toml b/crates/zed/src/languages/toml/config.toml index 701dbbd65a..660d893ec2 100644 --- a/crates/zed/src/languages/toml/config.toml +++ b/crates/zed/src/languages/toml/config.toml @@ -1,4 +1,5 @@ name = "TOML" +grammar = "toml" path_suffixes = ["Cargo.lock", "toml"] line_comments = ["# "] autoclose_before = ",]}" diff --git a/crates/zed/src/languages/tsx/config.toml b/crates/zed/src/languages/tsx/config.toml index 6806924d4a..666c55d9a7 100644 --- a/crates/zed/src/languages/tsx/config.toml +++ b/crates/zed/src/languages/tsx/config.toml @@ -1,4 +1,5 @@ name = "TSX" +grammar = "tsx" path_suffixes = ["tsx"] line_comments = ["// "] autoclose_before = ";:.,=}])>" diff --git a/crates/zed/src/languages/typescript/config.toml b/crates/zed/src/languages/typescript/config.toml index 4d51a74098..8a9f2bc8f0 100644 --- a/crates/zed/src/languages/typescript/config.toml +++ b/crates/zed/src/languages/typescript/config.toml @@ -1,4 +1,5 @@ name = "TypeScript" +grammar = "typescript" path_suffixes = ["ts", "cts", "d.cts", "d.mts", "mts"] line_comments = ["// "] autoclose_before = ";:.,=}])>" diff --git a/crates/zed/src/languages/uiua/config.toml b/crates/zed/src/languages/uiua/config.toml index 88cd8c7ad0..31f9303659 100644 --- a/crates/zed/src/languages/uiua/config.toml +++ b/crates/zed/src/languages/uiua/config.toml @@ -1,4 +1,5 @@ name = "Uiua" +grammar = "uiua" path_suffixes = ["ua"] line_comments = ["# "] autoclose_before = ")]}\"" diff --git a/crates/zed/src/languages/vue/config.toml b/crates/zed/src/languages/vue/config.toml index c41a667b75..cf966d02d7 100644 --- a/crates/zed/src/languages/vue/config.toml +++ b/crates/zed/src/languages/vue/config.toml @@ -1,4 +1,5 @@ name = "Vue.js" +grammar = "vue" path_suffixes = ["vue"] block_comment = [""] autoclose_before = ";:.,=}])>" diff --git a/crates/zed/src/languages/yaml/config.toml b/crates/zed/src/languages/yaml/config.toml index dce8d68d66..a4588275c1 100644 --- a/crates/zed/src/languages/yaml/config.toml +++ b/crates/zed/src/languages/yaml/config.toml @@ -1,4 +1,5 @@ name = "YAML" +grammar = "yaml" path_suffixes = ["yml", "yaml"] line_comments = ["# "] autoclose_before = ",]}" diff --git a/crates/zed/src/languages/zig/config.toml b/crates/zed/src/languages/zig/config.toml index 6f72663280..2cf9cf79bc 100644 --- a/crates/zed/src/languages/zig/config.toml +++ b/crates/zed/src/languages/zig/config.toml @@ -1,4 +1,5 @@ name = "Zig" +grammar = "zig" path_suffixes = ["zig"] line_comments = ["// "] autoclose_before = ";:.,=}])>" diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index f010468d26..83eaa1de10 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -47,7 +47,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; @@ -173,6 +173,8 @@ fn main() { ); assistant::init(cx); + extension::init(fs.clone(), languages.clone(), ThemeRegistry::global(cx), cx); + load_user_themes_in_background(fs.clone(), cx); watch_themes(fs.clone(), cx); @@ -976,20 +978,13 @@ fn watch_themes(fs: Arc, cx: &mut AppContext) { .detach() } +#[cfg(debug_assertions)] async fn watch_languages(fs: Arc, languages: Arc) { 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(); @@ -1019,3 +1014,6 @@ fn watch_file_types(fs: Arc, cx: &mut AppContext) { #[cfg(not(debug_assertions))] fn watch_file_types(_fs: Arc, _cx: &mut AppContext) {} + +#[cfg(not(debug_assertions))] +async fn watch_languages(_fs: Arc, _languages: Arc) {} diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index b4ec471113..e9e64cff49 100644 --- a/crates/zed/src/zed.rs +++ b/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()),