Muokkaa

Add CodeLens indicators

CodeLens indicators display inline information above code elements (classes, methods, properties) in the editor — references, test status, change history, and more. They're one of the most visible integration points in Visual Studio, surfacing contextual metadata directly where developers work without requiring them to open a separate window.

Use CodeLens when you want to show per-code-element metadata like ownership, test results, linked issues, or change frequency. For general tooltips on arbitrary text, use Quick Info instead. For inline visual decorations, consider text adornments or taggers.

Note

This guide uses the VisualStudio.Extensibility SDK. See Choose the right extensibility model for model comparison.

Full example

The complete implementation:

// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

namespace CodeLensSample;

using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.VisualStudio.Extensibility;
using Microsoft.VisualStudio.Extensibility.Editor;

/// <summary>
/// A sample CodeLens provider that shows the number of words in a method or type and provides custom UI when clicked.
/// </summary>
[VisualStudioContribution]
internal class WordCountCodeLensProvider : ExtensionPart, ICodeLensProvider, ITextViewChangedListener
{
    public event EventHandler<TextViewChangedArgs>? TextViewChanged;

    public TextViewExtensionConfiguration TextViewExtensionConfiguration => new()
    {
        AppliesTo = [DocumentFilter.FromDocumentType(DocumentType.KnownValues.Code)],
    };

#pragma warning disable CEE0027 // String not localized
    public CodeLensProviderConfiguration CodeLensProviderConfiguration =>
        new("Remote UI CodeLens Sample Provider")
        {
            Priority = 600,
        };
#pragma warning restore CEE0027 // String not localized

    public Task<CodeLens?> TryCreateCodeLensAsync(CodeElement codeElement, CodeElementContext codeElementContext, CancellationToken token)
    {
        if (codeElement.Kind == CodeElementKind.KnownValues.Method)
        {
            return Task.FromResult<CodeLens?>(new WordCountCodeLens(codeElement, codeElementContext, this.Extensibility, this));
        }

        return Task.FromResult<CodeLens?>(null);
    }

    public Task TextViewChangedAsync(TextViewChangedArgs args, CancellationToken cancellationToken)
    {
        this.TextViewChanged?.Invoke(this, args);
        return Task.CompletedTask;
    }
}

Tip

See the full working sample: CodeLensSample ↗

Related concepts: Editor concepts — text view lifecycle and editor architecture

Understand the example

When to use CodeLens vs. alternatives:

  • Inline metadata above code elements (methods, classes) → CodeLens (this page)
  • Hover tooltips on arbitrary text spans → Quick Info
  • Persistent panels alongside the editor → margins
  • Visual decorations overlaid on text → text adornments (not yet supported out-of-process — use in-process extensions)

Types of CodeLens

You can create various CodeLens types. Each type offers its own unique set of functionalities.

Text view CodeLens

A text view CodeLens provides text-based information to segments of code, and it's the simplest form of CodeLens. The following concepts illustrate how to create a text view CodeLens:

  • The ICodeLensProvider ↗ interface is the main interface to implement. Your implementation of this interface defines when your CodeLens activates, which segments of code that your CodeLens applies to, and how it displays.
  • Within your implementation of ICodeLensProvider ↗, you need to define when the CodeLens should be activated by implementing CodeLensProviderConfiguration ↗.
  • You also need to implement the TryCreateCodeLensAsync ↗ method. This method runs when your CodeLens is activated. In this method, you can define how and when you want your CodeLens to be displayed. This method returns an instance of CodeLens ↗.
  • You need to create your own subclass of CodeLens ↗, where you get to define how you want your display text to appear through the GetLabelAsync ↗ method. This method returns an instance of CodeLensLabel ↗, which you can use to configure the text, a tooltip, and an optional icon.

Invokable CodeLens

An invokable CodeLens allows extensions to perform some action (for example, running a unit test) when the user selects the CodeLens. Extensions can contribute an invokable CodeLens by implementing InvokableCodeLens ↗, which derives from CodeLens ↗.

Visual CodeLens

A visual CodeLens allows extensions to provide custom UI, like a list of references to a method, to appear in a popup above the CodeLens when the user selects the CodeLens. Extensions can contribute a visual CodeLens by implementing VisualCodeLens ↗, which derives from CodeLens ↗.

Similar to text view margins, because extensions in VisualStudio.Extensibility might be out of process from Visual Studio, a visual CodeLens provides a UI by creating a RemoteUserControl and the corresponding data template for that control. Some simple examples are shown in the following sections. We recommend that you read the Remote UI documentation when you create visual CodeLens UI content.

The sample splits the implementation across separate files. WordCountCodeLensProvider creates the visual CodeLens, WordCountCodeLens supplies its label and popup UI, and ClickableCodeLens shows a separate invokable CodeLens implementation:

// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

namespace CodeLensSample;

using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.VisualStudio.Extensibility;
using Microsoft.VisualStudio.Extensibility.Editor;

/// <summary>
/// A sample CodeLens provider that shows the number of words in a method or type and provides custom UI when clicked.
/// </summary>
[VisualStudioContribution]
internal class WordCountCodeLensProvider : ExtensionPart, ICodeLensProvider, ITextViewChangedListener
{
    public event EventHandler<TextViewChangedArgs>? TextViewChanged;

    public TextViewExtensionConfiguration TextViewExtensionConfiguration => new()
    {
        AppliesTo = [DocumentFilter.FromDocumentType(DocumentType.KnownValues.Code)],
    };

#pragma warning disable CEE0027 // String not localized
    public CodeLensProviderConfiguration CodeLensProviderConfiguration =>
        new("Remote UI CodeLens Sample Provider")
        {
            Priority = 600,
        };
#pragma warning restore CEE0027 // String not localized

