Is CodeLens Enabled?

Is CodeLens Enabled?

In the CodeStream extension for Visual Studio, we recently added our own CodeLens provider (I blogged a little about THAT problem in another post) that provides information from New Relics observability platform / agents. The idea being that you could see at the method level, what the average duration, throughput, or errors, have been recorded over a certain timeframe. More on that that some other time šŸ˜Š.

What this did, however, is gave us cause for figuring out if CodeLens is enabled in Visual Studio at all (our provider doesnā€™t do much good if everything is disabled).

The questions are / become -

  • Is CodeLens Enabled?
  • Is our custom CodeLens provider enabled?

If either of them is ā€œfalseā€, then we can provide some markup in the extension to explain how to turn it on, etc. I.e., ā€œif you want to use this feature, you must turn on ā€œthisā€ and ā€œthatā€. Go to ā€œthis settings page to enableā€, etc.

Okay, so problem explained. Now, how do we solve it?

Well, turns out there is some magic (of course, it is Visual Studio extensibility!), but also not extremely difficult to get working either.

Iā€™m not going to go into great detail about the service provider, MEF, or COM, but if youā€™re doing any kind of VS extensibility, youā€™re probably somewhat familiar with this.

First, I created a new interface and service, because DI / IOC / MEF / etc. -

public interface IVsSettings
{
    bool IsCodeLevelMetricsEnabled();
    bool IsCodeLensEnabled();
}

[Export(typeof(IVsSettings))]
[PartCreationPolicy(CreationPolicy.Shared)]
public class VsSettings : IVsSettings
{
    private readonly ISettingsManager _roamingSettingsManager;

    [ImportingConstructor]
    public VsSettings([Import(typeof(SVsServiceProvider))] IServiceProvider serviceProvider)
    {
        _roamingSettingsManager = serviceProvider.GetService(typeof(SVsSettingsPersistenceManager)) as ISettingsManager;
    }
}

We need to inject the service provider here, so that we can resolve the settings manager - and here is why - the type SVsSettingsPersistenceManager is in an internal assembly that we canā€™t access without declaring our own ā€œversionā€ of it.

Adding to our definitions above, we now have this -

public interface IVsSettings
{
    bool IsCodeLevelMetricsEnabled();
    bool IsCodeLensEnabled();
}

[Export(typeof(IVsSettings))]
[PartCreationPolicy(CreationPolicy.Shared)]
public class VsSettings : IVsSettings
{
    private readonly ISettingsManager _settingsManager;

    //      THIS PART HERE
    [Guid("9B164E40-C3A2-4363-9BC5-EB4039DEF653")]
    private class SVsSettingsPersistenceManager { }

    [ImportingConstructor]
    public VsSettings([Import(typeof(SVsServiceProvider))] IServiceProvider serviceProvider)
    {
        _settingsManager = serviceProvider.GetService(typeof(SVsSettingsPersistenceManager)) as ISettingsManager;
    }
}

Now, I canā€™t really explain how / what / why thatā€™s needed - or what that GUID even matches to, honestly. I was led into this solution by reading the source for the Settings Store Explorer extension - which is a FANTASTIC extension for figuring out all the settings and their values for Visual Studio.

Once thatā€™s done, I added a (generic) method to obtain a setting from the store -

public interface IVsSettings
{
    bool IsCodeLevelMetricsEnabled();
    bool IsCodeLensEnabled();
}

[Export(typeof(IVsSettings))]
[PartCreationPolicy(CreationPolicy.Shared)]
public class VsSettings : IVsSettings
{
    private readonly ISettingsManager _settingsManager;

    [Guid("9B164E40-C3A2-4363-9BC5-EB4039DEF653")]
    private class SVsSettingsPersistenceManager { }

    [ImportingConstructor]
    public VsSettings([Import(typeof(SVsServiceProvider))] IServiceProvider serviceProvider)
    {
        _settingsManager = serviceProvider.GetService(typeof(SVsSettingsPersistenceManager)) as ISettingsManager;
    }

    //     THIS PART
    public T GetSetting<T>(string settingPath)
    {
        return _settingsManager.TryGetValue(settingPath, out T value); // could throw an exception
    }
}

Now you can inject an IVsSettings into some other class / service, and utilize it like so -

[ImportingConstructor]
public SomeOtherService(IVsSettings vsSettings)
{
    var isCodeLensEnabled = vsSettings.GetSetting<bool>("TextEditorGlobalOptions.IsCodeLensEnabled");
    var disabledCodeLensProviders = vsSettings.GetSetting<string[]>("TextEditorGlobalOptions.CodeLensDisabledProviders");
}

If you made it this far, thank you! I hope you enjoyed or learned something from the various trials and tribulations Iā€™ve been through over the past week trying to get this working.

Until next time, friends!


This post, "Is CodeLens Enabled?", first appeared on https://www.codingwithcalvin.net/is-codelens-enabled/