Skip to content

Commit df56ee4

Browse files
committed
Make TextDocument immutable.
1 parent 75c6594 commit df56ee4

File tree

9 files changed

+267
-189
lines changed

9 files changed

+267
-189
lines changed

DemoLanguageServer/DiagnosticProvider.cs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,11 @@ namespace DemoLanguageServer
99
public class DiagnosticProvider
1010
{
1111

12-
public DiagnosticProvider(TextDocumentCollection textDocuments)
12+
public DiagnosticProvider()
1313
{
14-
if (textDocuments == null) throw new ArgumentNullException(nameof(textDocuments));
15-
TextDocuments = textDocuments;
14+
1615
}
1716

18-
public TextDocumentCollection TextDocuments { get; }
1917

2018
private static readonly string[] Keywords =
2119
{".NET Framework", ".NET Core", ".NET Standard", ".NET Compact", ".NET"};

DemoLanguageServer/LanguageServerSession.cs

Lines changed: 76 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
11
using System;
2+
using System.Collections.Concurrent;
3+
using System.Collections.Generic;
4+
using System.Linq;
5+
using System.Runtime.InteropServices.ComTypes;
26
using System.Threading;
7+
using System.Threading.Tasks;
38
using JsonRpc.Standard.Client;
49
using JsonRpc.Standard.Contracts;
510
using JsonRpc.Standard.Server;
11+
using LanguageServer.VsCode.Contracts;
612
using LanguageServer.VsCode.Contracts.Client;
713
using LanguageServer.VsCode.Server;
814

@@ -18,8 +24,8 @@ public LanguageServerSession(JsonRpcClient rpcClient, IJsonRpcContractResolver c
1824
RpcClient = rpcClient;
1925
var builder = new JsonRpcProxyBuilder {ContractResolver = contractResolver};
2026
Client = new ClientProxy(builder, rpcClient);
21-
Documents = new TextDocumentCollection();
22-
DiagnosticProvider = new DiagnosticProvider(Documents);
27+
Documents = new ConcurrentDictionary<Uri, SessionDocument>();
28+
DiagnosticProvider = new DiagnosticProvider();
2329
}
2430

2531
public CancellationToken CancellationToken => cts.Token;
@@ -28,7 +34,7 @@ public LanguageServerSession(JsonRpcClient rpcClient, IJsonRpcContractResolver c
2834

2935
public ClientProxy Client { get; }
3036

31-
public TextDocumentCollection Documents { get; }
37+
public ConcurrentDictionary<Uri, SessionDocument> Documents { get; }
3238

3339
public DiagnosticProvider DiagnosticProvider { get; }
3440

@@ -38,6 +44,72 @@ public void StopServer()
3844
{
3945
cts.Cancel();
4046
}
41-
47+
48+
}
49+
50+
public class SessionDocument
51+
{
52+
/// <summary>
53+
/// Actually makes the changes to the inner document per this milliseconds.
54+
/// </summary>
55+
private const int RenderChangesDelay = 100;
56+
57+
public SessionDocument(TextDocumentItem doc)
58+
{
59+
Document = TextDocument.Load<FullTextDocument>(doc);
60+
}
61+
62+
private Task updateChangesDelayTask;
63+
64+
private readonly object syncLock = new object();
65+
66+
private List<TextDocumentContentChangeEvent> impendingChanges = new List<TextDocumentContentChangeEvent>();
67+
68+
public event EventHandler DocumentChanged;
69+
70+
public TextDocument Document { get; set; }
71+
72+
public void NotifyChanges(IEnumerable<TextDocumentContentChangeEvent> changes)
73+
{
74+
lock (syncLock)
75+
{
76+
if (impendingChanges == null)
77+
impendingChanges = changes.ToList();
78+
else
79+
impendingChanges.AddRange(changes);
80+
}
81+
if (updateChangesDelayTask == null || updateChangesDelayTask.IsCompleted)
82+
{
83+
updateChangesDelayTask = Task.Delay(RenderChangesDelay);
84+
updateChangesDelayTask.ContinueWith(t => Task.Run((Action)MakeChanges));
85+
}
86+
}
87+
88+
private void MakeChanges()
89+
{
90+
List<TextDocumentContentChangeEvent> localChanges;
91+
lock (syncLock)
92+
{
93+
localChanges = impendingChanges;
94+
if (localChanges == null || localChanges.Count == 0) return;
95+
impendingChanges = null;
96+
}
97+
Document = Document.ApplyChanges(localChanges);
98+
if (impendingChanges == null)
99+
{
100+
localChanges.Clear();
101+
lock (syncLock)
102+
{
103+
if (impendingChanges == null)
104+
impendingChanges = localChanges;
105+
}
106+
}
107+
OnDocumentChanged();
108+
}
109+
110+
protected virtual void OnDocumentChanged()
111+
{
112+
DocumentChanged?.Invoke(this, EventArgs.Empty);
113+
}
42114
}
43115
}

DemoLanguageServer/Services/DemoLanguageServiceBase.cs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
using System;
2+
using System.Collections.Concurrent;
23
using System.Collections.Generic;
34
using System.Text;
45
using JsonRpc.Standard.Server;
6+
using LanguageServer.VsCode.Contracts;
57
using LanguageServer.VsCode.Contracts.Client;
68
using LanguageServer.VsCode.Server;
79

@@ -14,7 +16,14 @@ public class DemoLanguageServiceBase : JsonRpcService
1416

