Repository: helix-editor/helix (language: rust)

Issue:
Inconsistent syntax highlighting for tagged templates in JavaScript
### Summary

[Tagged template](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#tagged_templates) strings in JavaScript have strange, inconsistent highlighting. It appears that with certain characters in the function name, the entire template string content is highlighted as regular JavaScript.

![screenshot](https://github.com/user-attachments/assets/af33bab7-460a-4ab3-8d02-73bdd0cb72ac)

The characters 'r', 'd', and 'c' seem to change the highlighting. Strangely, the function names starting with 'd' and 'c' also change the colour of the single quoted string 'Baz', but function names starting with 'r' do not (even if they're not .

**Edit:** Just to be clear, the `qwetyuiopasfghjklzxvbnm` tagged templates in the screenshot have the correct highlighting (so named because they don't include any of the characters which cause the highlighting to change).

### Reproduction Steps

The source code above:

```javascript
qwetyuiopasfghjklzxvbnm`
  Foo 123 Bar -456 'Baz';
`;

r`
  Foo 123 Bar -456 'Baz';
`;

d`
  Foo 123 Bar -456 'Baz';
`;

c`
  Foo 123 Bar -456 'Baz';
`;

qwetyuiopasfghjklzxvbnm`
  Foo 123 Bar -456 'Baz';
`;

r`
  Foo 123 Bar -456 'Baz';
`;

rdc`
  Foo 123 Bar -456 'Baz';
`;

```

### Helix log

_No response_

### Platform

Linux

### Terminal Emulator

alacritty 0.14.0 (22a44757)

### Installation Method

source

### Helix Version

helix 24.7 (101a74bf)


Discussion / hints:
The 3 magic characters happen to correspond to 3 popular single-character programming languages:
- [r](https://www.r-project.org/)
- [d](https://dlang.org/)
- [c](https://en.wikipedia.org/wiki/C_(programming_language))

Helix supports tree sitter [language injections](https://tree-sitter.github.io/tree-sitter/syntax-highlighting#language-injection), using a different parser for a part of a file (here - a template literal). There's no reliable way to tell what language a string represents, so it uses a simple heuristic, matching the template tag against a list of known injection regexes.

For example, the `d` comes from: https://github.com/helix-editor/helix/blob/38faf74febf3332fb119302324bfd21229d39e14/languages.toml#L2579

So, long story short, that's just a negative side effect of supporting injections in the first place.

### Disclaimer

I'm not an expert. I'm not even a contributor. I was just hoping to pick up a simple task to contribute, but there's nothing to do here :(
Ah! Thanks for pointing that out. Perhaps these patterns could be a bit more strict, as a single letter which matches in _any_ string including them seems far from ideal.

```toml
[[language]]
name = "c"
injection-regex = "^c$"

[[language]]
name = "c"
injection-regex = "^d$"

[[language]]
name = "c"
injection-regex = "^r$"
```

This is how I'm working around it now anyway. I'm also not really convinced that template strings should be language injection sites. Unlike for example Markdown code blocks where the intention is clear, in JavaScript it's just a function name which could mean anything. Either way though, I think the `injection-regex` patterns could be improved

**Edit:** and this doesn't just apply to single letter patterns. Should the `injection-regex` just imply `^` and `$`? This is probably a separate issue at this point. Should I close this and open another one?
> Should I close this and open another one?

I'd leave it open until a real maintainer shares their thoughts.

I agree it's a bit confusing, though.
> I'm also not really convinced that template strings should be language injection sites. Unlike for example Markdown code blocks where the intention is clear, in JavaScript it's just a function name which could mean anything

Fwiw this is a very popular feature request on Zed editor's issue board atm - people definitely want this, at least for applications such as with `html` or `css` tagged templates. Maybe the list of matched tagged templates could be restricted to a hard coded list including `html`, `css`, and maybe a few others if requested?

This is where this injection is performed:

https://github.com/helix-editor/helix/blob/e1d1a5c5a12127ded88dfcf66dd74aad1650977c/runtime/queries/ecma/injections.scm#L1-L10

And my suggestion above can easily be implemented with something like:

```diff
; Parse the contents of tagged template literals using
; a language inferred from the tag.

(call_expression
  function: [
    (identifier) @injection.language
    (member_expression
      property: (property_identifier) @injection.language)
  ]
  arguments: (template_string) @injection.content
+ (#match? @injection.language "^(html|css)$"))
```

You can see this working here:

![CleanShot 2024-11-28 at 16 47 26](https://github.com/user-attachments/assets/736f1152-0fc2-4aae-9b86-a1c3e227b0c4)

<details><summary>`foo.js` tagged template sample code</summary>

```
/* Should highlight: */
html`<div>bar</div>`;

css`
	:root {
		color: red;
	}
`;

/* Should not highlight: */

htmlll`<div>bar</div>`;

cssss`
	:root {
		color: red;
	}
`;

qwetyuiopasfghjklzxvbnm`
  Foo 123 Bar -456 'Baz';
`;

r`
  Foo 123 Bar -456 'Baz';
`;

d`
  Foo 123 Bar -456 'Baz';
`;

c`
  Foo 123 Bar -456 'Baz';
`;

rdc`
  Foo 123 Bar -456 'Baz';
`;
```

</details>

Happy to open a pull request adding this line if y'all agree :)
> > I'm also not really convinced that template strings should be language injection sites. Unlike for example Markdown code blocks where the intention is clear, in JavaScript it's just a function name which could mean anything
> 
> Fwiw this is a very popular feature request on Zed editor's issue board atm - people definitely want this, at least for applications such as with `html` or `css` tagged templates. Maybe the list of matched tagged templates could be restricted to a hard coded list including `html`, `css`, and maybe a few others if requested?
> 
> This is where this injection is performed:
> 
> https://github.com/helix-editor/helix/blob/e1d1a5c5a12127ded88dfcf66dd74aad1650977c/runtime/queries/ecma/injections.scm#L1-L10
> 
> And my suggestion above can easily be implemented with something like:
> 
> ```diff
> ; Parse the contents of tagged template literals using
> ; a language inferred from the tag.
> 
> (call_expression
>   function: [
>     (identifier) @injection.language
>     (member_expression
>       property: (property_identifier) @injection.language)
>   ]
>   arguments: (template_string) @injection.content
> + (#match? @injection.language "^(html|css)$"))
> ```
> 
> You can see this working here:
> 
> ![CleanShot 2024-11-28 at 16 47 26](https://private-user-images.githubusercontent.com/47499684/390911518-736f1152-0fc2-4aae-9b86-a1c3e227b0c4.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MzM2NzAzMTIsIm5iZiI6MTczMzY3MDAxMiwicGF0aCI6Ii80NzQ5OTY4NC8zOTA5MTE1MTgtNzM2ZjExNTItMGZjMi00YWFlLTliODYtYTFjM2UyMjdiMGM0LnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNDEyMDglMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjQxMjA4VDE1MDAxMlomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPWY4ZDg2MDZiODU3NWZjNmU0OWI3ZmVjM2Y0MTNkODZmODViNDBkMzVlYjA4YWMxMGNmMGQyNmI0NDk1OTZlZTImWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0In0.paSoFfxhC8zguJeSculBiL-AUsZzLQaJEY5eUq6y514)
> `foo.js` tagged template sample code
> 
> Happy to open a pull request adding this line if y'all agree :)

I think that's a great idea

Fix patch:
diff --git a/runtime/queries/ecma/injections.scm b/runtime/queries/ecma/injections.scm
index af3aef10510e..09cca4418bd4 100644
--- a/runtime/queries/ecma/injections.scm
+++ b/runtime/queries/ecma/injections.scm
@@ -7,7 +7,8 @@
     (member_expression
       property: (property_identifier) @injection.language)
   ]
-  arguments: (template_string) @injection.content)
+  arguments: (template_string) @injection.content
+  (#any-of? @injection.language "html" "css" "json" "sql" "js" "ts" "bash"))
 
 ; Parse the contents of gql template literals
 


Test patch: