procedura dettagliata: Visualizzazione delle parentesi graffe corrispondenti
È possibile distribuire le funzionalità a livello di linguaggio come corrispondenza di parentesi graffe definendo le parentesi graffe che si desidera accoppiare e quindi aggiungere un tag del marcatore di testo alle parentesi graffe corrispondenti quando il cursore è posizionato su una delle parentesi graffe. È possibile definire le parentesi graffe nel contesto di un linguaggio, oppure è possibile definire diventi proprietaria l'estensione di file e il tipo di contenuto e applicare i tag solo a quel tipo, oppure è possibile applicare i tag a un tipo di contenuto esistente (ad esempio “testo„). Di seguito viene illustrato procedura dettagliata viene illustrato come applicare i tag di corrispondenza di parentesi graffe il tipo di contenuto “text„.
Prerequisiti
Per completare questa procedura dettagliata, è necessario installare Visual Studio 2010 SDK.
Nota
per ulteriori informazioni su Visual Studio SDK, vedere Cenni preliminari sull'estensione di Visual Studio.Per ulteriori informazioni su come scaricare Visual Studio SDK, vedere Centro per sviluppatori di estensibilità di Visual Studio il sito Web MSDN.
Creare un progetto gestito Framework (MEF) di estensibilità
Per creare un progetto MEF
Creare un progetto di classificatore editor. Assegnare alla soluzione BraceMatchingTest.
Aprire il file source.extension.vsixmanifest nell'editor del manifesto VSIX.
Assicurarsi che la direzione di Content contenga un tipo di contenuto componente MEF e che Path è impostato su BraceMatchingTest.dll.
salvare e chiudere Source.extension.vsixmanifest.
Eliminare i file di classe esistenti.
implementare un Tagger di corrispondenza di parentesi graffe
Per ottenere evidenziazione della parentesi graffa effettuare analogo a quello utilizzato in Visual Studio, è possibile implementare un tagger di tipo TextMarkerTag. Nel codice seguente viene illustrato come definire il tagger per le coppie di parentesi graffe a qualsiasi livello di annidamento. In questo esempio, le coppie di parentesi graffe []. [] e {} essere definito nel costruttore di tagger, ma in un'implementazione completa del linguaggio le coppie importanti della parentesi graffa verrebbe definito nella specifica di linguaggio.
Per implementare un tagger di corrispondenza di parentesi graffe
Aggiungere il file di classe e denominarlo BraceMatching.
Includere gli spazi dei nomi seguenti.
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;
Definire una classe BraceMatchingTagger che eredita da ITagger di tipo TextMarkerTag.
Friend Class BraceMatchingTagger Implements ITagger(Of TextMarkerTag)
internal class BraceMatchingTagger : ITagger<TextMarkerTag>
Aggiungere le proprietà per la visualizzazione di testo, il buffer di origine e il punto corrente dello snapshot e un set di coppie di parentesi graffe.
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;
Nel costruttore di tagger, impostare le proprietà e per sottoscrivere gli eventi di modifica di visualizzazione PositionChanged e LayoutChanged. In questo esempio, possa provocare, le coppie corrispondenti sono definite nel costruttore.
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; }
Come parte dell'implementazione di ITagger , dichiarare un evento TagsChanged.
Public Event TagsChanged As EventHandler(Of SnapshotSpanEventArgs) _ Implements ITagger(Of TextMarkerTag).TagsChanged
public event EventHandler<SnapshotSpanEventArgs> TagsChanged;
I gestori eventi aggiornano la posizione corrente del cursore della proprietà di CurrentChar e generano l'evento 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))); }
Implementare il metodo di GetTags per la corrispondenza delle parentesi graffe generalmente quando il carattere corrente è una parentesi graffa aperta o quando il carattere precedente è una parentesi graffa di chiusura, come in Visual Studio. Quando la corrispondenza trovata, questo metodo crea un'istanza di due tag, uno per la parentesi graffa aperta e uno per la parentesi graffa di chiusura.
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")); } } }
I seguenti metodi privati cerca la parentesi graffa corrispondente a qualsiasi livello di annidamento. Il primo metodo esamina il carattere da che corrisponde a un carattere aperto:
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; }
Il metodo di supporto seguente viene trovato il carattere aperto che corrisponde a un carattere da:
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; }
implementare un provider di Tagger di corrispondenza di parentesi graffe
Oltre all'implementazione di un tagger, è necessario distribuire ed esportare un provider di tagger. In questo caso, il tipo di contenuto del provider è “testo„. Ciò significa che l'utente può essere visualizzate due potenzialmente diversi stili dell'interfaccia utente quando si utilizzano il controllo del codice sorgente: interfaccia utente di Visual Studio elencate e l'interfaccia utente del plug-in controllo del codice sorgente elencati.
Per implementare un provider di tagger di corrispondenza di parentesi graffe
Dichiarare un provider di tagger che eredita da IViewTaggerProvider, il nome BraceMatchingTaggerProvider e lo l'esportazione con ContentTypeAttribute di “testo„ e TagTypeAttribute di TextMarkerTag.
<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
Implementare il metodo di CreateTagger``1 per creare un'istanza di un 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>; }
Compilazione e test del codice
Per testare questo codice, compilare la soluzione di BraceMatchingTest ed eseguirla nell'istanza sperimentale.
Per compilare e testare la soluzione di BraceMatchingTest
Compilare la soluzione.
Quando si esegue il progetto nel debugger, una seconda istanza di Visual Studio viene creata un'istanza.
Creare un file di testo e digitare un testo che comprende parentesi graffe corrispondenti.
hello { goodbye} {} {hello}
Quando si passa il cursore prima di una parentesi graffa aperta, sia che rinforzino e la parentesi graffa di chiusura corrispondente devono essere evidenziati. Quando si passa il cursore subito dopo la parentesi graffa di chiusura, sia che rinforzino e la parentesi graffa aperta corrispondente deve essere evidenziata.
Vedere anche
Attività
procedura dettagliata: Collegare un tipo di contenuto a un'estensione di file