演练:显示匹配的大括号

可以实现与语言为基础的功能 (如匹配通过定义要匹配的大括号,然后添加的大括号文本标记一直到含有匹配的大括号,在插入符号位于某个大括号时。 可以定义大括号在语言中,也可以定义拥有文件扩展名与内容类型和标记应用于该类型,也可以将标记应用于现有内容类型 (例如 “text”)。 下面的演练演示如何应用括号匹配的标记应用到 “text”内容类型。

系统必备

若要完成本演练,您必须安装 Visual Studio 2010 SDK。

备注

有关 Visual Studio SDK 的更多信息,请参见 扩展 Visual Studio 概述。若要查找有关中所列如何下载 Visual Studio SDK,请 Visual Studio Extensibility Developer Center 参见 MSDN 网站上。

创建一个 managed extensibility framework 项目 (MEF)

创建 MEF 项目

  1. 创建一个编辑分类器项目。 将解决方案命名为 BraceMatchingTest。

  2. 打开在 VSIX 清单编辑器中的 source.extension.vsixmanifest 文件。

  3. 确保 Content 归为包含一个 MEF 组件内容类型,而 Path 设置为 BraceMatchingTest.dll。

  4. 保存并关闭的 Source.extension.vsixmanifest。

  5. 删除现有类文件。

实现大括号匹配的标记

获取显示类似于个在 Visual Studio 的影响的大括号,可以实现类型 TextMarkerTag标记。 下面的代码演示如何定义大括号的标记对嵌套级别。 在此示例中,该大括号对 []。 [],并且, {} 在标记构造函数定义,但是,在该相关大括号对的进行完整语言实施者应定义在语言规范 (cls)。

实现大括号匹配的标记

  1. 将类文件并将其命名为 BraceMatching。

  2. 导入下列命名空间。

    Imports System.ComponentModel.Composition
    Imports Microsoft.VisualStudio.Text
    Imports Microsoft.VisualStudio.Text.Editor
    Imports Microsoft.VisualStudio.Text.Tagging
    Imports Microsoft.VisualStudio.Utilities
    
    using System;
    using System.Linq;
    using System.Collections.Generic;
    using System.ComponentModel.Composition;
    using Microsoft.VisualStudio.Text;
    using Microsoft.VisualStudio.Text.Editor;
    using Microsoft.VisualStudio.Text.Tagging;
    using Microsoft.VisualStudio.Utilities;
    
  3. 定义从类型继承 TextMarkerTagITagger 的类 BraceMatchingTagger

    Friend Class BraceMatchingTagger
        Implements ITagger(Of TextMarkerTag)
    
    internal class BraceMatchingTagger : ITagger<TextMarkerTag>
    
  4. 添加文本视图中,源缓冲区属性,并且,当前快照点,并设置大括号对。

    Private _View As ITextView
    Private Property View() As ITextView
        Get 
            Return _View
        End Get 
        Set(ByVal value As ITextView)
            _View = value
        End Set 
    End Property 
    Private _SourceBuffer As ITextBuffer
    Private Property SourceBuffer() As ITextBuffer
        Get 
            Return _SourceBuffer
        End Get 
        Set(ByVal value As ITextBuffer)
            _SourceBuffer = value
        End Set 
    End Property 
    Private _CurrentChar As System.Nullable(Of SnapshotPoint)
    Private Property CurrentChar() As System.Nullable(Of SnapshotPoint)
        Get 
            Return _CurrentChar
        End Get 
        Set(ByVal value As System.Nullable(Of SnapshotPoint))
            _CurrentChar = value
        End Set 
    End Property 
    Private m_braceList As Dictionary(Of Char, Char)
    
    ITextView View { get; set; }
    ITextBuffer SourceBuffer { get; set; }
    SnapshotPoint? CurrentChar { get; set; }
    private Dictionary<char, char> m_braceList;
    
  5. 在标记构造函数中,设置属性并订阅视图更改事件 PositionChangedLayoutChanged。 在此示例中,用于说明目的,构造函数相对还定义。

    Friend Sub New(ByVal view As ITextView, ByVal sourceBuffer As ITextBuffer)
        'here the keys are the open braces, and the values are the close braces
        m_braceList = New Dictionary(Of Char, Char)()
        m_braceList.Add("{"c, "}"c)
        m_braceList.Add("["c, "]"c)
        m_braceList.Add("("c, ")"c)
        Me.View = view
        Me.SourceBuffer = sourceBuffer
        Me.CurrentChar = Nothing 
    
        AddHandler Me.View.Caret.PositionChanged, AddressOf Me.CaretPositionChanged
        AddHandler Me.View.LayoutChanged, AddressOf Me.ViewLayoutChanged
    End Sub
    
    internal BraceMatchingTagger(ITextView view, ITextBuffer sourceBuffer)
    {
        //here the keys are the open braces, and the values are the close braces
        m_braceList = new Dictionary<char, char>();
        m_braceList.Add('{', '}');
        m_braceList.Add('[', ']');
        m_braceList.Add('(', ')');
        this.View = view;
        this.SourceBuffer = sourceBuffer;
        this.CurrentChar = null;
    
        this.View.Caret.PositionChanged += CaretPositionChanged;
        this.View.LayoutChanged += ViewLayoutChanged;
    }
    
  6. 作为 ITagger 实现一部分,声明 TagsChanged 事件。

    Public Event TagsChanged As EventHandler(Of SnapshotSpanEventArgs) _
        Implements ITagger(Of TextMarkerTag).TagsChanged
    
    public event EventHandler<SnapshotSpanEventArgs> TagsChanged;
    
  7. 事件处理程序更新 CurrentChar 属性的当前脱字号位置并引发 TagsChanged 事件。

    Private Sub ViewLayoutChanged(ByVal sender As Object, ByVal e As TextViewLayoutChangedEventArgs)
        If e.NewSnapshot IsNot e.OldSnapshot Then 
            'make sure that there has really been a change
            UpdateAtCaretPosition(View.Caret.Position)
        End If 
    End Sub 
    
    Private Sub CaretPositionChanged(ByVal sender As Object, ByVal e As CaretPositionChangedEventArgs)
        UpdateAtCaretPosition(e.NewPosition)
    End Sub 
    
    Private Sub UpdateAtCaretPosition(ByVal caretPosition As CaretPosition)
        CurrentChar = caretPosition.Point.GetPoint(SourceBuffer, caretPosition.Affinity)
    
        If Not CurrentChar.HasValue Then 
            Exit Sub 
        End If 
    
        RaiseEvent TagsChanged(Me, New SnapshotSpanEventArgs(New SnapshotSpan(SourceBuffer.CurrentSnapshot, 0, SourceBuffer.CurrentSnapshot.Length)))
    End Sub
    
    void ViewLayoutChanged(object sender, TextViewLayoutChangedEventArgs e)
    {
        if (e.NewSnapshot != e.OldSnapshot) //make sure that there has really been a change
        {
            UpdateAtCaretPosition(View.Caret.Position);
        }
    }
    
    void CaretPositionChanged(object sender, CaretPositionChangedEventArgs e)
    {
        UpdateAtCaretPosition(e.NewPosition);
    }
    void UpdateAtCaretPosition(CaretPosition caretPosition)
    {
        CurrentChar = caretPosition.Point.GetPoint(SourceBuffer, caretPosition.Affinity);
    
        if (!CurrentChar.HasValue)
            return;
    
        var tempEvent = TagsChanged;
        if (tempEvent != null)
            tempEvent(this, new SnapshotSpanEventArgs(new SnapshotSpan(SourceBuffer.CurrentSnapshot, 0,
                SourceBuffer.CurrentSnapshot.Length)));
    }
    
  8. 请执行 GetTags 方法与大括号其中之一,在当前字符是一个左大括号时,或者当字符是一个关闭的大括号时, Visual Studio。 当找到匹配项时,此方法实例化两个标记,其中打开大括号的另一个用于关闭的大括号的。

    Public Function GetTags(ByVal spans As NormalizedSnapshotSpanCollection) As IEnumerable(Of ITagSpan(Of TextMarkerTag)) Implements ITagger(Of Microsoft.VisualStudio.Text.Tagging.TextMarkerTag).GetTags
        If spans.Count = 0 Then 
            'there is no content in the buffer 
            Exit Function 
        End If 
    
        'don't do anything if the current SnapshotPoint is not initialized or at the end of the buffer 
        If Not CurrentChar.HasValue OrElse CurrentChar.Value.Position >= CurrentChar.Value.Snapshot.Length Then 
            Exit Function 
        End If 
    
        'hold on to a snapshot of the current character 
        Dim currentChar__1 As SnapshotPoint = CurrentChar.Value
    
        'if the requested snapshot isn't the same as the one the brace is on, translate our spans to the expected snapshot 
        If spans(0).Snapshot IsNot currentChar__1.Snapshot Then
            currentChar__1 = currentChar__1.TranslateTo(spans(0).Snapshot, PointTrackingMode.Positive)
        End If 
    
        'get the current char and the previous char 
        Dim currentText As Char = currentChar__1.GetChar()
        Dim lastChar As SnapshotPoint = If(CInt(currentChar__1) = 0, currentChar__1, currentChar__1 - 1)
        'if currentChar is 0 (beginning of buffer), don't move it back 
        Dim lastText As Char = lastChar.GetChar()
        Dim pairSpan As New SnapshotSpan()
    
        If m_braceList.ContainsKey(currentText) Then 
            'the key is the open brace 
            Dim closeChar As Char
            m_braceList.TryGetValue(currentText, closeChar)
            If BraceMatchingTagger.FindMatchingCloseChar(currentChar__1, currentText, closeChar, View.TextViewLines.Count, pairSpan) = True Then 
                Exit Function 
            End If 
        ElseIf m_braceList.ContainsValue(lastText) Then 
            'the value is the close brace, which is the *previous* character  
            Dim open = From n In m_braceList _
                Where n.Value.Equals(lastText) _
                Select n.Key
            If BraceMatchingTagger.FindMatchingOpenChar(lastChar, CChar(open.ElementAt(0)), lastText, View.TextViewLines.Count, pairSpan) = True Then 
                Exit Function 
            End If 
        End If 
    End Function
    
    public IEnumerable<ITagSpan<TextMarkerTag>> GetTags(NormalizedSnapshotSpanCollection spans)
    {
        if (spans.Count == 0)   //there is no content in the buffer 
            yield break;
    
        //don't do anything if the current SnapshotPoint is not initialized or at the end of the buffer 
        if (!CurrentChar.HasValue || CurrentChar.Value.Position >= CurrentChar.Value.Snapshot.Length)
            yield break;
    
        //hold on to a snapshot of the current character
        SnapshotPoint currentChar = CurrentChar.Value;
    
        //if the requested snapshot isn't the same as the one the brace is on, translate our spans to the expected snapshot 
        if (spans[0].Snapshot != currentChar.Snapshot)
        {
            currentChar = currentChar.TranslateTo(spans[0].Snapshot, PointTrackingMode.Positive);
        }
    
        //get the current char and the previous char 
        char currentText = currentChar.GetChar();
        SnapshotPoint lastChar = currentChar == 0 ? currentChar : currentChar - 1; //if currentChar is 0 (beginning of buffer), don't move it back 
        char lastText = lastChar.GetChar();
        SnapshotSpan pairSpan = new SnapshotSpan();
    
        if (m_braceList.ContainsKey(currentText))   //the key is the open brace
        {
            char closeChar;
            m_braceList.TryGetValue(currentText, out closeChar);
            if (BraceMatchingTagger.FindMatchingCloseChar(currentChar, currentText, closeChar, View.TextViewLines.Count, out pairSpan) == true)
            {
                yield return new TagSpan<TextMarkerTag>(new SnapshotSpan(currentChar, 1), new TextMarkerTag("blue"));
                yield return new TagSpan<TextMarkerTag>(pairSpan, new TextMarkerTag("blue"));
            }
        }
        else if (m_braceList.ContainsValue(lastText))    //the value is the close brace, which is the *previous* character 
        {
            var open = from n in m_braceList
                       where n.Value.Equals(lastText)
                       select n.Key;
            if (BraceMatchingTagger.FindMatchingOpenChar(lastChar, (char)open.ElementAt<char>(0), lastText, View.TextViewLines.Count, out pairSpan) == true)
            {
                yield return new TagSpan<TextMarkerTag>(new SnapshotSpan(lastChar, 1), new TextMarkerTag("blue"));
                yield return new TagSpan<TextMarkerTag>(pairSpan, new TextMarkerTag("blue"));
            }
        }
    }
    
  9. 以下私有方法查找匹配的大括号在嵌套级别。 第一个方法查找与打开字符的结束字符:

    Private Shared Function FindMatchingCloseChar(ByVal startPoint As SnapshotPoint, ByVal open As Char, ByVal close As Char, ByVal maxLines As Integer, ByRef pairSpan As SnapshotSpan) As Boolean
        pairSpan = New SnapshotSpan(startPoint.Snapshot, 1, 1)
        Dim line As ITextSnapshotLine = startPoint.GetContainingLine()
        Dim lineText As String = line.GetText()
        Dim lineNumber As Integer = line.LineNumber
        Dim offset As Integer = startPoint.Position - line.Start.Position + 1
    
        Dim stopLineNumber As Integer = startPoint.Snapshot.LineCount - 1
        If maxLines > 0 Then
            stopLineNumber = Math.Min(stopLineNumber, lineNumber + maxLines)
        End If 
    
        Dim openCount As Integer = 0
        While True 
            'walk the entire line 
            While offset < line.Length
                Dim currentChar As Char = lineText(offset)
                If currentChar = close Then 
                    'found the close character 
                    If openCount > 0 Then
                        openCount -= 1
                    Else 
                        'found the matching close
                        pairSpan = New SnapshotSpan(startPoint.Snapshot, line.Start + offset, 1)
                        Return True 
                    End If 
                ElseIf currentChar = open Then 
                    ' this is another open
                    openCount += 1
                End If
                offset += 1
            End While 
    
            'move on to the next line 
            If System.Threading.Interlocked.Increment(lineNumber) > stopLineNumber Then 
                Exit While 
            End If
    
            line = line.Snapshot.GetLineFromLineNumber(lineNumber)
            lineText = line.GetText()
            offset = 0
        End While 
    
        Return False 
    End Function
    
    private static bool FindMatchingCloseChar(SnapshotPoint startPoint, char open, char close, int maxLines, out SnapshotSpan pairSpan)
    {
        pairSpan = new SnapshotSpan(startPoint.Snapshot, 1, 1);
        ITextSnapshotLine line = startPoint.GetContainingLine();
        string lineText = line.GetText();
        int lineNumber = line.LineNumber;
        int offset = startPoint.Position - line.Start.Position + 1;
    
        int stopLineNumber = startPoint.Snapshot.LineCount - 1;
        if (maxLines > 0)
            stopLineNumber = Math.Min(stopLineNumber, lineNumber + maxLines);
    
        int openCount = 0;
        while (true)
        {
            //walk the entire line 
            while (offset < line.Length)
            {
                char currentChar = lineText[offset];
                if (currentChar == close) //found the close character
                {
                    if (openCount > 0)
                    {
                        openCount--;
                    }
                    else     //found the matching close
                    {
                        pairSpan = new SnapshotSpan(startPoint.Snapshot, line.Start + offset, 1);
                        return true;
                    }
                }
                else if (currentChar == open) // this is another open
                {
                    openCount++;
                }
                offset++;
            }
    
            //move on to the next line 
            if (++lineNumber > stopLineNumber)
                break;
    
            line = line.Snapshot.GetLineFromLineNumber(lineNumber);
            lineText = line.GetText();
            offset = 0;
        }
    
        return false;
    }
    
  10. 下面的帮助器方法查找与结束字符的打开字符:

    Private Shared Function FindMatchingOpenChar(ByVal startPoint As SnapshotPoint, ByVal open As Char, ByVal close As Char, ByVal maxLines As Integer, ByRef pairSpan As SnapshotSpan) As Boolean
        pairSpan = New SnapshotSpan(startPoint, startPoint)
    
        Dim line As ITextSnapshotLine = startPoint.GetContainingLine()
    
        Dim lineNumber As Integer = line.LineNumber
        Dim offset As Integer = startPoint - line.Start - 1
        'move the offset to the character before this one 
        'if the offset is negative, move to the previous line 
        If offset < 0 Then
            line = line.Snapshot.GetLineFromLineNumber(System.Threading.Interlocked.Decrement(lineNumber))
            offset = line.Length - 1
        End If 
    
        Dim lineText As String = line.GetText()
    
        Dim stopLineNumber As Integer = 0
        If maxLines > 0 Then
            stopLineNumber = Math.Max(stopLineNumber, lineNumber - maxLines)
        End If 
    
        Dim closeCount As Integer = 0
    
        While True 
            ' Walk the entire line 
            While offset >= 0
                Dim currentChar As Char = lineText(offset)
    
                If currentChar = open Then 
                    If closeCount > 0 Then
                        closeCount -= 1
                    Else 
                        ' We've found the open character
                        pairSpan = New SnapshotSpan(line.Start + offset, 1)
                        'we just want the character itself 
                        Return True 
                    End If 
                ElseIf currentChar = close Then
                    closeCount += 1
                End If
                offset -= 1
            End While 
    
            ' Move to the previous line 
            If System.Threading.Interlocked.Decrement(lineNumber) < stopLineNumber Then 
                Exit While 
            End If
    
            line = line.Snapshot.GetLineFromLineNumber(lineNumber)
            lineText = line.GetText()
            offset = line.Length - 1
        End While 
        Return False 
    End Function
    
    private static bool FindMatchingOpenChar(SnapshotPoint startPoint, char open, char close, int maxLines, out SnapshotSpan pairSpan)
    {
        pairSpan = new SnapshotSpan(startPoint, startPoint);
    
        ITextSnapshotLine line = startPoint.GetContainingLine();
    
        int lineNumber = line.LineNumber;
        int offset = startPoint - line.Start - 1; //move the offset to the character before this one 
    
        //if the offset is negative, move to the previous line 
        if (offset < 0)
        {
            line = line.Snapshot.GetLineFromLineNumber(--lineNumber);
            offset = line.Length - 1;
        }
    
        string lineText = line.GetText();
    
        int stopLineNumber = 0;
        if (maxLines > 0)
            stopLineNumber = Math.Max(stopLineNumber, lineNumber - maxLines);
    
        int closeCount = 0;
    
        while (true)
        {
            // Walk the entire line 
            while (offset >= 0)
            {
                char currentChar = lineText[offset];
    
                if (currentChar == open)
                {
                    if (closeCount > 0)
                    {
                        closeCount--;
                    }
                    else // We've found the open character
                    {
                        pairSpan = new SnapshotSpan(line.Start + offset, 1); //we just want the character itself 
                        return true;
                    }
                }
                else if (currentChar == close)
                {
                    closeCount++;
                }
                offset--;
            }
    
            // Move to the previous line 
            if (--lineNumber < stopLineNumber)
                break;
    
            line = line.Snapshot.GetLineFromLineNumber(lineNumber);
            lineText = line.GetText();
            offset = line.Length - 1;
        }
        return false;
    }
    

实现大括号匹配的标记提供程序

除实现标记外,您还必须实现和导出标记提供程序。 在这种情况下,提供的内容类型为 “text”。 这意味着括号匹配将出现在文本文件中的所有类型,但是,一次更全面的实现将应用只匹配到特定内容类型的大括号。

实现大括号匹配的标记提供程序

  1. 声明从 IViewTaggerProvider继承的标记提供程序,将其命名为 BraceMatchingTaggerProvider,并将其导出与 “text” ContentTypeAttributeTextMarkerTagTagTypeAttribute

    <Export(GetType(IViewTaggerProvider))> _
    <ContentType("text")> _
    <TagType(GetType(TextMarkerTag))> _
    Friend Class BraceMatchingTaggerProvider
        Implements IViewTaggerProvider
    
    [Export(typeof(IViewTaggerProvider))]
    [ContentType("text")]
    [TagType(typeof(TextMarkerTag))]
    internal class BraceMatchingTaggerProvider : IViewTaggerProvider
    
  2. 执行 CreateTagger``1 方法实例化 BraceMatchingTagger。

    Public Function CreateTagger(Of T As ITag)(ByVal textView As ITextView, ByVal buffer As ITextBuffer) As ITagger(Of T) Implements IViewTaggerProvider.CreateTagger
        If textView Is Nothing Then 
            Return Nothing 
        End If 
    
        'provide highlighting only on the top-level buffer 
        If textView.TextBuffer IsNot buffer Then 
            Return Nothing 
        End If 
    
        Return TryCast(New BraceMatchingTagger(textView, buffer), ITagger(Of T))
    End Function
    
    public ITagger<T> CreateTagger<T>(ITextView textView, ITextBuffer buffer) where T : ITag
    {
        if (textView == null)
            return null;
    
        //provide highlighting only on the top-level buffer 
        if (textView.TextBuffer != buffer)
            return null;
    
        return new BraceMatchingTagger(textView, buffer) as ITagger<T>;
    }
    

生成并测试代码

若要测试此代码,请生成 BraceMatchingTest 解决方案并运行在的实验实例。

生成和测试 BraceMatchingTest 解决方案

  1. 生成解决方案。

  2. 当您运行在调试器中查看此项目, Visual Studio 的第二个实例进行实例化。

  3. 创建一个文本文件并键入包含匹配的大括号的某些文本。

    hello {
    goodbye}
    
    {}
    
    {hello}
    
  4. 当您确定插入符号,在左大括号,辅助,和的两个预先时应显示匹配的结束大括号。 当确定光标,在 " 关闭 " 大括号,辅助,和的两个后期时应显示匹配的左大括号。

请参见

任务

演练:链接到文件扩展名的内容类型