From 6fadc263cf8bbc671bf076751b841e3960f14639 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 26 May 2024 15:53:58 -0700 Subject: [PATCH 01/13] Lower allocations in tagging --- .../Collections/TemporaryArrayExtensions.cs | 11 +++ .../Core/Copilot/CopilotTaggerProvider.cs | 6 +- .../InlineHintsDataTaggerProvider.cs | 8 +- ...ReferenceHighlightingViewTaggerProvider.cs | 8 +- ...ousTaggerProvider.TagSource_ProduceTags.cs | 91 ++++++++++++------- .../AbstractAsynchronousTaggerProvider.cs | 6 +- ...erProvider.SingleViewportTaggerProvider.cs | 19 ++-- 7 files changed, 93 insertions(+), 56 deletions(-) diff --git a/src/Compilers/Core/Portable/Collections/TemporaryArrayExtensions.cs b/src/Compilers/Core/Portable/Collections/TemporaryArrayExtensions.cs index 62ef11b4bb4d0..8efb7283e366f 100644 --- a/src/Compilers/Core/Portable/Collections/TemporaryArrayExtensions.cs +++ b/src/Compilers/Core/Portable/Collections/TemporaryArrayExtensions.cs @@ -107,6 +107,17 @@ private static void ThrowSequenceContainsMoreThanOneElement() return result; } + public static T? FirstOrDefault(this in TemporaryArray array, Func predicate, TArg arg) + { + foreach (var item in array) + { + if (predicate(item, arg)) + return item; + } + + return default; + } + public static void AddIfNotNull(this ref TemporaryArray array, T? value) where T : struct { diff --git a/src/EditorFeatures/Core/Copilot/CopilotTaggerProvider.cs b/src/EditorFeatures/Core/Copilot/CopilotTaggerProvider.cs index 9647385753adc..450bdf6b7662c 100644 --- a/src/EditorFeatures/Core/Copilot/CopilotTaggerProvider.cs +++ b/src/EditorFeatures/Core/Copilot/CopilotTaggerProvider.cs @@ -14,6 +14,7 @@ using Microsoft.CodeAnalysis.Editor.Tagging; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Options; +using Microsoft.CodeAnalysis.Shared.Collections; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.CodeAnalysis.Workspaces; @@ -58,13 +59,14 @@ protected override ITaggerEventSource CreateEventSource(ITextView textView, ITex TaggerEventSources.OnTextChanged(subjectBuffer)); } - protected override IEnumerable GetSpansToTag(ITextView? textView, ITextBuffer subjectBuffer) + protected override void AddSpansToTag(ITextView? textView, ITextBuffer subjectBuffer, ref TemporaryArray result) { this.ThreadingContext.ThrowIfNotOnUIThread(); Contract.ThrowIfNull(textView); // We only care about the cases where we have caret. - return textView.GetCaretPoint(subjectBuffer) is { } caret ? [new SnapshotSpan(caret, 0)] : []; + if (textView.GetCaretPoint(subjectBuffer) is { } caret) + result.Add(new SnapshotSpan(caret, 0)); } protected override async Task ProduceTagsAsync(TaggerContext context, DocumentSnapshotSpan spanToTag, int? caretPosition, CancellationToken cancellationToken) diff --git a/src/EditorFeatures/Core/InlineHints/InlineHintsDataTaggerProvider.cs b/src/EditorFeatures/Core/InlineHints/InlineHintsDataTaggerProvider.cs index 16c437592808a..047adfa6421e6 100644 --- a/src/EditorFeatures/Core/InlineHints/InlineHintsDataTaggerProvider.cs +++ b/src/EditorFeatures/Core/InlineHints/InlineHintsDataTaggerProvider.cs @@ -14,6 +14,7 @@ using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.InlineHints; using Microsoft.CodeAnalysis.Options; +using Microsoft.CodeAnalysis.Shared.Collections; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.CodeAnalysis.Text.Shared.Extensions; @@ -77,7 +78,7 @@ protected override ITaggerEventSource CreateEventSource(ITextView textView, ITex TaggerEventSources.OnGlobalOptionChanged(GlobalOptions, InlineHintsOptionsStorage.ForImplicitObjectCreation)); } - protected override IEnumerable GetSpansToTag(ITextView? textView, ITextBuffer subjectBuffer) + protected override void AddSpansToTag(ITextView? textView, ITextBuffer subjectBuffer, ref TemporaryArray result) { this.ThreadingContext.ThrowIfNotOnUIThread(); Contract.ThrowIfNull(textView); @@ -88,10 +89,11 @@ protected override IEnumerable GetSpansToTag(ITextView? textView, if (visibleSpanOpt == null) { // Couldn't find anything visible, just fall back to tagging all hint locations - return base.GetSpansToTag(textView, subjectBuffer); + base.AddSpansToTag(textView, subjectBuffer, ref result); + return; } - return [visibleSpanOpt.Value]; + result.Add(visibleSpanOpt.Value); } protected override async Task ProduceTagsAsync( diff --git a/src/EditorFeatures/Core/ReferenceHighlighting/ReferenceHighlightingViewTaggerProvider.cs b/src/EditorFeatures/Core/ReferenceHighlighting/ReferenceHighlightingViewTaggerProvider.cs index ad02af6c7d1a2..95795e81268ce 100644 --- a/src/EditorFeatures/Core/ReferenceHighlighting/ReferenceHighlightingViewTaggerProvider.cs +++ b/src/EditorFeatures/Core/ReferenceHighlighting/ReferenceHighlightingViewTaggerProvider.cs @@ -20,6 +20,7 @@ using Microsoft.CodeAnalysis.Internal.Log; using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.ReferenceHighlighting; +using Microsoft.CodeAnalysis.Shared.Collections; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.CodeAnalysis.Text.Shared.Extensions; @@ -86,13 +87,12 @@ protected override ITaggerEventSource CreateEventSource(ITextView textView, ITex return textViewOpt.BufferGraph.MapDownToFirstMatch(textViewOpt.Selection.Start.Position, PointTrackingMode.Positive, b => IsSupportedContentType(b.ContentType), PositionAffinity.Successor); } - protected override IEnumerable GetSpansToTag(ITextView textViewOpt, ITextBuffer subjectBuffer) + protected override void AddSpansToTag(ITextView textViewOpt, ITextBuffer subjectBuffer, ref TemporaryArray result) { // Note: this may return no snapshot spans. We have to be resilient to that // when processing the TaggerContext<>.SpansToTag below. - return textViewOpt.BufferGraph.GetTextBuffers(b => IsSupportedContentType(b.ContentType)) - .Select(b => b.CurrentSnapshot.GetFullSpan()) - .ToList(); + foreach (var buffer in textViewOpt.BufferGraph.GetTextBuffers(b => IsSupportedContentType(b.ContentType))) + result.Add(buffer.CurrentSnapshot.GetFullSpan()); } protected override Task ProduceTagsAsync( diff --git a/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource_ProduceTags.cs b/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource_ProduceTags.cs index e9e4fdfa3b487..456cf985b308b 100644 --- a/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource_ProduceTags.cs +++ b/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource_ProduceTags.cs @@ -15,6 +15,7 @@ using Microsoft.CodeAnalysis.Internal.Log; using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Shared.Collections; using Microsoft.CodeAnalysis.Text; using Microsoft.CodeAnalysis.Workspaces; using Microsoft.VisualStudio.Text; @@ -287,7 +288,7 @@ await _visibilityTracker.DelayWhileNonVisibleAsync( // Make a copy of all the data we need while we're on the foreground. Then switch to a threadpool // thread to do the computation. Finally, once new tags have been computed, then we update our state // again on the foreground. - var spansToTag = GetSpansAndDocumentsToTag(); + var snapshotSpansToTag = GetSnapshotSpansToTag(frozenPartialSemantics, cancellationToken); var caretPosition = _dataSource.GetCaretPoint(_textView, _subjectBuffer); // If we're being called from within a blocking JTF.Run call, we don't want to switch to the background @@ -298,12 +299,9 @@ await _visibilityTracker.DelayWhileNonVisibleAsync( if (cancellationToken.IsCancellationRequested) return null; - if (frozenPartialSemantics) - { - spansToTag = spansToTag.SelectAsArray(ds => new DocumentSnapshotSpan( - ds.Document?.WithFrozenPartialSemantics(cancellationToken), - ds.SnapshotSpan)); - } + // Now that we're on the threadpool, figure out what documents we need to tag corresponding to those + // SnapshotSpan the underlying data source asked us to tag. + var spansToTag = GetDocumentSnapshotSpansToTag(snapshotSpansToTag, frozenPartialSemantics, cancellationToken); // Now spin, trying to compute the updated tags. We only need to do this as the tag state is also // allowed to change on the UI thread (for example, taggers can say they want tags to be immediately @@ -348,32 +346,52 @@ await _visibilityTracker.DelayWhileNonVisibleAsync( return newTagTrees; } - } - private ImmutableArray GetSpansAndDocumentsToTag() - { - _dataSource.ThreadingContext.ThrowIfNotOnUIThread(); - - // TODO: Update to tag spans from all related documents. + static ImmutableArray GetDocumentSnapshotSpansToTag( + ImmutableArray snapshotSpansToTag, + bool frozenPartialSemantics, + CancellationToken cancellationToken) + { + // We only ever have a tiny number of snapshots we're classifying. So it's easier and faster to just store + // the mapping from it to a particular document in an on-stack array. + // + // document can be null if the buffer the given span is part of is not part of our workspace. + using var snapshotToDocument = TemporaryArray<(ITextSnapshot snapshot, Document? document)>.Empty; - using var _ = PooledDictionary.GetInstance(out var snapshotToDocumentMap); - var spansToTag = _dataSource.GetSpansToTag(_textView, _subjectBuffer); + var result = new FixedSizeArrayBuilder(snapshotSpansToTag.Length); - var spansAndDocumentsToTag = spansToTag.SelectAsArray(span => - { - if (!snapshotToDocumentMap.TryGetValue(span.Snapshot, out var document)) + foreach (var spanToTag in snapshotSpansToTag) { - CheckSnapshot(span.Snapshot); + var snapshot = spanToTag.Snapshot; + var document = snapshotToDocument.FirstOrDefault( + static (t, snapshot) => t.snapshot == snapshot, snapshot).document; + + if (document is null) + { + CheckSnapshot(snapshot); - document = span.Snapshot.GetOpenDocumentInCurrentContextWithChanges(); - snapshotToDocumentMap[span.Snapshot] = document; + document = snapshot.GetOpenDocumentInCurrentContextWithChanges(); + if (frozenPartialSemantics) + document = document?.WithFrozenPartialSemantics(cancellationToken); + + snapshotToDocument.Add((snapshot, document)); + } + + result.Add(new DocumentSnapshotSpan(document, spanToTag)); } - // document can be null if the buffer the given span is part of is not part of our workspace. - return new DocumentSnapshotSpan(document, span); - }); + return result.MoveToImmutable(); + } + } + + private ImmutableArray GetSnapshotSpansToTag( + bool frozenPartialSemantics, CancellationToken cancellationToken) + { + _dataSource.ThreadingContext.ThrowIfNotOnUIThread(); - return spansAndDocumentsToTag; + using var spansToTag = TemporaryArray.Empty; + _dataSource.AddSpansToTag(_textView, _subjectBuffer, ref spansToTag.AsRef()); + return spansToTag.ToImmutableAndClear(); } [Conditional("DEBUG")] @@ -483,21 +501,24 @@ private static void AddNonIntersectingTagSpans( oldTagTree.RemoveIntersectingTagSpans(spansToInvalidate, nonIntersectingTagSpans); } - private bool ShouldSkipTagProduction() + private async ValueTask ProduceTagsAsync(TaggerContext context, CancellationToken cancellationToken) { + // If the feature is disabled, then just produce no tags. if (_dataSource.Options.OfType>().Any(option => !_dataSource.GlobalOptions.GetOption(option))) - return true; + return; var languageName = _subjectBuffer.GetLanguageName(); - return _dataSource.Options.OfType>().Any(option => languageName == null || !_dataSource.GlobalOptions.GetOption(option, languageName)); - } + if (_dataSource.Options.OfType>().Any( + option => languageName == null || !_dataSource.GlobalOptions.GetOption(option, languageName))) + { + return; + } - private Task ProduceTagsAsync(TaggerContext context, CancellationToken cancellationToken) - { - // If the feature is disabled, then just produce no tags. - return ShouldSkipTagProduction() - ? Task.CompletedTask - : _dataSource.ProduceTagsAsync(context, cancellationToken); + // If we have no spans to tag, there's no point in continuing. + if (context.SpansToTag.IsEmpty) + return; + + await _dataSource.ProduceTagsAsync(context, cancellationToken).ConfigureAwait(false); } private Dictionary ProcessNewTagTrees( diff --git a/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.cs b/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.cs index b5ebabac14f20..675068027aedb 100644 --- a/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.cs +++ b/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.cs @@ -15,6 +15,7 @@ using Microsoft.CodeAnalysis.Editor.Shared.Options; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.Options; +using Microsoft.CodeAnalysis.Shared.Collections; using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.CodeAnalysis.Tagging; using Microsoft.CodeAnalysis.Text; @@ -214,10 +215,11 @@ private void StoreTagSource(ITextView? textView, ITextBuffer subjectBuffer, TagS /// and will asynchronously call into at some point in /// the future to produce tags for these spans. /// - protected virtual IEnumerable GetSpansToTag(ITextView? textView, ITextBuffer subjectBuffer) + protected virtual void AddSpansToTag( + ITextView? textView, ITextBuffer subjectBuffer, ref TemporaryArray result) { // For a standard tagger, the spans to tag is the span of the entire snapshot. - return [subjectBuffer.CurrentSnapshot.GetFullSpan()]; + result.Add(subjectBuffer.CurrentSnapshot.GetFullSpan()); } /// diff --git a/src/EditorFeatures/Core/Tagging/AsynchronousViewportTaggerProvider.SingleViewportTaggerProvider.cs b/src/EditorFeatures/Core/Tagging/AsynchronousViewportTaggerProvider.SingleViewportTaggerProvider.cs index a51d376bd7f02..ec253fb98b43e 100644 --- a/src/EditorFeatures/Core/Tagging/AsynchronousViewportTaggerProvider.SingleViewportTaggerProvider.cs +++ b/src/EditorFeatures/Core/Tagging/AsynchronousViewportTaggerProvider.SingleViewportTaggerProvider.cs @@ -59,7 +59,7 @@ protected override TaggerDelay EventChangeDelay // looked at. => _viewPortToTag == ViewPortToTag.InView ? _callback.EventChangeDelay : TaggerDelay.NonFocus; - protected override IEnumerable GetSpansToTag(ITextView? textView, ITextBuffer subjectBuffer) + protected override void AddSpansToTag(ITextView? textView, ITextBuffer subjectBuffer, ref TemporaryArray result) { this.ThreadingContext.ThrowIfNotOnUIThread(); Contract.ThrowIfNull(textView); @@ -71,16 +71,20 @@ protected override IEnumerable GetSpansToTag(ITextView? textView, { // couldn't figure out the visible span. So the InView tagger will need to tag everything, and the // above/below tagger should tag nothing. - return _viewPortToTag == ViewPortToTag.InView - ? base.GetSpansToTag(textView, subjectBuffer) - : []; + if (_viewPortToTag == ViewPortToTag.InView) + base.AddSpansToTag(textView, subjectBuffer, ref result); + + return; } var visibleSpan = visibleSpanOpt.Value; // If we're the 'InView' tagger, tag what was visible. if (_viewPortToTag is ViewPortToTag.InView) - return [visibleSpan]; + { + result.Add(visibleSpan); + return; + } // For the above/below tagger, broaden the span to to the requested portion above/below what's visible, then // subtract out the visible range. @@ -90,8 +94,6 @@ protected override IEnumerable GetSpansToTag(ITextView? textView, var widenedSpan = widenedSpanOpt.Value; Contract.ThrowIfFalse(widenedSpan.Span.Contains(visibleSpan.Span), "The widened span must be at least as large as the visible one."); - using var result = TemporaryArray.Empty; - if (_viewPortToTag is ViewPortToTag.Above) { var aboveSpan = new SnapshotSpan(visibleSpan.Snapshot, Span.FromBounds(widenedSpan.Span.Start, visibleSpan.Span.Start)); @@ -101,12 +103,9 @@ protected override IEnumerable GetSpansToTag(ITextView? textView, else if (_viewPortToTag is ViewPortToTag.Below) { var belowSpan = new SnapshotSpan(visibleSpan.Snapshot, Span.FromBounds(visibleSpan.Span.End, widenedSpan.Span.End)); - if (!belowSpan.IsEmpty) result.Add(belowSpan); } - - return result.ToImmutableAndClear(); } protected override async Task ProduceTagsAsync( From ee0a71c668790ddd04acd36dff87adc2fbea05f5 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 26 May 2024 15:57:40 -0700 Subject: [PATCH 02/13] Cleanup --- ...ousTaggerProvider.TagSource_ProduceTags.cs | 21 +++++++++---------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource_ProduceTags.cs b/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource_ProduceTags.cs index bbab71c3f4f95..4030aacc1d74a 100644 --- a/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource_ProduceTags.cs +++ b/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource_ProduceTags.cs @@ -292,7 +292,7 @@ await _visibilityTracker.DelayWhileNonVisibleAsync( // Make a copy of all the data we need while we're on the foreground. Then switch to a threadpool // thread to do the computation. Finally, once new tags have been computed, then we update our state // again on the foreground. - var snapshotSpansToTag = GetSnapshotSpansToTag(frozenPartialSemantics, cancellationToken); + var snapshotSpansToTag = GetSnapshotSpansToTag(); var caretPosition = _dataSource.GetCaretPoint(_textView, _subjectBuffer); // If we're being called from within a blocking JTF.Run call, we don't want to switch to the background @@ -351,6 +351,15 @@ await _visibilityTracker.DelayWhileNonVisibleAsync( return newTagTrees; } + ImmutableArray GetSnapshotSpansToTag() + { + _dataSource.ThreadingContext.ThrowIfNotOnUIThread(); + + using var spansToTag = TemporaryArray.Empty; + _dataSource.AddSpansToTag(_textView, _subjectBuffer, ref spansToTag.AsRef()); + return spansToTag.ToImmutableAndClear(); + } + static ImmutableArray GetDocumentSnapshotSpansToTag( ImmutableArray snapshotSpansToTag, bool frozenPartialSemantics, @@ -388,16 +397,6 @@ static ImmutableArray GetDocumentSnapshotSpansToTag( } } - private ImmutableArray GetSnapshotSpansToTag( - bool frozenPartialSemantics, CancellationToken cancellationToken) - { - _dataSource.ThreadingContext.ThrowIfNotOnUIThread(); - - using var spansToTag = TemporaryArray.Empty; - _dataSource.AddSpansToTag(_textView, _subjectBuffer, ref spansToTag.AsRef()); - return spansToTag.ToImmutableAndClear(); - } - [Conditional("DEBUG")] private static void CheckSnapshot(ITextSnapshot snapshot) { From d082aee0bb7d8d914551f5c0b1e7d115ac03ca6d Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 26 May 2024 15:59:10 -0700 Subject: [PATCH 03/13] Remove --- ...ctAsynchronousTaggerProvider.TagSource_ProduceTags.cs | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource_ProduceTags.cs b/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource_ProduceTags.cs index 4030aacc1d74a..30ba1aaf8e51c 100644 --- a/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource_ProduceTags.cs +++ b/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource_ProduceTags.cs @@ -503,15 +503,6 @@ private ImmutableDictionary> ComputeNewTa } } - private bool ShouldSkipTagProduction() - { - if (_dataSource.Options.OfType>().Any(option => !_dataSource.GlobalOptions.GetOption(option))) - return true; - - var languageName = _subjectBuffer.GetLanguageName(); - return _dataSource.Options.OfType>().Any(option => languageName == null || !_dataSource.GlobalOptions.GetOption(option, languageName)); - } - private async ValueTask ProduceTagsAsync(TaggerContext context, CancellationToken cancellationToken) { // If the feature is disabled, then just produce no tags. From 7b559ce9d9b4c54dee70900d8b2def7c421ff001 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 26 May 2024 16:12:26 -0700 Subject: [PATCH 04/13] Switch to oneormay --- .../Portable/Collections/TemporaryArray`1.cs | 20 ++++++++++++- .../ActiveStatementTaggerProvider.cs | 4 +-- ...ReferenceHighlightingViewTaggerProvider.cs | 10 +++---- ...ousTaggerProvider.TagSource_ProduceTags.cs | 29 ++++++++++++++----- .../Core/Tagging/TaggerContext.cs | 13 +++++---- 5 files changed, 54 insertions(+), 22 deletions(-) diff --git a/src/Compilers/Core/Portable/Collections/TemporaryArray`1.cs b/src/Compilers/Core/Portable/Collections/TemporaryArray`1.cs index 72e9ac3c8196b..cb06950e4a2e3 100644 --- a/src/Compilers/Core/Portable/Collections/TemporaryArray`1.cs +++ b/src/Compilers/Core/Portable/Collections/TemporaryArray`1.cs @@ -265,11 +265,29 @@ public readonly Enumerator GetEnumerator() return new Enumerator(in this); } + /// + /// Create an with the elements currently held in the temporary array, and clear the + /// array. + /// + public OneOrMany ToOneOrManyAndClear() + { + switch (this.Count) + { + case 0: + return OneOrMany.Empty; + case 1: + var result = OneOrMany.Create(this[0]); + this.Clear(); + return result; + default: + return new(this.ToImmutableAndClear()); + } + } + /// /// Create an with the elements currently held in the temporary array, and clear /// the array. /// - /// public ImmutableArray ToImmutableAndClear() { if (_builder is not null) diff --git a/src/EditorFeatures/Core/EditAndContinue/ActiveStatementTaggerProvider.cs b/src/EditorFeatures/Core/EditAndContinue/ActiveStatementTaggerProvider.cs index 911b8fa3f5487..48a63a2556ed4 100644 --- a/src/EditorFeatures/Core/EditAndContinue/ActiveStatementTaggerProvider.cs +++ b/src/EditorFeatures/Core/EditAndContinue/ActiveStatementTaggerProvider.cs @@ -55,9 +55,9 @@ protected override ITaggerEventSource CreateEventSource(ITextView? textView, ITe protected override async Task ProduceTagsAsync( TaggerContext context, CancellationToken cancellationToken) { - Debug.Assert(context.SpansToTag.IsSingle()); + Debug.Assert(context.SpansToTag.Count == 1); - var spanToTag = context.SpansToTag.Single(); + var spanToTag = context.SpansToTag.First(); var document = spanToTag.Document; if (document == null) diff --git a/src/EditorFeatures/Core/ReferenceHighlighting/ReferenceHighlightingViewTaggerProvider.cs b/src/EditorFeatures/Core/ReferenceHighlighting/ReferenceHighlightingViewTaggerProvider.cs index 8eb6f68c7f624..6a4051f1985f6 100644 --- a/src/EditorFeatures/Core/ReferenceHighlighting/ReferenceHighlightingViewTaggerProvider.cs +++ b/src/EditorFeatures/Core/ReferenceHighlighting/ReferenceHighlightingViewTaggerProvider.cs @@ -145,8 +145,6 @@ private static async Task ProduceTagsAsync( HighlightingOptions options, CancellationToken cancellationToken) { - var solution = document.Project.Solution; - using (Logger.LogBlock(FunctionId.Tagger_ReferenceHighlighting_TagProducer_ProduceTags, cancellationToken)) { if (document != null) @@ -160,15 +158,17 @@ private static async Task ProduceTagsAsync( // We only want to search inside documents that correspond to the snapshots // we're looking at - var documentsToSearch = ImmutableHashSet.CreateRange(context.SpansToTag.Select(vt => vt.Document).WhereNotNull()); + var documentsToSearchBuilder = ImmutableHashSet.CreateBuilder(); + foreach (var snapshotSpan in context.SpansToTag) + documentsToSearchBuilder.AddIfNotNull(snapshotSpan.Document); + + var documentsToSearch = documentsToSearchBuilder.ToImmutable(); var documentHighlightsList = await service.GetDocumentHighlightsAsync( document, position, documentsToSearch, options, cancellationToken).ConfigureAwait(false); if (documentHighlightsList != null) { foreach (var documentHighlights in documentHighlightsList) - { AddTagSpans(context, documentHighlights, cancellationToken); - } } } } diff --git a/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource_ProduceTags.cs b/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource_ProduceTags.cs index 30ba1aaf8e51c..f588889861325 100644 --- a/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource_ProduceTags.cs +++ b/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource_ProduceTags.cs @@ -351,17 +351,17 @@ await _visibilityTracker.DelayWhileNonVisibleAsync( return newTagTrees; } - ImmutableArray GetSnapshotSpansToTag() + OneOrMany GetSnapshotSpansToTag() { _dataSource.ThreadingContext.ThrowIfNotOnUIThread(); using var spansToTag = TemporaryArray.Empty; _dataSource.AddSpansToTag(_textView, _subjectBuffer, ref spansToTag.AsRef()); - return spansToTag.ToImmutableAndClear(); + return spansToTag.ToOneOrManyAndClear(); } - static ImmutableArray GetDocumentSnapshotSpansToTag( - ImmutableArray snapshotSpansToTag, + static OneOrMany GetDocumentSnapshotSpansToTag( + OneOrMany snapshotSpansToTag, bool frozenPartialSemantics, CancellationToken cancellationToken) { @@ -371,7 +371,7 @@ static ImmutableArray GetDocumentSnapshotSpansToTag( // document can be null if the buffer the given span is part of is not part of our workspace. using var snapshotToDocument = TemporaryArray<(ITextSnapshot snapshot, Document? document)>.Empty; - var result = new FixedSizeArrayBuilder(snapshotSpansToTag.Length); + using var result = TemporaryArray.Empty; foreach (var spanToTag in snapshotSpansToTag) { @@ -393,7 +393,7 @@ static ImmutableArray GetDocumentSnapshotSpansToTag( result.Add(new DocumentSnapshotSpan(document, spanToTag)); } - return result.MoveToImmutable(); + return result.ToOneOrManyAndClear(); } } @@ -524,7 +524,7 @@ private async ValueTask ProduceTagsAsync(TaggerContext context, Cancellati } private Dictionary ProcessNewTagTrees( - ImmutableArray spansToTag, + OneOrMany spansToTag, ImmutableDictionary> oldTagTrees, ImmutableDictionary> newTagTrees) { @@ -534,7 +534,7 @@ private Dictionary ProcessNewTagTrees( foreach (var (latestBuffer, latestSpans) in newTagTrees) { - var snapshot = spansToTag.First(s => s.SnapshotSpan.Snapshot.TextBuffer == latestBuffer).SnapshotSpan.Snapshot; + var snapshot = GetSpanForBuffer(spansToTag, latestBuffer).SnapshotSpan.Snapshot; if (oldTagTrees.TryGetValue(latestBuffer, out var previousSpans)) { @@ -559,6 +559,19 @@ private Dictionary ProcessNewTagTrees( return bufferToChanges; } + + static DocumentSnapshotSpan GetSpanForBuffer( + OneOrMany spansToTag, + ITextBuffer latestBuffer) + { + foreach (var span in spansToTag) + { + if (span.SnapshotSpan.Snapshot.TextBuffer == latestBuffer) + return span; + } + + throw ExceptionUtilities.Unreachable(); + } } /// diff --git a/src/EditorFeatures/Core/Tagging/TaggerContext.cs b/src/EditorFeatures/Core/Tagging/TaggerContext.cs index bba0730503ceb..3dbcc864fbd52 100644 --- a/src/EditorFeatures/Core/Tagging/TaggerContext.cs +++ b/src/EditorFeatures/Core/Tagging/TaggerContext.cs @@ -11,6 +11,7 @@ using Microsoft.CodeAnalysis.Text.Shared.Extensions; using Microsoft.VisualStudio.Text; using Microsoft.VisualStudio.Text.Tagging; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Editor.Tagging; @@ -18,7 +19,7 @@ internal sealed class TaggerContext where TTag : ITag { private readonly ImmutableDictionary> _existingTags; - internal ImmutableArray _spansTagged; + internal OneOrMany _spansTagged; public readonly SegmentedList> TagSpans = []; /// @@ -29,7 +30,7 @@ internal sealed class TaggerContext where TTag : ITag /// public bool FrozenPartialSemantics { get; } - public ImmutableArray SpansToTag { get; } + public OneOrMany SpansToTag { get; } public SnapshotPoint? CaretPosition { get; } /// @@ -51,7 +52,7 @@ internal TaggerContext( : this( state: null, frozenPartialSemantics, - [new DocumentSnapshotSpan(document, snapshot.GetFullSpan())], + OneOrMany.Create(new DocumentSnapshotSpan(document, snapshot.GetFullSpan())), caretPosition, existingTags: null) { @@ -60,7 +61,7 @@ [new DocumentSnapshotSpan(document, snapshot.GetFullSpan())], internal TaggerContext( object state, bool frozenPartialSemantics, - ImmutableArray spansToTag, + OneOrMany spansToTag, SnapshotPoint? caretPosition, ImmutableDictionary> existingTags) { @@ -69,7 +70,7 @@ internal TaggerContext( this.SpansToTag = spansToTag; this.CaretPosition = caretPosition; - _spansTagged = spansToTag.SelectAsArray(ds => ds.SnapshotSpan); + _spansTagged = spansToTag.Select(static ds => ds.SnapshotSpan); _existingTags = existingTags; } @@ -86,7 +87,7 @@ public void ClearTags() /// newly produced tags. /// public void SetSpansTagged(ImmutableArray spansTagged) - => _spansTagged = spansTagged; + => _spansTagged = OneOrMany.Create(spansTagged); public bool HasExistingContainingTags(SnapshotPoint point) => _existingTags != null && From a7e4d63020801c6be4813e9ffadb5693654c31cc Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 26 May 2024 16:14:03 -0700 Subject: [PATCH 05/13] Update src/EditorFeatures/Core/EditAndContinue/ActiveStatementTaggerProvider.cs --- .../Core/EditAndContinue/ActiveStatementTaggerProvider.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/EditorFeatures/Core/EditAndContinue/ActiveStatementTaggerProvider.cs b/src/EditorFeatures/Core/EditAndContinue/ActiveStatementTaggerProvider.cs index 48a63a2556ed4..38a7de7776998 100644 --- a/src/EditorFeatures/Core/EditAndContinue/ActiveStatementTaggerProvider.cs +++ b/src/EditorFeatures/Core/EditAndContinue/ActiveStatementTaggerProvider.cs @@ -55,7 +55,7 @@ protected override ITaggerEventSource CreateEventSource(ITextView? textView, ITe protected override async Task ProduceTagsAsync( TaggerContext context, CancellationToken cancellationToken) { - Debug.Assert(context.SpansToTag.Count == 1); + Contract.ThrowIfTrue(context.SpansToTag.Count != 1); var spanToTag = context.SpansToTag.First(); From c0542ac3642f5acecc12bc7c4bc5a7634eb7c11c Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 26 May 2024 16:19:08 -0700 Subject: [PATCH 06/13] Avoid allocations --- ...ynchronousTaggerProvider.TagSource_ProduceTags.cs | 12 +++++++++--- src/EditorFeatures/Core/Tagging/TaggerContext.cs | 3 +-- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource_ProduceTags.cs b/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource_ProduceTags.cs index f588889861325..6a0ec81b8cda1 100644 --- a/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource_ProduceTags.cs +++ b/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource_ProduceTags.cs @@ -322,7 +322,7 @@ await _visibilityTracker.DelayWhileNonVisibleAsync( oldState, frozenPartialSemantics, spansToTag, caretPosition, oldTagTrees); await ProduceTagsAsync(context, cancellationToken).ConfigureAwait(false); - return ComputeNewTagTrees(oldTagTrees, context); + return ComputeNewTagTrees(oldTagTrees, context, snapshotSpansToTag); }, cancellationToken).ConfigureAwait(continueOnCapturedContext: calledFromJtfRun); // We may get back null if we were canceled. Immediately bail out in that case. @@ -408,7 +408,10 @@ private static void CheckSnapshot(ITextSnapshot snapshot) } } - private ImmutableDictionary> ComputeNewTagTrees(ImmutableDictionary> oldTagTrees, TaggerContext context) + private ImmutableDictionary> ComputeNewTagTrees( + ImmutableDictionary> oldTagTrees, + TaggerContext context, + OneOrMany spansToTag) { using var _1 = PooledHashSet.GetInstance(out var buffersToTag); foreach (var spanToTag in context.SpansToTag) @@ -431,7 +434,10 @@ private ImmutableDictionary> ComputeNewTa newTagsInBuffer.Add(tagSpan); } - foreach (var span in context._spansTagged) + // Invalidate all the spans that were actually tagged. If the context doesn't have any recorded spans + // that were tagged, then assume we tagged everything we were asked to tag. + var spansTagged = context._spansTagged ?? spansToTag; + foreach (var span in spansTagged) { if (span.Snapshot.TextBuffer == buffer) spansToInvalidateInBuffer.Add(span); diff --git a/src/EditorFeatures/Core/Tagging/TaggerContext.cs b/src/EditorFeatures/Core/Tagging/TaggerContext.cs index 3dbcc864fbd52..03f2cbf8e665a 100644 --- a/src/EditorFeatures/Core/Tagging/TaggerContext.cs +++ b/src/EditorFeatures/Core/Tagging/TaggerContext.cs @@ -19,7 +19,7 @@ internal sealed class TaggerContext where TTag : ITag { private readonly ImmutableDictionary> _existingTags; - internal OneOrMany _spansTagged; + internal OneOrMany? _spansTagged; public readonly SegmentedList> TagSpans = []; /// @@ -70,7 +70,6 @@ internal TaggerContext( this.SpansToTag = spansToTag; this.CaretPosition = caretPosition; - _spansTagged = spansToTag.Select(static ds => ds.SnapshotSpan); _existingTags = existingTags; } From d5e2fdaf7acbd1e109e5cce2ed84ca5640e176f5 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 26 May 2024 16:46:05 -0700 Subject: [PATCH 07/13] Simplify --- ...AsynchronousTaggerProvider.TagSource_ProduceTags.cs | 10 ++++------ src/EditorFeatures/Core/Tagging/TaggerContext.cs | 5 ++++- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource_ProduceTags.cs b/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource_ProduceTags.cs index 6a0ec81b8cda1..9eb10b06e63cf 100644 --- a/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource_ProduceTags.cs +++ b/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource_ProduceTags.cs @@ -319,10 +319,10 @@ await _visibilityTracker.DelayWhileNonVisibleAsync( { // Create a context to store pass the information along and collect the results. context = new TaggerContext( - oldState, frozenPartialSemantics, spansToTag, caretPosition, oldTagTrees); + oldState, frozenPartialSemantics, spansToTag, snapshotSpansToTag, caretPosition, oldTagTrees); await ProduceTagsAsync(context, cancellationToken).ConfigureAwait(false); - return ComputeNewTagTrees(oldTagTrees, context, snapshotSpansToTag); + return ComputeNewTagTrees(oldTagTrees, context); }, cancellationToken).ConfigureAwait(continueOnCapturedContext: calledFromJtfRun); // We may get back null if we were canceled. Immediately bail out in that case. @@ -410,8 +410,7 @@ private static void CheckSnapshot(ITextSnapshot snapshot) private ImmutableDictionary> ComputeNewTagTrees( ImmutableDictionary> oldTagTrees, - TaggerContext context, - OneOrMany spansToTag) + TaggerContext context) { using var _1 = PooledHashSet.GetInstance(out var buffersToTag); foreach (var spanToTag in context.SpansToTag) @@ -436,8 +435,7 @@ private ImmutableDictionary> ComputeNewTa // Invalidate all the spans that were actually tagged. If the context doesn't have any recorded spans // that were tagged, then assume we tagged everything we were asked to tag. - var spansTagged = context._spansTagged ?? spansToTag; - foreach (var span in spansTagged) + foreach (var span in context._spansTagged) { if (span.Snapshot.TextBuffer == buffer) spansToInvalidateInBuffer.Add(span); diff --git a/src/EditorFeatures/Core/Tagging/TaggerContext.cs b/src/EditorFeatures/Core/Tagging/TaggerContext.cs index 03f2cbf8e665a..97d0af6163fe3 100644 --- a/src/EditorFeatures/Core/Tagging/TaggerContext.cs +++ b/src/EditorFeatures/Core/Tagging/TaggerContext.cs @@ -19,7 +19,7 @@ internal sealed class TaggerContext where TTag : ITag { private readonly ImmutableDictionary> _existingTags; - internal OneOrMany? _spansTagged; + internal OneOrMany _spansTagged; public readonly SegmentedList> TagSpans = []; /// @@ -53,6 +53,7 @@ internal TaggerContext( state: null, frozenPartialSemantics, OneOrMany.Create(new DocumentSnapshotSpan(document, snapshot.GetFullSpan())), + OneOrMany.Create(snapshot.GetFullSpan()), caretPosition, existingTags: null) { @@ -62,6 +63,7 @@ internal TaggerContext( object state, bool frozenPartialSemantics, OneOrMany spansToTag, + OneOrMany spansTagged, SnapshotPoint? caretPosition, ImmutableDictionary> existingTags) { @@ -70,6 +72,7 @@ internal TaggerContext( this.SpansToTag = spansToTag; this.CaretPosition = caretPosition; + _spansTagged = spansTagged; _existingTags = existingTags; } From c54f36dd6d2a8705e5c2bdc85611ccb818535f43 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 26 May 2024 16:47:44 -0700 Subject: [PATCH 08/13] Doc --- src/EditorFeatures/Core/Tagging/TaggerContext.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/EditorFeatures/Core/Tagging/TaggerContext.cs b/src/EditorFeatures/Core/Tagging/TaggerContext.cs index 97d0af6163fe3..612c29121c4af 100644 --- a/src/EditorFeatures/Core/Tagging/TaggerContext.cs +++ b/src/EditorFeatures/Core/Tagging/TaggerContext.cs @@ -19,6 +19,12 @@ internal sealed class TaggerContext where TTag : ITag { private readonly ImmutableDictionary> _existingTags; + /// + /// The spans we actually tagged. Initially set to the spans the tagger originally asked to tag. However, if the + /// tagger tags a smaller span than the one it was asked to tag, this will be updated to reflect the actual spans + /// tagged. For example, classification may initially say it wants to tag everything in the view, but then may + /// decide to only tag the containing method if it sees that all edits were contained within that method. + /// internal OneOrMany _spansTagged; public readonly SegmentedList> TagSpans = []; From 47ee3b9d57c46c3a4eac9190a5f776fc65828d07 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 26 May 2024 16:50:41 -0700 Subject: [PATCH 09/13] remove linq --- ...ousTaggerProvider.TagSource_ProduceTags.cs | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource_ProduceTags.cs b/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource_ProduceTags.cs index 9eb10b06e63cf..90eaed91b8296 100644 --- a/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource_ProduceTags.cs +++ b/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource_ProduceTags.cs @@ -509,20 +509,23 @@ private ImmutableDictionary> ComputeNewTa private async ValueTask ProduceTagsAsync(TaggerContext context, CancellationToken cancellationToken) { - // If the feature is disabled, then just produce no tags. - if (_dataSource.Options.OfType>().Any(option => !_dataSource.GlobalOptions.GetOption(option))) + // If we have no spans to tag, there's no point in continuing. + if (context.SpansToTag.IsEmpty) return; + // If the feature is disabled, then just produce no tags. var languageName = _subjectBuffer.GetLanguageName(); - if (_dataSource.Options.OfType>().Any( - option => languageName == null || !_dataSource.GlobalOptions.GetOption(option, languageName))) + foreach (var option in _dataSource.Options) { - return; - } + if (option is Option2 option2 && !_dataSource.GlobalOptions.GetOption(option2)) + return; - // If we have no spans to tag, there's no point in continuing. - if (context.SpansToTag.IsEmpty) - return; + if (option is PerLanguageOption2 perLanguageOption && + (languageName == null || !_dataSource.GlobalOptions.GetOption(perLanguageOption, languageName))) + { + return; + } + } await _dataSource.ProduceTagsAsync(context, cancellationToken).ConfigureAwait(false); } From 8455e8325b85597147dfc724a7d64884412d7f7f Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 26 May 2024 21:04:16 -0700 Subject: [PATCH 10/13] FirstOrDefault --- ...nousTaggerProvider.TagSource_ProduceTags.cs | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource_ProduceTags.cs b/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource_ProduceTags.cs index 90eaed91b8296..ded74d7aa8003 100644 --- a/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource_ProduceTags.cs +++ b/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource_ProduceTags.cs @@ -541,7 +541,10 @@ private Dictionary ProcessNewTagTrees( foreach (var (latestBuffer, latestSpans) in newTagTrees) { - var snapshot = GetSpanForBuffer(spansToTag, latestBuffer).SnapshotSpan.Snapshot; + var snapshot = spansToTag.FirstOrDefault( + static (span, latestBuffer) => span.SnapshotSpan.Snapshot.TextBuffer == latestBuffer, + latestBuffer).SnapshotSpan.Snapshot; + Contract.ThrowIfNull(snapshot); if (oldTagTrees.TryGetValue(latestBuffer, out var previousSpans)) { @@ -566,19 +569,6 @@ private Dictionary ProcessNewTagTrees( return bufferToChanges; } - - static DocumentSnapshotSpan GetSpanForBuffer( - OneOrMany spansToTag, - ITextBuffer latestBuffer) - { - foreach (var span in spansToTag) - { - if (span.SnapshotSpan.Snapshot.TextBuffer == latestBuffer) - return span; - } - - throw ExceptionUtilities.Unreachable(); - } } /// From e67b168e35bf1536dd5c0e9897c1f04120e25e69 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 28 May 2024 09:47:50 -0700 Subject: [PATCH 11/13] Simplify --- ...ousTaggerProvider.TagSource_ProduceTags.cs | 31 +++++++++++-------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource_ProduceTags.cs b/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource_ProduceTags.cs index ded74d7aa8003..0d360a7940aeb 100644 --- a/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource_ProduceTags.cs +++ b/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource_ProduceTags.cs @@ -295,6 +295,11 @@ await _visibilityTracker.DelayWhileNonVisibleAsync( var snapshotSpansToTag = GetSnapshotSpansToTag(); var caretPosition = _dataSource.GetCaretPoint(_textView, _subjectBuffer); +#if DEBUG + foreach (var snapshotSpan in snapshotSpansToTag) + CheckSnapshot(snapshotSpan.Snapshot1); +#endif + // If we're being called from within a blocking JTF.Run call, we don't want to switch to the background // if we can avoid it. if (!calledFromJtfRun) @@ -376,13 +381,13 @@ static OneOrMany GetDocumentSnapshotSpansToTag( foreach (var spanToTag in snapshotSpansToTag) { var snapshot = spanToTag.Snapshot; - var document = snapshotToDocument.FirstOrDefault( - static (t, snapshot) => t.snapshot == snapshot, snapshot).document; + var (foundSnapshot, document) = snapshotToDocument.FirstOrDefault( + static (t, snapshot) => t.snapshot == snapshot, snapshot); - if (document is null) + // If this is the first time looking at this snapshot, then go fetch the document (which we may or + // may not have), and freeze it if necessary.. + if (foundSnapshot is null) { - CheckSnapshot(snapshot); - document = snapshot.GetOpenDocumentInCurrentContextWithChanges(); if (frozenPartialSemantics) document = document?.WithFrozenPartialSemantics(cancellationToken); @@ -395,16 +400,16 @@ static OneOrMany GetDocumentSnapshotSpansToTag( return result.ToOneOrManyAndClear(); } - } - [Conditional("DEBUG")] - private static void CheckSnapshot(ITextSnapshot snapshot) - { - var container = snapshot.TextBuffer.AsTextContainer(); - if (Workspace.TryGetWorkspace(container, out _)) + [Conditional("DEBUG")] + static void CheckSnapshot(ITextSnapshot snapshot) { - // if the buffer is part of our workspace, it must be the latest. - Debug.Assert(snapshot.Version.Next == null, "should be on latest snapshot"); + var container = snapshot.TextBuffer.AsTextContainer(); + if (Workspace.TryGetWorkspace(container, out _)) + { + // if the buffer is part of our workspace, it must be the latest. + Debug.Assert(snapshot.Version.Next == null, "should be on latest snapshot"); + } } } From f1815547eafe52d8982a20f237610461d88601bd Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 28 May 2024 10:35:38 -0700 Subject: [PATCH 12/13] Fix --- .../AbstractAsynchronousTaggerProvider.TagSource_ProduceTags.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource_ProduceTags.cs b/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource_ProduceTags.cs index 0d360a7940aeb..b7c554d1d9bf8 100644 --- a/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource_ProduceTags.cs +++ b/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource_ProduceTags.cs @@ -297,7 +297,7 @@ await _visibilityTracker.DelayWhileNonVisibleAsync( #if DEBUG foreach (var snapshotSpan in snapshotSpansToTag) - CheckSnapshot(snapshotSpan.Snapshot1); + CheckSnapshot(snapshotSpan.Snapshot); #endif // If we're being called from within a blocking JTF.Run call, we don't want to switch to the background From 260e44c909e6d741a60a8bcd4076d950b33a0534 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 28 May 2024 11:27:45 -0700 Subject: [PATCH 13/13] fix --- ...AbstractAsynchronousTaggerProvider.TagSource_ProduceTags.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource_ProduceTags.cs b/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource_ProduceTags.cs index b7c554d1d9bf8..95a173163626d 100644 --- a/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource_ProduceTags.cs +++ b/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource_ProduceTags.cs @@ -401,7 +401,7 @@ static OneOrMany GetDocumentSnapshotSpansToTag( return result.ToOneOrManyAndClear(); } - [Conditional("DEBUG")] +#if DEBUG static void CheckSnapshot(ITextSnapshot snapshot) { var container = snapshot.TextBuffer.AsTextContainer(); @@ -411,6 +411,7 @@ static void CheckSnapshot(ITextSnapshot snapshot) Debug.Assert(snapshot.Version.Next == null, "should be on latest snapshot"); } } +#endif } private ImmutableDictionary> ComputeNewTagTrees(