Partager via


Procédure pas à pas : mettre en surbrillance le texte

Vous pouvez ajouter différents effets visuels à l’éditeur en créant des composants MEF (Managed Extensibility Framework). Cette procédure pas à pas montre comment mettre en évidence chaque occurrence du mot actuel dans un fichier texte. Si un mot se produit plus d’une fois dans un fichier texte et que vous positionnez le point d’insertion dans une occurrence, chaque occurrence est mise en surbrillance.

Créer un projet MEF

  1. Créez un projet VSIX C#. (Dans le Boîte de dialogue Nouveau projet , sélectionnez Visual C# / Extensibilité, puis VSIX Project.) Nommez la solution HighlightWordTest.

  2. 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.

  3. Supprimez les fichiers de classe existants.

Définir un TextMarkerTag

La première étape de la mise en surbrillance du texte consiste à sous-classe TextMarkerTag et à définir son apparence.

Pour définir un TextMarkerTag et un MarkerFormatDefinition

  1. Ajoutez un fichier de classe et nommez-le HighlightWordTag.

  2. Ajoutez les références suivantes :

    1. Microsoft.VisualStudio.CoreUtility

    2. Microsoft.VisualStudio.Text.Data

    3. Microsoft.VisualStudio.Text.Logic

    4. Microsoft.VisualStudio.Text.UI

    5. Microsoft.VisualStudio.Text.UI.Wpf

    6. System.ComponentModel.Composition

    7. Presentation.Core

    8. Presentation.Framework

  3. Importez les espaces de noms suivants.

    using System;
    using System.Collections.Generic;
    using System.ComponentModel.Composition;
    using System.Linq;
    using System.Threading;
    using Microsoft.VisualStudio.Text;
    using Microsoft.VisualStudio.Text.Classification;
    using Microsoft.VisualStudio.Text.Editor;
    using Microsoft.VisualStudio.Text.Operations;
    using Microsoft.VisualStudio.Text.Tagging;
    using Microsoft.VisualStudio.Utilities;
    using System.Windows.Media;
    
  4. Créez une classe qui hérite et TextMarkerTag nommez-la HighlightWordTag.

    internal class HighlightWordTag : TextMarkerTag
    {
    
    }
    
  5. Créez une deuxième classe qui hérite de MarkerFormatDefinition, et nommez-la HighlightWordFormatDefinition. Pour utiliser cette définition de format pour votre balise, vous devez l’exporter avec les attributs suivants :

    
    [Export(typeof(EditorFormatDefinition))]
    [Name("MarkerFormatDefinition/HighlightWordFormatDefinition")]
    [UserVisible(true)]
    internal class HighlightWordFormatDefinition : MarkerFormatDefinition
    {
    
    }
    
  6. Dans le constructeur pour HighlightWordFormatDefinition, définissez son nom d’affichage et son apparence. La propriété Background définit la couleur de remplissage, tandis que la propriété de premier plan définit la couleur de bordure.

    public HighlightWordFormatDefinition()
    {
        this.BackgroundColor = Colors.LightBlue;
        this.ForegroundColor = Colors.DarkBlue;
        this.DisplayName = "Highlight Word";
        this.ZOrder = 5;
    }
    
  7. Dans le constructeur de HighlightWordTag, transmettez le nom de la définition de format que vous avez créée.

    public HighlightWordTag() : base("MarkerFormatDefinition/HighlightWordFormatDefinition") { }
    

Implémenter un ITagger

L’étape suivante consiste à implémenter l’interface ITagger<T> . Cette interface affecte, à une mémoire tampon de texte donnée, des balises qui fournissent la mise en surbrillance du texte et d’autres effets visuels.

