Packages icon indicating copy to clipboard operation
Packages copied to clipboard

[JavaScript] HTML and CSS syntax highlighting in tagged template strings

Open jrburke opened this issue 9 years ago • 32 comments

Is it possible to support HTML/CSS syntax highlighting inside a tagged template string? I am looking to do the equivalent of this atom editor change.

My first attempt at this did not work out so well. I just tried modifying the existing literal-quasi (but locally renamed it to literal-template-string everywhere in the file), just to see if I could get the HTML referencing correctly, before creating a new kind of match just for the "ending in html" tagged template function case:

  literal-template-string:
    - match: '([a-zA-Z$_][\w$_]*)?(`)'
      captures:
        1: entity.template-string.tag.name.js
        2: punctuation.definition.template-string.begin.js
      push:
        - meta_content_scope: source.html.embedded.js
        - include: 'scope:text.html.basic'
      with_prototype:
        - match: "`"
          captures:
            0: punctuation.definition.template-string.end.js
          pop: true
        - match: '\${'
          captures:
            0: punctuation.template-string.element.begin.js
          push:
            - meta_scope: entity.template-string.element.js
            - match: "}"
              captures:
                0: punctuation.template-string.element.end.js
              pop: true
            - include: expression

But I get an Apparent recursion within a with_prototype action: 25000 context sanity limit hit error. I am very new to these kinds of syntax files, so I expect I am making some very simple mistakes. I appreciate receiving any pointers on how to fix the issue.

My guess is that since JavaScript.sublime-syntax's expression has include: literal-template-string and literal-template-string is referencing source:text.html.basic, which inside of it references scope:source.js, maybe some pathway with all of that is causing the recursion.

Related context:

  • This does not take into account the changes in #165, I expect to redo some of it once that changeset lands.
  • Previous discussion in Benvie/JavaScriptNext.tmLanguage#134, but I would want to scope the use of HTML to tagged template functions ending in (?i)html, to allow alternate highlighting for other content, like CSS for functions ending in (?i)css, as the Atom changeset does.
  • GitHub's source display now does this kind of higlighting: example test.js.
  • That test file is part of a tts-syntax-highlight repo to track this sort of enhancement in editors now that ES2015 template strings are used more.

jrburke avatar Feb 16 '16 00:02 jrburke

There's also a suggested YML solution in this thread https://forum.sublimetext.com/t/javascript-es6-template-literals-syntax-for-html/18242

orizens avatar Jun 09 '16 14:06 orizens

I made it work by extending the default JavaScript Syntax.

image

Edited to include suggestions from comments below.

%YAML 1.2
---
# See http://www.sublimetext.com/docs/3/syntax.html
name: JavaScript Custom Element
file_extensions:
  - element.js
  - tag.js
  - jsx
  - js
scope: source.js.tag
contexts:
  main:
    - match: ""
      push: Packages/JavaScript/JavaScript.sublime-syntax
      with_prototype:
      - match: '([a-zA-Z$_][\w$_]*)?(`)'
        push:
          - meta_content_scope: text.html.basic.embedded.js
          - include: 'scope:text.html.basic'
          - match: '`'
            pop: true
          - match: '\$\{'
            captures:
              0: punctuation.definition.template-expression.begin.js
            push:
              - meta_scope: meta.template.expression.js
              - include: 'scope:source.js'
              #- meta_content_scope: source.js.embedded.expression
              - match: '\}'
                scope: punctuation.definition.template-expression.end.js
                pop: true

Rayraegah avatar Oct 18 '16 11:10 Rayraegah

@Rayraegah Your solution doesn't work with tagged template literals.

Replace - match: '`\n' with - match: '([a-zA-Z$_][\w$_]*)?(`)' and they will work.

3mcd avatar Nov 26 '16 06:11 3mcd

@Rayraegah where did you save that file and with what name?

qodesmith avatar Apr 23 '17 11:04 qodesmith

@qodesmith Just create a new syntax file and dump that code in there. Also, use @ericmcdaniel 's suggestion.

Then, in your editor settings, set open all files with the new syntax instead of the default.

Rayraegah avatar Apr 23 '17 18:04 Rayraegah

The JS Custom package lets you add user-defined template tags. In my opinion, this is a better approach than adding them to the core syntax.

Thom1729 avatar Jan 06 '18 02:01 Thom1729

@Thom1729 would you mind explaining in a bit more detail how the JS Custom package should be set up to show html syntax within `` backticks in JS files?

screen shot 2018-05-09 at 13 16 56

Slakinov avatar May 09 '18 12:05 Slakinov

JS Custom version 1.0.13 is out, updating the package for Sublime 3.1.

More importantly, I've just fixed a really egregious bug in the documentation. Try this:

{
    "configurations": {
        "Vue Backticks": {
            "custom_template_tags": {
                "html": "scope:text.html.basic"
            },
        },
    },
}

