Skip to content

Conversation

@vitallium
Copy link
Contributor

@vitallium vitallium commented Oct 24, 2025

Closes #40632

TL;DR: The wrap selections in tag action was unavailable in ERB files, even when the cursor was positioned in HTML content (outside of Ruby code blocks). This happened because syntax_layer_at() incorrectly returned the Ruby language for positions that were actually in HTML. NOTE: I am not familiar with that part of Zed so it could be that the fix here is completely incorrect.

Previously, syntax_layer_at incorrectly reported injected languages (e.g., Ruby in ERB files) even when the cursor was in the base language content (HTML). This broke actions like wrap selections in tag that depend on language-specific configuration.

The issue had two parts:

  1. Missing start boundary check: The filter only checked if a layer's end was after the cursor (end_byte() > offset), not if it started before, causing layers outside the cursor position to be included. See the BEFORE video: when I click on the HTML part it reports Ruby language instead of HTML.
  2. Wrong boundary reference for injections: For injected layers with included_sub_ranges (like Ruby code blocks in ERB), checking the root node boundaries returned the entire file range instead of the actual injection ranges.

This fix:

  • Adds the containment check using half-open range semantics [start, end) for root node boundaries. That ensures proper reporting of the detected language when a cursor (|) is located right after the injection:

    <body>
     <%= yield %>|
    </body>
    
  • Checks included_sub_ranges for injected layers to determine if the cursor is actually within an injection

  • Falls back to root node boundaries for base layers without sub-ranges. This is the original behavior.

Fixes ERB language support where actions should be available based on the cursor's actual language context. I think that also applies to some other template languages like HEEX (Phoenix) and *.pug. On short videos below you can see how I navigate through the ERB template and the terminal on the right outputs the detected language if you apply the following patch:

diff --git i/crates/editor/src/editor.rs w/crates/editor/src/editor.rs
index 15af61f5d2..54a8e0ae37 100644
--- i/crates/editor/src/editor.rs
+++ w/crates/editor/src/editor.rs
@@ -10671,6 +10671,7 @@ impl Editor {
         for selection in self.selections.disjoint_anchors_arc().iter() {
             if snapshot
                 .language_at(selection.start)
+                .inspect(|language| println!("Detected language: {:?}", language))
                 .and_then(|lang| lang.config().wrap_characters.as_ref())
                 .is_some()
             {

Before:

before.mp4

After:

after.mp4

Here is the ERB template:

<!DOCTYPE html>
<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <style>
      /* Email styles need to be inline */
    </style>
  </head>
  <body>
    <%= yield %>
  </body>
</html>

Release Notes:

  • N/A

Previously, `syntax_layer_at` incorrectly reported injected languages
(e.g., Ruby in ERB files) even when the cursor was in the base language
content (HTML). This broke actions like "wrap selections in tag" that
depend on language-specific configuration.

The issue had two parts:
1. Missing start boundary check: The filter only checked if a layer's
   end was after the cursor (`end_byte() > offset`), not if it started
   before, causing layers outside the cursor position to be included.
2. Wrong boundary reference for injections: For injected layers with
   `included_sub_ranges` (like Ruby code blocks in ERB), checking the
   root node boundaries returned the entire file range instead of the
   actual injection ranges.

This fix:
- Adds proper containment check using half-open range semantics
  [start, end) for root node boundaries
- Checks `included_sub_ranges` for injected layers to determine if
  the cursor is actually within an injection
- Falls back to root node boundaries for base layers without sub-ranges

Fixes template language support (ERB, Vue, JSX, etc.) where actions
should be available based on the cursor's actual language context.
@cla-bot cla-bot bot added the cla-signed The user has signed the Contributor License Agreement label Oct 24, 2025
@@ -2633,7 +2627,7 @@ fn test_language_scope_at_with_combined_injections(cx: &mut App) {
buffer.set_language_registry(language_registry.clone());
buffer.set_language(
language_registry
.language_for_name("ERB")
.language_for_name("HTML+ERB")
Copy link
Contributor Author

@vitallium vitallium Oct 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@@ -3655,7 +3680,7 @@ fn html_lang() -> Language {
fn erb_lang() -> Language {
Language::new(
LanguageConfig {
name: "ERB".into(),
name: "HTML+ERB".into(),
Copy link
Contributor Author

@vitallium vitallium Oct 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment on lines +3701 to +3703
(code) @content
(#set! "language" "ruby")
(#set! "combined")
Copy link
Contributor Author

@vitallium vitallium Oct 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment on lines +3707 to +3709
(content) @content
(#set! "language" "html")
(#set! "combined")
Copy link
Contributor Author

@vitallium vitallium Oct 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment on lines +754 to +767
let included_sub_ranges: Option<Vec<Range<Anchor>>> = if is_combined {
Some(
included_ranges
.into_iter()
.filter(|r| r.start_byte < r.end_byte)
.map(|r| {
text.anchor_before(r.start_byte + step_start_byte)
..text.anchor_after(r.end_byte + step_start_byte)
})
.collect(),
);
)
} else {
None
};
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ensure we always provide accurate metadata for combined injections.

@JosephTLyons
Copy link
Collaborator

Always happy to see your PRs, @vitallium! We'll take a look!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cla-signed The user has signed the Contributor License Agreement community champion

Projects

None yet

Development

Successfully merging this pull request may close these issues.

The editor::WrapSelectionsInTag action not working in ERB files anymore

3 participants