1517
protected ClientProxy Client => Session.Client;
1618

17-
protected TextDocumentCollection Documents => Session.Documents;
19+
protected TextDocument GetDocument(Uri uri)
20+
{
21+
if (Session.Documents.TryGetValue(uri, out var sd))
22+
return sd.Document;
23+
return null;
24+
}
25+
26+
protected TextDocument GetDocument(TextDocumentIdentifier id) => GetDocument(id.Uri);
1827

1928
}
2029
}

DemoLanguageServer/Services/TextDocumentService.cs

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -25,22 +25,29 @@ public async Task<Hover> Hover(TextDocumentIdentifier textDocument, Position pos
2525
[JsonRpcMethod(IsNotification = true)]
2626
public async Task DidOpen(TextDocumentItem textDocument)
2727
{
28-
var doc = TextDocument.Load<FullTextDocument>(textDocument);
29-
Documents.Add(doc);
30-
var diag = Session.DiagnosticProvider.LintDocument(doc, Session.Settings.MaxNumberOfProblems);
31-
await Client.Document.PublishDiagnostics(doc.Uri, diag);
28+
var doc = new SessionDocument(textDocument);
29+
var session = Session;
30+
doc.DocumentChanged += async (sender, args) =>
31+
{
32+
// Lint the document when it's changed.
33+
var doc1 = ((SessionDocument) sender).Document;
34+
var diag1 = session.DiagnosticProvider.LintDocument(doc1, session.Settings.MaxNumberOfProblems);
35+
if (session.Documents.ContainsKey(doc1.Uri))
36+
{
37+
// In case the document has been closed when we were linting…
38+
await session.Client.Document.PublishDiagnostics(doc1.Uri, diag1);
39+
}
40+
};
41+
Session.Documents.TryAdd(textDocument.Uri, doc);
42+
var diag = Session.DiagnosticProvider.LintDocument(doc.Document, Session.Settings.MaxNumberOfProblems);
43+
await Client.Document.PublishDiagnostics(textDocument.Uri, diag);
3244
}
3345

3446
[JsonRpcMethod(IsNotification = true)]
35-
public async Task DidChange(TextDocumentIdentifier textDocument,
47+
public void DidChange(TextDocumentIdentifier textDocument,
3648
ICollection<TextDocumentContentChangeEvent> contentChanges)
3749
{
38-
var doc = Documents[textDocument];
39-
doc.ApplyChanges(contentChanges);
40-
//await Client.Window.LogMessage(MessageType.Log, "-----------");
41-
//await Client.Window.LogMessage(MessageType.Log, doc.Content);
42-
var diag = Session.DiagnosticProvider.LintDocument(doc, Session.Settings.MaxNumberOfProblems);
43-
await Client.Document.PublishDiagnostics(doc.Uri, diag);
50+
Session.Documents[textDocument.Uri].NotifyChanges(contentChanges);
4451
}
4552

4653
[JsonRpcMethod(IsNotification = true)]
@@ -57,7 +64,7 @@ public async Task DidClose(TextDocumentIdentifier textDocument)
5764
{
5865
await Client.Document.PublishDiagnostics(textDocument.Uri, new Diagnostic[0]);
5966
}
60-
Documents.Remove(textDocument);
67+
Session.Documents.TryRemove(textDocument.Uri, out _);
6168
}
6269

6370
private static readonly CompletionItem[] PredefinedCompletionItems =

DemoLanguageServer/Services/WorkspaceService.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,10 @@ public class WorkspaceService : DemoLanguageServiceBase
1616
public async Task DidChangeConfiguration(SettingsRoot settings)
1717
{
1818
Session.Settings = settings.DemoLanguageServer;
19-
foreach (var doc in Session.Documents)
19+
foreach (var doc in Session.Documents.Values)
2020
{
21-
var diag = Session.DiagnosticProvider.LintDocument(doc, Session.Settings.MaxNumberOfProblems);
22-
await Client.Document.PublishDiagnostics(doc.Uri, diag);
21+
var diag = Session.DiagnosticProvider.LintDocument(doc.Document, Session.Settings.MaxNumberOfProblems);
22+
await Client.Document.PublishDiagnostics(doc.Document.Uri, diag);
2323
}
2424
}
2525

LanguageServer.VsCode/LanguageServer.VsCode.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
<PackageId>CXuesong.LanguageServer.VsCode</PackageId>
66
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
77
<Authors>CXuesong</Authors>
8-
<Version>0.1.0-int1</Version>
8+
<Version>0.1.1-int1</Version>
99
<Company />
1010
<Description>A .NET Language Server library for VSCode.</Description>
1111
<Copyright>Copyright 2017 CXuesong</Copyright>

LanguageServer.VsCode/LanguageServerExtensions.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,11 @@ public static void UseCancellationHandling(this ServiceHostBuilder builder)
3838
}
3939

4040
/// <summary>
41-
/// Determines whether the specified document URI indeicates an unsaved document.
41+
/// Determines whether the specified document URI indeicates an "untitled" document.
4242
/// </summary>
43+
/// <remarks>
44+
/// The URI of an untitled document has the following structure: <c>untitled:xxxxx</c>.
45+
/// </remarks>
4346
public static bool IsUntitled(this Uri documentUri)
4447
{
4548
return documentUri.Scheme == "untitled";

0 commit comments

Comments
 (0)