MEF Components

The Visual Studio editor uses MEF (Managed Extensibility Framework) for extensibility. MEF components are automatically discovered and composed at runtime without explicit registration.

MEF Basics

Export and Import

MEF uses attributes to declare exports and imports:

// Export a service
[Export(typeof(IMyService))]
public class MyService : IMyService
{
    public void DoWork() { }
}

// Import the service elsewhere
[Export(typeof(IMyConsumer))]
public class MyConsumer : IMyConsumer
{
    [Import]
    private IMyService _service;

    public void Consume()
    {
        _service.DoWork();
    }
}

Metadata

Add metadata to exports:

[Export(typeof(IMyHandler))]
[ExportMetadata("Name", "MyHandler")]
[ExportMetadata("Priority", 100)]
public class MyHandler : IMyHandler { }

// Import with metadata
[ImportMany]
private IEnumerable<Lazy<IMyHandler, IHandlerMetadata>> _handlers;

Editor Extension Points

Text View Creation Listener

React when editors open:

[Export(typeof(IWpfTextViewCreationListener))]
[ContentType("CSharp")]
[TextViewRole(PredefinedTextViewRoles.Document)]
internal sealed class CSharpEditorListener : IWpfTextViewCreationListener
{
    [Import]
    internal IClassificationFormatMapService FormatMapService { get; set; }

    [Import]
    internal IEditorFormatMapService EditorFormatMapService { get; set; }

    public void TextViewCreated(IWpfTextView textView)
    {
        // Called when a C# document editor is created
        textView.Properties.GetOrCreateSingletonProperty(
            () => new CSharpEditorEnhancement(textView, FormatMapService));
    }
}

Content Type Definition

Define custom content types:

internal static class ContentTypes
{
    [Export]
    [Name("myLanguage")]
    [BaseDefinition("code")]
    internal static ContentTypeDefinition MyLanguageContentType;

    [Export]
    [FileExtension(".mylang")]
    [ContentType("myLanguage")]
    internal static FileExtensionToContentTypeDefinition MyLanguageFileExtension;
}
Note

Content types form a hierarchy. Defining BaseDefinition("code") means your type inherits all code editor features.

Classification (Syntax Highlighting)

Classification Type Definition

internal static class ClassificationTypes
{
    [Export(typeof(ClassificationTypeDefinition))]
    [Name("myLanguage.keyword")]
    internal static ClassificationTypeDefinition KeywordType;

    [Export(typeof(ClassificationTypeDefinition))]
    [Name("myLanguage.string")]
    internal static ClassificationTypeDefinition StringType;

    [Export(typeof(ClassificationTypeDefinition))]
    [Name("myLanguage.comment")]
    internal static ClassificationTypeDefinition CommentType;
}

Classification Format Definition

[Export(typeof(EditorFormatDefinition))]
[ClassificationType(ClassificationTypeNames = "myLanguage.keyword")]
[Name("myLanguage.keyword")]
[UserVisible(true)]
[Order(Before = Priority.Default)]
internal sealed class KeywordFormat : ClassificationFormatDefinition
{
    public KeywordFormat()
    {
        DisplayName = "MyLanguage Keyword";
        ForegroundColor = Colors.Blue;
        IsBold = true;
    }
}

[Export(typeof(EditorFormatDefinition))]
[ClassificationType(ClassificationTypeNames = "myLanguage.string")]
[Name("myLanguage.string")]
[UserVisible(true)]
internal sealed class StringFormat : ClassificationFormatDefinition
{
    public StringFormat()
    {
        DisplayName = "MyLanguage String";
        ForegroundColor = Color.FromRgb(214, 157, 133);
    }
}

Classifier Provider

[Export(typeof(IClassifierProvider))]
[ContentType("myLanguage")]
internal sealed class MyLanguageClassifierProvider : IClassifierProvider
{
    [Import]
    internal IClassificationTypeRegistryService ClassificationRegistry { get; set; }

