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 04f18936cd774580903214dc2ed7578a6f21f111..2bbc846044f24319190183047ee58f69bb331efe 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 4587e947c6c56c65ae3cc16269435eb3cf0ca92a..c7e40436f41f84a9443dec02d68eceefc913bffc 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 d592ce88aedca8a42f8551e668bab6efeda85e41..c1a663d7efe363833752cf5138c8a317c6c79313 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 9042fda5be6d33d6858dbd7e9627957c85b884c1..f40a841f4c64ccce2bba2920df52f78b154f6fb9 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 0e11ec1684ff2b2c100dbcec6182200a4865987e..458f347efb9f1f18b4b019e35c165ba2c5d6cf70 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 eb0beb6f6653d7f1a5846e8584fc522ab44aad53..746f5aeeaf3b140f4f4e3aabcf4e81bcb12fcd77 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 53d47eb6b5b44e9a5e34518bcc305c9e27ed399f..37103a3382eae3b1df08fa97bbed0e834f0ad82a 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 23507eab3e4a5012626c54c178b4260d0a62b5cd..f975a833c1eec243cff9579ab125b8681bd72698 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 6b23e13d15bb64ba3678234071d2fafb02661bed..865fcb1a5c37d54f1a77067593eeb0f835d45585 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 1ed7700f37a56cd428015fdffbe25b78b2c02fad..787be1999e63d67ac3009b6bbf28aaf5f3d22a82 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 bc90b6face8e32da68c545242630d2c8f6700ce7..dc2616c6efebeef6249888ce06017716d4636f17 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 70c1699b83d090ef24c74f393cd8530502b7ce02..b083e63890da8ab3902dc8d1f0f21934b26460ab 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 0000000000000000000000000000000000000000..092f7b0bec7d6d7148855a2616da5e6fcf506c82 --- /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 0000000000000000000000000000000000000000..89e542f750cd3860a0598eff0dc34b56d7336dc4 --- /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 0000000000000000000000000000000000000000..9b9892641d73e8c8bb75d6f6ebc0a2c13ab9ff3a --- /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 0000000000000000000000000000000000000000..e95496b5298e153281b14aa72f4997b64c7a1dc1 --- /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 8c12c1c970c279279c4476d9ecf25012b65edab8..1aa1bcae950e2884cf30961df8c589781b475b67 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 821fa80840b182131d08c6c517e517a5dc0f6f83..328327a21298063f4ea150c6b8648d59315df953 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 05cec881269d3a70635089f2d14865da2200ee88..ccbab9d6d10bb23af6ac07b5c9e2c134abdcca9b 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 70e85761d2663bd2648c18c954b2c17a3512e053..eee05ea8e2b71cbddb453209d79bcc74b99be419 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 abf52c64e56663f64269cb97f6a16f15534b6f0d..0601a9f3c1d5efbbf383f97073573773c62486a7 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 24b23df88035f68caf9e49c807d56ca463b6e369..b00d1bb79a0b9282663d804caf2b033f602fa6ce 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 2e222c24fb2976a999c07285dd199c0edc89e1fa..4cb321f9c05938baee9f7510b965c898b0ce29f4 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 8aa7fac3594b48aee782fd8337e13cabeb8a1df2..bb09741f075bdbf129d7c39ca3509a65e3cd22f7 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 979151d4b26fa16a68cddc38f97cea936d513297..23ed45ff1d596ad0eef765a670f6e49495fdca55 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 2cc4d902a2d010777edad12e281f46332a9fcfcb..5a86c877f97878c6351abac7762a85958bf12b24 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 c6afabbbaa80046ea391e17ca85b522871f8c80c..02ad1fa1f0a17179c9d68450be4f45750d5ac043 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 cd839ae50e82e510ed481bdc9acfdd0b91e76318..dcf9270fb178ed1abbad0e6ac68620d5e13c36b3 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 c023faf6f7fb8795952984668787c179d24209af..1e9a5ba31e69699dee260994e6126c714790ffe5 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 1250aa92c74bfecfbc5c6fe1fec55a7393409e65..2931d537632afe93e6ae8f331146d74936029389 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 8b86318ecd13ab8b28529069d4145c3fc65d57c8..abbb95bda50e14ad712fe4edb3cb0078a7684220 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 722b8a39b74bb54eb71148a7d1e93d3134448dd2..fff6411e9dd944bdcacc52f41eae67a11d62448f 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 f99c0416cd90c81142e5af8e611dca27ebf4c70c..b41f469bd5a2fee758ff1afebdd88743a8b432eb 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 7630f1dd79cb31ad9cde6c868c5da667ebea1b4c..eecb09bc2036fa32a593d78c11b9c58216267e8d 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 7835283df496e705fbd9c9adf45a3a27cbf5f7cf..51a10c70e0ea2bfe12ba855e85b858646b2415e8 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 24a844c239da56b841036fefca82712a566db1fb..e22abe6d7056dc465b6c7b0743cd83deb23d2303 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 a3aab0ee8ae30b40d465ab70e8eb39f1ba7ee56e..81e92d45b0bdeec1f0cb31e7e3e50786569b7f2b 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 b95c85d444333a8b6fd813b14eec22da067bcc77..96fb2989ad60e05b266a8ebd1d0b1167a51e37b8 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 ebc45e9984b63dab1a960f96a0e2004a48a8a412..5ec987e139acaa2f53cc8a4c396571cd45cfecc1 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 5f92c0fe2744366f3a0b6a5f755f6f21548c84af..01eba93ff6f7c4a861d928d620db68135564ae32 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 70c68d9385cbcae49f409e8b04229673cc382aed..0b4d84069e7ef4fbade27ca4860ae3e3d6858309 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 a8418627092d8ddb7358d862c1f01262d1bf6371..0a472172ad64f8ddd3709659c9a7511c7408b161 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 9b60179662919849835b2ae5216217fa98441c88..01d7c88214cc2ae1c0599a2ba1628079013f0180 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 d07c46731a4891e4275c1401a77803fe23de12ae..cf29bfbb23fe46b34f4fd92aa7d301a265233555 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 80c252949dbaa3a5c1f0597ea507d204c7978ddf..19c7b20e8ccf2323d2f7651c8923fcec0df90d87 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 919f1db5127762c8cf02c9ecd3ce5ebf525a5925..79621ecdafb7a60d53bdbb17925a8857a2d026f0 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 7b1454431e317c9d97e52dd60e78a166ca89ce9f..3d83ab907de7c938a6486316cac3e58cbff23863 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 c7c2ebf6baba57d6f1a3efe7d00798073bd37659..891b2f38d4182cfd5fbcb3a741381be00eb12873 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 74cb5ac9ff5df179bf190aac8f843fd82820e29a..c28ffa16b025107685b4dd40e99266a8a0faa646 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 3c24490fc4abd8df6554895ca1df10b621796b76..389020b89d07f607b07c7cadf89588605c133e5b 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 a19d2588edbc66ab769d1769faec0854d133f6ba..a3acd1aac064b13478408148341e18660f256e82 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 eed86826d5ca0e6b2883dd9e7f2aa713ff408abd..34b3bc8bc6188dd80baa41298f62a60e1438bf63 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 31946f46e9ca8edbf71d06ad71c73b764b491e6f..19577fed9132f214702468a7dc4f80b149d38e28 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 98691a4ef73a4329a34fa745d40d0f56ebeb060e..e44ba6ec1aa0be33ead0db00ba11ea6bffc5f44a 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 20b3921271fbffa3b1f95a5d34ab18667fefb770..b62ead99b332af029534ddeaa8e88ceb87080411 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 63f5a615918b970f0c22f002c5722e923357e977..23f19cdcd23551a7c71ff3e81ce5e59119564997 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 f7401f774ce6cd196658f299f338903dc946a77d..fdbf1aad815124ffc1ed91ea95d014dd19b20fbe 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 522db6dae1f1f86e4edde7b5400dc16f9b69d2b5..313cbb46df99efc869435cd818cf7c51998541a8 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 9c6e9b767cb4e8be99a29fbf8e814fc570a490c9..db594f8a18b6298456fe65d2310a0cf6cbc0642d 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 81fe1bdfb4d3077428c7d1704315f021d33b8a7d..b8bccfd39b6a392dca4541ad0571b88ce4780917 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 ee9810dbb7b264f6b5413b0a5ac598e43ad7c446..350f4769786c9f8e9a0a097e649e80868121a94c 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 3496f845dcac235f6d572327affaa95a1b5476cb..d5254eac9098ed9f7fdc907340ae5795f6d529b2 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 aea5ec5fce85e59f93e0e166a689d2c62f31fa60..d5975a36e5ee8d89058d125a920fb2c3504569b8 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 8f2f1fab97863bd435cf5dc0ec3bb33d38965dcc..d3285d48d2b4f58b433fafa8e2801f53374aca9a 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 9382c9d78b279261039a101272ac416244789af7..d01f62e354ea483c634e16705603acf0c7e644f3 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 c080f6d2ee06f4df569e96caea14b5b9622202d3..abe6d043e93176ec30f74065dab252ba4bf07314 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 ff450f3f84795f32a32a5451bc977de85e42697b..a05f4f9a4faf6258e7a2a294a73d475644c51387 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 9ea58960014d1a6b936ec2c11fc8b762bc0039d6..09e52621a9bcb39c618f696a8c801cb7d0f395c7 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 701dbbd65a400ed57639ac088327ede527ed4b12..660d893ec21bdebc530762cb5b75b9e202ef161a 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 6806924d4a57f0383a2cf7bfb540a8bd9e1d0510..666c55d9a750710f467c59166554326ff3b2761f 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 4d51a740985a398a6c1627cf760ecb954a8a0e5e..8a9f2bc8f08a0363c7d5992e137144d12fb46c75 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 88cd8c7ad0ec94721c6f9ed18f6494ab55b02245..31f93036590cc85fdbc5865c57a106c05ec143be 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 c41a667b752c53bd2fed270490d2bc56fe1f04ed..cf966d02d775287ec98d052568c68ffa5e766ce8 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 dce8d68d6684d4bac5008a09245cf645a383e755..a4588275c16394155c3c9826a8342c9447a92291 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 6f726632802b85a8ac2ef4af949c519fe18927ec..2cf9cf79bc062832eb8e961eb34ec9fdad70c860 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 f010468d26a508cb7ee17a5db1c88b1f7d4da96d..83eaa1de10477ce108281dea5ab6d014eab8bda9 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 b4ec47111368a820b3e76cc700c484c8ebc129b5..e9e64cff49face0727e0a14add5724ce2d3a8f36 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()),