Tutorial Table of Contents:

During the tutorial we are going to build a VS extensions called CodyDocs and place it on GitHub . Each tutorial part is a standalone tutorial on a specific topic and can be viewed individually. CodyDocs will save code documentation in a separate file and the extension will allow to view and edit the documentation in the editor itself.

Part 5: Highlight code in Editor

In this tutorial we’re going to learn how to highlight code in Visual Studio’s code editor. This includes highlighting text by changing Background color, changing Foreground color and adding border.

If you followed the rest of the series, in the previous tutorial we allowed the user to select text and add documentation for the selection. The documentation was then serialized to a file. In this part, we will show how to highlight all the text spans that have had documentation added to them.

To be able to change code format, we’ll use two new technologies: Managed Extensibility Framework (MEF) and Taggers .

MEF

Managed Extensibility Framework, MEF in short, is a framework by Microsoft to extend applications. In our case, it allows us to add functionality to Visual Studio. It’s somewhat similar to a Dependency Injection container like Unity.

See this example:

[Export(typeof(IViewTaggerProvider))]
public class DocumentedCodeHighlighterTaggerProvider : IViewTaggerProvider
{

The Export attribute is a bit like Register in a dependency injection framework. It means add “DocumentedCodeHighlighterTaggerProvider” to anyone who requests (imports) IViewTaggerProvider.

To import you can write in a constructor:

[ImportingConstructor]
public MyClass([ImportMany]IEnumerable<IViewTaggerProvider> providers)
{

This will create a new instance of “DocumentedCodeHighlighterTaggerProvider” and other exported classes (We can also configure to always have the same instance returned).

Taggers

To change text formatting and color we will use Tags .

A Tag instance usually doesn’t hold functionality by itself, but attached to a span of code. We can set Tags to apply some visual representation or effect on the code span it’s attached to. For example, ErrorTag will make the text Red and Bold. The visual effect doesn’t have to affect the code itself. For example, we can insert a button after the code.

To create tags, we will create a class that inherits from a ITagger, which includes GetTags method and TagsChanged event. GetTags returns the collection of Tags. TagsChanged should be invoked whenever the Tags need an update. After invoking TagsChanged, GetTags will be called.

Since we have many documents (.cs files for example) open in different tabs, we will have an instance of ITagger for each document.

We will use MEF Export attribute to add Taggers to Visual Studio.

Changing code format with Classifications

It’s worth mentioning classifications, even though we won’t be using them for this tutorial.

In this tutorial we’ll use TextMarkerTag to highlight text, but to change code format we will need to use Classifications. Classifications allow us to do text manipulations like making text Bold, Underlined, changing font and text size. I’ll include links at the end of the tutorial to classifications as well.

Getting Started

We will need to add several references to the project:

  • System.ComponentModel.Composition reference for MEF, can be found in ‘Assemblies’.
  • Microsoft.VisualStudio.Text.UI NuGet package
  • Microsoft.VisualStudio.Text.UI.Wpf NuGet package

To initialize MEF components, we’ll need to add a new Asset to source.extension.vsixmanifest.

<pre class="lang:default mark:3 decode:true"><Assets>
  ...
  <Asset Type="Microsoft.VisualStudio.MefComponent" d:Source="Project" d:ProjectName="%CurrentProject%" Path="|%CurrentProject%|" />
</Assets>

Creating Tags

To highlight, we need to create Tags and tell VS how to highlight them. First , we need to implement IViewTaggerProvider and export it with MEF. So we’ll add DocumentedCodeHighlighterTaggerProvider to the project:

<pre class="wrap:false lang:default decode:true">using CodyDocs.Events;
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.Text.Editor;
using Microsoft.VisualStudio.Text.Operations;
using Microsoft.VisualStudio.Text.Tagging;
using Microsoft.VisualStudio.Utilities;
using System.ComponentModel.Composition;

namespace CodyDocs.TextFormatting.DocumentedCodeHighlighter
{
    [Export]
    [Export(typeof(IViewTaggerProvider))]
    [ContentType("code")]
    [TagType(typeof(DocumentedCodeHighlighterTag))]
    public class DocumentedCodeHighlighterTaggerProvider : IViewTaggerProvider
    {
        private IEventAggregator _eventAggregator;

        [ImportingConstructor]
        public DocumentedCodeHighlighterTaggerProvider(IEventAggregator eventAggregator)
        {
            _eventAggregator = eventAggregator;
        }

        /// <summary>
        /// This method is called by VS to generate the tagger
        /// </summary>
        /// <param name="textView"> The text view we are creating a tagger for</param>
        /// <param name="buffer"> The buffer that the tagger will examine for instances of the current word</param>
        /// <returns> Returns a HighlightWordTagger instance</returns>
        public ITagger<T> CreateTagger<T>(ITextView textView, ITextBuffer buffer) where T : ITag
        {
            // Only provide highlighting on the top-level buffer
            if (textView.TextBuffer != buffer)
                return null;

            return new DocumentedCodeHighlighterTagger(textView, buffer, 
                   _eventAggregator) as ITagger<T>;
        }
    }
}

This class serves as a Factory of ITagger classes. The method CreateTagger is called every time a new document (.cs file) is opened.

The [Export] and [Export(typeof(IViewTaggerProvider))] means this method will be imported whenever we import this class explicitly and whenever we import IViewTaggerProvider. Visual Studio knows to import all IViewTaggerProvider classes.

_eventAggregator is a simple implementation of the EventAggregator design pattern, which I use to refresh the tags whenever a new documentation is added (I used EventAggregator.Net ).

Let’s look at our ITagger and Tag:

<pre class="wrap:false lang:default decode:true">using CodyDocs.Events;
using CodyDocs.Services;
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.Text.Editor;
using Microsoft.VisualStudio.Text.Tagging;
using System;
using System.Collections.Generic;

namespace CodyDocs.TextFormatting.DocumentedCodeHighlighter
{
    public class DocumentedCodeHighlighterTag : TextMarkerTag
    {
        public DocumentedCodeHighlighterTag() 
           : base("MarkerFormatDefinition/DocumentedCodeFormatDefinition") { }
    }

    class DocumentedCodeHighlighterTagger : ITagger<DocumentedCodeHighlighterTag>
    {
        private ITextView _textView;
        private ITextBuffer _buffer;
        private IEventAggregator _eventAggregator;
        private readonly string _codyDocsFilename;
        private readonly DelegateListener<DocumentationAddedEvent> _listener;

        public DocumentedCodeHighlighterTagger(ITextView textView, ITextBuffer buffer, IEventAggregator eventAggregator)
        {
            _textView = textView;
            _buffer = buffer;
            _eventAggregator = eventAggregator;
            var filename = GetFileName(buffer);
            _codyDocsFilename = filename + Consts.CODY_DOCS_EXTENSION;
            _listener = new DelegateListener<DocumentationAddedEvent>
                 (OnDocumentationAdded);
            _eventAggregator.AddListener<DocumentationAddedEvent>(_listener);

        }

        private string GetFileName(ITextBuffer buffer)
        {
            buffer.Properties.TryGetProperty(
                typeof(ITextDocument), out ITextDocument document);
            return document == null ? null : document.FilePath;
        }

        private void OnDocumentationAdded(DocumentationAddedEvent e)
        {
            string filepath = e.Filepath;
            if (filepath == _codyDocsFilename)
            {
                TagsChanged?.Invoke(this, new SnapshotSpanEventArgs(
                    new SnapshotSpan(_buffer.CurrentSnapshot, 
                          new Span(0, _buffer.CurrentSnapshot.Length - 1))));
            }
        }

        public event EventHandler<SnapshotSpanEventArgs> TagsChanged;

        public IEnumerable<ITagSpan<DocumentedCodeHighlighterTag>> GetTags(
              NormalizedSnapshotSpanCollection spans)
        {
            var documentation = Services.DocumentationFileSerializer.Deserialize(_codyDocsFilename);

            List<ITagSpan<DocumentedCodeHighlighterTag>> res = 
                new List<ITagSpan<DocumentedCodeHighlighterTag>>();
            var currentSnapshot = _buffer.CurrentSnapshot;
            foreach (var fragment in documentation.Fragments)
            {
                int startPos = fragment.Selection.StartPosition;
                int length = fragment.Selection.EndPosition - fragment.Selection.StartPosition;
                var snapshotSpan = new SnapshotSpan(
                     currentSnapshot, new Span(startPos, length));
                res.Add(new TagSpan<DocumentedCodeHighlighterTag>(snapshotSpan, 
                     new DocumentedCodeHighlighterTag()));
            }

            return res;
        }
    }
}

DocumentedCodeHighlighterTag – This is our Tag type that we will be creating. It derives from TextMarkerTag, which is a special kind of tag that allows us to highlight code. We pass it the name “MarkerFormatDefinition/DocumentedCodeFormatDefinition”, which we will later see used to set code format for this Tag.

The constructor receives ITextView and ITextBuffer as parameters. ITextView represents the visual element of the text view in the document. ITextBuffer is the textual data of the text view. We’re mainly interested in CurrentSnapshot property, which represents the current text in the buffer. This is immutable and a new Snapshot instance is created whenever there’s a change in text.

GetTags – This is the main method of ITagger. Calculates and returns the Tags in the given range.

TagsChanged – Invoking this notifies Visual Studio that the tags should be updated in the given range. GetTags will be called to get the updated Tags.

OnDocumentationAdded – When a documentation is added, we need to refresh the Tags. To do that, we call the TagsChanged event (which is part of the ITagger interface). The parameter is a SnapshotSpan, which represents a text Span (text range) in a Snapshot.

GetFileName – The documentation was serialized in a file with a similar name to the edited file and one way to get its name is from the Text Buffer’s properties.

Define highlight properties for the Tags

To define highlight format, we will use a MarkerFormatDefinition class:

using Microsoft.VisualStudio.Text.Classification;
using Microsoft.VisualStudio.Utilities;
using System.ComponentModel.Composition;
using System.Windows.Media;

namespace CodyDocs.TextFormatting.DocumentedCodeHighlighter
{
    [Export(typeof(EditorFormatDefinition))]
    [Name("MarkerFormatDefinition/DocumentedCodeFormatDefinition")]
    [UserVisible(true)]
    internal class DocumentedCodeFormatDefinition : MarkerFormatDefinition
    {
        public DocumentedCodeFormatDefinition()
        {
            var orange = Brushes.Orange.Clone();
            orange.Opacity = 0.25;
            this.Fill = orange;
            this.Border = new Pen(Brushes.Gray, 1.0);
            this.DisplayName = "Highlight Word";
            this.ZOrder = 5;
        }
    }
}

Note the attribute [Name(“MarkerFormatDefinition/DocumentedCodeFormatDefinition”)]. This is what links this format definition to our Tag type.

Is this it?

We’re almost finished. The code up to now will create Tags for every open document in Visual Studio. This code will get the spans that it needs to highlight by deserializing a file, which we created in the previous tutorial .

Note that the GetTags method will be called just when we open the document or on Visual Studio startup. It will be called again if we close and reopen the document.

We need an additional functionality: To add Tags by demand when we add documentation. To refresh the collection of Tags, we need to invoke the TagsChanged event, which will cause GetTags to be called again for an updated collection of Tags. For that, we need to publish an event to the EventAggregator when a documentation is added:

<pre class="lang:default mark:19 decode:true">private void OnSave(object sender, RoutedEventArgs e)
{
    if (this.DocumentationTextBox.Text.Trim() == "")
    {
        MessageBox.Show("Documentation can't be empty.");
        return;
    }

    var newDocFragment = new DocumentationFragment()
    {
        Documentation = this.DocumentationTextBox.Text,
        Selection = this._selectionText
    };
    try
    {
        string filepath = this._documentPath + Consts.CODY_DOCS_EXTENSION;
        DocumentationFileHandler.AddDocumentationFragment(newDocFragment, filepath);
        MessageBox.Show("Documentation added successfully.");
        _eventAggregator.SendMessage<DocumentationAddedEvent>(
            new DocumentationAddedEvent() { Filepath = filepath }
            );
        this.Close();
    }
    catch(Exception ex)
    {
        MessageBox.Show("Documentation add failed. Exception: " + ex.ToString());
    }

}

If you look back at the ITagger we created, you’ll see it listens to DocumentationAddedEvent and invokes TagsChanged,

The Result

Our extension, so far:

All the code is available on [GitHub](https://github.com/michaelscodingspot/CodyDocs). To checkout to the extension code right after this tutorial part, use the Tag **Part5**:
<pre class="lang:ps decode:true ">git clone https://github.com/michaelscodingspot/CodyDocs.git
git checkout tags/Part5

Summary

In this tutorial we worked with MEF, a very big part of Visual Studio extensibility.

We also saw how to add UI effects using Tags.

The way we created Tags in this tutorial is a pattern often used to add UI effects in VS extensibility. There will usually be a Factory class, like the IViewTaggerProvider in this part. The factory is called for each open document. It will import MEF components and create instances of a Tagger, passing the imported components to that instance. The Tagger will create Tags and update them. Another MEF component will define the Tag’s visual effect.

We also saw how components are binded together with MEF. For example, the MarkerFormatDefinition, which defined the highlight effect, used a [Name] attribute with the same string as our Tag DocumentedCodeHighlighterTag, telling Visual Studio this Tag should get that effect.

In the next tutorial, I’ll cover Tacking Spans. This allows us to track a span of code as it is edited. This will be necessary for the CodyDocs extension, since editing a file will change the position of documented code.

Additional Resources

  • To see other examples with Tags, checkout the VS extensibility samples page . It’s an extremely useful resource. See Highlight_Word, Diff_Classifier and Todo_Classification for samples that change code format.
  • Microsoft has a useful tutorial on highlighting text.
  • A good tutorial on classifications.