protected override void AddAdornmentsToAdornmentLayer_CallOnlyOnUIThread(NormalizedSnapshotSpanCollection changedSpanCollection) { // this method should only run on UI thread as we do WPF here. Contract.ThrowIfFalse(TextView.VisualElement.Dispatcher.CheckAccess()); var viewSnapshot = TextView.TextSnapshot; var viewLines = TextView.TextViewLines; foreach (var changedSpan in changedSpanCollection) { // is there any effect on the view? if (!viewLines.IntersectsBufferSpan(changedSpan)) { continue; } var tagSpans = TagAggregator.GetTags(changedSpan); foreach (var tagMappingSpan in tagSpans) { if (!ShouldDrawTag(changedSpan, tagMappingSpan, out _)) { continue; } if (!TryMapToSingleSnapshotSpan(tagMappingSpan.Span, TextView.TextSnapshot, out var span)) { continue; } // add the visual to the adornment layer. var geometry = viewLines.GetMarkerGeometry(span); if (geometry != null) { var tag = tagMappingSpan.Tag; var graphicsResult = tag.GetGraphics(TextView, geometry, format: null); AdornmentLayer.AddAdornment( behavior: AdornmentPositioningBehavior.TextRelative, visualSpan: span, tag: tag, adornment: graphicsResult.VisualElement, removedCallback: delegate { graphicsResult.Dispose(); }); } } } }
/// <summary> /// Get the spans located on each line so that it can only display the first one that appears on the line /// </summary> private void AddSpansOnEachLine(NormalizedSnapshotSpanCollection changedSpanCollection, Dictionary <int, IMappingTagSpan <InlineDiagnosticsTag> > map) { var viewLines = TextView.TextViewLines; foreach (var changedSpan in changedSpanCollection) { if (!viewLines.IntersectsBufferSpan(changedSpan)) { continue; } var tagSpans = TagAggregator.GetTags(changedSpan); foreach (var tagMappingSpan in tagSpans) { if (!ShouldDrawTag(changedSpan, tagMappingSpan, out var mappedPoint)) { continue; } // mappedPoint is known to not be null here because it is checked in the ShouldDrawTag method call. var lineNum = mappedPoint.GetContainingLine().LineNumber; // If the line does not have an associated tagMappingSpan and changedSpan, then add the first one. if (!map.TryGetValue(lineNum, out var value)) { map.Add(lineNum, tagMappingSpan); } else if (value.Tag.ErrorType is not PredefinedErrorTypeNames.SyntaxError && tagMappingSpan.Tag.ErrorType is PredefinedErrorTypeNames.SyntaxError) { // Draw the first instance of an error, if what is stored in the map at a specific line is // not an error, then replace it. Otherwise, just get the first warning on the line. map[lineNum] = tagMappingSpan; } } } }
/// <summary> /// Iterates through the mapping of line number to span and draws the diagnostic in the appropriate position on the screen, /// as well as adding the tag to the adornment layer. /// </summary> protected override void AddAdornmentsToAdornmentLayer_CallOnlyOnUIThread(NormalizedSnapshotSpanCollection changedSpanCollection) { // this method should only run on UI thread as we do WPF here. Contract.ThrowIfFalse(TextView.VisualElement.Dispatcher.CheckAccess()); if (changedSpanCollection.IsEmpty()) { return; } var viewLines = TextView.TextViewLines; using var _ = PooledDictionary <IWpfTextViewLine, IMappingTagSpan <InlineDiagnosticsTag> > .GetInstance(out var map); // First loop iterates through the snap collection and determines if an inline diagnostic can be drawn. // Creates a mapping of the view line to the IMappingTagSpan with getting the first error that appears // on the line if there are multiple. foreach (var changedSpan in changedSpanCollection) { if (!viewLines.IntersectsBufferSpan(changedSpan)) { continue; } var tagSpans = TagAggregator.GetTags(changedSpan); foreach (var tagMappingSpan in tagSpans) { if (!ShouldDrawTag(changedSpan, tagMappingSpan, out var mappedPoint)) { continue; } var viewLine = viewLines.GetTextViewLineContainingBufferPosition(mappedPoint); // If the line does not have an associated tagMappingSpan and changedSpan, then add the first one. if (!map.TryGetValue(viewLine, out var value)) { map.Add(viewLine, tagMappingSpan); } else if (value.Tag.ErrorType is not PredefinedErrorTypeNames.SyntaxError && tagMappingSpan.Tag.ErrorType is PredefinedErrorTypeNames.SyntaxError) { // Draw the first instance of an error, if what is stored in the map at a specific line is // not an error, then replace it. Otherwise, just get the first warning on the line. map[viewLine] = tagMappingSpan; } } } // Second loop iterates through the map to go through and create the graphics that is being drawn // on the canvas as well adding the tag to the Inline Diagnostics adornment layer. foreach (var(lineView, tagMappingSpan) in map) { // Looking for IEndOfLineTags and seeing if they exist on the same line as where the // diagnostic would be drawn. If they are the same, then we do not want to draw // the diagnostic. var obstructingTags = _endLineTagAggregator.GetTags(lineView.Extent); if (obstructingTags.Where(tag => tag.Tag.Type is not "Inline Diagnostics").Any()) { continue; } var tag = tagMappingSpan.Tag; var classificationType = _classificationRegistryService.GetClassificationType(InlineDiagnosticsTag.GetClassificationId(tag.ErrorType)); // Pass in null! because the geometry is unused for drawing anything for Inline Diagnostics var graphicsResult = tag.GetGraphics(TextView, unused: null !, GetFormat(classificationType)); var visualElement = graphicsResult.VisualElement; // Only place the diagnostics if the diagnostic would not intersect with the editor window if (lineView.Right >= TextView.ViewportWidth - visualElement.DesiredSize.Width) { graphicsResult.Dispose(); continue; } Canvas.SetLeft(visualElement, tag.Location == InlineDiagnosticsLocations.PlacedAtEndOfCode ? lineView.Right : tag.Location == InlineDiagnosticsLocations.PlacedAtEndOfEditor ? TextView.ViewportRight - visualElement.DesiredSize.Width : throw ExceptionUtilities.UnexpectedValue(tag.Location)); Canvas.SetTop(visualElement, lineView.Bottom - visualElement.DesiredSize.Height); AdornmentLayer.AddAdornment( behavior: AdornmentPositioningBehavior.TextRelative, visualSpan: lineView.Extent, tag: tag, adornment: visualElement, removedCallback: delegate { graphicsResult.Dispose(); }); } }
protected override void AddAdornmentsToAdornmentLayer_CallOnlyOnUIThread(NormalizedSnapshotSpanCollection changedSpanCollection) { // this method should only run on UI thread as we do WPF here. Contract.ThrowIfFalse(TextView.VisualElement.Dispatcher.CheckAccess()); var viewSnapshot = TextView.TextSnapshot; var viewLines = TextView.TextViewLines; foreach (var changedSpan in changedSpanCollection) { if (!viewLines.IntersectsBufferSpan(changedSpan)) { continue; } var tagSpans = TagAggregator.GetTags(changedSpan); foreach (var tagMappingSpan in tagSpans) { if (!ShouldDrawTag(changedSpan, tagMappingSpan, out _)) { continue; } if (!TryMapToSingleSnapshotSpan(tagMappingSpan.Span, TextView.TextSnapshot, out var span)) { continue; } if (!TryMapHoleSpans(tagMappingSpan.Tag.OrderedHoleSpans, out var orderedHoleSpans)) { continue; } if (VisibleBlock.CreateVisibleBlock(span, orderedHoleSpans, TextView) is not VisibleBlock block) { continue; } var tag = tagMappingSpan.Tag; var brush = tag.GetBrush(TextView); foreach (var(start, end) in block.YSegments) { var line = new Line { SnapsToDevicePixels = true, StrokeThickness = 1.0, X1 = block.X, X2 = block.X, Y1 = start, Y2 = end, Stroke = brush, }; AdornmentLayer.AddAdornment( behavior: AdornmentPositioningBehavior.TextRelative, visualSpan: span, tag: block, adornment: line, removedCallback: delegate { }); } } } }