Custom keyword coloring in Visual Studio 2010+
Asked Answered
W

3

12

I'm trying to add custom coloring for only certain keywords in my Visual Studio editor for C# code. I want to be able to color any type that implements IDisposable as a different color. Ideally I'd like to create a simple list of classes/interfaces that derive from IDisposable in some sort of configuration that I can edit. (Although if you said there was a method/plugin that would automatically find all disposable types and color them independently that would be the Holy Grail).

I've done a ton of research and it looks like an "editor classifier" extension might do the trick. However I created one that merely tries to color the word "Stream" and although it does hit my code that attempts to highlight that word, it does not end up highlighted in the editor.

I have added my VS extension to Github here

This really seems like this should be fairly straightforward but I have gone down many alleys on this one only to find dead-ends. Is there a simpler way to do this, or is my extension broken?

Update

Very strange. I just ran my extension again and although it does not highlight the text in the editor it highlights all instances of "Stream" in the popup text when you hover over a type/variable! Is there any way to get it to apply to the editor?

enter image description here

Willable answered 10/7, 2014 at 20:47 Comment(10)
This would be wondrous.Vestige
I downloaded your extension code and ran through it locally, It looks like it's working correctly for me. Only difference I can think of it I'm running in VS2012 vs. 2010 so I had to re-make a project and copy the code over. your vsixmanifest didn't play nice on my computer, maybe the 2010->2012 upgrade broke it somehowSapsago
See my updates about the tooltip text. Am I misunderstanding what an editor classifier is?Willable
@mjmarsh updated my answer. it may contain the solution. unfortunately i can't try myself. i dont have the necessary references. judging by topic+class names it seems to be what you need though.Stryker
This worked for the most part, although is surrounds the text wit ha colored rectangle rather than changing the text color. If I can't find a way to do the latter I will mark it as the answerWillable
Don't make an IClassifier (that will force the background colour of the tagged area to be set, among other things), use an ITagger<ClassificationSpan> and export an ITaggerProvider for it.Reni
And make sure the ContentType attribute on the provider is set to "csharp" (case-insensitive). You also need an [TagType(typeof(ClassificationTag))] attribute on the ITaggerProvider, I believe.Reni
@mjmarsh Ok. If you can update your github project and add the required assemblies i can fiddle around with it too. I think i was missing references for VisualStudio.Utility and others, which weren't on nuget. Unless you get it working with the additional indications from Cameron that is :)Stryker
@AndrewWalters when you say it is running correctly you are seeing "Stream" colored a different color than all other class types?Willable
@mjmarsh I am, here's a screenshot: i.sstatic.net/1fLj7.png It might be related to the content type. gitHub project has "text" vs. the new screenshot in your question which looks like it's "code"Sapsago
S
3

Depending on wether you are using Jetbrains Resharper or not you may write a plugin for that. That way you are able not only to add visual notification of IDisposable on a variable but also provide quickfixes if, and only if, it is not beeing called, which is what i am assuming you want to catch. Mind you that i can imagine that there's already a R# plugin for that. I know i've considered this too, but i was too lazy to write a plugin for that.

Don't get me wrong btw - If you're not using r# yet you should consider trying it out.

Among others you'd be working with this: API-QuickFix

There are also ways to define custom keywords, as resharper does, given by a custom markup and apply quickfixes to that.

PS: No i don't work at jetbrains. it's just that good :)

UPDATE:

potential VS Extension fix?

check this one out: MSDN Link Highlighting Text

I tried opening your github project but couldn't so i thought i'll just check msdn instead. it seems you are deriving from the wrong class to fulfill your needs?

MSDN keyword "Editors - Extending the Editor - Walkthrough: Highlighting Text"

I know SO wants code on the site, but msdn links going down is rather unlikely and with the given information the content can be found easily enough :)

Stryker answered 14/7, 2014 at 15:12 Comment(2)
Love Resharper but it hasn't made the budget the last handful of years. Hoping for a free or DIY solutionWillable
@mjmarsh hey. what's your status then? got it working? didn't see a github update yetStryker
R
2

I'm a bit late to the party, but hey, why not throw my 2 cents in.

As you've explained in your question, your project has two basic parts:

  1. Finding the classes that implement IDisposable
  2. Highlighting them

The first is by far the hardest, though not impossible. A word-list based approach is probably the simplest, though it should be possible with Roslyn to figure out on the fly which classes inherit IDisposible.

You could also always resort to loading the project's compiled .exe/.dll in the background after a build and figuring out what the types are there, but you'd still have to write some magic glue code to figure out what short class names in the code referred to what actual full-name classes in the assembly.

The second part, highlighting, is quite easy once you know how to do it (it helps that I've spent the last several months working full-time on extending VS). Of course, with Visual Studio, nothing is as simple as it looks (despite the efforts of Microsoft to try to make it user-friendly). So, I've built a sample extension that highlights just classes named "Stream" within C# files to get you started.

highlighting at work

The relevant code is below, and the full project source is on GitHub). It starts with a classification-tagger provider:

[Export(typeof(ITaggerProvider))]
[ContentType("CSharp")]
[TagType(typeof(ClassificationTag))]
[Name("HighlightDisposableTagger")]
public class HighlightDisposableTaggerProvider : ITaggerProvider
{
    [Import]
    private IClassificationTypeRegistryService _classificationRegistry = null;

    [Import]
    private IClassifierAggregatorService _classifierAggregator = null;

    private bool _reentrant;

