Search code examples
sublimetext3sublimetextsublime-text-plugin

Sublime Text custom syntax highlighting


I'm working on a custom syntax highlighting and copied few snippets from the HTML syntax since this piece is very similar. I'm stuck on trying to figure out how to highlight the text inside of tags. Here is my definition:

- match: "(</?)([a-zA-Z0-9:]+)"
  captures:
    1: punctuation.definition.tag.begin.html
    2: entity.name.tag.other.html
  push:
    - meta_scope: meta.tag.other.html
    - match: '>'
      scope: punctuation.definition.tag.end.html
      pop: true
    - match: '.'
      scope: string.quoted.single.html

Samle text:

<file bash>
Some bash code block
Some bash code block
</file>

My code highlights the brackets <>, the file and bash keywords, but I can't figure out how to add color to the block inside. Ultimately I want to have it as a block comment or something similar so it will stand out. Any suggestions?

I need a solution that avoids adding the comment highlighting for tags that have no closing tag. For example there are certain tags in the markup I'm working with that don't use closing, such as <tag without close> that doesn't have </tag>. Any way to add an exclusion in the regex to only work if there is open and close tags, but not when there is only open tag?

<tag without close>
This should not be a comment.

<file bash>
This should be a comment.
</file>

This also should not be a comment.

Only a small selection of tags will be used like <tag> above, mostly for metadata.


Solution

  • One way, based loosely on how the interiors of both <style> and <script> are managed, is to use a with_prototype which has a pop.

    - match: '(?:^\s+)?(<)([a-zA-Z0-9:]+)\b(?![^>]*/>)'
      captures:
        1: punctuation.definition.tag.begin.html
        2: entity.name.tag.other.html
      push:
        - match: '(</)(\2)(>)'
          captures:
            1: punctuation.definition.tag.begin.html
            2: entity.name.tag.other.html
            3: punctuation.definition.tag.end.html
          pop: true
        - match: '>'
          scope: punctuation.definition.tag.end.html
          push:
            - match: '.'
              scope: comment.block.html
          with_prototype:
            - match: (?=</\2)
              pop: true
            - include: main
        - match: '.'
          scope: string.quoted.single.html
    

    Note that the ([a-zA-Z0-9:]+) here is matching any valid tag name, as you were in your question, and \2 is used to match that group later, both in the immediate decoupling match condition and in the with_prototype condition. The with_protoype defines patterns that are applied to everything in the current context, so here we use it to make sure we pop the comment-esque highlighting once we reach a </file>, rather than being treated as part of the comment.

    Within the with_prototype, the - include: main statement ensures that any tags within your comment-like content behave like the outer <file> tags. For example, <hello> below acts just the same as <file>.

    <file bash>
    Some bash code block
    <hello stuff>
    Some bash code block
    </hello>
    Some bash code block
    </file>
    

    If you have tags that don't have matching end tags, you can override this behaviour by defining specific behaviour for those tags higher up the stack, like so:

    - match: '(?:^\s+)?(<)(hello)\b(?![^>]*/>)'
      captures:
        1: punctuation.definition.tag.begin.html
        2: entity.name.tag.other.html
      push:
        - match: '>'
          scope: punctuation.definition.tag.end.html
          pop: true
        - match: '.'
          scope: string.quoted.single.html
    

    Provided this is earlier than the match: '(?:^\s+)?(<)([a-zA-Z0-9:]+)\b(?![^>]*/>)' line, any <hello> tags will not invoke the comment-esque highlighting.

    This text is not comment.block.html. Some bash code block Some bash code block Some bash code block