Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I get syntax highlighting in Sublime for webgl/glsl inline script tags

I'm trying to get syntax highlighting/coloring for a glsl fragment shader in a script tag in an html file.

Even though I installed all related Shader Syntax packages in Sublime Text I still don't get syntax highlighting. I believe those only fix it for external files or for non-html files. But I'm using an internal script tag.

Has anyone ran into this?

uncolored syntax for glsl in sublime

like image 304
Shai UI Avatar asked Feb 15 '19 00:02

Shai UI


People also ask

How do I enable syntax highlighting in sublime?

To enable Syntax Highlighting click on “View” in the top bar, then hover your mouse over “Syntax”, and select your programming language from the list. Alternatively, if you save a document with a supported file extension, Sublime Text 3 will automatically apply the Syntax Highlighting for that language.

Does Sublime Text support syntax highlighting?

Sublime Text can use both . sublime-syntax and . tmLanguage files for syntax highlighting.

How do I install syntax highlighting in Sublime Text 3?

All I have to do in order to install a new syntax scheme is open Sublime's Command Palette (⌘-⇧-P). and issue the following command: Package Control: Install Package . Now I can choose whatever package I want to install — easy! And just like that!

How do you highlight in Sublime Text?

🖍 Text Marker (Highlighter) Simply use Alt + Space to mark selected text.


2 Answers

Indeed you are correct that in Sublime a syntax definition generally refers to a particular type of file based on external criteria: the extension that the file has, the first line of the file (e.g. a bash shebang) or the user specifically overriding the automatic detection.

It is possible for one syntax to allow another to temporarily "take control" of the syntax highlighting, but this needs to be a conscious decision implemented by the author of the base syntax definition. The system needs to know when it should enter a new syntax, when it should leave back to the previous one and (most importantly) what syntax to actually use in between.

For example, Sublime ships with both a JavaScript syntax that applies to .js files on their own, but the HTML syntax it ships with also includes a rule that allows the contents of an appropriate <script> tag to be highlighted as JavaScript by handing control to that syntax, and taking it back when the enclosing </script> tag appears.

So in your case, what you want to do is technically possible, but it doesn't work "out of the box" so to speak; you need an HTML syntax that knows to handle that type of script tag specially. Such a syntax might already exist on Package Control. This is also a modification that you can make yourself if you're so inclined. The remainder of this answer tells you how to do that (and there is a link at the bottom to a modified file as well).


The basic idea is that we are going to create an override of the HTML syntax file that ships with Sublime. As the name suggests, that means that the file we're creating will always be used in place of the file that ships with Sublime transparently. Note however that even if the underlying file gets updated, your changed file will still be used (and you get no direct warning). One of the features of the OverrideAudit package (disclaimer: I'm the author) is to tell you when that happens so you can be sure you're not missing out on new features or bug fixes in the underlying file.

These instructions also assume that you are using the latest stable build of Sublime, which is 3176 at the moment. New builds often bring new syntaxes with them, so if you're running an older version now (or a newer version if you're from the mysterious future) the fundamental instructions remain the same but the content of the file might be different. As we'll see, we're basing our changes on copying existing functionality.

1. Open the HTML syntax for editing

If you don't already have it installed, install the PackageResourceViewer package, and then use the command PackageResourceViewer: Open Resource from the command palette (be sure you don't accidentally pick the extract variant) and pick first the HTML package, and then the HTML.sublime-syntax file. This opens the file for editing purposes; when you save the file an override will automatically be created for you.

Behind the scenes this is creating a folder named HTML in the Packages folder (use Preferences > Browse Packages... to find that) and storing a copy of the resource file inside. Deleting that file will restore the original syntax.

You can manually create the override by using the View Package File built in command to open the HTML/HTML.sublime-syntax resource and save it in the appropriate location. If you do this, you need to close and reopen the file because it will open as read-only.

2. Tell the syntax how to recognize a shader tag

Near the top of the open syntax file is a set of variables, which in the syntax are a way to provide named regular expressions to make following steps easier to read. In the variable list is a variable named javascript_mime_type which is defined like this:

  javascript_mime_type: |-
    (?ix)(?:
      # https://mimesniff.spec.whatwg.org/#javascript-mime-type
      (?:application|text)/(?:x-)?(?:java|ecma)script
      | text/javascript1\.[0-5]
      | text/jscript
      | text/livescript
    )

Right below that, we will create our own variable that contains the regular expression to match the internals of the type attribute of the script tag. An example of that is the following:

  shader_mime_type: |-
    (?ix)(?:
      x-shader/x-fragment
    )

