From 0f2abb601d0991198b4b3bb8d6e16375ee1eac61 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 19 Dec 2024 12:29:20 -0800 Subject: [PATCH 01/12] Simplify inlint hint tagging --- .../Core.Wpf/InlineHints/InlineHintsTagger.cs | 221 ++++++++++-------- .../InlineHints/InlineHintsTaggerProvider.cs | 29 ++- .../InlineHintsDataTaggerProvider.cs | 14 +- ...actAsynchronousTaggerProvider.TagSource.cs | 1 - .../Core/Tagging/EfficientTagger.cs | 2 +- 5 files changed, 151 insertions(+), 116 deletions(-) diff --git a/src/EditorFeatures/Core.Wpf/InlineHints/InlineHintsTagger.cs b/src/EditorFeatures/Core.Wpf/InlineHints/InlineHintsTagger.cs index ed2bb08bd6060..b4284a8911709 100644 --- a/src/EditorFeatures/Core.Wpf/InlineHints/InlineHintsTagger.cs +++ b/src/EditorFeatures/Core.Wpf/InlineHints/InlineHintsTagger.cs @@ -3,10 +3,14 @@ // See the LICENSE file in the project root for more information. using System; -using System.Collections.Generic; +using System.Collections.Immutable; +using Microsoft.CodeAnalysis.Collections; using Microsoft.CodeAnalysis.Editor.Shared.Extensions; +using Microsoft.CodeAnalysis.Editor.Tagging; using Microsoft.CodeAnalysis.ErrorReporting; +using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Text; +using Microsoft.CodeAnalysis.Utilities; using Microsoft.VisualStudio.Text; using Microsoft.VisualStudio.Text.Classification; using Microsoft.VisualStudio.Text.Editor; @@ -16,24 +20,29 @@ namespace Microsoft.CodeAnalysis.Editor.InlineHints { - /// - /// The purpose of this tagger is to convert the to the , which actually creates the UIElement. It reacts to tags changing and updates the - /// adornments accordingly. - /// - internal sealed class InlineHintsTagger : ITagger, IDisposable - { - private readonly ITagAggregator _tagAggregator; + using InlineHintTagCache = ImmutableDictionary; + internal class InlineHintTags(TagSpan dataTagSpan) + { /// - /// stores the parameter hint tags in a global location + /// Provided at creation time. Never changes. /// - private readonly List<(IMappingTagSpan mappingTagSpan, TagSpan? tagSpan)> _cache = []; + public readonly TagSpan DataTagSpan = dataTagSpan; /// - /// Stores the snapshot associated with the cached tags in + /// Created on demand when the adornment is needed. /// - private ITextSnapshot? _cacheSnapshot; + public TagSpan? AdornmentTagSpan; + } + + /// + /// The purpose of this tagger is to convert the to the , which actually creates the UIElement. It reacts to tags changing and updates the + /// adornments accordingly. + /// + internal sealed class InlineHintsTagger : EfficientTagger + { + private readonly EfficientTagger _underlyingTagger; private readonly IClassificationFormatMap _formatMap; @@ -45,57 +54,49 @@ internal sealed class InlineHintsTagger : ITagger, IDispo private readonly InlineHintsTaggerProvider _taggerProvider; - private readonly ITextBuffer _buffer; private readonly IWpfTextView _textView; - public event EventHandler? TagsChanged; + private readonly object _gate = new(); + /// + /// Stores the snapshot associated with the cached tags in . + /// Locked by . + /// + private ITextSnapshot? _cacheSnapshot_doNotAccessOutsideOfGate; + + /// + /// Mapping from position to the data tag computed for it, and the adornment tag (once we've computed that). + /// Locked by . + /// + private InlineHintTagCache _cache_doNotAccessOutsideOfGate = InlineHintTagCache.Empty; public InlineHintsTagger( InlineHintsTaggerProvider taggerProvider, IWpfTextView textView, - ITextBuffer buffer, - ITagAggregator tagAggregator) + EfficientTagger tagger) { _taggerProvider = taggerProvider; _textView = textView; - _buffer = buffer; - _tagAggregator = tagAggregator; + _underlyingTagger = tagger; + _underlyingTagger.TagsChanged += OnUnderlyingTagger_TagsChanged; + _formatMap = taggerProvider.ClassificationFormatMapService.GetClassificationFormatMap(textView); _hintClassification = taggerProvider.ClassificationTypeRegistryService.GetClassificationType(InlineHintsTag.TagId); _formatMap.ClassificationFormatMappingChanged += this.OnClassificationFormatMappingChanged; - _tagAggregator.BatchedTagsChanged += TagAggregator_BatchedTagsChanged; } - /// - /// Goes through all the spans in which tags have changed and - /// invokes a TagsChanged event. Using the BatchedTagsChangedEvent since it is raised - /// on the same thread that created the tag aggregator, unlike TagsChanged. - /// - private void TagAggregator_BatchedTagsChanged(object sender, BatchedTagsChangedEventArgs e) + public override void Dispose() { - _taggerProvider.ThreadingContext.ThrowIfNotOnUIThread(); - InvalidateCache(); - - var tagsChanged = TagsChanged; - if (tagsChanged is null) - { - return; - } + _underlyingTagger.TagsChanged -= OnUnderlyingTagger_TagsChanged; + _underlyingTagger.Dispose(); + _formatMap.ClassificationFormatMappingChanged -= OnClassificationFormatMappingChanged; + } - var mappingSpans = e.Spans; - foreach (var item in mappingSpans) - { - var spans = item.GetSpans(_buffer); - foreach (var span in spans) - { - if (tagsChanged != null) - { - tagsChanged.Invoke(this, new SnapshotSpanEventArgs(span)); - } - } - } + private void OnUnderlyingTagger_TagsChanged(object sender, SnapshotSpanEventArgs e) + { + InvalidateCache(); + OnTagsChanged(this, e); } private void OnClassificationFormatMappingChanged(object sender, EventArgs e) @@ -110,9 +111,7 @@ private void OnClassificationFormatMappingChanged(object sender, EventArgs e) var tags = GetTags(new NormalizedSnapshotSpanCollection(_textView.TextViewLines.FormattedSpan)); foreach (var tag in tags) - { - TagsChanged?.Invoke(this, new SnapshotSpanEventArgs(tag.Span)); - } + OnTagsChanged(this, new SnapshotSpanEventArgs(tag.Span)); } } @@ -128,72 +127,90 @@ private TextFormattingRunProperties Format private void InvalidateCache() { - _taggerProvider.ThreadingContext.ThrowIfNotOnUIThread(); - _cacheSnapshot = null; - _cache.Clear(); + lock (_gate) + { + _cacheSnapshot_doNotAccessOutsideOfGate = null; + _cache_doNotAccessOutsideOfGate = InlineHintTagCache.Empty; + } } - IEnumerable> ITagger.GetTags(NormalizedSnapshotSpanCollection spans) - => GetTags(spans); - - public IReadOnlyList> GetTags(NormalizedSnapshotSpanCollection spans) + public override void AddTags( + NormalizedSnapshotSpanCollection spans, + SegmentedList> adornmentTagSpans) { try { if (spans.Count == 0) - return []; + return; + + ITextSnapshot? cacheSnapshot; + InlineHintTagCache cache; + lock (_gate) + { + cacheSnapshot = _cacheSnapshot_doNotAccessOutsideOfGate; + cache = _cache_doNotAccessOutsideOfGate; + } + + // If the snapshot has changed, we can't use any of the cached data, as it is associated with the + // original snapshot they were created against. var snapshot = spans[0].Snapshot; - if (snapshot != _cacheSnapshot) + + var cacheChanged = false; + var cacheBuilder = cache.ToBuilder(); + + if (snapshot != cacheSnapshot) { - // Calculate UI elements - _cache.Clear(); - _cacheSnapshot = snapshot; - - // Calling into the InlineParameterNameHintsDataTaggerProvider which only responds with the current - // active view and disregards and requests for tags not in that view - var fullSpan = new SnapshotSpan(snapshot, 0, snapshot.Length); - var tags = _tagAggregator.GetTags(new NormalizedSnapshotSpanCollection(fullSpan)); - foreach (var tag in tags) - { - // Gets the associated span from the snapshot span and creates the IntraTextAdornmentTag from the data - // tags. Only dealing with the dataTagSpans if the count is 1 because we do not see a multi-buffer case - // occurring - var dataTagSpans = tag.Span.GetSpans(snapshot); - if (dataTagSpans.Count == 1) - { - _cache.Add((tag, tagSpan: null)); - } - } + cacheBuilder.Clear(); + cacheChanged = true; } var document = snapshot.GetOpenDocumentInCurrentContextWithChanges(); var classify = document != null && _taggerProvider.EditorOptionsService.GlobalOptions.GetOption(InlineHintsViewOptionsStorage.ColorHints, document.Project.Language); - var selectedSpans = new List>(); - for (var i = 0; i < _cache.Count; i++) + using var _1 = SegmentedListPool.GetPooledList>(out var dataTagSpans); + _underlyingTagger.AddTags(spans, dataTagSpans); + + // Presize so we can add the elements below without continually resizing. + adornmentTagSpans.Capacity += dataTagSpans.Count; + + using var _2 = PooledHashSet.GetInstance(out var seenPositions); + + foreach (var dataTagSpan in dataTagSpans) { - var tagSpans = _cache[i].mappingTagSpan.Span.GetSpans(snapshot); - if (tagSpans.Count == 1) + // Check if we already have a tag at this position. If not, initialize the cache to just point at + // the new data tag. + var position = dataTagSpan.Span.Start; + if (!cache.TryGetValue(position, out var inlineHintTags)) + { + inlineHintTags = new(dataTagSpan); + cacheBuilder[position] = inlineHintTags; + cacheChanged = true; + } + + if (seenPositions.Add(position)) { - var tagSpan = tagSpans[0]; - if (spans.IntersectsWith(tagSpan)) - { - if (_cache[i].tagSpan is not { } hintTagSpan) - { - var hintUITag = InlineHintsTag.Create( - _cache[i].mappingTagSpan.Tag.Hint, Format, _textView, tagSpan, _taggerProvider, _formatMap, classify); - - hintTagSpan = new TagSpan(tagSpan, hintUITag); - _cache[i] = (_cache[i].mappingTagSpan, hintTagSpan); - } - - selectedSpans.Add(hintTagSpan); - } + // Now check if this is the first time we've been asked to compute the adornment for this particular + // data tag. If so, create and cache it so we don't recreate the adornments in the future for the + // same text snapshot. + // + // Note: creating the adornment doesn't change the cache itself. It just updates one of the values + // the cache is already pointing to. We only need to change the cache if we've added a new + // key/value mapping to it. + inlineHintTags.AdornmentTagSpan ??= CreateAdornmentTagSpan(inlineHintTags.DataTagSpan, classify); + adornmentTagSpans.Add(inlineHintTags.AdornmentTagSpan); } } - return selectedSpans; + if (cacheChanged) + { + cache = cacheBuilder.ToImmutable(); + lock (_gate) + { + _cacheSnapshot_doNotAccessOutsideOfGate = snapshot; + _cache_doNotAccessOutsideOfGate = cache; + } + } } catch (Exception e) when (FatalError.ReportAndPropagateUnlessCanceled(e, ErrorSeverity.General)) { @@ -201,11 +218,15 @@ public IReadOnlyList> GetTags(NormalizedSnapshotS } } - public void Dispose() + private TagSpan CreateAdornmentTagSpan( + TagSpan dataTagSpan, bool classify) { - _tagAggregator.BatchedTagsChanged -= TagAggregator_BatchedTagsChanged; - _tagAggregator.Dispose(); - _formatMap.ClassificationFormatMappingChanged -= OnClassificationFormatMappingChanged; + var adornmentSpan = dataTagSpan.Span; + + var hintUITag = InlineHintsTag.Create( + dataTagSpan.Tag.Hint, Format, _textView, adornmentSpan, _taggerProvider, _formatMap, classify); + + return new TagSpan(adornmentSpan, hintUITag); } } } diff --git a/src/EditorFeatures/Core.Wpf/InlineHints/InlineHintsTaggerProvider.cs b/src/EditorFeatures/Core.Wpf/InlineHints/InlineHintsTaggerProvider.cs index db59432deb50e..fd965a0194ddb 100644 --- a/src/EditorFeatures/Core.Wpf/InlineHints/InlineHintsTaggerProvider.cs +++ b/src/EditorFeatures/Core.Wpf/InlineHints/InlineHintsTaggerProvider.cs @@ -7,6 +7,7 @@ using Microsoft.CodeAnalysis.Editor.Host; using Microsoft.CodeAnalysis.Editor.Shared.Extensions; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; +using Microsoft.CodeAnalysis.Editor.Tagging; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.Shared.TestHooks; @@ -27,9 +28,9 @@ namespace Microsoft.CodeAnalysis.Editor.InlineHints [ContentType(ContentTypeNames.RoslynContentType)] [TagType(typeof(IntraTextAdornmentTag))] [Name(nameof(InlineHintsTaggerProvider))] - internal class InlineHintsTaggerProvider : IViewTaggerProvider + internal sealed class InlineHintsTaggerProvider : IViewTaggerProvider { - private readonly IViewTagAggregatorFactoryService _viewTagAggregatorFactoryService; + // private readonly IViewTagAggregatorFactoryService _viewTagAggregatorFactoryService; public readonly IClassificationFormatMapService ClassificationFormatMapService; public readonly IClassificationTypeRegistryService ClassificationTypeRegistryService; public readonly IThreadingContext ThreadingContext; @@ -40,10 +41,12 @@ internal class InlineHintsTaggerProvider : IViewTaggerProvider public readonly Lazy StreamingFindUsagesPresenter; public readonly EditorOptionsService EditorOptionsService; + private readonly InlineHintsDataTaggerProvider _dataTaggerProvider; + [ImportingConstructor] [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] public InlineHintsTaggerProvider( - IViewTagAggregatorFactoryService viewTagAggregatorFactoryService, + // IViewTagAggregatorFactoryService viewTagAggregatorFactoryService, IClassificationFormatMapService classificationFormatMapService, IClassificationTypeRegistryService classificationTypeRegistryService, IThreadingContext threadingContext, @@ -52,9 +55,11 @@ public InlineHintsTaggerProvider( IToolTipService toolTipService, ClassificationTypeMap typeMap, Lazy streamingFindUsagesPresenter, - EditorOptionsService editorOptionsService) + EditorOptionsService editorOptionsService, + TaggerHost taggerHost, + [Import(AllowDefault = true)] IInlineHintKeyProcessor inlineHintKeyProcessor) { - _viewTagAggregatorFactoryService = viewTagAggregatorFactoryService; + // _viewTagAggregatorFactoryService = viewTagAggregatorFactoryService; ClassificationFormatMapService = classificationFormatMapService; ClassificationTypeRegistryService = classificationTypeRegistryService; ThreadingContext = threadingContext; @@ -65,17 +70,27 @@ public InlineHintsTaggerProvider( EditorOptionsService = editorOptionsService; AsynchronousOperationListener = listenerProvider.GetListener(FeatureAttribute.InlineHints); + + _dataTaggerProvider = new InlineHintsDataTaggerProvider(taggerHost, inlineHintKeyProcessor); } public ITagger? CreateTagger(ITextView textView, ITextBuffer buffer) where T : ITag { if (textView.IsNotSurfaceBufferOfTextView(buffer)) + return null; + + if (textView is not IWpfTextView wpfTextView) + return null; + + var tagger = new InlineHintsTagger( + this, wpfTextView, _dataTaggerProvider.CreateTagger(textView, buffer)); + if (tagger is not ITagger typedTagger) { + tagger.Dispose(); return null; } - var tagAggregator = _viewTagAggregatorFactoryService.CreateTagAggregator(textView); - return new InlineHintsTagger(this, (IWpfTextView)textView, buffer, tagAggregator) as ITagger; + return typedTagger; } } } diff --git a/src/EditorFeatures/Core/InlineHints/InlineHintsDataTaggerProvider.cs b/src/EditorFeatures/Core/InlineHints/InlineHintsDataTaggerProvider.cs index 43cdbfe4190bf..3148b38a8285f 100644 --- a/src/EditorFeatures/Core/InlineHints/InlineHintsDataTaggerProvider.cs +++ b/src/EditorFeatures/Core/InlineHints/InlineHintsDataTaggerProvider.cs @@ -25,15 +25,15 @@ namespace Microsoft.CodeAnalysis.Editor.InlineHints; /// /// The TaggerProvider that calls upon the service in order to locate the spans and names /// -[Export(typeof(IViewTaggerProvider))] -[VSUtilities.ContentType(ContentTypeNames.RoslynContentType)] -[TagType(typeof(InlineHintDataTag))] -[VSUtilities.Name(nameof(InlineHintsDataTaggerProvider))] -[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] -[method: ImportingConstructor] +//[Export(typeof(IViewTaggerProvider))] +//[VSUtilities.ContentType(ContentTypeNames.RoslynContentType)] +//[TagType(typeof(InlineHintDataTag))] +//[VSUtilities.Name(nameof(InlineHintsDataTaggerProvider))] +//[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +//[method: ImportingConstructor] internal sealed partial class InlineHintsDataTaggerProvider( TaggerHost taggerHost, - [Import(AllowDefault = true)] IInlineHintKeyProcessor inlineHintKeyProcessor) + IInlineHintKeyProcessor inlineHintKeyProcessor) : AsynchronousViewportTaggerProvider(taggerHost, FeatureAttribute.InlineHints) { private readonly IInlineHintKeyProcessor _inlineHintKeyProcessor = inlineHintKeyProcessor; diff --git a/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource.cs b/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource.cs index d1b98745ebe9e..18d853a4388a0 100644 --- a/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource.cs +++ b/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource.cs @@ -7,7 +7,6 @@ using System.Collections.Immutable; using System.Linq; using System.Threading; -using Microsoft.CodeAnalysis.Collections; using Microsoft.CodeAnalysis.Editor.Shared.Extensions; using Microsoft.CodeAnalysis.Editor.Shared.Tagging; using Microsoft.CodeAnalysis.Options; diff --git a/src/EditorFeatures/Core/Tagging/EfficientTagger.cs b/src/EditorFeatures/Core/Tagging/EfficientTagger.cs index af187baaba78b..90cfe9a52606a 100644 --- a/src/EditorFeatures/Core/Tagging/EfficientTagger.cs +++ b/src/EditorFeatures/Core/Tagging/EfficientTagger.cs @@ -34,7 +34,7 @@ IEnumerable> ITagger.GetTags(NormalizedSnapshotSpanCollecti /// public IEnumerable> GetTags(NormalizedSnapshotSpanCollection spans) { - using var pooledObject = SegmentedListPool.GetPooledList>(out var list); + using var _ = SegmentedListPool.GetPooledList>(out var list); AddTags(spans, list); From 632cfc42ed4cdb91ef057177bb8574f1046c4153 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 19 Dec 2024 12:40:04 -0800 Subject: [PATCH 02/12] In progress --- .../Core.Wpf/InlineHints/InlineHintsTagger.cs | 21 ++++++------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/src/EditorFeatures/Core.Wpf/InlineHints/InlineHintsTagger.cs b/src/EditorFeatures/Core.Wpf/InlineHints/InlineHintsTagger.cs index b4284a8911709..3b3f354078291 100644 --- a/src/EditorFeatures/Core.Wpf/InlineHints/InlineHintsTagger.cs +++ b/src/EditorFeatures/Core.Wpf/InlineHints/InlineHintsTagger.cs @@ -152,18 +152,13 @@ public override void AddTags( cache = _cache_doNotAccessOutsideOfGate; } + var cacheBuilder = cache.ToBuilder(); + // If the snapshot has changed, we can't use any of the cached data, as it is associated with the // original snapshot they were created against. var snapshot = spans[0].Snapshot; - - var cacheChanged = false; - var cacheBuilder = cache.ToBuilder(); - if (snapshot != cacheSnapshot) - { cacheBuilder.Clear(); - cacheChanged = true; - } var document = snapshot.GetOpenDocumentInCurrentContextWithChanges(); var classify = document != null && _taggerProvider.EditorOptionsService.GlobalOptions.GetOption(InlineHintsViewOptionsStorage.ColorHints, document.Project.Language); @@ -185,7 +180,6 @@ public override void AddTags( { inlineHintTags = new(dataTagSpan); cacheBuilder[position] = inlineHintTags; - cacheChanged = true; } if (seenPositions.Add(position)) @@ -202,14 +196,11 @@ public override void AddTags( } } - if (cacheChanged) + cache = cacheBuilder.ToImmutable(); + lock (_gate) { - cache = cacheBuilder.ToImmutable(); - lock (_gate) - { - _cacheSnapshot_doNotAccessOutsideOfGate = snapshot; - _cache_doNotAccessOutsideOfGate = cache; - } + _cacheSnapshot_doNotAccessOutsideOfGate = snapshot; + _cache_doNotAccessOutsideOfGate = cache; } } catch (Exception e) when (FatalError.ReportAndPropagateUnlessCanceled(e, ErrorSeverity.General)) From 5e2c1139fa6ed77d8454d14e53a0852d61d83e0b Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 19 Dec 2024 13:00:12 -0800 Subject: [PATCH 03/12] Clean up --- .../Core.Wpf/InlineHints/InlineHintsTagger.cs | 151 ++++++------------ .../InlineHints/InlineHintsTaggerProvider.cs | 75 ++++----- .../InlineHintsViewOptionsStorage.cs | 15 +- .../InlineHintsDataTaggerProvider.cs | 10 -- 4 files changed, 89 insertions(+), 162 deletions(-) diff --git a/src/EditorFeatures/Core.Wpf/InlineHints/InlineHintsTagger.cs b/src/EditorFeatures/Core.Wpf/InlineHints/InlineHintsTagger.cs index 3b3f354078291..f3b20afb46af9 100644 --- a/src/EditorFeatures/Core.Wpf/InlineHints/InlineHintsTagger.cs +++ b/src/EditorFeatures/Core.Wpf/InlineHints/InlineHintsTagger.cs @@ -4,12 +4,17 @@ using System; using System.Collections.Immutable; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices.ComTypes; using Microsoft.CodeAnalysis.Collections; using Microsoft.CodeAnalysis.Editor.Shared.Extensions; using Microsoft.CodeAnalysis.Editor.Tagging; using Microsoft.CodeAnalysis.ErrorReporting; +using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Text; +using Microsoft.CodeAnalysis.Text.Shared.Extensions; using Microsoft.CodeAnalysis.Utilities; using Microsoft.VisualStudio.Text; using Microsoft.VisualStudio.Text.Classification; @@ -20,21 +25,6 @@ namespace Microsoft.CodeAnalysis.Editor.InlineHints { - using InlineHintTagCache = ImmutableDictionary; - - internal class InlineHintTags(TagSpan dataTagSpan) - { - /// - /// Provided at creation time. Never changes. - /// - public readonly TagSpan DataTagSpan = dataTagSpan; - - /// - /// Created on demand when the adornment is needed. - /// - public TagSpan? AdornmentTagSpan; - } - /// /// The purpose of this tagger is to convert the to the , which actually creates the UIElement. It reacts to tags changing and updates the @@ -42,6 +32,8 @@ internal class InlineHintTags(TagSpan dataTagSpan) /// internal sealed class InlineHintsTagger : EfficientTagger { + private static ConditionalWeakTable, TagSpan> s_dataTagToAdornmentTag = new(); + private readonly EfficientTagger _underlyingTagger; private readonly IClassificationFormatMap _formatMap; @@ -55,63 +47,61 @@ internal sealed class InlineHintsTagger : EfficientTagger private readonly InlineHintsTaggerProvider _taggerProvider; private readonly IWpfTextView _textView; - - private readonly object _gate = new(); - /// - /// Stores the snapshot associated with the cached tags in . - /// Locked by . - /// - private ITextSnapshot? _cacheSnapshot_doNotAccessOutsideOfGate; - - /// - /// Mapping from position to the data tag computed for it, and the adornment tag (once we've computed that). - /// Locked by . - /// - private InlineHintTagCache _cache_doNotAccessOutsideOfGate = InlineHintTagCache.Empty; + private readonly ITextBuffer _subjectBuffer; public InlineHintsTagger( InlineHintsTaggerProvider taggerProvider, IWpfTextView textView, + ITextBuffer subjectBuffer, EfficientTagger tagger) { _taggerProvider = taggerProvider; _textView = textView; + _subjectBuffer = subjectBuffer; + // When the underlying tagger produced new data tags, inform any clients of us that we have new adornment tags. _underlyingTagger = tagger; - _underlyingTagger.TagsChanged += OnUnderlyingTagger_TagsChanged; + _underlyingTagger.TagsChanged += OnTagsChanged; _formatMap = taggerProvider.ClassificationFormatMapService.GetClassificationFormatMap(textView); _hintClassification = taggerProvider.ClassificationTypeRegistryService.GetClassificationType(InlineHintsTag.TagId); + _formatMap.ClassificationFormatMappingChanged += this.OnClassificationFormatMappingChanged; + _taggerProvider.GlobalOptionService.AddOptionChangedHandler(this, OnGlobalOptionChanged); } public override void Dispose() { - _underlyingTagger.TagsChanged -= OnUnderlyingTagger_TagsChanged; - _underlyingTagger.Dispose(); _formatMap.ClassificationFormatMappingChanged -= OnClassificationFormatMappingChanged; - } - - private void OnUnderlyingTagger_TagsChanged(object sender, SnapshotSpanEventArgs e) - { - InvalidateCache(); - OnTagsChanged(this, e); + _taggerProvider.GlobalOptionService.RemoveOptionChangedHandler(this, OnGlobalOptionChanged); + _underlyingTagger.TagsChanged -= OnTagsChanged; + _underlyingTagger.Dispose(); } private void OnClassificationFormatMappingChanged(object sender, EventArgs e) { _taggerProvider.ThreadingContext.ThrowIfNotOnUIThread(); + + // When classifications change we need to rebuild the inline tags with updated Font and Color information. + + // Clear out the cached adornment tags we have associated with each data tag. + s_dataTagToAdornmentTag = new(); + if (_format != null) { _format = null; - InvalidateCache(); - - // When classifications change we need to rebuild the inline tags with updated Font and Color information. - var tags = GetTags(new NormalizedSnapshotSpanCollection(_textView.TextViewLines.FormattedSpan)); + OnTagsChanged(this, new SnapshotSpanEventArgs(_subjectBuffer.CurrentSnapshot.GetFullSpan())); + } + } - foreach (var tag in tags) - OnTagsChanged(this, new SnapshotSpanEventArgs(tag.Span)); + private void OnGlobalOptionChanged(object sender, object target, OptionChangedEventArgs e) + { + if (e.HasOption(option => option.Equals(InlineHintsViewOptionsStorage.ColorHints))) + { + // Clear out cached adornments and reclassify everything. + s_dataTagToAdornmentTag = new(); + OnTagsChanged(this, new SnapshotSpanEventArgs(_subjectBuffer.CurrentSnapshot.GetFullSpan())); } } @@ -125,15 +115,6 @@ private TextFormattingRunProperties Format } } - private void InvalidateCache() - { - lock (_gate) - { - _cacheSnapshot_doNotAccessOutsideOfGate = null; - _cache_doNotAccessOutsideOfGate = InlineHintTagCache.Empty; - } - } - public override void AddTags( NormalizedSnapshotSpanCollection spans, SegmentedList> adornmentTagSpans) @@ -143,22 +124,9 @@ public override void AddTags( if (spans.Count == 0) return; - ITextSnapshot? cacheSnapshot; - InlineHintTagCache cache; - - lock (_gate) - { - cacheSnapshot = _cacheSnapshot_doNotAccessOutsideOfGate; - cache = _cache_doNotAccessOutsideOfGate; - } - - var cacheBuilder = cache.ToBuilder(); - // If the snapshot has changed, we can't use any of the cached data, as it is associated with the // original snapshot they were created against. var snapshot = spans[0].Snapshot; - if (snapshot != cacheSnapshot) - cacheBuilder.Clear(); var document = snapshot.GetOpenDocumentInCurrentContextWithChanges(); var classify = document != null && _taggerProvider.EditorOptionsService.GlobalOptions.GetOption(InlineHintsViewOptionsStorage.ColorHints, document.Project.Language); @@ -173,34 +141,8 @@ public override void AddTags( foreach (var dataTagSpan in dataTagSpans) { - // Check if we already have a tag at this position. If not, initialize the cache to just point at - // the new data tag. - var position = dataTagSpan.Span.Start; - if (!cache.TryGetValue(position, out var inlineHintTags)) - { - inlineHintTags = new(dataTagSpan); - cacheBuilder[position] = inlineHintTags; - } - - if (seenPositions.Add(position)) - { - // Now check if this is the first time we've been asked to compute the adornment for this particular - // data tag. If so, create and cache it so we don't recreate the adornments in the future for the - // same text snapshot. - // - // Note: creating the adornment doesn't change the cache itself. It just updates one of the values - // the cache is already pointing to. We only need to change the cache if we've added a new - // key/value mapping to it. - inlineHintTags.AdornmentTagSpan ??= CreateAdornmentTagSpan(inlineHintTags.DataTagSpan, classify); - adornmentTagSpans.Add(inlineHintTags.AdornmentTagSpan); - } - } - - cache = cacheBuilder.ToImmutable(); - lock (_gate) - { - _cacheSnapshot_doNotAccessOutsideOfGate = snapshot; - _cache_doNotAccessOutsideOfGate = cache; + if (seenPositions.Add(dataTagSpan.Span.Start)) + adornmentTagSpans.Add(GetAdornmentTagsSpan(dataTagSpan, classify)); } } catch (Exception e) when (FatalError.ReportAndPropagateUnlessCanceled(e, ErrorSeverity.General)) @@ -209,15 +151,28 @@ public override void AddTags( } } - private TagSpan CreateAdornmentTagSpan( + private TagSpan GetAdornmentTagsSpan( TagSpan dataTagSpan, bool classify) { - var adornmentSpan = dataTagSpan.Span; + if (s_dataTagToAdornmentTag.TryGetValue(dataTagSpan, out var adornmentTagSpan)) + return adornmentTagSpan; + + // Extracted as a helper method to avoid closure allocations when we find the adornment span in the map. + return GetOrCreateAdornmentTagSpan(dataTagSpan, classify); + } + + private TagSpan GetOrCreateAdornmentTagSpan( + TagSpan dataTagSpan, bool classify) + { + return s_dataTagToAdornmentTag.GetValue(dataTagSpan, dataTagSpan => + { + var adornmentSpan = dataTagSpan.Span; - var hintUITag = InlineHintsTag.Create( - dataTagSpan.Tag.Hint, Format, _textView, adornmentSpan, _taggerProvider, _formatMap, classify); + var hintUITag = InlineHintsTag.Create( + dataTagSpan.Tag.Hint, Format, _textView, adornmentSpan, _taggerProvider, _formatMap, classify); - return new TagSpan(adornmentSpan, hintUITag); + return new TagSpan(adornmentSpan, hintUITag); + }); } } } diff --git a/src/EditorFeatures/Core.Wpf/InlineHints/InlineHintsTaggerProvider.cs b/src/EditorFeatures/Core.Wpf/InlineHints/InlineHintsTaggerProvider.cs index fd965a0194ddb..1b2d81ce219d2 100644 --- a/src/EditorFeatures/Core.Wpf/InlineHints/InlineHintsTaggerProvider.cs +++ b/src/EditorFeatures/Core.Wpf/InlineHints/InlineHintsTaggerProvider.cs @@ -28,62 +28,45 @@ namespace Microsoft.CodeAnalysis.Editor.InlineHints [ContentType(ContentTypeNames.RoslynContentType)] [TagType(typeof(IntraTextAdornmentTag))] [Name(nameof(InlineHintsTaggerProvider))] - internal sealed class InlineHintsTaggerProvider : IViewTaggerProvider + [method: ImportingConstructor] + [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + internal sealed class InlineHintsTaggerProvider( + IGlobalOptionService globalOptionService, + IClassificationFormatMapService classificationFormatMapService, + IClassificationTypeRegistryService classificationTypeRegistryService, + IThreadingContext threadingContext, + IUIThreadOperationExecutor operationExecutor, + IAsynchronousOperationListenerProvider listenerProvider, + IToolTipService toolTipService, + ClassificationTypeMap typeMap, + Lazy streamingFindUsagesPresenter, + EditorOptionsService editorOptionsService, + TaggerHost taggerHost, + [Import(AllowDefault = true)] IInlineHintKeyProcessor inlineHintKeyProcessor) : IViewTaggerProvider { - // private readonly IViewTagAggregatorFactoryService _viewTagAggregatorFactoryService; - public readonly IClassificationFormatMapService ClassificationFormatMapService; - public readonly IClassificationTypeRegistryService ClassificationTypeRegistryService; - public readonly IThreadingContext ThreadingContext; - public readonly IUIThreadOperationExecutor OperationExecutor; - public readonly IAsynchronousOperationListener AsynchronousOperationListener; - public readonly IToolTipService ToolTipService; - public readonly ClassificationTypeMap TypeMap; - public readonly Lazy StreamingFindUsagesPresenter; - public readonly EditorOptionsService EditorOptionsService; + public readonly IGlobalOptionService GlobalOptionService = globalOptionService; + public readonly IClassificationFormatMapService ClassificationFormatMapService = classificationFormatMapService; + public readonly IClassificationTypeRegistryService ClassificationTypeRegistryService = classificationTypeRegistryService; + public readonly IThreadingContext ThreadingContext = threadingContext; + public readonly IUIThreadOperationExecutor OperationExecutor = operationExecutor; + public readonly IAsynchronousOperationListener AsynchronousOperationListener = listenerProvider.GetListener(FeatureAttribute.InlineHints); + public readonly IToolTipService ToolTipService = toolTipService; + public readonly ClassificationTypeMap TypeMap = typeMap; + public readonly Lazy StreamingFindUsagesPresenter = streamingFindUsagesPresenter; + public readonly EditorOptionsService EditorOptionsService = editorOptionsService; - private readonly InlineHintsDataTaggerProvider _dataTaggerProvider; + private readonly InlineHintsDataTaggerProvider _dataTaggerProvider = new(taggerHost, inlineHintKeyProcessor); - [ImportingConstructor] - [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public InlineHintsTaggerProvider( - // IViewTagAggregatorFactoryService viewTagAggregatorFactoryService, - IClassificationFormatMapService classificationFormatMapService, - IClassificationTypeRegistryService classificationTypeRegistryService, - IThreadingContext threadingContext, - IUIThreadOperationExecutor operationExecutor, - IAsynchronousOperationListenerProvider listenerProvider, - IToolTipService toolTipService, - ClassificationTypeMap typeMap, - Lazy streamingFindUsagesPresenter, - EditorOptionsService editorOptionsService, - TaggerHost taggerHost, - [Import(AllowDefault = true)] IInlineHintKeyProcessor inlineHintKeyProcessor) + public ITagger? CreateTagger(ITextView textView, ITextBuffer subjectBuffer) where T : ITag { - // _viewTagAggregatorFactoryService = viewTagAggregatorFactoryService; - ClassificationFormatMapService = classificationFormatMapService; - ClassificationTypeRegistryService = classificationTypeRegistryService; - ThreadingContext = threadingContext; - OperationExecutor = operationExecutor; - ToolTipService = toolTipService; - StreamingFindUsagesPresenter = streamingFindUsagesPresenter; - TypeMap = typeMap; - EditorOptionsService = editorOptionsService; - - AsynchronousOperationListener = listenerProvider.GetListener(FeatureAttribute.InlineHints); - - _dataTaggerProvider = new InlineHintsDataTaggerProvider(taggerHost, inlineHintKeyProcessor); - } - - public ITagger? CreateTagger(ITextView textView, ITextBuffer buffer) where T : ITag - { - if (textView.IsNotSurfaceBufferOfTextView(buffer)) + if (textView.IsNotSurfaceBufferOfTextView(subjectBuffer)) return null; if (textView is not IWpfTextView wpfTextView) return null; var tagger = new InlineHintsTagger( - this, wpfTextView, _dataTaggerProvider.CreateTagger(textView, buffer)); + this, wpfTextView, subjectBuffer, _dataTaggerProvider.CreateTagger(textView, subjectBuffer)); if (tagger is not ITagger typedTagger) { tagger.Dispose(); diff --git a/src/EditorFeatures/Core.Wpf/InlineHints/InlineHintsViewOptionsStorage.cs b/src/EditorFeatures/Core.Wpf/InlineHints/InlineHintsViewOptionsStorage.cs index c4188e0c17927..b7df8d2ad50b7 100644 --- a/src/EditorFeatures/Core.Wpf/InlineHints/InlineHintsViewOptionsStorage.cs +++ b/src/EditorFeatures/Core.Wpf/InlineHints/InlineHintsViewOptionsStorage.cs @@ -4,14 +4,13 @@ using Microsoft.CodeAnalysis.Options; -namespace Microsoft.CodeAnalysis.Editor.InlineHints +namespace Microsoft.CodeAnalysis.Editor.InlineHints; + +internal sealed class InlineHintsViewOptionsStorage { - internal sealed class InlineHintsViewOptionsStorage - { - public static readonly Option2 DisplayAllHintsWhilePressingAltF1 = new( - "dotnet_display_inline_hints_while_pressing_alt_f1", defaultValue: true); + public static readonly Option2 DisplayAllHintsWhilePressingAltF1 = new( + "dotnet_display_inline_hints_while_pressing_alt_f1", defaultValue: true); - public static readonly PerLanguageOption2 ColorHints = new( - "dotnet_colorize_inline_hints", defaultValue: true); - } + public static readonly PerLanguageOption2 ColorHints = new( + "dotnet_colorize_inline_hints", defaultValue: true); } diff --git a/src/EditorFeatures/Core/InlineHints/InlineHintsDataTaggerProvider.cs b/src/EditorFeatures/Core/InlineHints/InlineHintsDataTaggerProvider.cs index 3148b38a8285f..3f06571b8a46d 100644 --- a/src/EditorFeatures/Core/InlineHints/InlineHintsDataTaggerProvider.cs +++ b/src/EditorFeatures/Core/InlineHints/InlineHintsDataTaggerProvider.cs @@ -2,15 +2,12 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System; -using System.ComponentModel.Composition; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Editor.Shared.Extensions; using Microsoft.CodeAnalysis.Editor.Shared.Tagging; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.Editor.Tagging; -using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.InlineHints; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Shared.TestHooks; @@ -18,19 +15,12 @@ using Microsoft.VisualStudio.Text; using Microsoft.VisualStudio.Text.Editor; using Microsoft.VisualStudio.Text.Tagging; -using VSUtilities = Microsoft.VisualStudio.Utilities; namespace Microsoft.CodeAnalysis.Editor.InlineHints; /// /// The TaggerProvider that calls upon the service in order to locate the spans and names /// -//[Export(typeof(IViewTaggerProvider))] -//[VSUtilities.ContentType(ContentTypeNames.RoslynContentType)] -//[TagType(typeof(InlineHintDataTag))] -//[VSUtilities.Name(nameof(InlineHintsDataTaggerProvider))] -//[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] -//[method: ImportingConstructor] internal sealed partial class InlineHintsDataTaggerProvider( TaggerHost taggerHost, IInlineHintKeyProcessor inlineHintKeyProcessor) From 5840f8b27d6ee9fdc3aa671b097f2ed95373f6de Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 19 Dec 2024 13:03:38 -0800 Subject: [PATCH 04/12] Clean up --- src/EditorFeatures/Core.Wpf/InlineHints/InlineHintsTagger.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/EditorFeatures/Core.Wpf/InlineHints/InlineHintsTagger.cs b/src/EditorFeatures/Core.Wpf/InlineHints/InlineHintsTagger.cs index f3b20afb46af9..110387fe516f8 100644 --- a/src/EditorFeatures/Core.Wpf/InlineHints/InlineHintsTagger.cs +++ b/src/EditorFeatures/Core.Wpf/InlineHints/InlineHintsTagger.cs @@ -3,10 +3,7 @@ // See the LICENSE file in the project root for more information. using System; -using System.Collections.Immutable; -using System.Linq; using System.Runtime.CompilerServices; -using System.Runtime.InteropServices.ComTypes; using Microsoft.CodeAnalysis.Collections; using Microsoft.CodeAnalysis.Editor.Shared.Extensions; using Microsoft.CodeAnalysis.Editor.Tagging; From 4a424c1e270f6eb48b6c254586d241398a74eb3d Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 19 Dec 2024 13:10:08 -0800 Subject: [PATCH 05/12] Docs --- .../Core.Wpf/InlineHints/InlineHintsTaggerProvider.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/EditorFeatures/Core.Wpf/InlineHints/InlineHintsTaggerProvider.cs b/src/EditorFeatures/Core.Wpf/InlineHints/InlineHintsTaggerProvider.cs index 1b2d81ce219d2..afbfe2c13c741 100644 --- a/src/EditorFeatures/Core.Wpf/InlineHints/InlineHintsTaggerProvider.cs +++ b/src/EditorFeatures/Core.Wpf/InlineHints/InlineHintsTaggerProvider.cs @@ -55,6 +55,14 @@ internal sealed class InlineHintsTaggerProvider( public readonly Lazy StreamingFindUsagesPresenter = streamingFindUsagesPresenter; public readonly EditorOptionsService EditorOptionsService = editorOptionsService; + /// + /// Underlying data tagger that produces the raw data tags. We defer to it to own the actual low level tagger. + /// That tagger is responsible for listening to events (like scrolling/editing the buffer) and emitting events when the tags change. We then forward those events along to any + /// client of us. When that client then asks us for our adornment tags, we call into the underlying tagger for + /// its data tags. Then, on demand, we convert and cache those data tags into adornment tags and pass on the + /// results. + /// private readonly InlineHintsDataTaggerProvider _dataTaggerProvider = new(taggerHost, inlineHintKeyProcessor); public ITagger? CreateTagger(ITextView textView, ITextBuffer subjectBuffer) where T : ITag From f1a2f7fd3a0ff89234c28f24c916f0f3f1fbf932 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 19 Dec 2024 13:12:04 -0800 Subject: [PATCH 06/12] Docs --- .../Core.Wpf/InlineHints/InlineHintsTagger.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/EditorFeatures/Core.Wpf/InlineHints/InlineHintsTagger.cs b/src/EditorFeatures/Core.Wpf/InlineHints/InlineHintsTagger.cs index 110387fe516f8..1a9a933820f5d 100644 --- a/src/EditorFeatures/Core.Wpf/InlineHints/InlineHintsTagger.cs +++ b/src/EditorFeatures/Core.Wpf/InlineHints/InlineHintsTagger.cs @@ -29,6 +29,13 @@ namespace Microsoft.CodeAnalysis.Editor.InlineHints /// internal sealed class InlineHintsTagger : EfficientTagger { + /// + /// On demand mapping of data tags to adornment tags created for them. As long as the data tags are alive, + /// we'll keep the corresponding adornment tags alive once it has been created. Note: the underlying + /// tagger is a view tagger, which will toss tags once they get far enough out of view. So we will only create + /// and cache as many adornment tags as what the underlying tagger thinks there should be tags for and + /// which have been requested by the view tagger. + /// private static ConditionalWeakTable, TagSpan> s_dataTagToAdornmentTag = new(); private readonly EfficientTagger _underlyingTagger; From f70d25b01bed8594fd03dcf6b0e22d39a658c585 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 19 Dec 2024 13:24:57 -0800 Subject: [PATCH 07/12] Simplify --- .../Core.Wpf/InlineHints/InlineHintsTagger.cs | 69 +++++++++---------- .../InlineHints/InlineHintsTaggerProvider.cs | 2 +- .../Core/InlineHints/InlineHintDataTag.cs | 6 ++ 3 files changed, 38 insertions(+), 39 deletions(-) diff --git a/src/EditorFeatures/Core.Wpf/InlineHints/InlineHintsTagger.cs b/src/EditorFeatures/Core.Wpf/InlineHints/InlineHintsTagger.cs index 1a9a933820f5d..90a06fd0935e5 100644 --- a/src/EditorFeatures/Core.Wpf/InlineHints/InlineHintsTagger.cs +++ b/src/EditorFeatures/Core.Wpf/InlineHints/InlineHintsTagger.cs @@ -20,24 +20,27 @@ using Microsoft.VisualStudio.Text.Tagging; using Roslyn.Utilities; -namespace Microsoft.CodeAnalysis.Editor.InlineHints +namespace Microsoft.CodeAnalysis.Editor.InlineHints; + +internal partial class InlineHintsTaggerProvider { + private sealed class AdornmentTagInformation( + bool Classified, + TextFormattingRunProperties Format, + TagSpan AdornmentTagSpan) + { + public bool Classified { get; } = Classified; + public TextFormattingRunProperties Format { get; } = Format; + public TagSpan AdornmentTagSpan { get; } = AdornmentTagSpan; + } + /// /// The purpose of this tagger is to convert the to the , which actually creates the UIElement. It reacts to tags changing and updates the /// adornments accordingly. /// - internal sealed class InlineHintsTagger : EfficientTagger + private sealed class InlineHintsTagger : EfficientTagger { - /// - /// On demand mapping of data tags to adornment tags created for them. As long as the data tags are alive, - /// we'll keep the corresponding adornment tags alive once it has been created. Note: the underlying - /// tagger is a view tagger, which will toss tags once they get far enough out of view. So we will only create - /// and cache as many adornment tags as what the underlying tagger thinks there should be tags for and - /// which have been requested by the view tagger. - /// - private static ConditionalWeakTable, TagSpan> s_dataTagToAdornmentTag = new(); - private readonly EfficientTagger _underlyingTagger; private readonly IClassificationFormatMap _formatMap; @@ -89,9 +92,6 @@ private void OnClassificationFormatMappingChanged(object sender, EventArgs e) // When classifications change we need to rebuild the inline tags with updated Font and Color information. - // Clear out the cached adornment tags we have associated with each data tag. - s_dataTagToAdornmentTag = new(); - if (_format != null) { _format = null; @@ -101,12 +101,9 @@ private void OnClassificationFormatMappingChanged(object sender, EventArgs e) private void OnGlobalOptionChanged(object sender, object target, OptionChangedEventArgs e) { + // Reclassify everything. if (e.HasOption(option => option.Equals(InlineHintsViewOptionsStorage.ColorHints))) - { - // Clear out cached adornments and reclassify everything. - s_dataTagToAdornmentTag = new(); OnTagsChanged(this, new SnapshotSpanEventArgs(_subjectBuffer.CurrentSnapshot.GetFullSpan())); - } } private TextFormattingRunProperties Format @@ -133,7 +130,10 @@ public override void AddTags( var snapshot = spans[0].Snapshot; var document = snapshot.GetOpenDocumentInCurrentContextWithChanges(); - var classify = document != null && _taggerProvider.EditorOptionsService.GlobalOptions.GetOption(InlineHintsViewOptionsStorage.ColorHints, document.Project.Language); + if (document is null) + return; + + var colorHints = _taggerProvider.EditorOptionsService.GlobalOptions.GetOption(InlineHintsViewOptionsStorage.ColorHints, document.Project.Language); using var _1 = SegmentedListPool.GetPooledList>(out var dataTagSpans); _underlyingTagger.AddTags(spans, dataTagSpans); @@ -143,10 +143,11 @@ public override void AddTags( using var _2 = PooledHashSet.GetInstance(out var seenPositions); + var format = this.Format; foreach (var dataTagSpan in dataTagSpans) { if (seenPositions.Add(dataTagSpan.Span.Start)) - adornmentTagSpans.Add(GetAdornmentTagsSpan(dataTagSpan, classify)); + adornmentTagSpans.Add(GetOrCreateAdornmentTagsSpan(dataTagSpan, colorHints, format)); } } catch (Exception e) when (FatalError.ReportAndPropagateUnlessCanceled(e, ErrorSeverity.General)) @@ -155,28 +156,20 @@ public override void AddTags( } } - private TagSpan GetAdornmentTagsSpan( - TagSpan dataTagSpan, bool classify) + private TagSpan GetOrCreateAdornmentTagsSpan( + TagSpan dataTagSpan, bool classify, TextFormattingRunProperties format) { - if (s_dataTagToAdornmentTag.TryGetValue(dataTagSpan, out var adornmentTagSpan)) - return adornmentTagSpan; - - // Extracted as a helper method to avoid closure allocations when we find the adornment span in the map. - return GetOrCreateAdornmentTagSpan(dataTagSpan, classify); - } - - private TagSpan GetOrCreateAdornmentTagSpan( - TagSpan dataTagSpan, bool classify) - { - return s_dataTagToAdornmentTag.GetValue(dataTagSpan, dataTagSpan => + // If we've never computed the adornment info, or options have changed, then compute and cache the new information. + var cachedTagInformation = (AdornmentTagInformation?)dataTagSpan.Tag.AdditionalData; + if (cachedTagInformation is null || cachedTagInformation.Classified != classify || cachedTagInformation.Format != format) { var adornmentSpan = dataTagSpan.Span; + cachedTagInformation = new(classify, format, new TagSpan(adornmentSpan, InlineHintsTag.Create( + dataTagSpan.Tag.Hint, format, _textView, adornmentSpan, _taggerProvider, _formatMap, classify))); + dataTagSpan.Tag.AdditionalData = cachedTagInformation; + } - var hintUITag = InlineHintsTag.Create( - dataTagSpan.Tag.Hint, Format, _textView, adornmentSpan, _taggerProvider, _formatMap, classify); - - return new TagSpan(adornmentSpan, hintUITag); - }); + return cachedTagInformation.AdornmentTagSpan; } } } diff --git a/src/EditorFeatures/Core.Wpf/InlineHints/InlineHintsTaggerProvider.cs b/src/EditorFeatures/Core.Wpf/InlineHints/InlineHintsTaggerProvider.cs index afbfe2c13c741..569e64dc45e90 100644 --- a/src/EditorFeatures/Core.Wpf/InlineHints/InlineHintsTaggerProvider.cs +++ b/src/EditorFeatures/Core.Wpf/InlineHints/InlineHintsTaggerProvider.cs @@ -30,7 +30,7 @@ namespace Microsoft.CodeAnalysis.Editor.InlineHints [Name(nameof(InlineHintsTaggerProvider))] [method: ImportingConstructor] [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - internal sealed class InlineHintsTaggerProvider( + internal sealed partial class InlineHintsTaggerProvider( IGlobalOptionService globalOptionService, IClassificationFormatMapService classificationFormatMapService, IClassificationTypeRegistryService classificationTypeRegistryService, diff --git a/src/EditorFeatures/Core/InlineHints/InlineHintDataTag.cs b/src/EditorFeatures/Core/InlineHints/InlineHintDataTag.cs index 8607f14677567..d3be4de1951cd 100644 --- a/src/EditorFeatures/Core/InlineHints/InlineHintDataTag.cs +++ b/src/EditorFeatures/Core/InlineHints/InlineHintDataTag.cs @@ -27,6 +27,12 @@ internal sealed class InlineHintDataTag(InlineHintsDataTaggerProvider provider, public readonly InlineHint Hint = hint; + /// + /// Additional data that can be attached to the tag. For example, the view tagger uses this to attach the adornment + /// tag information so it can be created and cached on demand. + /// + public object? AdditionalData; + // Intentionally throwing, we have never supported this facility, and there is no contract around placing // these tags in sets or maps. public override int GetHashCode() From 14e8575bfa0199dbe10782d1df70851899174feb Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 19 Dec 2024 13:30:20 -0800 Subject: [PATCH 08/12] Docs --- .../Core.Wpf/InlineHints/InlineHintsTagger.cs | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/src/EditorFeatures/Core.Wpf/InlineHints/InlineHintsTagger.cs b/src/EditorFeatures/Core.Wpf/InlineHints/InlineHintsTagger.cs index 90a06fd0935e5..a9e22d9e4c6ff 100644 --- a/src/EditorFeatures/Core.Wpf/InlineHints/InlineHintsTagger.cs +++ b/src/EditorFeatures/Core.Wpf/InlineHints/InlineHintsTagger.cs @@ -24,14 +24,19 @@ namespace Microsoft.CodeAnalysis.Editor.InlineHints; internal partial class InlineHintsTaggerProvider { - private sealed class AdornmentTagInformation( - bool Classified, - TextFormattingRunProperties Format, - TagSpan AdornmentTagSpan) + /// Whether or not the adornment tag was classified. If the option for this changes, this + /// cached tag should not be reused. + /// The text formatting used to create the hint. If this format no longer matches the current + /// formatting, this should not be reused. + /// The actual adornment tag to render. + private sealed class CachedAdornmentTagSpan( + bool classified, + TextFormattingRunProperties format, + TagSpan adornmentTagSpan) { - public bool Classified { get; } = Classified; - public TextFormattingRunProperties Format { get; } = Format; - public TagSpan AdornmentTagSpan { get; } = AdornmentTagSpan; + public bool Classified { get; } = classified; + public TextFormattingRunProperties Format { get; } = format; + public TagSpan AdornmentTagSpan { get; } = adornmentTagSpan; } /// @@ -160,7 +165,7 @@ private TagSpan GetOrCreateAdornmentTagsSpan( TagSpan dataTagSpan, bool classify, TextFormattingRunProperties format) { // If we've never computed the adornment info, or options have changed, then compute and cache the new information. - var cachedTagInformation = (AdornmentTagInformation?)dataTagSpan.Tag.AdditionalData; + var cachedTagInformation = (CachedAdornmentTagSpan?)dataTagSpan.Tag.AdditionalData; if (cachedTagInformation is null || cachedTagInformation.Classified != classify || cachedTagInformation.Format != format) { var adornmentSpan = dataTagSpan.Span; From 11822cef1bd84a55b7cba4a30c610cc87cf15f8a Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 19 Dec 2024 13:32:24 -0800 Subject: [PATCH 09/12] Docs --- src/EditorFeatures/Core.Wpf/InlineHints/InlineHintsTagger.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/EditorFeatures/Core.Wpf/InlineHints/InlineHintsTagger.cs b/src/EditorFeatures/Core.Wpf/InlineHints/InlineHintsTagger.cs index a9e22d9e4c6ff..865d8bd7358b2 100644 --- a/src/EditorFeatures/Core.Wpf/InlineHints/InlineHintsTagger.cs +++ b/src/EditorFeatures/Core.Wpf/InlineHints/InlineHintsTagger.cs @@ -24,6 +24,9 @@ namespace Microsoft.CodeAnalysis.Editor.InlineHints; internal partial class InlineHintsTaggerProvider { + /// The computed adornment tag for an inline hint, along with information needed to determine if it can be + /// reused. This is created and cached on on demand so that we only + /// create adornment tags once and reuse as long as possible. /// Whether or not the adornment tag was classified. If the option for this changes, this /// cached tag should not be reused. /// The text formatting used to create the hint. If this format no longer matches the current From b724568509cb5502d7514f3f50c6f53dd66fc584 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 19 Dec 2024 13:32:49 -0800 Subject: [PATCH 10/12] Extract type --- .../InlineHints/CachedAdornmentTagSpan.cs | 30 +++++++++++++++++++ .../Core.Wpf/InlineHints/InlineHintsTagger.cs | 18 ----------- 2 files changed, 30 insertions(+), 18 deletions(-) create mode 100644 src/EditorFeatures/Core.Wpf/InlineHints/CachedAdornmentTagSpan.cs diff --git a/src/EditorFeatures/Core.Wpf/InlineHints/CachedAdornmentTagSpan.cs b/src/EditorFeatures/Core.Wpf/InlineHints/CachedAdornmentTagSpan.cs new file mode 100644 index 0000000000000..2823271c64042 --- /dev/null +++ b/src/EditorFeatures/Core.Wpf/InlineHints/CachedAdornmentTagSpan.cs @@ -0,0 +1,30 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.VisualStudio.Text.Editor; +using Microsoft.VisualStudio.Text.Formatting; +using Microsoft.VisualStudio.Text.Tagging; + +namespace Microsoft.CodeAnalysis.Editor.InlineHints; + +internal partial class InlineHintsTaggerProvider +{ + /// The computed adornment tag for an inline hint, along with information needed to determine if it can be + /// reused. This is created and cached on on demand so that we only + /// create adornment tags once and reuse as long as possible. + /// Whether or not the adornment tag was classified. If the option for this changes, this + /// cached tag should not be reused. + /// The text formatting used to create the hint. If this format no longer matches the current + /// formatting, this should not be reused. + /// The actual adornment tag to render. + private sealed class CachedAdornmentTagSpan( + bool classified, + TextFormattingRunProperties format, + TagSpan adornmentTagSpan) + { + public bool Classified { get; } = classified; + public TextFormattingRunProperties Format { get; } = format; + public TagSpan AdornmentTagSpan { get; } = adornmentTagSpan; + } +} diff --git a/src/EditorFeatures/Core.Wpf/InlineHints/InlineHintsTagger.cs b/src/EditorFeatures/Core.Wpf/InlineHints/InlineHintsTagger.cs index 865d8bd7358b2..e70815073d644 100644 --- a/src/EditorFeatures/Core.Wpf/InlineHints/InlineHintsTagger.cs +++ b/src/EditorFeatures/Core.Wpf/InlineHints/InlineHintsTagger.cs @@ -24,24 +24,6 @@ namespace Microsoft.CodeAnalysis.Editor.InlineHints; internal partial class InlineHintsTaggerProvider { - /// The computed adornment tag for an inline hint, along with information needed to determine if it can be - /// reused. This is created and cached on on demand so that we only - /// create adornment tags once and reuse as long as possible. - /// Whether or not the adornment tag was classified. If the option for this changes, this - /// cached tag should not be reused. - /// The text formatting used to create the hint. If this format no longer matches the current - /// formatting, this should not be reused. - /// The actual adornment tag to render. - private sealed class CachedAdornmentTagSpan( - bool classified, - TextFormattingRunProperties format, - TagSpan adornmentTagSpan) - { - public bool Classified { get; } = classified; - public TextFormattingRunProperties Format { get; } = format; - public TagSpan AdornmentTagSpan { get; } = adornmentTagSpan; - } - /// /// The purpose of this tagger is to convert the to the , which actually creates the UIElement. It reacts to tags changing and updates the From e5777b3bd564161002e5e87e00f89cf5db1dbb69 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 19 Dec 2024 13:33:51 -0800 Subject: [PATCH 11/12] Wrapping --- .../Core.Wpf/InlineHints/CachedAdornmentTagSpan.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/EditorFeatures/Core.Wpf/InlineHints/CachedAdornmentTagSpan.cs b/src/EditorFeatures/Core.Wpf/InlineHints/CachedAdornmentTagSpan.cs index 2823271c64042..0589d08fbd108 100644 --- a/src/EditorFeatures/Core.Wpf/InlineHints/CachedAdornmentTagSpan.cs +++ b/src/EditorFeatures/Core.Wpf/InlineHints/CachedAdornmentTagSpan.cs @@ -10,9 +10,11 @@ namespace Microsoft.CodeAnalysis.Editor.InlineHints; internal partial class InlineHintsTaggerProvider { - /// The computed adornment tag for an inline hint, along with information needed to determine if it can be - /// reused. This is created and cached on on demand so that we only - /// create adornment tags once and reuse as long as possible. + /// + /// The computed adornment tag for an inline hint, along with information needed to determine if it can be reused. + /// This is created and cached on on demand so that we only create + /// adornment tags once and reuse as long as possible. + /// /// Whether or not the adornment tag was classified. If the option for this changes, this /// cached tag should not be reused. /// The text formatting used to create the hint. If this format no longer matches the current From a1438f124a4cddb4d2a9206183019cdc485fefe9 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 19 Dec 2024 13:41:58 -0800 Subject: [PATCH 12/12] Make strongly typed --- .../Core.Wpf/InlineHints/CachedAdornmentTagSpan.cs | 4 ++-- .../Core.Wpf/InlineHints/InlineHintsTagger.cs | 13 ++++++------- .../InlineHints/InlineHintsTaggerProvider.cs | 2 +- .../Core/InlineHints/InlineHintDataTag.cs | 13 ++++++++----- .../InlineHintKeyProcessorEventSource.cs | 2 +- .../InlineHints/InlineHintsDataTaggerProvider.cs | 13 +++++++------ 6 files changed, 25 insertions(+), 22 deletions(-) diff --git a/src/EditorFeatures/Core.Wpf/InlineHints/CachedAdornmentTagSpan.cs b/src/EditorFeatures/Core.Wpf/InlineHints/CachedAdornmentTagSpan.cs index 0589d08fbd108..4f6f0a0efc4c6 100644 --- a/src/EditorFeatures/Core.Wpf/InlineHints/CachedAdornmentTagSpan.cs +++ b/src/EditorFeatures/Core.Wpf/InlineHints/CachedAdornmentTagSpan.cs @@ -12,8 +12,8 @@ internal partial class InlineHintsTaggerProvider { /// /// The computed adornment tag for an inline hint, along with information needed to determine if it can be reused. - /// This is created and cached on on demand so that we only create - /// adornment tags once and reuse as long as possible. + /// This is created and cached on on demand + /// so that we only create adornment tags once and reuse as long as possible. /// /// Whether or not the adornment tag was classified. If the option for this changes, this /// cached tag should not be reused. diff --git a/src/EditorFeatures/Core.Wpf/InlineHints/InlineHintsTagger.cs b/src/EditorFeatures/Core.Wpf/InlineHints/InlineHintsTagger.cs index e70815073d644..c7fad83f466a5 100644 --- a/src/EditorFeatures/Core.Wpf/InlineHints/InlineHintsTagger.cs +++ b/src/EditorFeatures/Core.Wpf/InlineHints/InlineHintsTagger.cs @@ -3,7 +3,6 @@ // See the LICENSE file in the project root for more information. using System; -using System.Runtime.CompilerServices; using Microsoft.CodeAnalysis.Collections; using Microsoft.CodeAnalysis.Editor.Shared.Extensions; using Microsoft.CodeAnalysis.Editor.Tagging; @@ -25,13 +24,13 @@ namespace Microsoft.CodeAnalysis.Editor.InlineHints; internal partial class InlineHintsTaggerProvider { /// - /// The purpose of this tagger is to convert the to the to the , which actually creates the UIElement. It reacts to tags changing and updates the /// adornments accordingly. /// private sealed class InlineHintsTagger : EfficientTagger { - private readonly EfficientTagger _underlyingTagger; + private readonly EfficientTagger> _underlyingTagger; private readonly IClassificationFormatMap _formatMap; @@ -50,7 +49,7 @@ public InlineHintsTagger( InlineHintsTaggerProvider taggerProvider, IWpfTextView textView, ITextBuffer subjectBuffer, - EfficientTagger tagger) + EfficientTagger> tagger) { _taggerProvider = taggerProvider; @@ -125,7 +124,7 @@ public override void AddTags( var colorHints = _taggerProvider.EditorOptionsService.GlobalOptions.GetOption(InlineHintsViewOptionsStorage.ColorHints, document.Project.Language); - using var _1 = SegmentedListPool.GetPooledList>(out var dataTagSpans); + using var _1 = SegmentedListPool.GetPooledList>>(out var dataTagSpans); _underlyingTagger.AddTags(spans, dataTagSpans); // Presize so we can add the elements below without continually resizing. @@ -147,10 +146,10 @@ public override void AddTags( } private TagSpan GetOrCreateAdornmentTagsSpan( - TagSpan dataTagSpan, bool classify, TextFormattingRunProperties format) + TagSpan> dataTagSpan, bool classify, TextFormattingRunProperties format) { // If we've never computed the adornment info, or options have changed, then compute and cache the new information. - var cachedTagInformation = (CachedAdornmentTagSpan?)dataTagSpan.Tag.AdditionalData; + var cachedTagInformation = dataTagSpan.Tag.AdditionalData; if (cachedTagInformation is null || cachedTagInformation.Classified != classify || cachedTagInformation.Format != format) { var adornmentSpan = dataTagSpan.Span; diff --git a/src/EditorFeatures/Core.Wpf/InlineHints/InlineHintsTaggerProvider.cs b/src/EditorFeatures/Core.Wpf/InlineHints/InlineHintsTaggerProvider.cs index 569e64dc45e90..3d668d60618c2 100644 --- a/src/EditorFeatures/Core.Wpf/InlineHints/InlineHintsTaggerProvider.cs +++ b/src/EditorFeatures/Core.Wpf/InlineHints/InlineHintsTaggerProvider.cs @@ -63,7 +63,7 @@ internal sealed partial class InlineHintsTaggerProvider( /// its data tags. Then, on demand, we convert and cache those data tags into adornment tags and pass on the /// results. /// - private readonly InlineHintsDataTaggerProvider _dataTaggerProvider = new(taggerHost, inlineHintKeyProcessor); + private readonly InlineHintsDataTaggerProvider _dataTaggerProvider = new(taggerHost, inlineHintKeyProcessor); public ITagger? CreateTagger(ITextView textView, ITextBuffer subjectBuffer) where T : ITag { diff --git a/src/EditorFeatures/Core/InlineHints/InlineHintDataTag.cs b/src/EditorFeatures/Core/InlineHints/InlineHintDataTag.cs index d3be4de1951cd..d9505b71205e3 100644 --- a/src/EditorFeatures/Core/InlineHints/InlineHintDataTag.cs +++ b/src/EditorFeatures/Core/InlineHints/InlineHintDataTag.cs @@ -16,9 +16,12 @@ namespace Microsoft.CodeAnalysis.Editor.InlineHints; /// The simple tag that only holds information regarding the associated parameter name /// for the argument /// -internal sealed class InlineHintDataTag(InlineHintsDataTaggerProvider provider, ITextSnapshot snapshot, InlineHint hint) : ITag, IEquatable +internal sealed class InlineHintDataTag( + InlineHintsDataTaggerProvider provider, ITextSnapshot snapshot, InlineHint hint) + : ITag, IEquatable> + where TAdditionalInformation : class { - private readonly InlineHintsDataTaggerProvider _provider = provider; + private readonly InlineHintsDataTaggerProvider _provider = provider; /// /// The snapshot this tag was created against. @@ -31,7 +34,7 @@ internal sealed class InlineHintDataTag(InlineHintsDataTaggerProvider provider, /// Additional data that can be attached to the tag. For example, the view tagger uses this to attach the adornment /// tag information so it can be created and cached on demand. /// - public object? AdditionalData; + public TAdditionalInformation? AdditionalData; // Intentionally throwing, we have never supported this facility, and there is no contract around placing // these tags in sets or maps. @@ -39,9 +42,9 @@ public override int GetHashCode() => throw new NotImplementedException(); public override bool Equals(object? obj) - => obj is InlineHintDataTag tag && Equals(tag); + => obj is InlineHintDataTag tag && Equals(tag); - public bool Equals(InlineHintDataTag? other) + public bool Equals(InlineHintDataTag? other) { if (other is null) return false; diff --git a/src/EditorFeatures/Core/InlineHints/InlineHintKeyProcessorEventSource.cs b/src/EditorFeatures/Core/InlineHints/InlineHintKeyProcessorEventSource.cs index d14a599f3c6b3..f832a457ac39b 100644 --- a/src/EditorFeatures/Core/InlineHints/InlineHintKeyProcessorEventSource.cs +++ b/src/EditorFeatures/Core/InlineHints/InlineHintKeyProcessorEventSource.cs @@ -6,7 +6,7 @@ namespace Microsoft.CodeAnalysis.Editor.InlineHints; -internal partial class InlineHintsDataTaggerProvider +internal partial class InlineHintsDataTaggerProvider { private sealed class InlineHintKeyProcessorEventSource(IInlineHintKeyProcessor? inlineHintKeyProcessor) : AbstractTaggerEventSource { diff --git a/src/EditorFeatures/Core/InlineHints/InlineHintsDataTaggerProvider.cs b/src/EditorFeatures/Core/InlineHints/InlineHintsDataTaggerProvider.cs index 3f06571b8a46d..b104436e84086 100644 --- a/src/EditorFeatures/Core/InlineHints/InlineHintsDataTaggerProvider.cs +++ b/src/EditorFeatures/Core/InlineHints/InlineHintsDataTaggerProvider.cs @@ -21,10 +21,11 @@ namespace Microsoft.CodeAnalysis.Editor.InlineHints; /// /// The TaggerProvider that calls upon the service in order to locate the spans and names /// -internal sealed partial class InlineHintsDataTaggerProvider( +internal sealed partial class InlineHintsDataTaggerProvider( TaggerHost taggerHost, IInlineHintKeyProcessor inlineHintKeyProcessor) - : AsynchronousViewportTaggerProvider(taggerHost, FeatureAttribute.InlineHints) + : AsynchronousViewportTaggerProvider>(taggerHost, FeatureAttribute.InlineHints) + where TAdditionalInformation : class { private readonly IInlineHintKeyProcessor _inlineHintKeyProcessor = inlineHintKeyProcessor; @@ -63,7 +64,7 @@ protected override ITaggerEventSource CreateEventSource(ITextView textView, ITex } protected override async Task ProduceTagsAsync( - TaggerContext context, + TaggerContext> context, DocumentSnapshotSpan spanToTag, CancellationToken cancellationToken) { @@ -95,12 +96,12 @@ protected override async Task ProduceTagsAsync( if (hint.DisplayParts.Sum(p => p.ToString().Length) == 0) continue; - context.AddTag(new TagSpan( + context.AddTag(new TagSpan>( hint.Span.ToSnapshotSpan(snapshotSpan.Snapshot), - new InlineHintDataTag(this, snapshotSpan.Snapshot, hint))); + new(this, snapshotSpan.Snapshot, hint))); } } - protected override bool TagEquals(InlineHintDataTag tag1, InlineHintDataTag tag2) + protected override bool TagEquals(InlineHintDataTag tag1, InlineHintDataTag tag2) => tag1.Equals(tag2); }