提交 c6b74a29 编写于 作者: C Cyrus Najmabadi

Simplify diagnostic tagging by making it use the standard tagging model.

上级 005f4f63
using System;
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Shared.TestHooks;
using Microsoft.VisualStudio.Text;
......
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Common;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Shared.TestHooks;
using Microsoft.CodeAnalysis.Text;
using Microsoft.CodeAnalysis.Text.Shared.Extensions;
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.Text.Tagging;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.Editor.Implementation.Diagnostics
{
internal abstract partial class AbstractDiagnosticsTaggerProvider<TTag>
{
private class AggregatingTagger : ForegroundThreadAffinitizedObject, IAccurateTagger<TTag>, IDisposable
{
private readonly AbstractDiagnosticsTaggerProvider<TTag> _owner;
private readonly ITextBuffer _subjectBuffer;
private int _refCount;
private bool _disposed;
/// <summary>
/// The current Document that our <see cref="_subjectBuffer"/> is associated with.
/// If our buffer becomes associated with another document, we will clear out any
/// cached diagnostic information we've collected so far as it's no longer valid.
/// </summary>
private DocumentId _currentDocumentId;
private readonly Dictionary<object, (TaggerProvider provider, IAccurateTagger<TTag> tagger)> _idToProviderAndTagger = new Dictionary<object, (TaggerProvider provider, IAccurateTagger<TTag> tagger)>();
public event EventHandler<SnapshotSpanEventArgs> TagsChanged;
// Use a chain of tasks to make sure that we process each diagnostic event serially.
// This also ensures that the first diagnostic event we hear about will be processed
// after the initial background work to get the first group of diagnostics.
private readonly object _taskGate = new object();
private Task _taskChain;
private readonly CancellationTokenSource _initialDiagnosticsCancellationSource = new CancellationTokenSource();
public AggregatingTagger(
AbstractDiagnosticsTaggerProvider<TTag> owner,
ITextBuffer subjectBuffer)
{
_owner = owner;
_subjectBuffer = subjectBuffer;
var document = _subjectBuffer.AsTextContainer().GetOpenDocumentInCurrentContext();
_currentDocumentId = document?.Id;
// Kick off a background task to collect the initial set of diagnostics.
var cancellationToken = _initialDiagnosticsCancellationSource.Token;
var asyncToken = _owner._listener.BeginAsyncOperation(GetType() + ".GetInitialDiagnostics");
var task = Task.Run(() => GetInitialDiagnosticsInBackground(document, cancellationToken), cancellationToken);
task.CompletesAsyncOperation(asyncToken);
_taskChain = task;
// Register to hear about diagnostics changing. When we're notified about new
// diagnostics (and those diagnostics are for our buffer), we'll ensure that
// we have an underlying tagger responsible for asynchronously handling diagnostics
// from the owner of that diagnostic update.
_owner._diagnosticService.DiagnosticsUpdated += OnDiagnosticsUpdated;
}
private void GetInitialDiagnosticsInBackground(
Document document, CancellationToken cancellationToken)
{
this.AssertIsBackground();
cancellationToken.ThrowIfCancellationRequested();
if (document != null)
{
var project = document.Project;
var workspace = project.Solution.Workspace;
foreach (var updateArgs in _owner._diagnosticService.GetDiagnosticsUpdatedEventArgs(workspace, project.Id, document.Id, cancellationToken))
{
var diagnostics = AdjustInitialDiagnostics(project.Solution, updateArgs, cancellationToken);
if (diagnostics.Length == 0)
{
continue;
}
OnDiagnosticsUpdatedOnBackground(
DiagnosticsUpdatedArgs.DiagnosticsCreated(
updateArgs.Id, updateArgs.Workspace, project.Solution, updateArgs.ProjectId, updateArgs.DocumentId, diagnostics));
}
}
}
private ImmutableArray<DiagnosticData> AdjustInitialDiagnostics(
Solution solution, UpdatedEventArgs args, CancellationToken cancellationToken)
{
this.AssertIsBackground();
// we only reach here if there is the document
var document = solution.GetDocument(args.DocumentId);
Contract.ThrowIfNull(document);
// if there is no source text for this document, we don't populate the initial tags. this behavior is equivalent of existing
// behavior in OnDiagnosticsUpdated.
if (!document.TryGetText(out var text))
{
return ImmutableArray<DiagnosticData>.Empty;
}
// GetDiagnostics returns whatever cached diagnostics in the service which can be stale ones. for example, build error will be most likely stale
// diagnostics. so here we make sure we filter out any diagnostics that is not in the text range.
var builder = ArrayBuilder<DiagnosticData>.GetInstance();
var fullSpan = new TextSpan(0, text.Length);
foreach (var diagnostic in _owner._diagnosticService.GetDiagnostics(
args.Workspace, args.ProjectId, args.DocumentId, args.Id, includeSuppressedDiagnostics: false, cancellationToken: cancellationToken))
{
if (fullSpan.Contains(diagnostic.GetExistingOrCalculatedTextSpan(text)))
{
builder.Add(diagnostic);
}
}
return builder.ToImmutableAndFree();
}
public void OnTaggerCreated()
{
this.AssertIsForeground();
Debug.Assert(_refCount >= 0);
Debug.Assert(!_disposed);
_refCount++;
}
public void Dispose()
{
this.AssertIsForeground();
Debug.Assert(_refCount > 0);
Debug.Assert(!_disposed);
_refCount--;
if (_refCount == 0)
{
_disposed = true;
// Stop listening to diagnostic changes from the diagnostic service.
_owner._diagnosticService.DiagnosticsUpdated -= OnDiagnosticsUpdated;
_initialDiagnosticsCancellationSource.Cancel();
// Disconnect us from our underlying taggers and make sure they're
// released as well.
DisconnectFromAllTaggers();
_owner.RemoveTagger(this, _subjectBuffer);
}
}
private void DisconnectFromTagger(IAccurateTagger<TTag> tagger)
{
this.AssertIsForeground();
tagger.TagsChanged -= OnUnderlyingTaggerTagsChanged;
if (tagger is IDisposable disposable)
{
disposable.Dispose();
}
}
private void DisconnectFromAllTaggers()
{
this.AssertIsForeground();
foreach (var kvp in _idToProviderAndTagger)
{
var tagger = kvp.Value.tagger;
DisconnectFromTagger(tagger);
}
_idToProviderAndTagger.Clear();
}
private void RegisterNotification(Action action)
{
var token = _owner._listener.BeginAsyncOperation(GetType().Name + "RegisterNotification");
_owner._notificationService.RegisterNotification(action, token);
}
private void OnDiagnosticsUpdated(object sender, DiagnosticsUpdatedArgs e)
{
lock (_taskGate)
{
// Chain the events so we process them serially. This also ensures
// that we don't process events while still getting our initial set
// of diagnostics.
var asyncToken = _owner._listener.BeginAsyncOperation(GetType() + ".OnDiagnosticsUpdated");
_taskChain = _taskChain.SafeContinueWith(
_ => OnDiagnosticsUpdatedOnBackground(e), TaskScheduler.Default);
_taskChain.CompletesAsyncOperation(asyncToken);
}
}
private void OnDiagnosticsUpdatedOnBackground(DiagnosticsUpdatedArgs e)
{
this.AssertIsBackground();
RegisterNotification(() => OnDiagnosticsUpdatedOnForeground(e));
}
/// <summary>
/// We do all mutation work on the UI thread. That ensures that all mutation
/// is processed serially *and* it means we can safely examine things like
/// subject buffers and open documents without any threat of race conditions.
/// Note that we do not do any expensive work here. We just store (or remove)
/// the data we were given in appropriate buckets.
///
/// For example, we create a TaggerProvider per unique DiagnosticUpdatedArgs.Id
/// we get. So if we see a new id we simply create a tagger for it and pass it
/// these args to store. Otherwise we pass these args to the existing tagger.
///
/// Similarly, clearing out data is just a matter of us clearing our reference
/// to the data.
/// </summary>
private void OnDiagnosticsUpdatedOnForeground(DiagnosticsUpdatedArgs e)
{
this.AssertIsForeground();
if (_disposed)
{
return;
}
// Do some quick checks to avoid doing any further work for diagnostics we don't
// care about.
var ourDocument = _subjectBuffer.AsTextContainer().GetOpenDocumentInCurrentContext();
var ourDocumentId = ourDocument?.Id;
if (ourDocumentId != _currentDocumentId)
{
// Our buffer has started tracking some other document entirely.
// We have to clear out all of the diagnostics we have currently stored.
RemoveAllCachedDiagnostics();
}
_currentDocumentId = ourDocumentId;
// Now see if the document we're tracking corresponds to the diagnostics
// we're hearing about. If not, just ignore them.
if (ourDocument == null ||
ourDocument.Project.Solution.Workspace != e.Workspace ||
ourDocument.Id != e.DocumentId)
{
return;
}
// We're hearing about diagnostics for our document. We may be hearing
// about new diagnostics coming, or existing diagnostics being cleared
// out.
// First see if this is a document/project removal. If so, clear out any state we
// have associated with any analyzers we have for that document/project.
ProcessRemovedDiagnostics(e);
// Make sure we can find an editor snapshot for these errors. Otherwise we won't
// be able to make ITagSpans for them. If we can't, just bail out. This happens
// when the solution crawler is very far behind. However, it will have a more
// up to date document within it that it will eventually process. Until then
// we just keep around the stale tags we have.
//
// Note: if the Solution or Document is null here, then that means the document
// was removed. In that case, we do want to proceed so that we'll produce 0
// tags and we'll update the editor appropriately.
SourceText sourceText = null;
ITextSnapshot editorSnapshot = null;
if (e.Solution != null)
{
var diagnosticDocument = e.Solution.GetDocument(e.DocumentId);
if (diagnosticDocument != null)
{
if (!diagnosticDocument.TryGetText(out sourceText))
{
return;
}
editorSnapshot = sourceText.FindCorrespondingEditorTextSnapshot();
if (editorSnapshot == null)
{
return;
}
// Make sure the editor we've got associated with these diagnostics is the
// same one we're a tagger for. It is possible for us to hear about diagnostics
// for the *same* Document that are not from the *same* buffer. For example,
// say we have the following chain of events:
//
// Document is opened.
// Diagnostics start analyzing.
// Document is closed.
// Document is opened.
// Diagnostics finish and report for document.
//
// We'll hear about diagnostics for the original Document/Buffer that was
// opened. But we'll be trying to apply them to this current Document/Buffer.
// That won't work since these will be different buffers (and thus, we won't
// be able to map the diagnostic spans appropriately).
//
// Note: returning here is safe. Because the file is closed/opened, The
// Diagnostics Service will reanalyze it. It will then report the new results
// which we will hear about and use.
if (editorSnapshot.TextBuffer != _subjectBuffer)
{
return;
}
}
}
OnDiagnosticsUpdatedOnForeground(e, sourceText, editorSnapshot);
}
private void ProcessRemovedDiagnostics(DiagnosticsUpdatedArgs e)
{
this.AssertIsForeground();
Debug.Assert(!_disposed);
if (e.Kind != DiagnosticsUpdatedKind.DiagnosticsRemoved)
{
// Wasn't a removal. We don't need to do anything here.
return;
}
// See if we're being told about diagnostics going away because a document/project
// was removed. If so, clear out any diagnostics we have associated with this
// diagnostic source ID and notify any listeners that
var id = e.Id;
if (!_idToProviderAndTagger.TryGetValue(id, out var providerAndTagger))
{
// Wasn't a diagnostic source we care about.
return;
}
_idToProviderAndTagger.Remove(id);
DisconnectFromTagger(providerAndTagger.tagger);
OnUnderlyingTaggerTagsChanged(this, new SnapshotSpanEventArgs(_subjectBuffer.CurrentSnapshot.GetFullSpan()));
}
private void RemoveAllCachedDiagnostics()
{
this.AssertIsForeground();
DisconnectFromAllTaggers();
OnUnderlyingTaggerTagsChanged(this, new SnapshotSpanEventArgs(_subjectBuffer.CurrentSnapshot.GetFullSpan()));
}
private void OnDiagnosticsUpdatedOnForeground(
DiagnosticsUpdatedArgs e, SourceText sourceText, ITextSnapshot editorSnapshot)
{
this.AssertIsForeground();
Debug.Assert(!_disposed);
// Find the appropriate async tagger for this diagnostics id, and let it know that
// there were new diagnostics produced for it.
var id = e.Id;
if (!_idToProviderAndTagger.TryGetValue(id, out var providerAndTagger))
{
// We didn't have an existing tagger for this diagnostic id. If there are no actual
// diagnostics being reported, then don't bother actually doing anything. This saves
// us from creating a lot of objects, and subscribing to tons of events that we don't
// actually need (since we don't even have any diagnostics to show!).
if (e.Diagnostics.Length == 0)
{
return;
}
// Didn't have an existing tagger for this diagnostic id. Make a new one
// and cache it so we can use it in the future.
var taggerProvider = new TaggerProvider(_owner);
var tagger = taggerProvider.CreateTagger<TTag>(_subjectBuffer);
providerAndTagger = (taggerProvider, tagger);
_idToProviderAndTagger[id] = providerAndTagger;
// Register for changes from the underlying tagger. When it tells us about
// changes, we'll let anyone know who is registered with us.
tagger.TagsChanged += OnUnderlyingTaggerTagsChanged;
}
// Let the provider know that there are new diagnostics. It will then
// handle all the async processing of those diagnostics.
providerAndTagger.provider.OnDiagnosticsUpdated(e, sourceText, editorSnapshot);
}
private void OnUnderlyingTaggerTagsChanged(object sender, SnapshotSpanEventArgs args)
{
this.AssertIsForeground();
if (_disposed)
{
return;
}
this.TagsChanged?.Invoke(sender, args);
}
public IEnumerable<ITagSpan<TTag>> GetAllTags(NormalizedSnapshotSpanCollection spans, CancellationToken cancel)
{
this.AssertIsForeground();
return _idToProviderAndTagger.Values.SelectMany(t => t.tagger.GetAllTags(spans, cancel)).ToList();
}
public IEnumerable<ITagSpan<TTag>> GetTags(NormalizedSnapshotSpanCollection spans)
{
this.AssertIsForeground();
return _idToProviderAndTagger.Values.SelectMany(t => t.tagger.GetTags(spans)).ToList();
}
}
}
}
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Editor.Shared.Preview;
using Microsoft.CodeAnalysis.Editor.Shared.Tagging;
using Microsoft.CodeAnalysis.Editor.Tagging;
using Microsoft.CodeAnalysis.ErrorReporting;
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.Text;
using Microsoft.CodeAnalysis.Text.Shared.Extensions;
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.Text.Editor;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.Editor.Implementation.Diagnostics
{
internal abstract partial class AbstractDiagnosticsTaggerProvider<TTag>
{
/// <summary>
/// we create an instance of this async tagger provider for each diagnostic source
/// we hear about for a particular buffer. Each async tagger is then responsible
/// for asynchronous producing tags for that diagnostic source. This allows each
/// individual async tagger to collect diagnostics, diff them against the last set
/// produced by that diagnostic source, and then notify any interested parties about
/// what changed.
/// </summary>
private class TaggerProvider : AsynchronousTaggerProvider<TTag>, ITaggerEventSource
{
private readonly AbstractDiagnosticsTaggerProvider<TTag> _owner;
private readonly object _gate = new object();
// The latest diagnostics we've head about for this
private object _latestId;
private ImmutableArray<DiagnosticData> _latestDiagnostics;
private ITextSnapshot _latestEditorSnapshot;
private SourceText _latestSourceText;
protected override IEnumerable<Option<bool>> Options => _owner.Options;
public TaggerProvider(AbstractDiagnosticsTaggerProvider<TTag> owner)
: base(owner._listener, owner._notificationService)
{
_owner = owner;
}
public event EventHandler<TaggerEventArgs> Changed;
event EventHandler ITaggerEventSource.UIUpdatesPaused { add { } remove { } }
event EventHandler ITaggerEventSource.UIUpdatesResumed { add { } remove { } }
void ITaggerEventSource.Connect() { }
void ITaggerEventSource.Disconnect() { }
// we will show new tags to users very slowly.
// don't confused this with data changed event which is for tag producer (which is set to NearImmediate).
// this delay is for letting editor know about newly added tags.
protected override TaggerDelay AddedTagNotificationDelay => TaggerDelay.OnIdle;
protected override ITaggerEventSource CreateEventSource(ITextView textViewOpt, ITextBuffer subjectBuffer)
{
// We act as a source of events ourselves. When the diagnostics service tells
// us about new diagnostics, we'll use that to kick of the asynchronous tagging
// work.
var eventSource = TaggerEventSources.Compose(
TaggerEventSources.OnWorkspaceRegistrationChanged(subjectBuffer, TaggerDelay.Medium),
this);
// See if our owner has any additional events for us to listen to.
var ownerEventSource = _owner.GetTaggerEventSource();
return ownerEventSource == null
? eventSource
: TaggerEventSources.Compose(ownerEventSource, eventSource);
}
protected override Task ProduceTagsAsync(TaggerContext<TTag> context, DocumentSnapshotSpan spanToTag, int? caretPosition)
{
ProduceTags(context, spanToTag);
return SpecializedTasks.EmptyTask;
}
private void ProduceTags(TaggerContext<TTag> context, DocumentSnapshotSpan spanToTag)
{
try
{
if (!_owner.IsEnabled)
{
return;
}
var document = spanToTag.Document;
if (document == null)
{
return;
}
// See if we've marked any spans as those we want to suppress diagnostics for.
// This can happen for buffers used in the preview workspace where some feature
// is generating code that it doesn't want errors shown for.
var buffer = spanToTag.SnapshotSpan.Snapshot.TextBuffer;
var suppressedDiagnosticsSpans = default(NormalizedSnapshotSpanCollection);
buffer?.Properties.TryGetProperty(PredefinedPreviewTaggerKeys.SuppressDiagnosticsSpansKey, out suppressedDiagnosticsSpans);
// Producing tags is simple. We just grab the diagnostics we were already told about,
// and we convert them to tag spans.
object id;
ImmutableArray<DiagnosticData> diagnostics;
SourceText sourceText;
ITextSnapshot editorSnapshot;
lock (_gate)
{
id = _latestId;
diagnostics = _latestDiagnostics;
sourceText = _latestSourceText;
editorSnapshot = _latestEditorSnapshot;
}
if (sourceText == null || editorSnapshot == null)
{
return;
}
var project = document.Project;
var requestedSnapshot = spanToTag.SnapshotSpan.Snapshot;
var requestedSpan = spanToTag.SnapshotSpan;
var isLiveUpdate = id is ISupportLiveUpdate;
foreach (var diagnosticData in diagnostics)
{
if (_owner.IncludeDiagnostic(diagnosticData))
{
var actualSpan = diagnosticData
.GetExistingOrCalculatedTextSpan(sourceText)
.ToSnapshotSpan(editorSnapshot)
.TranslateTo(requestedSnapshot, SpanTrackingMode.EdgeExclusive);
if (actualSpan.IntersectsWith(requestedSpan) &&
!IsSuppressed(suppressedDiagnosticsSpans, actualSpan))
{
var tagSpan = _owner.CreateTagSpan(isLiveUpdate, actualSpan, diagnosticData);
if (tagSpan != null)
{
context.AddTag(tagSpan);
}
}
}
}
}
catch (ArgumentOutOfRangeException ex) when (FatalError.ReportWithoutCrash(ex))
{
// https://devdiv.visualstudio.com/DefaultCollection/DevDiv/_workitems?id=428328&_a=edit&triage=false
// explicitly report NFW to find out what is causing us for out of range.
// stop crashing on such occations
return;
}
}
private bool IsSuppressed(NormalizedSnapshotSpanCollection suppressedSpans, SnapshotSpan span)
=> suppressedSpans != null && suppressedSpans.IntersectsWith(span);
internal void OnDiagnosticsUpdated(DiagnosticsUpdatedArgs e, SourceText sourceText, ITextSnapshot editorSnapshot)
{
// We were told about new diagnostics. Store them, and then let the
// AsynchronousTaggerProvider know it should ProduceTags again.
lock (_gate)
{
_latestId = e.Id;
_latestDiagnostics = e.Diagnostics;
_latestSourceText = sourceText;
_latestEditorSnapshot = editorSnapshot;
}
// unlike any other tagger, actual work to produce data is done by other service rather than tag provider itself.
// so we don't need to do any big delay for diagnostic tagger (producer) to reduce doing expensive work repeatedly. that is already
// taken cared by the external service (diagnostic service).
this.Changed?.Invoke(this, new TaggerEventArgs(TaggerDelay.NearImmediate));
}
}
protected virtual ITaggerEventSource GetTaggerEventSource()
{
return null;
}
}
}
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Common;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.Editor.Shared.Preview;
using Microsoft.CodeAnalysis.Editor.Shared.Tagging;
using Microsoft.CodeAnalysis.Editor.Tagging;
using Microsoft.CodeAnalysis.ErrorReporting;
using Microsoft.CodeAnalysis.Shared.TestHooks;
using Microsoft.CodeAnalysis.Text;
using Microsoft.CodeAnalysis.Text.Shared.Extensions;
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.Text.Editor;
using Microsoft.VisualStudio.Text.Tagging;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.Editor.Implementation.Diagnostics
{
......@@ -22,47 +31,132 @@ namespace Microsoft.CodeAnalysis.Editor.Implementation.Diagnostics
/// Each of these taggers is nicely asynchronous and properly works within the async
/// tagging infrastructure.
/// </summary>
internal abstract partial class AbstractDiagnosticsTaggerProvider<TTag> :
ForegroundThreadAffinitizedObject,
ITaggerProvider
internal abstract partial class AbstractDiagnosticsTaggerProvider<TTag> : AsynchronousTaggerProvider<TTag>
where TTag : ITag
{
private readonly object _uniqueKey = new object();
private readonly IDiagnosticService _diagnosticService;
private readonly IForegroundNotificationService _notificationService;
private readonly IAsynchronousOperationListener _listener;
protected AbstractDiagnosticsTaggerProvider(
IDiagnosticService diagnosticService,
IForegroundNotificationService notificationService,
IAsynchronousOperationListener listener)
: base(listener, notificationService)
{
_diagnosticService = diagnosticService;
_notificationService = notificationService;
_listener = listener;
}
protected internal abstract IEnumerable<Option<bool>> Options { get; }
protected override TaggerDelay AddedTagNotificationDelay => TaggerDelay.OnIdle;
protected override ITaggerEventSource CreateEventSource(ITextView textViewOpt, ITextBuffer subjectBuffer)
{
return TaggerEventSources.Compose(
TaggerEventSources.OnDocumentActiveContextChanged(subjectBuffer, TaggerDelay.Medium),
TaggerEventSources.OnWorkspaceRegistrationChanged(subjectBuffer, TaggerDelay.Medium),
TaggerEventSources.OnDiagnosticsChanged(subjectBuffer, _diagnosticService, TaggerDelay.Short));
}
protected internal abstract bool IsEnabled { get; }
protected internal abstract bool IncludeDiagnostic(DiagnosticData data);
protected internal abstract ITagSpan<TTag> CreateTagSpan(bool isLiveUpdate, SnapshotSpan span, DiagnosticData data);
ITagger<T> ITaggerProvider.CreateTagger<T>(ITextBuffer buffer)
protected override Task ProduceTagsAsync(TaggerContext<TTag> context, DocumentSnapshotSpan spanToTag, int? caretPosition)
{
return CreateTagger<T>(buffer);
ProduceTags(context, spanToTag);
return SpecializedTasks.EmptyTask;
}
public IAccurateTagger<T> CreateTagger<T>(ITextBuffer buffer) where T : ITag
private void ProduceTags(TaggerContext<TTag> context, DocumentSnapshotSpan spanToTag)
{
var tagger = buffer.Properties.GetOrCreateSingletonProperty(
_uniqueKey, () => new AggregatingTagger(this, buffer));
tagger.OnTaggerCreated();
return tagger as IAccurateTagger<T>;
if (!this.IsEnabled)
{
return;
}
var document = spanToTag.Document;
if (document == null)
{
return;
}
var editorSnapshot = spanToTag.SnapshotSpan.Snapshot;
var cancellationToken = context.CancellationToken;
var workspace = document.Project.Solution.Workspace;
// See if we've marked any spans as those we want to suppress diagnostics for.
// This can happen for buffers used in the preview workspace where some feature
// is generating code that it doesn't want errors shown for.
var buffer = editorSnapshot.TextBuffer;
var suppressedDiagnosticsSpans = default(NormalizedSnapshotSpanCollection);
buffer?.Properties.TryGetProperty(PredefinedPreviewTaggerKeys.SuppressDiagnosticsSpansKey, out suppressedDiagnosticsSpans);
var eventArgs = _diagnosticService.GetDiagnosticsUpdatedEventArgs(
workspace, document.Project.Id, document.Id, context.CancellationToken);
var sourceText = editorSnapshot.AsText();
foreach (var updateArg in eventArgs)
{
ProduceTags(
context, spanToTag, workspace, document, sourceText, editorSnapshot,
suppressedDiagnosticsSpans, updateArg, cancellationToken);
}
}
private void RemoveTagger(AggregatingTagger tagger, ITextBuffer buffer)
private void ProduceTags(
TaggerContext<TTag> context, DocumentSnapshotSpan spanToTag,
Workspace workspace, Document document, SourceText sourceText, ITextSnapshot editorSnapshot,
NormalizedSnapshotSpanCollection suppressedDiagnosticsSpans, UpdatedEventArgs updateArgs, CancellationToken cancellationToken)
{
buffer.Properties.RemoveProperty(_uniqueKey);
try
{
var id = updateArgs.Id;
var diagnostics = _diagnosticService.GetDiagnostics(
workspace, document.Project.Id, document.Id, id, false, cancellationToken);
var isLiveUpdate = id is ISupportLiveUpdate;
var requestedSpan = spanToTag.SnapshotSpan;
var requestedSnapshot = requestedSpan.Snapshot;
foreach (var diagnosticData in diagnostics)
{
if (this.IncludeDiagnostic(diagnosticData))
{
// We're going to be retrieving the diagnostics against the last time the engine
// computed them against this document *id*. That might have been a different
// version of the document vs what we're looking at now. As such, we have to
// ensure that the information we get back is not outside the bounds of the editor
// snapshot we're currently looking at.
// Note: GetExistingOrCalculatedTextSpan always succeeds. But it does not ensure
// that the span it returns is within the span of sourceText. So we make sure that
// the start/end of it fits in our snapshot.
var diagnosticSpan = diagnosticData.GetExistingOrCalculatedTextSpan(sourceText)
.ToSnapshotSpan(editorSnapshot);
if (diagnosticSpan.IntersectsWith(requestedSpan) &&
!IsSuppressed(suppressedDiagnosticsSpans, diagnosticSpan))
{
var tagSpan = this.CreateTagSpan(isLiveUpdate, diagnosticSpan, diagnosticData);
if (tagSpan != null)
{
context.AddTag(tagSpan);
}
}
}
}
}
catch (ArgumentOutOfRangeException ex) when (FatalError.ReportWithoutCrash(ex))
{
// https://devdiv.visualstudio.com/DefaultCollection/DevDiv/_workitems?id=428328&_a=edit&triage=false
// explicitly report NFW to find out what is causing us for out of range.
// stop crashing on such occations
return;
}
}
private bool IsSuppressed(NormalizedSnapshotSpanCollection suppressedSpans, SnapshotSpan span)
=> suppressedSpans != null && suppressedSpans.IntersectsWith(span);
}
}
......@@ -29,7 +29,7 @@ internal partial class DiagnosticsClassificationTaggerProvider : AbstractDiagnos
private readonly ClassificationTag _classificationTag;
private readonly IEditorOptionsFactoryService _editorOptionsFactoryService;
protected internal override IEnumerable<Option<bool>> Options => s_tagSourceOptions;
protected override IEnumerable<Option<bool>> Options => s_tagSourceOptions;
[ImportingConstructor]
public DiagnosticsClassificationTaggerProvider(
......
......@@ -27,7 +27,7 @@ internal partial class DiagnosticsSquiggleTaggerProvider : AbstractDiagnosticsAd
private static readonly IEnumerable<Option<bool>> s_tagSourceOptions =
ImmutableArray.Create(EditorComponentOnOffOptions.Tagger, InternalFeatureOnOffOptions.Squiggles, ServiceComponentOnOffOptions.DiagnosticProvider);
protected internal override IEnumerable<Option<bool>> Options => s_tagSourceOptions;
protected override IEnumerable<Option<bool>> Options => s_tagSourceOptions;
private bool? _blueSquiggleForBuildDiagnostic;
......
......@@ -6,8 +6,6 @@
using System.ComponentModel.Composition;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Editor.Shared.Options;
using Microsoft.CodeAnalysis.Editor.Shared.Tagging;
using Microsoft.CodeAnalysis.Editor.Tagging;
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.Shared.Options;
using Microsoft.CodeAnalysis.Shared.TestHooks;
......@@ -29,8 +27,7 @@ internal partial class DiagnosticsSuggestionTaggerProvider :
private static readonly IEnumerable<Option<bool>> s_tagSourceOptions =
ImmutableArray.Create(EditorComponentOnOffOptions.Tagger, InternalFeatureOnOffOptions.Squiggles, ServiceComponentOnOffOptions.DiagnosticProvider);
protected internal override IEnumerable<Option<bool>> Options => s_tagSourceOptions;
protected override IEnumerable<Option<bool>> Options => s_tagSourceOptions;
[ImportingConstructor]
public DiagnosticsSuggestionTaggerProvider(
......@@ -42,9 +39,7 @@ internal partial class DiagnosticsSuggestionTaggerProvider :
}
protected internal override bool IncludeDiagnostic(DiagnosticData diagnostic)
{
return diagnostic.Severity == DiagnosticSeverity.Info;
}
=> diagnostic.Severity == DiagnosticSeverity.Info;
protected override IErrorTag CreateTag(DiagnosticData diagnostic) =>
new ErrorTag(PredefinedErrorTypeNames.HintedSuggestion, diagnostic.Message);
......
......@@ -47,8 +47,8 @@ internal class TaggerContext<TTag> where TTag : ITag
SnapshotPoint? caretPosition = null,
TextChangeRange? textChangeRange = null,
CancellationToken cancellationToken = default)
: this(null, ImmutableArray.Create(new DocumentSnapshotSpan(document, snapshot.GetFullSpan())),
caretPosition, textChangeRange, null, cancellationToken)
: this(state: null, ImmutableArray.Create(new DocumentSnapshotSpan(document, snapshot.GetFullSpan())),
caretPosition, textChangeRange, existingTags: null, cancellationToken)
{
}
......
......@@ -233,9 +233,12 @@ public override string ToString()
}
public TextSpan GetExistingOrCalculatedTextSpan(SourceText text)
{
return HasTextSpan ? TextSpan : GetTextSpan(this.DataLocation, text);
}
=> HasTextSpan ? EnsureInBounds(TextSpan, text) : GetTextSpan(this.DataLocation, text);
private static TextSpan EnsureInBounds(TextSpan textSpan, SourceText text)
=> TextSpan.FromBounds(
Math.Min(textSpan.Start, text.Length),
Math.Min(textSpan.End, text.Length));
public DiagnosticData WithCalculatedSpan(SourceText text)
{
......@@ -284,7 +287,7 @@ public static TextSpan GetTextSpan(DiagnosticDataLocation dataLocation, SourceTe
SwapIfNeeded(ref startLinePosition, ref endLinePosition);
var span = text.Lines.GetTextSpan(new LinePositionSpan(startLinePosition, endLinePosition));
return TextSpan.FromBounds(Math.Min(Math.Max(span.Start, 0), text.Length), Math.Min(Math.Max(span.End, 0), text.Length));
return EnsureInBounds(TextSpan.FromBounds(Math.Max(span.Start, 0), Math.Max(span.End, 0)), text);
}
private static void AdjustBoundaries(DiagnosticDataLocation dataLocation,
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册