Bagikan melalui


Panduan: Sorot teks

Anda dapat menambahkan efek visual yang berbeda ke editor dengan membuat komponen Managed Extensibility Framework (MEF). Panduan ini memperlihatkan cara menyoroti setiap kemunculan kata saat ini dalam file teks. Jika kata terjadi lebih dari satu kali dalam file teks, dan Anda memosisikan tanda sisipan dalam satu kemunculan, setiap kemunculan disorot.

Membuat proyek MEF

  1. Buat proyek C# VSIX. (Dalam Dialog Proyek Baru, pilih Visual C# / Ekstensibilitas, lalu Proyek VSIX.) Beri nama solusi HighlightWordTest.

  2. Tambahkan templat item Pengklasifikasi Editor ke proyek. Untuk informasi selengkapnya, lihat Membuat ekstensi dengan templat item editor.

  3. Hapus file kelas yang ada.

Menentukan TextMarkerTag

Langkah pertama dalam menyoroti teks adalah dengan subkelas TextMarkerTag dan menentukan tampilannya.

Untuk menentukan TextMarkerTag dan MarkerFormatDefinition

  1. Tambahkan file kelas dan beri nama HighlightWordTag.

  2. Tambahkan referensi berikut:

    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. Presentasi.Core

    8. Presentation.Framework

  3. Impor namespace berikut.

    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. Buat kelas yang mewarisi dan TextMarkerTag beri nama HighlightWordTag.

    internal class HighlightWordTag : TextMarkerTag
    {
    
    }
    
  5. Buat kelas kedua yang mewarisi dari MarkerFormatDefinition, dan beri nama HighlightWordFormatDefinition. Untuk menggunakan definisi format ini untuk tag Anda, Anda harus mengekspornya dengan atribut berikut:

    
    [Export(typeof(EditorFormatDefinition))]
    [Name("MarkerFormatDefinition/HighlightWordFormatDefinition")]
    [UserVisible(true)]
    internal class HighlightWordFormatDefinition : MarkerFormatDefinition
    {
    
    }
    
  6. Dalam konstruktor untuk HighlightWordFormatDefinition, tentukan nama tampilan dan tampilannya. Properti Latar Belakang menentukan warna isian, sementara properti Latar Depan menentukan warna batas.

    public HighlightWordFormatDefinition()
    {
        this.BackgroundColor = Colors.LightBlue;
        this.ForegroundColor = Colors.DarkBlue;
        this.DisplayName = "Highlight Word";
        this.ZOrder = 5;
    }
    
  7. Di konstruktor untuk HighlightWordTag, berikan nama definisi format yang Anda buat.

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

Menerapkan ITagger

Langkah selanjutnya adalah mengimplementasikan ITagger<T> antarmuka. Antarmuka ini menetapkan, ke buffer teks tertentu, tag yang menyediakan penyorotan teks dan efek visual lainnya.

Untuk mengimplementasikan tagger

  1. Buat kelas yang mengimplementasikan ITagger<T> jenis HighlightWordTag, dan beri nama HighlightWordTagger.

    internal class HighlightWordTagger : ITagger<HighlightWordTag>
    {
    
    }
    
  2. Tambahkan bidang dan properti privat berikut ke kelas :

    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. Tambahkan konstruktor yang menginisialisasi properti yang tercantum sebelumnya dan menambahkan LayoutChanged dan PositionChanged penanganan aktivitas.

    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. Penanganan aktivitas keduanya memanggil UpdateAtCaretPosition metode .

    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. Anda juga harus menambahkan TagsChanged peristiwa yang dipanggil oleh metode pembaruan.

    public event EventHandler<SnapshotSpanEventArgs> TagsChanged;
    
  6. Metode ini UpdateAtCaretPosition() menemukan setiap kata dalam buffer teks yang identik dengan kata di mana kursor diposisikan dan membuat daftar SnapshotSpan objek yang sesuai dengan kemunculan kata. Kemudian memanggil SynchronousUpdate, yang meningkatkan TagsChanged peristiwa.

    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. melakukan SynchronousUpdate pembaruan sinkron pada WordSpans properti dan CurrentWord , dan menaikkan TagsChanged peristiwa.

    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. Anda harus menerapkan metode .GetTags Metode ini mengambil kumpulan SnapshotSpan objek dan mengembalikan enumerasi rentang tag.

    Dalam C#, terapkan metode ini sebagai iterator hasil, yang memungkinkan evaluasi malas (yaitu, evaluasi dari set hanya ketika item individual diakses) dari tag. Di Visual Basic, tambahkan tag ke daftar dan kembalikan daftar.

    Di sini metode mengembalikan TagSpan<T> objek yang memiliki "biru" TextMarkerTag, yang menyediakan latar belakang biru.

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

Membuat penyedia Tagger

Untuk membuat tagger, Anda harus menerapkan IViewTaggerProvider. Kelas ini adalah bagian komponen MEF, jadi Anda harus mengatur atribut yang benar sehingga ekstensi ini dikenali.

Catatan

Untuk mendapatkan informasi selengkapnya tentang MEF, lihat Managed Extensibility Framework (MEF).

Untuk membuat penyedia tagger

  1. Buat kelas bernama HighlightWordTaggerProvider yang mengimplementasikan IViewTaggerProvider, dan ekspor dengan ContentTypeAttribute "teks" dan TagTypeAttribute dari TextMarkerTag.

    [Export(typeof(IViewTaggerProvider))]
    [ContentType("text")]
    [TagType(typeof(TextMarkerTag))]
    internal class HighlightWordTaggerProvider : IViewTaggerProvider
    { }
    
  2. Anda harus mengimpor dua layanan editor, ITextSearchService dan ITextStructureNavigatorSelectorService, untuk membuat instans tagger.

    [Import]
    internal ITextSearchService TextSearchService { get; set; }
    
    [Import]
    internal ITextStructureNavigatorSelectorService TextStructureNavigatorSelector { get; set; }
    
    
  3. Terapkan CreateTagger metode untuk mengembalikan instans 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>;
    }
    

Membangun dan menguji kode

Untuk menguji kode ini, buat solusi HighlightWordTest dan jalankan dalam instans eksperimental.

Untuk membangun dan menguji solusi HighlightWordTest

  1. Bangun solusinya.

  2. Saat Anda menjalankan proyek ini di debugger, instans kedua Visual Studio dimulai.

  3. Buat file teks dan ketik beberapa teks di mana kata-kata diulang, misalnya, "halo halo halo".

  4. Posisikan kursor di salah satu kemunculan "halo". Setiap kemunculan harus disorot dengan warna biru.