藉由定義您想要對稱的括號,並在插入號位於其中一個括號時,將文字標記標籤新增至對稱括號,以實作語言型功能,例如括號對稱。 您可以在語言的內容中定義括號、定義您自己的副檔名和內容類型,並將標籤套用至該類型,或將標籤套用至現有的內容類型 (例如「文字」)。 下列逐步解說示範如何將括號對稱標籤套用至「文字」內容類型。
建立 Managed Extensibility Framework (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
定義繼承自類型 TextMarkerTag 之 ITagger<T> 的類別 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
實作括號對稱標籤器提供者
除了實作標籤器之外,您也必須實作和匯出標籤器提供者。 在此情況下,提供者的內容類型為 「文字」。 因此,括號對稱會出現在所有類型的文字檔中,但較完整的實作只會將括號對稱套用至特定內容類型。
實作括號對稱標籤器提供者
宣告繼承自 IViewTaggerProvider 的標籤器提供者,將其命名為 BraceMatchingTaggerProvider,並以 “text” 的 ContentTypeAttribute 和 TextMarkerTag 的 TagTypeAttribute 將其匯出。
[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}
當您將插入號放在左大括號之前,應該反白顯示該大括號和對稱的右大括號。 當您將游標放在右大括號之後,應該反白顯示該大括號和對稱的左大括號。
相關內容