    public IClassifier GetClassifier(ITextBuffer buffer)
    {
        return buffer.Properties.GetOrCreateSingletonProperty(
            () => new MyLanguageClassifier(ClassificationRegistry));
    }
}

internal sealed class MyLanguageClassifier : IClassifier
{
    private readonly IClassificationType _keywordType;
    private readonly IClassificationType _stringType;
    private readonly IClassificationType _commentType;

    public MyLanguageClassifier(IClassificationTypeRegistryService registry)
    {
        _keywordType = registry.GetClassificationType("myLanguage.keyword");
        _stringType = registry.GetClassificationType("myLanguage.string");
        _commentType = registry.GetClassificationType("myLanguage.comment");
    }

    public event EventHandler<ClassificationChangedEventArgs> ClassificationChanged;

    public IList<ClassificationSpan> GetClassificationSpans(SnapshotSpan span)
    {
        var result = new List<ClassificationSpan>();
        var text = span.GetText();

        // Example: classify keywords
        var keywords = new[] { "if", "else", "for", "while", "return" };
        foreach (var keyword in keywords)
        {
            var index = 0;
            while ((index = text.IndexOf(keyword, index)) >= 0)
            {
                var keywordSpan = new SnapshotSpan(span.Snapshot, span.Start + index, keyword.Length);
                result.Add(new ClassificationSpan(keywordSpan, _keywordType));
                index += keyword.Length;
            }
        }

        return result;
    }
}

Adornments

Adornment Layer Definition

[Export(typeof(AdornmentLayerDefinition))]
[Name("MyAdornment")]
[Order(After = PredefinedAdornmentLayers.Selection, Before = PredefinedAdornmentLayers.Text)]
[TextViewRole(PredefinedTextViewRoles.Document)]
internal AdornmentLayerDefinition _adornmentLayer;

Viewport Adornment

Add decorations to the editor viewport:

[Export(typeof(IWpfTextViewCreationListener))]
[ContentType("text")]
[TextViewRole(PredefinedTextViewRoles.Document)]
internal sealed class ViewportAdornmentFactory : IWpfTextViewCreationListener
{
    [Export(typeof(AdornmentLayerDefinition))]
    [Name("ViewportAdornment")]
    [Order(After = PredefinedAdornmentLayers.Caret)]
    private AdornmentLayerDefinition _adornmentLayer;

    public void TextViewCreated(IWpfTextView textView)
    {
        new ViewportAdornment(textView);
    }
}

internal sealed class ViewportAdornment
{
    private readonly IAdornmentLayer _layer;
    private readonly IWpfTextView _view;
    private readonly Canvas _canvas;

    public ViewportAdornment(IWpfTextView view)
    {
        _view = view;
        _layer = view.GetAdornmentLayer("ViewportAdornment");

        _canvas = new Canvas();
        _canvas.Children.Add(new TextBlock
        {
            Text = "My Adornment",
            Foreground = Brushes.Gray,
            FontSize = 10
        });

        _view.ViewportHeightChanged += OnViewportChanged;
        _view.ViewportWidthChanged += OnViewportChanged;

        UpdatePosition();
    }

    private void OnViewportChanged(object sender, EventArgs e)
    {
        UpdatePosition();
    }

    private void UpdatePosition()
    {
        _layer.RemoveAllAdornments();

        Canvas.SetLeft(_canvas, _view.ViewportRight - 100);
        Canvas.SetTop(_canvas, _view.ViewportBottom - 20);

        _layer.AddAdornment(AdornmentPositioningBehavior.ViewportRelative,
            null, null, _canvas, null);
    }
}

Completion (IntelliSense)

Completion Source Provider

[Export(typeof(ICompletionSourceProvider))]
[ContentType("myLanguage")]
[Name("myLanguageCompletion")]
internal sealed class MyCompletionSourceProvider : ICompletionSourceProvider
{
    [Import]
    internal IGlyphService GlyphService { get; set; }

    public ICompletionSource TryCreateCompletionSource(ITextBuffer textBuffer)
    {
        return new MyCompletionSource(textBuffer, GlyphService);
    }
}

