Search code examples
regexsublimetext3tmlanguage

SublimeText3 prevent backslash from escaping in quoted strings


I have created a tmlanguage file for a specialized language. This language allows backslashes to be used in strings and it does not escape them. So when I create the variable:

Path = 'C:\Temp\';

Sublime thinks that the quote is escaped and then my syntax coloring is off for the rest of the script.

How can I prevent this from happening?

EDIT: Ooops! Forgot the source (thanks MattDMo)

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>fileTypes</key>
    <array>
        <string>txt</string>
    </array>
    <key>name</key>
    <string>TM1</string>
    <key>patterns</key>
    <array>
        <dict>
            <key>match</key>
            <string>(=|&lt;&gt;|&gt;|&lt;|@=|@&lt;&gt;|@&gt;|@&lt;)</string>
            <key>name</key>
            <string>keyword.operator.tm1</string>
        </dict>     
        <dict>
            <key>match</key>
            <string>(?i)\b(If|While|Else|ElseIf|End|EndIf)\b</string>
            <key>name</key>
            <string>keyword.control.tm1</string>
        </dict> 
        <dict>
            <key>begin</key>
            <string>'</string>
            <key>beginCaptures</key>
            <dict>
                <key>0</key>
                <dict>
                    <key>name</key>
                    <string>punctuation.definition.string.begin.tm1</string>
                </dict>
            </dict>
            <key>end</key>
            <string>'</string>
            <key>endCaptures</key>
            <dict>
                <key>0</key>
                <dict>
                    <key>name</key>
                    <string>punctuation.definition.string.end.tm1</string>
                </dict>
            </dict>
            <key>name</key>
            <string>string.quoted.single.tm1</string>
            <key>patterns</key>
            <array>
                <dict>
                    <key>match</key>
                    <string>\\(x\h{2}|[0-2][0-7]{,2}|3[0-6][0-7]?|37[0-7]?|[4-7][0-7]?|.)</string>
                    <key>name</key>
                    <string>constant.character.escape.tm1</string>
                </dict>
            </array>
        </dict>
        <dict>
            <key>begin</key>
            <string>"</string>
            <key>beginCaptures</key>
            <dict>
                <key>0</key>
                <dict>
                    <key>name</key>
                    <string>punctuation.definition.string.begin.tm1</string>
                </dict>
            </dict>
            <key>end</key>
            <string>"</string>
            <key>endCaptures</key>
            <dict>
                <key>0</key>
                <dict>
                    <key>name</key>
                    <string>punctuation.definition.string.end.tm1</string>
                </dict>
            </dict>
            <key>name</key>
            <string>string.quoted.double.tm1</string>
            <key>patterns</key>
            <array>
                <dict>
                    <key>match</key>
                    <string>\\(x\h{2}|[0-2][0-7]{,2}|3[0-6][0-7]|37[0-7]?|[4-7][0-7]?|.)</string>
                    <key>name</key>
                    <string>constant.character.escape.tm1</string>
                </dict>
            </array>
        </dict>
        <dict>
            <key>captures</key>
            <dict>
                <key>1</key>
                <dict>
                    <key>name</key>
                    <string>punctuation.definition.comment.tm1</string>
                </dict>
            </dict>
            <key>match</key>
            <string>(#).*$\n?</string>
            <key>name</key>
            <string>comment.line.double-slash.tm1</string>
        </dict>
        <dict>
            <key>match</key>
            <string>\;</string>
            <key>name</key>
            <string>punctuation.terminator.statement.tm1</string>
        </dict>
    </array>
    <key>scopeName</key>
    <string>source.tm1</string>
</dict>
</plist>

Solution

  • If you're not already using PackageDev and its YAML-tmLanguage format for language development, you really should be. Here's what all the XML you posted looks like:

    # [PackageDev] target_format: plist, ext: tmLanguage
    name: TM1
    scopeName: source.tm1
    fileTypes: [txt]
    
    patterns:
    - name: keyword.operator.tm1
      match: (=|<>|>|<|@=|@<>|@>|@<)
    
    - name: keyword.control.tm1
      match: (?i)\b(If|While|Else|ElseIf|End|EndIf)\b
    
    - name: string.quoted.single.tm1
      begin: ''''
      beginCaptures:
        '0': {name: punctuation.definition.string.begin.tm1}
      end: ''''
      endCaptures:
        '0': {name: punctuation.definition.string.end.tm1}
      patterns:
      - name: constant.character.escape.tm1
        match: \\(x\h{2}|[0-2][0-7]{,2}|3[0-6][0-7]?|37[0-7]?|[4-7][0-7]?|.)
    
    - name: string.quoted.double.tm1
      begin: '"'
      beginCaptures:
        '0': {name: punctuation.definition.string.begin.tm1}
      end: '"'
      endCaptures:
        '0': {name: punctuation.definition.string.end.tm1}
      patterns:
      - name: constant.character.escape.tm1
        match: \\(x\h{2}|[0-2][0-7]{,2}|3[0-6][0-7]|37[0-7]?|[4-7][0-7]?|.)
    
    - name: comment.line.double-slash.tm1
      match: (#).*$\n?
      captures:
        '1': {name: punctuation.definition.comment.tm1}
    
    - name: punctuation.terminator.statement.tm1
      match: \;
    

    Once I put it in this format, the problem jumped out right away: it's with the constant.character.escape.tm1 section of your two string.quoted sections. The regex:

    \\(x\h{2}|[0-2][0-7]{,2}|3[0-6][0-7]|37[0-7]?|[4-7][0-7]?|.)
    

    matches a backslash followed by a bunch of options (separated by the | character), the last of which is .. This is what's causing your problem - . matches any character (except newline), so your regex is essentially saying "match a backslash followed by any of these combinations, including a backslash followed by any single character." Just get rid of |. at the end of each regex, and you'll be fine.

    As a side note, you really should be using a repository for holding the constant.character.escape.tm1 scope:

    - name: string.quoted.double.tm1
      begin: '"'
      beginCaptures:
        '0': {name: punctuation.definition.string.begin.tm1}
      end: '"'
      endCaptures:
        '0': {name: punctuation.definition.string.end.tm1}
      patterns:
      - include: '#constant_character_escape'
    
      (...)
    
    repository:
      constant_character_escape:
        name: constant.character.escape.tm1
        match: \\(x\h{2}|[0-2][0-7]{,2}|3[0-6][0-7]|37[0-7]?|[4-7][0-7]?)
    

    The repository section should be (I believe) at the end of the language definition, as that's the only place I've ever seen it. With this pattern, you can essentially break your definitions into blocks and assign them "variable names", then include them where needed.