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

Pull all specialized ReferenceHighlighting behavior into the core tagging infrastructure.

上级 b519d004
......@@ -762,7 +762,6 @@
<Compile Include="Shared\Tagging\Tags\ConflictTagDefinition.cs" />
<Compile Include="Shared\Tagging\Tags\PreviewWarningTag.cs" />
<Compile Include="Shared\Tagging\Tags\PreviewWarningTagDefinition.cs" />
<Compile Include="Implementation\ReferenceHighlighting\ReferenceHighlightingTagSource.cs" />
<Compile Include="Shared\Tagging\TagSources\ProducerPopulatedTagSource.cs" />
<Compile Include="Shared\Tagging\TagSources\TagSource_ReferenceCounting.cs" />
<Compile Include="Shared\Tagging\Utilities\BatchChangeNotifier.cs" />
......
......@@ -22,7 +22,7 @@ internal abstract partial class AbstractAggregatedDiagnosticsTagSource<TTag> : T
IForegroundNotificationService notificationService,
DiagnosticService service,
IAsynchronousOperationListener asyncListener)
: base(textViewOpt: null, subjectBuffer: subjectBuffer, ignoreCaretMovementToExistingTag: false, notificationService: notificationService, asyncListener: asyncListener)
: base(subjectBuffer, notificationService, asyncListener)
{
_service = service;
_mode = GetMode(subjectBuffer);
......
......@@ -35,7 +35,6 @@ internal class HighlighterViewTaggerProvider : AsynchronousViewTaggerProvider<Hi
// Whenever any text change happens, we want to immediately remove any highlights that
// touch the edit.
public override TaggerTextChangeBehavior TextChangeBehavior => TaggerTextChangeBehavior.RemoveTagsThatIntersectEdits;
public override bool IgnoreCaretMovementToExistingTag => true;
public override IEnumerable<Option<bool>> Options => SpecializedCollections.SingletonEnumerable(InternalFeatureOnOffOptions.KeywordHighlight);
[ImportingConstructor]
......
// 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.Collections.Immutable;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Editor.Shared.Tagging;
using Microsoft.CodeAnalysis.Editor.Tagging;
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.ReferenceHighlighting
{
using Context = AsynchronousTaggerContext<AbstractNavigatableReferenceHighlightingTag, object>;
internal partial class ReferenceHighlightingTagSource : ProducerPopulatedTagSource<AbstractNavigatableReferenceHighlightingTag, object>
{
private const int VoidVersion = -1;
// last solution version we used to update tags
// * NOTE * here unfortunately, we only hold onto version without caret position since
// it is not easy to pass through it. for now, we will void the last version whenever
// we see caret changes.
private int _lastUpdateTagsSolutionVersion = VoidVersion;
public ReferenceHighlightingTagSource(
ITextView textView,
ITextBuffer subjectBuffer,
ReferenceHighlightingViewTaggerProvider taggerProvider,
IAsynchronousOperationListener asyncListener,
IForegroundNotificationService notificationService)
: base(textView, subjectBuffer, taggerProvider, asyncListener, notificationService)
{
}
protected override SnapshotPoint? GetCaretPoint()
{
return this.TextViewOpt.Caret.Position.Point.GetPoint(b => b.ContentType.IsOfType(ContentTypeNames.RoslynContentType), PositionAffinity.Successor);
}
protected override void RecalculateTagsOnChangedCore(TaggerEventArgs e)
{
var cancellationToken = this.WorkQueue.CancellationToken;
VoidLastSolutionVersionIfCaretChanged(e);
RegisterNotification(() =>
{
this.WorkQueue.AssertIsForeground();
var caret = GetCaretPoint();
if (!caret.HasValue)
{
ClearTags(cancellationToken);
return;
}
var spansToTag = TryGetSpansAndDocumentsToTag(e.Kind);
if (spansToTag != null)
{
// we will eagerly remove tags except for semantic change case.
// in semantic change case, we don't actually know whether it will affect highlight we currently
// have, so we will wait until we get new tags before removing them.
if (e.Kind != PredefinedChangedEventKinds.SemanticsChanged)
{
ClearTags(spansToTag, cancellationToken);
}
base.RecalculateTagsOnChangedCore(e);
}
}, delay: TaggerConstants.NearImmediateDelay, cancellationToken: cancellationToken);
}
private void VoidLastSolutionVersionIfCaretChanged(TaggerEventArgs e)
{
if (e.Kind == PredefinedChangedEventKinds.CaretPositionChanged)
{
_lastUpdateTagsSolutionVersion = VoidVersion;
}
}
private void ClearTags(CancellationToken cancellationToken)
{
this.WorkQueue.AssertIsForeground();
ClearTags(spansToTag: null, cancellationToken: cancellationToken);
}
private void ClearTags(List<DocumentSnapshotSpan> spansToTag, CancellationToken cancellationToken)
{
this.WorkQueue.AssertIsForeground();
spansToTag = spansToTag ?? GetSpansAndDocumentsToTag();
// Save to access CachedTagTrees here because we're on the foreground thread.
var oldTagsTrees = this.CachedTagTrees;
this.WorkQueue.EnqueueBackgroundTask(
c => this.ClearTagsAsync(spansToTag, oldTagsTrees, c), "ClearTags", cancellationToken);
}
private List<DocumentSnapshotSpan> TryGetSpansAndDocumentsToTag(string kind)
{
this.WorkQueue.AssertIsForeground();
// TODO: tagger creates so much temporary objects. GetSpansAndDocumentsToTags creates handful of objects per events
// (in this case, on every caret move or text change). at some point of time, we should either re-write tagger framework
// or do some audit to reduce memory allocations.
var spansToTag = GetSpansAndDocumentsToTag();
if (kind == PredefinedChangedEventKinds.SemanticsChanged || kind == PredefinedChangedEventKinds.TextChanged)
{
// check whether we already processed highlight for this document
// * this can happen if we are called twice for same document due to two different change events caused by
// same root change (text edit)
var spanAndTag = spansToTag.First(s => s.SnapshotSpan.Snapshot.TextBuffer == this.SubjectBuffer);
var version = spanAndTag.SnapshotSpan.Snapshot.Version.ReiteratedVersionNumber;
var document = spanAndTag.Document;
if (version == this.SubjectBuffer.CurrentSnapshot.Version.ReiteratedVersionNumber &&
document != null && document.Project.Solution.WorkspaceVersion == _lastUpdateTagsSolutionVersion)
{
return null;
}
}
// we are going to update tags, clear last update tags solution version
_lastUpdateTagsSolutionVersion = VoidVersion;
return spansToTag;
}
private Task ClearTagsAsync(
List<DocumentSnapshotSpan> spansToTag,
ImmutableDictionary<ITextBuffer, TagSpanIntervalTree<AbstractNavigatableReferenceHighlightingTag>> oldTagTrees,
CancellationToken cancellationToken)
{
this.WorkQueue.AssertIsBackground();
cancellationToken.ThrowIfCancellationRequested();
var tagSpans = SpecializedCollections.EmptyEnumerable<ITagSpan<AbstractNavigatableReferenceHighlightingTag>>();
var newTagTrees = ConvertToTagTrees(oldTagTrees, tagSpans, spansToTag);
// here we call base.ProcessNewTags so that we can clear tags without setting last solution version
// clear tags is a special update mechanism where it represents clearing tags not updating tags.
// we don't care about accumulated text change, so give it null
base.ProcessNewTagTrees(spansToTag, oldTagTrees: oldTagTrees, newTagTrees: newTagTrees, newState: null, cancellationToken: cancellationToken);
return SpecializedTasks.EmptyTask;
}
protected override void ProcessNewTagTrees(
IEnumerable<DocumentSnapshotSpan> spansToCompute,
ImmutableDictionary<ITextBuffer, TagSpanIntervalTree<AbstractNavigatableReferenceHighlightingTag>> oldTagTrees,
ImmutableDictionary<ITextBuffer, TagSpanIntervalTree<AbstractNavigatableReferenceHighlightingTag>> newTags,
object newState,
CancellationToken cancellationToken)
{
base.ProcessNewTagTrees(spansToCompute, oldTagTrees, newTags, newState, cancellationToken);
// remember last solution version we updated the tags
var document = spansToCompute.First().Document;
if (document != null)
{
_lastUpdateTagsSolutionVersion = document.Project.Solution.WorkspaceVersion;
}
}
}
}
......@@ -5,11 +5,9 @@
using System.Collections.Immutable;
using System.ComponentModel.Composition;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Editor.Shared.Options;
using Microsoft.CodeAnalysis.Editor.Shared.Tagging;
using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
using Microsoft.CodeAnalysis.Editor.Tagging;
using Microsoft.CodeAnalysis.Internal.Log;
using Microsoft.CodeAnalysis.Notification;
......@@ -31,43 +29,24 @@ namespace Microsoft.CodeAnalysis.Editor.Implementation.ReferenceHighlighting
[ContentType(ContentTypeNames.RoslynContentType)]
[TagType(typeof(AbstractNavigatableReferenceHighlightingTag))]
[TextViewRole(PredefinedTextViewRoles.Interactive)]
internal partial class ReferenceHighlightingViewTaggerProvider :
ForegroundThreadAffinitizedObject,
IViewTaggerProvider,
IAsynchronousTaggerDataSource<AbstractNavigatableReferenceHighlightingTag, object>
internal partial class ReferenceHighlightingViewTaggerProvider : AsynchronousViewTaggerProvider<AbstractNavigatableReferenceHighlightingTag, object>
{
private readonly ISemanticChangeNotificationService _semanticChangeNotificationService;
private readonly Lazy<IViewTaggerProvider> _asynchronousTaggerProvider;
public TaggerTextChangeBehavior TextChangeBehavior => TaggerTextChangeBehavior.None;
public SpanTrackingMode SpanTrackingMode => SpanTrackingMode.EdgeExclusive;
public bool IgnoreCaretMovementToExistingTag => true;
public bool ComputeTagsSynchronouslyIfNoAsynchronousComputationHasCompleted => false;
public IEqualityComparer<AbstractNavigatableReferenceHighlightingTag> TagComparer => null;
public IEnumerable<Option<bool>> Options => null;
public IEnumerable<PerLanguageOption<bool>> PerLanguageOptions => SpecializedCollections.SingletonEnumerable(FeatureOnOffOptions.ReferenceHighlighting);
public override TaggerCaretChangeBehavior CaretChangeBehavior => TaggerCaretChangeBehavior.RemoveAllTagsOnCaretMoveOutsideOfTag;
public override IEnumerable<PerLanguageOption<bool>> PerLanguageOptions => SpecializedCollections.SingletonEnumerable(FeatureOnOffOptions.ReferenceHighlighting);
[ImportingConstructor]
public ReferenceHighlightingViewTaggerProvider(
IForegroundNotificationService notificationService,
ISemanticChangeNotificationService semanticChangeNotificationService,
[ImportMany] IEnumerable<Lazy<IAsynchronousOperationListener, FeatureMetadata>> asyncListeners)
: base(new AggregateAsynchronousOperationListener(asyncListeners, FeatureAttribute.ReferenceHighlighting), notificationService)
{
_semanticChangeNotificationService = semanticChangeNotificationService;
_asynchronousTaggerProvider = new Lazy<IViewTaggerProvider>(() =>
new AsynchronousViewTaggerProviderWithTagSource<AbstractNavigatableReferenceHighlightingTag, object>(
this,
new AggregateAsynchronousOperationListener(asyncListeners, FeatureAttribute.ReferenceHighlighting),
notificationService,
this.CreateTagSource));
}
public ITagger<T> CreateTagger<T>(ITextView textView, ITextBuffer buffer) where T : ITag
{
return _asynchronousTaggerProvider.Value.CreateTagger<T>(textView, buffer);
}
public ITaggerEventSource CreateEventSource(ITextView textView, ITextBuffer subjectBuffer)
public override ITaggerEventSource CreateEventSource(ITextView textView, ITextBuffer subjectBuffer)
{
// Note: we don't listen for OnTextChanged. Text changes to this this buffer will get
// reported by OnSemanticChanged.
......@@ -78,22 +57,19 @@ public ITaggerEventSource CreateEventSource(ITextView textView, ITextBuffer subj
TaggerEventSources.OnOptionChanged(subjectBuffer, FeatureOnOffOptions.ReferenceHighlighting, TaggerDelay.NearImmediate));
}
private ProducerPopulatedTagSource<AbstractNavigatableReferenceHighlightingTag, object> CreateTagSource(
ITextView textViewOpt, ITextBuffer subjectBuffer,
IAsynchronousOperationListener asyncListener,
IForegroundNotificationService notificationService)
public override SnapshotPoint? GetCaretPoint(ITextView textViewOpt, ITextBuffer subjectBuffer)
{
return new ReferenceHighlightingTagSource(textViewOpt, subjectBuffer, this, asyncListener, notificationService);
return textViewOpt.Caret.Position.Point.GetPoint(b => b.ContentType.IsOfType(ContentTypeNames.RoslynContentType), PositionAffinity.Successor);
}
public IEnumerable<SnapshotSpan> GetSpansToTag(ITextView textViewOpt, ITextBuffer subjectBuffer)
public override IEnumerable<SnapshotSpan> GetSpansToTag(ITextView textViewOpt, ITextBuffer subjectBuffer)
{
return textViewOpt.BufferGraph.GetTextBuffers(b => b.ContentType.IsOfType(ContentTypeNames.RoslynContentType))
.Select(b => b.CurrentSnapshot.GetFullSpan())
.ToList();
}
public Task ProduceTagsAsync(Context context)
public override Task ProduceTagsAsync(Context context)
{
// NOTE(cyrusn): Normally we'd limit ourselves to producing tags in the span we were
// asked about. However, we want to produce all tags here so that the user can actually
......@@ -104,21 +80,38 @@ public Task ProduceTagsAsync(Context context)
return SpecializedTasks.EmptyTask;
}
var position = context.CaretPosition.Value;
var caretPosition = context.CaretPosition.Value;
Workspace workspace;
if (!Workspace.TryGetWorkspace(position.Snapshot.AsText().Container, out workspace))
if (!Workspace.TryGetWorkspace(caretPosition.Snapshot.AsText().Container, out workspace))
{
return SpecializedTasks.EmptyTask;
}
var document = context.SpansToTag.First(vt => vt.SnapshotSpan.Snapshot == position.Snapshot).Document;
var document = context.SpansToTag.First(vt => vt.SnapshotSpan.Snapshot == caretPosition.Snapshot).Document;
if (document == null)
{
return SpecializedTasks.EmptyTask;
}
return ProduceTagsAsync(context, position, workspace, document);
// Don't produce tags if the feature is not enabled.
if (!workspace.Options.GetOption(FeatureOnOffOptions.ReferenceHighlighting, document.Project.Language))
{
return SpecializedTasks.EmptyTask;
}
var existingTags = context.GetExistingTags(new SnapshotSpan(caretPosition, 0));
if (!existingTags.IsEmpty())
{
// We already have a tag at this position. So the user is moving from one highlight
// tag to another. In this case we don't want to recompute anything. Let our caller
// know that we should preserve all tags.
context.SetSpansTagged(SpecializedCollections.EmptyEnumerable<DocumentSnapshotSpan>());
return SpecializedTasks.EmptyTask;
}
// Otherwise, we need to go produce all tags.
return ProduceTagsAsync(context, caretPosition, workspace, document);
}
internal async Task ProduceTagsAsync(
......@@ -128,11 +121,6 @@ public Task ProduceTagsAsync(Context context)
Document document)
{
var cancellationToken = context.CancellationToken;
// Don't produce tags if the feature is not enabled.
if (!workspace.Options.GetOption(FeatureOnOffOptions.ReferenceHighlighting, document.Project.Language))
{
return;
}
var solution = document.Project.Solution;
......
......@@ -49,6 +49,8 @@ internal partial class ProducerPopulatedTagSource<TTag, TState> : TagSource<TTag
#region Fields that can only be accessed from the foreground thread
private readonly ITextView _textViewOpt;
/// <summary>
/// Our tagger event source that lets us know when we should call into the tag producer for
/// new tags.
......@@ -74,26 +76,26 @@ internal partial class ProducerPopulatedTagSource<TTag, TState> : TagSource<TTag
IAsynchronousTaggerDataSource<TTag, TState> dataSource,
IAsynchronousOperationListener asyncListener,
IForegroundNotificationService notificationService)
: base(textViewOpt, subjectBuffer, dataSource.IgnoreCaretMovementToExistingTag, notificationService, asyncListener)
: base(subjectBuffer, notificationService, asyncListener)
{
if (dataSource.SpanTrackingMode == SpanTrackingMode.Custom)
{
throw new ArgumentException("SpanTrackingMode.Custom not allowed.", "spanTrackingMode");
}
_textViewOpt = textViewOpt;
_dataSource = dataSource;
_tagSpanComparer = new TagSpanComparer<TTag>(this.TagComparer);
this.CachedTagTrees = ImmutableDictionary.Create<ITextBuffer, TagSpanIntervalTree<TTag>>();
this.AccumulatedTextChanges = null;
_eventSource = dataSource.CreateEventSource(textViewOpt, subjectBuffer);
AttachEventHandlersAndStart();
}
private IEqualityComparer<TTag> TagComparer =>
private IEqualityComparer<TTag> TagComparer =>
_dataSource.TagComparer ?? EqualityComparer<TTag>.Default;
protected TextChangeRange? AccumulatedTextChanges
......@@ -154,6 +156,17 @@ private void AttachEventHandlersAndStart()
this.SubjectBuffer.Changed += OnSubjectBufferChanged;
}
if (_dataSource.CaretChangeBehavior.HasFlag(TaggerCaretChangeBehavior.RemoveAllTagsOnCaretMoveOutsideOfTag))
{
if (_textViewOpt == null)
{
throw new ArgumentException(
nameof(_dataSource.CaretChangeBehavior) + " can only be specified for an " + nameof(IViewTaggerProvider));
}
_textViewOpt.Caret.PositionChanged += OnCaretPositionChanged;
}
// Tell the interaction object to start issuing events.
_eventSource.Connect();
}
......@@ -167,6 +180,11 @@ protected override void Disconnect()
// Tell the interaction object to stop issuing events.
_eventSource.Disconnect();
if (_dataSource.CaretChangeBehavior.HasFlag(TaggerCaretChangeBehavior.RemoveAllTagsOnCaretMoveOutsideOfTag))
{
this._textViewOpt.Caret.PositionChanged -= OnCaretPositionChanged;
}
if (_dataSource.TextChangeBehavior.HasFlag(TaggerTextChangeBehavior.TrackTextChanges))
{
this.SubjectBuffer.Changed -= OnSubjectBufferChanged;
......@@ -193,13 +211,6 @@ private void OnUIUpdatesResumed(object sender, EventArgs e)
RaiseResumed();
}
private void OnSubjectBufferChanged(object sender, TextContentChangedEventArgs e)
{
this.WorkQueue.AssertIsForeground();
UpdateTagsForTextChange(e);
AccumulateTextChanges(e);
}
private void OnChanged(object sender, TaggerEventArgs e)
{
using (var token = this.Listener.BeginAsyncOperation("OnChanged"))
......@@ -209,12 +220,60 @@ private void OnChanged(object sender, TaggerEventArgs e)
this.WorkQueue.CancelCurrentWork();
// We don't currently have a request issued to re-compute our tags. Issue it for some
// time in the future
// time in the future.
RecalculateTagsOnChanged(e);
}
}
private void OnCaretPositionChanged(object sender, CaretPositionChangedEventArgs e)
{
this.AssertIsForeground();
Debug.Assert(_dataSource.CaretChangeBehavior.HasFlag(TaggerCaretChangeBehavior.RemoveAllTagsOnCaretMoveOutsideOfTag));
var caret = GetCaretPoint();
if (caret.HasValue)
{
// If it changed position and we're still in a tag, there's nothing more to do
var currentTags = GetTagIntervalTreeForBuffer(caret.Value.Snapshot.TextBuffer);
if (currentTags != null && currentTags.GetIntersectingSpans(new SnapshotSpan(caret.Value, 0)).Count > 0)
{
// Caret is inside a tag. No need to do anything.
return;
}
}
RemoveAllTags();
}
private void RemoveAllTags()
{
this.AssertIsForeground();
var oldTagTrees = this.CachedTagTrees;
this.CachedTagTrees = ImmutableDictionary<ITextBuffer, TagSpanIntervalTree<TTag>>.Empty;
var snapshot = this.SubjectBuffer.CurrentSnapshot;
var oldTagTree = GetTagTree(snapshot, oldTagTrees);
var newTagTree = GetTagTree(snapshot, this.CachedTagTrees);
var difference = ComputeDifference(snapshot, newTagTree, oldTagTree);
RaiseTagsChanged(snapshot.TextBuffer, difference);
}
protected SnapshotPoint? GetCaretPoint()
{
this.AssertIsForeground();
return _dataSource.GetCaretPoint(_textViewOpt, SubjectBuffer) ?? _textViewOpt?.GetCaretPoint(SubjectBuffer);
}
private void OnSubjectBufferChanged(object sender, TextContentChangedEventArgs e)
{
this.WorkQueue.AssertIsForeground();
UpdateTagsForTextChange(e);
AccumulateTextChanges(e);
}
private void AccumulateTextChanges(TextContentChangedEventArgs contentChanged)
{
this.WorkQueue.AssertIsForeground();
......@@ -254,12 +313,22 @@ private void UpdateTagsForTextChange(TextContentChangedEventArgs e)
{
this.WorkQueue.AssertIsForeground();
if (_dataSource.TextChangeBehavior.HasFlag(TaggerTextChangeBehavior.RemoveAllTags))
{
this.RemoveAllTags();
return;
}
// Don't bother going forward if we're not going adjust any tags based on edits.
if (!_dataSource.TextChangeBehavior.HasFlag(TaggerTextChangeBehavior.RemoveTagsThatIntersectEdits))
if (_dataSource.TextChangeBehavior.HasFlag(TaggerTextChangeBehavior.RemoveTagsThatIntersectEdits))
{
RemoveTagsThatIntersectEdit(e);
return;
}
}
private void RemoveTagsThatIntersectEdit(TextContentChangedEventArgs e)
{
if (!e.Changes.Any())
{
return;
......@@ -285,34 +354,30 @@ private void UpdateTagsForTextChange(TextContentChangedEventArgs e)
}
var allTags = treeForBuffer.GetSpans(e.After).ToList();
var newTreeForBuffer = new TagSpanIntervalTree<TTag>(
var newTagTree = new TagSpanIntervalTree<TTag>(
buffer,
treeForBuffer.SpanTrackingMode,
allTags.Except(tagsToRemove, _tagSpanComparer));
UpdateCachedTagsForBuffer(e.After, newTreeForBuffer);
}
private void UpdateCachedTagsForBuffer(ITextSnapshot snapshot, TagSpanIntervalTree<TTag> newTagsForBuffer)
{
this.WorkQueue.AssertIsForeground();
var oldCachedTagTrees = this.CachedTagTrees;
var snapshot = e.After;
this.CachedTagTrees = oldCachedTagTrees.SetItem(snapshot.TextBuffer, newTagsForBuffer);
var oldTagTrees = this.CachedTagTrees;
this.CachedTagTrees = oldTagTrees.SetItem(snapshot.TextBuffer, newTagTree);
// Grab our old tags. We might not have any, so in this case we'll just pretend it's
// empty
TagSpanIntervalTree<TTag> oldCachedTagsForBuffer = null;
if (!oldCachedTagTrees.TryGetValue(snapshot.TextBuffer, out oldCachedTagsForBuffer))
{
oldCachedTagsForBuffer = new TagSpanIntervalTree<TTag>(snapshot.TextBuffer, _dataSource.SpanTrackingMode);
}
var oldTagTree = GetTagTree(snapshot, oldTagTrees);
var difference = ComputeDifference(snapshot, oldCachedTagsForBuffer, newTagsForBuffer);
if (difference.Count > 0)
{
RaiseTagsChanged(snapshot.TextBuffer, difference);
}
var difference = ComputeDifference(snapshot, newTagTree, oldTagTree);
RaiseTagsChanged(snapshot.TextBuffer, difference);
}
private TagSpanIntervalTree<TTag> GetTagTree(ITextSnapshot snapshot, ImmutableDictionary<ITextBuffer, TagSpanIntervalTree<TTag>> tagTrees)
{
TagSpanIntervalTree<TTag> tagTree = null;
return tagTrees.TryGetValue(snapshot.TextBuffer, out tagTree)
? tagTree
: new TagSpanIntervalTree<TTag>(snapshot.TextBuffer, _dataSource.SpanTrackingMode);
}
private bool TryStealTagsFromRelatedTagSource(TextContentChangedEventArgs e)
......@@ -406,7 +471,7 @@ protected List<DocumentSnapshotSpan> GetSpansAndDocumentsToTag()
// TODO: Update to tag spans from all related documents.
var snapshotToDocumentMap = new Dictionary<ITextSnapshot, Document>();
var spansToTag = _dataSource.GetSpansToTag(TextViewOpt, SubjectBuffer) ?? this.GetFullBufferSpan();
var spansToTag = _dataSource.GetSpansToTag(_textViewOpt, SubjectBuffer) ?? this.GetFullBufferSpan();
var spansAndDocumentsToTag = spansToTag.Select(span =>
{
Document document = null;
......@@ -659,7 +724,8 @@ private IEnumerable<ITagSpan<TTag>> GetNonIntersectingTagSpans(IEnumerable<Snaps
var newTagSpans = SpecializedCollections.EmptyEnumerable<ITagSpan<TTag>>();
var context = new AsynchronousTaggerContext<TTag, TState>(oldState, spansToTag, caretPosition, textChangeRange, cancellationToken);
var context = new AsynchronousTaggerContext<TTag, TState>(
oldState, spansToTag, caretPosition, textChangeRange, oldTagTrees, cancellationToken);
await _dataSource.ProduceTagsAsync(context).ConfigureAwait(false);
ProcessContext(oldTagTrees, context);
......@@ -669,7 +735,7 @@ private IEnumerable<ITagSpan<TTag>> GetNonIntersectingTagSpans(IEnumerable<Snaps
ImmutableDictionary<ITextBuffer, TagSpanIntervalTree<TTag>> oldTagTrees,
AsynchronousTaggerContext<TTag, TState> context)
{
var spansTagged = context.spansTagged;
var spansTagged = context._spansTagged;
var newTagTrees = ConvertToTagTrees(oldTagTrees, context.tagSpans, spansTagged);
ProcessNewTagTrees(spansTagged, oldTagTrees, newTagTrees, context.State, context.CancellationToken);
......@@ -790,7 +856,7 @@ public override ITagSpanIntervalTree<TTag> GetTagIntervalTreeForBuffer(ITextBuff
// use can cancel out if this takes a long time.
var context = new AsynchronousTaggerContext<TTag, TState>(
this.State, spansToTag, GetCaretPoint(), this.AccumulatedTextChanges, CancellationToken.None);
this.State, spansToTag, GetCaretPoint(), this.AccumulatedTextChanges, oldTagTrees, CancellationToken.None);
_dataSource.ProduceTagsAsync(context).Wait();
var newTagTrees = ProcessContext(oldTagTrees, context);
......
......@@ -36,7 +36,6 @@ internal abstract partial class TagSource<TTag> :
/// </summary>
internal readonly AsynchronousSerialWorkQueue WorkQueue;
protected readonly ITextView TextViewOpt;
protected readonly ITextBuffer SubjectBuffer;
/// <summary>
......@@ -48,20 +47,15 @@ internal abstract partial class TagSource<TTag> :
/// foreground notification service
/// </summary>
private readonly IForegroundNotificationService _notificationService;
private readonly bool _ignoreCaretMovementToExistingTag;
#endregion
protected TagSource(
ITextView textViewOpt,
ITextBuffer subjectBuffer,
bool ignoreCaretMovementToExistingTag,
IForegroundNotificationService notificationService,
IAsynchronousOperationListener asyncListener)
{
TextViewOpt = textViewOpt;
this.SubjectBuffer = subjectBuffer;
_ignoreCaretMovementToExistingTag = ignoreCaretMovementToExistingTag;
_notificationService = notificationService;
this.Listener = asyncListener;
......@@ -94,40 +88,10 @@ public void RegisterNotification(Action action, int delay, CancellationToken can
/// Called by derived types to enqueue tags re-calculation request
/// </summary>
protected void RecalculateTagsOnChanged(TaggerEventArgs e)
{
if (_ignoreCaretMovementToExistingTag && e.Kind == PredefinedChangedEventKinds.CaretPositionChanged)
{
this.AssertIsForeground();
var caret = GetCaretPoint();
if (caret.HasValue)
{
// If it changed position and we're still in a tag, there's nothing more to do
var currentTags = GetTagIntervalTreeForBuffer(caret.Value.Snapshot.TextBuffer);
if (currentTags != null && currentTags.GetIntersectingSpans(new SnapshotSpan(caret.Value, 0)).Count > 0)
{
return;
}
}
}
RecalculateTagsOnChangedCore(e);
}
protected virtual void RecalculateTagsOnChangedCore(TaggerEventArgs e)
{
RegisterNotification(RecomputeTagsForeground, e.Delay.ComputeTimeDelayMS(this.SubjectBuffer), this.WorkQueue.CancellationToken);
}
/// <summary>
/// Implemented by derived types to return the caret position.
/// </summary>
/// <remarks>Called on the foreground thread.</remarks>
protected virtual SnapshotPoint? GetCaretPoint()
{
return TextViewOpt?.GetCaretPoint(SubjectBuffer);
}
protected virtual void Disconnect()
{
this.WorkQueue.AssertIsForeground();
......
......@@ -5,14 +5,21 @@
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Editor.Shared.Tagging;
using Microsoft.CodeAnalysis.Text;
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.Text.Tagging;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.Editor.Tagging
{
internal class AsynchronousTaggerContext<TTag, TState> where TTag : ITag
{
private readonly ImmutableDictionary<ITextBuffer, TagSpanIntervalTree<TTag>> _existingTags;
internal IEnumerable<DocumentSnapshotSpan> _spansTagged;
internal ImmutableArray<ITagSpan<TTag>>.Builder tagSpans = ImmutableArray.CreateBuilder<ITagSpan<TTag>>();
public TState State { get; set; }
public IEnumerable<DocumentSnapshotSpan> SpansToTag { get; }
public SnapshotPoint? CaretPosition { get; }
......@@ -25,14 +32,23 @@ internal class AsynchronousTaggerContext<TTag, TState> where TTag : ITag
public TextChangeRange? TextChangeRange { get; }
public CancellationToken CancellationToken { get; }
internal IEnumerable<DocumentSnapshotSpan> spansTagged;
internal ImmutableArray<ITagSpan<TTag>>.Builder tagSpans = ImmutableArray.CreateBuilder<ITagSpan<TTag>>();
// For testing only.
internal AsynchronousTaggerContext(
TState state,
IEnumerable<DocumentSnapshotSpan> spansToTag,
SnapshotPoint? caretPosition,
TextChangeRange? textChangeRange,
CancellationToken cancellationToken)
: this(state, spansToTag, caretPosition, textChangeRange, null, cancellationToken)
{
}
internal AsynchronousTaggerContext(
TState state,
IEnumerable<DocumentSnapshotSpan> spansToTag,
SnapshotPoint? caretPosition,
TextChangeRange? textChangeRange,
ImmutableDictionary<ITextBuffer, TagSpanIntervalTree<TTag>> existingTags,
CancellationToken cancellationToken)
{
this.State = state;
......@@ -41,7 +57,8 @@ internal class AsynchronousTaggerContext<TTag, TState> where TTag : ITag
this.TextChangeRange = textChangeRange;
this.CancellationToken = cancellationToken;
this.spansTagged = spansToTag;
_spansTagged = spansToTag;
_existingTags = existingTags;
}
public void AddTag(ITagSpan<TTag> tag)
......@@ -62,7 +79,15 @@ public void SetSpansTagged(IEnumerable<DocumentSnapshotSpan> spansTagged)
throw new ArgumentNullException(nameof(spansTagged));
}
this.spansTagged = spansTagged;
this._spansTagged = spansTagged;
}
public IEnumerable<ITagSpan<TTag>> GetExistingTags(SnapshotSpan span)
{
TagSpanIntervalTree<TTag> tree;
return _existingTags.TryGetValue(span.Snapshot.TextBuffer, out tree)
? tree.GetIntersectingSpans(span)
: SpecializedCollections.EmptyEnumerable<ITagSpan<TTag>>();
}
}
}
......@@ -18,7 +18,7 @@ namespace Microsoft.CodeAnalysis.Editor.Tagging
internal abstract class AsynchronousTaggerDataSource<TTag, TState> : IAsynchronousTaggerDataSource<TTag, TState> where TTag : ITag
{
public virtual TaggerTextChangeBehavior TextChangeBehavior => TaggerTextChangeBehavior.None;
public virtual bool IgnoreCaretMovementToExistingTag => false;
public virtual TaggerCaretChangeBehavior CaretChangeBehavior => TaggerCaretChangeBehavior.None;
public virtual SpanTrackingMode SpanTrackingMode => SpanTrackingMode.EdgeExclusive;
public virtual bool ComputeTagsSynchronouslyIfNoAsynchronousComputationHasCompleted => false;
......@@ -29,6 +29,12 @@ internal abstract class AsynchronousTaggerDataSource<TTag, TState> : IAsynchrono
protected AsynchronousTaggerDataSource() { }
public virtual SnapshotPoint? GetCaretPoint(ITextView textViewOpt, ITextBuffer subjectBuffer)
{
// Use 'null' to indicate that the tagger should get the default caret position.
return null;
}
public virtual IEnumerable<SnapshotSpan> GetSpansToTag(ITextView textViewOpt, ITextBuffer subjectBuffer)
{
// Use 'null' to indicate that the tagger should tag the default set of spans.
......
......@@ -12,6 +12,23 @@
namespace Microsoft.CodeAnalysis.Editor.Tagging
{
/// <summary>
/// Flags that affect how the tagger infrastructure responds to caret changes.
/// </summary>
[Flags]
internal enum TaggerCaretChangeBehavior
{
/// <summary>
/// No special caret change behavior.
/// </summary>
None = 0,
/// <summary>
/// If the caret moves outside of a tag, immediately remove all existing tags.
/// </summary>
RemoveAllTagsOnCaretMoveOutsideOfTag = 1 << 0,
}
/// <summary>
/// Data source for the <see cref="AsynchronousTaggerProvider{TTag, TState}"/>. This type tells the
/// <see cref="AsynchronousTaggerProvider{TTag, TState}"/> when tags need to be recomputed, as well
......@@ -31,6 +48,11 @@ internal interface IAsynchronousTaggerDataSource<TTag, TState> where TTag : ITag
/// </summary>
TaggerTextChangeBehavior TextChangeBehavior { get; }
/// <summary>
/// The bahavior the tagger will have when changes happen to the caret.
/// </summary>
TaggerCaretChangeBehavior CaretChangeBehavior { get; }
/// <summary>
/// The behavior of tags that are created by the async tagger. This will matter for tags
/// created for a previous version of a document that are mapped forward by the async
......@@ -43,13 +65,6 @@ internal interface IAsynchronousTaggerDataSource<TTag, TState> where TTag : ITag
/// </summary>
bool ComputeTagsSynchronouslyIfNoAsynchronousComputationHasCompleted { get; }
/// <summary>
/// <code>true</code> if the tagger infrastructure can avoid recomputing tags when the
/// user's caret moves to an already existing tag. This is useful to avoid work for
/// features like Highlighting if the user is navigating between highlight tags.
/// </summary>
bool IgnoreCaretMovementToExistingTag { get; }
/// <summary>
/// Options controlling this tagger. The tagger infrastructure will check this option
/// against the buffer it is associated with to see if it should tag or not.
......@@ -77,6 +92,17 @@ internal interface IAsynchronousTaggerDataSource<TTag, TState> where TTag : ITag
/// </summary>
ITaggerEventSource CreateEventSource(ITextView textViewOpt, ITextBuffer subjectBuffer);
/// <summary>
/// Called by the <see cref="AsynchronousTaggerProvider{TTag, TState}"/> infrastructure to
/// determine the caret position. This value will be passed in as the value to
/// <see cref="AsynchronousTaggerContext{TTag, TState}.CaretPosition"/> in the call to
/// <see cref="ProduceTagsAsync"/>.
///
/// Return <code>null</code> to get the default tagger behavior. This will the caret
/// position in the subject buffer this tagger is attached to.
/// </summary>
SnapshotPoint? GetCaretPoint(ITextView textViewOpt, ITextBuffer subjectBuffer);
/// <summary>
/// Called by the <see cref="AsynchronousTaggerProvider{TTag, TState}"/> infrastructure to determine
/// the set of spans that it should asynchronously tag. This will be called in response to
......
......@@ -3,7 +3,7 @@
namespace Microsoft.CodeAnalysis.Editor.Tagging
{
/// <summary>
/// What the async tagger infrastructure should do in the presence of text edits.
/// Flags that affect how the tagger infrastructure responds to text changes.
/// </summary>
[Flags]
internal enum TaggerTextChangeBehavior
......@@ -22,12 +22,20 @@ internal enum TaggerTextChangeBehavior
TrackTextChanges = 1 << 0,
/// <summary>
/// The async tagger infrastructure will not track text changes to the subject buffer it is
/// The async tagger infrastructure will track text changes to the subject buffer it is
/// attached to. The text changes will be provided to the <see cref="AsynchronousTaggerContext{TTag, TState}"/>
/// that is passed to <see cref="IAsynchronousTaggerDataSource{TTag, TState}.ProduceTagsAsync"/>.
///
/// Tags that intersect the text change range will immediately removed.
/// On any edit, tags that intersect the text change range will immediately removed.
/// </summary>
RemoveTagsThatIntersectEdits = TrackTextChanges | (1 << 1),
/// <summary>
/// The async tagger infrastructure will track text changes to the subject buffer it is
/// attached to.
///
/// On any edit all tags will we be removed.
/// </summary>
RemoveTagsThatIntersectEdits = TrackTextChanges | (1 << 1)
RemoveAllTags = TrackTextChanges | (1 << 2),
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册