internal sealed class MyCompletionSource : ICompletionSource
{
    private readonly ITextBuffer _buffer;
    private readonly IGlyphService _glyphService;

    public MyCompletionSource(ITextBuffer buffer, IGlyphService glyphService)
    {
        _buffer = buffer;
        _glyphService = glyphService;
    }

    public void AugmentCompletionSession(
        ICompletionSession session,
        IList<CompletionSet> completionSets)
    {
        var completions = new List<Completion>
        {
            new Completion("if", "if", "Conditional statement",
                _glyphService.GetGlyph(StandardGlyphGroup.GlyphKeyword, StandardGlyphItem.GlyphItemPublic), null),
            new Completion("for", "for", "Loop statement",
                _glyphService.GetGlyph(StandardGlyphGroup.GlyphKeyword, StandardGlyphItem.GlyphItemPublic), null),
            new Completion("while", "while", "Loop statement",
                _glyphService.GetGlyph(StandardGlyphGroup.GlyphKeyword, StandardGlyphItem.GlyphItemPublic), null),
        };

        var applicableTo = session.TextView.TextSnapshot.CreateTrackingSpan(
            session.GetTriggerPoint(session.TextView.TextBuffer).Value.Position,
            0,
            SpanTrackingMode.EdgeInclusive);

        completionSets.Add(new CompletionSet("Keywords", "Keywords", applicableTo, completions, null));
    }

    public void Dispose() { }
}

Quick Info (Hover Tooltips)

[Export(typeof(IAsyncQuickInfoSourceProvider))]
[Name("myLanguageQuickInfo")]
[ContentType("myLanguage")]
internal sealed class MyQuickInfoSourceProvider : IAsyncQuickInfoSourceProvider
{
    public IAsyncQuickInfoSource TryCreateQuickInfoSource(ITextBuffer textBuffer)
    {
        return new MyQuickInfoSource(textBuffer);
    }
}

internal sealed class MyQuickInfoSource : IAsyncQuickInfoSource
{
    private readonly ITextBuffer _buffer;

    public MyQuickInfoSource(ITextBuffer buffer)
    {
        _buffer = buffer;
    }

    public async Task<QuickInfoItem> GetQuickInfoItemAsync(
        IAsyncQuickInfoSession session,
        CancellationToken cancellationToken)
    {
        var triggerPoint = session.GetTriggerPoint(_buffer.CurrentSnapshot);
        if (triggerPoint == null) return null;

        var line = triggerPoint.Value.GetContainingLine();
        var lineText = line.GetText();

        // Example: show info for specific words
        var word = GetWordAtPoint(triggerPoint.Value);
        if (word == "myKeyword")
        {
            var span = new TrackingSpan(...);
            return new QuickInfoItem(span,
                new ContainerElement(ContainerElementStyle.Stacked,
                    new ClassifiedTextElement(
                        new ClassifiedTextRun(PredefinedClassificationTypeNames.Keyword, "myKeyword"),
                        new ClassifiedTextRun(PredefinedClassificationTypeNames.Text, " - A special keyword"))));
        }

        return null;
    }

    public void Dispose() { }
}

Best Practices

Lazy Loading

Use Lazy<T> for expensive imports:

[Import]
private Lazy<IExpensiveService> _service;

public void DoWork()
{
    // Service only created when accessed
    _service.Value.Process();
}

Disposing Resources

Implement proper disposal:

internal sealed class MyComponent : IDisposable
{
    private readonly IWpfTextView _textView;
    private bool _disposed;

    public MyComponent(IWpfTextView textView)
    {
        _textView = textView;
        _textView.Closed += OnClosed;
    }

    private void OnClosed(object sender, EventArgs e)
    {
        Dispose();
    }

    public void Dispose()
    {
        if (!_disposed)
        {
            _textView.Closed -= OnClosed;
            _disposed = true;
        }
    }
}

Next Steps

Learn about Language Services for complete language support including IntelliSense and navigation.