Pour implémenter un taggeur

  1. Créez une classe qui implémente ITagger<T> un type HighlightWordTaget nommez-la HighlightWordTagger.

    internal class HighlightWordTagger : ITagger<HighlightWordTag>
    {
    
    }
    
  2. Ajoutez les champs et propriétés privés suivants à la classe :

    ITextView View { get; set; }
    ITextBuffer SourceBuffer { get; set; }
    ITextSearchService TextSearchService { get; set; }
    ITextStructureNavigator TextStructureNavigator { get; set; }
    NormalizedSnapshotSpanCollection WordSpans { get; set; }
    SnapshotSpan? CurrentWord { get; set; }
    SnapshotPoint RequestedPoint { get; set; }
    object updateLock = new object();
    
    
  3. Ajoutez un constructeur qui initialise les propriétés répertoriées précédemment et ajoute et PositionChanged des LayoutChanged gestionnaires d’événements.

    public HighlightWordTagger(ITextView view, ITextBuffer sourceBuffer, ITextSearchService textSearchService,
    ITextStructureNavigator textStructureNavigator)
    {
        this.View = view;
        this.SourceBuffer = sourceBuffer;
        this.TextSearchService = textSearchService;
        this.TextStructureNavigator = textStructureNavigator;
        this.WordSpans = new NormalizedSnapshotSpanCollection();
        this.CurrentWord = null;
        this.View.Caret.PositionChanged += CaretPositionChanged;
        this.View.LayoutChanged += ViewLayoutChanged;
    }
    
    
  4. Les gestionnaires d’événements appellent la UpdateAtCaretPosition méthode.

    void ViewLayoutChanged(object sender, TextViewLayoutChangedEventArgs e)
    {
        // If a new snapshot wasn't generated, then skip this layout 
        if (e.NewSnapshot != e.OldSnapshot)
        {
            UpdateAtCaretPosition(View.Caret.Position);
        }
    }
    
    void CaretPositionChanged(object sender, CaretPositionChangedEventArgs e)
    {
        UpdateAtCaretPosition(e.NewPosition);
    }
    
  5. Vous devez également ajouter un TagsChanged événement appelé par la méthode de mise à jour.

    public event EventHandler<SnapshotSpanEventArgs> TagsChanged;
    
  6. La UpdateAtCaretPosition() méthode recherche chaque mot dans la mémoire tampon de texte identique au mot où le curseur est positionné et construit une liste d’objets SnapshotSpan qui correspondent aux occurrences du mot. Il appelle SynchronousUpdateensuite , ce qui déclenche l’événement TagsChanged .

    void UpdateAtCaretPosition(CaretPosition caretPosition)
    {
        SnapshotPoint? point = caretPosition.Point.GetPoint(SourceBuffer, caretPosition.Affinity);
    
        if (!point.HasValue)
            return;
    
        // If the new caret position is still within the current word (and on the same snapshot), we don't need to check it 
        if (CurrentWord.HasValue
            && CurrentWord.Value.Snapshot == View.TextSnapshot
            && point.Value >= CurrentWord.Value.Start
            && point.Value <= CurrentWord.Value.End)
        {
            return;
        }
    
        RequestedPoint = point.Value;
        UpdateWordAdornments();
    }
    
    void UpdateWordAdornments()
    {
        SnapshotPoint currentRequest = RequestedPoint;
        List<SnapshotSpan> wordSpans = new List<SnapshotSpan>();
        //Find all words in the buffer like the one the caret is on
        TextExtent word = TextStructureNavigator.GetExtentOfWord(currentRequest);
        bool foundWord = true;
        //If we've selected something not worth highlighting, we might have missed a "word" by a little bit
        if (!WordExtentIsValid(currentRequest, word))
        {
            //Before we retry, make sure it is worthwhile 
            if (word.Span.Start != currentRequest
                 || currentRequest == currentRequest.GetContainingLine().Start
                 || char.IsWhiteSpace((currentRequest - 1).GetChar()))
            {
                foundWord = false;
            }
            else
            {
                // Try again, one character previous.  
                //If the caret is at the end of a word, pick up the word.
                word = TextStructureNavigator.GetExtentOfWord(currentRequest - 1);
    
                //If the word still isn't valid, we're done 
                if (!WordExtentIsValid(currentRequest, word))
                    foundWord = false;
            }
        }
    
        if (!foundWord)
        {
            //If we couldn't find a word, clear out the existing markers
            SynchronousUpdate(currentRequest, new NormalizedSnapshotSpanCollection(), null);
            return;
        }
    
        SnapshotSpan currentWord = word.Span;
        //If this is the current word, and the caret moved within a word, we're done. 
        if (CurrentWord.HasValue && currentWord == CurrentWord)
            return;
    
        //Find the new spans
        FindData findData = new FindData(currentWord.GetText(), currentWord.Snapshot);
        findData.FindOptions = FindOptions.WholeWord | FindOptions.MatchCase;
    
        wordSpans.AddRange(TextSearchService.FindAll(findData));
    
        //If another change hasn't happened, do a real update 
        if (currentRequest == RequestedPoint)
            SynchronousUpdate(currentRequest, new NormalizedSnapshotSpanCollection(wordSpans), currentWord);
    }
    static bool WordExtentIsValid(SnapshotPoint currentRequest, TextExtent word)
    {
        return word.IsSignificant
            && currentRequest.Snapshot.GetText(word.Span).Any(c => char.IsLetter(c));
    }
    
    
  7. Effectue SynchronousUpdate une mise à jour synchrone sur les WordSpans propriétés et CurrentWord déclenche l’événement TagsChanged .

    void SynchronousUpdate(SnapshotPoint currentRequest, NormalizedSnapshotSpanCollection newSpans, SnapshotSpan? newCurrentWord)
    {
        lock (updateLock)
        {
            if (currentRequest != RequestedPoint)
                return;
    
            WordSpans = newSpans;
            CurrentWord = newCurrentWord;
    
            var tempEvent = TagsChanged;
            if (tempEvent != null)
                tempEvent(this, new SnapshotSpanEventArgs(new SnapshotSpan(SourceBuffer.CurrentSnapshot, 0, SourceBuffer.CurrentSnapshot.Length)));
        }
    }
    
  8. Vous devez implémenter la GetTags méthode. Cette méthode prend une collection d’objets SnapshotSpan et retourne une énumération d’étendues d’étiquettes.

    En C#, implémentez cette méthode en tant qu’itérateur de rendement, ce qui active l’évaluation différée (autrement dit, l’évaluation de l’ensemble uniquement lorsque des éléments individuels sont accessibles) des balises. Dans Visual Basic, ajoutez les balises à une liste et retournez la liste.

    Ici, la méthode retourne un TagSpan<T> objet qui a un « bleu », TextMarkerTagqui fournit un arrière-plan bleu.

    public IEnumerable<ITagSpan<HighlightWordTag>> GetTags(NormalizedSnapshotSpanCollection spans)
    {
        if (CurrentWord == null)
            yield break;
    
        // Hold on to a "snapshot" of the word spans and current word, so that we maintain the same
        // collection throughout
        SnapshotSpan currentWord = CurrentWord.Value;
        NormalizedSnapshotSpanCollection wordSpans = WordSpans;
    
        if (spans.Count == 0 || wordSpans.Count == 0)
            yield break;
    
        // If the requested snapshot isn't the same as the one our words are on, translate our spans to the expected snapshot 
        if (spans[0].Snapshot != wordSpans[0].Snapshot)
        {
            wordSpans = new NormalizedSnapshotSpanCollection(
                wordSpans.Select(span => span.TranslateTo(spans[0].Snapshot, SpanTrackingMode.EdgeExclusive)));
    
            currentWord = currentWord.TranslateTo(spans[0].Snapshot, SpanTrackingMode.EdgeExclusive);
        }
    
        // First, yield back the word the cursor is under (if it overlaps) 
        // Note that we'll yield back the same word again in the wordspans collection; 
        // the duplication here is expected. 
        if (spans.OverlapsWith(new NormalizedSnapshotSpanCollection(currentWord)))
            yield return new TagSpan<HighlightWordTag>(currentWord, new HighlightWordTag());
    
        // Second, yield all the other words in the file 
        foreach (SnapshotSpan span in NormalizedSnapshotSpanCollection.Overlap(spans, wordSpans))
        {
            yield return new TagSpan<HighlightWordTag>(span, new HighlightWordTag());
        }
    }
    

