Move `LanguageRegistry` into `buffer`

Antonio Scandurra , Nathan Sobo , and Max Brunsfeld created

Co-Authored-By: Nathan Sobo <nathan@zed.dev>
Co-Authored-By: Max Brunsfeld <max@zed.dev>

Change summary

buffer/src/language.rs |  97 ++++++++++++++++++++++++++++++
buffer/src/lib.rs      |   2 
server/src/rpc.rs      |   2 
zed/src/editor.rs      |  14 ++-
zed/src/language.rs    | 138 +++++++------------------------------------
zed/src/lib.rs         |   4 
zed/src/main.rs        |   2 
zed/src/project.rs     |   2 
zed/src/test.rs        |   8 +
zed/src/worktree.rs    |   3 
10 files changed, 141 insertions(+), 131 deletions(-)

Detailed changes

buffer/src/language.rs 🔗

@@ -1,7 +1,7 @@
 use crate::{HighlightMap, SyntaxTheme};
 use parking_lot::Mutex;
 use serde::Deserialize;
-use std::str;
+use std::{path::Path, str, sync::Arc};
 use tree_sitter::{Language as Grammar, Query};
 pub use tree_sitter::{Parser, Tree};
 
@@ -25,6 +25,41 @@ pub struct Language {
     pub highlight_map: Mutex<HighlightMap>,
 }
 
+#[derive(Default)]
+pub struct LanguageRegistry {
+    languages: Vec<Arc<Language>>,
+}
+
+impl LanguageRegistry {
+    pub fn new() -> Self {
+        Self::default()
+    }
+
+    pub fn add(&mut self, language: Arc<Language>) {
+        self.languages.push(language);
+    }
+
+    pub fn set_theme(&self, theme: &SyntaxTheme) {
+        for language in &self.languages {
+            language.set_theme(theme);
+        }
+    }
+
+    pub fn select_language(&self, path: impl AsRef<Path>) -> Option<&Arc<Language>> {
+        let path = path.as_ref();
+        let filename = path.file_name().and_then(|name| name.to_str());
+        let extension = path.extension().and_then(|name| name.to_str());
+        let path_suffixes = [extension, filename];
+        self.languages.iter().find(|language| {
+            language
+                .config
+                .path_suffixes
+                .iter()
+                .any(|suffix| path_suffixes.contains(&Some(suffix.as_str())))
+        })
+    }
+}
+
 impl Language {
     pub fn name(&self) -> &str {
         self.config.name.as_str()
@@ -38,3 +73,63 @@ impl Language {
         *self.highlight_map.lock() = HighlightMap::new(self.highlight_query.capture_names(), theme);
     }
 }
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn test_select_language() {
+        let grammar = tree_sitter_rust::language();
+        let registry = LanguageRegistry {
+            languages: vec![
+                Arc::new(Language {
+                    config: LanguageConfig {
+                        name: "Rust".to_string(),
+                        path_suffixes: vec!["rs".to_string()],
+                        ..Default::default()
+                    },
+                    grammar,
+                    highlight_query: Query::new(grammar, "").unwrap(),
+                    brackets_query: Query::new(grammar, "").unwrap(),
+                    highlight_map: Default::default(),
+                }),
+                Arc::new(Language {
+                    config: LanguageConfig {
+                        name: "Make".to_string(),
+                        path_suffixes: vec!["Makefile".to_string(), "mk".to_string()],
+                        ..Default::default()
+                    },
+                    grammar,
+                    highlight_query: Query::new(grammar, "").unwrap(),
+                    brackets_query: Query::new(grammar, "").unwrap(),
+                    highlight_map: Default::default(),
+                }),
+            ],
+        };
+
+        // matching file extension
+        assert_eq!(
+            registry.select_language("zed/lib.rs").map(|l| l.name()),
+            Some("Rust")
+        );
+        assert_eq!(
+            registry.select_language("zed/lib.mk").map(|l| l.name()),
+            Some("Make")
+        );
+
+        // matching filename
+        assert_eq!(
+            registry.select_language("zed/Makefile").map(|l| l.name()),
+            Some("Make")
+        );
+
+        // matching suffix that is not the full file extension or filename
+        assert_eq!(registry.select_language("zed/cars").map(|l| l.name()), None);
+        assert_eq!(
+            registry.select_language("zed/a.cars").map(|l| l.name()),
+            None
+        );
+        assert_eq!(registry.select_language("zed/sumk").map(|l| l.name()), None);
+    }
+}

