Udostępnij za pośrednictwem


Przewodnik: wyróżnianie tekstu

Możesz dodać różne efekty wizualne do edytora, tworząc części składnika Managed Extensibility Framework (MEF). W tym przewodniku pokazano, jak wyróżnić każde wystąpienie bieżącego wyrazu w pliku tekstowym. Jeśli wyraz występuje więcej niż jeden raz w pliku tekstowym i umieszczasz daszek w jednym wystąpieniu, każde wystąpienie jest wyróżnione.

Tworzenie projektu MEF

  1. Utwórz projekt VSIX w języku C#. (W Okno dialogowe Nowy projekt , wybierz pozycję Visual C# / Rozszerzalność, a następnie projekt VSIX. Nadaj rozwiązaniu HighlightWordTestnazwę .

  2. Dodaj szablon elementu Klasyfikator edytora do projektu. Aby uzyskać więcej informacji, zobacz Tworzenie rozszerzenia za pomocą szablonu elementu edytora.

  3. Usuń istniejące pliki klas.

Definiowanie elementu TextMarkerTag

Pierwszym krokiem wyróżniania tekstu jest podklasa TextMarkerTag i zdefiniowanie jego wyglądu.

Aby zdefiniować element TextMarkerTag i znacznikFormatDefinition

  1. Dodaj plik klasy i nadaj mu nazwę HighlightWordTag.

  2. Dodaj następujące odwołania:

    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. Zaimportuj następujące przestrzenie nazw.

    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. Utwórz klasę dziedziczą z TextMarkerTag klasy i nadaj jej HighlightWordTagnazwę .

    internal class HighlightWordTag : TextMarkerTag
    {
    
    }
    
  5. Utwórz drugą klasę, która dziedziczy z MarkerFormatDefinitionklasy , i nadaj jej HighlightWordFormatDefinitionnazwę . Aby użyć tej definicji formatu dla tagu, należy wyeksportować go z następującymi atrybutami:

    
    [Export(typeof(EditorFormatDefinition))]
    [Name("MarkerFormatDefinition/HighlightWordFormatDefinition")]
    [UserVisible(true)]
    internal class HighlightWordFormatDefinition : MarkerFormatDefinition
    {
    
    }
    
  6. W konstruktorze obiektu HighlightWordFormatDefinition zdefiniuj jego nazwę wyświetlaną i wygląd. Właściwość Tło definiuje kolor wypełnienia, a właściwość Pierwszego planu definiuje kolor obramowania.

    public HighlightWordFormatDefinition()
    {
        this.BackgroundColor = Colors.LightBlue;
        this.ForegroundColor = Colors.DarkBlue;
        this.DisplayName = "Highlight Word";
        this.ZOrder = 5;
    }
    
  7. W konstruktorze obiektu HighlightWordTag przekaż nazwę utworzonej definicji formatu.

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

Implementowanie elementu ITagger

Następnym krokiem jest zaimplementowanie interfejsu ITagger<T> . Ten interfejs przypisuje do danego buforu tekstowego tagi, które zapewniają wyróżnianie tekstu i inne efekty wizualne.

Aby zaimplementować sztylet

  1. Utwórz klasę, która implementuje ITagger<T> typ HighlightWordTag, i nadaj mu HighlightWordTaggernazwę .

    internal class HighlightWordTagger : ITagger<HighlightWordTag>
    {
    
    }
    
  2. Dodaj następujące pola prywatne i właściwości do klasy:

    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. Dodaj konstruktor, który inicjuje wymienione wcześniej właściwości i dodaje LayoutChanged programy obsługi zdarzeń i PositionChanged .

    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. Programy obsługi zdarzeń wywołają metodę UpdateAtCaretPosition .

    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. Należy również dodać TagsChanged zdarzenie wywoływane przez metodę update.

    public event EventHandler<SnapshotSpanEventArgs> TagsChanged;
    
  6. Metoda UpdateAtCaretPosition() znajduje każde słowo w buforze tekstowym, które jest identyczne ze słowem, w którym kursor jest umieszczony i tworzy listę SnapshotSpan obiektów odpowiadających wystąpieniom słowa. Następnie wywołuje SynchronousUpdatemetodę TagsChanged , która wywołuje zdarzenie.

    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. Element SynchronousUpdate wykonuje synchroniczną aktualizację właściwości WordSpans i CurrentWord i wywołuje TagsChanged zdarzenie.

    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. Należy zaimplementować metodę GetTags . Ta metoda pobiera kolekcję SnapshotSpan obiektów i zwraca wyliczenie zakresów tagów.

    W języku C#zaimplementuj tę metodę jako iterator wydajności, który umożliwia leniwą ocenę (czyli ocenę zestawu tylko wtedy, gdy dostęp do poszczególnych elementów jest uzyskiwany) tagów. W języku Visual Basic dodaj tagi do listy i zwróć listę.

    Tutaj metoda zwraca TagSpan<T> obiekt, który ma "niebieski" TextMarkerTag, który zapewnia niebieskie tło.

    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());
        }
    }
    

Tworzenie dostawcy narzędzia Tagger

Aby utworzyć sztylet, należy zaimplementować element IViewTaggerProvider. Ta klasa jest częścią składnika MEF, więc należy ustawić poprawne atrybuty, aby to rozszerzenie było rozpoznawane.

Uwaga

Aby uzyskać więcej informacji na temat mef, zobacz Managed Extensibility Framework (MEF).

Aby utworzyć dostawcę narzędzia tagger

  1. Utwórz klasę o nazwie HighlightWordTaggerProvider , która implementuje IViewTaggerProviderelement , i wyeksportuj ją z wartością ContentTypeAttribute "text" i elementem TagTypeAttribute TextMarkerTag.

    [Export(typeof(IViewTaggerProvider))]
    [ContentType("text")]
    [TagType(typeof(TextMarkerTag))]
    internal class HighlightWordTaggerProvider : IViewTaggerProvider
    { }
    
  2. Aby utworzyć wystąpienie sztyletu, należy zaimportować dwie usługi edytora , ITextSearchService i ITextStructureNavigatorSelectorService.

    [Import]
    internal ITextSearchService TextSearchService { get; set; }
    
    [Import]
    internal ITextStructureNavigatorSelectorService TextStructureNavigatorSelector { get; set; }
    
    
  3. Zaimplementuj metodę CreateTagger , aby zwrócić wystąpienie HighlightWordTaggerklasy .

    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>;
    }
    

Kompilowanie i testowanie kodu

Aby przetestować ten kod, skompiluj rozwiązanie HighlightWordTest i uruchom je w wystąpieniu eksperymentalnym.

Aby skompilować i przetestować rozwiązanie HighlightWordTest

  1. Stwórz rozwiązanie.

  2. Po uruchomieniu tego projektu w debugerze zostanie uruchomione drugie wystąpienie programu Visual Studio.

  3. Utwórz plik tekstowy i wpisz tekst, w którym wyrazy są powtarzane, na przykład "hello hello".

  4. Umieść kursor w jednym z wystąpień "hello". Każde wystąpienie powinno być wyróżnione kolorem niebieskim.