How to create green (or blue) squiggle adornments with a Visual Studio extension
Asked Answered
B

3

9

I have a Visual Studio extension that show red error squiggles. I also like to provide squiggles with other colors, for example yellow for warnings.

Creating red squiggles can be done by extending the ITagger class, along the lines:

internal sealed class MySquigglesTagger : ITagger<IErrorTag> {
    public IEnumerable<ITagSpan<IErrorTag>> GetTags(NormalizedSnapshotSpanCollection spans) {
        foreach (IMappingTagSpan<MyTokenTag> myTokenTag in this._aggregator.GetTags(spans))        
            SnapshotSpan tagSpan = myTokenTag.Span.GetSpans(this._sourceBuffer)[0];
            yield return new TagSpan<IErrorTag>(tagSpan, new ErrorTag("Error", "some info about the error"));
        }
    }
}

What I have tried:

  1. My intuition (incorrectly) says that returning an ErrorTag with an different errorType may yield a different type of tag, but whatever string you pass it, the squiggles remain red. Eg. new ErrorTag("Warning") gives red squiggles. The MSDN documentation is almost nonexistent. See ErrorTag.
  2. In the Tagging namespace there is no mention of a different Tag class implementing ITag. I hoped a WarningTag or InfoTag existed.
  3. Asked a question on MSDN forum here.

Question: how to create green (or blue or yellow) squiggle adornments? Sadly, even arcane or convoluted solutions are appreciated...

I'm targeting VS2015 and VS2017.

Edit: While typing this question someone at the MSDN forum replied that it cannot be done with the current API. Is it really impossible to make yellow squiggles in Visual Studio?!

Bangui answered 11/4, 2017 at 8:50 Comment(7)
The Tagging namespace also has the TextMarkerTag, amongst others, that implement ITag. Did you read msdn.microsoft.com/en-us/library/dd885121.aspx?Bethany
@Bethany Yes, I've read that and implemented it for brace matching and keyword highlighting, but I can't see how to use a TextMarkerTag for squiggles. I guess I need to implement the IErrorTag.Bangui
Then you might want to try one of these values for the ErrorType, those seem to be what VS supports: msdn.microsoft.com/en-us/library/…Bethany
@C.Evenhuis, Yes! If you upgrade your flag into an answer you will be eligible for the price.Bangui
I must admit I couldn't have answered this without your feedback :)Bethany
Hi, can you post here your code? I'm trying to make something similar but I didn't had very much luckArvillaarvin
@IonutEnache I've added an answer with some code.Bangui
B
5

The PredefinedErrorTypeNames contains the supported values for the ErrorType property of the ErrorTag.

You got close with "Warning", but the value of PredefinedErrorTypeNames.Warning appears to be "compiler warning".

Bethany answered 13/4, 2017 at 12:51 Comment(1)
If this had been documented (by MS) simply by adding one link at at the place you need it, I wouldn't have wasted several days. I'll ask someone at MS to fix it.Bangui
B
1

Just to document my own question and answer.

Create a file SquigglesTaggerProvider.cs with the following content:

[Export(typeof(IViewTaggerProvider))]
[ContentType("Your Content Type")]
[TagType(typeof(ErrorTag))]
internal sealed class SquigglesTaggerProvider : IViewTaggerProvider {
    [Import]
    private IBufferTagAggregatorFactoryService _aggregatorFactory = null;

    public ITagger<T> CreateTagger<T>(ITextView textView, ITextBuffer buffer) where T : ITag {
        ITagger<T> sc() {
            return new SquigglesTagger(buffer, this._aggregatorFactory) as ITagger<T>;
        }
        return buffer.Properties.GetOrCreateSingletonProperty(sc);
    }
}

Create a file named SquigglesTagger.cs with the following content:

internal sealed class SquigglesTagger : ITagger<IErrorTag> {
    private readonly ITextBuffer _sourceBuffer;
    private readonly ITagAggregator<AsmTokenTag> _aggregator;

    internal SquigglesTagger(ITextBuffer buffer, IBufferTagAggregatorFactoryService aggregatorFactory) {
        this._sourceBuffer = buffer;            
        ITagAggregator<AsmTokenTag> sc() {   
            return aggregatorFactory.CreateTagAggregator<AsmTokenTag>(buffer);
        }
        this._aggregator = buffer.Properties.GetOrCreateSingletonProperty(sc);
    }

    public IEnumerable<ITagSpan<IErrorTag>> GetTags(NormalizedSnapshotSpanCollection spans) {
        foreach (IMappingTagSpan<MyTokenTag> myTokenTag in this._aggregator.GetTags(spans))        
            SnapshotSpan tagSpan = myTokenTag.Span.GetSpans(this._sourceBuffer)[0];
            yield return new TagSpan<IErrorTag>(tagSpan, new ErrorTag(PredefinedErrorTypeNames.SyntaxError, "some info about the error"));
        }
    }
}