    public Task<CodeLens?> TryCreateCodeLensAsync(CodeElement codeElement, CodeElementContext codeElementContext, CancellationToken token)
    {
        if (codeElement.Kind == CodeElementKind.KnownValues.Method)
        {
            return Task.FromResult<CodeLens?>(new WordCountCodeLens(codeElement, codeElementContext, this.Extensibility, this));
        }

        return Task.FromResult<CodeLens?>(null);
    }

    public Task TextViewChangedAsync(TextViewChangedArgs args, CancellationToken cancellationToken)
    {
        this.TextViewChanged?.Invoke(this, args);
        return Task.CompletedTask;
    }
}
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

namespace CodeLensSample;

using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.VisualStudio.Extensibility;
using Microsoft.VisualStudio.Extensibility.Editor;
using Microsoft.VisualStudio.RpcContracts.RemoteUI;

internal class WordCountCodeLens : VisualCodeLens
{
    private readonly WordCountData wordCountData;

    public WordCountCodeLens(CodeElement codeElement, CodeElementContext codeElementContext, VisualStudioExtensibility extensibility, WordCountCodeLensProvider wordCountCodeLensProvider)
    {
        this.wordCountData = new WordCountData(this);
        this.wordCountData.WordCount = this.CountWords(codeElementContext.Range);
        wordCountCodeLensProvider.TextViewChanged += this.OnTextViewChanged;
    }

    public override void Dispose()
    {
    }

    public override Task<CodeLensLabel> GetLabelAsync(CodeElementContext codeElementContext, CancellationToken token)
    {
        this.wordCountData.WordCount = this.CountWords(codeElementContext.Range);
        return Task.FromResult(new CodeLensLabel()
        {
            Text = $"Occurrences of \"{this.wordCountData.WordToMatch}\": {this.wordCountData.WordCount}",
            Tooltip = "Number of occurrences of a word in the text",
        });
    }

    public override Task<IRemoteUserControl> GetVisualizationAsync(CodeElementContext codeElementContext, IClientContext clientContext, CancellationToken token)
    {
        return Task.FromResult<IRemoteUserControl>(new WordCountCodeLensVisual(this.wordCountData, this));
    }

    internal void UpdateWordCount()
    {
        this.Invalidate();
    }

    private int CountWords(TextRange range)
    {
        var rangeText = range.CopyToString();
        var wordToMatch = this.wordCountData.WordToMatch;

        // Use Regex to find all matches of wordToMatch in rangeText
        var matches = Regex.Matches(rangeText, $@"\b{Regex.Escape(wordToMatch)}\b", RegexOptions.IgnoreCase);
        return matches.Count;
    }

    private void OnTextViewChanged(object? sender, TextViewChangedArgs e)
    {
        this.Invalidate();
    }
}
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

namespace CodeLensSample;

using System.Threading;
using System.Threading.Tasks;
using Microsoft.VisualStudio.Extensibility;
using Microsoft.VisualStudio.Extensibility.Editor;

internal class ClickableCodeLens : InvokableCodeLens
{
    private readonly CodeElement codeElement;
    private readonly VisualStudioExtensibility extensibility;
    private int clickCount;

    public ClickableCodeLens(CodeElement codeElement, VisualStudioExtensibility extensibility)
    {
        this.codeElement = codeElement;
        this.extensibility = extensibility;
    }

    public override void Dispose()
    {
    }

    public override Task ExecuteAsync(CodeElementContext codeElementContext, IClientContext clientContext, CancellationToken cancelToken)
    {
        this.clickCount++;
        this.Invalidate();
        return Task.CompletedTask;
    }

    public override Task<CodeLensLabel> GetLabelAsync(CodeElementContext codeElementContext, CancellationToken token)
    {
        return Task.FromResult(new CodeLensLabel()
        {
            Text = this.clickCount == 0 ? "Click me" : $"Clicked {this.clickCount} times",
            Tooltip = "Invokable CodeLens Sample Tooltip",
        });
    }
}

In addition to configuring CodeLens provider display names, CodeLens providers can also configure the priority of their CodeLens. The priority value is used to determine the relative ordering of your CodeLens respective to any other CodeLens. You can set the order through the Priority ↗ property in the CodeLensProviderConfiguration ↗ object.

If a CodeLens provider has to reference code elements that an existing Visual Studio feature hasn't already tagged, you can create a CodeLens tagger by deriving its provider from TextViewTaggerProvider<CodeLensTag, TTagger>.

Tips

✅ DO

  • Use CodeElementKind.KnownValues.Method (or .Class, .Property) to filter which code elements get CodeLens
  • Call this.Invalidate() to refresh CodeLens display when data changes
  • Return null from TryCreateCodeLensAsync if CodeLens doesn't apply to a given element
  • Track state per text view if your CodeLens is stateful (provider is singleton)
  • Use DocumentFilter.FromDocumentType(DocumentType.KnownValues.Code) for broad code file coverage

❌ DON'T

  • Don't do expensive computation in GetLabelAsync — it runs on every refresh
  • Don't forget that VS creates only one instance of your provider (manage state carefully)
  • Don't create CodeLens for non-code elements without a custom tagger

🔧 Troubleshooting

Symptom Fix
CodeLens not appearing Check AppliesTo document filter matches the file type; verify CodeLensProviderConfiguration is defined
CodeLens shows stale data Call this.Invalidate() when underlying data changes
CodeLens appears on wrong elements Verify codeElement.Kind check in TryCreateCodeLensAsync
Visual CodeLens popup is empty Ensure RemoteUserControl XAML is marked as EmbeddedResource

Next steps