Add Buffer::language_at, update MultiBuffer to use it

Max Brunsfeld and Julia Risley created

Co-authored-by: Julia Risley <floc@unpromptedtirade.com>

Change summary

Cargo.lock                             | 12 +++
crates/editor/Cargo.toml               |  4 +
crates/editor/src/editor.rs            | 94 ++++++++++++++++++++++++++-
crates/editor/src/multi_buffer.rs      | 27 +++++++
crates/language/src/buffer.rs          | 21 +++++
crates/language/src/language.rs        | 29 ++++----
crates/language/src/syntax_map.rs      | 91 +++++++++++++++++---------
crates/language/src/tests.rs           |  2 
crates/zed/src/languages.rs            |  6 
crates/zed/src/languages/c.rs          |  7 -
crates/zed/src/languages/elixir.rs     |  4 
crates/zed/src/languages/go.rs         |  4 
crates/zed/src/languages/python.rs     |  7 -
crates/zed/src/languages/rust.rs       |  6 
crates/zed/src/languages/typescript.rs |  8 -
crates/zed/src/zed.rs                  |  4 
16 files changed, 245 insertions(+), 81 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -1719,6 +1719,8 @@ dependencies = [
  "text",
  "theme",
  "tree-sitter",
+ "tree-sitter-html",
+ "tree-sitter-javascript",
  "tree-sitter-rust",
  "unindent",
  "util",
@@ -6062,6 +6064,16 @@ dependencies = [
  "tree-sitter",
 ]
 
+[[package]]
+name = "tree-sitter-javascript"
+version = "0.20.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2490fab08630b2c8943c320f7b63473cbf65511c8d83aec551beb9b4375906ed"
+dependencies = [
+ "cc",
+ "tree-sitter",
+]
+
 [[package]]
 name = "tree-sitter-json"
 version = "0.19.0"

crates/editor/Cargo.toml 🔗

@@ -51,6 +51,8 @@ serde = { version = "1.0", features = ["derive", "rc"] }
 smallvec = { version = "1.6", features = ["union"] }
 smol = "1.2"
 tree-sitter-rust = { version = "*", optional = true }
+tree-sitter-html = { version = "*", optional = true }
+tree-sitter-javascript = { version = "*", optional = true }
 
 [dev-dependencies]
 text = { path = "../text", features = ["test-support"] }
@@ -67,3 +69,5 @@ rand = "0.8"
 unindent = "0.1.7"
 tree-sitter = "0.20"
 tree-sitter-rust = "0.20"
+tree-sitter-html = "0.19"
+tree-sitter-javascript = "0.20"

crates/editor/src/editor.rs 🔗

@@ -1116,7 +1116,7 @@ impl Editor {
         &self,
         point: T,
         cx: &'a AppContext,
-    ) -> Option<&'a Arc<Language>> {
+    ) -> Option<Arc<Language>> {
         self.buffer.read(cx).language_at(point, cx)
     }
 
@@ -4501,9 +4501,9 @@ impl Editor {
                     // as that portion won't be used for detecting if a line is a comment.
                     let full_comment_prefix: Arc<str> = if let Some(prefix) = buffer
                         .language_at(selection.start, cx)
-                        .and_then(|l| l.line_comment_prefix())
+                        .and_then(|l| l.line_comment_prefix().map(|p| p.into()))
                     {
-                        prefix.into()
+                        prefix
                     } else {
                         return;
                     };
@@ -6713,7 +6713,7 @@ mod tests {
         platform::{WindowBounds, WindowOptions},
     };
     use indoc::indoc;
-    use language::{FakeLspAdapter, LanguageConfig};
+    use language::{FakeLspAdapter, LanguageConfig, LanguageRegistry};
     use project::FakeFs;
     use settings::EditorSettings;
     use std::{cell::RefCell, rc::Rc, time::Instant};
@@ -9792,6 +9792,92 @@ mod tests {
         });
     }
 
