From 2e2333107a21aa34c983089502c2133eca4bbfdb Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Tue, 11 Jul 2023 14:15:45 -0700 Subject: [PATCH] Find the layer with the smallest enclosing node in language_scope_at --- crates/language/src/buffer.rs | 34 ++--- crates/language/src/buffer_tests.rs | 154 ++++++++++++++++------ crates/language/src/syntax_map.rs | 27 ++-- crates/project/src/project.rs | 2 + crates/project/src/worktree.rs | 1 + crates/zed/src/languages/heex/config.toml | 2 +- 6 files changed, 151 insertions(+), 69 deletions(-) diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index 5041ab759d1fe0aa892feec2b472086f62a01242..0b10432a9f4747d93ff974ac72ddbbb6783fe676 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -2145,23 +2145,27 @@ impl BufferSnapshot { pub fn language_scope_at(&self, position: D) -> Option { let offset = position.to_offset(self); + let mut range = 0..self.len(); + let mut scope = self.language.clone().map(|language| LanguageScope { + language, + override_id: None, + }); - if let Some(layer_info) = self - .syntax - .layers_for_range(offset..offset, &self.text) - .filter(|l| l.node().end_byte() > offset) - .last() - { - Some(LanguageScope { - language: layer_info.language.clone(), - override_id: layer_info.override_id(offset, &self.text), - }) - } else { - self.language.clone().map(|language| LanguageScope { - language, - override_id: None, - }) + // Use the layer that has the smallest node intersecting the given point. + for layer in self.syntax.layers_for_range(offset..offset, &self.text) { + let mut cursor = layer.node().walk(); + while cursor.goto_first_child_for_byte(offset).is_some() {} + let node_range = cursor.node().byte_range(); + if node_range.to_inclusive().contains(&offset) && node_range.len() < range.len() { + range = node_range; + scope = Some(LanguageScope { + language: layer.language.clone(), + override_id: layer.override_id(offset, &self.text), + }); + } } + + scope } pub fn surrounding_word(&self, start: T) -> (Range, Option) { diff --git a/crates/language/src/buffer_tests.rs b/crates/language/src/buffer_tests.rs index 38cefbcef9393ef577f67829febca7bbf03a6358..db9693a5306492f51a818c61bece0f0baa1f9fec 100644 --- a/crates/language/src/buffer_tests.rs +++ b/crates/language/src/buffer_tests.rs @@ -1533,47 +1533,9 @@ fn test_autoindent_with_injected_languages(cx: &mut AppContext) { ]) }); - let html_language = Arc::new( - Language::new( - LanguageConfig { - name: "HTML".into(), - ..Default::default() - }, - Some(tree_sitter_html::language()), - ) - .with_indents_query( - " - (element - (start_tag) @start - (end_tag)? @end) @indent - ", - ) - .unwrap() - .with_injection_query( - r#" - (script_element - (raw_text) @content - (#set! "language" "javascript")) - "#, - ) - .unwrap(), - ); + let html_language = Arc::new(html_lang()); - let javascript_language = Arc::new( - Language::new( - LanguageConfig { - name: "JavaScript".into(), - ..Default::default() - }, - Some(tree_sitter_javascript::language()), - ) - .with_indents_query( - r#" - (object "}" @end) @indent - "#, - ) - .unwrap(), - ); + let javascript_language = Arc::new(javascript_lang()); let language_registry = Arc::new(LanguageRegistry::test()); language_registry.add(html_language.clone()); @@ -1669,7 +1631,7 @@ fn test_autoindent_query_with_outdent_captures(cx: &mut AppContext) { } #[gpui::test] -fn test_language_config_at(cx: &mut AppContext) { +fn test_language_scope_at(cx: &mut AppContext) { init_settings(cx, |_| {}); cx.add_model(|cx| { @@ -1756,6 +1718,54 @@ fn test_language_config_at(cx: &mut AppContext) { }); } +#[gpui::test] +fn test_language_scope_at_with_combined_injections(cx: &mut AppContext) { + init_settings(cx, |_| {}); + + cx.add_model(|cx| { + let text = r#" +
    + <% people.each do |person| %> +
  1. + <%= person.name %> +
  2. + <% end %> +
+ "# + .unindent(); + + let language_registry = Arc::new(LanguageRegistry::test()); + language_registry.add(Arc::new(ruby_lang())); + language_registry.add(Arc::new(html_lang())); + language_registry.add(Arc::new(erb_lang())); + + let mut buffer = Buffer::new(0, text, cx); + buffer.set_language_registry(language_registry.clone()); + buffer.set_language( + language_registry + .language_for_name("ERB") + .now_or_never() + .unwrap() + .ok(), + cx, + ); + + let snapshot = buffer.snapshot(); + let html_config = snapshot.language_scope_at(Point::new(2, 4)).unwrap(); + assert_eq!(html_config.line_comment_prefix(), None); + assert_eq!( + html_config.block_comment_delimiters(), + Some((&"".into())) + ); + + let ruby_config = snapshot.language_scope_at(Point::new(3, 12)).unwrap(); + assert_eq!(ruby_config.line_comment_prefix().unwrap().as_ref(), "# "); + assert_eq!(ruby_config.block_comment_delimiters(), None); + + buffer + }); +} + #[gpui::test] fn test_serialization(cx: &mut gpui::AppContext) { let mut now = Instant::now(); @@ -2143,6 +2153,7 @@ fn ruby_lang() -> Language { LanguageConfig { name: "Ruby".into(), path_suffixes: vec!["rb".to_string()], + line_comment: Some("# ".into()), ..Default::default() }, Some(tree_sitter_ruby::language()), @@ -2158,6 +2169,61 @@ fn ruby_lang() -> Language { .unwrap() } +fn html_lang() -> Language { + Language::new( + LanguageConfig { + name: "HTML".into(), + block_comment: Some(("".into())), + ..Default::default() + }, + Some(tree_sitter_html::language()), + ) + .with_indents_query( + " + (element + (start_tag) @start + (end_tag)? @end) @indent + ", + ) + .unwrap() + .with_injection_query( + r#" + (script_element + (raw_text) @content + (#set! "language" "javascript")) + "#, + ) + .unwrap() +} + +fn erb_lang() -> Language { + Language::new( + LanguageConfig { + name: "ERB".into(), + path_suffixes: vec!["erb".to_string()], + block_comment: Some(("<%#".into(), "%>".into())), + ..Default::default() + }, + Some(tree_sitter_embedded_template::language()), + ) + .with_injection_query( + r#" + ( + (code) @content + (#set! "language" "ruby") + (#set! "combined") + ) + + ( + (content) @content + (#set! "language" "html") + (#set! "combined") + ) + "#, + ) + .unwrap() +} + fn rust_lang() -> Language { Language::new( LanguageConfig { @@ -2236,6 +2302,12 @@ fn javascript_lang() -> Language { "#, ) .unwrap() + .with_indents_query( + r#" + (object "}" @end) @indent + "#, + ) + .unwrap() } fn get_tree_sexp(buffer: &ModelHandle, cx: &gpui::TestAppContext) -> String { diff --git a/crates/language/src/syntax_map.rs b/crates/language/src/syntax_map.rs index b6431c228674f6076b5d4bec2a3fa59bb7795d96..526f99ec95f8180915261608070d3f480165777f 100644 --- a/crates/language/src/syntax_map.rs +++ b/crates/language/src/syntax_map.rs @@ -771,8 +771,10 @@ impl SyntaxSnapshot { range: Range, buffer: &'a BufferSnapshot, ) -> impl 'a + Iterator { - let start = buffer.anchor_before(range.start.to_offset(buffer)); - let end = buffer.anchor_after(range.end.to_offset(buffer)); + let start_offset = range.start.to_offset(buffer); + let end_offset = range.end.to_offset(buffer); + let start = buffer.anchor_before(start_offset); + let end = buffer.anchor_after(end_offset); let mut cursor = self.layers.filter::<_, ()>(move |summary| { if summary.max_depth > summary.min_depth { @@ -787,20 +789,21 @@ impl SyntaxSnapshot { cursor.next(buffer); iter::from_fn(move || { while let Some(layer) = cursor.item() { + let mut info = None; if let SyntaxLayerContent::Parsed { tree, language } = &layer.content { - let info = SyntaxLayerInfo { + let layer_start_offset = layer.range.start.to_offset(buffer); + let layer_start_point = layer.range.start.to_point(buffer).to_ts_point(); + + info = Some(SyntaxLayerInfo { tree, language, depth: layer.depth, - offset: ( - layer.range.start.to_offset(buffer), - layer.range.start.to_point(buffer).to_ts_point(), - ), - }; - cursor.next(buffer); - return Some(info); - } else { - cursor.next(buffer); + offset: (layer_start_offset, layer_start_point), + }); + } + cursor.next(buffer); + if info.is_some() { + return info; } } None diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 3bb5457b1ca50572322f372cf5ff95c664c83eee..fded9ec309e7b38b4c563eaf15b312cb00483baf 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -3045,6 +3045,8 @@ impl Project { ) -> Task<(Option, Vec)> { let key = (worktree_id, adapter_name); if let Some(server_id) = self.language_server_ids.remove(&key) { + log::info!("stopping language server {}", key.1 .0); + // Remove other entries for this language server as well let mut orphaned_worktrees = vec![worktree_id]; let other_keys = self.language_server_ids.keys().cloned().collect::>(); diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index 2c3c9d53047e48b55f556038504bf3546a4ad284..b113af34ad40c9f06d5fa2989aee94219c2091b6 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -397,6 +397,7 @@ impl Worktree { })) } + // abcdefghi pub fn remote( project_remote_id: u64, replica_id: ReplicaId, diff --git a/crates/zed/src/languages/heex/config.toml b/crates/zed/src/languages/heex/config.toml index fafd75dc8db3a94ba732406348218a51bf3b8b70..b5d74f80f319d85feccbe5055576066a65ad0958 100644 --- a/crates/zed/src/languages/heex/config.toml +++ b/crates/zed/src/languages/heex/config.toml @@ -4,4 +4,4 @@ autoclose_before = ">})" brackets = [ { start = "<", end = ">", close = true, newline = true }, ] -block_comment = ["<%#", "%>"] +block_comment = ["<%!--", "--%>"]