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
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
HighlightWordTest
nazwę .Dodaj szablon elementu Klasyfikator edytora do projektu. Aby uzyskać więcej informacji, zobacz Tworzenie rozszerzenia za pomocą szablonu elementu edytora.
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
Dodaj plik klasy i nadaj mu nazwę HighlightWordTag.
Dodaj następujące odwołania:
Microsoft.VisualStudio.CoreUtility
Microsoft.VisualStudio.Text.Data
Microsoft.VisualStudio.Text.Logic
Microsoft.VisualStudio.Text.UI
Microsoft.VisualStudio.Text.UI.Wpf
System.componentmodel.composition
Presentation.Core
Presentation.Framework
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;
Utwórz klasę dziedziczą z TextMarkerTag klasy i nadaj jej
HighlightWordTag
nazwę .internal class HighlightWordTag : TextMarkerTag { }
Utwórz drugą klasę, która dziedziczy z MarkerFormatDefinitionklasy , i nadaj jej
HighlightWordFormatDefinition
nazwę . Aby użyć tej definicji formatu dla tagu, należy wyeksportować go z następującymi atrybutami:NameAttribute: tagi używają tego do odwołowania się do tego formatu
UserVisibleAttribute: powoduje to wyświetlenie formatu w interfejsie użytkownika
[Export(typeof(EditorFormatDefinition))] [Name("MarkerFormatDefinition/HighlightWordFormatDefinition")] [UserVisible(true)] internal class HighlightWordFormatDefinition : MarkerFormatDefinition { }
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; }
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
Utwórz klasę, która implementuje ITagger<T> typ
HighlightWordTag
, i nadaj muHighlightWordTagger
nazwę .internal class HighlightWordTagger : ITagger<HighlightWordTag> { }
Dodaj następujące pola prywatne i właściwości do klasy:
Element ITextView, który odpowiada bieżącemu widokowi tekstowemu.
Element ITextBuffer, który odpowiada buforowi tekstowemu, który podsytuje widok tekstu.
Element ITextSearchService, który służy do znajdowania tekstu.
Element ITextStructureNavigator, który zawiera metody nawigowania w obrębie zakresu tekstu.
Element NormalizedSnapshotSpanCollection, który zawiera zestaw wyrazów do wyróżnienia.
A SnapshotSpan, który odpowiada bieżącemu słowu.
A SnapshotPoint, który odpowiada bieżącemu położeniu karetki.
Obiekt blokady.
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();
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; }
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); }
Należy również dodać
TagsChanged
zdarzenie wywoływane przez metodę update.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łujeSynchronousUpdate
metodę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)); }
Element
SynchronousUpdate
wykonuje synchroniczną aktualizację właściwościWordSpans
iCurrentWord
i wywołujeTagsChanged
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))); } }
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
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 { }
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; }
Zaimplementuj metodę CreateTagger , aby zwrócić wystąpienie
HighlightWordTagger
klasy .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
Stwórz rozwiązanie.
Po uruchomieniu tego projektu w debugerze zostanie uruchomione drugie wystąpienie programu Visual Studio.
Utwórz plik tekstowy i wpisz tekst, w którym wyrazy są powtarzane, na przykład "hello hello".
Umieść kursor w jednym z wystąpień "hello". Każde wystąpienie powinno być wyróżnione kolorem niebieskim.