Search code examples
regexyamlsublimetextsublime-syntax

How can I fix my custom Sublime Text syntax highlighting?


I'm trying to create a very basic .sublime-syntax file to understand how the YAML-based definitions work and eventually create a full definition (the language is an old version of UnrealScript - definitions exist for this language already but I'm doing this as more of a learning exercise).

The first thing I'm trying to tackle is the declaration of variables, which can exist in a few different forms:

var int myInt;
var int myIntA, myIntB, myIntC;
var() int myInt;
var(foo) int myInt;

My syntax file currently looks like:

%YAML 1.2
---
name: UnrealScript

file_extensions:
  - uc

scope: source.uscript

variables:
  valid_variable_name: '[a-zA-Z_][a-zA-Z_0-9]{0,}'
  valid_type_name: '{{valid_variable_name}}'

contexts:
  main:
    - match: ;
      scope: punctuation.terminator.statement.empty.uscript

    - match: '(?i)\bvar'
      scope: storage.variable.var.uscript
      push:
        - meta_scope: meta.variable.assignment
        - match: \(
          scope: punctuation.variable.property.begin.uscript
          push:
            - match: '{{valid_variable_name}}'
              scope: variable.parameter.uscript
            - match: ''
              pop: 1
        - match: \)
          scope: punctuation.variable.property.end.uscript
        - match: '{{valid_type_name}}(?=\s+)'
          scope: storage.type.uscript
          push:
            - match: '{{valid_variable_name}}'
              scope: variable.other.readwrite.uscript
              pop: 1
        - match: ;
          pop: 1

This seems to highlight things correctly, but let's say I start typing this and haven't yet added a semi-colon:

var int myInt, somethingElse
               ^^^^^^^^^^^^^

The variable name somethingElse is assigned the storage.type.uscript scope but I want it to be assigned variable.other.readwrite.uscript - what am I missing?

Side note: I'm aware of named contexts but am using anonymous contexts for this example.


Solution

  • The reason why somethingElse is scoped as storage.type.uscript is because it is still in the context with the meta scope meta.variable.assignment. (It would be much easier to discuss if the contexts were named btw...) So it sees {{valid_type_name}}(?=\s+) as matching somethingElse (assuming a newline after it and not typing exactly at EOF).

    The simple solution here is to scope the variables after a comma correctly - so instead of popping after assigning scope variable.other.readwrite.uscript, only pop at semi-colon for example.

    %YAML 1.2
    ---
    name: UnrealScript
    
    file_extensions:
      - uc
    
    scope: source.uscript
    
    variables:
      valid_variable_name: '[a-zA-Z_][a-zA-Z_0-9]{0,}'
      valid_type_name: '{{valid_variable_name}}'
    
    contexts:
      main:
        - match: ;
          scope: punctuation.terminator.statement.empty.uscript
    
        - match: '(?i)\bvar'
          scope: storage.variable.var.uscript
          push:
            - meta_scope: meta.variable.assignment
            - match: \(
              scope: punctuation.variable.property.begin.uscript
              push:
                - match: '{{valid_variable_name}}'
                  scope: variable.parameter.uscript
                - match: ''
                  pop: 1
            - match: \)
              scope: punctuation.variable.property.end.uscript
            - match: '{{valid_type_name}}(?=\s+)'
              scope: storage.type.uscript
              push:
                - match: '{{valid_variable_name}}'
                  scope: variable.other.readwrite.uscript
                - match: ','
                  scope: punctuation.separator.sequence.uscript
                - match: (?=;)
                  pop: 1
            - match: ;
              scope: punctuation.terminator.uscript
              pop: 1