Visual Studio 2010 SDK -- How to place an adornment next to XML comment groups?
Asked Answered
M

1

7

I'm having trouble finding out how to do this and the Visual Studio SDK Reference is not very helpful either.

I'm trying to figure out how to get a NormalizedSnapshotSpanCollection of XML comments. I want to place an icon next to them... I don't want an icon next to each line, but only next to the first line of each group...

///<summary>SomeXML Comment</summary>   [ICON]
///<remarks>some remarks</remarks>
public void Foo()
{
    ///Some false XML comment line that does not get an icon.
}
Murderous answered 14/2, 2012 at 19:11 Comment(3)
I don't think I can give you a full answer, unfortunately, however I can point you in the direction that you'll at least need to understand IAdornmentLayer (that's how you draw on top of the view). The view itself is a type of IWpfTextView. That's the part for drawing the actual icon on the screen. Figuring out the exact position I can't help you with because I have no idea how to discover the top-right of an xml-comment block, let alone one that is specifically above a method/field/property/class/etc.Honan
why do you need a NormalizedSnapshotSpanCollection?? do you know you can get from IWpfTextView all lines for current editor buffer, and using a simple regex find all that match ///<summary>whatever</summary> line then add a icon, (in your adornment layer), on the coordinates you want on that line?Confute
No, I have no idea about that. I just saw an example that used a NormalizedSnapshotSpancollection so I figured I needed that... basically, anything that will give me the coordinates I need without false positives so I can put an icon up where desired.Murderous
A
11

Here's what I could get, I think it's pretty similar to what you need. I'm going to update this with more details, if you have questions.

VS 2010 icon adornment

I started with this sample from VS 2010 SDK web site. It is already pretty close to what you need, but requires several more steps.


Download the C# version, unpack to a folder, compile. To run it and test you need to go to Project > Properties > Debug

You need to choose "Start External Program" option and set path to your VS 2010 app, for example C:\Program Files (x86)\Microsoft Visual Studio 10.0\Common7\IDE\devenv.exe

In the command line arguments set: /rootsuffix Exp

Now you should be able to run it, create some sample project in the opened VS, and if you type anywhere a six-digit number like 00AA00 it will be shown as a rectangle with the corresponding color. Close the debug VS instance.


Now let's edit some code. In ColorAdornmentTagger.cs comment the define #define HIDING_TEXT. This will show the adornment next to the text, not instead of it.

In the same file you need to find where SnapshotSpan adornmentSpan is initialized and change the line to:

SnapshotSpan adornmentSpan = new SnapshotSpan(colorTagSpans[0].End, 0);

This will place adornment after the text span, not before it.


In the ColorTagger.cs. Change the regex in constructor, so the constructor now looks like

    internal ColorTagger(ITextBuffer buffer)
        : base(
        buffer, 
        new[] { new Regex(@"/// <summary>.*", RegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.IgnoreCase) }
        )
    {
    }

This will set the regex to recognize the method commentary line.

Other methods in this class we won't use, you can comment them or return some random color.


In the 'ColorAdornment.cs'. This is the adornment WPF control itself. First change the base class from Button to ContentControl. Change the constructor of the class to

    internal ColorAdornment(ColorTag colorTag)
    {
        BitmapImage image = new BitmapImage(); 
        using (FileStream stream = File.OpenRead("c:\\temp\\sologo.png")) 
        { 
            image.BeginInit(); 
            image.StreamSource = stream; 
            image.CacheOption = BitmapCacheOption.OnLoad; 
            image.EndInit(); 
        }

        this.Content = new Image() { Margin = new Thickness(20,0,0,0), Width = 100, Height = 30, Source = image };
    }

You can change the image path to the image path you need. I just downloaded SO logo from Wikipedia and put into my temp folder.

Compile and run. You should be able to see the SO logo next to the comments in the debug VS instance.


Some extra remarks.

First, in this way you just get a working prototype to start with, you should rename the classes and clean-up the code for your needs.

Second, when I was debugging it my debug VS was freezing from time to time. I think this might be related to locks in the IntraTextAdornmentTagger.cs

If you also see freezing, try to update the following method in this way:

    protected void InvalidateSpans(IList<SnapshotSpan> spans)
    {
        if (spans.Count == 0)
            return;
        bool wasEmpty = false;
        lock (this.invalidatedSpans)
        {
            wasEmpty = this.invalidatedSpans.Count == 0;
            this.invalidatedSpans.AddRange(spans);
        }

        if (wasEmpty)
            this.view.VisualElement.Dispatcher.BeginInvoke(new Action(AsyncUpdate));
    }

and the AsyncUpdate in this way:

    private void AsyncUpdate()
    {
        // Store the snapshot that we're now current with and send an event
        // for the text that has changed.
        if (this.snapshot != this.view.TextBuffer.CurrentSnapshot)
        {
            this.snapshot = this.view.TextBuffer.CurrentSnapshot;

            Dictionary<SnapshotSpan, TAdornment> translatedAdornmentCache = new Dictionary<SnapshotSpan, TAdornment>();

            foreach (var keyValuePair in this.adornmentCache)
                translatedAdornmentCache.Add(keyValuePair.Key.TranslateTo(this.snapshot, SpanTrackingMode.EdgeExclusive), keyValuePair.Value);

            this.adornmentCache = translatedAdornmentCache;
        }

        List<SnapshotSpan> spansCopy;
        lock (this.invalidatedSpans)
        {
            spansCopy = this.invalidatedSpans.ToList();
            this.invalidatedSpans.Clear();
        }

        List<SnapshotSpan> translatedSpans = spansCopy.Select(s => s.TranslateTo(this.snapshot, SpanTrackingMode.EdgeInclusive)).ToList();

        if (translatedSpans.Count == 0)
            return;

        var start = translatedSpans.Select(span => span.Start).Min();
        var end = translatedSpans.Select(span => span.End).Max();

        RaiseTagsChanged(new SnapshotSpan(start, end));
    }
Allhallowtide answered 21/2, 2012 at 22:37 Comment(4)
Well, it's definitely a good starting point. I was hoping for some algorithm to detect that a comment block is above a valid class, enum, struct, property, field, method, etc. but this one is as close as I'm gonna get for now I think. It has some holes in the fact that it relies on /// <summary>* because I could put that elsewhere and get false positives (i know it doesn't make sense to do that). Either way, full +200 for helping out a lot.Murderous
Also, why do I need the /rootsuffix Exp parameter for the devenv.exe call?Murderous
@michael: Thanks! The /rootsuffix Exp is to safeguard the default VS from your experiment msdn.microsoft.com/en-us/library/bb166560(v=VS.80).aspxAllhallowtide
@michael: you're right regarding false positives (though it is quite unlikely to see that string anywhere else), you can try to come up with a better regext though matching class name, enum, field, or so... Another option would be to explore the SDK documentation, I think there is a way to use the editor's knowloedge about which part of the text belongs to which type in a program. Maybe there is a way to detect where's a comment for field or a class...Allhallowtide

© 2022 - 2024 — McMap. All rights reserved.