Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Injected grammars don't always properly relinquish control once completed. #125

Closed
NTaylorMullen opened this issue Mar 2, 2020 · 8 comments
Labels
info-needed Issue requires more information from poster question

Comments

@NTaylorMullen
Copy link
Member

We've been developing ASP.NET Core Razor's TextMate grammar and have recently ran into an issue that seems to be a root issue with the VSCode TextMate parser (doesn't repro in Visual Studio's TextMate parser). It seems to be that injections don't always properly relinquish control to the previous grammar once completed.

Repro:

  1. Install the C# extension 1.21.13. It should release 2/2/2020; however if it's not yet available you can download the pre-release here: https://github.com/OmniSharp/omnisharp-vscode/releases/tag/v1.21.13
  2. Open VSCode and create a Razor file myfile.razor
  3. Add the following content to myfile.razor:
@functions {
    interface Bar { }
    class Baz : Bar
    {
    }
}
  1. Notice the class Baz miscolored:
    image

In other textmate parsers this works as expected, Visual Studio's:
image


My preliminary investigation

This is being classified through Razor's TextMate grammar (json, yml) and removing this injection ends up resolving the problem. From what I've found it looks like after the interface Bar { } the injection does not relinquish control to the parent rule that was running.

@alexdima
Copy link
Member

alexdima commented Mar 2, 2020

@NTaylorMullen

  1. Can you please reduce the repro to something smaller? Giving the entire C# + Razor grammar as a repro is very difficult to track down, especially if I am not familiar with what's going on or what the intent of those regular expressions is.
  2. Once you have a smaller repro, does it reproduce in TextMate itself? How about SublimeText or Atom? It is equally possible that you are running in a bug in Visual Studio's TextMate parser, especially if this grammar was developed only against Visual Studio.

Last time I investigated something similar, microsoft/vscode#87496, I spent 1 hour and in the end there was no change needed to vscode or vscode-textmate, so before I go ahead and invest 1-2hrs to understand what is going on, can you please invest a bit of time to try to reduce the surface area that I need to debug?

Thank you! ❤️

@NTaylorMullen
Copy link
Member Author

  1. Can you please reduce the repro to something smaller? Giving the entire C# + Razor grammar as a repro is very difficult to track down, especially if I am not familiar with what's going on or what the intent of those regular expressions is.

Sure, I was able to simplify the Razor + C# grammars (put them into a single grammar) to only include the pieces for this repro:

Simplified Repro Grammar (Razor + C#)
{
  "name": "ASP.NET Razor",
  "scopeName": "text.aspnetcorerazor",
  "patterns": [
    {
      "include": "#code-directive"
    }
  ],
  "repository": {
    "code-directive": {
      "begin": "(@)(code)\\s*",
      "beginCaptures": {
        "1": {
          "name": "keyword.control.cshtml.transition"
        },
        "2": {
          "name": "keyword.control.razor.directive.code"
        }
      },
      "patterns": [
        {
          "include": "#directive-codeblock"
        }
      ],
      "end": "(?<=})|\\s"
    },
    "directive-codeblock": {
      "begin": "(\\{)",
      "beginCaptures": {
        "1": {
          "name": "keyword.control.razor.directive.codeblock.open"
        }
      },
      "name": "meta.structure.razor.directive.codeblock",
      "patterns": [
        {
          "include": "#source-cs-light"
        }
      ],
      "end": "(\\})",
      "endCaptures": {
        "1": {
          "name": "keyword.control.razor.directive.codeblock.close"
        }
      }
    },
    "source-cs-light": {
      "patterns": [
        {
          "include": "#storage-modifier"
        },
        {
          "include": "#interface-declaration"
        },
        {
          "include": "#class-declaration"
        }
      ]
    },
    "interface-declaration": {
      "begin": "(?=\\binterface\\b)",
      "end": "(?<=\\})",
      "patterns": [
        {
          "begin": "(?x)\n(interface)\\b\\s+\n(@?[_[:alpha:]][_[:alnum:]]*)",
          "beginCaptures": {
            "1": {
              "name": "keyword.other.interface.cs"
            },
            "2": {
              "name": "entity.name.type.interface.cs"
            }
          },
          "end": "(?=\\{)"
        },
        {
          "begin": "\\{",
          "beginCaptures": {
            "0": {
              "name": "punctuation.curlybrace.open.cs"
            }
          },
          "end": "\\}",
          "endCaptures": {
            "0": {
              "name": "punctuation.curlybrace.close.cs"
            }
          }
        }
      ]
    },
    "class-declaration": {
      "begin": "(?=\\bclass\\b)",
      "end": "(?<=\\})",
      "patterns": [
        {
          "begin": "(?x)\n\\b(class)\\b\\s+\n(@?[_[:alpha:]][_[:alnum:]]*)\\s*",
          "beginCaptures": {
            "1": {
              "name": "keyword.other.class.cs"
            },
            "2": {
              "name": "entity.name.type.class.cs"
            }
          },
          "end": "(?=\\{)"
        },
        {
          "begin": "\\{",
          "beginCaptures": {
            "0": {
              "name": "punctuation.curlybrace.open.cs"
            }
          },
          "end": "\\}",
          "endCaptures": {
            "0": {
              "name": "punctuation.curlybrace.close.cs"
            }
          }
        }
      ]
    },
    "storage-modifier": {
      "name": "storage.modifier.cs",
      "match": "(?<!\\.)\\b(new|public|protected|internal|private|abstract|virtual|override|sealed|static|partial|readonly|volatile|const|extern|async|unsafe|ref)\\b"
    }
  }
}

2. Once you have a smaller repro, does it reproduce in TextMate itself? How about SublimeText or Atom? It is equally possible that you are running in a bug in Visual Studio's TextMate parser, especially if this grammar was developed only against Visual Studio.

Is there an easy way to test TextMate/SublimeText/Atom? I'm unfamiliar with getting TextMate pieces to run in those editors sadly. Also, to note I was developing the grammar for VSCode first and found this issue as part of that. I just happened to try it in VS and saw that it worked after the fact.


Oh and commenting out the injection makes everything work perfectly 😄 .

@alexdima
Copy link
Member

alexdima commented Mar 3, 2020

@NTaylorMullen

Thank you for the reduced repro. But when I try the reduced grammar you have provided, class does get the scope keyword.other.class.cs:

  • test.txt:
@code {
    interface Bar { }
    class Baz : Bar
    {
    }
}

I have executed npm run inspect -- grammar.json test.txt. This is a tool that shows exactly what is going:

...
===========================================
TOKENIZING LINE 3: |    class Baz : Bar|

@@scanNext 0: |    class Baz : Bar\n|
matched rule id: 17 from 4 to 4
  token: |    |
      * text.aspnetcorerazor
      * meta.structure.razor.directive.codeblock
  pushing BeginEndRule#17 @ grammar.json:92 - (?=\bclass\b)

@@scanNext 4: |class Baz : Bar\n|
matched rule id: 18 from 4 to 14
  pushing BeginEndRule#18 @ grammar.json:96 - (?x)
\b(class)\b\s+
(@?[_[:alpha:]][_[:alnum:]]*)\s*
  token: |class|
      * text.aspnetcorerazor
      * meta.structure.razor.directive.codeblock
      * keyword.other.class.cs
  token: | |
      * text.aspnetcorerazor
      * meta.structure.razor.directive.codeblock
  token: |Baz|
      * text.aspnetcorerazor
      * meta.structure.razor.directive.codeblock
      * entity.name.type.class.cs
  token: | |
      * text.aspnetcorerazor
      * meta.structure.razor.directive.codeblock

@@scanNext 14: |: Bar\n|
  no more matches.
  token: |: Bar\n|
      * text.aspnetcorerazor
      * meta.structure.razor.directive.codeblock

@@LINE END RULE STACK CONTAINS 5 RULES:
  * IncludeOnlyRule#1 @ grammar.json:1  -- [1,3] "text.aspnetcorerazor", "text.aspnetcorerazor"
  * BeginEndRule#2 @ grammar.json:10  -- [2,2] "text.aspnetcorerazor", "text.aspnetcorerazor"
  * BeginEndRule#5 @ grammar.json:27  -- [5,1] "text.aspnetcorerazor,meta.structure.razor.directive.codeblock", "text.aspnetcorerazor,meta.structure.razor.directive.codeblock"
  * BeginEndRule#17 @ grammar.json:92  -- [17,5] "text.aspnetcorerazor,meta.structure.razor.directive.codeblock", "text.aspnetcorerazor,meta.structure.razor.directive.codeblock"
  * BeginEndRule#18 @ grammar.json:96  -- [18,4] "text.aspnetcorerazor,meta.structure.razor.directive.codeblock", "text.aspnetcorerazor,meta.structure.razor.directive.codeblock"

...

@alexdima alexdima added the info-needed Issue requires more information from poster label Mar 3, 2020
@NTaylorMullen
Copy link
Member Author

@alexdima my apologies. It was a total fat finger mistake on my part. It looks like I pasted the grammar with the injection already deleted. I just added back the injection here and it repros:
image

Simplified Repro Grammar (Razor + C#)
{
  "name": "ASP.NET Razor",
  "scopeName": "text.aspnetcorerazor",
  "patterns": [
    {
      "include": "#code-directive"
    }
  ],
  "injections": {
    "L:meta.structure.razor.codeblock - (meta.statement | string.quoted | comment), L:meta.structure.razor.directive.codeblock - (meta.statement | string.quoted | comment)": {
      "patterns": [
        {
          "begin": "(\\{)",
          "beginCaptures": {
            "1": {
              "name": "punctuation.curlybrace.open.cs"
            }
          },
          "patterns": [
            {
              "include": "#source-cs-light"
            }
          ],
          "end": "(\\})",
          "endCaptures": {
            "1": {
              "name": "punctuation.curlybrace.close.cs"
            }
          }
        }
      ]
    }
  },
  "repository": {
    "code-directive": {
      "begin": "(@)(code)\\s*",
      "beginCaptures": {
        "1": {
          "name": "keyword.control.cshtml.transition"
        },
        "2": {
          "name": "keyword.control.razor.directive.code"
        }
      },
      "patterns": [
        {
          "include": "#directive-codeblock"
        }
      ],
      "end": "(?<=})|\\s"
    },
    "directive-codeblock": {
      "begin": "(\\{)",
      "beginCaptures": {
        "1": {
          "name": "keyword.control.razor.directive.codeblock.open"
        }
      },
      "name": "meta.structure.razor.directive.codeblock",
      "patterns": [
        {
          "include": "#source-cs-light"
        }
      ],
      "end": "(\\})",
      "endCaptures": {
        "1": {
          "name": "keyword.control.razor.directive.codeblock.close"
        }
      }
    },
    "source-cs-light": {
      "patterns": [
        {
          "include": "#storage-modifier"
        },
        {
          "include": "#interface-declaration"
        },
        {
          "include": "#class-declaration"
        }
      ]
    },
    "interface-declaration": {
      "begin": "(?=\\binterface\\b)",
      "end": "(?<=\\})",
      "patterns": [
        {
          "begin": "(?x)\n(interface)\\b\\s+\n(@?[_[:alpha:]][_[:alnum:]]*)",
          "beginCaptures": {
            "1": {
              "name": "keyword.other.interface.cs"
            },
            "2": {
              "name": "entity.name.type.interface.cs"
            }
          },
          "end": "(?=\\{)"
        },
        {
          "begin": "\\{",
          "beginCaptures": {
            "0": {
              "name": "punctuation.curlybrace.open.cs"
            }
          },
          "end": "\\}",
          "endCaptures": {
            "0": {
              "name": "punctuation.curlybrace.close.cs"
            }
          }
        }
      ]
    },
    "class-declaration": {
      "begin": "(?=\\bclass\\b)",
      "end": "(?<=\\})",
      "patterns": [
        {
          "begin": "(?x)\n\\b(class)\\b\\s+\n(@?[_[:alpha:]][_[:alnum:]]*)\\s*",
          "beginCaptures": {
            "1": {
              "name": "keyword.other.class.cs"
            },
            "2": {
              "name": "entity.name.type.class.cs"
            }
          },
          "end": "(?=\\{)"
        },
        {
          "begin": "\\{",
          "beginCaptures": {
            "0": {
              "name": "punctuation.curlybrace.open.cs"
            }
          },
          "end": "\\}",
          "endCaptures": {
            "0": {
              "name": "punctuation.curlybrace.close.cs"
            }
          }
        }
      ]
    },
    "storage-modifier": {
      "name": "storage.modifier.cs",
      "match": "(?<!\\.)\\b(new|public|protected|internal|private|abstract|virtual|override|sealed|static|partial|readonly|volatile|const|extern|async|unsafe|ref)\\b"
    }
  }
}

Here's the output from running the npm inspect as you suggested (note the public class Bar : Bazs {} doesn't get a rule associated (-1):

npm inspect output

> vscode-textmate@4.4.0 inspect C:\GitHub\vscode-textmate
> node scripts/inspect.js "C:\GitHub\AspNetCore-Tooling\src\Razor\src\Microsoft.AspNetCore.Razor.VSCode.Extension\syntaxes\aspnetcorerazor.tmLanguage.json" "C:\GitHub\AspNetCore-Tooling\src\Razor\test\testapps\ComponentApp\Components\Pages\Index.razor"

LOADING GRAMMAR: C:\GitHub\AspNetCore-Tooling\src\Razor\src\Microsoft.AspNetCore.Razor.VSCode.Extension\syntaxes\aspnetcorerazor.tmLanguage.json


===========================================
TOKENIZING LINE 1: |@code {|

@@scanNext 0: |@code {\n|
matched rule id: 2 from 1 to 7
  token: ||
      * text.aspnetcorerazor
  pushing BeginEndRule#2 @ aspnetcorerazor.tmLanguage.json:35 - (@)(code)\s*
  token: |@|
      * text.aspnetcorerazor
      * keyword.control.cshtml.transition
  token: |code|
      * text.aspnetcorerazor
      * keyword.control.razor.directive.code
  token: | |
      * text.aspnetcorerazor

@@scanNext 7: |{\n|
matched rule id: 5 from 7 to 8
  pushing BeginEndRule#5 @ aspnetcorerazor.tmLanguage.json:52 - (\{)
  token: |{|
      * text.aspnetcorerazor
      * meta.structure.razor.directive.codeblock
      * keyword.control.razor.directive.codeblock.open

@@scanNext 8: |\n|
  scanning for injections
   - 26: (\{)
  no more matches.
  token: |\n|
      * text.aspnetcorerazor
      * meta.structure.razor.directive.codeblock

@@LINE END RULE STACK CONTAINS 3 RULES:
  * IncludeOnlyRule#1 @ aspnetcorerazor.tmLanguage.json:1  -- [1,3] "text.aspnetcorerazor", "text.aspnetcorerazor"
  * BeginEndRule#2 @ aspnetcorerazor.tmLanguage.json:35  -- [2,2] "text.aspnetcorerazor", "text.aspnetcorerazor"
  * BeginEndRule#5 @ aspnetcorerazor.tmLanguage.json:52  -- [5,1] "text.aspnetcorerazor,meta.structure.razor.directive.codeblock", "text.aspnetcorerazor,meta.structure.razor.directive.codeblock"


===========================================
TOKENIZING LINE 2: |    interface Baz { }|

@@scanNext 0: |    interface Baz { }\n|
matched rule id: 10 from 4 to 4
  scanning for injections
   - 26: (\{)
  token: |    |
      * text.aspnetcorerazor
      * meta.structure.razor.directive.codeblock
  pushing BeginEndRule#10 @ aspnetcorerazor.tmLanguage.json:85 - (?=\binterface\b)

@@scanNext 4: |interface Baz { }\n|
matched rule id: 11 from 4 to 17
  scanning for injections
   - 26: (\{)
  pushing BeginEndRule#11 @ aspnetcorerazor.tmLanguage.json:89 - (?x)
(interface)\b\s+
(@?[_[:alpha:]][_[:alnum:]]*)
  token: |interface|
      * text.aspnetcorerazor
      * meta.structure.razor.directive.codeblock
      * keyword.other.interface.cs
  token: | |
      * text.aspnetcorerazor
      * meta.structure.razor.directive.codeblock
  token: |Baz|
      * text.aspnetcorerazor
      * meta.structure.razor.directive.codeblock
      * entity.name.type.interface.cs

@@scanNext 17: | { }\n|
matched rule id: -1 from 18 to 18
  scanning for injections
   - 26: (\{)
  token: | |
      * text.aspnetcorerazor
      * meta.structure.razor.directive.codeblock
  pushing BeginEndRule#26 @ aspnetcorerazor.tmLanguage.json:12 - (\{)
  token: |{|
      * text.aspnetcorerazor
      * meta.structure.razor.directive.codeblock
      * punctuation.curlybrace.open.cs

@@scanNext 19: | }\n|
matched rule id: -1 from 20 to 21
  scanning for injections
   - 26: (\{)
  popping BeginEndRule#26 @ aspnetcorerazor.tmLanguage.json:12 - (\})
  token: | |
      * text.aspnetcorerazor
      * meta.structure.razor.directive.codeblock
  token: |}|
      * text.aspnetcorerazor
      * meta.structure.razor.directive.codeblock
      * punctuation.curlybrace.close.cs

@@scanNext 21: |\n|
  scanning for injections
   - 26: (\{)
  no more matches.
  token: |\n|
      * text.aspnetcorerazor
      * meta.structure.razor.directive.codeblock

@@LINE END RULE STACK CONTAINS 5 RULES:
  * IncludeOnlyRule#1 @ aspnetcorerazor.tmLanguage.json:1  -- [1,3] "text.aspnetcorerazor", "text.aspnetcorerazor"
  * BeginEndRule#2 @ aspnetcorerazor.tmLanguage.json:35  -- [2,2] "text.aspnetcorerazor", "text.aspnetcorerazor"
  * BeginEndRule#5 @ aspnetcorerazor.tmLanguage.json:52  -- [5,1] "text.aspnetcorerazor,meta.structure.razor.directive.codeblock", "text.aspnetcorerazor,meta.structure.razor.directive.codeblock"
  * BeginEndRule#10 @ aspnetcorerazor.tmLanguage.json:85  -- [10,5] "text.aspnetcorerazor,meta.structure.razor.directive.codeblock", "text.aspnetcorerazor,meta.structure.razor.directive.codeblock"
  * BeginEndRule#11 @ aspnetcorerazor.tmLanguage.json:89  -- [11,4] "text.aspnetcorerazor,meta.structure.razor.directive.codeblock", "text.aspnetcorerazor,meta.structure.razor.directive.codeblock"


===========================================
TOKENIZING LINE 3: ||

@@scanNext 0: |\n|
  scanning for injections
   - 26: (\{)
  no more matches.
  token: |\n|
      * text.aspnetcorerazor
      * meta.structure.razor.directive.codeblock
  token: |\n|
      * text.aspnetcorerazor
      * meta.structure.razor.directive.codeblock

@@LINE END RULE STACK CONTAINS 5 RULES:
  * IncludeOnlyRule#1 @ aspnetcorerazor.tmLanguage.json:1  -- [1,3] "text.aspnetcorerazor", "text.aspnetcorerazor"
  * BeginEndRule#2 @ aspnetcorerazor.tmLanguage.json:35  -- [2,2] "text.aspnetcorerazor", "text.aspnetcorerazor"
  * BeginEndRule#5 @ aspnetcorerazor.tmLanguage.json:52  -- [5,1] "text.aspnetcorerazor,meta.structure.razor.directive.codeblock", "text.aspnetcorerazor,meta.structure.razor.directive.codeblock"
  * BeginEndRule#10 @ aspnetcorerazor.tmLanguage.json:85  -- [10,5] "text.aspnetcorerazor,meta.structure.razor.directive.codeblock", "text.aspnetcorerazor,meta.structure.razor.directive.codeblock"
  * BeginEndRule#11 @ aspnetcorerazor.tmLanguage.json:89  -- [11,4] "text.aspnetcorerazor,meta.structure.razor.directive.codeblock", "text.aspnetcorerazor,meta.structure.razor.directive.codeblock"


===========================================
TOKENIZING LINE 4: |    public class Bar : Bazs {}|

@@scanNext 0: |    public class Bar : Bazs {}\n|
matched rule id: -1 from 28 to 28
  scanning for injections
   - 26: (\{)
  token: |    public class Bar : Bazs |
      * text.aspnetcorerazor
      * meta.structure.razor.directive.codeblock
  pushing BeginEndRule#26 @ aspnetcorerazor.tmLanguage.json:12 - (\{)
  token: |{|
      * text.aspnetcorerazor
      * meta.structure.razor.directive.codeblock
      * punctuation.curlybrace.open.cs

@@scanNext 29: |}\n|
matched rule id: -1 from 29 to 30
  scanning for injections
   - 26: (\{)
  popping BeginEndRule#26 @ aspnetcorerazor.tmLanguage.json:12 - (\})
  token: |}|
      * text.aspnetcorerazor
      * meta.structure.razor.directive.codeblock
      * punctuation.curlybrace.close.cs

@@scanNext 30: |\n|
  scanning for injections
   - 26: (\{)
  no more matches.
  token: |\n|
      * text.aspnetcorerazor
      * meta.structure.razor.directive.codeblock

@@LINE END RULE STACK CONTAINS 5 RULES:
  * IncludeOnlyRule#1 @ aspnetcorerazor.tmLanguage.json:1  -- [1,3] "text.aspnetcorerazor", "text.aspnetcorerazor"
  * BeginEndRule#2 @ aspnetcorerazor.tmLanguage.json:35  -- [2,2] "text.aspnetcorerazor", "text.aspnetcorerazor"
  * BeginEndRule#5 @ aspnetcorerazor.tmLanguage.json:52  -- [5,1] "text.aspnetcorerazor,meta.structure.razor.directive.codeblock", "text.aspnetcorerazor,meta.structure.razor.directive.codeblock"
  * BeginEndRule#10 @ aspnetcorerazor.tmLanguage.json:85  -- [10,5] "text.aspnetcorerazor,meta.structure.razor.directive.codeblock", "text.aspnetcorerazor,meta.structure.razor.directive.codeblock"
  * BeginEndRule#11 @ aspnetcorerazor.tmLanguage.json:89  -- [11,4] "text.aspnetcorerazor,meta.structure.razor.directive.codeblock", "text.aspnetcorerazor,meta.structure.razor.directive.codeblock"


===========================================
TOKENIZING LINE 5: |}|

@@scanNext 0: |}\n|
  scanning for injections
   - 26: (\{)
  no more matches.
  token: |}\n|
      * text.aspnetcorerazor
      * meta.structure.razor.directive.codeblock

@@LINE END RULE STACK CONTAINS 5 RULES:
  * IncludeOnlyRule#1 @ aspnetcorerazor.tmLanguage.json:1  -- [1,3] "text.aspnetcorerazor", "text.aspnetcorerazor"
  * BeginEndRule#2 @ aspnetcorerazor.tmLanguage.json:35  -- [2,2] "text.aspnetcorerazor", "text.aspnetcorerazor"
  * BeginEndRule#5 @ aspnetcorerazor.tmLanguage.json:52  -- [5,1] "text.aspnetcorerazor,meta.structure.razor.directive.codeblock", "text.aspnetcorerazor,meta.structure.razor.directive.codeblock"
  * BeginEndRule#10 @ aspnetcorerazor.tmLanguage.json:85  -- [10,5] "text.aspnetcorerazor,meta.structure.razor.directive.codeblock", "text.aspnetcorerazor,meta.structure.razor.directive.codeblock"
  * BeginEndRule#11 @ aspnetcorerazor.tmLanguage.json:89  -- [11,4] "text.aspnetcorerazor,meta.structure.razor.directive.codeblock", "text.aspnetcorerazor,meta.structure.razor.directive.codeblock"

@alexdima
Copy link
Member

alexdima commented Mar 3, 2020

Ok, I have now debugged through it.

The rule "(?x)\n(interface)\\b\\s+\n(@?[_[:alpha:]][_[:alnum:]]*)" misses its end (?=\\{).

The problem is that at offset 18 in the line interface Bar { }, so right before the {, two rules match:

  • the end rule (?=\\{) for "(?x)\n(interface)\\b\\s+\n(@?[_[:alpha:]][_[:alnum:]]*)"
  • the begin rule (\{) for the injection.

Both of them match at the same offset. In this case, the tie is broken via the injection priority. In the way you wrote the injection selector:

L:meta.structure.razor.codeblock - (meta.statement | string.quoted | comment), L:meta.structure.razor.directive.codeblock - (meta.statement | string.quoted | comment)

This means that the injection is placed to the "left" (L:), i.e. kind of before the rules. But it appears that you want in this case for the rule to win, so you need to insert the injection to the "right" (R:):

R:meta.structure.razor.codeblock - (meta.statement | string.quoted | comment), R:meta.structure.razor.directive.codeblock - (meta.statement | string.quoted | comment)

See e.g. https://stackoverflow.com/a/47517414 , http://textmate.1073791.n5.nabble.com/Grammar-injection-Main-grammar-taking-priority-td29990.html

I can only imagine that VS perhaps does not implement L: injection priority correctly.

@NTaylorMullen
Copy link
Member Author

Hmm, i'm intentionally trying to have the injection take priority here so we can hijack the curly brace scope and add additional features.

I was under the assumption that's (?=\\{) and (\{) would never be treated as a tie.

Is it wrong to think that (?=\{ means the "next" token is the curly brace open and (\{ infers the "current" token is the curly brace open? I ask because I would have figured (?= would always be handled before a set of normal captures given the regex explanations of the rules.

@alexdima
Copy link
Member

alexdima commented Mar 4, 2020

@NTaylorMullen I have transformed your grammar to one accepted by TextMate and tried out your sample in TextMate:

{	injections = {
		'L:meta.structure.razor.codeblock - (meta.statement | string.quoted | comment), L:meta.structure.razor.directive.codeblock - (meta.statement | string.quoted | comment)' = {
			patterns = (
				{	begin = '(\{)';
					end = '(\})';
					beginCaptures = { 1 = { name = 'punctuation.curlybrace.open.cs'; }; };
					endCaptures = { 1 = { name = 'punctuation.curlybrace.close.cs'; }; };
					patterns = ( { include = '#source-cs-light'; } );
				},
			);
		};
	};
	patterns = ( { include = '#code-directive'; } );
	repository = {
		class-declaration = {
			begin = '(?=\bclass\b)';
			end = '(?<=\})';
			patterns = (
				{	begin = '(?x)\b(class)\b\s+(@?[_[:alpha:]][_[:alnum:]]*)\s*';
					end = '(?=\{)';
					beginCaptures = {
						1 = { name = 'keyword.other.class.cs'; };
						2 = { name = 'entity.name.type.class.cs'; };
					};
				},
				{	begin = '\{';
					end = '\}';
					beginCaptures = { 0 = { name = 'punctuation.curlybrace.open.cs'; }; };
					endCaptures = { 0 = { name = 'punctuation.curlybrace.close.cs'; }; };
				},
			);
		};
		code-directive = {
			begin = '(@)(code)\s*';
			end = '(?<=})|\s';
			beginCaptures = {
				1 = { name = 'keyword.control.cshtml.transition'; };
				2 = { name = 'keyword.control.razor.directive.code'; };
			};
			patterns = ( { include = '#directive-codeblock'; } );
		};
		directive-codeblock = {
			name = 'meta.structure.razor.directive.codeblock';
			begin = '(\{)';
			end = '(\})';
			beginCaptures = { 1 = { name = 'keyword.control.razor.directive.codeblock.open'; }; };
			endCaptures = { 1 = { name = 'keyword.control.razor.directive.codeblock.close'; }; };
			patterns = ( { include = '#source-cs-light'; } );
		};
		interface-declaration = {
			begin = '(?=\binterface\b)';
			end = '(?<=\})';
			patterns = (
				{	begin = '(?x)(interface)\b\s+(@?[_[:alpha:]][_[:alnum:]]*)';
					end = '(?=\{)';
					beginCaptures = {
						1 = { name = 'keyword.other.interface.cs'; };
						2 = { name = 'entity.name.type.interface.cs'; };
					};
				},
				{	begin = '\{';
					end = '\}';
					beginCaptures = { 0 = { name = 'punctuation.curlybrace.open.cs'; }; };
					endCaptures = { 0 = { name = 'punctuation.curlybrace.close.cs'; }; };
				},
			);
		};
		source-cs-light = {
			patterns = (
				{	include = '#storage-modifier'; },
				{	include = '#interface-declaration'; },
				{	include = '#class-declaration'; },
			);
		};
		storage-modifier = {
			name = 'storage.modifier.cs';
			match = '(?<!\.)\b(new|public|protected|internal|private|abstract|virtual|override|sealed|static|partial|readonly|volatile|const|extern|async|unsafe|ref)\b';
		};
	};
}

When using L: injections:
image

When changing the injection to use R::
image

I therefore believe we are working in the same way as TextMate, which is the goal of this project. Because we pursue grammar compatibility with the TextMate grammars, we will not make any change to diverge from the TextMate interpreter.

@NTaylorMullen
Copy link
Member Author

So looks like VS is the one acting funky then 😄. @alexdima thanks for all your help here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
info-needed Issue requires more information from poster question
Projects
None yet
Development

No branches or pull requests

2 participants