+    #[gpui::test]
+    async fn test_autoclose_with_embedded_language(cx: &mut gpui::TestAppContext) {
+        let mut cx = EditorTestContext::new(cx).await;
+
+        let html_language = Arc::new(
+            Language::new(
+                LanguageConfig {
+                    name: "HTML".into(),
+                    brackets: vec![BracketPair {
+                        start: "<".to_string(),
+                        end: ">".to_string(),
+                        close: true,
+                        newline: true,
+                    }],
+                    autoclose_before: "})]".to_string(),
+                    ..Default::default()
+                },
+                Some(tree_sitter_html::language()),
+            )
+            .with_injection_query(
+                r#"
+                (script_element
+                    (raw_text) @content
+                    (#set! "language" "javascript"))
+                "#,
+            )
+            .unwrap(),
+        );
+
+        let javascript_language = Arc::new(Language::new(
+            LanguageConfig {
+                name: "JavaScript".into(),
+                brackets: vec![BracketPair {
+                    start: "/*".to_string(),
+                    end: "*/".to_string(),
+                    close: true,
+                    newline: true,
+                }],
+                autoclose_before: "})]".to_string(),
+                ..Default::default()
+            },
+            Some(tree_sitter_javascript::language()),
+        ));
+
+        let registry = Arc::new(LanguageRegistry::test());
+        registry.add(html_language.clone());
+        registry.add(javascript_language.clone());
+
+        cx.update_buffer(|buffer, cx| {
+            buffer.set_language_registry(registry);
+            buffer.set_language(Some(html_language), cx);
+        });
+
+        cx.set_state(
+            &r#"
+                <body>ˇ
+                    <script>
+                        var x = 1;ˇ
+                    </script>
+                </body>
+            "#
+            .unindent(),
+        );
+
+        let cursors = cx.update_editor(|editor, cx| editor.selections.ranges::<usize>(cx));
+        cx.update_buffer(|buffer, _| {
+            let snapshot = buffer.snapshot();
+            assert_eq!(
+                snapshot
+                    .language_at(cursors[0].start)
+                    .unwrap()
+                    .name()
+                    .as_ref(),
+                "HTML"
+            );
+            assert_eq!(
+                snapshot
+                    .language_at(cursors[1].start)
+                    .unwrap()
+                    .name()
+                    .as_ref(),
+                "JavaScript"
+            );
+        });
+    }
+
     #[gpui::test]
     async fn test_surround_with_pair(cx: &mut gpui::TestAppContext) {
         cx.update(|cx| cx.set_global(Settings::test(cx)));

crates/editor/src/multi_buffer.rs 🔗

@@ -1212,9 +1212,9 @@ impl MultiBuffer {
         &self,
         point: T,
         cx: &'a AppContext,
-    ) -> Option<&'a Arc<Language>> {
+    ) -> Option<Arc<Language>> {
         self.point_to_buffer_offset(point, cx)
-            .and_then(|(buffer, _)| buffer.read(cx).language())
+            .and_then(|(buffer, offset)| buffer.read(cx).language_at(offset))
     }
 
     pub fn files<'a>(&'a self, cx: &'a AppContext) -> SmallVec<[&'a dyn File; 2]> {
@@ -1940,6 +1940,24 @@ impl MultiBufferSnapshot {
         }
     }
 
+    pub fn point_to_buffer_offset<T: ToOffset>(
+        &self,
+        point: T,
+    ) -> Option<(&BufferSnapshot, usize)> {
+        let offset = point.to_offset(&self);
+        let mut cursor = self.excerpts.cursor::<usize>();
+        cursor.seek(&offset, Bias::Right, &());
+        if cursor.item().is_none() {
+            cursor.prev(&());
+        }
+
+        cursor.item().map(|excerpt| {
+            let excerpt_start = excerpt.range.context.start.to_offset(&excerpt.buffer);
+            let buffer_point = excerpt_start + offset - *cursor.start();
+            (&excerpt.buffer, buffer_point)
+        })
+    }
+
     pub fn suggested_indents(
         &self,
         rows: impl IntoIterator<Item = u32>,
@@ -2490,6 +2508,11 @@ impl MultiBufferSnapshot {
             .and_then(|excerpt| excerpt.buffer.language())
     }
 
+    pub fn language_at<'a, T: ToOffset>(&'a self, point: T) -> Option<&'a Arc<Language>> {
+        self.point_to_buffer_offset(point)
+            .and_then(|(buffer, offset)| buffer.language_at(offset))
+    }
+
     pub fn is_dirty(&self) -> bool {
         self.is_dirty
     }