The PredefinedErrorTypeNames has different errors predefined.

public const string SyntaxError = "syntax error";
public const string CompilerError = "compiler error";
public const string OtherError = "other error";
public const string Warning = "compiler warning";
public const string Suggestion = "suggestion";

The code is taken from my repository here.

Bangui answered 23/1, 2019 at 16:5 Comment(2)
I tried the above version but it doesn't work for me. When I made debugging on my code the variables looks ok, the right file and lines are selected, but in the end in the Visual Studio editor didn't appear any red squiggles. I must make a manual refresh of the VS editor? In this case, how I can do that?Arvillaarvin
@IonutEnache Have you linked a contenttype to a file extension? Is CreateTagger ever called? Could be many things that are not set up properly.Bangui
B
1

To be precise, the squiggle adornment has a Brush that is either a Red SolidColorBrush or is a brush that is determined by the exported EditorFormatDefinition with NameAttribute name matching that of the IErrorTag.ErrorType with a corresponding exported ErrorTypeDefinition that too has a NameAttribute matching the IErrorTag.ErrorType.

It is the ResourceDictionary from EditorFormatDefinition.CreateResourceDictionaryFromDefinition that determines the Brush. If you are not overriding this method then your exported EditorFormatDefinition can set either ForegroundColor for a SolidColorBrush or set ForegroundBrush to any Brush implementation.

These exports can be on the ITagger<T> implementation. For instance

class ErrorEditorFormatDefinition : EditorFormatDefinition
{

    public ErrorEditorFormatDefinition(Color errorColor, string displayName = null) : this(displayName)
    {
        this.ForegroundColor = errorColor;

    }
    public ErrorEditorFormatDefinition(Brush brush, string displayName = null) : this(displayName)
    {
        this.ForegroundBrush = brush;
    }
    private ErrorEditorFormatDefinition(string displayName)
    {
        if (displayName != null)
        {
            this.DisplayName = displayName;
        }
        this.BackgroundCustomizable = false;
    }
}

internal class ErrorTagger : ITagger<IErrorTag>
{
    public const string MethodNotCoveredErrorType = "MethodNotCovered";
    public const string MethodPartiallyCoveredErrorType = "MethodPartiallyCovered";
    public const string LineNotCoveredErrorType = "LineNotCovered";
    public const string LinePartiallyCoveredErrorType = "LinePartiallyCovered";

    public event EventHandler<SnapshotSpanEventArgs> TagsChanged;

    [Export(typeof(ErrorTypeDefinition))]
    [Name(MethodNotCoveredErrorType)]
    public ErrorTypeDefinition MethodNotCoveredErrorTypeDefinition { get; }

    [Export(typeof(ErrorTypeDefinition))]
    [Name(MethodPartiallyCoveredErrorType)]
    public ErrorTypeDefinition MethodPartiallyErrorTypeDefinition { get; }
    
    [Export(typeof(ErrorTypeDefinition))]
    [Name(LineNotCoveredErrorType)]
    public ErrorTypeDefinition LineNotCoveredErrorTypeDefinition { get; }
    
    [Export(typeof(ErrorTypeDefinition))]
    [Name(LinePartiallyCoveredErrorType)]
    public ErrorTypeDefinition LinePartiallyErrorTypeDefinition { get; }


    [Export(typeof(EditorFormatDefinition))]
    [Name(MethodNotCoveredErrorType)]
    [UserVisible(true)]
    public EditorFormatDefinition MethodNotCoveredErrorFormatDefinition { get; } = new ErrorEditorFormatDefinition(new SolidColorBrush(Colors.Pink));

    [Export(typeof(EditorFormatDefinition))]
    [Name(MethodPartiallyCoveredErrorType)]
    public EditorFormatDefinition MethodPartiallyCoveredErrorFormatDefinition { get; } = new ErrorEditorFormatDefinition(new LinearGradientBrush()
    {
        StartPoint = new System.Windows.Point(0, 0),
        EndPoint = new System.Windows.Point(1, 0),
        GradientStops = new GradientStopCollection
        {
            new GradientStop(Colors.Yellow, 0.0),
            new GradientStop(Colors.Red, 0.25),
            new GradientStop(Colors.Blue, 0.75),
            new GradientStop(Colors.LimeGreen, 1.0)
        }
    },"Call me what you want");
    
    [Name(LineNotCoveredErrorType)]
    [Export(typeof(EditorFormatDefinition))]
    public EditorFormatDefinition LineNotCoveredErrorFormatDefinition { get; } = new ErrorEditorFormatDefinition(Colors.Brown);
    [Name(LinePartiallyCoveredErrorType)]
    public EditorFormatDefinition LinePartiallyCoveredErrorFormatDefinition { get; } = new ErrorEditorFormatDefinition(Colors.Cyan);

