lib.rs

  1use anyhow::Context;
  2use gpui::{AppContext, BorrowAppContext};
  3pub use language::*;
  4use node_runtime::NodeRuntime;
  5use rust_embed::RustEmbed;
  6use settings::SettingsStore;
  7use smol::stream::StreamExt;
  8use std::{str, sync::Arc};
  9use util::{asset_str, ResultExt};
 10
 11use crate::{bash::bash_task_context, python::python_task_context, rust::RustContextProvider};
 12
 13mod bash;
 14mod c;
 15mod css;
 16mod go;
 17mod json;
 18mod python;
 19mod ruby;
 20mod rust;
 21mod tailwind;
 22mod typescript;
 23mod yaml;
 24
 25// 1. Add tree-sitter-{language} parser to zed crate
 26// 2. Create a language directory in zed/crates/zed/src/languages and add the language to init function below
 27// 3. Add config.toml to the newly created language directory using existing languages as a template
 28// 4. Copy highlights from tree sitter repo for the language into a highlights.scm file.
 29//      Note: github highlights take the last match while zed takes the first
 30// 5. Add indents.scm, outline.scm, and brackets.scm to implement indent on newline, outline/breadcrumbs,
 31//    and autoclosing brackets respectively
 32// 6. If the language has injections add an injections.scm query file
 33
 34#[derive(RustEmbed)]
 35#[folder = "src/"]
 36#[exclude = "*.rs"]
 37struct LanguageDir;
 38
 39pub fn init(
 40    languages: Arc<LanguageRegistry>,
 41    node_runtime: Arc<dyn NodeRuntime>,
 42    cx: &mut AppContext,
 43) {
 44    languages.register_native_grammars([
 45        ("bash", tree_sitter_bash::language()),
 46        ("c", tree_sitter_c::language()),
 47        ("cpp", tree_sitter_cpp::language()),
 48        ("css", tree_sitter_css::language()),
 49        (
 50            "embedded_template",
 51            tree_sitter_embedded_template::language(),
 52        ),
 53        ("go", tree_sitter_go::language()),
 54        ("gomod", tree_sitter_gomod::language()),
 55        ("gowork", tree_sitter_gowork::language()),
 56        ("jsdoc", tree_sitter_jsdoc::language()),
 57        ("json", tree_sitter_json::language()),
 58        ("markdown", tree_sitter_markdown::language()),
 59        ("proto", tree_sitter_proto::language()),
 60        ("python", tree_sitter_python::language()),
 61        ("regex", tree_sitter_regex::language()),
 62        ("ruby", tree_sitter_ruby::language()),
 63        ("rust", tree_sitter_rust::language()),
 64        ("tsx", tree_sitter_typescript::language_tsx()),
 65        ("typescript", tree_sitter_typescript::language_typescript()),
 66        ("yaml", tree_sitter_yaml::language()),
 67    ]);
 68
 69    macro_rules! language {
 70        ($name:literal) => {
 71            let config = load_config($name);
 72            languages.register_language(
 73                config.name.clone(),
 74                config.grammar.clone(),
 75                config.matcher.clone(),
 76                move || Ok((config.clone(), load_queries($name), None)),
 77            );
 78        };
 79        ($name:literal, $adapters:expr) => {
 80            let config = load_config($name);
 81            // typeck helper
 82            let adapters: Vec<Arc<dyn LspAdapter>> = $adapters;
 83            for adapter in adapters {
 84                languages.register_lsp_adapter(config.name.clone(), adapter);
 85            }
 86            languages.register_language(
 87                config.name.clone(),
 88                config.grammar.clone(),
 89                config.matcher.clone(),
 90                move || Ok((config.clone(), load_queries($name), None)),
 91            );
 92        };
 93        ($name:literal, $adapters:expr, $context_provider:expr) => {
 94            let config = load_config($name);
 95            // typeck helper
 96            let adapters: Vec<Arc<dyn LspAdapter>> = $adapters;
 97            for adapter in adapters {
 98                languages.register_lsp_adapter(config.name.clone(), adapter);
 99            }
100            languages.register_language(
101                config.name.clone(),
102                config.grammar.clone(),
103                config.matcher.clone(),
104                move || {
105                    Ok((
106                        config.clone(),
107                        load_queries($name),
108                        Some(Arc::new($context_provider)),
109                    ))
110                },
111            );
112        };
113    }
114    language!("bash", Vec::new(), bash_task_context());
115    language!("c", vec![Arc::new(c::CLspAdapter) as Arc<dyn LspAdapter>]);
116    language!("cpp", vec![Arc::new(c::CLspAdapter)]);
117    language!(
118        "css",
119        vec![
120            Arc::new(css::CssLspAdapter::new(node_runtime.clone())),
121            Arc::new(tailwind::TailwindLspAdapter::new(node_runtime.clone())),
122        ]
123    );
124    language!("go", vec![Arc::new(go::GoLspAdapter)]);
125    language!("gomod");
126    language!("gowork");
127    language!(
128        "json",
129        vec![Arc::new(json::JsonLspAdapter::new(
130            node_runtime.clone(),
131            languages.clone(),
132        ))]
133    );
134    language!("markdown");
135    language!(
136        "python",
137        vec![Arc::new(python::PythonLspAdapter::new(
138            node_runtime.clone(),
139        ))],
140        python_task_context()
141    );
142    language!(
143        "rust",
144        vec![Arc::new(rust::RustLspAdapter)],
145        RustContextProvider
146    );
147    language!(
148        "tsx",
149        vec![Arc::new(typescript::TypeScriptLspAdapter::new(
150            node_runtime.clone()
151        ))]
152    );
153    language!(
154        "typescript",
155        vec![Arc::new(typescript::TypeScriptLspAdapter::new(
156            node_runtime.clone()
157        ))]
158    );
159    language!(
160        "javascript",
161        vec![Arc::new(typescript::TypeScriptLspAdapter::new(
162            node_runtime.clone()
163        ))]
164    );
165    language!(
166        "jsdoc",
167        vec![Arc::new(typescript::TypeScriptLspAdapter::new(
168            node_runtime.clone(),
169        ))]
170    );
171    language!("ruby", vec![Arc::new(ruby::RubyLanguageServer)]);
172    language!(
173        "erb",
174        vec![
175            Arc::new(ruby::RubyLanguageServer),
176            Arc::new(tailwind::TailwindLspAdapter::new(node_runtime.clone())),
177        ]
178    );
179    language!("regex");
180    language!(
181        "yaml",
182        vec![Arc::new(yaml::YamlLspAdapter::new(node_runtime.clone()))]
183    );
184    language!("proto");
185
186    let tailwind_languages = [
187        "Astro",
188        "HEEX",
189        "HTML",
190        "PHP",
191        "Svelte",
192        "TSX",
193        "JavaScript",
194        "Vue.js",
195    ];
196
197    for language in tailwind_languages {
198        languages.register_secondary_lsp_adapter(
199            language.into(),
200            Arc::new(tailwind::TailwindLspAdapter::new(node_runtime.clone())),
201        );
202    }
203
204    let eslint_languages = ["TSX", "TypeScript", "JavaScript", "Vue.js"];
205    for language in eslint_languages {
206        languages.register_secondary_lsp_adapter(
207            language.into(),
208            Arc::new(typescript::EsLintLspAdapter::new(node_runtime.clone())),
209        );
210    }
211
212    let mut subscription = languages.subscribe();
213    let mut prev_language_settings = languages.language_settings();
214
215    cx.spawn(|cx| async move {
216        while subscription.next().await.is_some() {
217            let language_settings = languages.language_settings();
218            if language_settings != prev_language_settings {
219                cx.update(|cx| {
220                    cx.update_global(|settings: &mut SettingsStore, cx| {
221                        settings
222                            .set_extension_settings(language_settings.clone(), cx)
223                            .log_err();
224                    });
225                })?;
226                prev_language_settings = language_settings;
227            }
228        }
229        anyhow::Ok(())
230    })
231    .detach();
232}
233
234#[cfg(any(test, feature = "test-support"))]
235pub fn language(name: &str, grammar: tree_sitter::Language) -> Arc<Language> {
236    Arc::new(
237        Language::new(load_config(name), Some(grammar))
238            .with_queries(load_queries(name))
239            .unwrap(),
240    )
241}
242
243fn load_config(name: &str) -> LanguageConfig {
244    let config_toml = String::from_utf8(
245        LanguageDir::get(&format!("{}/config.toml", name))
246            .unwrap()
247            .data
248            .to_vec(),
249    )
250    .unwrap();
251
252    ::toml::from_str(&config_toml)
253        .with_context(|| format!("failed to load config.toml for language {name:?}"))
254        .unwrap()
255}
256
257fn load_queries(name: &str) -> LanguageQueries {
258    let mut result = LanguageQueries::default();
259    for path in LanguageDir::iter() {
260        if let Some(remainder) = path.strip_prefix(name).and_then(|p| p.strip_prefix('/')) {
261            if !remainder.ends_with(".scm") {
262                continue;
263            }
264            for (name, query) in QUERY_FILENAME_PREFIXES {
265                if remainder.starts_with(name) {
266                    let contents = asset_str::<LanguageDir>(path.as_ref());
267                    match query(&mut result) {
268                        None => *query(&mut result) = Some(contents),
269                        Some(r) => r.to_mut().push_str(contents.as_ref()),
270                    }
271                }
272            }
273        }
274    }
275    result
276}