33// See the LICENSE file in the project root for more information.
44
55using System ;
6- using System . Collections . Generic ;
6+ using Microsoft . CodeAnalysis . Collections ;
77using Microsoft . CodeAnalysis . Editor . Shared . Extensions ;
8+ using Microsoft . CodeAnalysis . Editor . Tagging ;
89using Microsoft . CodeAnalysis . ErrorReporting ;
10+ using Microsoft . CodeAnalysis . Options ;
11+ using Microsoft . CodeAnalysis . PooledObjects ;
912using Microsoft . CodeAnalysis . Text ;
13+ using Microsoft . CodeAnalysis . Text . Shared . Extensions ;
14+ using Microsoft . CodeAnalysis . Utilities ;
1015using Microsoft . VisualStudio . Text ;
1116using Microsoft . VisualStudio . Text . Classification ;
1217using Microsoft . VisualStudio . Text . Editor ;
1318using Microsoft . VisualStudio . Text . Formatting ;
1419using Microsoft . VisualStudio . Text . Tagging ;
1520using Roslyn . Utilities ;
1621
17- namespace Microsoft . CodeAnalysis . Editor . InlineHints
22+ namespace Microsoft . CodeAnalysis . Editor . InlineHints ;
23+
24+ internal partial class InlineHintsTaggerProvider
1825{
1926 /// <summary>
20- /// The purpose of this tagger is to convert the <see cref="InlineHintDataTag"/> to the <see
27+ /// The purpose of this tagger is to convert the <see cref="InlineHintDataTag{TAdditionalInformation} "/> to the <see
2128 /// cref="InlineHintsTag"/>, which actually creates the UIElement. It reacts to tags changing and updates the
2229 /// adornments accordingly.
2330 /// </summary>
24- internal sealed class InlineHintsTagger : ITagger < IntraTextAdornmentTag > , IDisposable
31+ private sealed class InlineHintsTagger : EfficientTagger < IntraTextAdornmentTag >
2532 {
26- private readonly ITagAggregator < InlineHintDataTag > _tagAggregator ;
27-
28- /// <summary>
29- /// stores the parameter hint tags in a global location
30- /// </summary>
31- private readonly List < ( IMappingTagSpan < InlineHintDataTag > mappingTagSpan , TagSpan < IntraTextAdornmentTag > ? tagSpan ) > _cache = [ ] ;
32-
33- /// <summary>
34- /// Stores the snapshot associated with the cached tags in <see cref="_cache" />
35- /// </summary>
36- private ITextSnapshot ? _cacheSnapshot ;
33+ private readonly EfficientTagger < InlineHintDataTag < CachedAdornmentTagSpan > > _underlyingTagger ;
3734
3835 private readonly IClassificationFormatMap _formatMap ;
3936
@@ -45,77 +42,59 @@ internal sealed class InlineHintsTagger : ITagger<IntraTextAdornmentTag>, IDispo
4542
4643 private readonly InlineHintsTaggerProvider _taggerProvider ;
4744
48- private readonly ITextBuffer _buffer ;
4945 private readonly IWpfTextView _textView ;
50-
51- public event EventHandler < SnapshotSpanEventArgs > ? TagsChanged ;
46+ private readonly ITextBuffer _subjectBuffer ;
5247
5348 public InlineHintsTagger (
5449 InlineHintsTaggerProvider taggerProvider ,
5550 IWpfTextView textView ,
56- ITextBuffer buffer ,
57- ITagAggregator < InlineHintDataTag > tagAggregator )
51+ ITextBuffer subjectBuffer ,
52+ EfficientTagger < InlineHintDataTag < CachedAdornmentTagSpan > > tagger )
5853 {
5954 _taggerProvider = taggerProvider ;
6055
6156 _textView = textView ;
62- _buffer = buffer ;
57+ _subjectBuffer = subjectBuffer ;
58+
59+ // When the underlying tagger produced new data tags, inform any clients of us that we have new adornment tags.
60+ _underlyingTagger = tagger ;
61+ _underlyingTagger . TagsChanged += OnTagsChanged ;
6362
64- _tagAggregator = tagAggregator ;
6563 _formatMap = taggerProvider . ClassificationFormatMapService . GetClassificationFormatMap ( textView ) ;
6664 _hintClassification = taggerProvider . ClassificationTypeRegistryService . GetClassificationType ( InlineHintsTag . TagId ) ;
65+
6766 _formatMap . ClassificationFormatMappingChanged += this . OnClassificationFormatMappingChanged ;
68- _tagAggregator . BatchedTagsChanged += TagAggregator_BatchedTagsChanged ;
67+ _taggerProvider . GlobalOptionService . AddOptionChangedHandler ( this , OnGlobalOptionChanged ) ;
6968 }
7069
71- /// <summary>
72- /// Goes through all the spans in which tags have changed and
73- /// invokes a TagsChanged event. Using the BatchedTagsChangedEvent since it is raised
74- /// on the same thread that created the tag aggregator, unlike TagsChanged.
75- /// </summary>
76- private void TagAggregator_BatchedTagsChanged ( object sender , BatchedTagsChangedEventArgs e )
70+ public override void Dispose ( )
7771 {
78- _taggerProvider . ThreadingContext . ThrowIfNotOnUIThread ( ) ;
79- InvalidateCache ( ) ;
80-
81- var tagsChanged = TagsChanged ;
82- if ( tagsChanged is null )
83- {
84- return ;
85- }
86-
87- var mappingSpans = e . Spans ;
88- foreach ( var item in mappingSpans )
89- {
90- var spans = item . GetSpans ( _buffer ) ;
91- foreach ( var span in spans )
92- {
93- if ( tagsChanged != null )
94- {
95- tagsChanged . Invoke ( this , new SnapshotSpanEventArgs ( span ) ) ;
96- }
97- }
98- }
72+ _formatMap . ClassificationFormatMappingChanged -= OnClassificationFormatMappingChanged ;
73+ _taggerProvider . GlobalOptionService . RemoveOptionChangedHandler ( this , OnGlobalOptionChanged ) ;
74+ _underlyingTagger . TagsChanged -= OnTagsChanged ;
75+ _underlyingTagger . Dispose ( ) ;
9976 }
10077
10178 private void OnClassificationFormatMappingChanged ( object sender , EventArgs e )
10279 {
10380 _taggerProvider . ThreadingContext . ThrowIfNotOnUIThread ( ) ;
81+
82+ // When classifications change we need to rebuild the inline tags with updated Font and Color information.
83+
10484 if ( _format != null )
10585 {
10686 _format = null ;
107- InvalidateCache ( ) ;
108-
109- // When classifications change we need to rebuild the inline tags with updated Font and Color information.
110- var tags = GetTags ( new NormalizedSnapshotSpanCollection ( _textView . TextViewLines . FormattedSpan ) ) ;
111-
112- foreach ( var tag in tags )
113- {
114- TagsChanged ? . Invoke ( this , new SnapshotSpanEventArgs ( tag . Span ) ) ;
115- }
87+ OnTagsChanged ( this , new SnapshotSpanEventArgs ( _subjectBuffer . CurrentSnapshot . GetFullSpan ( ) ) ) ;
11688 }
11789 }
11890
91+ private void OnGlobalOptionChanged ( object sender , object target , OptionChangedEventArgs e )
92+ {
93+ // Reclassify everything.
94+ if ( e . HasOption ( option => option . Equals ( InlineHintsViewOptionsStorage . ColorHints ) ) )
95+ OnTagsChanged ( this , new SnapshotSpanEventArgs ( _subjectBuffer . CurrentSnapshot . GetFullSpan ( ) ) ) ;
96+ }
97+
11998 private TextFormattingRunProperties Format
12099 {
121100 get
@@ -126,86 +105,60 @@ private TextFormattingRunProperties Format
126105 }
127106 }
128107
129- private void InvalidateCache ( )
130- {
131- _taggerProvider . ThreadingContext . ThrowIfNotOnUIThread ( ) ;
132- _cacheSnapshot = null ;
133- _cache . Clear ( ) ;
134- }
135-
136- IEnumerable < ITagSpan < IntraTextAdornmentTag > > ITagger < IntraTextAdornmentTag > . GetTags ( NormalizedSnapshotSpanCollection spans )
137- => GetTags ( spans ) ;
138-
139- public IReadOnlyList < TagSpan < IntraTextAdornmentTag > > GetTags ( NormalizedSnapshotSpanCollection spans )
108+ public override void AddTags (
109+ NormalizedSnapshotSpanCollection spans ,
110+ SegmentedList < TagSpan < IntraTextAdornmentTag > > adornmentTagSpans )
140111 {
141112 try
142113 {
143114 if ( spans . Count == 0 )
144- return [ ] ;
115+ return ;
145116
117+ // If the snapshot has changed, we can't use any of the cached data, as it is associated with the
118+ // original snapshot they were created against.
146119 var snapshot = spans [ 0 ] . Snapshot ;
147- if ( snapshot != _cacheSnapshot )
148- {
149- // Calculate UI elements
150- _cache . Clear ( ) ;
151- _cacheSnapshot = snapshot ;
152-
153- // Calling into the InlineParameterNameHintsDataTaggerProvider which only responds with the current
154- // active view and disregards and requests for tags not in that view
155- var fullSpan = new SnapshotSpan ( snapshot , 0 , snapshot . Length ) ;
156- var tags = _tagAggregator . GetTags ( new NormalizedSnapshotSpanCollection ( fullSpan ) ) ;
157- foreach ( var tag in tags )
158- {
159- // Gets the associated span from the snapshot span and creates the IntraTextAdornmentTag from the data
160- // tags. Only dealing with the dataTagSpans if the count is 1 because we do not see a multi-buffer case
161- // occurring
162- var dataTagSpans = tag . Span . GetSpans ( snapshot ) ;
163- if ( dataTagSpans . Count == 1 )
164- {
165- _cache . Add ( ( tag , tagSpan : null ) ) ;
166- }
167- }
168- }
169120
170121 var document = snapshot . GetOpenDocumentInCurrentContextWithChanges ( ) ;
171- var classify = document != null && _taggerProvider . EditorOptionsService . GlobalOptions . GetOption ( InlineHintsViewOptionsStorage . ColorHints , document . Project . Language ) ;
122+ if ( document is null )
123+ return ;
124+
125+ var colorHints = _taggerProvider . EditorOptionsService . GlobalOptions . GetOption ( InlineHintsViewOptionsStorage . ColorHints , document . Project . Language ) ;
126+
127+ using var _1 = SegmentedListPool . GetPooledList < TagSpan < InlineHintDataTag < CachedAdornmentTagSpan > > > ( out var dataTagSpans ) ;
128+ _underlyingTagger . AddTags ( spans , dataTagSpans ) ;
129+
130+ // Presize so we can add the elements below without continually resizing.
131+ adornmentTagSpans . Capacity += dataTagSpans . Count ;
132+
133+ using var _2 = PooledHashSet < int > . GetInstance ( out var seenPositions ) ;
172134
173- var selectedSpans = new List < TagSpan < IntraTextAdornmentTag > > ( ) ;
174- for ( var i = 0 ; i < _cache . Count ; i ++ )
135+ var format = this . Format ;
136+ foreach ( var dataTagSpan in dataTagSpans )
175137 {
176- var tagSpans = _cache [ i ] . mappingTagSpan . Span . GetSpans ( snapshot ) ;
177- if ( tagSpans . Count == 1 )
178- {
179- var tagSpan = tagSpans [ 0 ] ;
180- if ( spans . IntersectsWith ( tagSpan ) )
181- {
182- if ( _cache [ i ] . tagSpan is not { } hintTagSpan )
183- {
184- var hintUITag = InlineHintsTag . Create (
185- _cache [ i ] . mappingTagSpan . Tag . Hint , Format , _textView , tagSpan , _taggerProvider , _formatMap , classify ) ;
186-
187- hintTagSpan = new TagSpan < IntraTextAdornmentTag > ( tagSpan , hintUITag ) ;
188- _cache [ i ] = ( _cache [ i ] . mappingTagSpan , hintTagSpan ) ;
189- }
190-
191- selectedSpans . Add ( hintTagSpan ) ;
192- }
193- }
138+ if ( seenPositions . Add ( dataTagSpan . Span . Start ) )
139+ adornmentTagSpans . Add ( GetOrCreateAdornmentTagsSpan ( dataTagSpan , colorHints , format ) ) ;
194140 }
195-
196- return selectedSpans ;
197141 }
198142 catch ( Exception e ) when ( FatalError . ReportAndPropagateUnlessCanceled ( e , ErrorSeverity . General ) )
199143 {
200144 throw ExceptionUtilities . Unreachable ( ) ;
201145 }
202146 }
203147
204- public void Dispose ( )
148+ private TagSpan < IntraTextAdornmentTag > GetOrCreateAdornmentTagsSpan (
149+ TagSpan < InlineHintDataTag < CachedAdornmentTagSpan > > dataTagSpan , bool classify , TextFormattingRunProperties format )
205150 {
206- _tagAggregator . BatchedTagsChanged -= TagAggregator_BatchedTagsChanged ;
207- _tagAggregator . Dispose ( ) ;
208- _formatMap . ClassificationFormatMappingChanged -= OnClassificationFormatMappingChanged ;
151+ // If we've never computed the adornment info, or options have changed, then compute and cache the new information.
152+ var cachedTagInformation = dataTagSpan . Tag . AdditionalData ;
153+ if ( cachedTagInformation is null || cachedTagInformation . Classified != classify || cachedTagInformation . Format != format )
154+ {
155+ var adornmentSpan = dataTagSpan . Span ;
156+ cachedTagInformation = new ( classify , format , new TagSpan < IntraTextAdornmentTag > ( adornmentSpan , InlineHintsTag . Create (
157+ dataTagSpan . Tag . Hint , format , _textView , adornmentSpan , _taggerProvider , _formatMap , classify ) ) ) ;
158+ dataTagSpan . Tag . AdditionalData = cachedTagInformation ;
159+ }
160+
161+ return cachedTagInformation . AdornmentTagSpan ;
209162 }
210163 }
211164}
0 commit comments