This may or may not need to be extended (I'm unfamiliar with how this tag is used for WebGL). If there is more than one variant of possible contents, this can be extended as in the JavaScript example above to include extras.

3. Include a context that embeds the GLSL syntax

Further down in the file (in build 3176 this is around line 315 or so after making the above change) is a context named script-javascript that contains the syntax rules for knowing how to highlight the contents of a javascript based <script> tag, and it looks like this:

  script-javascript:
    - meta_content_scope: meta.tag.script.begin.html
    - include: script-common
    - match: '>'
      scope: punctuation.definition.tag.end.html
      set:
        - include: script-close-tag
        - match: (?=\S)
          embed: scope:source.js
          embed_scope: source.js.embedded.html
          escape: (?i)(?=(?:-->\s*)?</script)

The first few lines include the logic for syntax highlighting the rest of the attributes in the <script> that follow the type attribute. The contents of set are the magic here; they say that if the body of the tag is not empty, the contents should be highlighting by embeding the Javascript syntax, and that a closing </script> tag is what escapes the embed and goes back to regular HTML.

Here we will create our own section for hangling a glsl script:

  script-glsl:
    - meta_content_scope: meta.tag.script.begin.html
    - include: script-common
    - match: '>'
      scope: punctuation.definition.tag.end.html
      set:
        - include: script-close-tag
        - match: (?=\S)
          embed: scope:source.glsl
          embed_scope: source.glsl.embedded.html
          escape: (?i)(?=(?:-->\s*)?</script)

As we can see, this is nearly identical to the above, but a different scope is used (more on that below).

4. Tell the script tag about our new rules

We've laid the groundwork, now to tie everything together. Lower down in the file (in 3176 this is roughly line 390 after the above changes) there is a set of rules entitled script-type-decider. There are five match rules in this context, so for brevity we're only going to show the first one here.

  script-type-decider:
    - match: (?i)(?={{javascript_mime_type}}(?!{{unquoted_attribute_value}})|'{{javascript_mime_type}}'|"{{javascript_mime_type}}")
      set:
        - script-javascript
        - tag-generic-attribute-meta
        - tag-generic-attribute-value

This context lists the rules that gets applied while inside of a <script> tag's type attribute to see what should happen to the contents of this particular tag. The rule specifically quoted here uses the variable outlined above to determine that this is a JavaScript tag and uses the rule we just looked at to embed the JavaScript syntax.

Right below this match (order of rules matters) we will add our own match to mimic this one, using our own variable and rule set:

    - match: (?i)(?={{shader_mime_type}}(?!{{unquoted_attribute_value}})|'{{shader_mime_type}}'|"{{shader_mime_type}}")
      set:
        - script-glsl
        - tag-generic-attribute-meta
        - tag-generic-attribute-value

5. Make the changes active

With all of our changes made, all you have to do is save the file to make them active. Once you do so, Sublime will immediately recompile the changed syntax file and put it into effect. The Sublime console (View > Show Console) will say generating syntax summary when it does this. If you don't see any error messages, you're ready to go:

Sample of the syntax in action

A full version of the file with the changes talked about is available in this gist for comparison purposes and was used to generate the above image. The gist is laid out with the base file as revision 1 and the modifications as version 2, so it is easy to see exactly what changes were made to the file.


Final Notes

It's worth mentioning that the package in question actually provides three different syntaxes (Cg, HLSL and GLSL). The syntax modifications above only handle the GLSL syntax embedding, which I guessed was what you were going for.

Following the same instructions as above, you can swap the syntax used or add rules for the others as well (assuming you know what type should go in the <script> tag to differentiate them). The same rules will also apply if there is another syntax that provides a subjectively "better" highlighting experience.

The important piece of the puzzle is the scope that is used in the embedded syntax. In the above example we used source.glsl, which is something that is defined directly in the syntax definition for this file type in the package that you're using.

The other scopes in that package are source.cg and source.hlsl. The easiest way to determine the appropriate scope is to create a new file, use the appropriate Set Syntax: item from the command palette to set the syntax for the empty buffer, and use Tools > Developer > Show Scope Name to get a popup that will tell you.

like image 135
OdatNurd Avatar answered Sep 29 '22 23:09

OdatNurd


not a direct answer but an alternative solution.

Using ES6 import you can store your shaders in separate files

// some-vertex-shader.glsl
export default `
void main() {
  gl_Position = vec4(0, 0, 0, 1);
}
`;

Then in your JavaScript you can include it like this

import someVertexShaderSource from 'path/to/some-shader-source.glsl';
...

With that you can probably setup Sublime to highlight code in .glsl files

You can then use something like rollup to merge all your files into one if you want to support older browsers.

This is what three.js does.

Also in VSCode (sorry, not sublimetext) I believe you can tag template literals with a language to get it to highlight

const shaderSource = /* glsl */`
void main() {
  gl_Position = vec4(0, 0, 0, 1);
}
like image 21
gman Avatar answered Sep 29 '22 23:09

gman