buffer/src/lib.rs 🔗

@@ -15,7 +15,7 @@ use clock::ReplicaId;
 use gpui::{AppContext, Entity, ModelContext, MutableAppContext, Task};
 pub use highlight_map::{HighlightId, HighlightMap};
 use language::Tree;
-pub use language::{Language, LanguageConfig};
+pub use language::{Language, LanguageConfig, LanguageRegistry};
 use lazy_static::lazy_static;
 use operation_queue::OperationQueue;
 use parking_lot::Mutex;

server/src/rpc.rs 🔗

@@ -975,10 +975,10 @@ mod tests {
         time::Duration,
     };
     use zed::{
+        buffer::LanguageRegistry,
         channel::{Channel, ChannelDetails, ChannelList},
         editor::{Editor, EditorStyle, Insert},
         fs::{FakeFs, Fs as _},
-        language::LanguageRegistry,
         people_panel::JoinWorktree,
         project::ProjectPath,
         rpc::{self, Client, Credentials, EstablishConnectionError},

zed/src/editor.rs 🔗

@@ -2753,7 +2753,11 @@ impl SelectionExt for Selection {
 #[cfg(test)]
 mod tests {
     use super::*;
-    use crate::{editor::Point, language::LanguageRegistry, settings, test::sample_text};
+    use crate::{
+        editor::Point,
+        settings,
+        test::{self, sample_text},
+    };
     use buffer::History;
     use unindent::Unindent;
 
@@ -4232,9 +4236,9 @@ mod tests {
 
     #[gpui::test]
     async fn test_select_larger_smaller_syntax_node(mut cx: gpui::TestAppContext) {
-        let settings = cx.read(settings::test).1;
-        let languages = LanguageRegistry::new();
-        let lang = languages.select_language("z.rs");
+        let app_state = cx.update(test::test_app_state);
+
+        let lang = app_state.languages.select_language("z.rs");
         let text = r#"
             use mod1::mod2::{mod3, mod4};
 
@@ -4247,7 +4251,7 @@ mod tests {
             let history = History::new(text.into());
             Buffer::from_history(0, history, None, lang.cloned(), cx)
         });
-        let (_, view) = cx.add_window(|cx| build_editor(buffer, settings.clone(), cx));
+        let (_, view) = cx.add_window(|cx| build_editor(buffer, app_state.settings.clone(), cx));
         view.condition(&cx, |view, cx| !view.buffer.read(cx).is_parsing())
             .await;
 

zed/src/language.rs 🔗

@@ -1,128 +1,36 @@
-use buffer::{HighlightMap, Language, SyntaxTheme};
+use buffer::{HighlightMap, Language, LanguageRegistry};
 use parking_lot::Mutex;
 use rust_embed::RustEmbed;
-use std::{path::Path, str, sync::Arc};
+use std::{str, sync::Arc};
 use tree_sitter::Query;
-pub use tree_sitter::{Parser, Tree};
 
 #[derive(RustEmbed)]
 #[folder = "languages"]
-pub struct LanguageDir;
+struct LanguageDir;
 
-pub struct LanguageRegistry {
-    languages: Vec<Arc<Language>>,
+pub fn build_language_registry() -> LanguageRegistry {
+    let mut languages = LanguageRegistry::default();
+    languages.add(Arc::new(rust()));
+    languages
 }
 
-impl LanguageRegistry {
-    pub fn new() -> Self {
-        let grammar = tree_sitter_rust::language();
-        let rust_config =
-            toml::from_slice(&LanguageDir::get("rust/config.toml").unwrap().data).unwrap();
-        let rust_language = Language {
-            config: rust_config,
-            grammar,
-            highlight_query: Self::load_query(grammar, "rust/highlights.scm"),
-            brackets_query: Self::load_query(grammar, "rust/brackets.scm"),
-            highlight_map: Mutex::new(HighlightMap::default()),
-        };
-
-        Self {
-            languages: vec![Arc::new(rust_language)],
-        }
-    }
-
-    pub fn set_theme(&self, theme: &SyntaxTheme) {
-        for language in &self.languages {
-            language.set_theme(theme);
-        }
-    }
-
-    pub fn select_language(&self, path: impl AsRef<Path>) -> Option<&Arc<Language>> {
-        let path = path.as_ref();
-        let filename = path.file_name().and_then(|name| name.to_str());
-        let extension = path.extension().and_then(|name| name.to_str());
-        let path_suffixes = [extension, filename];
-        self.languages.iter().find(|language| {
-            language
-                .config
-                .path_suffixes
-                .iter()
-                .any(|suffix| path_suffixes.contains(&Some(suffix.as_str())))
-        })
-    }
-
-    fn load_query(grammar: tree_sitter::Language, path: &str) -> Query {
-        Query::new(
-            grammar,
-            str::from_utf8(&LanguageDir::get(path).unwrap().data).unwrap(),
-        )
-        .unwrap()
+pub fn rust() -> Language {
+    let grammar = tree_sitter_rust::language();
+    let rust_config =
+        toml::from_slice(&LanguageDir::get("rust/config.toml").unwrap().data).unwrap();
+    Language {
+        config: rust_config,
+        grammar,
+        highlight_query: load_query(grammar, "rust/highlights.scm"),
+        brackets_query: load_query(grammar, "rust/brackets.scm"),
+        highlight_map: Mutex::new(HighlightMap::default()),
     }
 }
 
-impl Default for LanguageRegistry {
-    fn default() -> Self {
-        Self::new()
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-    use buffer::LanguageConfig;
-
-    #[test]
-    fn test_select_language() {
-        let grammar = tree_sitter_rust::language();
-        let registry = LanguageRegistry {
-            languages: vec![
-                Arc::new(Language {
-                    config: LanguageConfig {
-                        name: "Rust".to_string(),
-                        path_suffixes: vec!["rs".to_string()],
-                        ..Default::default()
-                    },
-                    grammar,
-                    highlight_query: Query::new(grammar, "").unwrap(),
-                    brackets_query: Query::new(grammar, "").unwrap(),
-                    highlight_map: Default::default(),
-                }),
-                Arc::new(Language {
-                    config: LanguageConfig {
-                        name: "Make".to_string(),
-                        path_suffixes: vec!["Makefile".to_string(), "mk".to_string()],
-                        ..Default::default()
-                    },
-                    grammar,
-                    highlight_query: Query::new(grammar, "").unwrap(),
-                    brackets_query: Query::new(grammar, "").unwrap(),
-                    highlight_map: Default::default(),
-                }),
-            ],
-        };
-
-        // matching file extension
-        assert_eq!(
-            registry.select_language("zed/lib.rs").map(|l| l.name()),
-            Some("Rust")
-        );
-        assert_eq!(
-            registry.select_language("zed/lib.mk").map(|l| l.name()),
-            Some("Make")
-        );
-
-        // matching filename
-        assert_eq!(
-            registry.select_language("zed/Makefile").map(|l| l.name()),
-            Some("Make")
-        );
-
-        // matching suffix that is not the full file extension or filename
-        assert_eq!(registry.select_language("zed/cars").map(|l| l.name()), None);
-        assert_eq!(
-            registry.select_language("zed/a.cars").map(|l| l.name()),
-            None
-        );
-        assert_eq!(registry.select_language("zed/sumk").map(|l| l.name()), None);
-    }
+fn load_query(grammar: tree_sitter::Language, path: &str) -> Query {
+    Query::new(
+        grammar,
+        str::from_utf8(&LanguageDir::get(path).unwrap().data).unwrap(),
+    )
+    .unwrap()
 }

zed/src/lib.rs 🔗

@@ -23,6 +23,8 @@ pub mod workspace;
 pub mod worktree;
 
 use crate::util::TryFutureExt;
+pub use buffer;
+use buffer::LanguageRegistry;
 use channel::ChannelList;
 use gpui::{action, keymap::Binding, ModelHandle};
 use parking_lot::Mutex;
@@ -41,7 +43,7 @@ const MIN_FONT_SIZE: f32 = 6.0;
 pub struct AppState {
     pub settings_tx: Arc<Mutex<watch::Sender<Settings>>>,
     pub settings: watch::Receiver<Settings>,
-    pub languages: Arc<language::LanguageRegistry>,
+    pub languages: Arc<LanguageRegistry>,
     pub themes: Arc<settings::ThemeRegistry>,
     pub rpc: Arc<rpc::Client>,
     pub user_store: ModelHandle<user::UserStore>,

zed/src/main.rs 🔗

@@ -32,7 +32,7 @@ fn main() {
 
     let themes = settings::ThemeRegistry::new(Assets, app.font_cache());
     let (settings_tx, settings) = settings::channel(&app.font_cache(), &themes).unwrap();
-    let languages = Arc::new(language::LanguageRegistry::new());
+    let languages = Arc::new(language::build_language_registry());
     languages.set_theme(&settings.borrow().theme.syntax);
 
     app.run(move |cx| {

zed/src/project.rs 🔗

@@ -1,13 +1,13 @@
 use crate::{
     fs::Fs,
     fuzzy::{self, PathMatch},
-    language::LanguageRegistry,
     rpc::Client,
     util::TryFutureExt as _,
     worktree::{self, Worktree},
     AppState,
 };
 use anyhow::Result;
+use buffer::LanguageRegistry;
 use futures::Future;
 use gpui::{AppContext, Entity, ModelContext, ModelHandle, Task};
 use std::{

zed/src/test.rs 🔗

@@ -3,13 +3,14 @@ use crate::{
     channel::ChannelList,
     fs::FakeFs,
     http::{HttpClient, Request, Response, ServerResponse},
-    language::LanguageRegistry,
+    language,
     rpc::{self, Client, Credentials, EstablishConnectionError},
     settings::{self, ThemeRegistry},
     user::UserStore,
     AppState,
 };
 use anyhow::{anyhow, Result};
+use buffer::LanguageRegistry;
 use futures::{future::BoxFuture, Future};
 use gpui::{AsyncAppContext, Entity, ModelHandle, MutableAppContext, TestAppContext};
 use parking_lot::Mutex;
@@ -83,7 +84,8 @@ fn write_tree(path: &Path, tree: serde_json::Value) {
 
 pub fn test_app_state(cx: &mut MutableAppContext) -> Arc<AppState> {
     let (settings_tx, settings) = settings::test(cx);
-    let languages = Arc::new(LanguageRegistry::new());
+    let mut languages = LanguageRegistry::new();
+    languages.add(Arc::new(language::rust()));
     let themes = ThemeRegistry::new(Assets, cx.font_cache().clone());
     let rpc = rpc::Client::new();
     let http = FakeHttpClient::new(|_| async move { Ok(ServerResponse::new(404)) });
@@ -92,7 +94,7 @@ pub fn test_app_state(cx: &mut MutableAppContext) -> Arc<AppState> {
         settings_tx: Arc::new(Mutex::new(settings_tx)),
         settings,
         themes,
-        languages: languages.clone(),
+        languages: Arc::new(languages),
         channel_list: cx.add_model(|cx| ChannelList::new(user_store.clone(), rpc.clone(), cx)),
         rpc,
         user_store,

zed/src/worktree.rs 🔗

@@ -4,13 +4,12 @@ use self::ignore::IgnoreStack;
 use crate::{
     fs::{self, Fs},
     fuzzy::CharBag,
-    language::LanguageRegistry,
     rpc::{self, proto, Status},
     util::{Bias, TryFutureExt},
 };
 use ::ignore::gitignore::{Gitignore, GitignoreBuilder};
 use anyhow::{anyhow, Result};
-use buffer::{self, Buffer, History, Operation, Rope};
+use buffer::{self, Buffer, History, LanguageRegistry, Operation, Rope};
 use clock::ReplicaId;
 use futures::{Stream, StreamExt};
 use gpui::{