İzlenecek yol: Metni vurgulama

Yönetilen Genişletilebilirlik Çerçevesi (MEF) bileşen parçaları oluşturarak düzenleyiciye farklı görsel efektler ekleyebilirsiniz. Bu izlenecek yol, geçerli sözcüğün bir metin dosyasındaki her geçtiği yeri vurgulama işlemini gösterir. Metin dosyasında bir sözcük birden çok kez oluşuyorsa ve şapka işaretini tek bir oluşumda konumlandırıyorsanız, her oluşum vurgulanır.

MEF projesi oluşturma

  1. C# VSIX projesi oluşturun. (Yeni Proje iletişim kutusu, Visual C# / Genişletilebilirlik'i ve ardından VSIX Projesi'ni seçin.) Çözümü HighlightWordTestolarak adlandırın.

  2. Projeye bir Düzenleyici Sınıflandırıcısı öğesi şablonu ekleyin. Daha fazla bilgi için bkz . Düzenleyici öğesi şablonuyla uzantı oluşturma.

  3. Varolan sınıf dosyalarını silin.

TextMarkerTag Tanımlama

Metni vurgulamanın ilk adımı alt sınıfa TextMarkerTag geçmek ve görünümünü tanımlamaktır.

TextMarkerTag ve MarkerFormatDefinition tanımlamak için

  1. Sınıf dosyasını ekleyin ve HighlightWordTag olarak adlandırnın.

  2. Aşağıdaki başvuruları ekleyin:

    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. Aşağıdaki ad alanlarını içeri aktarın.

    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. öğesinden TextMarkerTag devralan bir sınıf oluşturun ve olarak adlandırın HighlightWordTag.

    internal class HighlightWordTag : TextMarkerTag
    {
    
    }
    
  5. öğesinden MarkerFormatDefinitiondevralan ikinci bir sınıf oluşturun ve olarak adlandırın HighlightWordFormatDefinition. Etiketinizde bu biçim tanımını kullanmak için aşağıdaki özniteliklerle dışarı aktarmanız gerekir:

    
    [Export(typeof(EditorFormatDefinition))]
    [Name("MarkerFormatDefinition/HighlightWordFormatDefinition")]
    [UserVisible(true)]
    internal class HighlightWordFormatDefinition : MarkerFormatDefinition
    {
    
    }
    
  6. HighlightWordFormatDefinition oluşturucusunda görünen adını ve görünümünü tanımlayın. Background özelliği dolgu rengini tanımlarken Foreground özelliği kenarlık rengini tanımlar.

    public HighlightWordFormatDefinition()
    {
        this.BackgroundColor = Colors.LightBlue;
        this.ForegroundColor = Colors.DarkBlue;
        this.DisplayName = "Highlight Word";
        this.ZOrder = 5;
    }
    
  7. HighlightWordTag oluşturucusunda, oluşturduğunuz biçim tanımının adını geçirin.

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

ITagger uygulama

Sonraki adım arabirimini uygulamaktır ITagger<T> . Bu arabirim, belirli bir metin arabelleğine, metin vurgulama sağlayan etiketlere ve diğer görsel efektlere atar.

Bir etiketleme uygulamak için

  1. türünde uygulayan ITagger<T> bir sınıf oluşturun ve olarak adlandırınHighlightWordTagger.HighlightWordTag

    internal class HighlightWordTagger : ITagger<HighlightWordTag>
    {
    
    }
    
  2. Sınıfına aşağıdaki özel alanları ve özellikleri ekleyin:

    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. Daha önce listelenen özellikleri başlatan ve ile PositionChanged olay işleyicileri ekleyen LayoutChanged bir oluşturucu ekleyin.

    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. Olay işleyicilerinin her ikisi de yöntemini çağırır 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. Güncelleştirme yöntemi tarafından çağrılan bir TagsChanged olay da eklemeniz gerekir.

    public event EventHandler<SnapshotSpanEventArgs> TagsChanged;
    
  6. yöntemi, UpdateAtCaretPosition() metin arabelleğindeki imlecin konumlandırıldığı sözcükle aynı olan her sözcüğü bulur ve sözcüğün SnapshotSpan oluşumlarına karşılık gelen nesnelerin listesini oluşturur. Ardından, olayı tetikleyen TagsChanged öğesini çağırırSynchronousUpdate.

    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. ve SynchronousUpdate CurrentWord özelliklerinde WordSpans zaman uyumlu bir güncelleştirme gerçekleştirir ve olayı tetiklerTagsChanged.

    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. yöntemini uygulamanız GetTags gerekir. Bu yöntem bir nesne koleksiyonu SnapshotSpan alır ve etiket yayılmalarının bir numaralandırması döndürür.

    C# dilinde bu yöntemi, etiketlerin gecikmeli değerlendirmesini (yani yalnızca tek tek öğelere erişildiğinde kümenin değerlendirilmesini) sağlayan bir verim yineleyicisi olarak uygulayın. Visual Basic'te etiketleri bir listeye ekleyin ve listeyi döndürün.

    Burada yöntemi, mavi arka TagSpan<T> plan sağlayan "mavi" TextMarkerTagolan bir nesne döndürür.

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

Tagger sağlayıcısı oluşturma

Etiketleyicinizi oluşturmak için bir IViewTaggerProvideruygulamanız gerekir. Bu sınıf bir MEF bileşeni bölümüdür, bu nedenle bu uzantının tanınması için doğru öznitelikleri ayarlamanız gerekir.

Not

MEF hakkında daha fazla bilgi için bkz . Yönetilen Genişletilebilirlik Çerçevesi (MEF).

Bir etiketleme sağlayıcısı oluşturmak için

  1. uygulayan adlı HighlightWordTaggerProvider bir sınıf oluşturun ve bunu bir "text" ve bir TagTypeAttribute öğesiyle dışarı ContentTypeAttribute aktarınTextMarkerTag.IViewTaggerProvider

    [Export(typeof(IViewTaggerProvider))]
    [ContentType("text")]
    [TagType(typeof(TextMarkerTag))]
    internal class HighlightWordTaggerProvider : IViewTaggerProvider
    { }
    
  2. Tagger örneği için ve ITextStructureNavigatorSelectorServiceolmak üzere iki düzenleyici hizmetini ITextSearchService içeri aktarmanız gerekir.

    [Import]
    internal ITextSearchService TextSearchService { get; set; }
    
    [Import]
    internal ITextStructureNavigatorSelectorService TextStructureNavigatorSelector { get; set; }
    
    
  3. bir örneğini CreateTagger HighlightWordTaggerdöndürmek için yöntemini uygulayın.

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

Kodu derleme ve test etme

Bu kodu test etmek için HighlightWordTest çözümünü derleyin ve deneysel örnekte çalıştırın.

HighlightWordTest çözümünü derlemek ve test etmek için

  1. Çözümü oluşturun.

  2. Bu projeyi hata ayıklayıcıda çalıştırdığınızda, Visual Studio'nun ikinci bir örneği başlatılır.

  3. Bir metin dosyası oluşturun ve sözcüklerin yinelendiği bir metin yazın; örneğin, "merhaba merhaba merhaba".

  4. İmleci "hello" örneğinden birine getirin. Her oluşum mavi renkle vurgulanmalıdır.