%YAML 1.2 --- # http://www.sublimetext.com/docs/3/syntax.html name: Tcl file_extensions: - tcl scope: source.tcl variables: special_chars: '[;{}\[\]"\\]' unquoted_string: '[^\s{{special_chars}}][^\s${{special_chars}}]*' end_chars: '[;\n\}\]]' inline_end_chars: '[;\s\}\]\\]' var_unquoted_string: '(?:\$\{[^ \}]+\}|{{unquoted_string}})+' most_likely_code: 'while|for|catch|return|break|continue|switch|exit|foreach|if|after|append|array|auto_execok|auto_import|auto_load|auto_mkindex|auto_mkindex_old|auto_qualify|auto_reset|bgerror|binary|cd|clock|close|concat|dde|encoding|eof|error|eval|exec|expr|fblocked|fconfigure|fcopy|file|fileevent|filename|flush|format|gets|glob|global|history|http|incr|info|interp|join|lappend|library|lindex|linsert|list|llength|load|lrange|lreplace|lsearch|lset|lsort|memory|msgcat|namespace|open|package|parray|pid|pkg::create|pkg_mkIndex|proc|puts|pwd|re_syntax|read|registry|rename|resource|scan|seek|set|socket|SafeBase|source|split|string|subst|Tcl|tcl_endOfWord|tcl_findLibrary|tcl_startOfNextWord|tcl_startOfPreviousWord|tcl_wordBreakAfter|tcl_wordBreakBefore|tcltest|tclvars|tell|time|trace|unknown|unset|update|uplevel|upvar|variable|vwait' contexts: main: - include: commands commands: - match: ^(?=\s*[^\[\{]) push: command-name - match: ';' scope: punctuation.terminator.tcl push: command-name - match: '(?={{most_likely_code}})' push: command-name - include: expressions continued-line: - match: (?={{end_chars}}) pop: true - match: '^(?=\s*#)' push: - match: '#' scope: comment.line.number-sign.tcl punctuation.definition.comment.tcl set: - meta_content_scope: comment.line.number-sign.tcl - match: \n pop: true - match: \\\\ - match: \\\n scope: punctuation.separator.continuation.tcl - include: expressions expressions: - match: \\(?=\n) scope: punctuation.separator.continuation.tcl push: - match: \n - match: (?=\S) set: continued-line - include: braces - include: substitution - include: escape - include: variable - match: '-' scope: keyword.operator.tcl - include: numbers - include: strings strings: - match: '"' scope: punctuation.definition.string.begin.tcl push: - meta_scope: string.quoted.double.tcl - match: '"' scope: punctuation.definition.string.end.tcl pop: true - match: \\(?=\n) scope: punctuation.separator.continuation.tcl push: - match: \n - match: (?=\S) pop: true - include: escape - include: variable - match: '(?=\[(?!{{special_chars}}))' push: - clear_scopes: 1 - include: substitution - match: '' pop: true - match: '{{unquoted_string}}' numbers: - match: \b0x\h*\b(?={{inline_end_chars}}) scope: constant.numeric.integer.tcl - match: \b0b[01]*\b(?={{inline_end_chars}}) scope: constant.numeric.integer.tcl - match: \b0o[0-7]*\b(?={{inline_end_chars}}) scope: constant.numeric.integer.tcl - match: \b[0-9]+\.[0-9]+\b(?={{inline_end_chars}}) scope: constant.numeric.float.tcl - match: \b[0-9]+\b(?={{inline_end_chars}}) scope: constant.numeric.integer.tcl comments: - match: '#' scope: comment.line.number-sign.tcl punctuation.definition.comment.tcl set: - meta_content_scope: comment.line.number-sign.tcl - match: \n scope: comment.line.number-sign.tcl pop: true - match: \\\\ - match: \\\n scope: punctuation.separator.continuation.tcl command-name: - match: '(?=\bproc\b\s)' set: proc - match: '\bexpr\b(?=\s)' scope: keyword.other.tcl set: expr - match: \b(namespace)\s+eval\s+({{var_unquoted_string}}) captures: 1: keyword.other.tcl 2: entity.name.namespace.tcl set: - meta_scope: meta.namespace.tcl - match: '(?={{end_chars}})' pop: true - include: commands - match: \b(if)\b\s+(\{) captures: 1: keyword.control.tcl 2: meta.block.tcl punctuation.section.block.begin.tcl set: [conditional, conditional-expr] - match: \b(if)\b\s+(?=\S) captures: 1: keyword.control.tcl 2: meta.block.tcl punctuation.section.block.begin.tcl set: [conditional, conditional-bare-expr] - match: \bset\b scope: keyword.other.tcl push: - match: \b\w+\b set: - match: '\{(?=(\n|\s*({{most_likely_code}})))' scope: punctuation.section.block.begin.tcl set: [command-braces, command-name] - match: '\{(?=\s)' scope: punctuation.section.block.begin.tcl set: non-command-braces - match: '\{' scope: punctuation.definition.string.begin.tcl set: string-braces - match: '(?=\S)' set: - match: (?={{inline_end_chars}}) pop: true - include: expressions - match: (?=\S) pop: true - match: \b(while|for|catch|return|break|continue|switch|exit|foreach)\b(?=[{{inline_end_chars}}]) scope: keyword.control.tcl set: - match: '(?={{end_chars}})' pop: true - include: expr-body - match: '\b(after|append|array|auto_execok|auto_import|auto_load|auto_mkindex|auto_mkindex_old|auto_qualify|auto_reset|bgerror|binary|cd|clock|close|concat|dde|encoding|eof|error|eval|exec|expr|fblocked|fconfigure|fcopy|file|fileevent|filename|flush|format|gets|glob|global|history|http|incr|info|interp|join|lappend|library|lindex|linsert|list|llength|load|lrange|lreplace|lsearch|lset|lsort|memory|msgcat|namespace|open|package|parray|pid|pkg::create|pkg_mkIndex|proc|puts|pwd|re_syntax|read|registry|rename|resource|scan|seek|set|socket|SafeBase|source|split|string|subst|Tcl|tcl_endOfWord|tcl_findLibrary|tcl_startOfNextWord|tcl_startOfPreviousWord|tcl_wordBreakAfter|tcl_wordBreakBefore|tcltest|tclvars|tell|time|trace|unknown|unset|update|uplevel|upvar|variable|vwait)\b' scope: keyword.other.tcl pop: true - match: '\b(regexp|regsub)\b(?=\s)' captures: 1: keyword.other.tcl set: - match: '(?={{end_chars}})' pop: true # Consume the switches (https://www.tcl.tk/man/tcl8.4/TclCmd/regexp.htm) - match: '\s+-(about|all|expanded|indices|inline|linestop|lineanchor|line|nocase|start\s+\b[0-9]+)\b(?=\s)' # The next non-space element will be a regex string - match: '(?=\{|\s+--\s+)' set: - match: \s+(--)\s+ captures: 1: punctuation.separator.tcl - match: \{ set: - meta_content_scope: string.regexp.tcl - match: \} pop: true - include: regexp-braces - match: '(?=")' set: - include: strings # One of these characters indicates the string is complete - match: '(?={{inline_end_chars}})' pop: true - match: '(?=\S)' set: - meta_content_scope: string.regexp.tcl # One of these characters indicates the string is complete - match: '(?={{inline_end_chars}})' pop: true - match: '(?=")' set: - include: strings # One of these characters indicates the string is complete - match: '(?={{inline_end_chars}})' pop: true - match: '(?=\S)' set: - meta_content_scope: string.regexp.tcl # One of these characters indicates the string is complete - match: '(?={{inline_end_chars}})' pop: true - match: (?=#) set: comments - match: '(?={{var_unquoted_string}})' set: - meta_content_scope: variable.function.tcl - include: variable - match: '{{unquoted_string}}' - match: '' pop: true - match: '(?=\S)' pop: true regexp-braces: - match: \{ push: - match: \} pop: true - include: regexp-braces - include: escape conditional: - match: '(?={{end_chars}})' pop: true - match: '\b(elseif)\b\s+(\{)' captures: 1: keyword.control.tcl 2: meta.block.tcl punctuation.section.block.begin.tcl push: conditional-expr - match: '\b(then|elseif|else)\b(?=\s)' scope: keyword.control.tcl - include: commands conditional-expr: - meta_content_scope: meta.block.tcl - match: '(\})([^{{inline_end_chars}}]*)' captures: 1: meta.block.tcl punctuation.section.block.end.tcl 2: invalid.illegal.tcl pop: true - match: '\\[\\{}n]' scope: constant.character.escape.tcl - include: operators - include: commands conditional-bare-expr: - meta_content_scope: meta.block.tcl - match: '(?=[\{{{inline_end_chars}}{{end_chars}}])' pop: true - match: '\\[\\{}n]' scope: constant.character.escape.tcl - include: operators - include: commands proc: - match: \b(proc)\s+({{var_unquoted_string}}) scope: meta.function.tcl captures: 1: keyword.other.tcl 2: entity.name.function.tcl set: proc-parameters - match: \bproc\b(?=\s) scope: keyword.other.tcl pop: true proc-parameters: - match: \s+ scope: meta.function.tcl - match: \{ scope: punctuation.section.block.begin.tcl set: - meta_scope: meta.function.parameters.tcl meta.block.tcl - match: '(\})([^{{inline_end_chars}}]*)' captures: 1: punctuation.section.block.end.tcl 2: invalid.illegal.tcl set: proc-body - match: '^\s*(#)' captures: 1: comment.line.number-sign.tcl punctuation.definition.comment.tcl push: - meta_content_scope: comment.line.number-sign.tcl - match: \n pop: true - match: \\\\ - match: \\\n scope: punctuation.separator.continuation.tcl - include: proc-parameter - match: '(\[)\s*(list)\b(?=\s)' captures: 1: punctuation.definition.substitution.begin.tcl 2: keyword.other.tcl set: - meta_scope: meta.function.parameters.tcl meta.substitution.tcl - match: \] scope: punctuation.definition.substitution.end.tcl set: proc-body - include: proc-parameter - match: '' pop: true proc-parameter: - match: '\{' scope: punctuation.section.block.begin.tcl push: - meta_scope: meta.block.tcl - match: '{{unquoted_string}}' scope: variable.parameter.tcl set: - meta_content_scope: meta.block.tcl - match: '(\})([^{{inline_end_chars}}]*)' captures: 1: meta.block.tcl punctuation.section.block.end.tcl 2: invalid.illegal.tcl pop: true - include: commands - match: '\[\s*(list)\b(?=\s)' captures: 1: punctuation.definition.substitution.begin.tcl 2: keyword.other.tcl push: - meta_scope: meta.substitution.tcl - match: '{{unquoted_string}}' scope: variable.parameter.tcl set: - match: \] scope: punctuation.definition.substitution.end.tcl pop: true - include: commands - match: '{{unquoted_string}}' scope: variable.parameter.tcl - match: (?=\S) pop: true proc-body: - meta_content_scope: meta.function.tcl - match: '(?={{end_chars}})' pop: true - include: commands expr: - match: (?={{end_chars}}) pop: true - include: expr-body expr-body: - match: '\{' scope: punctuation.section.block.begin.tcl push: - meta_scope: meta.block.tcl - match: '(\})([^{{inline_end_chars}}]*)' captures: 1: punctuation.section.block.end.tcl 2: invalid.illegal.tcl pop: true - match: '\\[\\{}n]' scope: constant.character.escape.tcl - include: operators - include: commands - include: operators - include: commands operators: - match: '[~!*/%<>&^|=?:+-]' scope: keyword.operator.tcl - match: '\b(eq|ne|in|ni)\b(?=\s)' scope: keyword.operator.word.tcl command-braces: - meta_scope: meta.block.tcl - match: \} scope: punctuation.section.block.end.tcl pop: true - include: commands non-command-braces: - meta_scope: meta.block.tcl - match: ^\s*(?=#) push: comments - include: braces - match: '(\})([^{{inline_end_chars}}]*)' captures: 1: punctuation.section.block.end.tcl 2: invalid.illegal.tcl pop: true - match: '\\[\\{}n]' scope: constant.character.escape.tcl - include: expressions string-braces: - meta_scope: string.quoted.brace.tcl - match: '(\})(?=[\s\n{{inline_end_chars}}])' captures: 1: punctuation.definition.string.end.tcl 2: invalid.illegal.tcl pop: true - match: '\n' pop: true - match: '\\[\\{}n]' scope: constant.character.escape.tcl braces: - match: (\{)(\*)(\}) scope: meta.block.tcl captures: 1: punctuation.section.block.begin.tcl 2: keyword.operator.tcl 3: punctuation.section.block.end.tcl # Heuristic: if the brace is followed by whitespace, # or what appears to be a command name followed by # whitespace, treat it as code, otherwise a regexp - match: '\{(?=\s*({{most_likely_code}})\b|\n)' scope: punctuation.section.block.begin.tcl push: - meta_scope: meta.block.tcl - match: \} scope: punctuation.section.block.end.tcl pop: true - include: commands - match: '\{(?=\s|\})' scope: punctuation.section.block.begin.tcl push: non-command-braces - match: '\{' scope: punctuation.definition.string.begin.tcl push: string-braces substitution: - match: '\[(?!{{special_chars}})' scope: punctuation.definition.substitution.begin.tcl push: [substitution-body, command-name] substitution-body: - meta_scope: meta.substitution.tcl - match: '\]' scope: meta.substitution.tcl punctuation.definition.substitution.end.tcl pop: true - include: commands escape: - match: '\\(\d{1,3}|x\h+|u\h{1,4}|.|n)' scope: constant.character.escape.tcl variable: - match: '(\$)((?:[a-zA-Z0-9_]|::)+(\([^\)]+\))?|\{[^\}]*\})' scope: variable.other.tcl captures: 1: punctuation.definition.variable.tcl