Procédure pas à pas : afficher les accolades correspondantes
Implémentez des fonctionnalités basées sur la langue, telles que les accolades correspondantes en définissant les accolades que vous souhaitez mettre en correspondance, et en ajoutant une balise de marqueur de texte aux accolades correspondantes lorsque le trait est sur l’une des accolades. Vous pouvez définir des accolades dans le contexte d’une langue, définir votre propre extension de nom de fichier et le type de contenu, et appliquer les balises à ce type ou appliquer les balises à un type de contenu existant (par exemple, « text »). La procédure pas à pas suivante montre comment appliquer des balises de correspondance d’accolades au type de contenu « text ».
Créer un projet MEF (Managed Extensibility Framework)
Pour créer un projet MEF
Créez un projet Classifieur d’éditeur. Nommez la solution
BraceMatchingTest
.Ajoutez un modèle d’élément Classifieur d’éditeur au projet. Pour plus d’informations, consultez Créer une extension avec un modèle d’élément d’éditeur.
Supprimez les fichiers de classe existants.
Implémenter un balisage correspondant à l’accolade
Pour obtenir un effet de mise en surbrillance de l’accolades qui ressemble à celui utilisé dans Visual Studio, vous pouvez implémenter un balisage de type TextMarkerTag. Le code suivant montre comment définir le balisage pour les paires d’accolades à n’importe quel niveau d’imbrication. Dans cet exemple, les paires d’accolades de [] et {} sont définies dans le constructeur du balisage, mais dans une implémentation de langage complète, les paires d’accolades pertinentes sont définies dans la spécification du langage.
Pour implémenter un balisage correspondant à l’accolade
Ajoutez un fichier de classe et nommez-le BraceMatching.
Importez les espaces de noms suivants.
Définissez une classe
BraceMatchingTagger
qui hérite du ITagger<T> type TextMarkerTag.Ajoutez des propriétés pour la vue de texte, la mémoire tampon source, le point de instantané actuel, ainsi qu’un ensemble de paires d’accolades.
Dans le constructeur du balisage, définissez les propriétés et abonnez-vous aux événements PositionChanged de modification d’affichage et LayoutChanged. Dans cet exemple, à des fins d’illustration, les paires correspondantes sont également définies dans le constructeur.
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; }
Dans le cadre de l’implémentation ITagger<T> , déclarez un événement TagsChanged.
Les gestionnaires d’événements mettent à jour la position d’insertion actuelle de la
CurrentChar
propriété et déclenchent l’événement 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))); }
Implémentez la GetTags méthode pour faire correspondre les accolades lorsque le caractère actuel est une accolade ouverte ou lorsque le caractère précédent est une accolade proche, comme dans Visual Studio. Lorsque la correspondance est trouvée, cette méthode instancie deux balises, une pour l’accolade ouverte et une pour l’accolades fermées.
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")); } } }
Les méthodes privées suivantes recherchent l’accolade correspondante à n’importe quel niveau d’imbrication. La première méthode recherche le caractère de fermeture qui correspond au caractère ouvert :
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; }
La méthode d’assistance suivante recherche le caractère ouvert qui correspond à un caractère de fermeture :
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; }
Implémenter un fournisseur de balisage correspondant à l’accolade
Outre l’implémentation d’un taggeur, vous devez également implémenter et exporter un fournisseur de balisage. Dans ce cas, le type de contenu du fournisseur est « text ». Par conséquent, la correspondance entre accolades s’affiche dans tous les types de fichiers texte, mais une implémentation complète applique uniquement la correspondance d’accolades à un type de contenu spécifique.
Pour implémenter un fournisseur de balisage correspondant à l’accolade
Déclarez un fournisseur de balisage qui hérite de IViewTaggerProvider, nommez-le BraceMatchingTaggerProvider, puis exportez-le avec un ContentTypeAttribute « texte » et un TagTypeAttribute de TextMarkerTag.
Implémentez la CreateTagger méthode pour instancier un BraceMatchingTagger.
Générer et tester le code
Pour tester ce code, générez la solution BraceMatchingTest et exécutez-la dans l’instance expérimentale.
Pour générer et tester la solution BraceMatchingTest
Générez la solution.
Lorsque vous exécutez ce projet dans le débogueur, une deuxième instance de Visual Studio est démarrée.
Créez un fichier texte et tapez du texte incluant des accolades correspondantes.
hello { goodbye} {} {hello}
Lorsque vous positionnez le caret avant une accolade ouverte, cette accolade et l’accolades correspondantes doivent être mises en surbrillance. Lorsque vous positionnez le curseur juste après l’accolade de fermeture, cette accolade et l’accolade ouverte correspondante doivent être mises en surbrillance.