name: ASP.NET Razor
scopeName: text.aspnetcorerazor
fileTypes:
  - 'razor'
  - 'cshtml'

# These injections make sure that razor expressions can be recognized in the HTML grammar, specifically in strings
injections:
  string.quoted.double.html:
    patterns:
      - include: '#explicit-razor-expression'
      - include: '#implicit-expression'

  string.quoted.single.html:
    patterns:
      - include: '#explicit-razor-expression'
      - include: '#implicit-expression'

  # Inject inline templates into C# scope to handle @<tag> inside C# expressions
  source.cs:
    patterns:
      - include: '#inline-template'

patterns:
  - include: '#razor-control-structures'
  - include: 'text.html.basic'

repository:

 # Razor control structures, including C# control structures that require an '@'
  razor-control-structures:
    patterns:
      - include: '#razor-comment'
      - include: '#razor-codeblock'
      - include: '#explicit-razor-expression'
      - include: '#escaped-transition'
      - include: '#directives'
      - include: '#transitioned-csharp-control-structures'
      - include: '#implicit-expression'

# Razor control structures, including C# control structures that don't require an '@' (ie, are nested in a C# context)
  optionally-transitioned-razor-control-structures:
    patterns:
      - include: '#razor-comment'
      - include: '#razor-codeblock'
      - include: '#explicit-razor-expression'
      - include: '#escaped-transition'
      - include: '#directives'
      - include: '#optionally-transitioned-csharp-control-structures'
      - include: '#implicit-expression'


  escaped-transition:
    name: constant.character.escape.razor.transition
    match: '@@'

  transition:
    match: '@'
    name: keyword.control.cshtml.transition

# ----------  Razor Code Block ------------

  razor-codeblock:
    name: 'meta.structure.razor.codeblock'
    begin: '(@)(\{)'
    beginCaptures:
      1: { patterns: [ include: '#transition' ] }
      2: { name: 'keyword.control.razor.directive.codeblock.open' }
    contentName: 'source.cs'
    patterns:
      - include: '#razor-codeblock-body'
    end: '(\})'
    endCaptures:
      1: { name: 'keyword.control.razor.directive.codeblock.close' }

  razor-codeblock-body:
    patterns:
      - include: '#text-tag'
      - include: '#inline-template'
      - include: '#wellformed-html'
      - include: '#razor-single-line-markup'
      - include: '#optionally-transitioned-razor-control-structures'
      - include: 'source.cs'

  razor-single-line-markup:
    match: '(\@\:)([^$]*)$'
    captures:
      1: { name: 'keyword.control.razor.singleLineMarkup' }
      2:
        patterns:
          - include: '#razor-control-structures'
          - include: 'text.html.basic'

  text-tag:
    begin: '(<text\s*>)'
    beginCaptures:
      1: { name: 'keyword.control.cshtml.transition.textTag.open' }
    patterns:
      - include: '#wellformed-html'
      - include: '$self'
    end: '(</text>)'
    endCaptures:
      1: { name: 'keyword.control.cshtml.transition.textTag.close' }

  # Inline template @<tag>...</tag> inside C# contexts
  inline-template:
    patterns:
      - include: '#inline-template-void-tag'
      - include: '#inline-template-non-void-tag'

  inline-template-void-tag:
    name: 'meta.tag.structure.$4.void.html'
    begin: '(?i)(@)(<)(!)?(area|base|br|col|command|embed|hr|img|input|keygen|link|meta|param|source|track|wbr)(?=\s|/?>)'
    beginCaptures:
      1: { patterns: [ include: '#transition' ] }
      2: { name: 'punctuation.definition.tag.begin.html'}
      3: { name: 'constant.character.escape.razor.tagHelperOptOut' }
      4: { name: 'entity.name.tag.html' }
    patterns:
      - include: '#razor-control-structures'
      - include: 'text.html.basic#attribute'
    end: '/?>'
    endCaptures:
      0: { name: 'punctuation.definition.tag.end.html' }

  inline-template-non-void-tag:
    begin: '(@)(<)(!)?([^/\s>]+)(?=\s|/?>)'
    beginCaptures:
      1: { patterns: [ include: '#transition' ] }
      2: { name: 'punctuation.definition.tag.begin.html' }
      3: { name: 'constant.character.escape.razor.tagHelperOptOut' }
      4: { name: 'entity.name.tag.html' }
    end: '(</)(\4)\s*(>)|(/>)'
    endCaptures:
      1: { name: 'punctuation.definition.tag.begin.html' }
      2: { name: 'entity.name.tag.html' }
      3: { name: 'punctuation.definition.tag.end.html' }
      4: { name: 'punctuation.definition.tag.end.html' }
    patterns:
      - begin: '(?<=>)(?!$)'
        end: '(?=</)'
        patterns:
          - include: '#inline-template'
          - include: '#wellformed-html'
          - include: '#razor-control-structures'
      - include: '#razor-control-structures'
      - include: 'text.html.basic#attribute'

