gandm / language-babel

ES2017, flow, React JSX and GraphQL grammar and transpilation for ATOM
https://atom.io/packages/language-babel
MIT License
476 stars 83 forks source link

HTML syntax highlighting in string #200

Closed bcarroll22 closed 8 years ago

bcarroll22 commented 8 years ago

Would it be possible to enable syntax highlighting for HTML inside of a string literal? I've seen this done in other packages, such as this one. It would be extremely helpful for writing single-file Angular 1.5 components.

Thanks!

gandm commented 8 years ago

Can you give me an example of the type of syntax you want highlighted.


From: Brandon Carrollmailto:notifications@github.com Sent: ‎04/‎06/‎2016 21:25 To: gandm/language-babelmailto:language-babel@noreply.github.com Subject: [gandm/language-babel] HTML syntax highlighting in string (#200)

Would it be possible to enable syntax highlighting for HTML inside of a string literal? I've seen this done in other packages, such as this one. It would be extremely helpful for writing single-file Angular 1.5 components.

Thanks!


You are receiving this because you are subscribed to this thread. Reply to this email directly or view it on GitHub: https://github.com/gandm/language-babel/issues/200

bcarroll22 commented 8 years ago

Sure. The simplest example would be:

const template = `<div class="menu">text here</div>`;

So inside of the backticks, it would be HTML syntax highlighting.

gandm commented 8 years ago

I can't find a package that does this. The one you mentioned, unless I'm mistaken, does highlight but the scopes are totally wrong. e.g. the < is a relational operator, div is a variable, class is a strorage.type...etc.

There was some info about using template tags to indicate that a string was html or css here https://github.com/atom/language-javascript/pull/282

bcarroll22 commented 8 years ago

If that same thing could work inside the Babel grammar, that would be awesome. Would that be more of a possibility than doing it with generic backticks?

gandm commented 8 years ago

Well generic backticks are not really possible due to the multitude of uses they are put to. The only thing I can do is follow the JS spec and highlight like this: image

With tagged backticks it is easy to parse in the grammar but no standards exist!!! someHTML as a tagname might not contain HTML at all but something quite different. Also, the tag name isn't just cosmetic but must exist as a function. How does Angular support this scenario?

bcarroll22 commented 8 years ago

Well the problem is that when you use backticks to delimit a template, it's all just treated as a string. So if I wanted to do something like:

.component('randomComponent', {
  template: `<div class="test">asdf</div>`,
})

It's obviously not huge in this example, but when you start having larger templates, it gets bad trying to keep track of what's happening in your template. Another package that, as far as I can tell, supports the html highlighting in backticks is the atom-typescript package. Here's an issue where they discuss the feature

gandm commented 8 years ago

Ok, all the typescript package does is assume any object property named template followed by an opening backtick is an angular html structure. Again this is just a opinionated use of JavaScript property names. I'll give this some thought.

bcarroll22 commented 8 years ago

If this isn't something that would make it into this package, would it be something that could easily be implemented as an add-on to the package?

gandm commented 8 years ago

The issue is trying to determine when a backtick string contains Angular, handlebars, moustache, CSS.. or just text'. The grammar regex's don't allow one to read ahead to determine which it might be, so an add on wouldn't be that much help. Even if one could read ahead it's not very deterministic.

Language-JavaScript uses the tag format htmlWhereas typescript only works as { template: }. Both assume someway of identifying code, which is great when you write the code but maybe not so good for third party code. If the code is totally in your control you could use a comment before the backtick to denote angular. E.g. /ANGULAR/`` this is easy to parse in grammar and then the enclosed code can be assumed to be angular and highlighted, but this won't work for third-party code.

bcarroll22 commented 8 years ago

We tried to add the matcher from the typescript package and include your babel source type, but it just ignores the matcher. Would that be because it'll always prefer your string literal regex over the template regex we add?

gandm commented 8 years ago

No you could add the regex block to my package with no issues. I would be better to change the scope names to end .js not .ts. Also the relative position within all regex's makes a huge difference as that determines the precedence - it doesn't live in isolation. I'm not sure where and how you're adding it?

bcarroll22 commented 8 years ago

I added it to the grammar file in .apm/packages.

No matter whether I put it at the end or the beginning of the other rules, it seemed to not honor the highlighting for me. On Tue, Jun 7, 2016 at 4:35 PM Graham notifications@github.com wrote:

No you could add the regex block to my package with no issues. I would be better to change the scope names to end .js not .ts. Also the relative position within all regex's makes a huge difference as that determines the precedence - it doesn't live in isolation. I'm not sure where and how you're adding it?

— You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub https://github.com/gandm/language-babel/issues/200#issuecomment-224405803, or mute the thread https://github.com/notifications/unsubscribe/AKgodmQ5FUwzSePvm9DdqjI6cSChvXiyks5qJdYSgaJpZM4IuNiO .

gandm commented 8 years ago

I've just pushed a branch named angular to show you what and how html in backticks could be supported.

it parses an object member like

 a.component('randomComponent', {
   template: `<div class="test">asdf</div>`,
 })

and if found embeds the grammar from atom's default html highlighter to scope the backtick text.

bcarroll22 commented 8 years ago

I'm trying to create a separate Atom package so that this doesn't have to pollute your Babel package. Here's what I put in:

"name": "Babel ES6 JavaScript (Angular)",
"scopeName": "source.angular.js.jsx",
"fileTypes": ["js", "es6", "es", "babel"],
"patterns": [
  { "include": "source.js.jsx" },
  { "include": "#angular-html"}
],
"repository":
  "angular-html":
    "comment": "Match Angular Component html templates",
    "begin": "(?:(?<=^))\\s*\\b(template)\\b\\s*(:)\\s*(`)",
    "end": "\\s*(`)",
    "beginCaptures":
      "1": "name": "string.quoted.template.js",
      "2": "name": "punctuation.js",
      "3": "name": "punctuation.definition.quasi.begin.js"
    "endCaptures":
      "1": "name": "punctuation.definition.quasi.begin.js"
    "patterns": [
      { "include": "text.html.angular" }
    ]

That doesn't work. It also doesn't work if I move your source.js.jsx below "include": "#angular.hml"

Any suggestions?

gandm commented 8 years ago

No that won't work. All of the regex's in my grammar will be processed before your grammar. I.e. the order of includes and the patterns within them are listed in the order provided. So on the first pass you will end up with something like 130+ regular expressions that are then applied to the first line. The first one in the list that matches and also matches earliest in the line wins, and so it continues. Your angular script is at the bottom of the stack so will not be matched, as more generic matches will occur first. If you position angular above my scope it should match but maybe at points in the code that aren't always appropriate.

bcarroll22 commented 8 years ago

Same results if I position it above, it still doesn't match.

gandm commented 8 years ago

Ah sorry, it won't. The other thing that happens in grammar is nesting. As an example, when my grammar detects an object via a begin statement only the includes within that begin/end block's patterns are used until an end pattern is matched, then the regex stack unwinds. That's why I put the include within the 'literal-object' block so that it would only trigger inside an object.

I can't see how you can do this the way you intend. Maybe I should just put my change into the grammar and see what feedback I get.

bcarroll22 commented 8 years ago

That would be awesome, I'd really appreciate it. I'd be willing to keep working on this problem though to see if it's something that could be pulled out of this package. I'm surprised there's not an easy way to extend an Atom grammar like this... It seems like it'd be a handy thing.

gandm commented 8 years ago

Ok i'll do that, probably sometime tomorrow. Is it only object literals that have a template or can it appear elsewhere? Maybe a class property?

bcarroll22 commented 8 years ago

Yeah that's the only use case. A key in an object literal.

gandm commented 8 years ago

Should now work in 2.27.0. I actually use language-mustache which in turn uses language-html to scope templates. This allows scopes for {{}} and {{{}}} as well as HTML I also added support in my grammar for ${} syntax. If problems occur in these package's grammar it will cascade into my grammar. e.g. Using a tag without closing it will mean my grammar will miss the closing back tick. Issues related to anything embedded inside the template should for raised at those packages repo's.

Closing issue but I'll re-open if you have a problem.

PinkaminaDianePie commented 7 years ago

Can i enable this functionality for instance properties/static properties? For example

class MyComponent{
    static template = `
        <div>{{data}}</div>
    `
}
gandm commented 7 years ago

@PinkaminaDianePie you could use one of my package settings. use "(?<=template\s=\s)":text.html.mustache in this package setting image and after 10 seconds a new extension grammar will appear that will use a psitive lookbehind regex to look for your pattern and use a grammar to interpret the content of the template. In this case using the Atom language-mustache. image

See here for more info

PinkaminaDianePie commented 7 years ago

thx bro, you just saved my day!

jwhitmarsh commented 7 years ago

@gandm I'm trying to use that but with

template: `...`

and my regex is "(?<=template\:\s)":text.html.angular

But the scope at cursor is still wrong:

screen shot 2017-03-09 at 12 24 40

What am I doing wrong?

gandm commented 7 years ago

@jwhitmarsh What grammar uses the scope text.html.angular?

jwhitmarsh commented 7 years ago

https://atom.io/packages/angularjs

I'm pretty sure my regex is correct. I've just updated the extension to use your regex and mine: "(?<=template\:\s)":text.html.angular, "(?<=template\s=\s)":text.html.angular

And my test is

class ClassName {
  constructor() {
    //  works
    this.template = `
      <div></div>
    `;

    // doesn't work
    this.foo = {
      template: `
        <div></div>
      `
    };
  }
}

And the generated grammar json is

{
  "name": "language-babel-extension",
  "comment": "Auto generated Tag Extensions for language-babel",
  "comment": "Please do not edit this file directly",
  "scopeName": "languagebabel.ttlextension",
  "fileTypes": [],
  "patterns": [
    {
      "contentName": "text.html.angular",
      "begin": "\\s*+((?<=template\\:\\s))\\s*(`)",
      "beginCaptures": {
        "1": { "name": "entity.name.tag.js" },
        "2": { "name": "punctuation.definition.quasi.begin.js" }
      },
      "end": "\\s*(?<!\\\\)(`)",
      "endCaptures": {
        "1": { "name": "punctuation.definition.quasi.end.js" }
      },
      "patterns": [
        { "include": "source.js.jsx#literal-quasi-embedded" },
        { "include": "text.html.angular" }
      ]
    },{
      "contentName": "text.html.angular",
      "begin": "\\s*+((?<=template\\s=\\s))\\s*(`)",
      "beginCaptures": {
        "1": { "name": "entity.name.tag.js" },
        "2": { "name": "punctuation.definition.quasi.begin.js" }
      },
      "end": "\\s*(?<!\\\\)(`)",
      "endCaptures": {
        "1": { "name": "punctuation.definition.quasi.end.js" }
      },
      "patterns": [
        { "include": "source.js.jsx#literal-quasi-embedded" },
        { "include": "text.html.angular" }
      ]
    }
  ]
}

And because a gif speaks a thousand words language-babel

gandm commented 7 years ago

@jwhitmarsh ok. It's because template: is being matched inside my grammar and uses moustache as per the original request of the OP. This was before I implemented tagged template extensions as an option and it has precedence over any regex supplied. Can you open a new issue and I'll look at changing the precedence so that it will work.

jwhitmarsh commented 7 years ago

Will do.

That's interesting though - does moustache use // for commenting?