Thom1729 avatar May 09 '18 18:05 Thom1729

@Thom1729 Thanks for the info! Just updated the package and tried that config. I'm now getting the recursion warning someone mentioned above. Any idea how to fix? Do I need to make an alternative html syntax definition that doesn't attempt to parse js again within itself? Sorry if these are dumb questions. screen shot 2018-05-09 at 19 24 01

Slakinov avatar May 09 '18 18:05 Slakinov

Are you running Sublime 3.1? The new version of the core HTML syntax uses embed/escape instead of push/with_prototype, and that should avoid the infinite recursion.

Thom1729 avatar May 09 '18 19:05 Thom1729

I'm running build 3170, which is 3.1 according to the sublime site, but 3.0 in my about window.

Had to remove some user package files after removing the JS Custom package to stop the recursion error when viewing any .html file.

I've now reinstalled JS Custom, and added your config above, no recursion error now, but no backtick highlighting either...

Any more ideas?

screen shot 2018-05-10 at 10 54 31

Slakinov avatar May 10 '18 09:05 Slakinov

my guess is you need to write html before the opening backtick.

keith-hall avatar May 10 '18 10:05 keith-hall

Yes, you'd need to write:

template: html`
    <div class="playlist">...</div>
`

The JS Custom extension isn't designed to apply arbitrary highlighting to regular, untagged templates. You probably wouldn't want all template strings to be highlighted as HTML, because template strings are used for many purposes.

However, there may be an opportunity for extension here. The Ecmascript Sublime lets you specify a syntax in a comment, like so:

template: /* syntax: html */ `
    <div class="playlist">...</div>
`

A similar feature might be useful for JS Custom. Of course, like custom_template_tags, the feature would be fully configurable. Would this be useful to you?

Thom1729 avatar May 10 '18 12:05 Thom1729

Hmm. This isn't straightforward is it :( Putting html before a backtick makes the JS invalid, Vue can't execute it.

Slakinov avatar May 15 '18 10:05 Slakinov

I personally would find a syntax highlighter useful that assumes all backtick blocks in JS contain html, since I never use them for anything else in JS files.

Slakinov avatar May 15 '18 10:05 Slakinov

Putting html before a backtick makes the JS invalid

That’s not correct. Those are called tagged template literals.

I personally would find a syntax highlighter that assumes all backtick blocks in JS to contain html, since I never use them for anything else in JS files.

You might not use them for anything else, but a lot of people use them for everything but HTML. The default should definitely be no extra highlighting.

kleinfreund avatar May 15 '18 11:05 kleinfreund

I'm not saying this should be default JS highlighting at all, I'm just trying to get a solution to work for my own case, and would imagine other people would want this behaviour as an option, too.

Haven't tried running my code through Babel in this case, but Chrome throws an error with tagged template literals: screen shot 2018-05-15 at 14 12 33

Slakinov avatar May 15 '18 13:05 Slakinov

The MDN reference explains in more detail. The "tag" of a template literal is a function that processes the literal's contents. So in this context, html should be a function that takes in the literal's text and produces some value.

As a workaround for now, you could implement it with const html = String.raw.

Thom1729 avatar May 15 '18 13:05 Thom1729

Aha! That works, thanks :)

Slakinov avatar May 15 '18 13:05 Slakinov

...And now the recursion errors are back when reopening Sublime 😭
Regular HTML syntax highlighting appears to be hosed.

Sublime build 3176 Latest JSCustom, with the settings suggested above Have tried 'rebuild syntaxes'

screen shot 2018-05-15 at 15 02 33

Slakinov avatar May 15 '18 14:05 Slakinov

https://github.com/bathos/Ecmascript-Sublime works great, I can live with those syntax declaration comments.

Thanks again @Thom1729

Slakinov avatar May 15 '18 16:05 Slakinov

I've figured it out.

The underlying problem is that the HTML syntax is embedding the source.js scope, while the JS Custom syntax is embedding the HTML syntax using with_prototype. This is causing the recursion error. I had thought that if at least one of those two syntaxes used embed, then it would work. Apparently, I was wrong.

Ecmascript-Sublime works around this by accident. Because of the way that syntax is written, the HTML syntax won't embed it; it will embed another JavaScript syntax instead, such as the core syntax. This means that you can't use any of Ecmascript-Sublime's features in embedded JavaScript, and so the error never comes up.

The HTML syntax will embed JS Custom syntaxes, though. Specifically, it will embed the syntax for the last configuration in lexicographic order. If that syntax embeds HTML using with_prototype, the error you've found will occur. I couldn't reproduce the error because the configuration I was testing all of this on was not the last one in lexicographic order.

I plan to solve this in a systematic manner. However, there is a simple workaround that will solve the problem for you today. Add the following configuration to your JS Custom.sublime-settings:

        "~default": {
            "hidden": true,
            "custom_template_tags": null,
        },

