Question

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.
}
Was it helpful?

Solution

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));
    }
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top