    public IEnumerable<ITagSpan<IErrorTag>> GetTags(NormalizedSnapshotSpanCollection spans)
    {
        return new List<ITagSpan<IErrorTag>>
        {
            // determine as appropriate
            new TagSpan<IErrorTag>(new SnapshotSpan(spans[0].Snapshot, new Span(0, spans[0].Snapshot.Length)), new ErrorTag(MethodPartiallyCoveredErrorType, "Method partially covered")),
        };
    }
}

LinearGradientBrush squiggles

The PredefinedErrorTypeNames are just those that are guaranteed to have a matching ErrorTypeDefinition/EditorFormatDefinition.

public static class PredefinedErrorTypeNames { 
  /// <summary>Represents syntax errors.</summary> 
  public const string SyntaxError = "syntax error"; 
  /// <summary>Represents compiler errors.</summary> 
  public const string CompilerError = "compiler error"; 
  /// <summary>Represents other errors.</summary> 
  public const string OtherError = "other error"; 
  /// <summary>Represents compiler warnings.</summary> 
  public const string Warning = "compiler warning"; 
  /// <summary>Represents a suggestion with no visual treatment.</summary> 
  public const string Suggestion = "suggestion";
  ///<summary>Represents a suggestion with subtle visual treatment.</summary> 
  public const string HintedSuggestion = "hinted suggestion"; 
}

Note that Suggestion is not visible in Fonts And Colors - also check the DisplayName changes below for how to find these in Fonts And Colors.

[Export(typeof(EditorFormatDefinition))]
[Name("suggestion")]
[UserVisible(false)]
internal class SuggestionClassificationFormatDefinition : EditorFormatDefinition
{
    public SuggestionClassificationFormatDefinition()
    {
        this.ForegroundBrush = (Brush)Brushes.Transparent;
        this.BackgroundCustomizable = new bool?(false);
        this.DisplayName = "Suggestion";
    }
}

[Export(typeof(EditorFormatDefinition))]
[Name("compiler warning")]
[UserVisible(true)]
internal class WarningClassificationFormatDefinition : EditorFormatDefinition
{
    public WarningClassificationFormatDefinition()
    {
        this.ForegroundBrush = (Brush)new SolidColorBrush(Color.FromRgb((byte)0, (byte)128, (byte)0));
        this.BackgroundCustomizable = new bool?(false);
        this.DisplayName = "Warning";
    }
}

[Export(typeof(EditorFormatDefinition))]
[Name("compiler error")]
[UserVisible(true)]
internal class CompilerErrorClassificationFormatDefinition : EditorFormatDefinition
{
    public CompilerErrorClassificationFormatDefinition()
    {
        this.ForegroundBrush = (Brush)Brushes.Blue;
        this.BackgroundCustomizable = new bool?(false);
        this.DisplayName = "Compiler Error";
    }
}

[Export(typeof(EditorFormatDefinition))]
[Name("other error")]
[UserVisible(true)]
internal class OtherErrorClassificationFormatDefinition : EditorFormatDefinition
{
    public OtherErrorClassificationFormatDefinition()
    {
        this.ForegroundBrush = (Brush)new SolidColorBrush(Color.FromRgb((byte)149, (byte)23, (byte)149));
        this.BackgroundCustomizable = new bool?(false);
        this.DisplayName = "Other Error";
    }
}

[Export(typeof(EditorFormatDefinition))]
[Name("syntax error")]
[UserVisible(true)]
internal class SyntaxErrorFormatDefinition : EditorFormatDefinition
{
    public SyntaxErrorFormatDefinition()
    {
        this.ForegroundBrush = (Brush)Brushes.Red;
        this.BackgroundCustomizable = new bool?(false);
        this.DisplayName = "Syntax Error";
    }
}

[Export(typeof(EditorFormatDefinition))]
[Name("hinted suggestion")]
[UserVisible(true)]
internal class HintedSuggestionClassificationFormatDefinition : EditorFormatDefinition
{
    public HintedSuggestionClassificationFormatDefinition()
    {
        this.ForegroundBrush = (Brush)new SolidColorBrush(Color.FromRgb((byte)165, (byte)165, (byte)165));
        this.BackgroundCustomizable = new bool?(false);
        this.DisplayName = "Suggestion ellipses (...)";
    }
}

There is one in Vs 2022 that is not included in this list. ErrorType "Edit and Continue" that by default provides a Purple brush, it is "Rude Edit" in Fonts And Colors.

If you want the squiggle colour to be edited in visual studio then set [UserVisible(true)] on the EditorFormatDefinition and if you do so this.BackgroundCustomizable = false;, as I have done with the ErrorEditorFormatDefinition class, given that the background is not used. Do not make user visible if you are supplying a non SolidColorBrush.

Finally, there is a different adornment for the ErrorType = PredefinedErrorTypeNames.HintedSuggestion, all others are squiggles.

Burnejones answered 18/11, 2023 at 17:51 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.