44
55using System ;
66using System . Collections . Immutable ;
7+ using System . Linq ;
8+ using System . Runtime . CompilerServices ;
9+ using System . Runtime . InteropServices . ComTypes ;
710using Microsoft . CodeAnalysis . Collections ;
811using Microsoft . CodeAnalysis . Editor . Shared . Extensions ;
912using Microsoft . CodeAnalysis . Editor . Tagging ;
1013using Microsoft . CodeAnalysis . ErrorReporting ;
14+ using Microsoft . CodeAnalysis . Options ;
1115using Microsoft . CodeAnalysis . PooledObjects ;
1216using Microsoft . CodeAnalysis . Text ;
17+ using Microsoft . CodeAnalysis . Text . Shared . Extensions ;
1318using Microsoft . CodeAnalysis . Utilities ;
1419using Microsoft . VisualStudio . Text ;
1520using Microsoft . VisualStudio . Text . Classification ;
2025
2126namespace Microsoft . CodeAnalysis . Editor . InlineHints
2227{
23- using InlineHintTagCache = ImmutableDictionary < int , InlineHintTags > ;
24-
25- internal class InlineHintTags ( TagSpan < InlineHintDataTag > dataTagSpan )
26- {
27- /// <summary>
28- /// Provided at creation time. Never changes.
29- /// </summary>
30- public readonly TagSpan < InlineHintDataTag > DataTagSpan = dataTagSpan ;
31-
32- /// <summary>
33- /// Created on demand when the adornment is needed.
34- /// </summary>
35- public TagSpan < IntraTextAdornmentTag > ? AdornmentTagSpan ;
36- }
37-
3828 /// <summary>
3929 /// The purpose of this tagger is to convert the <see cref="InlineHintDataTag"/> to the <see
4030 /// cref="InlineHintsTag"/>, which actually creates the UIElement. It reacts to tags changing and updates the
4131 /// adornments accordingly.
4232 /// </summary>
4333 internal sealed class InlineHintsTagger : EfficientTagger < IntraTextAdornmentTag >
4434 {
35+ private static ConditionalWeakTable < TagSpan < InlineHintDataTag > , TagSpan < IntraTextAdornmentTag > > s_dataTagToAdornmentTag = new ( ) ;
36+
4537 private readonly EfficientTagger < InlineHintDataTag > _underlyingTagger ;
4638
4739 private readonly IClassificationFormatMap _formatMap ;
@@ -55,63 +47,61 @@ internal sealed class InlineHintsTagger : EfficientTagger<IntraTextAdornmentTag>
5547 private readonly InlineHintsTaggerProvider _taggerProvider ;
5648
5749 private readonly IWpfTextView _textView ;
58-
59- private readonly object _gate = new ( ) ;
60- /// <summary>
61- /// Stores the snapshot associated with the cached tags in <see cref="_cache_doNotAccessOutsideOfGate"/>.
62- /// Locked by <see cref="_gate"/>.
63- /// </summary>
64- private ITextSnapshot ? _cacheSnapshot_doNotAccessOutsideOfGate ;
65-
66- /// <summary>
67- /// Mapping from position to the data tag computed for it, and the adornment tag (once we've computed that).
68- /// Locked by <see cref="_gate"/>.
69- /// </summary>
70- private InlineHintTagCache _cache_doNotAccessOutsideOfGate = InlineHintTagCache . Empty ;
50+ private readonly ITextBuffer _subjectBuffer ;
7151
7252 public InlineHintsTagger (
7353 InlineHintsTaggerProvider taggerProvider ,
7454 IWpfTextView textView ,
55+ ITextBuffer subjectBuffer ,
7556 EfficientTagger < InlineHintDataTag > tagger )
7657 {
7758 _taggerProvider = taggerProvider ;
7859
7960 _textView = textView ;
61+ _subjectBuffer = subjectBuffer ;
8062
63+ // When the underlying tagger produced new data tags, inform any clients of us that we have new adornment tags.
8164 _underlyingTagger = tagger ;
82- _underlyingTagger . TagsChanged += OnUnderlyingTagger_TagsChanged ;
65+ _underlyingTagger . TagsChanged += OnTagsChanged ;
8366
8467 _formatMap = taggerProvider . ClassificationFormatMapService . GetClassificationFormatMap ( textView ) ;
8568 _hintClassification = taggerProvider . ClassificationTypeRegistryService . GetClassificationType ( InlineHintsTag . TagId ) ;
69+
8670 _formatMap . ClassificationFormatMappingChanged += this . OnClassificationFormatMappingChanged ;
71+ _taggerProvider . GlobalOptionService . AddOptionChangedHandler ( this , OnGlobalOptionChanged ) ;
8772 }
8873
8974 public override void Dispose ( )
9075 {
91- _underlyingTagger . TagsChanged -= OnUnderlyingTagger_TagsChanged ;
92- _underlyingTagger . Dispose ( ) ;
9376 _formatMap . ClassificationFormatMappingChanged -= OnClassificationFormatMappingChanged ;
94- }
95-
96- private void OnUnderlyingTagger_TagsChanged ( object sender , SnapshotSpanEventArgs e )
97- {
98- InvalidateCache ( ) ;
99- OnTagsChanged ( this , e ) ;
77+ _taggerProvider . GlobalOptionService . RemoveOptionChangedHandler ( this , OnGlobalOptionChanged ) ;
78+ _underlyingTagger . TagsChanged -= OnTagsChanged ;
79+ _underlyingTagger . Dispose ( ) ;
10080 }
10181
10282 private void OnClassificationFormatMappingChanged ( object sender , EventArgs e )
10383 {
10484 _taggerProvider . ThreadingContext . ThrowIfNotOnUIThread ( ) ;
85+
86+ // When classifications change we need to rebuild the inline tags with updated Font and Color information.
87+
88+ // Clear out the cached adornment tags we have associated with each data tag.
89+ s_dataTagToAdornmentTag = new ( ) ;
90+
10591 if ( _format != null )
10692 {
10793 _format = null ;
108- InvalidateCache ( ) ;
109-
110- // When classifications change we need to rebuild the inline tags with updated Font and Color information.
111- var tags = GetTags ( new NormalizedSnapshotSpanCollection ( _textView . TextViewLines . FormattedSpan ) ) ;
94+ OnTagsChanged ( this , new SnapshotSpanEventArgs ( _subjectBuffer . CurrentSnapshot . GetFullSpan ( ) ) ) ;
95+ }
96+ }
11297
113- foreach ( var tag in tags )
114- OnTagsChanged ( this , new SnapshotSpanEventArgs ( tag . Span ) ) ;
98+ private void OnGlobalOptionChanged ( object sender , object target , OptionChangedEventArgs e )
99+ {
100+ if ( e . HasOption ( option => option . Equals ( InlineHintsViewOptionsStorage . ColorHints ) ) )
101+ {
102+ // Clear out cached adornments and reclassify everything.
103+ s_dataTagToAdornmentTag = new ( ) ;
104+ OnTagsChanged ( this , new SnapshotSpanEventArgs ( _subjectBuffer . CurrentSnapshot . GetFullSpan ( ) ) ) ;
115105 }
116106 }
117107
@@ -125,15 +115,6 @@ private TextFormattingRunProperties Format
125115 }
126116 }
127117
128- private void InvalidateCache ( )
129- {
130- lock ( _gate )
131- {
132- _cacheSnapshot_doNotAccessOutsideOfGate = null ;
133- _cache_doNotAccessOutsideOfGate = InlineHintTagCache . Empty ;
134- }
135- }
136-
137118 public override void AddTags (
138119 NormalizedSnapshotSpanCollection spans ,
139120 SegmentedList < TagSpan < IntraTextAdornmentTag > > adornmentTagSpans )
@@ -143,22 +124,9 @@ public override void AddTags(
143124 if ( spans . Count == 0 )
144125 return ;
145126
146- ITextSnapshot ? cacheSnapshot ;
147- InlineHintTagCache cache ;
148-
149- lock ( _gate )
150- {
151- cacheSnapshot = _cacheSnapshot_doNotAccessOutsideOfGate ;
152- cache = _cache_doNotAccessOutsideOfGate ;
153- }
154-
155- var cacheBuilder = cache . ToBuilder ( ) ;
156-
157127 // If the snapshot has changed, we can't use any of the cached data, as it is associated with the
158128 // original snapshot they were created against.
159129 var snapshot = spans [ 0 ] . Snapshot ;
160- if ( snapshot != cacheSnapshot )
161- cacheBuilder . Clear ( ) ;
162130
163131 var document = snapshot . GetOpenDocumentInCurrentContextWithChanges ( ) ;
164132 var classify = document != null && _taggerProvider . EditorOptionsService . GlobalOptions . GetOption ( InlineHintsViewOptionsStorage . ColorHints , document . Project . Language ) ;
@@ -173,34 +141,8 @@ public override void AddTags(
173141
174142 foreach ( var dataTagSpan in dataTagSpans )
175143 {
176- // Check if we already have a tag at this position. If not, initialize the cache to just point at
177- // the new data tag.
178- var position = dataTagSpan . Span . Start ;
179- if ( ! cache . TryGetValue ( position , out var inlineHintTags ) )
180- {
181- inlineHintTags = new ( dataTagSpan ) ;
182- cacheBuilder [ position ] = inlineHintTags ;
183- }
184-
185- if ( seenPositions . Add ( position ) )
186- {
187- // Now check if this is the first time we've been asked to compute the adornment for this particular
188- // data tag. If so, create and cache it so we don't recreate the adornments in the future for the
189- // same text snapshot.
190- //
191- // Note: creating the adornment doesn't change the cache itself. It just updates one of the values
192- // the cache is already pointing to. We only need to change the cache if we've added a new
193- // key/value mapping to it.
194- inlineHintTags . AdornmentTagSpan ??= CreateAdornmentTagSpan ( inlineHintTags . DataTagSpan , classify ) ;
195- adornmentTagSpans . Add ( inlineHintTags . AdornmentTagSpan ) ;
196- }
197- }
198-
199- cache = cacheBuilder . ToImmutable ( ) ;
200- lock ( _gate )
201- {
202- _cacheSnapshot_doNotAccessOutsideOfGate = snapshot ;
203- _cache_doNotAccessOutsideOfGate = cache ;
144+ if ( seenPositions . Add ( dataTagSpan . Span . Start ) )
145+ adornmentTagSpans . Add ( GetAdornmentTagsSpan ( dataTagSpan , classify ) ) ;
204146 }
205147 }
206148 catch ( Exception e ) when ( FatalError . ReportAndPropagateUnlessCanceled ( e , ErrorSeverity . General ) )
@@ -209,15 +151,28 @@ public override void AddTags(
209151 }
210152 }
211153
212- private TagSpan < IntraTextAdornmentTag > CreateAdornmentTagSpan (
154+ private TagSpan < IntraTextAdornmentTag > GetAdornmentTagsSpan (
213155 TagSpan < InlineHintDataTag > dataTagSpan , bool classify )
214156 {
215- var adornmentSpan = dataTagSpan . Span ;
157+ if ( s_dataTagToAdornmentTag . TryGetValue ( dataTagSpan , out var adornmentTagSpan ) )
158+ return adornmentTagSpan ;
159+
160+ // Extracted as a helper method to avoid closure allocations when we find the adornment span in the map.
161+ return GetOrCreateAdornmentTagSpan ( dataTagSpan , classify ) ;
162+ }
163+
164+ private TagSpan < IntraTextAdornmentTag > GetOrCreateAdornmentTagSpan (
165+ TagSpan < InlineHintDataTag > dataTagSpan , bool classify )
166+ {
167+ return s_dataTagToAdornmentTag . GetValue ( dataTagSpan , dataTagSpan =>
168+ {
169+ var adornmentSpan = dataTagSpan . Span ;
216170
217- var hintUITag = InlineHintsTag . Create (
218- dataTagSpan . Tag . Hint , Format , _textView , adornmentSpan , _taggerProvider , _formatMap , classify ) ;
171+ var hintUITag = InlineHintsTag . Create (
172+ dataTagSpan . Tag . Hint , Format , _textView , adornmentSpan , _taggerProvider , _formatMap , classify ) ;
219173
220- return new TagSpan < IntraTextAdornmentTag > ( adornmentSpan , hintUITag ) ;
174+ return new TagSpan < IntraTextAdornmentTag > ( adornmentSpan , hintUITag ) ;
175+ } ) ;
221176 }
222177 }
223178}
0 commit comments