Demonstra Passo a passo: realçar texto
Você pode adicionar efeitos visuais diferentes ao editor criando partes do componente MEF (Managed Extensibility Framework). Este passo a passo mostra como realçar cada ocorrência da palavra atual em um arquivo de texto. Se uma palavra ocorrer mais de uma vez em um arquivo de texto e você posicionar o cursor em uma ocorrência, cada ocorrência será realçada.
Criar um projeto MEF
Crie um projeto C# VSIX. (No Caixa de diálogo Novo Projeto, selecione Visual C# / Extensibilidade e, em seguida, Projeto VSIX.) Nomeie a solução
HighlightWordTest
.Adicione um modelo de item Editor Classificador ao projeto. Para obter mais informações, consulte Criar uma extensão com um modelo de item do editor.
Exclua os arquivos de classe existentes.
Definir um TextMarkerTag
A primeira etapa para realçar o texto é subclassificar TextMarkerTag e definir sua aparência.
Para definir um TextMarkerTag e um MarkerFormatDefinition
Adicione um arquivo de classe e nomeie-o HighlightWordTag.
Adicione as seguintes referências:
Microsoft.VisualStudio.CoreUtility
Microsoft.VisualStudio.Text.Data
Microsoft.VisualStudio.Text.Logic
Microsoft.VisualStudio.Text.UI
Microsoft.VisualStudio.Text.UI.Wpf
System.ComponentModel.Composition
Apresentação.Core
Apresentação.Framework
Importe os namespaces a seguir.
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;
Crie uma classe que herda de TextMarkerTag e nomeie-a
HighlightWordTag
.internal class HighlightWordTag : TextMarkerTag { }
Crie uma segunda classe que herde de MarkerFormatDefinitione nomeie-a
HighlightWordFormatDefinition
. Para usar essa definição de formato para sua tag, você deve exportá-la com os seguintes atributos:NameAttribute: use isso para fazer referência a esse formato
UserVisibleAttribute: isso faz com que o formato apareça na interface do usuário
[Export(typeof(EditorFormatDefinition))] [Name("MarkerFormatDefinition/HighlightWordFormatDefinition")] [UserVisible(true)] internal class HighlightWordFormatDefinition : MarkerFormatDefinition { }
No construtor para HighlightWordFormatDefinition, defina seu nome de exibição e aparência. A propriedade Background define a cor de preenchimento, enquanto a propriedade Foreground define a cor da borda.
public HighlightWordFormatDefinition() { this.BackgroundColor = Colors.LightBlue; this.ForegroundColor = Colors.DarkBlue; this.DisplayName = "Highlight Word"; this.ZOrder = 5; }
No construtor para HighlightWordTag, passe o nome da definição de formato que você criou.
public HighlightWordTag() : base("MarkerFormatDefinition/HighlightWordFormatDefinition") { }
Implementar um ITagger
O próximo passo é implementar a ITagger<T> interface. Essa interface atribui, a um determinado buffer de texto, marcas que fornecem realce de texto e outros efeitos visuais.
Para implementar um tagger
Crie uma classe que implementa ITagger<T> do tipo
HighlightWordTag
e nomeie-aHighlightWordTagger
.internal class HighlightWordTagger : ITagger<HighlightWordTag> { }
Adicione os seguintes campos particulares e propriedades à classe:
Um ITextView, que corresponde ao modo de exibição de texto atual.
Um ITextBuffer, que corresponde ao buffer de texto subjacente ao modo de exibição de texto.
Um ITextSearchService, que é usado para localizar texto.
Um ITextStructureNavigator, que tem métodos para navegar dentro de extensões de texto.
A NormalizedSnapshotSpanCollection, que contém o conjunto de palavras a destacar.
A SnapshotSpan, que corresponde à palavra atual.
A SnapshotPoint, que corresponde à posição atual do acento circunflexo.
Um objeto de bloqueio.
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();
Adicione um construtor que inicializa as propriedades listadas anteriormente e adiciona LayoutChanged manipuladores de PositionChanged eventos.
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; }
Os manipuladores de eventos chamam o
UpdateAtCaretPosition
método.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); }
Você também deve adicionar um
TagsChanged
evento que é chamado pelo método update.O
UpdateAtCaretPosition()
método localiza cada palavra no buffer de texto que é idêntica à palavra onde o cursor está posicionado e constrói uma lista de SnapshotSpan objetos que correspondem às ocorrências da palavra. Em seguida, chamaSynchronousUpdate
, o que eleva oTagsChanged
evento.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)); }
O
SynchronousUpdate
executa uma atualização síncronaWordSpans
nas propriedades e eCurrentWord
gera oTagsChanged
evento.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))); } }
Você deve implementar o GetTags método. Esse método pega uma coleção de objetos e retorna uma enumeração de extensões de SnapshotSpan marca.
Em C#, implemente esse método como um iterador de rendimento, que permite a avaliação lenta (ou seja, a avaliação do conjunto somente quando itens individuais são acessados) das tags. No Visual Basic, adicione as marcas a uma lista e retorne a lista.
Aqui, o método retorna um objeto que tem um "azul", TextMarkerTagque fornece um TagSpan<T> plano de fundo azul.
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()); } }
Criar um provedor de tagger
Para criar seu tagger, você deve implementar um IViewTaggerProviderarquivo . Essa classe é uma parte do componente MEF, portanto, você deve definir os atributos corretos para que essa extensão seja reconhecida.
Observação
Para saber mais informações sobre o MEF, consulte Managed Extensibility Framework (MEF).
Para criar um provedor de tagger
Crie uma classe chamada
HighlightWordTaggerProvider
que implementa IViewTaggerProvidero e exporte-a com um de "texto" e um ContentTypeAttribute TagTypeAttribute de TextMarkerTag.[Export(typeof(IViewTaggerProvider))] [ContentType("text")] [TagType(typeof(TextMarkerTag))] internal class HighlightWordTaggerProvider : IViewTaggerProvider { }
Você deve importar dois serviços de editor, o e o , para instanciar o ITextSearchService ITextStructureNavigatorSelectorServicepichador.
[Import] internal ITextSearchService TextSearchService { get; set; } [Import] internal ITextStructureNavigatorSelectorService TextStructureNavigatorSelector { get; set; }
Implemente o CreateTagger método para retornar uma instância do
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>; }
Compilar e testar o código
Para testar esse código, compile a solução HighlightWordTest e execute-a na instância experimental.
Para criar e testar a solução HighlightWordTest
Compile a solução.
Quando você executa esse projeto no depurador, uma segunda instância do Visual Studio é iniciada.
Crie um arquivo de texto e digite algum texto no qual as palavras são repetidas, por exemplo, "hello hello hello".
Posicione o cursor em uma das ocorrências de "Olá". Todas as ocorrências devem ser destacadas em azul.