Thursday, November 19, 2020

A festive introduction to Visual Studio Extensions

This article is part of Festive Tech Calendar 2020. An online event organized by the tech community with content created by many kind individuals around the world.


Visual Studio is highly extensible. This article will show you two simple ways to extend it by adding additional content to the editor. What you add is up to you. It could be something practical, or it could be something fun, like this:

Screenshot of Visual Studio editor with holly in the corners, and images of Santa, Christmas trees, and snowflakes among the text

While there are many festive images included in the above screenshot, they are added in two ways.

The holly and mistletoe are Viewport Adornments. The smaller images among the text are Intra-text Adornments.

This post has three parts.

  1. Instructions on how to create an extension that adds these festive adornments to the editor.
  2. Examples of how that knowledge can be used to create more practical extensions.
  3. A video showing how to modify or extend the festive editor code.

Creating a festive editor extension

The code for this extension can be found at https://github.com/mrlacey/FestiveEditor.

If you don't want the code but simply want to install it, you can download it from the marketplace.

Creating a project

To create an extension for Visual Studio, you must have the 'Visual Studio extension development' workload installed. This option is available when installing or modifying an installation of Visual Studio.

The Visual Studio Installer showing the extension development workload option

We'll start by creating an Empty VSIX Project within Visual Studio.

Empty VSIX Project option in the Create a new project dialog
Let's call this project FestiveEditor.

Adding a viewport adornment

The first things to add to the extension are the holly and mistletoe images.

I'm using images from SweetClipArt.com, but you could use any images you want.

mistletoe with red ribbon Holly with berries

For simplicity, I've created versions of the holly image rotated for each corner it will be displayed in. 
The images are in a folder called Images, and I've set the Include in VSIX property to True so that they'll be distributed with the extension.

partial screenshot showing images in solution explorer and the properties window

Firstly, we'll create an adornment that will display the mistletoe. We do this by adding an item to the project of the type Editor Viewport Adornment. Let's call it MistletoeAdornment.cs.

Add New Item dialog showing Editor Viewport Adornment option

This adds two files and several references to the project.

Solution Explorer showing new files and references

MistletoeAdornmentTextViewCreationListener.cs is responsible for the creation of the image (adornment) that we add to the editor. Because this class will create all of the adornments that we add, let's give it the more generic name of FestiveEditorTextViewCreationListener.

This file does three things.

  1. It determines which editors it applies to.
  2. It defines the visual layer to which the images will be added.
  3. It creates a class that displays the image.

Determining which editors the adornment applies to.

The class implements and exports the interface IWpfTextViewCreationListener. The ExportAttribute tells Visual Studio, via MEF (the Managed Extensibility Framework) that this class wants to listen to TextView creation events. By specifying the ContentType as "text" and the TextViewRole as PredefinedTextViewRoles.Document Visual Studio knows to add this to all editors that allow the editing of text documents.

Defining the visual layer that will host the images

The AdornmentLayerDefinition is a private field but is accessible within the extension as it is exported via MEF. By specifying the name of "FestiveAdornments," we will reference it in other classes. By specifying that it comes after the predefined Text layer, it will appear on top of all the content in the file/editor. The editor has three built-in layers: text, caret, and selection. If we wanted everything in the layer to appear under the editor's other contents, we could specify the Order as coming Before the Selection.

Creating the class that displays the image

The final part of this class is the implementation of the TextViewCreated method. Visual Studio calls this and passes a reference to the IWPFTextView interface. We'll pass this to the control that displays the mistletoe image.

Displaying the mistletoe image

The MistletoeAdornment class handles the creation and positioning of the mistletoe image.

When the class is created, it:

  • creates the BitmapImage class that will display the png file.
  • specifies which layer to add the adornment to.
  • subscribes to LayoutChanged events of the WpfTextView so that we can redraw it in the middle of the top of the screen whenever the window size changes.

If we debug the project, a new experimental instance of Visual Studio is opened with our extension installed. If you then open any document file, you will see the mistletoe added in the middle, at the top of the screen.

screenshot of Visual Studio showing the mistletoe adornment

Displaying the holly images

We now need to create four new classes to create the four holly image. We could use one class that creates and positions multiple images, but as the classes are so simple, I find it easier to debug and maintain them as separate classes.

We'll call these classes TopLeftHollyAdornment, TopRightHollyAdornment, BottomLeftHollyAdornment, and BottomRightHollyAdornment.

These classes can all be the same as MistletoAdornment.cs but with three differences:

  1. The fields containing the height and width with which to display the images.
  2. The name of the file used when creating the BitmapImage.
  3. The code to position the images.
The code for the positioning of the images is this.

The repetition in these classes makes them a suitable candidate for abstracting the commonality, but this is left as an exercise for you if you'd like to do this.  

We now need to tell the creation listener to create the new classes.

When this is debugged, we see all four images.

Visual Studio editor with holly in each corner and mistletoe at the top

Adding Intra-text adornments

We can now add additional images between some of the text in the editor.

We'll start by adding some images to the Images folder and again setting the Include in VSIX property to true.

Christmas treesanta facesnowflakesnowmanpresent

The aim is to put these images next to words of between three and seven characters in length.

The extensibility workload includes an item for creating an Editor Text Adornment. However, it is not suitable for the complexity of our scenario.

Instead, we'll take inspiration from one of the VSSDK Extensibility Samples.

Specifically, we'll reuse the IntraTextAdornmentTagger and RegexTagger classes.

Our code will use two taggers. A tag is a way of associating some data with a location (or span) in the text. A Tagger is responsible for creating tags.

