@@ -1716,28 +1716,14 @@ impl Buffer {
/// Returns the [`Language`] at the given location.
pub fn language_at<D: ToOffset>(&self, position: D) -> Option<Arc<Language>> {
let offset = position.to_offset(self);
- let mut is_first = true;
- let start_anchor = self.anchor_before(offset);
- let end_anchor = self.anchor_after(offset);
+ let text: &TextBufferSnapshot = &self.text;
self.syntax_map
.lock()
- .layers_for_range(offset..offset, &self.text, false)
+ .layers_for_range(offset..offset, text, false)
.filter(|layer| {
- if is_first {
- is_first = false;
- return true;
- }
-
layer
.included_sub_ranges
- .map(|sub_ranges| {
- sub_ranges.iter().any(|sub_range| {
- let is_before_start = sub_range.end.cmp(&start_anchor, self).is_lt();
- let is_after_end = sub_range.start.cmp(&end_anchor, self).is_gt();
- !is_before_start && !is_after_end
- })
- })
- .unwrap_or(true)
+ .is_none_or(|ranges| offset_in_sub_ranges(ranges, offset, text))
})
.last()
.map(|info| info.language.clone())
@@ -1747,10 +1733,17 @@ impl Buffer {
/// Returns each [`Language`] for the active syntax layers at the given location.
pub fn languages_at<D: ToOffset>(&self, position: D) -> Vec<Arc<Language>> {
let offset = position.to_offset(self);
+ let text: &TextBufferSnapshot = &self.text;
let mut languages: Vec<Arc<Language>> = self
.syntax_map
.lock()
- .layers_for_range(offset..offset, &self.text, false)
+ .layers_for_range(offset..offset, text, false)
+ .filter(|layer| {
+ // For combined injections, check if offset is within the actual sub-ranges.
+ layer
+ .included_sub_ranges
+ .is_none_or(|ranges| offset_in_sub_ranges(ranges, offset, text))
+ })
.map(|info| info.language.clone())
.collect();
@@ -3340,6 +3333,21 @@ impl Buffer {
impl EventEmitter<BufferEvent> for Buffer {}
+fn offset_in_sub_ranges(
+ sub_ranges: &[Range<Anchor>],
+ offset: usize,
+ snapshot: &TextBufferSnapshot,
+) -> bool {
+ let start_anchor = snapshot.anchor_before(offset);
+ let end_anchor = snapshot.anchor_after(offset);
+
+ sub_ranges.iter().any(|sub_range| {
+ let is_before_start = sub_range.end.cmp(&start_anchor, snapshot).is_lt();
+ let is_after_end = sub_range.start.cmp(&end_anchor, snapshot).is_gt();
+ !is_before_start && !is_after_end
+ })
+}
+
impl Deref for Buffer {
type Target = TextBuffer;
@@ -3854,12 +3862,19 @@ impl BufferSnapshot {
let offset = position.to_offset(self);
let mut scope = None;
let mut smallest_range_and_depth: Option<(Range<usize>, usize)> = None;
+ let text: &TextBufferSnapshot = self;
// Use the layer that has the smallest node intersecting the given point.
for layer in self
.syntax
.layers_for_range(offset..offset, &self.text, false)
{
+ if let Some(ranges) = layer.included_sub_ranges
+ && !offset_in_sub_ranges(ranges, offset, text)
+ {
+ continue;
+ }
+
let mut cursor = layer.node().walk();
let mut range = None;
@@ -2771,14 +2771,11 @@ fn test_language_scope_at_with_combined_injections(cx: &mut App) {
let mut buffer = Buffer::local(text, cx);
buffer.set_language_registry(language_registry.clone());
- buffer.set_language(
- language_registry
- .language_for_name("HTML+ERB")
- .now_or_never()
- .unwrap()
- .ok(),
- cx,
- );
+ let language = language_registry
+ .language_for_name("HTML+ERB")
+ .now_or_never()
+ .and_then(Result::ok);
+ buffer.set_language(language, cx);
let snapshot = buffer.snapshot();
let html_config = snapshot.language_scope_at(Point::new(2, 4)).unwrap();
@@ -2894,15 +2891,80 @@ fn test_language_at_for_markdown_code_block(cx: &mut App) {
}
#[gpui::test]
-fn test_syntax_layer_at_for_injected_languages(cx: &mut App) {
+fn test_syntax_layer_at_for_combined_injections(cx: &mut App) {
init_settings(cx, |_| {});
cx.new(|cx| {
+ // ERB template with HTML and Ruby content
let text = r#"
- ```html+erb
- <div>Hello</div>
- <%= link_to "Some", "https://zed.dev" %>
- ```
+<div>Hello</div>
+<%= link_to "Click", url %>
+<p>World</p>
+ "#
+ .unindent();
+
+ let language_registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone()));
+ language_registry.add(Arc::new(erb_lang()));
+ language_registry.add(Arc::new(html_lang()));
+ language_registry.add(Arc::new(ruby_lang()));
+
+ let mut buffer = Buffer::local(text, cx);
+ buffer.set_language_registry(language_registry.clone());
+ let language = language_registry
+ .language_for_name("HTML+ERB")
+ .now_or_never()
+ .and_then(Result::ok);
+ buffer.set_language(language, cx);
+
+ let snapshot = buffer.snapshot();
+
+ // Test language_at for HTML content (line 0: "<div>Hello</div>")
+ let html_point = Point::new(0, 4);
+ let language = snapshot.language_at(html_point).unwrap();
+ assert_eq!(
+ language.name().as_ref(),
+ "HTML",
+ "Expected HTML at {:?}, got {}",
+ html_point,
+ language.name()
+ );
+
+ // Test language_at for Ruby code (line 1: "<%= link_to ... %>")
+ let ruby_point = Point::new(1, 6);
+ let language = snapshot.language_at(ruby_point).unwrap();
+ assert_eq!(
+ language.name().as_ref(),
+ "Ruby",
+ "Expected Ruby at {:?}, got {}",
+ ruby_point,
+ language.name()
+ );
+
+ // Test language_at for HTML after Ruby (line 2: "<p>World</p>")
+ let html_after_ruby = Point::new(2, 2);
+ let language = snapshot.language_at(html_after_ruby).unwrap();
+ assert_eq!(
+ language.name().as_ref(),
+ "HTML",
+ "Expected HTML at {:?}, got {}",
+ html_after_ruby,
+ language.name()
+ );
+
+ buffer
+ });
+}
+
+#[gpui::test]
+fn test_languages_at_for_combined_injections(cx: &mut App) {
+ init_settings(cx, |_| {});
+
+ cx.new(|cx| {
+ // ERB template with HTML and Ruby content
+ let text = r#"
+<div>Hello</div>
+<%= yield %>
+<p>World</p>
"#
.unindent();
@@ -2922,16 +2984,47 @@ fn test_syntax_layer_at_for_injected_languages(cx: &mut App) {
cx,
);
- let snapshot = buffer.snapshot();
-
- // Test points in the code line
- let html_point = Point::new(1, 4);
- let language = snapshot.language_at(html_point).unwrap();
- assert_eq!(language.name().as_ref(), "HTML");
+ // Test languages_at for HTML content - should NOT include Ruby
+ let html_point = Point::new(0, 4);
+ let languages = buffer.languages_at(html_point);
+ let language_names: Vec<_> = languages.iter().map(|language| language.name()).collect();
+ assert!(
+ language_names
+ .iter()
+ .any(|language_name| language_name.as_ref() == "HTML"),
+ "Expected HTML in languages at {:?}, got {:?}",
+ html_point,
+ language_names
+ );
+ assert!(
+ !language_names
+ .iter()
+ .any(|language_name| language_name.as_ref() == "Ruby"),
+ "Did not expect Ruby in languages at {:?}, got {:?}",
+ html_point,
+ language_names
+ );
- let ruby_point = Point::new(2, 6);
- let language = snapshot.language_at(ruby_point).unwrap();
- assert_eq!(language.name().as_ref(), "Ruby");
+ // Test languages_at for Ruby code - should NOT include HTML
+ let ruby_point = Point::new(1, 6);
+ let languages = buffer.languages_at(ruby_point);
+ let language_names: Vec<_> = languages.iter().map(|language| language.name()).collect();
+ assert!(
+ language_names
+ .iter()
+ .any(|language_name| language_name.as_ref() == "Ruby"),
+ "Expected Ruby in languages at {:?}, got {:?}",
+ ruby_point,
+ language_names
+ );
+ assert!(
+ !language_names
+ .iter()
+ .any(|language_name| language_name.as_ref() == "HTML"),
+ "Did not expect HTML in languages at {:?}, got {:?}",
+ ruby_point,
+ language_names
+ );
buffer
});