I am writing a Visual Studio extension and currently adding margin glyphs to the editor. I have started with the walk-through example from MS documentation: https://learn.microsoft.com/en-us/visualstudio/extensibility/walkthrough-creating-a-margin-glyph?view=vs-2017.
My logic is a bit different from the example since I don't analyze the file content given by the spans parameter: I already get a set of results with defects locations from an analysis process before. And the rationale is simply to generate a tag if and only if it has not been rendered yet.
Indeed, I want to avoid a new glyph is falsely be rendered if the user adds a new line the line before an existing flag. Example: if there is a glyph on line 42, and cursor is on line 41, and user types a new line, then a new glyph is generated on line 42 (because the GetTags method is called again) while previous glyph is moved to line 43.
My code is:
internal class MyDefectTagger : ITagger<MyDefectTag>
{
private IClassifier m_classifier;
private ITextBuffer m_buffer;
internal MyDefectTagger(IClassifier classifier, ITextBuffer buffer)
{
m_classifier = classifier;
m_buffer = buffer;
}
IEnumerable<ITagSpan<MyDefectTag>>
ITagger<MyDefectTag>.GetTags(NormalizedSnapshotSpanCollection spans)
{
var filename = GetFileName(m_buffer);
if (MyModel.Instance == null ||
MyModel.Instance.defectsLocation == null ||
!MyModel.Instance.defectsLocation.ContainsKey(filename))
{
yield break;
}
foreach (SnapshotSpan span in spans)
{
ITextSnapshot textSnapshot = span.Snapshot;
foreach (ITextSnapshotLine textSnapshotLine in textSnapshot.Lines)
{
var line = textSnapshotLine.LineNumber + 1; // Lines start at 1 in VS Editor
if (MyModel.Instance.defectsLocation[filename].ContainsKey(line) &&
!MyModel.Instance.defectsLocation[filename][line].rendered)
{
MyModel.Instance.defectsLocation[filename][line].rendered = true; // YIELD WORKS IF THIS LINE IS COMMENTED OUT
yield return new TagSpan<MyDefectTag>(new SnapshotSpan(textSnapshotLine.Start, 0), new MyDefectTag());
}
}
}
}
}
public event EventHandler<SnapshotSpanEventArgs> TagsChanged;
private string GetFileName(ITextBuffer buffer)
{
buffer.Properties.TryGetProperty(typeof(ITextDocument), out ITextDocument document);
return document?.FilePath;
}
}
What is strange is that yield returns normally when the "rendered = true" line is commented out (although I get false new glyphs). While yields seems to be blocked when line rendered = true is run (while debugger still steps on the yield instruction).
Is there anything I am missing? Is it forbidden to modify local data when using yield return? Is there some hidden concurrency issue? Thanks for your help!
I finally found out that I was not correctly scanning lines given by the NormalizedSnapshotSpanCollection spans. I was wrongly re-iterating through all the lines for each span with foreach (ITextSnapshotLine textSnapshotLine in textSnapshot.Lines)
. In fact I needed to get the line number associated with the given span and now it works:
Hence my solution is:
foreach (SnapshotSpan span in spans)
{
var lineNumber= span.Snapshot.GetLineNumberFromPosition(span.Start.Position) + 1;
if (MyModel.Instance.defectsLocation[filename].ContainsKey(lineNumber) &&
!MyModel.Instance.defectsLocation[filename][lineNumber].rendered)
{
//MyModel.Instance.defectsLocation[filename][lineNumber].rendered = true;//Strangely no getting any MyDefectTag displayed when I uncomment this line...
yield return new TagSpan<MyDefectTag>(new SnapshotSpan(span.Start, 0), new MyDefectTag());
}
}