@@ -123,6 +123,13 @@ type internal FSharpClassificationService [<ImportingConstructor>] () =
123
123
let semanticClassificationCache =
124
124
new DocumentCache< SemanticClassificationLookup>( " fsharp-semantic-classification-cache" )
125
125
126
+ // We don't really care here about concurrency in neither DocumentCahce, nor underlying hashmap.
127
+ let syntacticClassificationCache =
128
+ new DocumentCache< Dictionary< TextSpan, ImmutableArray< ClassifiedSpan>>>(
129
+ " fsharp-syntactic-classification-cache" ,
130
+ CacheItemPolicy( SlidingExpiration = ( TimeSpan.FromMinutes 5 ))
131
+ )
132
+
126
133
interface IFSharpClassificationService with
127
134
// Do not perform classification if we don't have project options (#defines matter)
128
135
member _.AddLexicalClassifications ( _ : SourceText , _ : TextSpan , _ : List < ClassifiedSpan >, _ : CancellationToken ) = ()
@@ -160,8 +167,32 @@ type internal FSharpClassificationService [<ImportingConstructor>] () =
160
167
TelemetryReporter.ReportSingleEventWithDuration( TelemetryEvents.AddSyntacticCalssifications, eventProps)
161
168
162
169
if not isOpenDocument then
163
- let classifiedSpans = getLexicalClassifications ( document.FilePath, defines, sourceText, textSpan, cancellationToken)
164
- result.AddRange( classifiedSpans)
170
+ // If called concurrently, it'll likely race here (in both getting and setting/adding to underlying dictionary)
171
+ // But we don't really care for such small caches, will change the implementation if it shows that it's a problem.
172
+ match ! syntacticClassificationCache.TryGetValueAsync document with
173
+ | ValueSome classifiedSpansDict ->
174
+ match classifiedSpansDict.TryGetValue( textSpan) with
175
+ | true , _ ->
176
+ // if we already classified these spans in the _closed_ document, don't add them to result again
177
+ ()
178
+ | _ ->
179
+ let classifiedSpans =
180
+ getLexicalClassifications ( document.FilePath, defines, sourceText, textSpan, cancellationToken)
181
+
182
+ classifiedSpansDict.Add( textSpan, classifiedSpans)
183
+ result.AddRange( classifiedSpans)
184
+ | ValueNone ->
185
+
186
+ let classifiedSpans =
187
+ getLexicalClassifications ( document.FilePath, defines, sourceText, textSpan, cancellationToken)
188
+
189
+ let classifiedSpansDict = Dictionary< TextSpan, ImmutableArray< ClassifiedSpan>>()
190
+
191
+ do ! syntacticClassificationCache.SetAsync( document, classifiedSpansDict)
192
+
193
+ do classifiedSpansDict[ textSpan] <- classifiedSpans
194
+
195
+ result.AddRange( classifiedSpans)
165
196
else
166
197
Tokenizer.classifySpans (
167
198
document.Id,
0 commit comments