Adds visual decorations, overlays, and highlights to the Visual Studio text editor. Covers VSIX, VSSDK, and VisualStudio.Extensibility approaches.
How this skill is triggered — by the user, by Claude, or both
Slash command
/vs-extensibility-skills:adding-editor-adornmentsThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Adornments are WPF visuals drawn on the text editor surface. They can highlight text, add inline icons, render overlays, or display viewport-relative UI. Common scenarios:
Adornments are WPF visuals drawn on the text editor surface. They can highlight text, add inline icons, render overlays, or display viewport-relative UI. Common scenarios:
Adornments are one of the most powerful editor integration points — they let you draw arbitrary WPF content directly on the code surface without modifying the underlying text. The critical constraint is performance: LayoutChanged fires on every scroll and edit, so adornment rendering must be fast.
When to use adornments vs. alternatives:
IGlyphFactory (see vs-editor-margin).vsixmanifest[Export(typeof(AdornmentLayerDefinition))])Any extension that uses MEF editor exports must declare the MEF asset type in the .vsixmanifest file. Without this, Visual Studio will not discover your MEF components and your adornment will not load.
Add this inside the <Assets> element of source.extension.vsixmanifest:
<Asset Type="Microsoft.VisualStudio.MefComponent"
d:Source="Project"
d:ProjectName="%CurrentProject%"
Path="|%CurrentProject%|" />
If you are using a multi-project solution, replace %CurrentProject% with the project name that contains the MEF exports. The Visual Studio VSIX project template usually includes this automatically, but if your adornment is not loading, this is the first thing to check.
The VisualStudio.Extensibility SDK does not currently support editor adornments. There is no out-of-process API for rendering custom WPF visuals on the editor surface.
If you need editor adornments, use the VSIX Community Toolkit or VSSDK (in-process) approach.
For some visual scenarios, you may be able to use ITextViewChangedListener combined with diagnostics/text markers in the new model, but full custom WPF adornments require in-process MEF.
The Community Toolkit does not add a separate adornment API — adornments use the same MEF-based VSSDK APIs. The toolkit simplifies package initialization, but the adornment itself uses IWpfTextViewCreationListener and AdornmentLayer from the editor MEF model.
NuGet packages: Community.VisualStudio.Toolkit, Microsoft.VisualStudio.Editor, Microsoft.VisualStudio.Text.UI.Wpf
Key namespaces: Microsoft.VisualStudio.Text.Editor, Microsoft.VisualStudio.Utilities
The code below is identical to the VSSDK approach — the toolkit does not wrap this API.
Editor adornments are created by:
[Export(typeof(AdornmentLayerDefinition))]IWpfTextViewCreationListener to attach your adornment logic when an editor opensNuGet packages: Microsoft.VisualStudio.SDK, Microsoft.VisualStudio.Editor, Microsoft.VisualStudio.Text.UI.Wpf
Key namespaces: Microsoft.VisualStudio.Text.Editor, Microsoft.VisualStudio.Text.Formatting, Microsoft.VisualStudio.Utilities
using System.ComponentModel.Composition;
using Microsoft.VisualStudio.Text.Editor;
using Microsoft.VisualStudio.Utilities;
namespace MyExtension;
[Export(typeof(IWpfTextViewCreationListener))]
[ContentType("text")]
[TextViewRole(PredefinedTextViewRoles.Document)]
internal sealed class HighlightAdornmentFactory : IWpfTextViewCreationListener
{
[Export(typeof(AdornmentLayerDefinition))]
[Name("MyHighlightAdornment")]
[Order(After = PredefinedAdornmentLayers.Selection, Before = PredefinedAdornmentLayers.Text)]
private AdornmentLayerDefinition _editorAdornmentLayer;
public void TextViewCreated(IWpfTextView textView)
{
// Create the adornment manager and attach it to the view
new HighlightAdornment(textView);
}
}
This example highlights every occurrence of "TODO" with a colored background:
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.Text.Editor;
using Microsoft.VisualStudio.Text.Formatting;
namespace MyExtension;
internal sealed class HighlightAdornment
{
private const string AdornmentLayerName = "MyHighlightAdornment";
private readonly IAdornmentLayer _layer;
private readonly IWpfTextView _view;
private readonly Brush _brush;
public HighlightAdornment(IWpfTextView view)
{
_view = view;
_layer = view.GetAdornmentLayer(AdornmentLayerName);
_brush = new SolidColorBrush(Color.FromArgb(0x40, 0xFF, 0xFF, 0x00));
_brush.Freeze();
_view.LayoutChanged += OnLayoutChanged;
}
private void OnLayoutChanged(object sender, TextViewLayoutChangedEventArgs e)
{
foreach (ITextViewLine line in e.NewOrReformattedLines)
{
CreateVisuals(line);
}
}
private void CreateVisuals(ITextViewLine line)
{
IWpfTextViewLineCollection textViewLines = _view.TextViewLines;
string text = line.Extent.GetText();
int index = 0;
while ((index = text.IndexOf("TODO", index, StringComparison.OrdinalIgnoreCase)) >= 0)
{
var span = new SnapshotSpan(_view.TextSnapshot, line.Start + index, 4);
Geometry geometry = textViewLines.GetMarkerGeometry(span);
if (geometry != null)
{
var drawing = new GeometryDrawing(_brush, null, geometry);
drawing.Freeze();
var drawingImage = new DrawingImage(drawing);
drawingImage.Freeze();
var image = new Image
{
Source = drawingImage,
Width = geometry.Bounds.Width,
Height = geometry.Bounds.Height,
};
Canvas.SetLeft(image, geometry.Bounds.Left);
Canvas.SetTop(image, geometry.Bounds.Top);
_layer.AddAdornment(
AdornmentPositioningBehavior.TextRelative,
span,
tag: null,
adornment: image,
removedCallback: null);
}
index += 4;
}
}
}
| Behavior | Description |
|---|---|
AdornmentPositioningBehavior.TextRelative | Moves with the text it's attached to (most common) |
AdornmentPositioningBehavior.ViewportRelative | Fixed position in the viewport (e.g., watermarks) |
AdornmentPositioningBehavior.OwnerControlled | You manage the position manually |
Use [Order] to control where your adornment renders relative to built-in layers:
| Layer | Description |
|---|---|
PredefinedAdornmentLayers.Selection | The selection highlight |
PredefinedAdornmentLayers.Text | The actual text glyphs |
PredefinedAdornmentLayers.Caret | The caret |
PredefinedAdornmentLayers.CurrentLineHighlighter | Current line highlight |
PredefinedAdornmentLayers.TextMarker | Text marker decorations |
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using Microsoft.VisualStudio.Text.Editor;
namespace MyExtension;
internal sealed class WatermarkAdornment
{
private const string AdornmentLayerName = "MyWatermarkAdornment";
private readonly IAdornmentLayer _layer;
private readonly IWpfTextView _view;
private readonly UIElement _watermark;
public WatermarkAdornment(IWpfTextView view)
{
_view = view;
_layer = view.GetAdornmentLayer(AdornmentLayerName);
_watermark = new TextBlock
{
Text = "DRAFT",
FontSize = 48,
Foreground = new SolidColorBrush(Color.FromArgb(0x30, 0xFF, 0x00, 0x00)),
IsHitTestVisible = false,
};
_view.ViewportHeightChanged += (s, e) => Render();
_view.ViewportWidthChanged += (s, e) => Render();
Render();
}
private void Render()
{
_layer.RemoveAllAdornments();
Canvas.SetLeft(_watermark, _view.ViewportRight - 200);
Canvas.SetTop(_watermark, _view.ViewportTop + 10);
_layer.AddAdornment(
AdornmentPositioningBehavior.ViewportRelative,
null,
null,
_watermark,
null);
}
}
UIElement instances drawn on an IAdornmentLayer.LayoutChanged for text-relative adornments to redraw on scroll/edit.IsHitTestVisible = false on adornments that should not intercept mouse clicks.[ContentType] attribute controls which file types activate your adornment (e.g., "CSharp", "text", "code").[TextViewRole] attribute limits which editor instances trigger your listener (use PredefinedTextViewRoles.Document to avoid triggering in peek, diff, etc.)..vsixmanifest — see the top of this document.IWpfTextViewCreationListener with MEF exports. Define an AdornmentLayerDefinition, subscribe to LayoutChanged, and draw WPF elements on the layer.source.extension.vsixmanifest or the adornment will silently fail to load.ContentType and TextViewRole to scope your adornment to the right editors..vsixmanifest (the #1 cause). Also verify [ContentType] matches the file type you're testing with and [TextViewRole] isn't filtering out the editor instance.textViewLines.GetCharacterBounds() or GetTextMarkerGeometry() to calculate positions. Character positions change on scroll and view resize — recalculate in LayoutChanged.LayoutChanged handler is doing too much work. Pre-compute data on a background thread and only draw from cache during layout.IsHitTestVisible = false on decorative (non-interactive) adornment elements.ITextView.Closed to remove your handlers.Do NOT forget
IsHitTestVisible = falseon decorative adornment elements — without it, they steal mouse clicks and selections from the editor text underneath.
Do NOT forget to unsubscribe from
ITextView.LayoutChangedwhen the view closes — subscribe toITextView.Closedand remove handlers there. Leaked subscriptions cause memory leaks.
Do NOT forget the
MefComponentasset type in.vsixmanifest— without it, yourIWpfTextViewCreationListeneris silently ignored (#1 cause of "my adornment doesn't show up").
Do NOT do expensive work in
LayoutChanged— it fires on every scroll, resize, and edit. Pre-compute data on a background thread; only read from cache during layout.
Do NOT attempt to use VisualStudio.Extensibility for editor adornments — it doesn't support them. The VSSDK in-process MEF approach is the only option.
npx claudepluginhub madskristensen/vs-agent-plugins --plugin vs-extensibility-skillsExplains how to add custom margins (gutter, side panel, bottom bar) to the Visual Studio text editor via VS SDK, VSIX Toolkit, or VisualStudio.Extensibility. Includes MEF configuration and margin vs. adornment guidance.
Provides behavioral guidelines to reduce common LLM coding mistakes, focusing on simplicity, surgical changes, assumption surfacing, and verifiable success criteria.
Searches, retrieves, and installs Agent Skills from prompts.chat registry using MCP tools like search_skills and get_skill. Activates for finding skills, browsing catalogs, or extending Claude.