Walkthrough: Using a Shortcut Key with an Editor Extension
Note
This article applies to Visual Studio 2015. If you're looking for the latest Visual Studio documentation, see Visual Studio documentation. We recommend upgrading to the latest version of Visual Studio. Download it here
You can respond to shortcut keys in your editor extension. The following walkthrough shows how to add a view adornment to a text view by using a shortcut key. This walkthrough is based on the viewport adornment editor template, and it allows you to add the adornment by using the + character.
Prerequisites
Starting in Visual Studio 2015, you do not install the Visual Studio SDK from the download center. It is included as an optional feature in Visual Studio setup. You can also install the VS SDK later on. For more information, see Installing the Visual Studio SDK.
Creating a Managed Extensibility Framework (MEF) Project
Create a C# VSIX project. (In the New Project dialog, select Visual C# / Extensibility, then VSIX Project.) Name the solution
KeyBindingTest
.Add an Editor Text Adornment item template to the project and name it
KeyBindingTest
. For more information, see Creating an Extension with an Editor Item Template.Add the following references and set CopyLocal to
false
:Microsoft.VisualStudio.Editor
Microsoft.VisualStudio.OLE.Interop
Microsoft.VisualStudio.Shell.14.0
Microsoft.VisualStudio.TextManager.Interop
In the KeyBindingTest class file, change the class name to PurpleCornerBox. Use the light bulb that appears in the left margin to make the other appropriate changes. Inside the constructor, change the name of the adornment layer from KeyBindingTest to PurpleCornerBox:
this.layer = view.GetAdornmentLayer("PurpleCornerBox");
Defining the Command Filter
The command filter is an implementation of IOleCommandTarget, which handles the command by instantiating the adornment.
Add a class file and name it
KeyBindingCommandFilter
.Add the following using statements.
using System; using System.Runtime.InteropServices; using Microsoft.VisualStudio.OLE.Interop; using Microsoft.VisualStudio; using Microsoft.VisualStudio.Text.Editor;
The class named KeyBindingCommandFilter should inherit from IOleCommandTarget.
internal class KeyBindingCommandFilter : IOleCommandTarget
Add private fields for the text view, the next command in the command chain, and a flag to represent whether the command filter has already been added.
private IWpfTextView m_textView; internal IOleCommandTarget m_nextTarget; internal bool m_added; internal bool m_adorned;
Add a constructor that sets the text view.
public KeyBindingCommandFilter(IWpfTextView textView) { m_textView = textView; m_adorned = false; }
Implement the
QueryStatus()
method as follows.int IOleCommandTarget.QueryStatus(ref Guid pguidCmdGroup, uint cCmds, OLECMD[] prgCmds, IntPtr pCmdText) { return m_nextTarget.QueryStatus(ref pguidCmdGroup, cCmds, prgCmds, pCmdText); }
Implement the
Exec()
method so that it adds a purple box to the view if a + character is typed.int IOleCommandTarget.Exec(ref Guid pguidCmdGroup, uint nCmdID, uint nCmdexecopt, IntPtr pvaIn, IntPtr pvaOut) { if (m_adorned == false) { char typedChar = char.MinValue; if (pguidCmdGroup == VSConstants.VSStd2K && nCmdID == (uint)VSConstants.VSStd2KCmdID.TYPECHAR) { typedChar = (char)(ushort)Marshal.GetObjectForNativeVariant(pvaIn); if (typedChar.Equals('+')) { new PurpleCornerBox(m_textView); m_adorned = true; } } } return m_nextTarget.Exec(ref pguidCmdGroup, nCmdID, nCmdexecopt, pvaIn, pvaOut); }
Adding the Command Filter
The adornment provider must add a command filter to the text view. In this example, the provider implements IVsTextViewCreationListener to listen to text view creation events. This adornment provider also exports the adornment layer, which defines the Z-order of the adornment.
In the KeyBindingTestTextViewCreationListener file, add the following using statements:
using System; using System.Collections.Generic; using System.ComponentModel.Composition; using Microsoft.VisualStudio; using Microsoft.VisualStudio.OLE.Interop; using Microsoft.VisualStudio.Utilities; using Microsoft.VisualStudio.Editor; using Microsoft.VisualStudio.Text.Editor; using Microsoft.VisualStudio.TextManager.Interop;
In the adornment layer definition, change the name of the AdornmentLayer from KeyBindingTest to PurpleCornerBox.
[Export(typeof(AdornmentLayerDefinition))] [Name("PurpleCornerBox")] [Order(After = PredefinedAdornmentLayers.Selection, Before = PredefinedAdornmentLayers.Text)] public AdornmentLayerDefinition editorAdornmentLayer;
To get the text view adapter, you must import the IVsEditorAdaptersFactoryService.
[Import(typeof(IVsEditorAdaptersFactoryService))] internal IVsEditorAdaptersFactoryService editorFactory = null;
Change the TextViewCreated method so that it adds the
KeyBindingCommandFilter
.public void TextViewCreated(IWpfTextView textView) { AddCommandFilter(textView, new KeyBindingCommandFilter(textView)); }
The
AddCommandFilter
handler gets the text view adapter and adds the command filter.void AddCommandFilter(IWpfTextView textView, KeyBindingCommandFilter commandFilter) { if (commandFilter.m_added == false) { //get the view adapter from the editor factory IOleCommandTarget next; IVsTextView view = editorFactory.GetViewAdapter(textView); int hr = view.AddCommandFilter(commandFilter, out next); if (hr == VSConstants.S_OK) { commandFilter.m_added = true; //you'll need the next target for Exec and QueryStatus if (next != null) commandFilter.m_nextTarget = next; } } }
Making the Adornment Appear on Every Line
The original adornment appeared on every character ‘a’ in a text file. Now that we have changed the code to add the adornment in response to the ‘+’ character, it adds the adornment only on the line where the ‘+’ is typed. We can change the adornment code so that the adornment once more appears on every ‘a’.
In the KeyBindingTest.cs file, change the CreateVisuals() method to iterate through all the lines in the view to decorate the ‘a’ character.
private void CreateVisuals(ITextViewLine line)
{
IWpfTextViewLineCollection textViewLines = this.view.TextViewLines;
foreach (ITextViewLine textViewLine in textViewLines)
{
if (textViewLine.ToString().Contains("a"))
{
// Loop through each character, and place a box around any 'a'
for (int charIndex = textViewLine.Start; charIndex < textViewLine.End; charIndex++)
{
if (this.view.TextSnapshot[charIndex] == 'a')
{
SnapshotSpan span = new SnapshotSpan(this.view.TextSnapshot, Span.FromBounds(charIndex, charIndex + 1));
Geometry geometry = textViewLines.GetMarkerGeometry(span);
if (geometry != null)
{
var drawing = new GeometryDrawing(this.brush, this.pen, geometry);
drawing.Freeze();
var drawingImage = new DrawingImage(drawing);
drawingImage.Freeze();
var image = new Image
{
Source = drawingImage,
};
// Align the image with the top of the bounds of the text geometry
Canvas.SetLeft(image, geometry.Bounds.Left);
Canvas.SetTop(image, geometry.Bounds.Top);
this.layer.AddAdornment(AdornmentPositioningBehavior.TextRelative, span, null, image, null);
}
}
}
}
}
}
Building and Testing the Code
Build the KeyBindingTest solution and run it in the experimental instance.
Create or open a text file. Type some words containing the character ‘a’, and then type + anywhere in the text view.
A purple square should appear on every ‘a’ character in the file.