# --------- Razor Comment -----------

  razor-comment:
    name: 'meta.comment.razor'
    begin: '(@)(\*)'
    beginCaptures:
      1: { patterns: [ include: '#transition' ] }
      2: { name: 'keyword.control.razor.comment.star' }
    contentName: 'comment.block.razor'
    end: '(\*)(@)'
    endCaptures:
      1: { name: 'keyword.control.razor.comment.star' }
      2: { patterns: [ include: '#transition']}

# ----------  HTML ------------

  wellformed-html:
    patterns:
      - include: '#void-tag'
      - include: '#non-void-tag'

  # Void tags are well-known tags that do not have an end tag *and* are not required to be self-closing.
  # https://html.spec.whatwg.org/multipage/syntax.html#void-elements
  void-tag:
    name: 'meta.tag.structure.$3.void.html'
    begin: '(?i)(<)(!)?(area|base|br|col|command|embed|hr|img|input|keygen|link|meta|param|source|track|wbr)(?=\s|/?>)'
    beginCaptures:
      1: { name: 'punctuation.definition.tag.begin.html'}
      2: { name: 'constant.character.escape.razor.tagHelperOptOut' }
      3: { name: 'entity.name.tag.html' }
    patterns:
      - include: 'text.html.basic#attribute'
    end: '/?>'
    endCaptures:
      0: { name: 'punctuation.definition.tag.end.html' }

  # This is some complicated trickery to be able to detect & match
  # self-closing components as well as *matching* elements with begin/end tags,
  # more-or-less copied from microsoft/TypeScript-TmLanguage's TSX grammar.
  # Basically - we match both ways a tag can be closed (self-closing or an end tag),
  # and leave the inner patterns to figure out which one we matched.
  non-void-tag:
    begin: '(?=<(!)?([^/\s>]+)(\s|/?>))'
    end: '(</)(\2)\s*(>)|(/>)' # The \2 back-references the second capture in 'begin', for matching tags
    endCaptures:
      1: { name: 'punctuation.definition.tag.begin.html' }
      2: { name: 'entity.name.tag.html' }
      3: { name: 'punctuation.definition.tag.end.html' }
      4: { name: 'punctuation.definition.tag.end.html' }
    patterns:
      - begin: '(<)(!)?([^/\s>]+)(?=\s|/?>)'
        beginCaptures:
          1: { name: 'punctuation.definition.tag.begin.html' }
          2: { name: 'constant.character.escape.razor.tagHelperOptOut' }
          3: { name: 'entity.name.tag.html' }
        end: '(?=/?>)'
        patterns:
          - include: '#razor-control-structures'
          - include: 'text.html.basic#attribute'
      - begin: '>'
        beginCaptures:
          0: { name: 'punctuation.definition.tag.end.html' }
        end: '(?=</)'
        patterns:
          - include: '#wellformed-html'
          - include: '$self'

# ----------  Explicit Expression ------------

  explicit-razor-expression:
    name: 'meta.expression.explicit.cshtml'
    begin: '(@)\('
    beginCaptures:
      0: { name: 'keyword.control.cshtml' }
      1: { patterns: [ include: '#transition' ] }
    patterns:
      - include: 'source.cs#expression'
    end: '\)'
    endCaptures:
      0: { name: 'keyword.control.cshtml' }

