Skip to content

Fix Tailwind CSS completions breaking after hyphens#14

Open
PiasekDev wants to merge 1 commit intozed-extensions:mainfrom
PiasekDev:tailwind-completions-fix
Open

Fix Tailwind CSS completions breaking after hyphens#14
PiasekDev wants to merge 1 commit intozed-extensions:mainfrom
PiasekDev:tailwind-completions-fix

Conversation

@PiasekDev
Copy link
Copy Markdown

Fixes Tailwind completions breaking after hyphens in Leptos class="..." strings inside RSTML.

Leptos view! macro bodies are parsed as injected RSTML inside Rust files. This affects plain class attributes such as:

view! {
    <button class="px-4 py-2 text-white bg-blue-600">
        "Click"
    </button>
}

When tailwindcss-language-server is available for the Rust buffer, completions can appear in that string, but the completion query can break once a Tailwind hyphen is typed. For example, completions may appear while typing text, but after typing text-, the completion query resets. This is the same completion-query behavior described in zed-industries/zed#25670, though that issue was reported for Dioxus and this PR only addresses the Leptos/RSTML case. It also relates to the broader Leptos/Rust Tailwind support request in zed-industries/zed#16371.

The issue is not that RSTML lacks - as a completion query character: it already has this in languages/rstml/config.toml:

completion_query_characters = ["-"]

The issue is where the cursor ends up. RSTML parses the value of attributes like class="..." as a string-like Rust expression, and that expression is currently reinjected as Rust in languages/rstml/injections.scm. When the cursor is inside the class string, the active scope is therefore the nested Rust layer rather than the RSTML layer. Tailwind can still provide completions when the server is available, but the active Rust completion scope does not use RSTML's hyphen query behavior.

This change keeps string-like RSTML expressions in the RSTML layer by excluding them from the Rust injection:

((rust_expression) @injection.content
  (#not-match? @injection.content "^(r#*)?\"")
  (#set! injection.language "rust"))

Non-string Rust expressions inside RSTML are still injected as Rust.

This is intentionally limited to the simple Leptos class attribute form:

class="px-4 text-white bg-blue-600"

It does not try to fully support more complex Leptos class syntaxes, such as:

class=(["text-white", "bg-blue-600"], move || active.get())
class:red=move || active.get()

Those cases need more context than this small RSTML injection change provides.

Full support for Leptos-specific syntaxes like tuple classes or class:* directives likely needs better Zed support for routing language-server requests across nested Rust/RSTML/Rust language layers. Those cases are harder to express cleanly with only more query special cases in languages/rstml/injections.scm.

@cla-bot
Copy link
Copy Markdown

cla-bot Bot commented May 3, 2026

We require contributors to sign our Contributor License Agreement, and we don't have @PiasekDev on file. You can sign our CLA at https://zed.dev/cla. Once you've signed, post a comment here that says '@cla-bot check'.

@PiasekDev
Copy link
Copy Markdown
Author

@cla-bot check

@cla-bot cla-bot Bot added the cla-signed label May 3, 2026
@cla-bot
Copy link
Copy Markdown

cla-bot Bot commented May 3, 2026

The cla-bot has been summoned, and re-checked this pull request!

@PiasekDev
Copy link
Copy Markdown
Author

Some follow-up context:

I chose to submit the smallest code change that fixes the reported hyphen completion reset. RSTML already has completion_query_characters = ["-"]; the missing piece for this issue is keeping string-like RSTML expressions in the RSTML layer where that setting applies. A possibly more precise variant could also be to reinject Rust for string literal values of non-class attributes while still keeping string literal values of class attributes in the RSTML layer. I avoided that here because it adds an extra complex query for little practical benefit: Rust language-server behavior inside plain string literal contents does not seem useful, the extra query seems more brittle, and future Zed-side changes for nested language-server routing may require this area to change again.

I also tried adding Tailwind language-server opt-ins to RSTML string scopes, similar to the earlier Rust-side approach in zed-industries/zed#28674. In local testing, that did not activate tailwindcss-language-server for the Rust buffer by itself. Tailwind still had to be made available for Rust through settings, so this PR intentionally leaves server activation alone and only changes the injected-language scope that controls the completion query.

@PiasekDev PiasekDev force-pushed the tailwind-completions-fix branch from 33998c8 to 3f32420 Compare May 3, 2026 21:33
@PiasekDev
Copy link
Copy Markdown
Author

Ok, I actually didn't know that the . character is relevant in those completions too (e.g. m-0.5), so I updated the PR and added the dot into completion_query_characters to cover this too. This also could be done as a more targeted change, probably by introducing a custom override scope, but I left it at the RSTML language config level for now. Adding a custom scope would mean adding an overrides.scm query and also making sure the common string / comment scopes used by the existing bracket config are still defined there, so it becomes a larger query/config change for a fairly small amount of extra precision.

If the maintainers would prefer the narrower version, I can refactor it that way. I had tried override-based variants while working through this, but dropped them once they were not needed for the main server-routing/scope issue.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant