Passo a passo: Realçar texto
Você pode adicionar efeitos visuais diferentes para o editor, criando componentes de Managed Extensibility Framework (MEF). Esta explicação passo a passo mostra como realçar todas as ocorrências da palavra atual em um arquivo de texto. Se uma palavra ocorre mais de uma vez em um arquivo de texto e posicione o cursor em uma ocorrência, cada ocorrência é realçada.
Pré-requisitos
Para concluir este passo a passo, você deve instalar o SDL do Visual Studio 2010.
Dica
Para obter mais informações sobre o SDK de Visual Studio, consulte Ampliando a visão geral de Visual Studio.Para descobrir como fazer o download do SDK do Visual Studio, consulte Visual Studio extensibilidade Developer Center no site do MSDN.
Criando um projeto MEF
Para criar um projeto MEF
Crie um projeto do classificador de Editor. Nomeie a solução HighlightWordTest.
Abra o arquivo de source.extension.vsixmanifest no Editor de VSIX de manifesto.
Certifique-se de que o Content título contém um tipo de conteúdo do componente MEF e que o Path for definido como HighlightWordTest.dll.
Salve e feche o source.extension.vsixmanifest.
Exclua os arquivos de classe existentes.
Definindo um TextMarkerTag
A primeira etapa no realce o texto é a subclasse TextMarkerTag e definir sua aparência.
Para definir um TextMarkerTag e um MarkerFormatDefinition
Adicionar um arquivo de classe e o nome HighlightWordTag.
Importe os namespaces a seguir.
Imports System Imports System.Collections.Generic Imports System.ComponentModel.Composition Imports System.Linq Imports System.Threading Imports System.Windows.Media Imports Microsoft.VisualStudio.Text Imports Microsoft.VisualStudio.Text.Classification Imports Microsoft.VisualStudio.Text.Editor Imports Microsoft.VisualStudio.Text.Operations Imports Microsoft.VisualStudio.Text.Tagging Imports Microsoft.VisualStudio.Utilities
using System; using System.Collections.Generic; using System.ComponentModel.Composition; using System.Linq; using System.Threading; using System.Windows.Media; 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;
Crie uma classe que herda de TextMarkerTag e o nome de HighlightWordTag.
Friend Class HighlightWordTag Inherits TextMarkerTag
internal class HighlightWordTag : TextMarkerTag
Criar uma segunda classe que herda de MarkerFormatDefinitione nomeie-o como HighlightWordFormatDefinition. Para usar essa definição de formato para sua marca, você deve exportá-lo com os seguintes atributos:
NameAttribute: marcas usam esta opção para fazer referência a esse formato
UserVisibleAttribute: isso faz com que o formato deve aparecer na interface de usuário
<Export(GetType(EditorFormatDefinition))> <Name("MarkerFormatDefinition/HighlightWordFormatDefinition")> <UserVisible(True)> Friend Class HighlightWordFormatDefinition Inherits MarkerFormatDefinition
[Export(typeof(EditorFormatDefinition))] [Name("MarkerFormatDefinition/HighlightWordFormatDefinition")] [UserVisible(true)] internal class HighlightWordFormatDefinition : MarkerFormatDefinition
No construtor para HighlightWordFormatDefinition, defina o seu nome de exibição e a aparência. A propriedade Background define a cor de preenchimento, enquanto a propriedade Foreground define a cor da borda.
Public Sub New() Me.BackgroundColor = Colors.LightBlue Me.ForegroundColor = Colors.DarkBlue Me.DisplayName = "Highlight Word" Me.ZOrder = 5 End Sub
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ê acabou de criar.
Public Sub New() MyBase.New("MarkerFormatDefinition/HighlightWordFormatDefinition") End Sub
public HighlightWordTag() : base("MarkerFormatDefinition/HighlightWordFormatDefinition") { }
A implementação de um ITagger
A próxima etapa é implementar a ITagger interface. Essa interface atribui um buffer de texto fornecido, as marcas que fornecem o realce do texto e outros efeitos visuais.
Para implementar um tagger
Criar uma classe que implementa ITagger do tipo HighlightWordTage o nome de HighlightWordTagger.
Friend Class HighlightWordTagger Implements ITagger(Of HighlightWordTag)
internal class HighlightWordTagger : ITagger<HighlightWordTag>
Adicione os seguintes campos particulares e propriedades para a classe:
Um ITextView, que corresponde ao modo de exibição de texto atual.
Um ITextBuffer, que corresponde ao buffer de texto que dá suporte a exibição de texto.
Um ITextSearchService, que é usado para localizar o texto.
Um ITextStructureNavigator, que tem métodos para navegar dentro de extensões de texto.
A NormalizedSnapshotSpanCollection, que contém o conjunto de palavras para realçar.
A SnapshotSpan, que corresponde à palavra atual.
A SnapshotPoint, que corresponde à posição atual do cursor.
Um objeto de bloqueio.
Private _View As ITextView Private Property View() As ITextView Get Return _View End Get Set(ByVal value As ITextView) _View = value End Set End Property Private _SourceBuffer As ITextBuffer Private Property SourceBuffer() As ITextBuffer Get Return _SourceBuffer End Get Set(ByVal value As ITextBuffer) _SourceBuffer = value End Set End Property Private _TextSearchService As ITextSearchService Private Property TextSearchService() As ITextSearchService Get Return _TextSearchService End Get Set(ByVal value As ITextSearchService) _TextSearchService = value End Set End Property Private _TextStructureNavigator As ITextStructureNavigator Private Property TextStructureNavigator() As ITextStructureNavigator Get Return _TextStructureNavigator End Get Set(ByVal value As ITextStructureNavigator) _TextStructureNavigator = value End Set End Property Private _WordSpans As NormalizedSnapshotSpanCollection Private Property WordSpans() As NormalizedSnapshotSpanCollection Get Return _WordSpans End Get Set(ByVal value As NormalizedSnapshotSpanCollection) _WordSpans = value End Set End Property Private _CurrentWord As System.Nullable(Of SnapshotSpan) Private Property CurrentWord() As System.Nullable(Of SnapshotSpan) Get Return _CurrentWord End Get Set(ByVal value As System.Nullable(Of SnapshotSpan)) _CurrentWord = value End Set End Property Private _RequestedPoint As SnapshotPoint Private Property RequestedPoint() As SnapshotPoint Get Return _RequestedPoint End Get Set(ByVal value As SnapshotPoint) _RequestedPoint = value End Set End Property Private updateLock As New Object()
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();
Adicionar um construtor que inicializa as propriedades listadas anteriormente e adiciona LayoutChanged e PositionChanged manipuladores de eventos.
Public Sub New(ByVal view As ITextView, ByVal sourceBuffer As ITextBuffer, ByVal textSearchService As ITextSearchService, ByVal textStructureNavigator As ITextStructureNavigator) Me.View = view Me.SourceBuffer = sourceBuffer Me.TextSearchService = textSearchService Me.TextStructureNavigator = textStructureNavigator Me.WordSpans = New NormalizedSnapshotSpanCollection() Me.CurrentWord = Nothing AddHandler Me.View.Caret.PositionChanged, AddressOf CaretPositionChanged AddHandler Me.View.LayoutChanged, AddressOf ViewLayoutChanged End Sub
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 que chamam o UpdateAtCaretPosition método.
Private Sub ViewLayoutChanged(ByVal sender As Object, ByVal e As TextViewLayoutChangedEventArgs) ' If a new snapshot wasn't generated, then skip this layout If e.NewSnapshot IsNot e.OldSnapshot Then UpdateAtCaretPosition(View.Caret.Position) End If End Sub Private Sub CaretPositionChanged(ByVal sender As Object, ByVal e As CaretPositionChangedEventArgs) UpdateAtCaretPosition(e.NewPosition) End Sub
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 o evento que será chamado pelo método update.
Public Event TagsChanged(ByVal sender As Object, ByVal e As SnapshotSpanEventArgs) _ Implements ITagger(Of HighlightWordTag).TagsChanged
public event EventHandler<SnapshotSpanEventArgs> TagsChanged;
O UpdateAtCaretPosition() método localiza todas as palavras no buffer de texto que é idêntico à palavra onde o cursor está posicionado e constrói uma lista de SnapshotSpan objetos que correspondem às ocorrências da palavra. Em seguida, chama SynchronousUpdate, que gera a TagsChanged evento.
Private Sub UpdateAtCaretPosition(ByVal caretPosition As CaretPosition) Dim point As System.Nullable(Of SnapshotPoint) = caretPosition.Point.GetPoint(SourceBuffer, caretPosition.Affinity) If Not point.HasValue Then Exit Sub End If ' 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 AndAlso CurrentWord.Value.Snapshot Is View.TextSnapshot AndAlso point.Value > CurrentWord.Value.Start AndAlso point.Value < CurrentWord.Value.[End] Then Exit Sub End If RequestedPoint = point.Value UpdateWordAdornments() End Sub Private Sub UpdateWordAdornments() Dim currentRequest As SnapshotPoint = RequestedPoint Dim wordSpans As New List(Of SnapshotSpan)() 'Find all words in the buffer like the one the caret is on Dim word As TextExtent = TextStructureNavigator.GetExtentOfWord(currentRequest) Dim foundWord As Boolean = True 'If we've selected something not worth highlighting, we might have missed a "word" by a little bit If Not WordExtentIsValid(currentRequest, word) Then 'Before we retry, make sure it is worthwhile If word.Span.Start <> currentRequest OrElse currentRequest = currentRequest.GetContainingLine().Start OrElse Char.IsWhiteSpace((currentRequest - 1).GetChar()) Then 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 Not WordExtentIsValid(currentRequest, word) Then foundWord = False End If End If End If If Not foundWord Then 'If we couldn't find a word, clear out the existing markers SynchronousUpdate(currentRequest, New NormalizedSnapshotSpanCollection(), Nothing) Exit Sub End If Dim currentWord__1 As SnapshotSpan = word.Span 'If this is the current word, and the caret moved within a word, we're done. If CurrentWord.HasValue AndAlso currentWord__1 = CurrentWord Then Exit Sub End If 'Find the new spans Dim findData As New FindData(currentWord__1.GetText(), currentWord__1.Snapshot) findData.FindOptions = FindOptions.WholeWord Or FindOptions.MatchCase wordSpans.AddRange(TextSearchService.FindAll(findData)) 'If another change hasn't happened, do a real update If currentRequest = RequestedPoint Then SynchronousUpdate(currentRequest, New NormalizedSnapshotSpanCollection(wordSpans), currentWord__1) End If End Sub Private Shared Function WordExtentIsValid(ByVal currentRequest As SnapshotPoint, ByVal word As TextExtent) As Boolean Return word.IsSignificant AndAlso currentRequest.Snapshot.GetText(word.Span).Any(Function(c) Char.IsLetter(c)) End Function
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 realiza uma atualização síncrona na WordSpans e CurrentWord propriedades e gera a TagsChanged evento.
Private Sub SynchronousUpdate(ByVal currentRequest As SnapshotPoint, ByVal newSpans As NormalizedSnapshotSpanCollection, ByVal newCurrentWord As System.Nullable(Of SnapshotSpan)) SyncLock updateLock If currentRequest <> RequestedPoint Then Exit Sub End If WordSpans = newSpans CurrentWord = newCurrentWord RaiseEvent TagsChanged(Me, New SnapshotSpanEventArgs(New SnapshotSpan(SourceBuffer.CurrentSnapshot, 0, SourceBuffer.CurrentSnapshot.Length))) End SyncLock End Sub
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 a GetTags método. Este método usa uma coleção de SnapshotSpan objetos e retorna uma enumeração das extensões de marca.
C#, implemente esse método como um iterador de rendimento, que permite a avaliação lenta (isto é, de avaliação do conjunto apenas quando os itens individuais são acessados) das marcas. Na Visual Basic, adicionar marcas a uma lista e retornar a lista.
Aqui o método retorna um TagSpan objeto que tem um "azul" TextMarkerTag, que fornece um plano de fundo azul.
Public Function GetTags(ByVal spans As NormalizedSnapshotSpanCollection) As IEnumerable(Of ITagSpan(Of HighlightWordTag)) Implements ITagger(Of HighlightWordTag).GetTags If CurrentWord Is Nothing Then Return Nothing Exit Function End If ' Hold on to a "snapshot" of the word spans and current word, so that we maintain the same ' collection throughout Dim currentWord__1 As SnapshotSpan = CurrentWord.Value Dim wordSpans__2 As NormalizedSnapshotSpanCollection = WordSpans If spans.Count = 0 OrElse WordSpans.Count = 0 Then Return Nothing Exit Function End If ' 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 IsNot wordSpans__2(0).Snapshot Then wordSpans__2 = New NormalizedSnapshotSpanCollection(wordSpans__2.[Select](Function(span) span.TranslateTo(spans(0).Snapshot, SpanTrackingMode.EdgeExclusive))) currentWord__1 = currentWord__1.TranslateTo(spans(0).Snapshot, SpanTrackingMode.EdgeExclusive) End If 'in order to emulate the C# yield return functionality, 'create a list and add all the relevant spans to it, then return the list Dim list As List(Of TagSpan(Of HighlightWordTag)) list = New List(Of TagSpan(Of HighlightWordTag))() If spans.OverlapsWith(New NormalizedSnapshotSpanCollection(currentWord__1)) Then list.Add(New TagSpan(Of HighlightWordTag)(CurrentWord, New HighlightWordTag())) End If For Each span As SnapshotSpan In NormalizedSnapshotSpanCollection.Overlap(spans, wordSpans__2) list.Add(New TagSpan(Of HighlightWordTag)(span, New HighlightWordTag())) Next Return List End Function
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 sua tagger, você deve implementar um IViewTaggerProvider. Essa classe é uma parte de componente MEF, portanto, você deve definir os atributos corretos para que esta extensão seja reconhecida.
Dica
Para obter mais informações sobre o MEF, consulte Managed Extensibility Framework (MEF).
Para criar um provedor de tagger
Criar uma classe chamada HighlightWordTaggerProvider que implementa IViewTaggerProvidere exportá-lo com um ContentTypeAttribute de "texto" e um TagTypeAttribute de TextMarkerTag.
<Export(GetType(IViewTaggerProvider))> <ContentType("text")> <TagType(GetType(TextMarkerTag))> Friend Class HighlightWordTaggerProvider Implements IViewTaggerProvider
[Export(typeof(IViewTaggerProvider))] [ContentType("text")] [TagType(typeof(TextMarkerTag))] internal class HighlightWordTaggerProvider : IViewTaggerProvider
Você deve importar os dois serviços do editor, o ITextSearchService e o ITextStructureNavigatorSelectorService, para criar uma instância de tagger.
Private _TextSearchService As ITextSearchService <Import()> _ Friend Property TextSearchService() As ITextSearchService Get Return _TextSearchService End Get Set(ByVal value As ITextSearchService) _TextSearchService = value End Set End Property Private _TextStructureNavigatorSelector As ITextStructureNavigatorSelectorService <Import()> Friend Property TextStructureNavigatorSelector() As ITextStructureNavigatorSelectorService Get Return _TextStructureNavigatorSelector End Get Set(ByVal value As ITextStructureNavigatorSelectorService) _TextStructureNavigatorSelector = value End Set End Property
[Import] internal ITextSearchService TextSearchService { get; set; } [Import] internal ITextStructureNavigatorSelectorService TextStructureNavigatorSelector { get; set; }
Implementar a CreateTagger``1 método para retornar uma instância de HighlightWordTagger.
Public Function CreateTagger(Of T As ITag)(ByVal textView As ITextView, ByVal buffer As ITextBuffer) As ITagger(Of T) Implements IViewTaggerProvider.CreateTagger 'provide highlighting only on the top buffer If textView.TextBuffer IsNot buffer Then Return Nothing End If Dim textStructureNavigator As ITextStructureNavigator = TextStructureNavigatorSelector.GetTextStructureNavigator(buffer) Return TryCast(New HighlightWordTagger(textView, buffer, TextSearchService, textStructureNavigator), ITagger(Of T)) End Function
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>; }
Criar e testar o código
Para testar esse código, crie a solução de HighlightWordTest e execute-o na instância experimental.
Para criar e testar a solução de HighlightWordTest
Crie a solução.
Ao executar este projeto no depurador, uma segunda instância do Visual Studio é instanciada.
Crie um arquivo de texto e digite algum texto no qual as palavras são repetidas, por exemplo, "Olá Olá Olá".
Posicione o cursor em uma das ocorrências de "hello". Todas as ocorrências devem ser destacadas em azul.
Consulte também
Tarefas
Passo a passo: Vinculação a um tipo de conteúdo a uma extensão de nome de arquivo