Detailed changes
@@ -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",
@@ -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" }
@@ -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()),
@@ -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()),
@@ -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()),
@@ -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()),
@@ -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,
@@ -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()),
@@ -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()
},
@@ -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 {
@@ -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()),
@@ -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(),
@@ -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"] }
@@ -0,0 +1 @@
+../../LICENSE-GPL
@@ -0,0 +1,344 @@
+use anyhow::Result;
+use collections::{HashMap, HashSet};
+use fs::Fs;
+use futures::StreamExt as _;
+use gpui::{actions, AppContext, Context, Global, Model, ModelContext, Task};
+use language::{
+ LanguageConfig, LanguageMatcher, LanguageQueries, LanguageRegistry, QUERY_FILENAME_PREFIXES,
+};
+use parking_lot::RwLock;
+use serde::{Deserialize, Serialize};
+use std::{
+ ffi::OsStr,
+ path::{Path, PathBuf},
+ sync::Arc,
+ time::Duration,
+};
+use theme::ThemeRegistry;
+use util::{paths::EXTENSIONS_DIR, ResultExt};
+
+#[cfg(test)]
+mod extension_store_test;
+
+pub struct ExtensionStore {
+ manifest: Arc<RwLock<Manifest>>,
+ fs: Arc<dyn Fs>,
+ extensions_dir: PathBuf,
+ manifest_path: PathBuf,
+ language_registry: Arc<LanguageRegistry>,
+ theme_registry: Arc<ThemeRegistry>,
+ _watch_extensions_dir: Task<()>,
+}
+
+struct GlobalExtensionStore(Model<ExtensionStore>);
+
+impl Global for GlobalExtensionStore {}
+
+#[derive(Deserialize, Serialize, Default)]
+pub struct Manifest {
+ pub grammars: HashMap<String, GrammarManifestEntry>,
+ pub languages: HashMap<Arc<str>, LanguageManifestEntry>,
+ pub themes: HashMap<String, ThemeManifestEntry>,
+}
+
+#[derive(PartialEq, Eq, Debug, PartialOrd, Ord, Deserialize, Serialize)]
+pub struct GrammarManifestEntry {
+ extension: String,
+ path: PathBuf,
+}
+
+#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Deserialize, Serialize)]
+pub struct LanguageManifestEntry {
+ extension: String,
+ path: PathBuf,
+ matcher: LanguageMatcher,
+}
+
+#[derive(Clone, PartialEq, Debug, Deserialize, Serialize)]
+pub struct ThemeManifestEntry {
+ extension: String,
+ path: PathBuf,
+}
+
+actions!(zed, [ReloadExtensions]);
+
+pub fn init(
+ fs: Arc<fs::RealFs>,
+ language_registry: Arc<LanguageRegistry>,
+ theme_registry: Arc<ThemeRegistry>,
+ cx: &mut AppContext,
+) {
+ let store = cx.new_model(|cx| {
+ ExtensionStore::new(
+ EXTENSIONS_DIR.clone(),
+ fs.clone(),
+ language_registry.clone(),
+ theme_registry,
+ cx,
+ )
+ });
+
+ cx.on_action(|_: &ReloadExtensions, cx| {
+ let store = cx.global::<GlobalExtensionStore>().0.clone();
+ store
+ .update(cx, |store, cx| store.reload(cx))
+ .detach_and_log_err(cx);
+ });
+
+ cx.set_global(GlobalExtensionStore(store));
+}
+
+impl ExtensionStore {
+ pub fn new(
+ extensions_dir: PathBuf,
+ fs: Arc<dyn Fs>,
+ language_registry: Arc<LanguageRegistry>,
+ theme_registry: Arc<ThemeRegistry>,
+ cx: &mut ModelContext<Self>,
+ ) -> Self {
+ let mut this = Self {
+ manifest: Default::default(),
+ extensions_dir: extensions_dir.join("installed"),
+ manifest_path: extensions_dir.join("manifest.json"),
+ fs,
+ language_registry,
+ theme_registry,
+ _watch_extensions_dir: Task::ready(()),
+ };
+ this._watch_extensions_dir = this.watch_extensions_dir(cx);
+ this.load(cx);
+ this
+ }
+
+ pub fn load(&mut self, cx: &mut ModelContext<Self>) {
+ let (manifest_content, manifest_metadata, extensions_metadata) =
+ cx.background_executor().block(async {
+ futures::join!(
+ self.fs.load(&self.manifest_path),
+ self.fs.metadata(&self.manifest_path),
+ self.fs.metadata(&self.extensions_dir),
+ )
+ });
+
+ if let Some(manifest_content) = manifest_content.log_err() {
+ if let Some(manifest) = serde_json::from_str(&manifest_content).log_err() {
+ self.manifest_updated(manifest, cx);
+ }
+ }
+
+ let should_reload = if let (Ok(Some(manifest_metadata)), Ok(Some(extensions_metadata))) =
+ (manifest_metadata, extensions_metadata)
+ {
+ extensions_metadata.mtime > manifest_metadata.mtime
+ } else {
+ true
+ };
+
+ if should_reload {
+ self.reload(cx).detach_and_log_err(cx);
+ }
+ }
+
+ fn manifest_updated(&mut self, manifest: Manifest, cx: &mut ModelContext<Self>) {
+ for (grammar_name, grammar) in &manifest.grammars {
+ let mut grammar_path = self.extensions_dir.clone();
+ grammar_path.extend([grammar.extension.as_ref(), grammar.path.as_path()]);
+ self.language_registry
+ .register_grammar(grammar_name.clone(), grammar_path);
+ }
+ for (language_name, language) in &manifest.languages {
+ let mut language_path = self.extensions_dir.clone();
+ language_path.extend([language.extension.as_ref(), language.path.as_path()]);
+ self.language_registry.register_extension(
+ language_path.into(),
+ language_name.clone(),
+ language.matcher.clone(),
+ load_plugin_queries,
+ );
+ }
+ let fs = self.fs.clone();
+ let root_dir = self.extensions_dir.clone();
+ let theme_registry = self.theme_registry.clone();
+ let themes = manifest.themes.clone();
+ cx.background_executor()
+ .spawn(async move {
+ for theme in themes.values() {
+ let mut theme_path = root_dir.clone();
+ theme_path.extend([theme.extension.as_ref(), theme.path.as_path()]);
+
+ theme_registry
+ .load_user_theme(&theme_path, fs.clone())
+ .await
+ .log_err();
+ }
+ })
+ .detach();
+ *self.manifest.write() = manifest;
+ }
+
+ fn watch_extensions_dir(&self, cx: &mut ModelContext<Self>) -> Task<()> {
+ let manifest = self.manifest.clone();
+ let fs = self.fs.clone();
+ let language_registry = self.language_registry.clone();
+ let extensions_dir = self.extensions_dir.clone();
+ cx.background_executor().spawn(async move {
+ let mut changed_languages = HashSet::default();
+ let mut events = fs.watch(&extensions_dir, Duration::from_millis(250)).await;
+ while let Some(events) = events.next().await {
+ changed_languages.clear();
+ let manifest = manifest.read();
+ for event in events {
+ for (language_name, language) in &manifest.languages {
+ let mut language_path = extensions_dir.clone();
+ language_path
+ .extend([language.extension.as_ref(), language.path.as_path()]);
+ if event.path.starts_with(&language_path) || event.path == language_path {
+ changed_languages.insert(language_name.clone());
+ }
+ }
+ }
+ language_registry.reload_languages(&changed_languages);
+ }
+ })
+ }
+
+ pub fn reload(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
+ let fs = self.fs.clone();
+ let extensions_dir = self.extensions_dir.clone();
+ let manifest_path = self.manifest_path.clone();
+ cx.spawn(|this, mut cx| async move {
+ let manifest = cx
+ .background_executor()
+ .spawn(async move {
+ let mut manifest = Manifest::default();
+
+ let mut extension_paths = fs.read_dir(&extensions_dir).await?;
+ while let Some(extension_dir) = extension_paths.next().await {
+ let extension_dir = extension_dir?;
+ let Some(extension_name) =
+ extension_dir.file_name().and_then(OsStr::to_str)
+ else {
+ continue;
+ };
+
+ if let Ok(mut grammar_paths) =
+ fs.read_dir(&extension_dir.join("grammars")).await
+ {
+ while let Some(grammar_path) = grammar_paths.next().await {
+ let grammar_path = grammar_path?;
+ let Ok(relative_path) = grammar_path.strip_prefix(&extension_dir)
+ else {
+ continue;
+ };
+ let Some(grammar_name) =
+ grammar_path.file_stem().and_then(OsStr::to_str)
+ else {
+ continue;
+ };
+
+ manifest.grammars.insert(
+ grammar_name.into(),
+ GrammarManifestEntry {
+ extension: extension_name.into(),
+ path: relative_path.into(),
+ },
+ );
+ }
+ }
+
+ if let Ok(mut language_paths) =
+ fs.read_dir(&extension_dir.join("languages")).await
+ {
+ while let Some(language_path) = language_paths.next().await {
+ let language_path = language_path?;
+ let Ok(relative_path) = language_path.strip_prefix(&extension_dir)
+ else {
+ continue;
+ };
+ let config = fs.load(&language_path.join("config.toml")).await?;
+ let config = ::toml::from_str::<LanguageConfig>(&config)?;
+
+ manifest.languages.insert(
+ config.name.clone(),
+ LanguageManifestEntry {
+ extension: extension_name.into(),
+ path: relative_path.into(),
+ matcher: config.matcher,
+ },
+ );
+ }
+ }
+
+ if let Ok(mut theme_paths) =
+ fs.read_dir(&extension_dir.join("themes")).await
+ {
+ while let Some(theme_path) = theme_paths.next().await {
+ let theme_path = theme_path?;
+ let Ok(relative_path) = theme_path.strip_prefix(&extension_dir)
+ else {
+ continue;
+ };
+
+ let Some(theme_family) =
+ ThemeRegistry::read_user_theme(&theme_path, fs.clone())
+ .await
+ .log_err()
+ else {
+ continue;
+ };
+
+ for theme in theme_family.themes {
+ let location = ThemeManifestEntry {
+ extension: extension_name.into(),
+ path: relative_path.into(),
+ };
+
+ manifest.themes.insert(theme.name, location);
+ }
+ }
+ }
+ }
+
+ fs.save(
+ &manifest_path,
+ &serde_json::to_string_pretty(&manifest)?.as_str().into(),
+ Default::default(),
+ )
+ .await?;
+
+ anyhow::Ok(manifest)
+ })
+ .await?;
+ this.update(&mut cx, |this, cx| this.manifest_updated(manifest, cx))
+ })
+ }
+}
+
+fn load_plugin_queries(root_path: &Path) -> LanguageQueries {
+ let mut result = LanguageQueries::default();
+ if let Some(entries) = std::fs::read_dir(root_path).log_err() {
+ for entry in entries {
+ let Some(entry) = entry.log_err() else {
+ continue;
+ };
+ let path = entry.path();
+ if let Some(remainder) = path.strip_prefix(root_path).ok().and_then(|p| p.to_str()) {
+ if !remainder.ends_with(".scm") {
+ continue;
+ }
+ for (name, query) in QUERY_FILENAME_PREFIXES {
+ if remainder.starts_with(name) {
+ if let Some(contents) = std::fs::read_to_string(&path).log_err() {
+ match query(&mut result) {
+ None => *query(&mut result) = Some(contents.into()),
+ Some(r) => r.to_mut().push_str(contents.as_ref()),
+ }
+ }
+ break;
+ }
+ }
+ }
+ }
+ }
+ result
+}
@@ -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);
+ });
+}
@@ -5,7 +5,7 @@ use util::arc_cow::ArcCow;
/// A shared string is an immutable string that can be cheaply cloned in GPUI
/// tasks. Essentially an abstraction over an `Arc<str>` and `&'static str`,
-#[derive(Deref, DerefMut, Eq, PartialEq, Hash, Clone)]
+#[derive(Deref, DerefMut, Eq, PartialEq, PartialOrd, Ord, Hash, Clone)]
pub struct SharedString(ArcCow<'static, str>);
impl Default for SharedString {
@@ -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
@@ -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()),
@@ -20,7 +20,7 @@ pub mod markdown;
use anyhow::{anyhow, Context, Result};
use async_trait::async_trait;
-use collections::{HashMap, HashSet};
+use collections::{hash_map, HashMap, HashSet};
use futures::{
channel::{mpsc, oneshot},
future::Shared,
@@ -33,12 +33,13 @@ use lsp::{CodeActionKind, LanguageServerBinary};
use parking_lot::{Mutex, RwLock};
use postage::watch;
use regex::Regex;
-use serde::{de, Deserialize, Deserializer};
+use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
use serde_json::Value;
use std::{
any::Any,
borrow::Cow,
cell::RefCell,
+ ffi::OsStr,
fmt::Debug,
hash::Hash,
mem,
@@ -392,14 +393,13 @@ pub struct LanguageConfig {
/// Human-readable name of the language.
pub name: Arc<str>,
// The name of the grammar in a WASM bundle (experimental).
- pub grammar_name: Option<Arc<str>>,
- /// Given a list of `LanguageConfig`'s, the language of a file can be determined based on the path extension matching any of the `path_suffixes`.
- pub path_suffixes: Vec<String>,
+ pub grammar: Option<Arc<str>>,
+ /// The criteria for matching this language to a given file.
+ #[serde(flatten)]
+ pub matcher: LanguageMatcher,
/// List of bracket types in a language.
+ #[serde(default)]
pub brackets: BracketPairConfig,
- /// A regex pattern that determines whether the language should be assigned to a file or not.
- #[serde(default, deserialize_with = "deserialize_regex")]
- pub first_line_pattern: Option<Regex>,
/// If set to true, auto indentation uses last non empty line to determine
/// the indentation level for a new line.
#[serde(default = "auto_indent_using_last_non_empty_line_default")]
@@ -443,6 +443,34 @@ pub struct LanguageConfig {
pub prettier_parser_name: Option<String>,
}
+#[derive(Clone, Debug, Serialize, Deserialize, Default)]
+pub struct LanguageMatcher {
+ /// Given a list of `LanguageConfig`'s, the language of a file can be determined based on the path extension matching any of the `path_suffixes`.
+ #[serde(default)]
+ pub path_suffixes: Vec<String>,
+ /// A regex pattern that determines whether the language should be assigned to a file or not.
+ #[serde(
+ default,
+ serialize_with = "serialize_regex",
+ deserialize_with = "deserialize_regex"
+ )]
+ pub first_line_pattern: Option<Regex>,
+}
+
+pub const QUERY_FILENAME_PREFIXES: &[(
+ &str,
+ fn(&mut LanguageQueries) -> &mut Option<Cow<'static, str>>,
+)] = &[
+ ("highlights", |q| &mut q.highlights),
+ ("brackets", |q| &mut q.brackets),
+ ("outline", |q| &mut q.outline),
+ ("indents", |q| &mut q.indents),
+ ("embedding", |q| &mut q.embedding),
+ ("injections", |q| &mut q.injections),
+ ("overrides", |q| &mut q.overrides),
+ ("redactions", |q| &mut q.redactions),
+];
+
/// Tree-sitter language queries for a given language.
#[derive(Debug, Default)]
pub struct LanguageQueries {
@@ -506,11 +534,10 @@ impl Default for LanguageConfig {
fn default() -> Self {
Self {
name: "".into(),
- grammar_name: None,
- path_suffixes: Default::default(),
+ grammar: None,
+ matcher: LanguageMatcher::default(),
brackets: Default::default(),
auto_indent_using_last_non_empty_line: auto_indent_using_last_non_empty_line_default(),
- first_line_pattern: Default::default(),
increase_indent_pattern: Default::default(),
decrease_indent_pattern: Default::default(),
autoclose_before: Default::default(),
@@ -538,6 +565,16 @@ fn deserialize_regex<'de, D: Deserializer<'de>>(d: D) -> Result<Option<Regex>, D
}
}
+fn serialize_regex<S>(regex: &Option<Regex>, serializer: S) -> Result<S::Ok, S::Error>
+where
+ S: Serializer,
+{
+ match regex {
+ Some(regex) => serializer.serialize_str(regex.as_str()),
+ None => serializer.serialize_none(),
+ }
+}
+
#[doc(hidden)]
#[cfg(any(test, feature = "test-support"))]
pub struct FakeLspAdapter {
@@ -702,22 +739,29 @@ type AvailableLanguageId = usize;
#[derive(Clone)]
struct AvailableLanguage {
id: AvailableLanguageId,
- config: LanguageConfig,
- grammar: AvailableGrammar,
+ name: Arc<str>,
+ source: AvailableLanguageSource,
lsp_adapters: Vec<Arc<dyn LspAdapter>>,
loaded: bool,
}
-#[derive(Clone)]
enum AvailableGrammar {
- Native {
- grammar: tree_sitter::Language,
+ Loaded(tree_sitter::Language),
+ Loading(Vec<oneshot::Sender<Result<tree_sitter::Language>>>),
+ Unloaded(PathBuf),
+}
+
+#[derive(Clone)]
+enum AvailableLanguageSource {
+ BuiltIn {
asset_dir: &'static str,
get_queries: fn(&str) -> LanguageQueries,
+ config: LanguageConfig,
},
- Wasm {
+ Extension {
path: Arc<Path>,
get_queries: fn(&Path) -> LanguageQueries,
+ matcher: LanguageMatcher,
},
}
@@ -737,6 +781,7 @@ struct LanguageRegistryState {
next_language_server_id: usize,
languages: Vec<Arc<Language>>,
available_languages: Vec<AvailableLanguage>,
+ grammars: HashMap<String, AvailableGrammar>,
next_available_language_id: AvailableLanguageId,
loading_languages: HashMap<AvailableLanguageId, Vec<oneshot::Sender<Result<Arc<Language>>>>>,
subscription: (watch::Sender<()>, watch::Receiver<()>),
@@ -758,6 +803,7 @@ impl LanguageRegistry {
next_language_server_id: 0,
languages: vec![PLAIN_TEXT.clone()],
available_languages: Default::default(),
+ grammars: Default::default(),
next_available_language_id: 0,
loading_languages: Default::default(),
subscription: watch::channel(),
@@ -787,20 +833,24 @@ impl LanguageRegistry {
self.state.write().reload();
}
+ /// Clear out the given languages and reload them from scratch.
+ pub fn reload_languages(&self, languages: &HashSet<Arc<str>>) {
+ self.state.write().reload_languages(languages);
+ }
+
pub fn register(
&self,
asset_dir: &'static str,
config: LanguageConfig,
- grammar: tree_sitter::Language,
lsp_adapters: Vec<Arc<dyn LspAdapter>>,
get_queries: fn(&str) -> LanguageQueries,
) {
let state = &mut *self.state.write();
state.available_languages.push(AvailableLanguage {
id: post_inc(&mut state.next_available_language_id),
- config,
- grammar: AvailableGrammar::Native {
- grammar,
+ name: config.name.clone(),
+ source: AvailableLanguageSource::BuiltIn {
+ config,
get_queries,
asset_dir,
},
@@ -809,28 +859,63 @@ impl LanguageRegistry {
});
}
- pub fn register_wasm(
+ pub fn register_extension(
&self,
path: Arc<Path>,
- config: LanguageConfig,
+ name: Arc<str>,
+ matcher: LanguageMatcher,
get_queries: fn(&Path) -> LanguageQueries,
) {
let state = &mut *self.state.write();
+ let source = AvailableLanguageSource::Extension {
+ path,
+ get_queries,
+ matcher,
+ };
+ for existing_language in &mut state.available_languages {
+ if existing_language.name == name
+ && matches!(
+ existing_language.source,
+ AvailableLanguageSource::Extension { .. }
+ )
+ {
+ existing_language.source = source;
+ return;
+ }
+ }
state.available_languages.push(AvailableLanguage {
id: post_inc(&mut state.next_available_language_id),
- config,
- grammar: AvailableGrammar::Wasm { path, get_queries },
+ name,
+ source,
lsp_adapters: Vec::new(),
loaded: false,
});
}
+ pub fn add_grammars(
+ &self,
+ grammars: impl IntoIterator<Item = (impl Into<String>, tree_sitter::Language)>,
+ ) {
+ self.state.write().grammars.extend(
+ grammars
+ .into_iter()
+ .map(|(name, grammar)| (name.into(), AvailableGrammar::Loaded(grammar))),
+ );
+ }
+
+ pub fn register_grammar(&self, name: String, path: PathBuf) {
+ self.state
+ .write()
+ .grammars
+ .insert(name, AvailableGrammar::Unloaded(path));
+ }
+
pub fn language_names(&self) -> Vec<String> {
let state = self.state.read();
let mut result = state
.available_languages
.iter()
- .filter_map(|l| l.loaded.not().then_some(l.config.name.to_string()))
+ .filter_map(|l| l.loaded.not().then_some(l.name.to_string()))
.chain(state.languages.iter().map(|l| l.config.name.to_string()))
.collect::<Vec<_>>();
result.sort_unstable_by_key(|language_name| language_name.to_lowercase());
@@ -873,7 +958,7 @@ impl LanguageRegistry {
name: &str,
) -> UnwrapFuture<oneshot::Receiver<Result<Arc<Language>>>> {
let name = UniCase::new(name);
- self.get_or_load_language(|config| UniCase::new(config.name.as_ref()) == name)
+ self.get_or_load_language(|language_name, _| UniCase::new(language_name) == name)
}
pub fn language_for_name_or_extension(
@@ -881,8 +966,8 @@ impl LanguageRegistry {
string: &str,
) -> UnwrapFuture<oneshot::Receiver<Result<Arc<Language>>>> {
let string = UniCase::new(string);
- self.get_or_load_language(|config| {
- UniCase::new(config.name.as_ref()) == string
+ self.get_or_load_language(|name, config| {
+ UniCase::new(name) == string
|| config
.path_suffixes
.iter()
@@ -899,7 +984,7 @@ impl LanguageRegistry {
let filename = path.file_name().and_then(|name| name.to_str());
let extension = path.extension_or_hidden_file_name();
let path_suffixes = [extension, filename];
- self.get_or_load_language(|config| {
+ self.get_or_load_language(|_, config| {
let path_matches = config
.path_suffixes
.iter()
@@ -919,7 +1004,7 @@ impl LanguageRegistry {
fn get_or_load_language(
self: &Arc<Self>,
- callback: impl Fn(&LanguageConfig) -> bool,
+ callback: impl Fn(&str, &LanguageMatcher) -> bool,
) -> UnwrapFuture<oneshot::Receiver<Result<Arc<Language>>>> {
let (tx, rx) = oneshot::channel();
@@ -927,52 +1012,60 @@ impl LanguageRegistry {
if let Some(language) = state
.languages
.iter()
- .find(|language| callback(&language.config))
+ .find(|language| callback(language.config.name.as_ref(), &language.config.matcher))
{
let _ = tx.send(Ok(language.clone()));
} else if let Some(executor) = self.executor.clone() {
if let Some(language) = state
.available_languages
.iter()
- .find(|l| !l.loaded && callback(&l.config))
+ .rfind(|l| {
+ !l.loaded
+ && match &l.source {
+ AvailableLanguageSource::BuiltIn { config, .. } => {
+ callback(l.name.as_ref(), &config.matcher)
+ }
+ AvailableLanguageSource::Extension { matcher, .. } => {
+ callback(l.name.as_ref(), &matcher)
+ }
+ }
+ })
.cloned()
{
- let txs = state
- .loading_languages
- .entry(language.id)
- .or_insert_with(|| {
+ match state.loading_languages.entry(language.id) {
+ hash_map::Entry::Occupied(mut entry) => entry.get_mut().push(tx),
+ hash_map::Entry::Vacant(entry) => {
let this = self.clone();
executor
.spawn(async move {
let id = language.id;
- let name = language.config.name.clone();
+ let name = language.name.clone();
let language = async {
- let (grammar, queries) = match language.grammar {
- AvailableGrammar::Native {
- grammar,
+ let (config, queries) = match language.source {
+ AvailableLanguageSource::BuiltIn {
asset_dir,
get_queries,
- } => (grammar, (get_queries)(asset_dir)),
- AvailableGrammar::Wasm { path, get_queries } => {
- let grammar_name =
- &language.config.grammar_name.as_ref().ok_or_else(
- || anyhow!("missing grammar name"),
- )?;
- let mut wasm_path = path.join(grammar_name.as_ref());
- wasm_path.set_extension("wasm");
- let wasm_bytes = std::fs::read(&wasm_path)?;
- let grammar = PARSER.with(|parser| {
- let mut parser = parser.borrow_mut();
- let mut store = parser.take_wasm_store().unwrap();
- let grammar =
- store.load_language(&grammar_name, &wasm_bytes);
- parser.set_wasm_store(store).unwrap();
- grammar
- })?;
- (grammar, get_queries(path.as_ref()))
+ config,
+ } => (config, (get_queries)(asset_dir)),
+ AvailableLanguageSource::Extension {
+ path,
+ get_queries,
+ ..
+ } => {
+ let config = std::fs::read(path.join("config.toml"));
+ let config: LanguageConfig =
+ ::toml::from_slice(&config?)?;
+ (config, get_queries(path.as_ref()))
}
};
- Language::new(language.config, Some(grammar))
+
+ let grammar = if let Some(grammar) = config.grammar.clone() {
+ Some(this.get_or_load_grammar(grammar).await?)
+ } else {
+ None
+ };
+
+ Language::new(config, grammar)
.with_lsp_adapters(language.lsp_adapters)
.await
.with_queries(queries)
@@ -1009,10 +1102,9 @@ impl LanguageRegistry {
};
})
.detach();
-
- Vec::new()
- });
- txs.push(tx);
+ entry.insert(vec![tx]);
+ }
+ }
} else {
let _ = tx.send(Err(anyhow!("language not found")));
}
@@ -1023,6 +1115,65 @@ impl LanguageRegistry {
rx.unwrap()
}
+ fn get_or_load_grammar(
+ self: &Arc<Self>,
+ name: Arc<str>,
+ ) -> UnwrapFuture<oneshot::Receiver<Result<tree_sitter::Language>>> {
+ let (tx, rx) = oneshot::channel();
+ let mut state = self.state.write();
+
+ if let Some(grammar) = state.grammars.get_mut(name.as_ref()) {
+ match grammar {
+ AvailableGrammar::Loaded(grammar) => {
+ tx.send(Ok(grammar.clone())).ok();
+ }
+ AvailableGrammar::Loading(txs) => {
+ txs.push(tx);
+ }
+ AvailableGrammar::Unloaded(wasm_path) => {
+ if let Some(executor) = &self.executor {
+ let this = self.clone();
+ let wasm_path = wasm_path.clone();
+ executor
+ .spawn(async move {
+ let wasm_bytes = std::fs::read(&wasm_path)?;
+ let grammar_name = wasm_path
+ .file_stem()
+ .and_then(OsStr::to_str)
+ .ok_or_else(|| anyhow!("invalid grammar filename"))?;
+ let grammar = PARSER.with(|parser| {
+ let mut parser = parser.borrow_mut();
+ let mut store = parser.take_wasm_store().unwrap();
+ let grammar = store.load_language(&grammar_name, &wasm_bytes);
+ parser.set_wasm_store(store).unwrap();
+ grammar
+ })?;
+
+ if let Some(AvailableGrammar::Loading(txs)) =
+ this.state.write().grammars.insert(
+ name.to_string(),
+ AvailableGrammar::Loaded(grammar.clone()),
+ )
+ {
+ for tx in txs {
+ tx.send(Ok(grammar.clone())).ok();
+ }
+ }
+
+ anyhow::Ok(())
+ })
+ .detach();
+ *grammar = AvailableGrammar::Loading(vec![tx]);
+ }
+ }
+ }
+ } else {
+ tx.send(Err(anyhow!("no such grammar {}", name))).ok();
+ }
+
+ rx.unwrap()
+ }
+
pub fn to_vec(&self) -> Vec<Arc<Language>> {
self.state.read().languages.iter().cloned().collect()
}
@@ -1206,6 +1357,19 @@ impl LanguageRegistryState {
*self.subscription.0.borrow_mut() = ();
}
+ fn reload_languages(&mut self, languages: &HashSet<Arc<str>>) {
+ self.languages
+ .retain(|language| !languages.contains(&language.config.name));
+ self.version += 1;
+ self.reload_count += 1;
+ for language in &mut self.available_languages {
+ if languages.contains(&language.name) {
+ language.loaded = false;
+ }
+ }
+ *self.subscription.0.borrow_mut() = ();
+ }
+
/// Mark the given language a having been loaded, so that the
/// language registry won't try to load it again.
fn mark_language_loaded(&mut self, id: AvailableLanguageId) {
@@ -1720,7 +1884,7 @@ impl Language {
}
pub fn path_suffixes(&self) -> &[String] {
- &self.config.path_suffixes
+ &self.config.matcher.path_suffixes
}
pub fn should_autoclose_before(&self, c: char) -> bool {
@@ -1911,6 +2075,33 @@ impl CodeLabel {
}
}
+impl Ord for LanguageMatcher {
+ fn cmp(&self, other: &Self) -> std::cmp::Ordering {
+ self.path_suffixes.cmp(&other.path_suffixes).then_with(|| {
+ self.first_line_pattern
+ .as_ref()
+ .map(Regex::as_str)
+ .cmp(&other.first_line_pattern.as_ref().map(Regex::as_str))
+ })
+ }
+}
+
+impl PartialOrd for LanguageMatcher {
+ fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
+ Some(self.cmp(other))
+ }
+}
+
+impl Eq for LanguageMatcher {}
+
+impl PartialEq for LanguageMatcher {
+ fn eq(&self, other: &Self) -> bool {
+ self.path_suffixes == other.path_suffixes
+ && self.first_line_pattern.as_ref().map(Regex::as_str)
+ == other.first_line_pattern.as_ref().map(Regex::as_str)
+ }
+}
+
#[cfg(any(test, feature = "test-support"))]
impl Default for FakeLspAdapter {
fn default() -> Self {
@@ -2034,11 +2225,12 @@ mod tests {
"/javascript",
LanguageConfig {
name: "JavaScript".into(),
- path_suffixes: vec!["js".into()],
- first_line_pattern: Some(Regex::new(r"\bnode\b").unwrap()),
+ matcher: LanguageMatcher {
+ path_suffixes: vec!["js".into()],
+ first_line_pattern: Some(Regex::new(r"\bnode\b").unwrap()),
+ },
..Default::default()
},
- tree_sitter_typescript::language_tsx(),
vec![],
|_| Default::default(),
);
@@ -2067,14 +2259,21 @@ mod tests {
let mut languages = LanguageRegistry::test();
languages.set_executor(cx.executor());
let languages = Arc::new(languages);
+ languages.add_grammars([
+ ("json", tree_sitter_json::language()),
+ ("rust", tree_sitter_rust::language()),
+ ]);
languages.register(
"/JSON",
LanguageConfig {
name: "JSON".into(),
- path_suffixes: vec!["json".into()],
+ grammar: Some("json".into()),
+ matcher: LanguageMatcher {
+ path_suffixes: vec!["json".into()],
+ ..Default::default()
+ },
..Default::default()
},
- tree_sitter_json::language(),
vec![],
|_| Default::default(),
);
@@ -2082,10 +2281,13 @@ mod tests {
"/rust",
LanguageConfig {
name: "Rust".into(),
- path_suffixes: vec!["rs".into()],
+ grammar: Some("rust".into()),
+ matcher: LanguageMatcher {
+ path_suffixes: vec!["rs".into()],
+ ..Default::default()
+ },
..Default::default()
},
- tree_sitter_rust::language(),
vec![],
|_| Default::default(),
);
@@ -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()),
@@ -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()),
@@ -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()),
@@ -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,
@@ -7,7 +7,7 @@ use crate::{
use ai::test::FakeEmbeddingProvider;
use gpui::{Task, TestAppContext};
-use language::{Language, LanguageConfig, LanguageRegistry, ToOffset};
+use language::{Language, LanguageConfig, LanguageMatcher, LanguageRegistry, ToOffset};
use parking_lot::Mutex;
use pretty_assertions::assert_eq;
use project::{project_settings::ProjectSettings, FakeFs, Fs, Project};
@@ -1251,7 +1251,10 @@ fn js_lang() -> Arc<Language> {
Language::new(
LanguageConfig {
name: "Javascript".into(),
- path_suffixes: vec!["js".into()],
+ matcher: LanguageMatcher {
+ path_suffixes: vec!["js".into()],
+ ..Default::default()
+ },
..Default::default()
},
Some(tree_sitter_typescript::language_tsx()),
@@ -1343,7 +1346,10 @@ fn rust_lang() -> Arc<Language> {
Language::new(
LanguageConfig {
name: "Rust".into(),
- path_suffixes: vec!["rs".into()],
+ matcher: LanguageMatcher {
+ path_suffixes: vec!["rs".into()],
+ ..Default::default()
+ },
collapsed_placeholder: " /* ... */ ".to_string(),
..Default::default()
},
@@ -1393,7 +1399,10 @@ fn json_lang() -> Arc<Language> {
Language::new(
LanguageConfig {
name: "JSON".into(),
- path_suffixes: vec!["json".into()],
+ matcher: LanguageMatcher {
+ path_suffixes: vec!["json".into()],
+ ..Default::default()
+ },
..Default::default()
},
Some(tree_sitter_json::language()),
@@ -1421,7 +1430,10 @@ fn toml_lang() -> Arc<Language> {
Arc::new(Language::new(
LanguageConfig {
name: "TOML".into(),
- path_suffixes: vec!["toml".into()],
+ matcher: LanguageMatcher {
+ path_suffixes: vec!["toml".into()],
+ ..Default::default()
+ },
..Default::default()
},
Some(tree_sitter_toml::language()),
@@ -1433,7 +1445,10 @@ fn cpp_lang() -> Arc<Language> {
Language::new(
LanguageConfig {
name: "CPP".into(),
- path_suffixes: vec!["cpp".into()],
+ matcher: LanguageMatcher {
+ path_suffixes: vec!["cpp".into()],
+ ..Default::default()
+ },
..Default::default()
},
Some(tree_sitter_cpp::language()),
@@ -1513,7 +1528,10 @@ fn lua_lang() -> Arc<Language> {
Language::new(
LanguageConfig {
name: "Lua".into(),
- path_suffixes: vec!["lua".into()],
+ matcher: LanguageMatcher {
+ path_suffixes: vec!["lua".into()],
+ ..Default::default()
+ },
collapsed_placeholder: "--[ ... ]--".to_string(),
..Default::default()
},
@@ -1542,7 +1560,10 @@ fn php_lang() -> Arc<Language> {
Language::new(
LanguageConfig {
name: "PHP".into(),
- path_suffixes: vec!["php".into()],
+ matcher: LanguageMatcher {
+ path_suffixes: vec!["php".into()],
+ ..Default::default()
+ },
collapsed_placeholder: "/* ... */".into(),
..Default::default()
},
@@ -1597,7 +1618,10 @@ fn ruby_lang() -> Arc<Language> {
Language::new(
LanguageConfig {
name: "Ruby".into(),
- path_suffixes: vec!["rb".into()],
+ matcher: LanguageMatcher {
+ path_suffixes: vec!["rb".into()],
+ ..Default::default()
+ },
collapsed_placeholder: "# ...".to_string(),
..Default::default()
},
@@ -1638,7 +1662,10 @@ fn elixir_lang() -> Arc<Language> {
Language::new(
LanguageConfig {
name: "Elixir".into(),
- path_suffixes: vec!["rs".into()],
+ matcher: LanguageMatcher {
+ path_suffixes: vec!["rs".into()],
+ ..Default::default()
+ },
..Default::default()
},
Some(tree_sitter_elixir::language()),
@@ -194,7 +194,9 @@ impl ThemeRegistry {
}
pub fn list_names(&self, _staff: bool) -> Vec<SharedString> {
- self.state.read().themes.keys().cloned().collect()
+ let mut names = self.state.read().themes.keys().cloned().collect::<Vec<_>>();
+ names.sort();
+ names
}
pub fn list(&self, _staff: bool) -> Vec<ThemeMeta> {
@@ -263,11 +265,17 @@ impl ThemeRegistry {
Ok(())
}
- /// Loads the user theme from the specified path and adds it to the registry.
- pub async fn load_user_theme(&self, theme_path: &Path, fs: Arc<dyn Fs>) -> Result<()> {
+ pub async fn read_user_theme(theme_path: &Path, fs: Arc<dyn Fs>) -> Result<ThemeFamilyContent> {
let reader = fs.open_sync(&theme_path).await?;
let theme = serde_json_lenient::from_reader(reader)?;
+ Ok(theme)
+ }
+
+ /// Loads the user theme from the specified path and adds it to the registry.
+ pub async fn load_user_theme(&self, theme_path: &Path, fs: Arc<dyn Fs>) -> Result<()> {
+ let theme = Self::read_user_theme(theme_path, fs).await?;
+
self.insert_user_theme_families([theme]);
Ok(())
@@ -1,5 +1,6 @@
use std::{
borrow::Cow,
+ cmp::Ordering,
fmt::{self, Debug},
hash::{Hash, Hasher},
sync::Arc,
@@ -18,6 +19,18 @@ impl<'a, T: ?Sized + PartialEq> PartialEq for ArcCow<'a, T> {
}
}
+impl<'a, T: ?Sized + PartialOrd> PartialOrd for ArcCow<'a, T> {
+ fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
+ self.as_ref().partial_cmp(other.as_ref())
+ }
+}
+
+impl<'a, T: ?Sized + Ord> Ord for ArcCow<'a, T> {
+ fn cmp(&self, other: &Self) -> Ordering {
+ self.as_ref().cmp(other.as_ref())
+ }
+}
+
impl<'a, T: ?Sized + Eq> Eq for ArcCow<'a, T> {}
impl<'a, T: ?Sized + Hash> Hash for ArcCow<'a, T> {
@@ -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");
@@ -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
@@ -4,8 +4,8 @@ pub use language::*;
use node_runtime::NodeRuntime;
use rust_embed::RustEmbed;
use settings::Settings;
-use std::{borrow::Cow, fs, path::Path, str, sync::Arc};
-use util::{asset_str, paths::PLUGINS_DIR, ResultExt};
+use std::{str, sync::Arc};
+use util::asset_str;
use self::{deno::DenoSettings, elixir::ElixirSettings};
@@ -62,30 +62,69 @@ pub fn init(
ElixirSettings::register(cx);
DenoSettings::register(cx);
- let language = |name, grammar, adapters| {
- languages.register(name, load_config(name), grammar, adapters, load_queries)
+ languages.add_grammars([
+ ("bash", tree_sitter_bash::language()),
+ ("beancount", tree_sitter_beancount::language()),
+ ("c", tree_sitter_c::language()),
+ ("c_sharp", tree_sitter_c_sharp::language()),
+ ("cpp", tree_sitter_cpp::language()),
+ ("css", tree_sitter_css::language()),
+ ("elixir", tree_sitter_elixir::language()),
+ ("elm", tree_sitter_elm::language()),
+ (
+ "embedded_template",
+ tree_sitter_embedded_template::language(),
+ ),
+ ("erlang", tree_sitter_erlang::language()),
+ ("gitcommit", tree_sitter_gitcommit::language()),
+ ("gleam", tree_sitter_gleam::language()),
+ ("glsl", tree_sitter_glsl::language()),
+ ("go", tree_sitter_go::language()),
+ ("gomod", tree_sitter_gomod::language()),
+ ("gowork", tree_sitter_gowork::language()),
+ ("haskell", tree_sitter_haskell::language()),
+ ("hcl", tree_sitter_hcl::language()),
+ ("heex", tree_sitter_heex::language()),
+ ("html", tree_sitter_html::language()),
+ ("json", tree_sitter_json::language()),
+ ("lua", tree_sitter_lua::language()),
+ ("markdown", tree_sitter_markdown::language()),
+ ("nix", tree_sitter_nix::language()),
+ ("nu", tree_sitter_nu::language()),
+ ("ocaml", tree_sitter_ocaml::language_ocaml()),
+ (
+ "ocaml_interface",
+ tree_sitter_ocaml::language_ocaml_interface(),
+ ),
+ ("php", tree_sitter_php::language_php()),
+ ("proto", tree_sitter_proto::language()),
+ ("purescript", tree_sitter_purescript::language()),
+ ("python", tree_sitter_python::language()),
+ ("racket", tree_sitter_racket::language()),
+ ("ruby", tree_sitter_ruby::language()),
+ ("rust", tree_sitter_rust::language()),
+ ("scheme", tree_sitter_scheme::language()),
+ ("svelte", tree_sitter_svelte::language()),
+ ("toml", tree_sitter_toml::language()),
+ ("tsx", tree_sitter_typescript::language_tsx()),
+ ("typescript", tree_sitter_typescript::language_typescript()),
+ ("uiua", tree_sitter_uiua::language()),
+ ("vue", tree_sitter_vue::language()),
+ ("yaml", tree_sitter_yaml::language()),
+ ("zig", tree_sitter_zig::language()),
+ ]);
+
+ let language = |name: &'static str, adapters| {
+ languages.register(name, load_config(name), adapters, load_queries)
};
- language("bash", tree_sitter_bash::language(), vec![]);
- language("beancount", tree_sitter_beancount::language(), vec![]);
- language(
- "c",
- tree_sitter_c::language(),
- vec![Arc::new(c::CLspAdapter) as Arc<dyn LspAdapter>],
- );
- language(
- "cpp",
- tree_sitter_cpp::language(),
- vec![Arc::new(c::CLspAdapter)],
- );
- language(
- "csharp",
- tree_sitter_c_sharp::language(),
- vec![Arc::new(csharp::OmniSharpAdapter {})],
- );
+ language("bash", vec![]);
+ language("beancount", vec![]);
+ language("c", vec![Arc::new(c::CLspAdapter) as Arc<dyn LspAdapter>]);
+ language("cpp", vec![Arc::new(c::CLspAdapter)]);
+ language("csharp", vec![Arc::new(csharp::OmniSharpAdapter {})]);
language(
"css",
- tree_sitter_css::language(),
vec![
Arc::new(css::CssLspAdapter::new(node_runtime.clone())),
Arc::new(tailwind::TailwindLspAdapter::new(node_runtime.clone())),
@@ -95,53 +134,32 @@ pub fn init(
match &ElixirSettings::get(None, cx).lsp {
elixir::ElixirLspSetting::ElixirLs => language(
"elixir",
- tree_sitter_elixir::language(),
vec![
Arc::new(elixir::ElixirLspAdapter),
Arc::new(tailwind::TailwindLspAdapter::new(node_runtime.clone())),
],
),
- elixir::ElixirLspSetting::NextLs => language(
- "elixir",
- tree_sitter_elixir::language(),
- vec![Arc::new(elixir::NextLspAdapter)],
- ),
+ elixir::ElixirLspSetting::NextLs => {
+ language("elixir", vec![Arc::new(elixir::NextLspAdapter)])
+ }
elixir::ElixirLspSetting::Local { path, arguments } => language(
"elixir",
- tree_sitter_elixir::language(),
vec![Arc::new(elixir::LocalLspAdapter {
path: path.clone(),
arguments: arguments.clone(),
})],
),
}
- language("gitcommit", tree_sitter_gitcommit::language(), vec![]);
- language(
- "erlang",
- tree_sitter_erlang::language(),
- vec![Arc::new(erlang::ErlangLspAdapter)],
- );
+ language("gitcommit", vec![]);
+ language("erlang", vec![Arc::new(erlang::ErlangLspAdapter)]);
- language(
- "gleam",
- tree_sitter_gleam::language(),
- vec![Arc::new(gleam::GleamLspAdapter)],
- );
- language(
- "go",
- tree_sitter_go::language(),
- vec![Arc::new(go::GoLspAdapter)],
- );
- language("gomod", tree_sitter_gomod::language(), vec![]);
- language("gowork", tree_sitter_gowork::language(), vec![]);
- language(
- "zig",
- tree_sitter_zig::language(),
- vec![Arc::new(zig::ZlsAdapter)],
- );
+ language("gleam", vec![Arc::new(gleam::GleamLspAdapter)]);
+ language("go", vec![Arc::new(go::GoLspAdapter)]);
+ language("gomod", vec![]);
+ language("gowork", vec![]);
+ language("zig", vec![Arc::new(zig::ZlsAdapter)]);
language(
"heex",
- tree_sitter_heex::language(),
vec![
Arc::new(elixir::ElixirLspAdapter),
Arc::new(tailwind::TailwindLspAdapter::new(node_runtime.clone())),
@@ -149,48 +167,32 @@ pub fn init(
);
language(
"json",
- tree_sitter_json::language(),
vec![Arc::new(json::JsonLspAdapter::new(
node_runtime.clone(),
languages.clone(),
))],
);
- language("markdown", tree_sitter_markdown::language(), vec![]);
+ language("markdown", vec![]);
language(
"python",
- tree_sitter_python::language(),
vec![Arc::new(python::PythonLspAdapter::new(
node_runtime.clone(),
))],
);
- language(
- "rust",
- tree_sitter_rust::language(),
- vec![Arc::new(rust::RustLspAdapter)],
- );
- language(
- "toml",
- tree_sitter_toml::language(),
- vec![Arc::new(toml::TaploLspAdapter)],
- );
+ language("rust", vec![Arc::new(rust::RustLspAdapter)]);
+ language("toml", vec![Arc::new(toml::TaploLspAdapter)]);
match &DenoSettings::get(None, cx).enable {
true => {
language(
"tsx",
- tree_sitter_typescript::language_tsx(),
vec![
Arc::new(deno::DenoLspAdapter::new()),
Arc::new(tailwind::TailwindLspAdapter::new(node_runtime.clone())),
],
);
- language(
- "typescript",
- tree_sitter_typescript::language_typescript(),
- vec![Arc::new(deno::DenoLspAdapter::new())],
- );
+ language("typescript", vec![Arc::new(deno::DenoLspAdapter::new())]);
language(
"javascript",
- tree_sitter_typescript::language_tsx(),
vec![
Arc::new(deno::DenoLspAdapter::new()),
Arc::new(tailwind::TailwindLspAdapter::new(node_runtime.clone())),
@@ -200,7 +202,6 @@ pub fn init(
false => {
language(
"tsx",
- tree_sitter_typescript::language_tsx(),
vec![
Arc::new(typescript::TypeScriptLspAdapter::new(node_runtime.clone())),
Arc::new(typescript::EsLintLspAdapter::new(node_runtime.clone())),
@@ -209,7 +210,6 @@ pub fn init(
);
language(
"typescript",
- tree_sitter_typescript::language_typescript(),
vec![
Arc::new(typescript::TypeScriptLspAdapter::new(node_runtime.clone())),
Arc::new(typescript::EsLintLspAdapter::new(node_runtime.clone())),
@@ -217,7 +217,6 @@ pub fn init(
);
language(
"javascript",
- tree_sitter_typescript::language_tsx(),
vec![
Arc::new(typescript::TypeScriptLspAdapter::new(node_runtime.clone())),
Arc::new(typescript::EsLintLspAdapter::new(node_runtime.clone())),
@@ -227,47 +226,31 @@ pub fn init(
}
}
- language(
- "haskell",
- tree_sitter_haskell::language(),
- vec![Arc::new(haskell::HaskellLanguageServer {})],
- );
+ language("haskell", vec![Arc::new(haskell::HaskellLanguageServer {})]);
language(
"html",
- tree_sitter_html::language(),
vec![
Arc::new(html::HtmlLspAdapter::new(node_runtime.clone())),
Arc::new(tailwind::TailwindLspAdapter::new(node_runtime.clone())),
],
);
- language(
- "ruby",
- tree_sitter_ruby::language(),
- vec![Arc::new(ruby::RubyLanguageServer)],
- );
+ language("ruby", vec![Arc::new(ruby::RubyLanguageServer)]);
language(
"erb",
- tree_sitter_embedded_template::language(),
vec![
Arc::new(ruby::RubyLanguageServer),
Arc::new(tailwind::TailwindLspAdapter::new(node_runtime.clone())),
],
);
- language("scheme", tree_sitter_scheme::language(), vec![]);
- language("racket", tree_sitter_racket::language(), vec![]);
- language(
- "lua",
- tree_sitter_lua::language(),
- vec![Arc::new(lua::LuaLspAdapter)],
- );
+ language("scheme", vec![]);
+ language("racket", vec![]);
+ language("lua", vec![Arc::new(lua::LuaLspAdapter)]);
language(
"yaml",
- tree_sitter_yaml::language(),
vec![Arc::new(yaml::YamlLspAdapter::new(node_runtime.clone()))],
);
language(
"svelte",
- tree_sitter_svelte::language(),
vec![
Arc::new(svelte::SvelteLspAdapter::new(node_runtime.clone())),
Arc::new(tailwind::TailwindLspAdapter::new(node_runtime.clone())),
@@ -275,7 +258,6 @@ pub fn init(
);
language(
"php",
- tree_sitter_php::language_php(),
vec![
Arc::new(php::IntelephenseLspAdapter::new(node_runtime.clone())),
Arc::new(tailwind::TailwindLspAdapter::new(node_runtime.clone())),
@@ -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<Cow<'static, str>>,
-)] = &[
- ("highlights", |q| &mut q.highlights),
- ("brackets", |q| &mut q.brackets),
- ("outline", |q| &mut q.outline),
- ("indents", |q| &mut q.indents),
- ("embedding", |q| &mut q.embedding),
- ("injections", |q| &mut q.injections),
- ("overrides", |q| &mut q.overrides),
- ("redactions", |q| &mut q.redactions),
-];
-
fn load_queries(name: &str) -> LanguageQueries {
let mut result = LanguageQueries::default();
for path in LanguageDir::iter() {
@@ -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
-}
@@ -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"
@@ -1,3 +1,4 @@
name = "Beancount"
+grammar = "beancount"
path_suffixes = ["beancount"]
brackets = [{ start = "\"", end = "\"", close = false, newline = false }]
@@ -1,4 +1,5 @@
name = "C"
+grammar = "c"
path_suffixes = ["c"]
line_comments = ["// "]
autoclose_before = ";:.,=}])>"
@@ -1,4 +1,5 @@
name = "C++"
+grammar = "cpp"
path_suffixes = ["cc", "cpp", "h", "hpp", "cxx", "hxx", "inl"]
line_comments = ["// "]
autoclose_before = ";:.,=}])>"
@@ -1,4 +1,5 @@
name = "CSharp"
+grammar = "c_sharp"
path_suffixes = ["cs"]
line_comments = ["// "]
autoclose_before = ";:.,=}])>"
@@ -1,4 +1,5 @@
name = "CSS"
+grammar = "css"
path_suffixes = ["css"]
autoclose_before = ";:.,=}])>"
brackets = [
@@ -1,4 +1,5 @@
name = "Elixir"
+grammar = "elixir"
path_suffixes = ["ex", "exs"]
line_comments = ["# "]
autoclose_before = ";:.,=}])>"
@@ -1,4 +1,5 @@
name = "Elm"
+grammar = "elm"
path_suffixes = ["elm"]
line_comments = ["-- "]
block_comment = ["{- ", " -}"]
@@ -1,4 +1,5 @@
name = "ERB"
+grammar = "embedded_template"
path_suffixes = ["erb"]
autoclose_before = ">})"
brackets = [
@@ -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"]
@@ -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",
@@ -1,4 +1,5 @@
name = "Gleam"
+grammar = "gleam"
path_suffixes = ["gleam"]
line_comments = ["// ", "/// "]
autoclose_before = ";:.,=}])>"
@@ -1,4 +1,5 @@
name = "GLSL"
+grammar = "glsl"
path_suffixes = ["vert", "frag", "tesc", "tese", "geom", "comp"]
line_comments = ["// "]
block_comment = ["/* ", " */"]
@@ -1,4 +1,5 @@
name = "Go"
+grammar = "go"
path_suffixes = ["go"]
line_comments = ["// "]
autoclose_before = ";:.,=}])>"
@@ -1,4 +1,5 @@
name = "Go Mod"
+grammar = "go"
path_suffixes = ["mod"]
line_comments = ["//"]
autoclose_before = ")"
@@ -1,4 +1,5 @@
name = "Go Work"
+grammar = "go_work"
path_suffixes = ["work"]
line_comments = ["//"]
autoclose_before = ")"
@@ -1,4 +1,5 @@
name = "Haskell"
+grammar = "haskell"
path_suffixes = ["hs"]
autoclose_before = ",=)}]"
line_comments = ["-- "]
@@ -1,4 +1,5 @@
name = "HCL"
+grammar = "hcl"
path_suffixes = ["hcl"]
line_comments = ["# ", "// "]
block_comment = ["/*", "*/"]
@@ -1,4 +1,5 @@
name = "HEEX"
+grammar = "heex"
path_suffixes = ["heex"]
autoclose_before = ">})"
brackets = [
@@ -1,4 +1,5 @@
name = "HTML"
+grammar = "html"
path_suffixes = ["html", "htm", "shtml"]
autoclose_before = ">})"
block_comment = ["<!-- ", " -->"]
@@ -1,4 +1,5 @@
name = "JavaScript"
+grammar = "tsx"
path_suffixes = ["js", "jsx", "mjs", "cjs"]
first_line_pattern = '^#!.*\bnode\b'
line_comments = ["// "]
@@ -1,4 +1,5 @@
name = "JSON"
+grammar = "json"
path_suffixes = ["json"]
line_comments = ["// "]
autoclose_before = ",]}"
@@ -1,4 +1,5 @@
name = "Lua"
+grammar = "lua"
path_suffixes = ["lua"]
line_comments = ["-- "]
autoclose_before = ",]}"
@@ -1,4 +1,5 @@
name = "Markdown"
+grammar = "markdown"
path_suffixes = ["md", "mdx"]
word_characters = ["-"]
brackets = [
@@ -1,4 +1,5 @@
name = "Nix"
+grammar = "nix"
path_suffixes = ["nix"]
line_comments = ["# "]
block_comment = ["/* ", " */"]
@@ -1,4 +1,5 @@
name = "Nu"
+grammar = "nu"
path_suffixes = ["nu"]
line_comments = ["# "]
autoclose_before = ";:.,=}])>` \n\t\""
@@ -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 },
]
@@ -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 },
@@ -1,4 +1,5 @@
name = "PHP"
+grammar = "php"
path_suffixes = ["php"]
first_line_pattern = '^#!.*php'
line_comments = ["// ", "# "]
@@ -1,4 +1,5 @@
name = "proto"
+grammar = "proto"
path_suffixes = ["proto"]
line_comments = ["// "]
autoclose_before = ";:.,=}])>"
@@ -1,4 +1,5 @@
name = "PureScript"
+grammar = "purescript"
path_suffixes = ["purs"]
autoclose_before = ",=)}]"
line_comments = ["-- "]
@@ -1,4 +1,5 @@
name = "Python"
+grammar = "python"
path_suffixes = ["py", "pyi", "mpy"]
first_line_pattern = '^#!.*\bpython[0-9.]*\b'
line_comments = ["# "]
@@ -1,4 +1,5 @@
name = "Racket"
+grammar = "racket"
path_suffixes = ["rkt"]
line_comments = ["; "]
autoclose_before = "])"
@@ -1,4 +1,5 @@
name = "Ruby"
+grammar = "ruby"
path_suffixes = [
"rb",
"Gemfile",
@@ -1,4 +1,5 @@
name = "Rust"
+grammar = "rust"
path_suffixes = ["rs"]
line_comments = ["// ", "/// ", "//! "]
autoclose_before = ";:.,=}])>"
@@ -1,4 +1,5 @@
name = "Scheme"
+grammar = "scheme"
path_suffixes = ["scm", "ss"]
line_comments = ["; "]
autoclose_before = "])"
@@ -1,4 +1,5 @@
name = "Svelte"
+grammar = "svelte"
path_suffixes = ["svelte"]
block_comment = ["<!-- ", " -->"]
autoclose_before = ";:.,=}])>"
@@ -1,4 +1,5 @@
name = "Terraform"
+grammar = "terraform"
path_suffixes = ["tf", "tfvars"]
line_comments = ["# ", "// "]
block_comment = ["/*", "*/"]
@@ -1,4 +1,5 @@
name = "TOML"
+grammar = "toml"
path_suffixes = ["Cargo.lock", "toml"]
line_comments = ["# "]
autoclose_before = ",]}"
@@ -1,4 +1,5 @@
name = "TSX"
+grammar = "tsx"
path_suffixes = ["tsx"]
line_comments = ["// "]
autoclose_before = ";:.,=}])>"
@@ -1,4 +1,5 @@
name = "TypeScript"
+grammar = "typescript"
path_suffixes = ["ts", "cts", "d.cts", "d.mts", "mts"]
line_comments = ["// "]
autoclose_before = ";:.,=}])>"
@@ -1,4 +1,5 @@
name = "Uiua"
+grammar = "uiua"
path_suffixes = ["ua"]
line_comments = ["# "]
autoclose_before = ")]}\""
@@ -1,4 +1,5 @@
name = "Vue.js"
+grammar = "vue"
path_suffixes = ["vue"]
block_comment = ["<!-- ", " -->"]
autoclose_before = ";:.,=}])>"
@@ -1,4 +1,5 @@
name = "YAML"
+grammar = "yaml"
path_suffixes = ["yml", "yaml"]
line_comments = ["# "]
autoclose_before = ",]}"
@@ -1,4 +1,5 @@
name = "Zig"
+grammar = "zig"
path_suffixes = ["zig"]
line_comments = ["// "]
autoclose_before = ";:.,=}])>"
@@ -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<dyn fs::Fs>, cx: &mut AppContext) {
.detach()
}
+#[cfg(debug_assertions)]
async fn watch_languages(fs: Arc<dyn fs::Fs>, languages: Arc<LanguageRegistry>) {
let reload_debounce = Duration::from_millis(250);
- let mut events = fs.watch(PLUGINS_DIR.as_ref(), reload_debounce).await;
-
- #[cfg(debug_assertions)]
- {
- events = futures::stream::select(
- events,
- fs.watch("crates/zed/src/languages".as_ref(), reload_debounce)
- .await,
- )
- .boxed();
- }
+ let mut events = fs
+ .watch("crates/zed/src/languages".as_ref(), reload_debounce)
+ .await;
while (events.next().await).is_some() {
languages.reload();
@@ -1019,3 +1014,6 @@ fn watch_file_types(fs: Arc<dyn fs::Fs>, cx: &mut AppContext) {
#[cfg(not(debug_assertions))]
fn watch_file_types(_fs: Arc<dyn fs::Fs>, _cx: &mut AppContext) {}
+
+#[cfg(not(debug_assertions))]
+async fn watch_languages(_fs: Arc<dyn fs::Fs>, _languages: Arc<LanguageRegistry>) {}
@@ -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()),