mirror of
https://github.com/zed-industries/zed.git
synced 2024-11-07 20:39:04 +03:00
Merge branch 'main' into kvark-linux
This commit is contained in:
commit
3a53db6502
20
Cargo.lock
generated
20
Cargo.lock
generated
@ -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",
|
||||
|
@ -21,6 +21,7 @@ members = [
|
||||
"crates/db",
|
||||
"crates/diagnostics",
|
||||
"crates/editor",
|
||||
"crates/extension",
|
||||
"crates/feature_flags",
|
||||
"crates/feedback",
|
||||
"crates/file_finder",
|
||||
@ -30,12 +31,9 @@ members = [
|
||||
"crates/git",
|
||||
"crates/go_to_line",
|
||||
"crates/gpui",
|
||||
"crates/gpui",
|
||||
"crates/gpui_macros",
|
||||
"crates/gpui_macros",
|
||||
"crates/install_cli",
|
||||
"crates/journal",
|
||||
"crates/journal",
|
||||
"crates/language",
|
||||
"crates/language_selector",
|
||||
"crates/language_tools",
|
||||
@ -114,6 +112,7 @@ copilot_ui = { path = "crates/copilot_ui" }
|
||||
db = { path = "crates/db" }
|
||||
diagnostics = { path = "crates/diagnostics" }
|
||||
editor = { path = "crates/editor" }
|
||||
extension = { path = "crates/extension" }
|
||||
feature_flags = { path = "crates/feature_flags" }
|
||||
feedback = { path = "crates/feedback" }
|
||||
file_finder = { path = "crates/file_finder" }
|
||||
|
@ -366,7 +366,8 @@ mod tests {
|
||||
use gpui::{Context, TestAppContext};
|
||||
use indoc::indoc;
|
||||
use language::{
|
||||
language_settings, tree_sitter_rust, Buffer, BufferId, Language, LanguageConfig, Point,
|
||||
language_settings, tree_sitter_rust, Buffer, BufferId, Language, LanguageConfig,
|
||||
LanguageMatcher, Point,
|
||||
};
|
||||
use rand::prelude::*;
|
||||
use serde::Serialize;
|
||||
@ -675,9 +676,12 @@ mod tests {
|
||||
Language::new(
|
||||
LanguageConfig {
|
||||
name: "Rust".into(),
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_rust::language()),
|
||||
)
|
||||
.with_indents_query(
|
||||
|
@ -172,24 +172,26 @@ 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(),
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_rust::language()),
|
||||
)
|
||||
.with_embedding_query(
|
||||
|
@ -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,9 +269,12 @@ async fn test_collaborating_with_completion(cx_a: &mut TestAppContext, cx_b: &mu
|
||||
let mut language = Language::new(
|
||||
LanguageConfig {
|
||||
name: "Rust".into(),
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_rust::language()),
|
||||
);
|
||||
let mut fake_language_servers = language
|
||||
@ -455,9 +458,12 @@ async fn test_collaborating_with_code_actions(
|
||||
let mut language = Language::new(
|
||||
LanguageConfig {
|
||||
name: "Rust".into(),
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_rust::language()),
|
||||
);
|
||||
let mut fake_language_servers = language.set_fake_lsp_adapter(Default::default()).await;
|
||||
@ -668,9 +674,12 @@ async fn test_collaborating_with_renames(cx_a: &mut TestAppContext, cx_b: &mut T
|
||||
let mut language = Language::new(
|
||||
LanguageConfig {
|
||||
name: "Rust".into(),
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_rust::language()),
|
||||
);
|
||||
let mut fake_language_servers = language
|
||||
@ -853,9 +862,12 @@ async fn test_language_server_statuses(cx_a: &mut TestAppContext, cx_b: &mut Tes
|
||||
let mut language = Language::new(
|
||||
LanguageConfig {
|
||||
name: "Rust".into(),
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_rust::language()),
|
||||
);
|
||||
let mut fake_language_servers = language
|
||||
@ -1144,9 +1156,12 @@ async fn test_on_input_format_from_host_to_guest(
|
||||
let mut language = Language::new(
|
||||
LanguageConfig {
|
||||
name: "Rust".into(),
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_rust::language()),
|
||||
);
|
||||
let mut fake_language_servers = language
|
||||
@ -1272,9 +1287,12 @@ async fn test_on_input_format_from_guest_to_host(
|
||||
let mut language = Language::new(
|
||||
LanguageConfig {
|
||||
name: "Rust".into(),
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_rust::language()),
|
||||
);
|
||||
let mut fake_language_servers = language
|
||||
@ -1431,9 +1449,12 @@ async fn test_mutual_editor_inlay_hint_cache_update(
|
||||
let mut language = Language::new(
|
||||
LanguageConfig {
|
||||
name: "Rust".into(),
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_rust::language()),
|
||||
);
|
||||
let mut fake_language_servers = language
|
||||
@ -1697,9 +1718,12 @@ async fn test_inlay_hint_refresh_is_forwarded(
|
||||
let mut language = Language::new(
|
||||
LanguageConfig {
|
||||
name: "Rust".into(),
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_rust::language()),
|
||||
);
|
||||
let mut fake_language_servers = language
|
||||
|
@ -14,7 +14,7 @@ use gpui::{
|
||||
use language::{
|
||||
language_settings::{AllLanguageSettings, Formatter},
|
||||
tree_sitter_rust, Diagnostic, DiagnosticEntry, FakeLspAdapter, Language, LanguageConfig,
|
||||
LineEnding, OffsetRangeExt, Point, Rope,
|
||||
LanguageMatcher, LineEnding, OffsetRangeExt, Point, Rope,
|
||||
};
|
||||
use live_kit_client::MacOSDisplay;
|
||||
use lsp::LanguageServerId;
|
||||
@ -2246,17 +2246,23 @@ async fn test_propagate_saves_and_fs_changes(
|
||||
let rust = Arc::new(Language::new(
|
||||
LanguageConfig {
|
||||
name: "Rust".into(),
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_rust::language()),
|
||||
));
|
||||
let javascript = Arc::new(Language::new(
|
||||
LanguageConfig {
|
||||
name: "JavaScript".into(),
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["js".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_rust::language()),
|
||||
));
|
||||
for client in [&client_a, &client_b, &client_c] {
|
||||
@ -3783,9 +3789,12 @@ async fn test_collaborating_with_diagnostics(
|
||||
let mut language = Language::new(
|
||||
LanguageConfig {
|
||||
name: "Rust".into(),
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_rust::language()),
|
||||
);
|
||||
let mut fake_language_servers = language.set_fake_lsp_adapter(Default::default()).await;
|
||||
@ -4061,9 +4070,12 @@ async fn test_collaborating_with_lsp_progress_updates_and_diagnostics_ordering(
|
||||
let mut language = Language::new(
|
||||
LanguageConfig {
|
||||
name: "Rust".into(),
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_rust::language()),
|
||||
);
|
||||
let mut fake_language_servers = language
|
||||
@ -4290,9 +4302,12 @@ async fn test_formatting_buffer(
|
||||
let mut language = Language::new(
|
||||
LanguageConfig {
|
||||
name: "Rust".into(),
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_rust::language()),
|
||||
);
|
||||
let mut fake_language_servers = language.set_fake_lsp_adapter(Default::default()).await;
|
||||
@ -4395,7 +4410,10 @@ async fn test_prettier_formatting_buffer(
|
||||
let mut language = Language::new(
|
||||
LanguageConfig {
|
||||
name: "Rust".into(),
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
prettier_parser_name: Some("test_parser".to_string()),
|
||||
..Default::default()
|
||||
},
|
||||
@ -4511,9 +4529,12 @@ async fn test_definition(
|
||||
let mut language = Language::new(
|
||||
LanguageConfig {
|
||||
name: "Rust".into(),
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_rust::language()),
|
||||
);
|
||||
let mut fake_language_servers = language.set_fake_lsp_adapter(Default::default()).await;
|
||||
@ -4655,9 +4676,12 @@ async fn test_references(
|
||||
let mut language = Language::new(
|
||||
LanguageConfig {
|
||||
name: "Rust".into(),
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_rust::language()),
|
||||
);
|
||||
let mut fake_language_servers = language.set_fake_lsp_adapter(Default::default()).await;
|
||||
@ -4852,9 +4876,12 @@ async fn test_document_highlights(
|
||||
let mut language = Language::new(
|
||||
LanguageConfig {
|
||||
name: "Rust".into(),
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_rust::language()),
|
||||
);
|
||||
let mut fake_language_servers = language.set_fake_lsp_adapter(Default::default()).await;
|
||||
@ -4955,9 +4982,12 @@ async fn test_lsp_hover(
|
||||
let mut language = Language::new(
|
||||
LanguageConfig {
|
||||
name: "Rust".into(),
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_rust::language()),
|
||||
);
|
||||
let mut fake_language_servers = language.set_fake_lsp_adapter(Default::default()).await;
|
||||
@ -5051,9 +5081,12 @@ async fn test_project_symbols(
|
||||
let mut language = Language::new(
|
||||
LanguageConfig {
|
||||
name: "Rust".into(),
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_rust::language()),
|
||||
);
|
||||
let mut fake_language_servers = language.set_fake_lsp_adapter(Default::default()).await;
|
||||
@ -5160,9 +5193,12 @@ async fn test_open_buffer_while_getting_definition_pointing_to_it(
|
||||
let mut language = Language::new(
|
||||
LanguageConfig {
|
||||
name: "Rust".into(),
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_rust::language()),
|
||||
);
|
||||
let mut fake_language_servers = language.set_fake_lsp_adapter(Default::default()).await;
|
||||
|
@ -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,9 +1024,12 @@ impl RandomizedTest for ProjectCollaborationTest {
|
||||
let mut language = Language::new(
|
||||
LanguageConfig {
|
||||
name: "Rust".into(),
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
None,
|
||||
);
|
||||
language
|
||||
|
@ -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,9 +1453,12 @@ pub mod tests {
|
||||
Language::new(
|
||||
LanguageConfig {
|
||||
name: "Test".into(),
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec![".test".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_rust::language()),
|
||||
)
|
||||
.with_highlights_query(
|
||||
@ -1540,9 +1543,12 @@ pub mod tests {
|
||||
Language::new(
|
||||
LanguageConfig {
|
||||
name: "Test".into(),
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec![".test".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_rust::language()),
|
||||
)
|
||||
.with_highlights_query(
|
||||
@ -1608,9 +1614,12 @@ pub mod tests {
|
||||
Language::new(
|
||||
LanguageConfig {
|
||||
name: "Test".into(),
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec![".test".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_rust::language()),
|
||||
)
|
||||
.with_highlights_query(
|
||||
|
@ -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,9 +5078,12 @@ async fn test_document_format_during_save(cx: &mut gpui::TestAppContext) {
|
||||
let mut language = Language::new(
|
||||
LanguageConfig {
|
||||
name: "Rust".into(),
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_rust::language()),
|
||||
);
|
||||
let mut fake_servers = language
|
||||
@ -5196,9 +5200,12 @@ async fn test_range_format_during_save(cx: &mut gpui::TestAppContext) {
|
||||
let mut language = Language::new(
|
||||
LanguageConfig {
|
||||
name: "Rust".into(),
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_rust::language()),
|
||||
);
|
||||
let mut fake_servers = 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(),
|
||||
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(),
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
brackets: BracketPairConfig {
|
||||
pairs: vec![BracketPair {
|
||||
start: "{".to_string(),
|
||||
@ -7859,9 +7872,12 @@ 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),
|
||||
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 {
|
||||
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(),
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
prettier_parser_name: Some("test_parser".to_string()),
|
||||
..Default::default()
|
||||
},
|
||||
|
@ -35,7 +35,7 @@ mod tests {
|
||||
use super::*;
|
||||
use crate::{editor_tests::init_test, test::editor_lsp_test_context::EditorLspTestContext};
|
||||
use indoc::indoc;
|
||||
use language::{BracketPair, BracketPairConfig, Language, LanguageConfig};
|
||||
use language::{BracketPair, BracketPairConfig, Language, LanguageConfig, LanguageMatcher};
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_matching_bracket_highlights(cx: &mut gpui::TestAppContext) {
|
||||
@ -45,7 +45,10 @@ mod tests {
|
||||
Language::new(
|
||||
LanguageConfig {
|
||||
name: "Rust".into(),
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
brackets: BracketPairConfig {
|
||||
pairs: vec![
|
||||
BracketPair {
|
||||
|
@ -1240,7 +1240,7 @@ pub mod tests {
|
||||
use itertools::Itertools;
|
||||
use language::{
|
||||
language_settings::AllLanguageSettingsContent, Capability, FakeLspAdapter, Language,
|
||||
LanguageConfig,
|
||||
LanguageConfig, LanguageMatcher,
|
||||
};
|
||||
use lsp::FakeLanguageServer;
|
||||
use parking_lot::Mutex;
|
||||
@ -1529,9 +1529,12 @@ pub mod tests {
|
||||
let mut language = Language::new(
|
||||
LanguageConfig {
|
||||
name: name.into(),
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec![path_suffix.to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_rust::language()),
|
||||
);
|
||||
let fake_servers = language
|
||||
@ -2209,9 +2212,12 @@ pub mod tests {
|
||||
let mut language = Language::new(
|
||||
LanguageConfig {
|
||||
name: "Rust".into(),
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_rust::language()),
|
||||
);
|
||||
let mut fake_servers = language
|
||||
@ -2500,9 +2506,12 @@ pub mod tests {
|
||||
let mut language = Language::new(
|
||||
LanguageConfig {
|
||||
name: "Rust".into(),
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_rust::language()),
|
||||
);
|
||||
let mut fake_servers = language
|
||||
@ -2848,9 +2857,12 @@ pub mod tests {
|
||||
let mut language = Language::new(
|
||||
LanguageConfig {
|
||||
name: "Rust".into(),
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_rust::language()),
|
||||
);
|
||||
let mut fake_servers = language
|
||||
@ -3079,9 +3091,12 @@ pub mod tests {
|
||||
let mut language = Language::new(
|
||||
LanguageConfig {
|
||||
name: "Rust".into(),
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_rust::language()),
|
||||
);
|
||||
let mut fake_servers = language
|
||||
@ -3319,9 +3334,12 @@ pub mod tests {
|
||||
let mut language = Language::new(
|
||||
LanguageConfig {
|
||||
name: "Rust".into(),
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_rust::language()),
|
||||
);
|
||||
let mut fake_servers = language
|
||||
|
@ -12,7 +12,9 @@ use collections::HashSet;
|
||||
use futures::Future;
|
||||
use gpui::{View, ViewContext, VisualTestContext};
|
||||
use indoc::indoc;
|
||||
use language::{point_to_lsp, FakeLspAdapter, Language, LanguageConfig, LanguageQueries};
|
||||
use language::{
|
||||
point_to_lsp, FakeLspAdapter, Language, LanguageConfig, LanguageMatcher, LanguageQueries,
|
||||
};
|
||||
use lsp::{notification, request};
|
||||
use multi_buffer::ToPointUtf16;
|
||||
use project::Project;
|
||||
@ -115,9 +117,12 @@ impl EditorLspTestContext {
|
||||
let language = Language::new(
|
||||
LanguageConfig {
|
||||
name: "Rust".into(),
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_rust::language()),
|
||||
)
|
||||
.with_queries(LanguageQueries {
|
||||
@ -160,7 +165,10 @@ impl EditorLspTestContext {
|
||||
let language = Language::new(
|
||||
LanguageConfig {
|
||||
name: "Typescript".into(),
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["ts".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
brackets: language::BracketPairConfig {
|
||||
pairs: vec![language::BracketPair {
|
||||
start: "{".to_string(),
|
||||
|
28
crates/extension/Cargo.toml
Normal file
28
crates/extension/Cargo.toml
Normal 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"] }
|
1
crates/extension/LICENSE-GPL
Symbolic link
1
crates/extension/LICENSE-GPL
Symbolic link
@ -0,0 +1 @@
|
||||
../../LICENSE-GPL
|
344
crates/extension/src/extension_store.rs
Normal file
344
crates/extension/src/extension_store.rs
Normal 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
|
||||
}
|
295
crates/extension/src/extension_store_test.rs
Normal file
295
crates/extension/src/extension_store_test.rs
Normal 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);
|
||||
});
|
||||
}
|
@ -5,7 +5,7 @@ use util::arc_cow::ArcCow;
|
||||
|
||||
/// A shared string is an immutable string that can be cheaply cloned in GPUI
|
||||
/// tasks. Essentially an abstraction over an `Arc<str>` and `&'static str`,
|
||||
#[derive(Deref, DerefMut, Eq, PartialEq, Hash, Clone)]
|
||||
#[derive(Deref, DerefMut, Eq, PartialEq, PartialOrd, Ord, Hash, Clone)]
|
||||
pub struct SharedString(ArcCow<'static, str>);
|
||||
|
||||
impl Default for SharedString {
|
||||
|
@ -52,6 +52,7 @@ smol.workspace = true
|
||||
sum_tree.workspace = true
|
||||
text.workspace = true
|
||||
theme.workspace = true
|
||||
toml.workspace = true
|
||||
tree-sitter-rust = { workspace = true, optional = true }
|
||||
tree-sitter-typescript = { workspace = true, optional = true }
|
||||
pulldown-cmark.workspace = true
|
||||
|
@ -73,17 +73,23 @@ fn test_select_language() {
|
||||
registry.add(Arc::new(Language::new(
|
||||
LanguageConfig {
|
||||
name: "Rust".into(),
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_rust::language()),
|
||||
)));
|
||||
registry.add(Arc::new(Language::new(
|
||||
LanguageConfig {
|
||||
name: "Make".into(),
|
||||
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(),
|
||||
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(),
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["erb".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
block_comment: Some(("<%#".into(), "%>".into())),
|
||||
..Default::default()
|
||||
},
|
||||
@ -2398,9 +2410,12 @@ fn rust_lang() -> Language {
|
||||
Language::new(
|
||||
LanguageConfig {
|
||||
name: "Rust".into(),
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_rust::language()),
|
||||
)
|
||||
.with_indents_query(
|
||||
@ -2450,9 +2465,12 @@ fn json_lang() -> Language {
|
||||
Language::new(
|
||||
LanguageConfig {
|
||||
name: "Json".into(),
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["js".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_json::language()),
|
||||
)
|
||||
}
|
||||
|
@ -20,7 +20,7 @@ pub mod markdown;
|
||||
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use async_trait::async_trait;
|
||||
use collections::{HashMap, HashSet};
|
||||
use collections::{hash_map, HashMap, HashSet};
|
||||
use futures::{
|
||||
channel::{mpsc, oneshot},
|
||||
future::Shared,
|
||||
@ -33,12 +33,13 @@ use lsp::{CodeActionKind, LanguageServerBinary};
|
||||
use parking_lot::{Mutex, RwLock};
|
||||
use postage::watch;
|
||||
use regex::Regex;
|
||||
use serde::{de, Deserialize, Deserializer};
|
||||
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
|
||||
use serde_json::Value;
|
||||
use std::{
|
||||
any::Any,
|
||||
borrow::Cow,
|
||||
cell::RefCell,
|
||||
ffi::OsStr,
|
||||
fmt::Debug,
|
||||
hash::Hash,
|
||||
mem,
|
||||
@ -392,14 +393,13 @@ pub struct LanguageConfig {
|
||||
/// Human-readable name of the language.
|
||||
pub name: Arc<str>,
|
||||
// The name of the grammar in a WASM bundle (experimental).
|
||||
pub grammar_name: Option<Arc<str>>,
|
||||
/// Given a list of `LanguageConfig`'s, the language of a file can be determined based on the path extension matching any of the `path_suffixes`.
|
||||
pub path_suffixes: Vec<String>,
|
||||
pub grammar: Option<Arc<str>>,
|
||||
/// The criteria for matching this language to a given file.
|
||||
#[serde(flatten)]
|
||||
pub matcher: LanguageMatcher,
|
||||
/// List of bracket types in a language.
|
||||
#[serde(default)]
|
||||
pub brackets: BracketPairConfig,
|
||||
/// A regex pattern that determines whether the language should be assigned to a file or not.
|
||||
#[serde(default, deserialize_with = "deserialize_regex")]
|
||||
pub first_line_pattern: Option<Regex>,
|
||||
/// If set to true, auto indentation uses last non empty line to determine
|
||||
/// the indentation level for a new line.
|
||||
#[serde(default = "auto_indent_using_last_non_empty_line_default")]
|
||||
@ -443,6 +443,34 @@ pub struct LanguageConfig {
|
||||
pub prettier_parser_name: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, Default)]
|
||||
pub struct LanguageMatcher {
|
||||
/// Given a list of `LanguageConfig`'s, the language of a file can be determined based on the path extension matching any of the `path_suffixes`.
|
||||
#[serde(default)]
|
||||
pub path_suffixes: Vec<String>,
|
||||
/// A regex pattern that determines whether the language should be assigned to a file or not.
|
||||
#[serde(
|
||||
default,
|
||||
serialize_with = "serialize_regex",
|
||||
deserialize_with = "deserialize_regex"
|
||||
)]
|
||||
pub first_line_pattern: Option<Regex>,
|
||||
}
|
||||
|
||||
pub const QUERY_FILENAME_PREFIXES: &[(
|
||||
&str,
|
||||
fn(&mut LanguageQueries) -> &mut Option<Cow<'static, str>>,
|
||||
)] = &[
|
||||
("highlights", |q| &mut q.highlights),
|
||||
("brackets", |q| &mut q.brackets),
|
||||
("outline", |q| &mut q.outline),
|
||||
("indents", |q| &mut q.indents),
|
||||
("embedding", |q| &mut q.embedding),
|
||||
("injections", |q| &mut q.injections),
|
||||
("overrides", |q| &mut q.overrides),
|
||||
("redactions", |q| &mut q.redactions),
|
||||
];
|
||||
|
||||
/// Tree-sitter language queries for a given language.
|
||||
#[derive(Debug, Default)]
|
||||
pub struct LanguageQueries {
|
||||
@ -506,11 +534,10 @@ impl Default for LanguageConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
name: "".into(),
|
||||
grammar_name: None,
|
||||
path_suffixes: Default::default(),
|
||||
grammar: None,
|
||||
matcher: LanguageMatcher::default(),
|
||||
brackets: Default::default(),
|
||||
auto_indent_using_last_non_empty_line: auto_indent_using_last_non_empty_line_default(),
|
||||
first_line_pattern: Default::default(),
|
||||
increase_indent_pattern: Default::default(),
|
||||
decrease_indent_pattern: Default::default(),
|
||||
autoclose_before: Default::default(),
|
||||
@ -538,6 +565,16 @@ fn deserialize_regex<'de, D: Deserializer<'de>>(d: D) -> Result<Option<Regex>, D
|
||||
}
|
||||
}
|
||||
|
||||
fn serialize_regex<S>(regex: &Option<Regex>, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
match regex {
|
||||
Some(regex) => serializer.serialize_str(regex.as_str()),
|
||||
None => serializer.serialize_none(),
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub struct FakeLspAdapter {
|
||||
@ -702,22 +739,29 @@ type AvailableLanguageId = usize;
|
||||
#[derive(Clone)]
|
||||
struct AvailableLanguage {
|
||||
id: AvailableLanguageId,
|
||||
config: LanguageConfig,
|
||||
grammar: AvailableGrammar,
|
||||
name: Arc<str>,
|
||||
source: AvailableLanguageSource,
|
||||
lsp_adapters: Vec<Arc<dyn LspAdapter>>,
|
||||
loaded: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
enum AvailableGrammar {
|
||||
Native {
|
||||
grammar: tree_sitter::Language,
|
||||
Loaded(tree_sitter::Language),
|
||||
Loading(Vec<oneshot::Sender<Result<tree_sitter::Language>>>),
|
||||
Unloaded(PathBuf),
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
enum AvailableLanguageSource {
|
||||
BuiltIn {
|
||||
asset_dir: &'static str,
|
||||
get_queries: fn(&str) -> LanguageQueries,
|
||||
config: LanguageConfig,
|
||||
},
|
||||
Wasm {
|
||||
Extension {
|
||||
path: Arc<Path>,
|
||||
get_queries: fn(&Path) -> LanguageQueries,
|
||||
matcher: LanguageMatcher,
|
||||
},
|
||||
}
|
||||
|
||||
@ -737,6 +781,7 @@ struct LanguageRegistryState {
|
||||
next_language_server_id: usize,
|
||||
languages: Vec<Arc<Language>>,
|
||||
available_languages: Vec<AvailableLanguage>,
|
||||
grammars: HashMap<String, AvailableGrammar>,
|
||||
next_available_language_id: AvailableLanguageId,
|
||||
loading_languages: HashMap<AvailableLanguageId, Vec<oneshot::Sender<Result<Arc<Language>>>>>,
|
||||
subscription: (watch::Sender<()>, watch::Receiver<()>),
|
||||
@ -758,6 +803,7 @@ impl LanguageRegistry {
|
||||
next_language_server_id: 0,
|
||||
languages: vec![PLAIN_TEXT.clone()],
|
||||
available_languages: Default::default(),
|
||||
grammars: Default::default(),
|
||||
next_available_language_id: 0,
|
||||
loading_languages: Default::default(),
|
||||
subscription: watch::channel(),
|
||||
@ -787,20 +833,24 @@ impl LanguageRegistry {
|
||||
self.state.write().reload();
|
||||
}
|
||||
|
||||
/// Clear out the given languages and reload them from scratch.
|
||||
pub fn reload_languages(&self, languages: &HashSet<Arc<str>>) {
|
||||
self.state.write().reload_languages(languages);
|
||||
}
|
||||
|
||||
pub fn register(
|
||||
&self,
|
||||
asset_dir: &'static str,
|
||||
config: LanguageConfig,
|
||||
grammar: tree_sitter::Language,
|
||||
lsp_adapters: Vec<Arc<dyn LspAdapter>>,
|
||||
get_queries: fn(&str) -> LanguageQueries,
|
||||
) {
|
||||
let state = &mut *self.state.write();
|
||||
state.available_languages.push(AvailableLanguage {
|
||||
id: post_inc(&mut state.next_available_language_id),
|
||||
name: config.name.clone(),
|
||||
source: AvailableLanguageSource::BuiltIn {
|
||||
config,
|
||||
grammar: AvailableGrammar::Native {
|
||||
grammar,
|
||||
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(),
|
||||
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(),
|
||||
grammar: Some("json".into()),
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["json".into()],
|
||||
..Default::default()
|
||||
},
|
||||
tree_sitter_json::language(),
|
||||
..Default::default()
|
||||
},
|
||||
vec![],
|
||||
|_| Default::default(),
|
||||
);
|
||||
@ -2082,10 +2281,13 @@ mod tests {
|
||||
"/rust",
|
||||
LanguageConfig {
|
||||
name: "Rust".into(),
|
||||
grammar: Some("rust".into()),
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["rs".into()],
|
||||
..Default::default()
|
||||
},
|
||||
tree_sitter_rust::language(),
|
||||
..Default::default()
|
||||
},
|
||||
vec![],
|
||||
|_| Default::default(),
|
||||
);
|
||||
|
@ -1,5 +1,5 @@
|
||||
use super::*;
|
||||
use crate::LanguageConfig;
|
||||
use crate::{LanguageConfig, LanguageMatcher};
|
||||
use rand::rngs::StdRng;
|
||||
use std::{env, ops::Range, sync::Arc};
|
||||
use text::{Buffer, BufferId};
|
||||
@ -1092,9 +1092,12 @@ fn html_lang() -> Language {
|
||||
Language::new(
|
||||
LanguageConfig {
|
||||
name: "HTML".into(),
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["html".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_html::language()),
|
||||
)
|
||||
.with_highlights_query(
|
||||
@ -1111,9 +1114,12 @@ fn ruby_lang() -> Language {
|
||||
Language::new(
|
||||
LanguageConfig {
|
||||
name: "Ruby".into(),
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["rb".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_ruby::language()),
|
||||
)
|
||||
.with_highlights_query(
|
||||
@ -1130,9 +1136,12 @@ fn erb_lang() -> Language {
|
||||
Language::new(
|
||||
LanguageConfig {
|
||||
name: "ERB".into(),
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["erb".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_embedded_template::language()),
|
||||
)
|
||||
.with_highlights_query(
|
||||
@ -1163,9 +1172,12 @@ fn rust_lang() -> Language {
|
||||
Language::new(
|
||||
LanguageConfig {
|
||||
name: "Rust".into(),
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_rust::language()),
|
||||
)
|
||||
.with_highlights_query(
|
||||
@ -1189,9 +1201,12 @@ fn markdown_lang() -> Language {
|
||||
Language::new(
|
||||
LanguageConfig {
|
||||
name: "Markdown".into(),
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["md".into()],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_markdown::language()),
|
||||
)
|
||||
.with_injection_query(
|
||||
@ -1209,9 +1224,12 @@ fn elixir_lang() -> Language {
|
||||
Language::new(
|
||||
LanguageConfig {
|
||||
name: "Elixir".into(),
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["ex".into()],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_elixir::language()),
|
||||
)
|
||||
.with_highlights_query(
|
||||
@ -1226,9 +1244,12 @@ fn heex_lang() -> Language {
|
||||
Language::new(
|
||||
LanguageConfig {
|
||||
name: "HEEx".into(),
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["heex".into()],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_heex::language()),
|
||||
)
|
||||
.with_injection_query(
|
||||
|
@ -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,9 +23,12 @@ async fn test_lsp_logs(cx: &mut TestAppContext) {
|
||||
let mut rust_language = Language::new(
|
||||
LanguageConfig {
|
||||
name: "Rust".into(),
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_rust::language()),
|
||||
);
|
||||
let mut fake_rust_servers = rust_language
|
||||
|
@ -5,7 +5,7 @@ use gpui::AppContext;
|
||||
use language::{
|
||||
language_settings::{AllLanguageSettings, LanguageSettingsContent},
|
||||
tree_sitter_rust, tree_sitter_typescript, Diagnostic, FakeLspAdapter, LanguageConfig,
|
||||
LineEnding, OffsetRangeExt, Point, ToPoint,
|
||||
LanguageMatcher, LineEnding, OffsetRangeExt, Point, ToPoint,
|
||||
};
|
||||
use lsp::Url;
|
||||
use parking_lot::Mutex;
|
||||
@ -149,17 +149,23 @@ async fn test_managing_language_servers(cx: &mut gpui::TestAppContext) {
|
||||
let mut rust_language = Language::new(
|
||||
LanguageConfig {
|
||||
name: "Rust".into(),
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_rust::language()),
|
||||
);
|
||||
let mut json_language = Language::new(
|
||||
LanguageConfig {
|
||||
name: "JSON".into(),
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["json".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
None,
|
||||
);
|
||||
let mut fake_rust_servers = rust_language
|
||||
@ -535,9 +541,12 @@ async fn test_reporting_fs_changes_to_language_servers(cx: &mut gpui::TestAppCon
|
||||
let mut language = Language::new(
|
||||
LanguageConfig {
|
||||
name: "Rust".into(),
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_rust::language()),
|
||||
);
|
||||
let mut fake_servers = language
|
||||
@ -970,9 +979,12 @@ async fn test_disk_based_diagnostics_progress(cx: &mut gpui::TestAppContext) {
|
||||
let mut language = Language::new(
|
||||
LanguageConfig {
|
||||
name: "Rust".into(),
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_rust::language()),
|
||||
);
|
||||
let mut fake_servers = language
|
||||
@ -1102,9 +1114,12 @@ async fn test_restarting_server_with_diagnostics_running(cx: &mut gpui::TestAppC
|
||||
let progress_token = "the-progress-token";
|
||||
let mut language = Language::new(
|
||||
LanguageConfig {
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
None,
|
||||
);
|
||||
let mut fake_servers = language
|
||||
@ -1183,9 +1198,12 @@ async fn test_restarting_server_with_diagnostics_published(cx: &mut gpui::TestAp
|
||||
|
||||
let mut language = Language::new(
|
||||
LanguageConfig {
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
None,
|
||||
);
|
||||
let mut fake_servers = language
|
||||
@ -1272,9 +1290,12 @@ async fn test_restarted_server_reporting_invalid_buffer_version(cx: &mut gpui::T
|
||||
|
||||
let mut language = Language::new(
|
||||
LanguageConfig {
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
None,
|
||||
);
|
||||
let mut fake_servers = language
|
||||
@ -1322,9 +1343,12 @@ async fn test_toggling_enable_language_server(cx: &mut gpui::TestAppContext) {
|
||||
let mut rust = Language::new(
|
||||
LanguageConfig {
|
||||
name: Arc::from("Rust"),
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
None,
|
||||
);
|
||||
let mut fake_rust_servers = rust
|
||||
@ -1336,9 +1360,12 @@ async fn test_toggling_enable_language_server(cx: &mut gpui::TestAppContext) {
|
||||
let mut js = Language::new(
|
||||
LanguageConfig {
|
||||
name: Arc::from("JavaScript"),
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["js".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
None,
|
||||
);
|
||||
let mut fake_js_servers = js
|
||||
@ -1451,9 +1478,12 @@ async fn test_transforming_diagnostics(cx: &mut gpui::TestAppContext) {
|
||||
let mut language = Language::new(
|
||||
LanguageConfig {
|
||||
name: "Rust".into(),
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_rust::language()),
|
||||
);
|
||||
let mut fake_servers = language
|
||||
@ -1862,9 +1892,12 @@ async fn test_edits_from_lsp2_with_past_version(cx: &mut gpui::TestAppContext) {
|
||||
let mut language = Language::new(
|
||||
LanguageConfig {
|
||||
name: "Rust".into(),
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_rust::language()),
|
||||
);
|
||||
let mut fake_servers = language.set_fake_lsp_adapter(Default::default()).await;
|
||||
@ -2249,9 +2282,12 @@ async fn test_definition(cx: &mut gpui::TestAppContext) {
|
||||
let mut language = Language::new(
|
||||
LanguageConfig {
|
||||
name: "Rust".into(),
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_rust::language()),
|
||||
);
|
||||
let mut fake_servers = language.set_fake_lsp_adapter(Default::default()).await;
|
||||
@ -2350,9 +2386,12 @@ async fn test_completions_without_edit_ranges(cx: &mut gpui::TestAppContext) {
|
||||
let mut language = Language::new(
|
||||
LanguageConfig {
|
||||
name: "TypeScript".into(),
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["ts".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_typescript::language_typescript()),
|
||||
);
|
||||
let mut fake_language_servers = language
|
||||
@ -2447,9 +2486,12 @@ async fn test_completions_with_carriage_returns(cx: &mut gpui::TestAppContext) {
|
||||
let mut language = Language::new(
|
||||
LanguageConfig {
|
||||
name: "TypeScript".into(),
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["ts".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_typescript::language_typescript()),
|
||||
);
|
||||
let mut fake_language_servers = language
|
||||
@ -2513,9 +2555,12 @@ async fn test_apply_code_actions_with_commands(cx: &mut gpui::TestAppContext) {
|
||||
let mut language = Language::new(
|
||||
LanguageConfig {
|
||||
name: "TypeScript".into(),
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["ts".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
None,
|
||||
);
|
||||
let mut fake_language_servers = language.set_fake_lsp_adapter(Default::default()).await;
|
||||
@ -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(),
|
||||
grammar: Some("rust".into()),
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["rs".into()],
|
||||
..Default::default()
|
||||
},
|
||||
tree_sitter_rust::language(),
|
||||
..Default::default()
|
||||
},
|
||||
vec![],
|
||||
|_| Default::default(),
|
||||
);
|
||||
@ -3649,9 +3698,12 @@ async fn test_rename(cx: &mut gpui::TestAppContext) {
|
||||
let mut language = Language::new(
|
||||
LanguageConfig {
|
||||
name: "Rust".into(),
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_rust::language()),
|
||||
);
|
||||
let mut fake_servers = language
|
||||
|
@ -260,7 +260,7 @@ mod tests {
|
||||
use super::*;
|
||||
use futures::StreamExt;
|
||||
use gpui::{TestAppContext, VisualContext};
|
||||
use language::{FakeLspAdapter, Language, LanguageConfig};
|
||||
use language::{FakeLspAdapter, Language, LanguageConfig, LanguageMatcher};
|
||||
use project::FakeFs;
|
||||
use serde_json::json;
|
||||
use settings::SettingsStore;
|
||||
@ -273,9 +273,12 @@ mod tests {
|
||||
let mut language = Language::new(
|
||||
LanguageConfig {
|
||||
name: "Rust".into(),
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
None,
|
||||
);
|
||||
let mut fake_servers = language
|
||||
|
@ -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,9 +1251,12 @@ fn js_lang() -> Arc<Language> {
|
||||
Language::new(
|
||||
LanguageConfig {
|
||||
name: "Javascript".into(),
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["js".into()],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_typescript::language_tsx()),
|
||||
)
|
||||
.with_embedding_query(
|
||||
@ -1343,7 +1346,10 @@ fn rust_lang() -> Arc<Language> {
|
||||
Language::new(
|
||||
LanguageConfig {
|
||||
name: "Rust".into(),
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["rs".into()],
|
||||
..Default::default()
|
||||
},
|
||||
collapsed_placeholder: " /* ... */ ".to_string(),
|
||||
..Default::default()
|
||||
},
|
||||
@ -1393,9 +1399,12 @@ fn json_lang() -> Arc<Language> {
|
||||
Language::new(
|
||||
LanguageConfig {
|
||||
name: "JSON".into(),
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["json".into()],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_json::language()),
|
||||
)
|
||||
.with_embedding_query(
|
||||
@ -1421,9 +1430,12 @@ fn toml_lang() -> Arc<Language> {
|
||||
Arc::new(Language::new(
|
||||
LanguageConfig {
|
||||
name: "TOML".into(),
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["toml".into()],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_toml::language()),
|
||||
))
|
||||
}
|
||||
@ -1433,9 +1445,12 @@ fn cpp_lang() -> Arc<Language> {
|
||||
Language::new(
|
||||
LanguageConfig {
|
||||
name: "CPP".into(),
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["cpp".into()],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_cpp::language()),
|
||||
)
|
||||
.with_embedding_query(
|
||||
@ -1513,7 +1528,10 @@ fn lua_lang() -> Arc<Language> {
|
||||
Language::new(
|
||||
LanguageConfig {
|
||||
name: "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(),
|
||||
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(),
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["rb".into()],
|
||||
..Default::default()
|
||||
},
|
||||
collapsed_placeholder: "# ...".to_string(),
|
||||
..Default::default()
|
||||
},
|
||||
@ -1638,9 +1662,12 @@ fn elixir_lang() -> Arc<Language> {
|
||||
Language::new(
|
||||
LanguageConfig {
|
||||
name: "Elixir".into(),
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["rs".into()],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_elixir::language()),
|
||||
)
|
||||
.with_embedding_query(
|
||||
|
@ -194,7 +194,9 @@ impl ThemeRegistry {
|
||||
}
|
||||
|
||||
pub fn list_names(&self, _staff: bool) -> Vec<SharedString> {
|
||||
self.state.read().themes.keys().cloned().collect()
|
||||
let mut names = self.state.read().themes.keys().cloned().collect::<Vec<_>>();
|
||||
names.sort();
|
||||
names
|
||||
}
|
||||
|
||||
pub fn list(&self, _staff: bool) -> Vec<ThemeMeta> {
|
||||
@ -263,11 +265,17 @@ impl ThemeRegistry {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Loads the user theme from the specified path and adds it to the registry.
|
||||
pub async fn load_user_theme(&self, theme_path: &Path, fs: Arc<dyn Fs>) -> Result<()> {
|
||||
pub async fn read_user_theme(theme_path: &Path, fs: Arc<dyn Fs>) -> Result<ThemeFamilyContent> {
|
||||
let reader = fs.open_sync(&theme_path).await?;
|
||||
let theme = serde_json_lenient::from_reader(reader)?;
|
||||
|
||||
Ok(theme)
|
||||
}
|
||||
|
||||
/// Loads the user theme from the specified path and adds it to the registry.
|
||||
pub async fn load_user_theme(&self, theme_path: &Path, fs: Arc<dyn Fs>) -> Result<()> {
|
||||
let theme = Self::read_user_theme(theme_path, fs).await?;
|
||||
|
||||
self.insert_user_theme_families([theme]);
|
||||
|
||||
Ok(())
|
||||
|
@ -1,5 +1,6 @@
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
cmp::Ordering,
|
||||
fmt::{self, Debug},
|
||||
hash::{Hash, Hasher},
|
||||
sync::Arc,
|
||||
@ -18,6 +19,18 @@ impl<'a, T: ?Sized + PartialEq> PartialEq for ArcCow<'a, T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: ?Sized + PartialOrd> PartialOrd for ArcCow<'a, T> {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
self.as_ref().partial_cmp(other.as_ref())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: ?Sized + Ord> Ord for ArcCow<'a, T> {
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
self.as_ref().cmp(other.as_ref())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: ?Sized + Eq> Eq for ArcCow<'a, T> {}
|
||||
|
||||
impl<'a, T: ?Sized + Hash> Hash for ArcCow<'a, T> {
|
||||
|
@ -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 {
|
||||
|
@ -44,6 +44,7 @@ db.workspace = true
|
||||
diagnostics.workspace = true
|
||||
editor.workspace = true
|
||||
env_logger.workspace = true
|
||||
extension.workspace = true
|
||||
feature_flags.workspace = true
|
||||
feedback.workspace = true
|
||||
file_finder.workspace = true
|
||||
|
@ -4,8 +4,8 @@ pub use language::*;
|
||||
use node_runtime::NodeRuntime;
|
||||
use rust_embed::RustEmbed;
|
||||
use settings::Settings;
|
||||
use std::{borrow::Cow, fs, path::Path, str, sync::Arc};
|
||||
use util::{asset_str, paths::PLUGINS_DIR, ResultExt};
|
||||
use std::{str, sync::Arc};
|
||||
use util::asset_str;
|
||||
|
||||
use self::{deno::DenoSettings, elixir::ElixirSettings};
|
||||
|
||||
@ -62,30 +62,69 @@ pub fn init(
|
||||
ElixirSettings::register(cx);
|
||||
DenoSettings::register(cx);
|
||||
|
||||
let language = |name, grammar, adapters| {
|
||||
languages.register(name, load_config(name), grammar, adapters, load_queries)
|
||||
languages.add_grammars([
|
||||
("bash", tree_sitter_bash::language()),
|
||||
("beancount", tree_sitter_beancount::language()),
|
||||
("c", tree_sitter_c::language()),
|
||||
("c_sharp", tree_sitter_c_sharp::language()),
|
||||
("cpp", tree_sitter_cpp::language()),
|
||||
("css", tree_sitter_css::language()),
|
||||
("elixir", tree_sitter_elixir::language()),
|
||||
("elm", tree_sitter_elm::language()),
|
||||
(
|
||||
"embedded_template",
|
||||
tree_sitter_embedded_template::language(),
|
||||
),
|
||||
("erlang", tree_sitter_erlang::language()),
|
||||
("gitcommit", tree_sitter_gitcommit::language()),
|
||||
("gleam", tree_sitter_gleam::language()),
|
||||
("glsl", tree_sitter_glsl::language()),
|
||||
("go", tree_sitter_go::language()),
|
||||
("gomod", tree_sitter_gomod::language()),
|
||||
("gowork", tree_sitter_gowork::language()),
|
||||
("haskell", tree_sitter_haskell::language()),
|
||||
("hcl", tree_sitter_hcl::language()),
|
||||
("heex", tree_sitter_heex::language()),
|
||||
("html", tree_sitter_html::language()),
|
||||
("json", tree_sitter_json::language()),
|
||||
("lua", tree_sitter_lua::language()),
|
||||
("markdown", tree_sitter_markdown::language()),
|
||||
("nix", tree_sitter_nix::language()),
|
||||
("nu", tree_sitter_nu::language()),
|
||||
("ocaml", tree_sitter_ocaml::language_ocaml()),
|
||||
(
|
||||
"ocaml_interface",
|
||||
tree_sitter_ocaml::language_ocaml_interface(),
|
||||
),
|
||||
("php", tree_sitter_php::language_php()),
|
||||
("proto", tree_sitter_proto::language()),
|
||||
("purescript", tree_sitter_purescript::language()),
|
||||
("python", tree_sitter_python::language()),
|
||||
("racket", tree_sitter_racket::language()),
|
||||
("ruby", tree_sitter_ruby::language()),
|
||||
("rust", tree_sitter_rust::language()),
|
||||
("scheme", tree_sitter_scheme::language()),
|
||||
("svelte", tree_sitter_svelte::language()),
|
||||
("toml", tree_sitter_toml::language()),
|
||||
("tsx", tree_sitter_typescript::language_tsx()),
|
||||
("typescript", tree_sitter_typescript::language_typescript()),
|
||||
("uiua", tree_sitter_uiua::language()),
|
||||
("vue", tree_sitter_vue::language()),
|
||||
("yaml", tree_sitter_yaml::language()),
|
||||
("zig", tree_sitter_zig::language()),
|
||||
]);
|
||||
|
||||
let language = |name: &'static str, adapters| {
|
||||
languages.register(name, load_config(name), adapters, load_queries)
|
||||
};
|
||||
|
||||
language("bash", tree_sitter_bash::language(), vec![]);
|
||||
language("beancount", tree_sitter_beancount::language(), vec![]);
|
||||
language(
|
||||
"c",
|
||||
tree_sitter_c::language(),
|
||||
vec![Arc::new(c::CLspAdapter) as Arc<dyn LspAdapter>],
|
||||
);
|
||||
language(
|
||||
"cpp",
|
||||
tree_sitter_cpp::language(),
|
||||
vec![Arc::new(c::CLspAdapter)],
|
||||
);
|
||||
language(
|
||||
"csharp",
|
||||
tree_sitter_c_sharp::language(),
|
||||
vec![Arc::new(csharp::OmniSharpAdapter {})],
|
||||
);
|
||||
language("bash", vec![]);
|
||||
language("beancount", vec![]);
|
||||
language("c", vec![Arc::new(c::CLspAdapter) as Arc<dyn LspAdapter>]);
|
||||
language("cpp", vec![Arc::new(c::CLspAdapter)]);
|
||||
language("csharp", vec![Arc::new(csharp::OmniSharpAdapter {})]);
|
||||
language(
|
||||
"css",
|
||||
tree_sitter_css::language(),
|
||||
vec![
|
||||
Arc::new(css::CssLspAdapter::new(node_runtime.clone())),
|
||||
Arc::new(tailwind::TailwindLspAdapter::new(node_runtime.clone())),
|
||||
@ -95,53 +134,32 @@ pub fn init(
|
||||
match &ElixirSettings::get(None, cx).lsp {
|
||||
elixir::ElixirLspSetting::ElixirLs => language(
|
||||
"elixir",
|
||||
tree_sitter_elixir::language(),
|
||||
vec![
|
||||
Arc::new(elixir::ElixirLspAdapter),
|
||||
Arc::new(tailwind::TailwindLspAdapter::new(node_runtime.clone())),
|
||||
],
|
||||
),
|
||||
elixir::ElixirLspSetting::NextLs => language(
|
||||
"elixir",
|
||||
tree_sitter_elixir::language(),
|
||||
vec![Arc::new(elixir::NextLspAdapter)],
|
||||
),
|
||||
elixir::ElixirLspSetting::NextLs => {
|
||||
language("elixir", vec![Arc::new(elixir::NextLspAdapter)])
|
||||
}
|
||||
elixir::ElixirLspSetting::Local { path, arguments } => language(
|
||||
"elixir",
|
||||
tree_sitter_elixir::language(),
|
||||
vec![Arc::new(elixir::LocalLspAdapter {
|
||||
path: path.clone(),
|
||||
arguments: arguments.clone(),
|
||||
})],
|
||||
),
|
||||
}
|
||||
language("gitcommit", tree_sitter_gitcommit::language(), vec![]);
|
||||
language(
|
||||
"erlang",
|
||||
tree_sitter_erlang::language(),
|
||||
vec![Arc::new(erlang::ErlangLspAdapter)],
|
||||
);
|
||||
language("gitcommit", vec![]);
|
||||
language("erlang", vec![Arc::new(erlang::ErlangLspAdapter)]);
|
||||
|
||||
language(
|
||||
"gleam",
|
||||
tree_sitter_gleam::language(),
|
||||
vec![Arc::new(gleam::GleamLspAdapter)],
|
||||
);
|
||||
language(
|
||||
"go",
|
||||
tree_sitter_go::language(),
|
||||
vec![Arc::new(go::GoLspAdapter)],
|
||||
);
|
||||
language("gomod", tree_sitter_gomod::language(), vec![]);
|
||||
language("gowork", tree_sitter_gowork::language(), vec![]);
|
||||
language(
|
||||
"zig",
|
||||
tree_sitter_zig::language(),
|
||||
vec![Arc::new(zig::ZlsAdapter)],
|
||||
);
|
||||
language("gleam", vec![Arc::new(gleam::GleamLspAdapter)]);
|
||||
language("go", vec![Arc::new(go::GoLspAdapter)]);
|
||||
language("gomod", vec![]);
|
||||
language("gowork", vec![]);
|
||||
language("zig", vec![Arc::new(zig::ZlsAdapter)]);
|
||||
language(
|
||||
"heex",
|
||||
tree_sitter_heex::language(),
|
||||
vec![
|
||||
Arc::new(elixir::ElixirLspAdapter),
|
||||
Arc::new(tailwind::TailwindLspAdapter::new(node_runtime.clone())),
|
||||
@ -149,48 +167,32 @@ pub fn init(
|
||||
);
|
||||
language(
|
||||
"json",
|
||||
tree_sitter_json::language(),
|
||||
vec![Arc::new(json::JsonLspAdapter::new(
|
||||
node_runtime.clone(),
|
||||
languages.clone(),
|
||||
))],
|
||||
);
|
||||
language("markdown", tree_sitter_markdown::language(), vec![]);
|
||||
language("markdown", vec![]);
|
||||
language(
|
||||
"python",
|
||||
tree_sitter_python::language(),
|
||||
vec![Arc::new(python::PythonLspAdapter::new(
|
||||
node_runtime.clone(),
|
||||
))],
|
||||
);
|
||||
language(
|
||||
"rust",
|
||||
tree_sitter_rust::language(),
|
||||
vec![Arc::new(rust::RustLspAdapter)],
|
||||
);
|
||||
language(
|
||||
"toml",
|
||||
tree_sitter_toml::language(),
|
||||
vec![Arc::new(toml::TaploLspAdapter)],
|
||||
);
|
||||
language("rust", vec![Arc::new(rust::RustLspAdapter)]);
|
||||
language("toml", vec![Arc::new(toml::TaploLspAdapter)]);
|
||||
match &DenoSettings::get(None, cx).enable {
|
||||
true => {
|
||||
language(
|
||||
"tsx",
|
||||
tree_sitter_typescript::language_tsx(),
|
||||
vec![
|
||||
Arc::new(deno::DenoLspAdapter::new()),
|
||||
Arc::new(tailwind::TailwindLspAdapter::new(node_runtime.clone())),
|
||||
],
|
||||
);
|
||||
language(
|
||||
"typescript",
|
||||
tree_sitter_typescript::language_typescript(),
|
||||
vec![Arc::new(deno::DenoLspAdapter::new())],
|
||||
);
|
||||
language("typescript", vec![Arc::new(deno::DenoLspAdapter::new())]);
|
||||
language(
|
||||
"javascript",
|
||||
tree_sitter_typescript::language_tsx(),
|
||||
vec![
|
||||
Arc::new(deno::DenoLspAdapter::new()),
|
||||
Arc::new(tailwind::TailwindLspAdapter::new(node_runtime.clone())),
|
||||
@ -200,7 +202,6 @@ pub fn init(
|
||||
false => {
|
||||
language(
|
||||
"tsx",
|
||||
tree_sitter_typescript::language_tsx(),
|
||||
vec![
|
||||
Arc::new(typescript::TypeScriptLspAdapter::new(node_runtime.clone())),
|
||||
Arc::new(typescript::EsLintLspAdapter::new(node_runtime.clone())),
|
||||
@ -209,7 +210,6 @@ pub fn init(
|
||||
);
|
||||
language(
|
||||
"typescript",
|
||||
tree_sitter_typescript::language_typescript(),
|
||||
vec![
|
||||
Arc::new(typescript::TypeScriptLspAdapter::new(node_runtime.clone())),
|
||||
Arc::new(typescript::EsLintLspAdapter::new(node_runtime.clone())),
|
||||
@ -217,7 +217,6 @@ pub fn init(
|
||||
);
|
||||
language(
|
||||
"javascript",
|
||||
tree_sitter_typescript::language_tsx(),
|
||||
vec![
|
||||
Arc::new(typescript::TypeScriptLspAdapter::new(node_runtime.clone())),
|
||||
Arc::new(typescript::EsLintLspAdapter::new(node_runtime.clone())),
|
||||
@ -227,47 +226,31 @@ pub fn init(
|
||||
}
|
||||
}
|
||||
|
||||
language(
|
||||
"haskell",
|
||||
tree_sitter_haskell::language(),
|
||||
vec![Arc::new(haskell::HaskellLanguageServer {})],
|
||||
);
|
||||
language("haskell", vec![Arc::new(haskell::HaskellLanguageServer {})]);
|
||||
language(
|
||||
"html",
|
||||
tree_sitter_html::language(),
|
||||
vec![
|
||||
Arc::new(html::HtmlLspAdapter::new(node_runtime.clone())),
|
||||
Arc::new(tailwind::TailwindLspAdapter::new(node_runtime.clone())),
|
||||
],
|
||||
);
|
||||
language(
|
||||
"ruby",
|
||||
tree_sitter_ruby::language(),
|
||||
vec![Arc::new(ruby::RubyLanguageServer)],
|
||||
);
|
||||
language("ruby", vec![Arc::new(ruby::RubyLanguageServer)]);
|
||||
language(
|
||||
"erb",
|
||||
tree_sitter_embedded_template::language(),
|
||||
vec![
|
||||
Arc::new(ruby::RubyLanguageServer),
|
||||
Arc::new(tailwind::TailwindLspAdapter::new(node_runtime.clone())),
|
||||
],
|
||||
);
|
||||
language("scheme", tree_sitter_scheme::language(), vec![]);
|
||||
language("racket", tree_sitter_racket::language(), vec![]);
|
||||
language(
|
||||
"lua",
|
||||
tree_sitter_lua::language(),
|
||||
vec![Arc::new(lua::LuaLspAdapter)],
|
||||
);
|
||||
language("scheme", vec![]);
|
||||
language("racket", vec![]);
|
||||
language("lua", vec![Arc::new(lua::LuaLspAdapter)]);
|
||||
language(
|
||||
"yaml",
|
||||
tree_sitter_yaml::language(),
|
||||
vec![Arc::new(yaml::YamlLspAdapter::new(node_runtime.clone()))],
|
||||
);
|
||||
language(
|
||||
"svelte",
|
||||
tree_sitter_svelte::language(),
|
||||
vec![
|
||||
Arc::new(svelte::SvelteLspAdapter::new(node_runtime.clone())),
|
||||
Arc::new(tailwind::TailwindLspAdapter::new(node_runtime.clone())),
|
||||
@ -275,7 +258,6 @@ pub fn init(
|
||||
);
|
||||
language(
|
||||
"php",
|
||||
tree_sitter_php::language_php(),
|
||||
vec![
|
||||
Arc::new(php::IntelephenseLspAdapter::new(node_runtime.clone())),
|
||||
Arc::new(tailwind::TailwindLspAdapter::new(node_runtime.clone())),
|
||||
@ -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
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
name = "Shell Script"
|
||||
grammar = "bash"
|
||||
path_suffixes = ["sh", "bash", "bashrc", "bash_profile", "bash_aliases", "bash_logout", "profile", "zsh", "zshrc", "zshenv", "zsh_profile", "zsh_aliases", "zsh_histfile", "zlogin", "zprofile", ".env"]
|
||||
line_comments = ["# "]
|
||||
first_line_pattern = "^#!.*\\b(?:ba|z)?sh\\b"
|
||||
|
@ -1,3 +1,4 @@
|
||||
name = "Beancount"
|
||||
grammar = "beancount"
|
||||
path_suffixes = ["beancount"]
|
||||
brackets = [{ start = "\"", end = "\"", close = false, newline = false }]
|
||||
|
@ -1,4 +1,5 @@
|
||||
name = "C"
|
||||
grammar = "c"
|
||||
path_suffixes = ["c"]
|
||||
line_comments = ["// "]
|
||||
autoclose_before = ";:.,=}])>"
|
||||
|
@ -1,4 +1,5 @@
|
||||
name = "C++"
|
||||
grammar = "cpp"
|
||||
path_suffixes = ["cc", "cpp", "h", "hpp", "cxx", "hxx", "inl"]
|
||||
line_comments = ["// "]
|
||||
autoclose_before = ";:.,=}])>"
|
||||
|
@ -1,4 +1,5 @@
|
||||
name = "CSharp"
|
||||
grammar = "c_sharp"
|
||||
path_suffixes = ["cs"]
|
||||
line_comments = ["// "]
|
||||
autoclose_before = ";:.,=}])>"
|
||||
|
@ -1,4 +1,5 @@
|
||||
name = "CSS"
|
||||
grammar = "css"
|
||||
path_suffixes = ["css"]
|
||||
autoclose_before = ";:.,=}])>"
|
||||
brackets = [
|
||||
|
@ -1,4 +1,5 @@
|
||||
name = "Elixir"
|
||||
grammar = "elixir"
|
||||
path_suffixes = ["ex", "exs"]
|
||||
line_comments = ["# "]
|
||||
autoclose_before = ";:.,=}])>"
|
||||
|
@ -1,4 +1,5 @@
|
||||
name = "Elm"
|
||||
grammar = "elm"
|
||||
path_suffixes = ["elm"]
|
||||
line_comments = ["-- "]
|
||||
block_comment = ["{- ", " -}"]
|
||||
|
@ -1,4 +1,5 @@
|
||||
name = "ERB"
|
||||
grammar = "embedded_template"
|
||||
path_suffixes = ["erb"]
|
||||
autoclose_before = ">})"
|
||||
brackets = [
|
||||
|
@ -1,4 +1,5 @@
|
||||
name = "Erlang"
|
||||
grammar = "erlang"
|
||||
# TODO: support parsing rebar.config files
|
||||
# # https://github.com/WhatsApp/tree-sitter-erlang/issues/3
|
||||
path_suffixes = ["erl", "hrl", "app.src", "escript", "xrl", "yrl", "Emakefile", "rebar.config"]
|
||||
|
@ -1,4 +1,5 @@
|
||||
name = "Git Commit"
|
||||
grammar = "git_commit"
|
||||
path_suffixes = [
|
||||
# Refer to https://github.com/neovim/neovim/blob/master/runtime/lua/vim/filetype.lua#L1286-L1290
|
||||
"TAG_EDITMSG",
|
||||
|
@ -1,4 +1,5 @@
|
||||
name = "Gleam"
|
||||
grammar = "gleam"
|
||||
path_suffixes = ["gleam"]
|
||||
line_comments = ["// ", "/// "]
|
||||
autoclose_before = ";:.,=}])>"
|
||||
|
@ -1,4 +1,5 @@
|
||||
name = "GLSL"
|
||||
grammar = "glsl"
|
||||
path_suffixes = ["vert", "frag", "tesc", "tese", "geom", "comp"]
|
||||
line_comments = ["// "]
|
||||
block_comment = ["/* ", " */"]
|
||||
|
@ -1,4 +1,5 @@
|
||||
name = "Go"
|
||||
grammar = "go"
|
||||
path_suffixes = ["go"]
|
||||
line_comments = ["// "]
|
||||
autoclose_before = ";:.,=}])>"
|
||||
|
@ -1,4 +1,5 @@
|
||||
name = "Go Mod"
|
||||
grammar = "go"
|
||||
path_suffixes = ["mod"]
|
||||
line_comments = ["//"]
|
||||
autoclose_before = ")"
|
||||
|
@ -1,4 +1,5 @@
|
||||
name = "Go Work"
|
||||
grammar = "go_work"
|
||||
path_suffixes = ["work"]
|
||||
line_comments = ["//"]
|
||||
autoclose_before = ")"
|
||||
|
@ -1,4 +1,5 @@
|
||||
name = "Haskell"
|
||||
grammar = "haskell"
|
||||
path_suffixes = ["hs"]
|
||||
autoclose_before = ",=)}]"
|
||||
line_comments = ["-- "]
|
||||
|
@ -1,4 +1,5 @@
|
||||
name = "HCL"
|
||||
grammar = "hcl"
|
||||
path_suffixes = ["hcl"]
|
||||
line_comments = ["# ", "// "]
|
||||
block_comment = ["/*", "*/"]
|
||||
|
@ -1,4 +1,5 @@
|
||||
name = "HEEX"
|
||||
grammar = "heex"
|
||||
path_suffixes = ["heex"]
|
||||
autoclose_before = ">})"
|
||||
brackets = [
|
||||
|
@ -1,4 +1,5 @@
|
||||
name = "HTML"
|
||||
grammar = "html"
|
||||
path_suffixes = ["html", "htm", "shtml"]
|
||||
autoclose_before = ">})"
|
||||
block_comment = ["<!-- ", " -->"]
|
||||
|
@ -1,4 +1,5 @@
|
||||
name = "JavaScript"
|
||||
grammar = "tsx"
|
||||
path_suffixes = ["js", "jsx", "mjs", "cjs"]
|
||||
first_line_pattern = '^#!.*\bnode\b'
|
||||
line_comments = ["// "]
|
||||
|
@ -1,4 +1,5 @@
|
||||
name = "JSON"
|
||||
grammar = "json"
|
||||
path_suffixes = ["json"]
|
||||
line_comments = ["// "]
|
||||
autoclose_before = ",]}"
|
||||
|
@ -1,4 +1,5 @@
|
||||
name = "Lua"
|
||||
grammar = "lua"
|
||||
path_suffixes = ["lua"]
|
||||
line_comments = ["-- "]
|
||||
autoclose_before = ",]}"
|
||||
|
@ -1,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 },
|
||||
|
@ -1,4 +1,5 @@
|
||||
name = "Nix"
|
||||
grammar = "nix"
|
||||
path_suffixes = ["nix"]
|
||||
line_comments = ["# "]
|
||||
block_comment = ["/* ", " */"]
|
||||
|
@ -1,4 +1,5 @@
|
||||
name = "Nu"
|
||||
grammar = "nu"
|
||||
path_suffixes = ["nu"]
|
||||
line_comments = ["# "]
|
||||
autoclose_before = ";:.,=}])>` \n\t\""
|
||||
|
@ -1,4 +1,5 @@
|
||||
name = "OCaml Interface"
|
||||
grammar = "ocaml_interface"
|
||||
path_suffixes = ["mli"]
|
||||
block_comment = ["(* ", "*)"]
|
||||
autoclose_before = ";,=)}"
|
||||
|
@ -1,4 +1,5 @@
|
||||
name = "OCaml"
|
||||
grammar = "ocaml"
|
||||
path_suffixes = ["ml"]
|
||||
block_comment = ["(* ", "*)"]
|
||||
autoclose_before = ";,=)}]"
|
||||
|
@ -1,4 +1,5 @@
|
||||
name = "PHP"
|
||||
grammar = "php"
|
||||
path_suffixes = ["php"]
|
||||
first_line_pattern = '^#!.*php'
|
||||
line_comments = ["// ", "# "]
|
||||
|
@ -1,4 +1,5 @@
|
||||
name = "proto"
|
||||
grammar = "proto"
|
||||
path_suffixes = ["proto"]
|
||||
line_comments = ["// "]
|
||||
autoclose_before = ";:.,=}])>"
|
||||
|
@ -1,4 +1,5 @@
|
||||
name = "PureScript"
|
||||
grammar = "purescript"
|
||||
path_suffixes = ["purs"]
|
||||
autoclose_before = ",=)}]"
|
||||
line_comments = ["-- "]
|
||||
|
@ -1,4 +1,5 @@
|
||||
name = "Python"
|
||||
grammar = "python"
|
||||
path_suffixes = ["py", "pyi", "mpy"]
|
||||
first_line_pattern = '^#!.*\bpython[0-9.]*\b'
|
||||
line_comments = ["# "]
|
||||
|
@ -1,4 +1,5 @@
|
||||
name = "Racket"
|
||||
grammar = "racket"
|
||||
path_suffixes = ["rkt"]
|
||||
line_comments = ["; "]
|
||||
autoclose_before = "])"
|
||||
|
@ -1,4 +1,5 @@
|
||||
name = "Ruby"
|
||||
grammar = "ruby"
|
||||
path_suffixes = [
|
||||
"rb",
|
||||
"Gemfile",
|
||||
|
@ -1,4 +1,5 @@
|
||||
name = "Rust"
|
||||
grammar = "rust"
|
||||
path_suffixes = ["rs"]
|
||||
line_comments = ["// ", "/// ", "//! "]
|
||||
autoclose_before = ";:.,=}])>"
|
||||
|
@ -1,4 +1,5 @@
|
||||
name = "Scheme"
|
||||
grammar = "scheme"
|
||||
path_suffixes = ["scm", "ss"]
|
||||
line_comments = ["; "]
|
||||
autoclose_before = "])"
|
||||
|
@ -1,4 +1,5 @@
|
||||
name = "Svelte"
|
||||
grammar = "svelte"
|
||||
path_suffixes = ["svelte"]
|
||||
block_comment = ["<!-- ", " -->"]
|
||||
autoclose_before = ";:.,=}])>"
|
||||
|
@ -1,4 +1,5 @@
|
||||
name = "Terraform"
|
||||
grammar = "terraform"
|
||||
path_suffixes = ["tf", "tfvars"]
|
||||
line_comments = ["# ", "// "]
|
||||
block_comment = ["/*", "*/"]
|
||||
|
@ -1,4 +1,5 @@
|
||||
name = "TOML"
|
||||
grammar = "toml"
|
||||
path_suffixes = ["Cargo.lock", "toml"]
|
||||
line_comments = ["# "]
|
||||
autoclose_before = ",]}"
|
||||
|
@ -1,4 +1,5 @@
|
||||
name = "TSX"
|
||||
grammar = "tsx"
|
||||
path_suffixes = ["tsx"]
|
||||
line_comments = ["// "]
|
||||
autoclose_before = ";:.,=}])>"
|
||||
|
@ -1,4 +1,5 @@
|
||||
name = "TypeScript"
|
||||
grammar = "typescript"
|
||||
path_suffixes = ["ts", "cts", "d.cts", "d.mts", "mts"]
|
||||
line_comments = ["// "]
|
||||
autoclose_before = ";:.,=}])>"
|
||||
|
@ -1,4 +1,5 @@
|
||||
name = "Uiua"
|
||||
grammar = "uiua"
|
||||
path_suffixes = ["ua"]
|
||||
line_comments = ["# "]
|
||||
autoclose_before = ")]}\""
|
||||
|
@ -1,4 +1,5 @@
|
||||
name = "Vue.js"
|
||||
grammar = "vue"
|
||||
path_suffixes = ["vue"]
|
||||
block_comment = ["<!-- ", " -->"]
|
||||
autoclose_before = ";:.,=}])>"
|
||||
|
@ -1,4 +1,5 @@
|
||||
name = "YAML"
|
||||
grammar = "yaml"
|
||||
path_suffixes = ["yml", "yaml"]
|
||||
line_comments = ["# "]
|
||||
autoclose_before = ",]}"
|
||||
|
@ -1,4 +1,5 @@
|
||||
name = "Zig"
|
||||
grammar = "zig"
|
||||
path_suffixes = ["zig"]
|
||||
line_comments = ["// "]
|
||||
autoclose_before = ";:.,=}])>"
|
||||
|
@ -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>) {}
|
||||
|
@ -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,9 +2742,12 @@ mod tests {
|
||||
Arc::new(language::Language::new(
|
||||
language::LanguageConfig {
|
||||
name: "Rust".into(),
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_rust::language()),
|
||||
))
|
||||
}
|
||||
|
@ -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.
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user