crates/language/src/buffer.rs 🔗

@@ -641,6 +641,15 @@ impl Buffer {
         self.language.as_ref()
     }
 
+    pub fn language_at<D: ToOffset>(&self, position: D) -> Option<Arc<Language>> {
+        let offset = position.to_offset(self);
+        self.syntax_map
+            .lock()
+            .layers_for_range(offset..offset, &self.text)
+            .last()
+            .map(|info| info.language.clone())
+    }
+
     pub fn parse_count(&self) -> usize {
         self.parse_count
     }
@@ -1826,6 +1835,14 @@ impl BufferSnapshot {
         self.language.as_ref()
     }
 
+    pub fn language_at<D: ToOffset>(&self, position: D) -> Option<&Arc<Language>> {
+        let offset = position.to_offset(self);
+        self.syntax
+            .layers_for_range(offset..offset, &self.text)
+            .last()
+            .map(|info| info.language)
+    }
+
     pub fn surrounding_word<T: ToOffset>(&self, start: T) -> (Range<usize>, Option<CharKind>) {
         let mut start = start.to_offset(self);
         let mut end = start;
@@ -1858,8 +1875,8 @@ impl BufferSnapshot {
     pub fn range_for_syntax_ancestor<T: ToOffset>(&self, range: Range<T>) -> Option<Range<usize>> {
         let range = range.start.to_offset(self)..range.end.to_offset(self);
         let mut result: Option<Range<usize>> = None;
-        'outer: for (_, _, node) in self.syntax.layers_for_range(range.clone(), &self.text) {
-            let mut cursor = node.walk();
+        'outer: for layer in self.syntax.layers_for_range(range.clone(), &self.text) {
+            let mut cursor = layer.node.walk();
 
             // Descend to the first leaf that touches the start of the range,
             // and if the range is non-empty, extends beyond the start.

crates/language/src/language.rs 🔗

@@ -135,7 +135,7 @@ impl CachedLspAdapter {
     pub async fn label_for_completion(
         &self,
         completion_item: &lsp::CompletionItem,
-        language: &Language,
+        language: &Arc<Language>,
     ) -> Option<CodeLabel> {
         self.adapter
             .label_for_completion(completion_item, language)
@@ -146,7 +146,7 @@ impl CachedLspAdapter {
         &self,
         name: &str,
         kind: lsp::SymbolKind,
-        language: &Language,
+        language: &Arc<Language>,
     ) -> Option<CodeLabel> {
         self.adapter.label_for_symbol(name, kind, language).await
     }
@@ -175,7 +175,7 @@ pub trait LspAdapter: 'static + Send + Sync {
     async fn label_for_completion(
         &self,
         _: &lsp::CompletionItem,
-        _: &Language,
+        _: &Arc<Language>,
     ) -> Option<CodeLabel> {
         None
     }
@@ -184,7 +184,7 @@ pub trait LspAdapter: 'static + Send + Sync {
         &self,
         _: &str,
         _: lsp::SymbolKind,
-        _: &Language,
+        _: &Arc<Language>,
     ) -> Option<CodeLabel> {
         None
     }
@@ -793,7 +793,7 @@ impl Language {
     }
 
     pub async fn label_for_completion(
-        &self,
+        self: &Arc<Self>,
         completion: &lsp::CompletionItem,
     ) -> Option<CodeLabel> {
         self.adapter
@@ -802,7 +802,11 @@ impl Language {
             .await
     }
 
-    pub async fn label_for_symbol(&self, name: &str, kind: lsp::SymbolKind) -> Option<CodeLabel> {
+    pub async fn label_for_symbol(
+        self: &Arc<Self>,
+        name: &str,
+        kind: lsp::SymbolKind,
+    ) -> Option<CodeLabel> {
         self.adapter
             .as_ref()?
             .label_for_symbol(name, kind, self)
@@ -810,20 +814,17 @@ impl Language {
     }
 
     pub fn highlight_text<'a>(
-        &'a self,
+        self: &'a Arc<Self>,
         text: &'a Rope,
         range: Range<usize>,
     ) -> Vec<(Range<usize>, HighlightId)> {
         let mut result = Vec::new();
         if let Some(grammar) = &self.grammar {
             let tree = grammar.parse_text(text, None);
-            let captures = SyntaxSnapshot::single_tree_captures(
-                range.clone(),
-                text,
-                &tree,
-                grammar,
-                |grammar| grammar.highlights_query.as_ref(),
-            );
+            let captures =
+                SyntaxSnapshot::single_tree_captures(range.clone(), text, &tree, self, |grammar| {
+                    grammar.highlights_query.as_ref()
+                });
             let highlight_maps = vec![grammar.highlight_map()];
             let mut offset = 0;
             for chunk in BufferChunks::new(text, range, Some((captures, highlight_maps)), vec![]) {

crates/language/src/syntax_map.rs 🔗

@@ -92,6 +92,12 @@ struct SyntaxLayer {
     language: Arc<Language>,
 }
 
+pub struct SyntaxLayerInfo<'a> {
+    pub depth: usize,
+    pub node: Node<'a>,
+    pub language: &'a Arc<Language>,
+}
+
 #[derive(Debug, Clone)]
 struct SyntaxLayerSummary {
     min_depth: usize,
@@ -473,13 +479,18 @@ impl SyntaxSnapshot {
         range: Range<usize>,
         text: &'a Rope,
         tree: &'a Tree,
-        grammar: &'a Grammar,
+        language: &'a Arc<Language>,
         query: fn(&Grammar) -> Option<&Query>,
     ) -> SyntaxMapCaptures<'a> {
         SyntaxMapCaptures::new(
             range.clone(),
             text,
-            [(grammar, 0, tree.root_node())].into_iter(),
+            [SyntaxLayerInfo {
+                language,
+                depth: 0,
+                node: tree.root_node(),
+            }]
+            .into_iter(),
             query,
         )
     }
@@ -513,7 +524,7 @@ impl SyntaxSnapshot {
     }
 
     #[cfg(test)]
-    pub fn layers(&self, buffer: &BufferSnapshot) -> Vec<(&Grammar, usize, Node)> {
+    pub fn layers(&self, buffer: &BufferSnapshot) -> Vec<SyntaxLayerInfo> {
         self.layers_for_range(0..buffer.len(), buffer)
     }
 
@@ -521,7 +532,7 @@ impl SyntaxSnapshot {
         &self,
         range: Range<T>,
         buffer: &BufferSnapshot,
-    ) -> Vec<(&Grammar, usize, Node)> {
+    ) -> Vec<SyntaxLayerInfo> {
         let start = buffer.anchor_before(range.start.to_offset(buffer));
         let end = buffer.anchor_after(range.end.to_offset(buffer));
 
@@ -538,16 +549,14 @@ impl SyntaxSnapshot {
         let mut result = Vec::new();
         cursor.next(buffer);
         while let Some(layer) = cursor.item() {
-            if let Some(grammar) = &layer.language.grammar {
-                result.push((
-                    grammar.as_ref(),
-                    layer.depth,
-                    layer.tree.root_node_with_offset(
-                        layer.range.start.to_offset(buffer),
-                        layer.range.start.to_point(buffer).to_ts_point(),
-                    ),
-                ));
-            }
+            result.push(SyntaxLayerInfo {
+                language: &layer.language,
+                depth: layer.depth,
+                node: layer.tree.root_node_with_offset(
+                    layer.range.start.to_offset(buffer),
+                    layer.range.start.to_point(buffer).to_ts_point(),
+                ),
+            });
             cursor.next(buffer)
         }
 
@@ -559,7 +568,7 @@ impl<'a> SyntaxMapCaptures<'a> {
     fn new(
         range: Range<usize>,
         text: &'a Rope,
-        layers: impl Iterator<Item = (&'a Grammar, usize, Node<'a>)>,
+        layers: impl Iterator<Item = SyntaxLayerInfo<'a>>,
         query: fn(&Grammar) -> Option<&Query>,
     ) -> Self {
         let mut result = Self {
@@ -567,11 +576,19 @@ impl<'a> SyntaxMapCaptures<'a> {
             grammars: Vec::new(),
             active_layer_count: 0,
         };
-        for (grammar, depth, node) in layers {
-            let query = if let Some(query) = query(grammar) {
-                query
-            } else {
-                continue;
+        for SyntaxLayerInfo {
+            language,
+            depth,
+            node,
+        } in layers
+        {
+            let grammar = match &language.grammar {
+                Some(grammer) => grammer,
+                None => continue,
+            };
+            let query = match query(&grammar) {
+                Some(query) => query,
+                None => continue,
             };
 
             let mut query_cursor = QueryCursorHandle::new();
@@ -678,15 +695,23 @@ impl<'a> SyntaxMapMatches<'a> {
     fn new(
         range: Range<usize>,
         text: &'a Rope,
-        layers: impl Iterator<Item = (&'a Grammar, usize, Node<'a>)>,
+        layers: impl Iterator<Item = SyntaxLayerInfo<'a>>,
         query: fn(&Grammar) -> Option<&Query>,
     ) -> Self {
         let mut result = Self::default();
-        for (grammar, depth, node) in layers {
-            let query = if let Some(query) = query(grammar) {
-                query
-            } else {
-                continue;
+        for SyntaxLayerInfo {
+            language,
+            depth,
+            node,
+        } in layers
+        {
+            let grammar = match &language.grammar {
+                Some(grammer) => grammer,
+                None => continue,
+            };
+            let query = match query(&grammar) {
+                Some(query) => query,
+                None => continue,
             };
 
             let mut query_cursor = QueryCursorHandle::new();
@@ -1624,8 +1649,8 @@ mod tests {
         let reference_layers = reference_syntax_map.layers(&buffer);
         for (edited_layer, reference_layer) in layers.into_iter().zip(reference_layers.into_iter())
         {
-            assert_eq!(edited_layer.2.to_sexp(), reference_layer.2.to_sexp());
-            assert_eq!(edited_layer.2.range(), reference_layer.2.range());
+            assert_eq!(edited_layer.node.to_sexp(), reference_layer.node.to_sexp());
+            assert_eq!(edited_layer.node.range(), reference_layer.node.range());
         }
     }
 
@@ -1770,13 +1795,13 @@ mod tests {
                 mutated_layers.into_iter().zip(reference_layers.into_iter())
             {
                 assert_eq!(
-                    edited_layer.2.to_sexp(),
-                    reference_layer.2.to_sexp(),
+                    edited_layer.node.to_sexp(),
+                    reference_layer.node.to_sexp(),
                     "different layer at step {i}"
                 );
                 assert_eq!(
-                    edited_layer.2.range(),
-                    reference_layer.2.range(),
+                    edited_layer.node.range(),
+                    reference_layer.node.range(),
                     "different layer at step {i}"
                 );
             }
@@ -1828,7 +1853,7 @@ mod tests {
             expected_layers.len(),
             "wrong number of layers"
         );
-        for (i, ((_, _, node), expected_s_exp)) in
+        for (i, (SyntaxLayerInfo { node, .. }, expected_s_exp)) in
             layers.iter().zip(expected_layers.iter()).enumerate()
         {
             let actual_s_exp = node.to_sexp();

crates/language/src/tests.rs 🔗

@@ -1449,7 +1449,7 @@ fn get_tree_sexp(buffer: &ModelHandle<Buffer>, cx: &gpui::TestAppContext) -> Str
     buffer.read_with(cx, |buffer, _| {
         let snapshot = buffer.snapshot();
         let layers = snapshot.syntax.layers(buffer.as_text_snapshot());
-        layers[0].2.to_sexp()
+        layers[0].node.to_sexp()
     })
 }
 

crates/zed/src/languages.rs 🔗

@@ -108,7 +108,7 @@ pub async fn init(languages: Arc<LanguageRegistry>, _executor: Arc<Background>)
             Some(CachedLspAdapter::new(html::HtmlLspAdapter).await),
         ),
     ] {
-        languages.add(Arc::new(language(name, grammar, lsp_adapter)));
+        languages.add(language(name, grammar, lsp_adapter));
     }
 }
 
@@ -116,7 +116,7 @@ pub(crate) fn language(
     name: &str,
     grammar: tree_sitter::Language,
     lsp_adapter: Option<Arc<CachedLspAdapter>>,
-) -> Language {
+) -> Arc<Language> {
     let config = toml::from_slice(
         &LanguageDir::get(&format!("{}/config.toml", name))
             .unwrap()
@@ -153,7 +153,7 @@ pub(crate) fn language(
     if let Some(lsp_adapter) = lsp_adapter {
         language = language.with_lsp_adapter(lsp_adapter)
     }
-    language
+    Arc::new(language)
 }
 
 fn load_query(name: &str, filename_prefix: &str) -> Option<Cow<'static, str>> {

crates/zed/src/languages/c.rs 🔗

@@ -112,7 +112,7 @@ impl super::LspAdapter for CLspAdapter {
     async fn label_for_completion(
         &self,
         completion: &lsp::CompletionItem,
-        language: &Language,
+        language: &Arc<Language>,
     ) -> Option<CodeLabel> {
         let label = completion
             .label
@@ -190,7 +190,7 @@ impl super::LspAdapter for CLspAdapter {
         &self,
         name: &str,
         kind: lsp::SymbolKind,
-        language: &Language,
+        language: &Arc<Language>,
     ) -> Option<CodeLabel> {
         let (text, filter_range, display_range) = match kind {
             lsp::SymbolKind::METHOD | lsp::SymbolKind::FUNCTION => {
@@ -251,7 +251,6 @@ mod tests {
     use gpui::MutableAppContext;
     use language::{AutoindentMode, Buffer};
     use settings::Settings;
-    use std::sync::Arc;
 
     #[gpui::test]
     fn test_c_autoindent(cx: &mut MutableAppContext) {
@@ -262,7 +261,7 @@ mod tests {
         let language = crate::languages::language("c", tree_sitter_c::language(), None);
 
         cx.add_model(|cx| {
-            let mut buffer = Buffer::new(0, "", cx).with_language(Arc::new(language), cx);
+            let mut buffer = Buffer::new(0, "", cx).with_language(language, cx);
 
             // empty function
             buffer.edit([(0..0, "int main() {}")], None, cx);

crates/zed/src/languages/elixir.rs 🔗

@@ -113,7 +113,7 @@ impl LspAdapter for ElixirLspAdapter {
     async fn label_for_completion(
         &self,
         completion: &lsp::CompletionItem,
-        language: &Language,
+        language: &Arc<Language>,
     ) -> Option<CodeLabel> {
         match completion.kind.zip(completion.detail.as_ref()) {
             Some((_, detail)) if detail.starts_with("(function)") => {
@@ -168,7 +168,7 @@ impl LspAdapter for ElixirLspAdapter {
         &self,
         name: &str,
         kind: SymbolKind,
-        language: &Language,
+        language: &Arc<Language>,
     ) -> Option<CodeLabel> {
         let (text, filter_range, display_range) = match kind {
             SymbolKind::METHOD | SymbolKind::FUNCTION => {

crates/zed/src/languages/go.rs 🔗

@@ -134,7 +134,7 @@ impl super::LspAdapter for GoLspAdapter {
     async fn label_for_completion(
         &self,
         completion: &lsp::CompletionItem,
-        language: &Language,
+        language: &Arc<Language>,
     ) -> Option<CodeLabel> {
         let label = &completion.label;
 
@@ -235,7 +235,7 @@ impl super::LspAdapter for GoLspAdapter {
         &self,
         name: &str,
         kind: lsp::SymbolKind,
-        language: &Language,
+        language: &Arc<Language>,
     ) -> Option<CodeLabel> {
         let (text, filter_range, display_range) = match kind {
             lsp::SymbolKind::METHOD | lsp::SymbolKind::FUNCTION => {

crates/zed/src/languages/python.rs 🔗

@@ -90,7 +90,7 @@ impl LspAdapter for PythonLspAdapter {
     async fn label_for_completion(
         &self,
         item: &lsp::CompletionItem,
-        language: &language::Language,
+        language: &Arc<language::Language>,
     ) -> Option<language::CodeLabel> {
         let label = &item.label;
         let grammar = language.grammar()?;
@@ -112,7 +112,7 @@ impl LspAdapter for PythonLspAdapter {
         &self,
         name: &str,
         kind: lsp::SymbolKind,
-        language: &language::Language,
+        language: &Arc<language::Language>,
     ) -> Option<language::CodeLabel> {
         let (text, filter_range, display_range) = match kind {
             lsp::SymbolKind::METHOD | lsp::SymbolKind::FUNCTION => {
@@ -149,7 +149,6 @@ mod tests {
     use gpui::{ModelContext, MutableAppContext};
     use language::{AutoindentMode, Buffer};
     use settings::Settings;
-    use std::sync::Arc;
 
     #[gpui::test]
     fn test_python_autoindent(cx: &mut MutableAppContext) {
@@ -160,7 +159,7 @@ mod tests {
         cx.set_global(settings);
 
         cx.add_model(|cx| {
-            let mut buffer = Buffer::new(0, "", cx).with_language(Arc::new(language), cx);
+            let mut buffer = Buffer::new(0, "", cx).with_language(language, cx);
             let append = |buffer: &mut Buffer, text: &str, cx: &mut ModelContext<Buffer>| {
                 let ix = buffer.len();
                 buffer.edit([(ix..ix, text)], Some(AutoindentMode::EachLine), cx);

crates/zed/src/languages/rust.rs 🔗

@@ -119,7 +119,7 @@ impl LspAdapter for RustLspAdapter {
     async fn label_for_completion(
         &self,
         completion: &lsp::CompletionItem,
-        language: &Language,
+        language: &Arc<Language>,
     ) -> Option<CodeLabel> {
         match completion.kind {
             Some(lsp::CompletionItemKind::FIELD) if completion.detail.is_some() => {
@@ -196,7 +196,7 @@ impl LspAdapter for RustLspAdapter {
         &self,
         name: &str,
         kind: lsp::SymbolKind,
-        language: &Language,
+        language: &Arc<Language>,
     ) -> Option<CodeLabel> {
         let (text, filter_range, display_range) = match kind {
             lsp::SymbolKind::METHOD | lsp::SymbolKind::FUNCTION => {
@@ -439,7 +439,7 @@ mod tests {
         cx.set_global(settings);
 
         cx.add_model(|cx| {
-            let mut buffer = Buffer::new(0, "", cx).with_language(Arc::new(language), cx);
+            let mut buffer = Buffer::new(0, "", cx).with_language(language, cx);
 
             // indent between braces
             buffer.set_text("fn a() {}", cx);

crates/zed/src/languages/typescript.rs 🔗

@@ -115,7 +115,7 @@ impl LspAdapter for TypeScriptLspAdapter {
     async fn label_for_completion(
         &self,
         item: &lsp::CompletionItem,
-        language: &language::Language,
+        language: &Arc<language::Language>,
     ) -> Option<language::CodeLabel> {
         use lsp::CompletionItemKind as Kind;
         let len = item.label.len();
@@ -144,7 +144,6 @@ impl LspAdapter for TypeScriptLspAdapter {
 
 #[cfg(test)]
 mod tests {
-    use std::sync::Arc;
 
     use gpui::MutableAppContext;
     use unindent::Unindent;
@@ -172,9 +171,8 @@ mod tests {
         "#
         .unindent();
 
-        let buffer = cx.add_model(|cx| {
-            language::Buffer::new(0, text, cx).with_language(Arc::new(language), cx)
-        });
+        let buffer =
+            cx.add_model(|cx| language::Buffer::new(0, text, cx).with_language(language, cx));
         let outline = buffer.read(cx).snapshot().outline(None).unwrap();
         assert_eq!(
             outline

crates/zed/src/zed.rs 🔗

@@ -1133,7 +1133,7 @@ mod tests {
             assert!(!editor.is_dirty(cx));
             assert_eq!(editor.title(cx), "untitled");
             assert!(Arc::ptr_eq(
-                editor.language_at(0, cx).unwrap(),
+                &editor.language_at(0, cx).unwrap(),
                 &languages::PLAIN_TEXT
             ));
             editor.handle_input("hi", cx);
@@ -1220,7 +1220,7 @@ mod tests {
 
         editor.update(cx, |editor, cx| {
             assert!(Arc::ptr_eq(
-                editor.language_at(0, cx).unwrap(),
+                &editor.language_at(0, cx).unwrap(),
                 &languages::PLAIN_TEXT
             ));
             editor.handle_input("hi", cx);