# ----------  Implicit Expression ------------

  implicit-expression:
    name: 'meta.expression.implicit.cshtml'
    contentName: 'source.cs'
    begin: '(?<![[:alpha:][:alnum:]])(@)'
    beginCaptures:
      1: { patterns: [ include: '#transition' ] }
    patterns:
      - include: '#await-prefix'
      - include: '#implicit-expression-body'
    end: '(?=[\s<>\{\}\)\]''"])'

  implicit-expression-body:
    patterns:
      - include: '#implicit-expression-invocation-start'
      - include: '#implicit-expression-accessor-start'
    end: '(?=[\s<>\{\}\)\]''"])'

  # CSharp colors method invocations differently than accessors
  # This captures situations where you do @SomeMethod()
  implicit-expression-invocation-start:
    begin: '([_[:alpha:]][_[:alnum:]]*)(?=\()'
    beginCaptures:
      1: { name: 'entity.name.function.cs' }
    patterns:
      - include: '#implicit-expression-continuation'
    end: '(?=[\s<>\{\}\)\]''"])'

  # CSharp colors property accessors differently than methods
  # This captures situations where you do @SomeProperty
  implicit-expression-accessor-start:
    begin: '([_[:alpha:]][_[:alnum:]]*)'
    beginCaptures:
      1: { name: 'variable.other.object.cs' }
    patterns:
      - include: '#implicit-expression-continuation'
    end: '(?=[\s<>\{\}\)\]''"])'

  implicit-expression-continuation:
    patterns:
      - include: '#balanced-parenthesis-csharp'
      - include: '#balanced-brackets-csharp'
      - include: '#implicit-expression-invocation'
      - include: '#implicit-expression-accessor'
      - include: '#implicit-expression-extension'
    end: '(?=[\s<>\{\}\)\]''"])'

  implicit-expression-accessor:
    match: '(?<=\.)[_[:alpha:]][_[:alnum:]]*'
    name: 'variable.other.object.property.cs'

  implicit-expression-invocation:
    match: '(?<=\.)[_[:alpha:]][_[:alnum:]]*(?=\()'
    name: 'entity.name.function.cs'

  implicit-expression-operator:
    patterns:
      - include: '#implicit-expression-dot-operator'
      - include: '#implicit-expression-null-conditional-operator'
      - include: '#implicit-expression-null-forgiveness-operator'

  implicit-expression-dot-operator:
    match: '(\.)(?=[_[:alpha:]][_[:alnum:]]*)'
    captures:
      1: { name: 'punctuation.accessor.cs' }

  implicit-expression-null-conditional-operator:
    match: '(\?)(?=[.\[])'
    captures:
      1: { name: 'keyword.operator.null-conditional.cs' }

  implicit-expression-null-forgiveness-operator:
    match: '(\!)(?=(?:\.[_[:alpha:]][_[:alnum:]]*)|\?|[\[\(])'
    captures:
      1: { name: 'keyword.operator.logical.cs' }

  balanced-parenthesis-csharp:
    begin: '(\()'
    beginCaptures:
      1: { name: 'punctuation.parenthesis.open.cs' }
    name: 'razor.test.balanced.parenthesis'
    patterns:
      - include: 'source.cs'
    end: '(\))'
    endCaptures:
      1: { name: 'punctuation.parenthesis.close.cs' }

  balanced-brackets-csharp:
    begin: '(\[)'
    beginCaptures:
      1: { name: 'punctuation.squarebracket.open.cs' }
    name: 'razor.test.balanced.brackets'
    patterns:
      - include: 'source.cs'
    end: '(\])'
    endCaptures:
      1: { name: 'punctuation.squarebracket.close.cs' }

