Add logic for managing language and theme extensions (#7467)

This PR adds the initial support for loading extensions in Zed.

### Extensions Directory

Extensions are loaded from the extensions directory.

The extensions directory has the following structure:

```
extensions/
  installed/
    extension-a/
      grammars/
      languages/
    extension-b/
      themes/
  manifest.json
```

The `manifest.json` file is used internally by Zed to keep track of
which extensions are installed. This file should be maintained
automatically, and shouldn't require any direct interaction with it.

Extensions can provide Tree-sitter grammars, languages, and themes.

Release Notes:

- N/A

---------

Co-authored-by: Marshall <marshall@zed.dev>
This commit is contained in:
Max Brunsfeld 2024-02-07 12:14:50 -08:00 committed by GitHub
parent 6b598a07d9
commit 6edeea7c8a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
77 changed files with 1503 additions and 387 deletions

20
Cargo.lock generated
View File

@ -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",

View File

@ -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" }

View File

@ -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()),

View File

@ -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()),

View File

@ -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()),

View File

@ -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()),

View File

@ -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,

View File

@ -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()),

View File

@ -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()
},

View File

@ -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 {

View File

@ -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()),

View File

@ -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(),

View File

@ -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"] }

View File

@ -0,0 +1 @@
../../LICENSE-GPL

View File

@ -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
}

View File

@ -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);
});
}

View File

@ -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 {

View File

@ -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

View File

@ -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()),

View File

@ -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(),
);

View File

@ -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()),

View File

@ -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()),

View File

@ -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()),

View File

@ -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,

View File

@ -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()),

View File

@ -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(())

View File

@ -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> {

View File

@ -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");

View File

@ -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

View File

@ -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
}

View File

@ -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"

View File

@ -1,3 +1,4 @@
name = "Beancount"
grammar = "beancount"
path_suffixes = ["beancount"]
brackets = [{ start = "\"", end = "\"", close = false, newline = false }]

View File

@ -1,4 +1,5 @@
name = "C"
grammar = "c"
path_suffixes = ["c"]
line_comments = ["// "]
autoclose_before = ";:.,=}])>"

View File

@ -1,4 +1,5 @@
name = "C++"
grammar = "cpp"
path_suffixes = ["cc", "cpp", "h", "hpp", "cxx", "hxx", "inl"]
line_comments = ["// "]
autoclose_before = ";:.,=}])>"

View File

@ -1,4 +1,5 @@
name = "CSharp"
grammar = "c_sharp"
path_suffixes = ["cs"]
line_comments = ["// "]
autoclose_before = ";:.,=}])>"

View File