This will create a new, hidden syntax that will be used by the HTML syntax and other syntaxes for embedded JavaScript. It will use all of your default settings, except that it won't use any custom template tags. This should eliminate those recursion errors once and for all.

Sorry that this ended up being a hassle!

Thom1729 avatar May 15 '18 17:05 Thom1729

Super-delayed thanks for that last post :) Have been using JS Custom with these settings for months, working perfectly for my needs. Just posting here for reference: screen shot 2018-08-23 at 13 43 09

Slakinov avatar Aug 23 '18 12:08 Slakinov

@Rayraegah your suggestion doesn’t seem to be working properly when you have JS inside template literal. Is this up to date?

niksy avatar Jul 14 '19 10:07 niksy

@niksy I didn't have a case where I had to handle JS inside a template literal (other than template expressions). Can you post a snippet of your code where syntax highlight fails? (screenshot would help as well, along with the name of the sublime theme)

I did try some edge cases: can you check with the following commented line uncommented?

            push:
              - meta_scope: meta.template.expression.js
              - include: 'scope:source.js'
              #- meta_content_scope: source.js.embedded.expression
              - match: '\}'
                scope: punctuation.definition.template-expression.end.js
                pop: true

Rayraegah avatar Jul 18 '19 02:07 Rayraegah

It's alive!! I went the JS Custom package route (preferred over custom syntax files) but it took me way longer than I'd like to admit to get it fully working. I would have been completely lost if not for this thread. However, I think it can do with a few updates to hopefully save the next person some time.

My setup:

  • Sublime Text v. 3.2.2.
  • JS Custom v. 2.4.1

Firstly, don't include the following setting referred to as a work-around above:

"~default": { "hidden": true, "custom_template_tags": null, },

I suppose this workaround was fixed and made reduant in a later JS Custom version. As far as I can tell, it prevented me from seeing the JS Custom option in the View > Syntax menu. Only when I removed it and rebuilt the JS Custom syntax file did the menu option finally appear. Also, JS Custom is now listed directly in that menu, rather than under a User submenu as suggested somewhere further up in the thread.

The HTML in Javascript highlighting did work in Sublime when I used the html`<div>... directive BUT I'm using this for VueJS and that convention breaks its Component parsing. Fortunately JS Custom now has a comments configuration setting that allows for the highlighting of untagged template literals based on a preceding block comment. So this is my configuration:

Screen Shot 2021-03-07 at 5 21 26 PM

Which now enables the flagging of my template literals using the /* html */ directive:

Screen Shot 2021-03-07 at 5 25 32 PM

It plays nice with VueJS setup and my SublimeLinter StandardJS settings, which continue to work with JS Custom in place. Note however, that I did have to move my Javascript syntax preferences from Javascript.sublime-settings to Vue Backticks.sublime-settings (namely to enforce 2-space tabs in JS files).

peterVG avatar Mar 08 '21 01:03 peterVG

I can confirm that the ~default workaround is obsolete. The current version of JS Custom avoids the infinite recursion issues, so there's no need to manually create a ~default configuration.

I'd appreciate any suggestion you might have to improve the documentation.

Thom1729 avatar Mar 08 '21 04:03 Thom1729

Hey @Thom1729! Thanks for this great package. It's a pleasure to use now that I have it up and running. So glad I don't have to give up syntax highlighting in template literals as it's so fundamental to VueJS which I'm trying to implement ATM.

As for docs, you might have picked up from my comment above that, because I've only defined one configuration in the User settings, it appears directly under the View > Syntax menu (which is nice):

Screen Shot 2021-03-07 at 8 21 16 PM

However, if I add another configuration then, yes, it does appear under View > Syntax > User > JS Custom > [config-name] as you have documented. Not sure you if you want to be more specific about that.

Also, while your "style": "scope:source.css" example value for the comments configuration option is useful, perhaps most users will want to use this option with html comments like I did? At the risk of making the documentation too verbose you may want to provide the html example as well as it's not totally straightforward what scope value to use if you're not familiar with the inner workings of the syntax highlighting functions.

{
    "configurations": {
        "Vue Backticks": {
            "custom_templates": {
                "comments": {
                    "html" : "scope:text.html.basic"
                },
            },
        },
    },
}

peterVG avatar Mar 08 '21 04:03 peterVG

Huh. So apparently if you have exactly one configuration, it will appear directly in the menu, but if you have several they will appear in a subfolder. I did not know this (though in hindsight it seems obvious). This is definitely confusing; I'll open an issue.

Thom1729 avatar Mar 08 '21 04:03 Thom1729

I am using the following configuration:

  • https://github.com/kireerik/refo/tree/master/example#sublime-text

 

This way I don't need to use comments.

kireerik avatar Mar 08 '21 22:03 kireerik