# ----------  Directives ------------

  directives:
    patterns:
      - include: '#code-directive'
      - include: '#functions-directive'
      - include: '#page-directive'
      - include: '#addTagHelper-directive'
      - include: '#removeTagHelper-directive'
      - include: '#tagHelperPrefix-directive'
      - include: '#model-directive'
      - include: '#inherits-directive'
      - include: '#implements-directive'
      - include: '#namespace-directive'
      - include: '#inject-directive'
      - include: '#attribute-directive'
      - include: '#section-directive'
      - include: '#layout-directive'
      - include: '#using-directive'
      - include: '#rendermode-directive'
      - include: '#preservewhitespace-directive'
      - include: '#typeparam-directive'

  #>>>>> @code and @functions <<<<<

  code-directive:
    begin: '(@)(code)((?=\{)|\s+)'
    beginCaptures:
      1: { patterns: [ include: '#transition' ] }
      2: { name: 'keyword.control.razor.directive.code'}
    patterns:
      - include: '#directive-codeblock'
    end: '(?<=})|\s'

  functions-directive:
    begin: '(@)(functions)((?=\{)|\s+)'
    beginCaptures:
      1: { patterns: [ include: '#transition' ] }
      2: { name: 'keyword.control.razor.directive.functions'}
    patterns:
      - include: '#directive-codeblock'
    end: '(?<=})|\s'

  directive-codeblock:
    begin: '(\{)'
    beginCaptures:
      1: { name: 'keyword.control.razor.directive.codeblock.open' }
    name: 'meta.structure.razor.directive.codeblock'
    contentName: 'source.cs'
    patterns:
      - include: 'source.cs#class-or-struct-members'
    end: '(\})'
    endCaptures:
      1: { name: 'keyword.control.razor.directive.codeblock.close' }

  #>>>>> @page <<<<<

  page-directive:
    name: 'meta.directive'
    match: '(@)(page)\s+([^$]+)?'
    captures:
      1: { patterns: [ include: '#transition' ] }
      2: { name: 'keyword.control.razor.directive.page'}
      3: { patterns: [ include: 'source.cs#string-literal' ] }

  #>>>>> @addTagHelper, @removeTagHelper and @tagHelperPrefix <<<<<

  addTagHelper-directive:
    name: 'meta.directive'
    match: '(@)(addTagHelper)\s+([^$]+)?'
    captures:
      1: { patterns: [ include: '#transition' ] }
      2: { name: 'keyword.control.razor.directive.addTagHelper'}
      3: { patterns: [ include: '#tagHelper-directive-argument' ] }

  removeTagHelper-directive:
    name: 'meta.directive'
    match: '(@)(removeTagHelper)\s+([^$]+)?'
    captures:
      1: { patterns: [ include: '#transition' ] }
      2: { name: 'keyword.control.razor.directive.removeTagHelper'}
      3: { patterns: [ include: '#tagHelper-directive-argument' ] }

  tagHelperPrefix-directive:
    name: 'meta.directive'
    match: '(@)(tagHelperPrefix)\s+([^$]+)?'
    captures:
      1: { patterns: [ include: '#transition' ] }
      2: { name: 'keyword.control.razor.directive.tagHelperPrefix'}
      3: { patterns: [ include: '#tagHelper-directive-argument' ] }

  tagHelper-directive-argument:
    patterns:
      - include: 'source.cs#string-literal'
      - include: '#unquoted-string-argument'

  unquoted-string-argument:
    name: 'string.quoted.double.cs'
    match: '[^$]+'

  #>>>>> @model, layout, @implements and @inherits <<<<<

  model-directive:
    name: 'meta.directive'
    match: '(@)(model)\s+([^$]+)?'
    captures:
      1: { patterns: [ include: '#transition' ] }
      2: { name: 'keyword.control.razor.directive.model'}
      3: { patterns: [ include: 'source.cs#type' ] }

  inherits-directive:
    name: 'meta.directive'
    match: '(@)(inherits)\s+([^$]+)?'
    captures:
      1: { patterns: [ include: '#transition' ] }
      2: { name: 'keyword.control.razor.directive.inherits'}
      3: { patterns: [ include: 'source.cs#type' ] }

  implements-directive:
    name: 'meta.directive'
    match: '(@)(implements)\s+([^$]+)?'
    captures:
      1: { patterns: [ include: '#transition' ] }
      2: { name: 'keyword.control.razor.directive.implements'}
      3: { patterns: [ include: 'source.cs#type' ] }

  layout-directive:
    name: 'meta.directive'
    match: '(@)(layout)\s+([^$]+)?'
    captures:
      1: { patterns: [ include: '#transition' ] }
      2: { name: 'keyword.control.razor.directive.layout'}
      3: { patterns: [ include: 'source.cs#type' ] }

  #>>>>> @namespace <<<<<

  namespace-directive:
    name: 'meta.directive'
    match: '(@)(namespace)\s+([^\s]+)?'
    captures:
      1: { patterns: [ include: '#transition' ] }
      2: { name: 'keyword.control.razor.directive.namespace'}
      3: { patterns: [ include: '#namespace-directive-argument' ] }

  namespace-directive-argument:
    match: '([_[:alpha:]][_[:alnum:]]*)(\.)?'
    captures:
      1: { name: 'entity.name.type.namespace.cs' }
      2: { name: 'punctuation.accessor.cs' }

  #>>>>> @inject <<<<<

  inject-directive:
    name: 'meta.directive'
    match: '(@)(inject)\s*([\S\s]+?)?\s*([_[:alpha:]][_[:alnum:]]*)?\s*(?=$)'
    captures:
      1: { patterns: [ include: '#transition' ] }
      2: { name: 'keyword.control.razor.directive.inject'}
      3: { patterns: [ include: 'source.cs#type' ] }
      4: { name: 'entity.name.variable.property.cs' }

  #>>>>> @rendermode <<<<<

  rendermode-directive:
    name: 'meta.directive'
    match: '(@)(rendermode)\s+([^$]+)?'
    captures:
      1: { patterns: [ include: '#transition' ] }
      2: { name: 'keyword.control.razor.directive.rendermode'}
      3: { patterns: [ include: 'source.cs#type' ] }

  #>>>>> @preservewhitespace <<<<<

  preservewhitespace-directive:
    name: 'meta.directive'
    match: '(@)(preservewhitespace)\s+([^$]+)?'
    captures:
      1: { patterns: [ include: '#transition' ] }
      2: { name: 'keyword.control.razor.directive.preservewhitespace'}
      3: { patterns: [ include: 'source.cs#boolean-literal' ] }

  #>>>>> @typeparam <<<<<

  typeparam-directive:
    name: 'meta.directive'
    match: '(@)(typeparam)\s+([^$]+)?'
    captures:
      1: { patterns: [ include: '#transition' ] }
      2: { name: 'keyword.control.razor.directive.typeparam'}
      3: { patterns: [ include: 'source.cs#type' ] }

  #>>>>> @attribute <<<<<

  attribute-directive:
    name: 'meta.directive'
    begin: '(@)(attribute)\b\s+'
    beginCaptures:
      1: { patterns: [ include: '#transition' ] }
      2: { name: 'keyword.control.razor.directive.attribute'}
    patterns:
      - include: 'source.cs#attribute-section'
    end: '(?<=\])|$'

  #>>>>> @section <<<<<

  section-directive:
    name: 'meta.directive.block'
    begin: '(@)(section)\b\s+([_[:alpha:]][_[:alnum:]]*)?'
    beginCaptures:
      1: { patterns: [ include: '#transition' ] }
      2: { name: 'keyword.control.razor.directive.section'}
      3: { name: 'variable.other.razor.directive.sectionName' }
    patterns:
      - include: '#directive-markupblock'
    end: '(?<=})'

  directive-markupblock:
    name: 'meta.structure.razor.directive.markblock'
    begin: '(\{)'
    beginCaptures:
      1: { name: 'keyword.control.razor.directive.codeblock.open' }
    patterns:
      - include: '$self'
    end: '(\})'
    endCaptures:
      1: { name: 'keyword.control.razor.directive.codeblock.close' }

  #>>>>> @using <<<<<

  using-directive:
    name: 'meta.directive'
    match: '(@)(using)\b\s+(?!\(|\s)(.+?)?(;)?$'
    captures:
      1: { patterns: [ include: '#transition' ] }
      2: { name: 'keyword.other.using.cs'}
      3:
        patterns:
          - include: '#using-static-directive'
          - include: '#using-alias-directive'
          - include: '#using-standard-directive'
      4: { name: 'keyword.control.razor.optionalSemicolon'}

  using-static-directive:
    match: '(static)\b\s+(.+)'
    captures:
      1: { name: 'keyword.other.static.cs' }
      2: { patterns: [ include: 'source.cs#type' ] }

  using-alias-directive:
    match: '([_[:alpha:]][_[:alnum:]]*)\b\s*(=)\s*(.+)\s*'
    captures:
      1: { name: 'entity.name.type.alias.cs' }
      2: { name: 'keyword.operator.assignment.cs' }
      3: { patterns: [ include: 'source.cs#type' ] }

  using-standard-directive:
    match: '([_[:alpha:]][_[:alnum:]]*)\s*'
    captures:
      1: { name: 'entity.name.type.namespace.cs' }

  # ----------  Razor C# Control Structures ------------

  optionally-transitioned-csharp-control-structures:
    patterns:
      - include: '#using-statement-with-optional-transition'
      - include: '#if-statement-with-optional-transition'
      - include: '#else-part'
      - include: '#foreach-statement-with-optional-transition'
      - include: '#for-statement-with-optional-transition'
  # while is incompatible with optional transitions because @do { } \n while { } is valid everywhere, and textmate only matches
  # one line at a time
      - include: '#while-statement'
      - include: '#switch-statement-with-optional-transition'
      - include: '#lock-statement-with-optional-transition'
      - include: '#do-statement-with-optional-transition'
      - include: '#try-statement-with-optional-transition'

  transitioned-csharp-control-structures:
    patterns:
      - include: '#using-statement'
      - include: '#if-statement'
      - include: '#else-part'
      - include: '#foreach-statement'
      - include: '#for-statement'
      - include: '#while-statement'
      - include: '#switch-statement'
      - include: '#lock-statement'
      - include: '#do-statement'
      - include: '#try-statement'

  #>>>>> @using (...) { ... } <<<<<

  using-statement:
    name: 'meta.statement.using.razor'
    begin: '(?:(@))(using)\b\s*(?=\()'
    beginCaptures:
      1: { patterns: [ include: '#transition' ] }
      2: { name: 'keyword.other.using.cs' }
    patterns:
      - include: '#csharp-condition'
      - include: '#csharp-code-block'
      - include: '#razor-codeblock-body'
    end: '(?<=})|(?<=;)|(?=^\s*\})'

  using-statement-with-optional-transition:
    name: 'meta.statement.using.razor'
    begin: '(?:^\s*|(@))(using)\b\s*(?=\()'
    beginCaptures:
      1: { patterns: [ include: '#transition' ] }
      2: { name: 'keyword.other.using.cs' }
    patterns:
      - include: '#csharp-condition'
      - include: '#csharp-code-block'
      - include: '#razor-codeblock-body'
    end: '(?<=})|(?<=;)|(?=^\s*\})'

  #>>>>> @if (...) { ... } <<<<<

  if-statement:
    name: 'meta.statement.if.razor'
    begin: '(?:(@))(if)\b\s*(?=\()'
    beginCaptures:
      1: { patterns: [ include: '#transition' ] }
      2: { name: 'keyword.control.conditional.if.cs' }
    patterns:
      - include: '#csharp-condition'
      - include: '#csharp-code-block'
      - include: '#razor-codeblock-body'
    end: '(?<=})|(?<=;)|(?=^\s*\})'

  if-statement-with-optional-transition:
    name: 'meta.statement.if.razor'
    begin: '(?:^\s*|(@))(if)\b\s*(?=\()'
    beginCaptures:
      1: { patterns: [ include: '#transition' ] }
      2: { name: 'keyword.control.conditional.if.cs' }
    patterns:
      - include: '#csharp-condition'
      - include: '#csharp-code-block'
      - include: '#razor-codeblock-body'
    end: '(?<=})|(?<=;)|(?=^\s*\})'

  #>>>>> else [if (...)] { ... } <<<<<

  else-part:
    name: 'meta.statement.else.razor'
    begin: '(?:^|(?<=}))\s*(else)\b\s*?(?: (if))?\s*?(?=[\n\(\{])'
    beginCaptures:
      1: { name: 'keyword.control.conditional.else.cs' }
      2: { name: 'keyword.control.conditional.if.cs' }
    patterns:
      - include: '#csharp-condition'
      - include: '#csharp-code-block'
      - include: '#razor-codeblock-body'
    end: '(?<=})|(?<=;)|(?=^\s*\})'

  #>>>>> @for (...) { ... } <<<<<

  for-statement:
    name: 'meta.statement.for.razor'
    begin: '(?:(@))(for)\b\s*(?=\()'
    beginCaptures:
      1: { patterns: [ include: '#transition' ] }
      2: { name: 'keyword.control.loop.for.cs' }
    patterns:
      - include: '#csharp-condition'
      - include: '#csharp-code-block'
      - include: '#razor-codeblock-body'
    end: '(?<=})|(?<=;)|(?=^\s*\})'

  for-statement-with-optional-transition:
    name: 'meta.statement.for.razor'
    begin: '(?:^\s*|(@))(for)\b\s*(?=\()'
    beginCaptures:
      1: { patterns: [ include: '#transition' ] }
      2: { name: 'keyword.control.loop.for.cs' }
    patterns:
      - include: '#csharp-condition'
      - include: '#csharp-code-block'
      - include: '#razor-codeblock-body'
    end: '(?<=})|(?<=;)|(?=^\s*\})'

  #>>>>> @foreach (...) { ... } <<<<<

  foreach-statement:
    name: 'meta.statement.foreach.razor'
    begin: '(?:(@)(await\s+)?)(foreach)\b\s*(?=\()'
    beginCaptures:
      1: { patterns: [ include: '#transition' ] }
      2: { patterns: [ include: '#await-prefix' ] }
      3: { name: 'keyword.control.loop.foreach.cs' }
    patterns:
      - include: '#foreach-condition'
      - include: '#csharp-code-block'
      - include: '#razor-codeblock-body'
    end: '(?<=})|(?<=;)|(?=^\s*\})'

  foreach-statement-with-optional-transition:
    name: 'meta.statement.foreach.razor'
    begin: '(?:^\s*|(@)(await\s+)?)(foreach)\b\s*(?=\()'
    beginCaptures:
      1: { patterns: [ include: '#transition' ] }
      2: { patterns: [ include: '#await-prefix' ] }
      3: { name: 'keyword.control.loop.foreach.cs' }
    patterns:
      - include: '#foreach-condition'
      - include: '#csharp-code-block'
      - include: '#razor-codeblock-body'
    end: '(?<=})|(?<=;)|(?=^\s*\})'

  # This condition is pulled directly from the C# grammar for foreach statements
  foreach-condition:
    begin: '\('
    beginCaptures:
      0: { name: 'punctuation.parenthesis.open.cs' }
    end: '\)'
    endCaptures:
      0: { name: 'punctuation.parenthesis.close.cs' }
    patterns:
      - match: |-
          (?x)
          (?:
            (\bvar\b)|
            (?<type-name>
              (?:
                (?:
                  (?:(?<identifier>@?[_[:alpha:]][_[:alnum:]]*)\s*\:\:\s*)? # alias-qualification
                  (?<name-and-type-args> # identifier + type arguments (if any)
                    \g<identifier>\s*
                    (?<type-args>\s*<(?:[^<>]|\g<type-args>)+>\s*)?
                  )
                  (?:\s*\.\s*\g<name-and-type-args>)* | # Are there any more names being dotted into?
                  (?<tuple>\s*\((?:[^\(\)]|\g<tuple>)+\))
                )
                (?:\s*\?\s*)? # nullable suffix?
                (?:\s*\[(?:\s*,\s*)*\]\s*)* # array suffix?
              )
            )
          )\s+
          (\g<identifier>)\s+
          \b(in)\b
        captures:
          1: { name: 'keyword.other.var.cs' }
          2:
            patterns:
              - include: 'source.cs#type'
          # '3': ?<identifier> is a sub-expression. It's final value is not considered.
          # '4': ?<name-and-type-args> is a sub-expression. It's final value is not considered.
          # '5': ?<type-args> is a sub-expression. It's final value is not considered.
          # '6': ?<tuple> is a sub-expression. It's final value is not considered.
          7: { name: 'entity.name.variable.local.cs' }
          8: { name: 'keyword.control.loop.in.cs' }
      - match: |-
          (?x) # match foreach (var (x, y) in ...)
          (?:\b(var)\b\s*)?
          (?<tuple>\((?:[^\(\)]|\g<tuple>)+\))\s+
          \b(in)\b
        captures:
          1: { name: 'keyword.other.var.cs' }
          2:
            patterns:
              - include: 'source.cs#tuple-declaration-deconstruction-element-list'
          3: { name: 'keyword.control.loop.in.cs' }
      - include: 'source.cs#expression'

  #>>>>> @do (...) while { ... } <<<<<

  do-statement:
    name: 'meta.statement.do.razor'
    begin: '(?:(@))(do)\b\s'
    beginCaptures:
      1: { patterns: [ include: '#transition' ] }
      2: { name: 'keyword.control.loop.do.cs' }
    patterns:
      - include: '#csharp-condition'
      - include: '#csharp-code-block'
      - include: '#razor-codeblock-body'
    end: '(?<=})|(?<=;)|(?=^\s*\})'

  do-statement-with-optional-transition:
    name: 'meta.statement.do.razor'
    begin: '(?:^\s*|(@))(do)\b\s'
    beginCaptures:
      1: { patterns: [ include: '#transition' ] }
      2: { name: 'keyword.control.loop.do.cs' }
    patterns:
      - include: '#csharp-condition'
      - include: '#csharp-code-block'
      - include: '#razor-codeblock-body'
    end: '(?<=})|(?<=;)|(?=^\s*\})'

  #>>>>> @while (...) { ... } <<<<<

  while-statement:
    name: 'meta.statement.while.razor'
    begin: '(?:(@)|^\s*|(?<=})\s*)(while)\b\s*(?=\()'
    beginCaptures:
      1: { patterns: [ include: '#transition' ] }
      2: { name: 'keyword.control.loop.while.cs' }
    patterns:
      - include: '#csharp-condition'
      - include: '#csharp-code-block'
      - include: '#razor-codeblock-body'
    end: '(?<=})|(;)'
    endCaptures:
      1: { name: 'punctuation.terminator.statement.cs'}

  #>>>>> @switch (...) { ... } <<<<<

  switch-statement:
    name: 'meta.statement.switch.razor'
    begin: '(?:(@))(switch)\b\s*(?=\()'
    beginCaptures:
      1: { patterns: [ include: '#transition' ] }
      2: { name: 'keyword.control.switch.cs' }
    patterns:
      - include: '#csharp-condition'
      - include: '#switch-code-block'
      - include: '#razor-codeblock-body'
    end: '(?<=})|(?<=;)|(?=^\s*\})'

  switch-statement-with-optional-transition:
    name: 'meta.statement.switch.razor'
    begin: '(?:^\s*|(@))(switch)\b\s*(?=\()'
    beginCaptures:
      1: { patterns: [ include: '#transition' ] }
      2: { name: 'keyword.control.switch.cs' }
    patterns:
      - include: '#csharp-condition'
      - include: '#switch-code-block'
      - include: '#razor-codeblock-body'
    end: '(?<=})|(?<=;)|(?=^\s*\})'

  switch-code-block:
    name: 'meta.structure.razor.csharp.codeblock.switch'
    begin: '(\{)'
    beginCaptures:
      1: { name: 'punctuation.curlybrace.open.cs' }
    patterns:
      - include: 'source.cs#switch-label'
      - include: '#csharp-code-block'
      - include: '#razor-codeblock-body'
    end: '(\})'
    endCaptures:
      1: { name: 'punctuation.curlybrace.close.cs' }

  #>>>>> @lock (...) { ... } <<<<<

  lock-statement:
    name: 'meta.statement.lock.razor'
    begin: '(?:(@))(lock)\b\s*(?=\()'
    beginCaptures:
      1: { patterns: [ include: '#transition' ] }
      2: { name: 'keyword.other.lock.cs' }
    patterns:
      - include: '#csharp-condition'
      - include: '#csharp-code-block'
      - include: '#razor-codeblock-body'
    end: '(?<=})|(?<=;)|(?=^\s*\})'

  lock-statement-with-optional-transition:
    name: 'meta.statement.lock.razor'
    begin: '(?:^\s*|(@))(lock)\b\s*(?=\()'
    beginCaptures:
      1: { patterns: [ include: '#transition' ] }
      2: { name: 'keyword.other.lock.cs' }
    patterns:
      - include: '#csharp-condition'
      - include: '#csharp-code-block'
      - include: '#razor-codeblock-body'
    end: '(?<=})|(?<=;)|(?=^\s*\})'

  #>>>>> @try { ... } [catch|finally] (...) { ... } <<<<<

  try-statement:
    patterns:
      - include: '#try-block'
      - include: '#catch-clause'
      - include: '#finally-clause'

  try-statement-with-optional-transition:
    patterns:
      - include: '#try-block-with-optional-transition'
      - include: '#catch-clause'
      - include: '#finally-clause'

  try-block:
    name: 'meta.statement.try.razor'
    begin: '(?:(@))(try)\b\s*'
    beginCaptures:
      1: { patterns: [ include: '#transition' ] }
      2: { name: 'keyword.control.try.cs' }
    patterns:
      - include: '#csharp-condition'
      - include: '#csharp-code-block'
      - include: '#razor-codeblock-body'
    end: '(?<=})|(?<=;)|(?=^\s*\})'

  try-block-with-optional-transition:
    name: 'meta.statement.try.razor'
    begin: '(?:^\s*|(@))(try)\b\s*'
    beginCaptures:
      1: { patterns: [ include: '#transition' ] }
      2: { name: 'keyword.control.try.cs' }
    patterns:
      - include: '#csharp-condition'
      - include: '#csharp-code-block'
      - include: '#razor-codeblock-body'
    end: '(?<=})|(?<=;)|(?=^\s*\})'

  catch-clause:
    name: 'meta.statement.catch.razor'
    begin: '(?:^|(?<=}))\s*(catch)\b\s*?(?=[\n\(\{])'
    beginCaptures:
      1: { name: 'keyword.control.try.catch.cs' }
    patterns:
      - include: '#catch-condition'
      - include: 'source.cs#when-clause'
      - include: '#csharp-code-block'
      - include: '#razor-codeblock-body'
    end: '(?<=})|(?<=;)|(?=^\s*\})'

  # This condition is pulled directly from the C# grammar for catch clauses
  catch-condition:
    begin: '\('
    beginCaptures:
      0: { name: 'punctuation.parenthesis.open.cs' }
    end: '\)'
    endCaptures:
      0: { name: 'punctuation.parenthesis.close.cs' }
    patterns:
      - match: |-
          (?x)
          (?<type-name>
            (?:
              (?:
                (?:(?<identifier>@?[_[:alpha:]][_[:alnum:]]*)\s*\:\:\s*)? # alias-qualification
                (?<name-and-type-args> # identifier + type arguments (if any)
                  \g<identifier>\s*
                  (?<type-args>\s*<(?:[^<>]|\g<type-args>)+>\s*)?
                )
                (?:\s*\.\s*\g<name-and-type-args>)* | # Are there any more names being dotted into?
                (?<tuple>\s*\((?:[^\(\)]|\g<tuple>)+\))
              )
              (?:\s*\?\s*)? # nullable suffix?
              (?:\s*\[(?:\s*,\s*)*\]\s*)* # array suffix?
            )
          )\s*
          (?:(\g<identifier>)\b)?
        captures:
          1:
            patterns:
              - include: 'source.cs#type'
          # '2': ?<identifier> is a sub-expression. It's final value is not considered.
          # '3': ?<name-and-type-args> is a sub-expression. It's final value is not considered.
          # '4': ?<type-args> is a sub-expression. It's final value is not considered.
          # '5': ?<tuple> is a sub-expression. It's final value is not considered.
          6: { name: entity.name.variable.local.cs }

  finally-clause:
    name: 'meta.statement.finally.razor'
    begin: '(?:^|(?<=}))\s*(finally)\b\s*?(?=[\n\{])'
    beginCaptures:
      1: { name: 'keyword.control.try.finally.cs' }
    patterns:
      - include: '#csharp-code-block'
      - include: '#razor-codeblock-body'
    end: '(?<=})|(?<=;)|(?=^\s*\})'

  # ----------  Misc C# ------------

  await-prefix:
    name: 'keyword.other.await.cs'
    match: '(await)\s+'

  csharp-code-block:
    name: 'meta.structure.razor.csharp.codeblock'
    begin: '(\{)'
    beginCaptures:
      1: { name: 'punctuation.curlybrace.open.cs' }
    patterns:
      - include: '#razor-codeblock-body'
    end: '(\})'
    endCaptures:
      1: { name: 'punctuation.curlybrace.close.cs' }

  csharp-condition:
    begin: '(\()'
    beginCaptures:
      1: { name: 'punctuation.parenthesis.open.cs' }
    patterns:
      - include: 'source.cs#local-variable-declaration'
      - include: 'source.cs#expression'
      - include: 'source.cs#punctuation-comma'
      - include: 'source.cs#punctuation-semicolon'
    end: '(\))'
    endCaptures:
      1: { name: 'punctuation.parenthesis.close.cs' }