We need the first tagger to look through the text to find places to add the images. It will use a regular expression to find those locations and so will be based on the RegexTagger class.

The second tagger will be responsible for drawing creating tags that show where to draw the image adornments. This will be based on the IntraTextAdornmentTagger class.

Tagging where to put the images

The first thing we need to do is to tell Visual Studio that we will provide an ITagger that will produce FestiveImageTags. This is done with the FestiveImageTaggerProvider.

Next, we need to define the FestiveImageTagger. Most of the work this class does is inherited from RegexTagger, but we need to specify the Regular Expression (Regex) to identify where we want to put the tags/images.

The final thing we need to create the tags is the tag definition itself. FestiveImageTag implements the ITag interface, but this is used purely for identification and does not contain any functionality. The only thing the tag needs is the "term" or word matched by the regular expression.

If we ran the code now, we wouldn't see anything. This is because (most) tags by themselves do not cause any UI to be displayed. (Notable exceptions to this are the OutliningRegionTag and the ErrorTag, but we are not using them here.)

Putting images where there are tags

As with creating the tagger earlier, we first need a provider to tell Visual Studio to create the tagger. FestiveImageAdornmentTaggerProvider tells Visual Studio that in text documents, it will create a FestiveImageAdornmentTagger that will create IntraTextAdornmentTag where there are FestiveImageTag tags.

FestiveImageAdornmentTagger gets most of its logic by inheriting from IntraTextAdornmentTagger and specifies how to place a FestiveImageAdornment where there is a FestiveImageTag.

FestiveImageAdornment is just an Image with a bit of extra logic where we specify the image's path to use, which is done based on the length of the term.

This might seem complicated, but it allows functionality to be clearly separated into different classes.
In summary, the process works like this:

  1. The FestiveImageTaggerProvider tells Visual Studio to create a FestiveImageTagger for all text documents, and in turn, that will create FestiveImageTags.
  2. The FestiveImageTagger watches the changing text and adds a FestiveImageTag to each location in the document where we want to add an image.
  3. The FestiveImageAdornmentTaggerProvider tells Visual Studio to create a FestiveImageAdornmentTagger for all text documents and that will create tags of type IntraTextAdornmentTag where there are FestiveImageTags. (As created separately.)
  4. The FestiveImageAdornmentTagger creates (and updates) instances of FestiveImageAdornment within the document, in the location specified in the FestiveImageTag, and these are the displayed images.

The result of all this is that festive images will be added within the text of a document.

Visual Studio editor showing all the adornments created in this article


Practical uses of editor adornments

I have previously made several extensions (for more serious purposes) that use the techniques shown above. 

Real extensions using viewport adornments

These two extensions extend the editor by adding adornments to the viewport.

Show Selection

This extension displays the start and end positions of the current text selection in the top right-hand corner of the window.
The need to know this is niche but the ability to create an extension that displays this when needed, has saved hours of trying to manually determine how the location of specific points within a file.

Visual Studio editor window showing the selected position in the corner adornment

Get it from the Visual Studio Marketplace or view the source on GitHub.

Watermark

This extension allows the display of a customizable, text-based watermark on the editor. This is useful for ensuring that specific information (such as a name, URI, or email address) is always available to someone else viewing the screen. It is intended for use in demonstrations at conferences/meetups or while streaming. The content, size, colors, and position of the text are all configurable.
The watermark (my Twitter handle - @mrlacey) is highlighted in a red oval in the image below.

The Visual Studio editor showing a watermark

This extension is available from the Visual Studio Marketplace, and the code is on GitHub.

Real extensions using text-based adornments

These three extensions include f

Comment Links

This extension makes it possible to navigate between files of any type or language by placing a prefix of "Link:" before the filename in the comment. When this is done it adds a button that, when clicked, will navigate to that file. It can also jump to specific line numbers or instances of a specific piece of text within the file.
This was created to make it possible to easily navigate within projects that use different languages and so Visual Studio's built-in functionality for navigating to types doesn't work.

partial screenshot showing a button added into a comment

This extension is available from the Visual Studio Marketplace, and the code is on GitHub.

String Resource Visualizer

This extension displays the value of localized strings next to where they're used in source code. It works by identifying the use of values from a resource file (.resx or .resw) and displays the localized value above the usage. Configurable options support showing the default translation of that of a specific culture. This extension exists to help developers confirm that they are using the right value in the right place. It works with the ResourceManager and the ASP.NET Localizer.

This extension is slightly different from the above as it places the adornment above the text.

screenshot showing the values of string resources being displayed above where they're used

Examples of resources being shown when used via the ASP.NET Localizer

This extension is available from the Visual Studio Marketplace, and the code is on GitHub.

Const Visualizer

This is a companion extension to the String Resouce Visualizer. Rather than displaying resources, it displays the values of constants above the places where they're used.
This can be helpful to verify that the value of the constant you are using has the value you expect or intend.

screenshot showing examples of constants being displayed

This extension is available from the Visual Studio Marketplace, and the code is on GitHub.


Extending the Festive Editor code

There are lots of ways you can extend the above code. I've demonstrated a couple of examples in this video


Congratulations on making it this far. If you try extending or customizing this code for your own purposes I'd love to see the results. Share them in issues on GitHub or via Twitter.

If you're interested, you can find all the extensions I've made on the Marketplace.



4 comments:

  1. This is so dumb, but yet so awesome at the same time!! :) This is just playful ribbing but seriously, it is super helpful learning this. Thank you

    ReplyDelete
  2. What an awesome article and useful usages of the decorators

    ReplyDelete
  3. thanks for the info!

    ReplyDelete

I get a lot of comment spam :( - moderation may take a while.