Merge branch 'main' into kvark-linux

This commit is contained in:
Mikayla 2024-02-07 12:30:36 -08:00
commit 3a53db6502
No known key found for this signature in database
78 changed files with 1509 additions and 387 deletions

20
Cargo.lock generated
View File

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

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

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

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())),
@ -287,62 +269,24 @@ pub fn init(
#[cfg(not(target_os = "linux"))]
language(
"purescript",
tree_sitter_purescript::language(),
vec![Arc::new(purescript::PurescriptLspAdapter::new(
node_runtime.clone(),
))],
);
language(
"elm",
tree_sitter_elm::language(),
vec![Arc::new(elm::ElmLspAdapter::new(node_runtime.clone()))],
);
language("glsl", tree_sitter_glsl::language(), vec![]);
language("nix", tree_sitter_nix::language(), vec![]);
language(
"nu",
tree_sitter_nu::language(),
vec![Arc::new(nu::NuLanguageServer {})],
);
language(
"ocaml",
tree_sitter_ocaml::language_ocaml(),
vec![Arc::new(ocaml::OCamlLspAdapter)],
);
language(
"ocaml-interface",
tree_sitter_ocaml::language_ocaml_interface(),
vec![Arc::new(ocaml::OCamlLspAdapter)],
);
language(
"vue",
tree_sitter_vue::language(),
vec![Arc::new(vue::VueLspAdapter::new(node_runtime))],
);
language(
"uiua",
tree_sitter_uiua::language(),
vec![Arc::new(uiua::UiuaLanguageServer {})],
);
language("proto", tree_sitter_proto::language(), vec![]);
language("terraform", tree_sitter_hcl::language(), vec![]);
language("hcl", tree_sitter_hcl::language(), vec![]);
if let Ok(children) = std::fs::read_dir(&*PLUGINS_DIR) {
for child in children {
if let Ok(child) = child {
let path = child.path();
let config_path = path.join("config.toml");
if let Ok(config) = std::fs::read(&config_path) {
languages.register_wasm(
path.into(),
::toml::from_slice(&config).unwrap(),
load_plugin_queries,
);
}
}
}
}
language("glsl", vec![]);
language("nix", vec![]);
language("nu", vec![Arc::new(nu::NuLanguageServer {})]);
language("ocaml", vec![Arc::new(ocaml::OCamlLspAdapter)]);
language("ocaml-interface", vec![Arc::new(ocaml::OCamlLspAdapter)]);
language("vue", vec![Arc::new(vue::VueLspAdapter::new(node_runtime))]);
language("uiua", vec![Arc::new(uiua::UiuaLanguageServer {})]);
language("proto", vec![]);
language("terraform", vec![]);
language("hcl", vec![]);
}
#[cfg(any(test, feature = "test-support"))]
@ -370,20 +314,6 @@ fn load_config(name: &str) -> LanguageConfig {
.unwrap()
}
const QUERY_FILENAME_PREFIXES: &[(
&str,
fn(&mut LanguageQueries) -> &mut Option<Cow<'static, str>>,
)] = &[
("highlights", |q| &mut q.highlights),
("brackets", |q| &mut q.brackets),
("outline", |q| &mut q.outline),
("indents", |q| &mut q.indents),
("embedding", |q| &mut q.embedding),
("injections", |q| &mut q.injections),
("overrides", |q| &mut q.overrides),
("redactions", |q| &mut q.redactions),
];
fn load_queries(name: &str) -> LanguageQueries {
let mut result = LanguageQueries::default();
for path in LanguageDir::iter() {
@ -404,32 +334,3 @@ fn load_queries(name: &str) -> LanguageQueries {
}
result
}
fn load_plugin_queries(root_path: &Path) -> LanguageQueries {
let mut result = LanguageQueries::default();
if let Some(entries) = fs::read_dir(root_path).log_err() {
for entry in entries {
let Some(entry) = entry.log_err() else {
continue;
};
let path = entry.path();
if let Some(remainder) = path.strip_prefix(root_path).ok().and_then(|p| p.to_str()) {
if !remainder.ends_with(".scm") {
continue;
}
for (name, query) in QUERY_FILENAME_PREFIXES {
if remainder.starts_with(name) {
if let Some(contents) = fs::read_to_string(&path).log_err() {
match query(&mut result) {
None => *query(&mut result) = Some(contents.into()),
Some(r) => r.to_mut().push_str(contents.as_ref()),
}
}
break;
}
}
}
}
}
result
}

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

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

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

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

View File

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