@ -1,4 +1,5 @@
name = "CSS"
grammar = "css"
path_suffixes = ["css"]
autoclose_before = ";:.,=}])>"
brackets = [

View File

@ -1,4 +1,5 @@
name = "Elixir"
grammar = "elixir"
path_suffixes = ["ex", "exs"]
line_comments = ["# "]
autoclose_before = ";:.,=}])>"

View File

@ -1,4 +1,5 @@
name = "Elm"
grammar = "elm"
path_suffixes = ["elm"]
line_comments = ["-- "]
block_comment = ["{- ", " -}"]

View File

@ -1,4 +1,5 @@
name = "ERB"
grammar = "embedded_template"
path_suffixes = ["erb"]
autoclose_before = ">})"
brackets = [

View File

@ -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"]

View File

@ -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",

View File

@ -1,4 +1,5 @@
name = "Gleam"
grammar = "gleam"
path_suffixes = ["gleam"]
line_comments = ["// ", "/// "]
autoclose_before = ";:.,=}])>"

View File

@ -1,4 +1,5 @@
name = "GLSL"
grammar = "glsl"
path_suffixes = ["vert", "frag", "tesc", "tese", "geom", "comp"]
line_comments = ["// "]
block_comment = ["/* ", " */"]

View File

@ -1,4 +1,5 @@
name = "Go"
grammar = "go"
path_suffixes = ["go"]
line_comments = ["// "]
autoclose_before = ";:.,=}])>"

View File

@ -1,4 +1,5 @@
name = "Go Mod"
grammar = "go"
path_suffixes = ["mod"]
line_comments = ["//"]
autoclose_before = ")"

View File

@ -1,4 +1,5 @@
name = "Go Work"
grammar = "go_work"
path_suffixes = ["work"]
line_comments = ["//"]
autoclose_before = ")"

View File

@ -1,4 +1,5 @@
name = "Haskell"
grammar = "haskell"
path_suffixes = ["hs"]
autoclose_before = ",=)}]"
line_comments = ["-- "]

View File

@ -1,4 +1,5 @@
name = "HCL"
grammar = "hcl"
path_suffixes = ["hcl"]
line_comments = ["# ", "// "]
block_comment = ["/*", "*/"]

View File

@ -1,4 +1,5 @@
name = "HEEX"
grammar = "heex"
path_suffixes = ["heex"]
autoclose_before = ">})"
brackets = [

View File

@ -1,4 +1,5 @@
name = "HTML"
grammar = "html"
path_suffixes = ["html", "htm", "shtml"]
autoclose_before = ">})"
block_comment = ["<!-- ", " -->"]

View File

@ -1,4 +1,5 @@
name = "JavaScript"
grammar = "tsx"
path_suffixes = ["js", "jsx", "mjs", "cjs"]
first_line_pattern = '^#!.*\bnode\b'
line_comments = ["// "]

View File

@ -1,4 +1,5 @@
name = "JSON"
grammar = "json"
path_suffixes = ["json"]
line_comments = ["// "]
autoclose_before = ",]}"

View File

@ -1,4 +1,5 @@
name = "Lua"
grammar = "lua"
path_suffixes = ["lua"]
line_comments = ["-- "]
autoclose_before = ",]}"

View File

@ -1,4 +1,5 @@
name = "Markdown"
grammar = "markdown"
path_suffixes = ["md", "mdx"]
word_characters = ["-"]
brackets = [

View File

@ -1,4 +1,5 @@
name = "Nix"
grammar = "nix"
path_suffixes = ["nix"]
line_comments = ["# "]
block_comment = ["/* ", " */"]

View File

@ -1,4 +1,5 @@
name = "Nu"
grammar = "nu"
path_suffixes = ["nu"]
line_comments = ["# "]
autoclose_before = ";:.,=}])>` \n\t\""

View File

@ -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 },
]

View File

@ -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 },

View File

@ -1,4 +1,5 @@
name = "PHP"
grammar = "php"
path_suffixes = ["php"]
first_line_pattern = '^#!.*php'
line_comments = ["// ", "# "]

View File

@ -1,4 +1,5 @@
name = "proto"
grammar = "proto"
path_suffixes = ["proto"]
line_comments = ["// "]
autoclose_before = ";:.,=}])>"

View File

@ -1,4 +1,5 @@
name = "PureScript"
grammar = "purescript"
path_suffixes = ["purs"]
autoclose_before = ",=)}]"
line_comments = ["-- "]

View File

@ -1,4 +1,5 @@
name = "Python"
grammar = "python"
path_suffixes = ["py", "pyi", "mpy"]
first_line_pattern = '^#!.*\bpython[0-9.]*\b'
line_comments = ["# "]

View File

@ -1,4 +1,5 @@
name = "Racket"
grammar = "racket"
path_suffixes = ["rkt"]
line_comments = ["; "]
autoclose_before = "])"

View File

@ -1,4 +1,5 @@
name = "Ruby"
grammar = "ruby"
path_suffixes = [
"rb",
"Gemfile",

View File

@ -1,4 +1,5 @@
name = "Rust"
grammar = "rust"
path_suffixes = ["rs"]
line_comments = ["// ", "/// ", "//! "]
autoclose_before = ";:.,=}])>"

View File

@ -1,4 +1,5 @@
name = "Scheme"
grammar = "scheme"
path_suffixes = ["scm", "ss"]
line_comments = ["; "]
autoclose_before = "])"

View File

@ -1,4 +1,5 @@
name = "Svelte"
grammar = "svelte"
path_suffixes = ["svelte"]
block_comment = ["<!-- ", " -->"]
autoclose_before = ";:.,=}])>"

View File

@ -1,4 +1,5 @@
name = "Terraform"
grammar = "terraform"
path_suffixes = ["tf", "tfvars"]
line_comments = ["# ", "// "]
block_comment = ["/*", "*/"]

View File

@ -1,4 +1,5 @@
name = "TOML"
grammar = "toml"
path_suffixes = ["Cargo.lock", "toml"]
line_comments = ["# "]
autoclose_before = ",]}"

View File

@ -1,4 +1,5 @@
name = "TSX"
grammar = "tsx"
path_suffixes = ["tsx"]
line_comments = ["// "]
autoclose_before = ";:.,=}])>"

View File

@ -1,4 +1,5 @@
name = "TypeScript"
grammar = "typescript"
path_suffixes = ["ts", "cts", "d.cts", "d.mts", "mts"]
line_comments = ["// "]
autoclose_before = ";:.,=}])>"

View File

@ -1,4 +1,5 @@
name = "Uiua"
grammar = "uiua"
path_suffixes = ["ua"]
line_comments = ["# "]
autoclose_before = ")]}\""

View File

@ -1,4 +1,5 @@
name = "Vue.js"
grammar = "vue"
path_suffixes = ["vue"]
block_comment = ["<!-- ", " -->"]
autoclose_before = ";:.,=}])>"

View File

@ -1,4 +1,5 @@
name = "YAML"
grammar = "yaml"
path_suffixes = ["yml", "yaml"]
line_comments = ["# "]
autoclose_before = ",]}"

View File

@ -1,4 +1,5 @@
name = "Zig"
grammar = "zig"
path_suffixes = ["zig"]
line_comments = ["// "]
autoclose_before = ";:.,=}])>"

View File

@ -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>) {}

View File

@ -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()),