    public ITagger<T> CreateTagger<T>(ITextBuffer buffer) where T : ITag
    {
        if (_reentrant)
            return null;

        try {
            _reentrant = true;
            var classifier = _classifierAggregator.GetClassifier(buffer);
            return new HighlightDisposableTagger(buffer, _classificationRegistry, classifier) as ITagger<T>;
        }
        finally {
            _reentrant = false;
        }
    }
}

Then the tagger itself:

public class HighlightDisposableTagger : ITagger<ClassificationTag>
{
    private const string DisposableFormatName = "HighlightDisposableFormat";

    [Export]
    [Name(DisposableFormatName)]
    public static ClassificationTypeDefinition DisposableFormatType = null;

    [Export(typeof(EditorFormatDefinition))]
    [Name(DisposableFormatName)]
    [ClassificationType(ClassificationTypeNames = DisposableFormatName)]
    [UserVisible(true)]
    public class DisposableFormatDefinition : ClassificationFormatDefinition
    {
        public DisposableFormatDefinition()
        {
            DisplayName = "Disposable Format";
            ForegroundColor = Color.FromRgb(0xFF, 0x00, 0x00);
        }
    }

    public event EventHandler<SnapshotSpanEventArgs> TagsChanged = delegate { };

    private ITextBuffer _subjectBuffer;
    private ClassificationTag _tag;
    private IClassifier _classifier;
    private bool _reentrant;

    public HighlightDisposableTagger(ITextBuffer subjectBuffer, IClassificationTypeRegistryService typeService, IClassifier classifier)
    {
        _subjectBuffer = subjectBuffer;

        var classificationType = typeService.GetClassificationType(DisposableFormatName);
        _tag = new ClassificationTag(classificationType);
        _classifier = classifier;
    }

    public IEnumerable<ITagSpan<ClassificationTag>> GetTags(NormalizedSnapshotSpanCollection spans)
    {
        if (_reentrant) {
            return Enumerable.Empty<ITagSpan<ClassificationTag>>();
        }

        var tags = new List<ITagSpan<ClassificationTag>>();
        try {
            _reentrant = true;

            foreach (var span in spans) {
                if (span.IsEmpty)
                    continue;

                foreach (var token in _classifier.GetClassificationSpans(span)) {
                    if (token.ClassificationType.IsOfType(/*PredefinedClassificationTypeNames.Identifier*/ "User Types")) {
                        // TODO: Somehow figure out if this refers to a class which implements IDisposable
                        if (token.Span.GetText() == "Stream") {
                            tags.Add(new TagSpan<ClassificationTag>(token.Span, _tag));
                        }
                    }
                }
            }
            return tags;
        }
        finally {
            _reentrant = false;
        }
    }
}

I've only tested this on VS2010, but it should work for VS2013 too (the only thing that might be different is the class classification name, but that's easy to discover with a well-placed breakpoint). I've never written an extension for VS2012, so I can't comment on that, but I know it's quite close to VS2013 in most respects.

Reni answered 22/7, 2014 at 3:46 Comment(0)
A
1

So, one possible solution(I believe this one works):

1) Create your own content type which inherits from csharp.

2) Create new TextViewCreationListener which will swap out all "csharp" content types with your own one, thus potentially "disarming" all the other classifiers.

3) Register your classifier to handle your own content type.

Here is some of the code:

[Export(typeof(IVsTextViewCreationListener))]
[ContentType("csharp")]
[TextViewRole(PredefinedTextViewRoles.Editable)]
class TextViewCreationListener : IVsTextViewCreationListener {
    internal readonly IVsEditorAdaptersFactoryService _adaptersFactory;

    [Import] internal IContentTypeRegistryService ContentTypeRegistryService = null;

    [ImportingConstructor]
    public TextViewCreationListener(IVsEditorAdaptersFactoryService adaptersFactory) {
        _adaptersFactory = adaptersFactory;
    }

    #region IVsTextViewCreationListener Members

    public void VsTextViewCreated(VisualStudio.TextManager.Interop.IVsTextView textViewAdapter) {
        var textView = _adaptersFactory.GetWpfTextView(textViewAdapter);

        var myContent = ContentTypeRegistryService.GetContentType(MyContentType);
        if(myContent == null)
        {
            ContentTypeRegistryService.AddContentType(MyContentType, new[] {"csharp"});
            myContent = ContentTypeRegistryService.GetContentType(MyContentType);
        }

        // some kind of check if the content type is not already MyContentType. 
        textView.TextBuffer.ChangeContentType(myContent, null);
    }

    #endregion
}

And now, just modify your IClassifierProvider to register with your own content type, as such: [ContentType(MyContentType)]

Iin your own IClassifier, you can basically do your own calculation and once you think you can't handle the stuff, you could pass the control to other classifiers.

If you use MEF and import IClassifierAggregatorService, you can get a "MASTER-classifier" which will run all the logic for you. I haven't implemented it yet, but I've suggestes something similiar in the past, and it seemed to work. Alternatively you could maybe use [ImportMany] with List<IClassifier> and filter out the csharp ones?!

Angeliqueangelis answered 16/7, 2014 at 14:59 Comment(2)
The Order attribute on editor format definitions only affects the order they're displayed in the Fonts & Colours options. As for applying Order to the provider itself, you can use Order(After = Priority.Default), but I'm not entirely sure anybody looks at that attribute when running the classifiers.Reni
I have edited my answer with new possible answer. As for your comment, it's true. Before actually posting the previous answer, I did some of the decompiling and found absolutely no evidence to suggest that Order[()] would work for classifiers, though I saw it to be used in some of Microsoft code and thought it it's worth the shot.Angeliqueangelis

© 2022 - 2024 — McMap. All rights reserved.