Jaa


Brace matching in a legacy language service

Applies to: yesVisual Studio noVisual Studio for Mac

Note

This article applies to Visual Studio 2017. If you're looking for the latest Visual Studio documentation, see Visual Studio documentation. We recommend upgrading to the latest version of Visual Studio. Download it here

Brace matching helps the developer track language elements that need to occur together, such as parentheses and curly braces. When a developer enters a closing brace, the opening brace is highlighted.

You can match two or three co-occurring elements, called pairs and triples. Triples are sets of three co-occurring elements. For example, in C#, the foreach statement forms a triple: foreach(), {, and }. All three elements are highlighted when the closing brace is typed.

Legacy language services are implemented as part of a VSPackage, but the newer way to implement language service features is to use MEF extensions. To find out more about the new way to implement brace matching, see Walkthrough: Display matching braces.

Note

We recommend that you begin to use the new editor API as soon as possible. This will improve the performance of your language service and let you take advantage of new editor features.

The AuthoringSink class supports both pairs and triples with the MatchPair and MatchTriple methods.

Implementation

The language service needs to identify all matched elements in the language and then locate all matching pairs. This is typically accomplished by implementing IScanner to detect a matched language and then using the ParseSource method to match the elements.

The OnCommand method calls the scanner to tokenize the line and return the token just before the caret. The scanner indicates that a language element pair has been found by setting a token trigger value of TokenTriggers on the current token. The OnCommand method calls the MatchBraces method that in turn calls the BeginParse method with the parse reason value of ParseReason to locate the matching language element. When the matching language element is found, both elements are highlighted.

For a complete description of how typing a brace triggers the brace highlighting, see the Example parse operation section in the article Legacy language service parser and scanner.

Enable support for brace matching

The ProvideLanguageServiceAttribute attribute can set the MatchBraces, MatchBracesAtCaret, and ShowMatchingBrace registry entries that set the corresponding properties of the LanguagePreferences class. Language preference properties can also be set by the user.

Registry entry Property Description
MatchBraces EnableMatchBraces Enables brace matching.
MatchBracesAtCaret EnableMatchBracesAtCaret Enables brace matching as the caret moves.
ShowMatchingBrace EnableShowMatchingBrace Highlights the matching brace.

Match conditional statements

You can match conditional statements, such as if, else if, and else, or #if, #elif, #else, #endif, in the same way as matching delimiters. You can subclass the AuthoringSink class and provide a method that can add text spans as well as delimiters to the internal array of matching elements.

Set the trigger

The following example shows how to detect matching parentheses, curly braces and square braces, and setting the trigger for it in the scanner. The OnCommand method on the Source class detects the trigger and calls the parser to find the matching pair (see the Finding the match section in this article). This example is for illustrative purposes only. It assumes that your scanner contains a method GetNextToken that identifies and returns tokens from a line of text.

using Microsoft.VisualStudio.Package;
using Microsoft.VisualStudio.TextManager.Interop;

namespace TestLanguagePackage
{
    public class TestScanner : IScanner
    {
        private const string braces = "()[]{}";
        private Lexer lex;

        public bool ScanTokenAndProvideInfoAboutIt(TokenInfo tokenInfo,
                                                   ref int state)
        {
            bool foundToken = false;
            string token = lex.GetNextToken();
            if (token != null)
            {
                foundToken = true;
                char firstChar = token[0];
                if (Char.IsPunctuation(firstChar) && token.Length > 0)
                {
                    if (braces.IndexOf(firstChar) != -1)
                    {
                        tokenInfo.Trigger = TokenTriggers.MatchBraces;
                    }
                }
            }
            return foundToken;
        }

Match the braces

Here is a simplified example for matching the language elements { }, ( ), and [ ], and adding their spans to the AuthoringSink object. This approach is not a recommended approach to parsing source code; it is for illustrative purposes only.

using Microsoft.VisualStudio.Package;
using Microsoft.VisualStudio.TextManager.Interop;

namespace TestLanguagePackage
{
    public class Parser
    {
         private IList<TextSpan[]> m_braces;
         public IList<TextSpan[]> Braces
         {
             get { return m_braces; }
         }
         private void AddMatchingBraces(TextSpan braceSpan1, TextSpan braceSpan2)
         {
             if IsMatch(braceSpan1, braceSpan2)
                 m_braces.Add(new TextSpan[] { braceSpan1, braceSpan2 });
         }

         private bool IsMatch(TextSpan braceSpan1, TextSpan braceSpan2)
         {
             //definition for matching here
          }
    }

    public class TestLanguageService : LanguageService
    {
         Parser parser = new Parser();
         Source source = (Source) this.GetSource(req.FileName);

         private AuthoringScope ParseSource(ParseRequest req)
         {
             if (req.Sink.BraceMatching)
             {
                 if (req.Reason==ParseReason.MatchBraces)
                 {
                     foreach (TextSpan[] brace in parser.Braces)
                     {
                         req.Sink.MatchPair(brace[0], brace[1], 1);
                     }
                 }
             }
             return new TestAuthoringScope();
         }
    }
}

See also