通过定义要匹配的大括号,并将文本标记标记添加到匹配大括号(当插入符号位于其中一个大括号上)来实现基于语言的功能,例如大括号匹配。 可以在语言上下文中定义大括号,定义自己的文件扩展名和内容类型,并将标记应用于该类型或将标记应用于现有内容类型(如“text”)。 以下演练演示如何将大括号匹配标记应用于“text”内容类型。
创建托管扩展性框架 (MEF) 项目
创建 MEF 项目
- 创建编辑器分类器项目。 将解决方案命名为 - BraceMatchingTest。
 
- 向项目添加编辑器分类器项模板。 有关详细信息,请参阅使用编辑器项模板创建扩展。 
- 删除现有的类文件。 
实现大括号匹配标记器
若要获取类似于 Visual Studio 中使用的大括号突出显示效果,可以实现类型 TextMarkerTag标记器。 以下代码演示如何在任何嵌套级别定义大括号对的标记器。 在此示例中,[] {} 的大括号对在标记器构造函数中定义,但在完整的语言实现中,相关大括号将在语言规范中定义。
实现大括号匹配标记器
- 添加类文件并将其命名为 BraceMatching。 
- 导入以下命名空间。 - 
- 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;
 - Imports System.ComponentModel.Composition
Imports Microsoft.VisualStudio.Text
Imports Microsoft.VisualStudio.Text.Editor
Imports Microsoft.VisualStudio.Text.Tagging
Imports Microsoft.VisualStudio.Utilities
 
 
- 定义继承自ITagger<T>类型的TextMarkerTag类- BraceMatchingTagger。
 - 
- internal class BraceMatchingTagger : ITagger<TextMarkerTag>
 - Friend Class BraceMatchingTagger
    Implements ITagger(Of TextMarkerTag)
 
 
- 为文本视图、源缓冲区、当前快照点以及一组大括号对添加属性。 - 
- ITextView View { get; set; }
ITextBuffer SourceBuffer { get; set; }
SnapshotPoint? CurrentChar { get; set; }
private Dictionary<char, char> m_braceList;
 - 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)
 
 
- 在标记器构造函数中,设置属性并订阅视图更改事件 PositionChanged 和 LayoutChanged。 在此示例中,为了说明目的,匹配对也在构造函数中定义。 - 
- 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;
}
 - 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
 
 
- 作为实现的 ITagger<T> 一部分,声明 TagsChanged 事件。 - 
- public event EventHandler<SnapshotSpanEventArgs> TagsChanged;
 - Public Event TagsChanged As EventHandler(Of SnapshotSpanEventArgs) _
    Implements ITagger(Of TextMarkerTag).TagsChanged
 
 
- 事件处理程序更新属性的 - CurrentChar当前插入点位置并引发 TagsChanged 事件。
 - 
- 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)));
}
 - 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
 
 
- 实现在 GetTags 当前字符为大括号或上一个字符是一个关闭大括号时(如 Visual Studio 中所示)时匹配大括号的方法。 找到匹配项后,此方法实例化两个标记,一个用于打开大括号,一个用于关闭大括号。 - 
- 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"));
        }
    }
}
 - 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
 
 
- 以下私有方法可在任何嵌套级别查找匹配大括号。 第一种方法查找与打开字符匹配的关闭字符: - 
- 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;
}
 - 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 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;
}
 - 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
 
 
实现大括号匹配标记器提供程序
除了实现标记器之外,还必须实现和导出标记器提供程序。 在这种情况下,提供程序的内容类型为“text”。 因此,大括号匹配将显示在所有类型的文本文件中,但更完整的实现将大括号匹配仅适用于特定内容类型。
实现大括号匹配标记器提供程序
- 声明一个继承自IViewTaggerProvider的标记器提供程序,将其命名为 BraceMatchingTaggerProvider,并使用ContentTypeAttribute“text”和 a of.TagTypeAttribute TextMarkerTag  - 
- [Export(typeof(IViewTaggerProvider))]
[ContentType("text")]
[TagType(typeof(TextMarkerTag))]
internal class BraceMatchingTaggerProvider : IViewTaggerProvider
 - <Export(GetType(IViewTaggerProvider))> _
<ContentType("text")> _
<TagType(GetType(TextMarkerTag))> _
Friend Class BraceMatchingTaggerProvider
    Implements IViewTaggerProvider
 
 
- CreateTagger实现实例化 BraceMatchingTagger 的方法。 - 
- 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>;
}
 - 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
 
 
生成并测试代码
若要测试此代码,请生成 BraceMatchingTest 解决方案并在实验实例中运行它。
生成和测试 BraceMatchingTest 解决方案
- 生成解决方案。 
- 在调试器中运行此项目时,将启动 Visual Studio 的第二个实例。 
- 创建文本文件并键入包含匹配大括号的某些文本。 - hello {
goodbye}
{}
{hello}
 
- 在打开大括号之前放置插入点时,应突出显示该大括号和匹配的关闭大括号。 将光标放在紧大括号之后时,应突出显示该大括号和匹配的打开大括号。 
相关内容