Tutorial: Resaltar texto
Puede agregar diferentes efectos visuales al editor mediante la creación de partes de componentes de Managed Extensibility Framework (MEF). En este tutorial se muestra cómo resaltar cada aparición de la palabra actual en un archivo de texto. Si una palabra tiene lugar más de una vez en un archivo de texto y coloca el símbolo de intercalación en una repetición, se resalta cada repetición.
Creación de un proyecto MEF
Cree un proyecto VSIX de C#. (En Cuadro de diálogo Nuevo proyecto , seleccione Visual C# / Extensibilidad y, después , Proyecto VSIX). Asigne un nombre a la solución
HighlightWordTest
.Agregue una plantilla de elemento clasificador del editor al proyecto. Para obtener más información, vea Creación de una extensión con una plantilla de elemento de editor.
Elimine los archivos de clase existentes.
Definir un TextMarkerTag
El primer paso para resaltar texto es subclase TextMarkerTag y definir su apariencia.
Para definir textMarkerTag y markerFormatDefinition
Agregue un archivo de clase y asígnele el nombre HighlightWordTag.
Agregue las siguientes referencias:
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
Importe los siguientes espacios de nombres.
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;
Cree una clase que herede de TextMarkerTag y asígnela
HighlightWordTag
el nombre .internal class HighlightWordTag : TextMarkerTag { }
Cree una segunda clase que herede de MarkerFormatDefinitiony asígnela
HighlightWordFormatDefinition
el nombre . Para poder usar esta definición de formato para la etiqueta, debe exportarla con los atributos siguientes:NameAttribute: las etiquetas lo usan para hacer referencia a este formato.
UserVisibleAttribute: esto hace que el formato aparezca en la interfaz de usuario.
[Export(typeof(EditorFormatDefinition))] [Name("MarkerFormatDefinition/HighlightWordFormatDefinition")] [UserVisible(true)] internal class HighlightWordFormatDefinition : MarkerFormatDefinition { }
En el constructor de HighlightWordFormatDefinition, defina su nombre para mostrar y apariencia. La propiedad Background define el color de relleno, mientras que la propiedad Foreground define el color del borde.
public HighlightWordFormatDefinition() { this.BackgroundColor = Colors.LightBlue; this.ForegroundColor = Colors.DarkBlue; this.DisplayName = "Highlight Word"; this.ZOrder = 5; }
En el constructor de HighlightWordTag, pase el nombre de la definición de formato que creó.
public HighlightWordTag() : base("MarkerFormatDefinition/HighlightWordFormatDefinition") { }
Implementación de un ITagger
El siguiente paso es implementar la ITagger<T> interfaz. Esta interfaz asigna, a un búfer de texto determinado, etiquetas que proporcionan resaltado de texto y otros efectos visuales.
Para implementar un tagger
Cree una clase que implemente ITagger<T> de tipo
HighlightWordTag
y asígnelaHighlightWordTagger
el nombre .internal class HighlightWordTagger : ITagger<HighlightWordTag> { }
Agregue los siguientes campos privados y propiedades a la clase :
, ITextViewque corresponde a la vista de texto actual.
, ITextBufferque corresponde al búfer de texto que subyace a la vista de texto.
, ITextSearchServiceque se usa para buscar texto.
, ITextStructureNavigatorque tiene métodos para navegar dentro de intervalos de texto.
, NormalizedSnapshotSpanCollectionque contiene el conjunto de palabras que se va a resaltar.
, SnapshotSpanque corresponde a la palabra actual.
, SnapshotPointque corresponde a la posición actual del símbolo de intercalación.
Objeto de bloqueo.
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();
Agregue un constructor que inicialice las propiedades enumeradas anteriormente y agregue LayoutChanged controladores de eventos y 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; }
Los controladores de eventos llaman al
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); }
También debe agregar un
TagsChanged
evento al que llama el método update.El
UpdateAtCaretPosition()
método busca todas las palabras del búfer de texto idénticas a la palabra donde se coloca el cursor y construye una lista de SnapshotSpan objetos que corresponden a las apariciones de la palabra. A continuación, llama aSynchronousUpdate
, que genera elTagsChanged
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)); }
SynchronousUpdate
realiza una actualización sincrónica en lasWordSpans
propiedades yCurrentWord
y genera elTagsChanged
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))); } }
Debe implementar el GetTags método . Este método toma una colección de SnapshotSpan objetos y devuelve una enumeración de intervalos de etiquetas.
En C#, implemente este método como iterador de rendimiento, que habilita la evaluación diferida (es decir, la evaluación del conjunto solo cuando se accede a elementos individuales) de las etiquetas. En Visual Basic, agregue las etiquetas a una lista y devuelva la lista.
Aquí el método devuelve un TagSpan<T> objeto que tiene un "azul", TextMarkerTagque proporciona un fondo 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()); } }
Creación de un proveedor de tagger
Para crear el tagger, debe implementar un IViewTaggerProvider. Esta clase es una parte del componente MEF, por lo que debe establecer los atributos correctos para que se reconozca esta extensión.
Nota:
Para obtener más información sobre MEF, vea Managed Extensibility Framework (MEF).
Para crear un proveedor de etiquetas
Cree una clase denominada
HighlightWordTaggerProvider
que implemente IViewTaggerProvidery expórtela con un ContentTypeAttribute de "texto" y un TagTypeAttribute de TextMarkerTag.[Export(typeof(IViewTaggerProvider))] [ContentType("text")] [TagType(typeof(TextMarkerTag))] internal class HighlightWordTaggerProvider : IViewTaggerProvider { }
Debe importar dos servicios de editor, y ITextSearchService ITextStructureNavigatorSelectorService, para crear una instancia del tagger.
[Import] internal ITextSearchService TextSearchService { get; set; } [Import] internal ITextStructureNavigatorSelectorService TextStructureNavigatorSelector { get; set; }
Implemente el CreateTagger método para devolver una instancia de
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>; }
Compilación y prueba del código
Para probar este código, compile la solución HighlightWordTest y ejecútela en la instancia experimental.
Para compilar y probar la solución HighlightWordTest
Compile la solución.
Al ejecutar este proyecto en el depurador, se inicia una segunda instancia de Visual Studio.
Cree un archivo de texto y escriba texto en el que se repiten las palabras, por ejemplo, "hello hello hello".
Coloque el cursor en una de las apariciones de "hello". Cada aparición debe resaltarse en azul.