Créer un fournisseur Tagger

Pour créer votre taggeur, vous devez implémenter un IViewTaggerProvider. Cette classe est une partie de composant MEF. Vous devez donc définir les attributs appropriés afin que cette extension soit reconnue.

Remarque

Pour plus d’informations sur MEF, consultez Managed Extensibility Framework (MEF).

Pour créer un fournisseur de balisage

  1. Créez une classe nommée HighlightWordTaggerProvider qui implémente IViewTaggerProvider, puis exportez-la avec un ContentTypeAttribute « text » et un TagTypeAttribute de TextMarkerTag.

    [Export(typeof(IViewTaggerProvider))]
    [ContentType("text")]
    [TagType(typeof(TextMarkerTag))]
    internal class HighlightWordTaggerProvider : IViewTaggerProvider
    { }
    
  2. Vous devez importer deux services d’éditeur, le ITextSearchService et le ITextStructureNavigatorSelectorService, pour instancier le balisage.

    [Import]
    internal ITextSearchService TextSearchService { get; set; }
    
    [Import]
    internal ITextStructureNavigatorSelectorService TextStructureNavigatorSelector { get; set; }
    
    
  3. Implémentez la CreateTagger méthode pour retourner une instance de HighlightWordTagger.

    public ITagger<T> CreateTagger<T>(ITextView textView, ITextBuffer buffer) where T : ITag
    {
        //provide highlighting only on the top buffer 
        if (textView.TextBuffer != buffer)
            return null;
    
        ITextStructureNavigator textStructureNavigator =
            TextStructureNavigatorSelector.GetTextStructureNavigator(buffer);
    
        return new HighlightWordTagger(textView, buffer, TextSearchService, textStructureNavigator) as ITagger<T>;
    }
    

Générer et tester le code

Pour tester ce code, générez la solution HighlightWordTest et exécutez-la dans l’instance expérimentale.

Pour générer et tester la solution HighlightWordTest

  1. Générez la solution.

  2. Lorsque vous exécutez ce projet dans le débogueur, une deuxième instance de Visual Studio est démarrée.

  3. Créez un fichier texte et tapez du texte dans lequel les mots sont répétés, par exemple « hello hello hello ».

  4. Positionnez le curseur dans l’une des occurrences de « hello ». Chaque occurrence doit être mise en surbrillance en bleu.