Walkthrough: Adding Outlining to a Managed Babel Language Service

[This documentation is for preview only, and is subject to change in later releases. Blank topics are included as placeholders.]

This walkthrough shows how to add outlining to a basic Managed Babel language service. It builds on the basic language service that is created in Walkthrough: Using Managed Babel. Because the MPLex and MPPG tools emit C# code, this walkthrough has example code only in C#.

Note Note

This walkthrough shows how to extend Managed Babel classes by adding code to those classes rather than by subclassing them. It also adds tokens to the lexer.lex and parser.y files.

The first step in supporting outlining in your language is to determine the tokens that define the start and end of an outlined section. For example, in C# the #region and #endregion preprocessor directives can be used to define sections that can be expanded or collapsed. In this walkthrough, the token that defines the start of an outlined section is [, and the token that defines the end of an outlined section is ]. The region that is defined in this manner can contain anything that a block of code that is delimited by braces (the { and } characters) can contain.

To add region tokens to the lexer.lex and parser.y files

  1. Open the YourManagedPackage project that you created in Walkthrough: Using Managed Babel.

  2. In the Generated folder, open the lexer.lex file.

  3. You must add the instructions for defining a token in the rules section of the lexer.lex file. The rules section starts with %% and ends with %%. Find the line that defines the period (search for return (int)'.';). After that line, add the following lines:

    \[{ return (int)'['; }
    \]{ return (int)']'; }
  4. Save and close lexer.lex.

  5. Now you must add the hidden region markers to the grammar that is defined in the parser.y file. That is, you must define what can appear before and after a region, and also what can be contained in the region. For this walkthrough, the region that is delimited by the [ and ] tokens is defined in the same manner as the Block, which is delimited by { and }.

  6. Open parser.y. In the rules section of the file, define the OpenHide and CloseHide symbols to resemble the OpenBlock and CloseBlock symbols. Find the section that resembles the following one:

        : '{'                       { /*  */ }
        : '}'                       { /*  */ }

    Add OpenHide and CloseHide symbols in the same way, as follows:

        : '['                       { /*  */ }
        : ']'                       { /*  */ }
  7. Next you must specify the symbols that can appear between the OpenHide and CloseHide symbols. Find the rule that defines Block. It should have a rule that resembles the following one:

        : OpenBlock CloseBlock      { Match(@1, @2); }
        | OpenBlock BlockContent1 CloseBlock
                                    { Match(@1, @3); }
        | OpenBlock BlockContent1 error 
                                    { CallHdlr("missing '}'", @3); }
        | OpenBlock error CloseBlock
                                    { Match(@1, @3); }
  8. Add a Hide symbol that resembles the Block symbol. This rule will enable the same content to occur inside a hidden region as inside a Block, and will match the OpenHide with the CloseHide and throw an error if the hidden region is not closed correctly.

        : OpenHide CloseHide      { Match(@1, @2); }
        | OpenHide BlockContent1 CloseHide
                                    { Match(@1, @3); }
        | OpenHide BlockContent1 error 
                                    { CallHdlr("missing '}'", @3); }
        | OpenHide error CloseHide
                                    { Match(@1, @3); }
  9. Now add the Hide symbol as a part of the Declaration_rule. This will enable the hidden region to occur before or after any other block of code. Change this rule to resemble the following one:

        : Class1 Type IDENTIFIER ParenParams Block  
        | Class1 IDENTIFIER ParenParams Block                                                                                              
        | Type IDENTIFIER ParenParams Block                                                                       
        | IDENTIFIER ParenParams Block                                                                       
        | SimpleDeclaration   
        | Hide 
  10. Save and close lexer.lex.

A language service that supports outlining must register as such by using the ProvideLanguageServiceAttribute with the named parameter AutoOutlining set to true.

To register outlining for your language

  1. In the UserSupplied folder of the YourManagedPackage project, open the Package.cs file.

  2. Add the AutoOutlining parameter to the ProvideLanguageService attribute so that it resembles the following:

    [Microsoft.VisualStudio.Shell.ProvideLanguageService(typeof(Babel.LanguageService), Configuration.Name, 0,
            AutoOutlining = true,
            EnableCommenting = true,
            MatchBraces = true,
            ShowMatchingBrace = true)]

You must specify the class, color, and triggers for the new [ and ] tokens. They should be defined in the same manner as the { and } tokens are defined.

To define the outlining tokens

  1. In the UserSupplied folder of the YourManagedPackage project, open the Configuration.cs file.

  2. In the section of the Configuration constructor where the tokens are defined, add the following lines:

    ColorToken((int)'[', TokenType.Delimiter, TokenColor.Comment, TokenTriggers.MatchBraces);
                ColorToken((int)']', TokenType.Delimiter, TokenColor.Comment, TokenTriggers.MatchBraces);

After you register your language service for outlining, you must add some code to the ParseSource method to detect the outlining tokens and construct a TextSpan object that defines the hidden region. The code that is given in the following procedure illustrates the general approach to take. A full language implementation would integrate this procedure into more general parsing procedures.

Important note Important

In this procedure you will edit the Managed Babel source code files. The code is meant only to demonstrate how to construct a hidden region. A full parser implementation would carry out the parse differently.

To detect outline tokens and construct a hidden region

  1. In the Invariant folder, open the LanguageService.cs file.

  2. Add some code to detect the region tokens in the ParseSource method. Because the tokens that define a hidden region should also be matched, the region tokens can be found when the ParseReason is HighlightBraces, MatchBraces, or MemberSelectAndHighlightBraces.

  3. Find the switch statement and the line case ParseReason.MemberSelectAndHighlightBraces. Add the following code to the block. This code constructs a TextSpan, sets ProcessHiddenRegions to true, and adds the hidden region to the list that is kept by the AuthoringSink object.

    if (brace.Length == 2)
         if (req.Sink.HiddenRegions == true)
              string first = source.GetText(brace[0]);
              if (source.GetText(brace[0]).Contains("[") && source.GetText(brace[1]).Contains("]"))
                   //construct a TextSpan of everything between the braces
                   TextSpan hideSpan = new TextSpan();
                   hideSpan.iStartIndex = brace[0].iStartIndex;
                   hideSpan.iStartLine = brace[0].iStartLine;
                   hideSpan.iEndIndex = brace[1].iEndIndex;
                   hideSpan.iEndLine = brace[1].iEndLine;
                   req.Sink.ProcessHiddenRegions = true;
        req.Sink.MatchPair(brace[0], brace[1], 1);
  4. This code should cause an expanded hidden region to be displayed. If you want to be notified when a user changes the state of this region (from expanded to collapsed or vice versa), you can add the following code to the Source.cs file in the Invariant folder:

    public override void OnHiddenRegionChange(IVsHiddenRegion region, HIDDEN_REGION_EVENT evt, int fBufferModifiable)
                Console.WriteLine("got OnHiddenRegionChange");
                base.OnHiddenRegionChange(region, evt, fBufferModifiable);
  5. Save all the files and rebuild the project.

Now that you have implemented outlining in your language service, you can test it by using a code file for your language.

To test outlining in your language service

  1. Build and run the project.

  2. Open a new file in the new instance of Visual Studio and save it as File.ym.

  3. Type something like the following in the File.ym file:

     int newInt;
  4. You should see that outlining appears on the block of text. When you expand or collapse the region, you should get an OnHiddenRegionChange event.