Skip to content

Commit 006e536

Browse files
Remove ProjectSnapshotManagerDispatcher from all "document processed listeners" (#10276)
This is a more significant change that removes usage of `ProjectSnapshotManagerDispatcher` from all "document processed listeners". As part of this work, I've re-worked the `RazorDiagnosticsPublisher` to use an `AsyncBatchingWorkQueue` and `Task.Delay` rather than a pair of timers. The result is a much simpler for implementation of `RazorDiagnosticsPublisher`.
2 parents caa579d + 7347195 commit 006e536

14 files changed

+598
-719
lines changed

src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeDocumentReferenceHolder.cs

Lines changed: 33 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -8,31 +8,31 @@
88

99
namespace Microsoft.AspNetCore.Razor.LanguageServer;
1010

11-
internal class CodeDocumentReferenceHolder : DocumentProcessedListener
11+
internal class CodeDocumentReferenceHolder : IDocumentProcessedListener
1212
{
13-
private Dictionary<DocumentKey, RazorCodeDocument> _codeDocumentCache;
14-
private IProjectSnapshotManager? _projectManager;
13+
private readonly IProjectSnapshotManager _projectManager;
14+
private readonly Dictionary<DocumentKey, RazorCodeDocument> _codeDocumentCache;
1515

16-
public CodeDocumentReferenceHolder()
16+
public CodeDocumentReferenceHolder(IProjectSnapshotManager projectManager)
1717
{
18-
_codeDocumentCache = new();
18+
_projectManager = projectManager;
19+
_codeDocumentCache = [];
20+
21+
_projectManager.Changed += ProjectManager_Changed;
1922
}
2023

21-
public override void DocumentProcessed(RazorCodeDocument codeDocument, IDocumentSnapshot documentSnapshot)
24+
public void DocumentProcessed(RazorCodeDocument codeDocument, IDocumentSnapshot documentSnapshot)
2225
{
2326
// We capture a reference to the code document after a document has been processed in order to ensure that
2427
// latest document state information is readily available without re-computation. The DocumentState type
2528
// (brains of DocumentSnapshot) will garbage collect its generated output aggressively and due to the
2629
// nature of LSP being heavily asynchronous (multiple requests for single keystrokes) we don't want to cause
2730
// multiple parses/regenerations across LSP requests that are all for the same document version.
28-
var key = new DocumentKey(documentSnapshot.Project.Key, documentSnapshot.FilePath.AssumeNotNull());
29-
_codeDocumentCache[key] = codeDocument;
30-
}
31-
32-
public override void Initialize(IProjectSnapshotManager projectManager)
33-
{
34-
_projectManager = projectManager;
35-
_projectManager.Changed += ProjectManager_Changed;
31+
lock (_codeDocumentCache)
32+
{
33+
var key = new DocumentKey(documentSnapshot.Project.Key, documentSnapshot.FilePath.AssumeNotNull());
34+
_codeDocumentCache[key] = codeDocument;
35+
}
3636
}
3737

3838
private void ProjectManager_Changed(object? sender, ProjectChangeEventArgs args)
@@ -45,26 +45,38 @@ private void ProjectManager_Changed(object? sender, ProjectChangeEventArgs args)
4545
case ProjectChangeKind.ProjectChanged:
4646
foreach (var documentFilePath in args.Newer!.DocumentFilePaths)
4747
{
48-
var key = new DocumentKey(args.Newer.Key, documentFilePath);
49-
_codeDocumentCache.Remove(key);
48+
lock (_codeDocumentCache)
49+
{
50+
var key = new DocumentKey(args.Newer.Key, documentFilePath);
51+
_codeDocumentCache.Remove(key);
52+
}
5053
}
5154

5255
break;
56+
5357
case ProjectChangeKind.ProjectRemoved:
5458
foreach (var documentFilePath in args.Older!.DocumentFilePaths)
5559
{
56-
var key = new DocumentKey(args.Older.Key, documentFilePath);
57-
_codeDocumentCache.Remove(key);
60+
lock (_codeDocumentCache)
61+
{
62+
var key = new DocumentKey(args.Older.Key, documentFilePath);
63+
_codeDocumentCache.Remove(key);
64+
}
5865
}
5966

6067
break;
68+
6169
case ProjectChangeKind.DocumentChanged:
6270
case ProjectChangeKind.DocumentRemoved:
6371
{
64-
var key = new DocumentKey(args.ProjectKey, args.DocumentFilePath.AssumeNotNull());
65-
_codeDocumentCache.Remove(key);
66-
break;
72+
lock (_codeDocumentCache)
73+
{
74+
var key = new DocumentKey(args.ProjectKey, args.DocumentFilePath.AssumeNotNull());
75+
_codeDocumentCache.Remove(key);
76+
}
6777
}
78+
79+
break;
6880
}
6981
}
7082
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the MIT license. See License.txt in the project root for license information.
3+
4+
using System.Collections.Generic;
5+
using Microsoft.CodeAnalysis.Razor;
6+
using Microsoft.CodeAnalysis.Razor.ProjectSystem;
7+
8+
namespace Microsoft.AspNetCore.Razor.LanguageServer.Diagnostics;
9+
10+
internal partial class RazorDiagnosticsPublisher
11+
{
12+
private sealed class Comparer : IEqualityComparer<IDocumentSnapshot>
13+
{
14+
public static readonly Comparer Instance = new();
15+
16+
private Comparer()
17+
{
18+
}
19+
20+
public bool Equals(IDocumentSnapshot? x, IDocumentSnapshot? y)
21+
{
22+
var filePathX = x?.FilePath;
23+
var filePathY = y?.FilePath;
24+
25+
return FilePathComparer.Instance.Equals(filePathX, filePathY);
26+
}
27+
28+
public int GetHashCode(IDocumentSnapshot obj)
29+
{
30+
var filePath = obj.FilePath.AssumeNotNull();
31+
return FilePathComparer.Instance.GetHashCode(filePath);
32+
}
33+
}
34+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the MIT license. See License.txt in the project root for license information.
3+
4+
using System.Collections.Immutable;
5+
using System.Threading;
6+
using System.Threading.Tasks;
7+
using Microsoft.AspNetCore.Razor.Language;
8+
using Microsoft.CodeAnalysis.Razor.ProjectSystem;
9+
using Microsoft.VisualStudio.LanguageServer.Protocol;
10+
11+
namespace Microsoft.AspNetCore.Razor.LanguageServer.Diagnostics;
12+
13+
internal partial class RazorDiagnosticsPublisher
14+
{
15+
internal TestAccessor GetTestAccessor() => new(this);
16+
17+
internal sealed class TestAccessor(RazorDiagnosticsPublisher instance)
18+
{
19+
public bool IsWaitingToClearClosedDocuments
20+
{
21+
get
22+
{
23+
return !instance._clearClosedDocumentsTask.IsCompleted;
24+
}
25+
}
26+
27+
public Task WaitForClearClosedDocumentsAsync()
28+
{
29+
#pragma warning disable VSTHRD003 // Avoid awaiting foreign Tasks
30+
return instance._clearClosedDocumentsTask;
31+
#pragma warning restore VSTHRD003 // Avoid awaiting foreign Tasks
32+
}
33+
34+
public Task WaitForDiagnosticsToPublishAsync()
35+
{
36+
return instance._workQueue.WaitUntilCurrentBatchCompletesAsync();
37+
}
38+
39+
public void SetPublishedDiagnostics(string filePath, RazorDiagnostic[] razorDiagnostics, Diagnostic[]? csharpDiagnostics)
40+
{
41+
lock (instance._publishedDiagnostics)
42+
{
43+
instance._publishedDiagnostics[filePath] = new(razorDiagnostics, csharpDiagnostics);
44+
}
45+
}
46+
47+
public void ClearClosedDocuments()
48+
{
49+
instance.ClearClosedDocuments();
50+
}
51+
52+
public Task PublishDiagnosticsAsync(IDocumentSnapshot document, CancellationToken cancellationToken)
53+
{
54+
return instance.PublishDiagnosticsAsync(document, cancellationToken);
55+
}
56+
}
57+
}

0 commit comments

Comments
 (0)