diff --git a/src/Stampeded.RoslynLsp/RoslynLspServer.cs b/src/Stampeded.RoslynLsp/RoslynLspServer.cs
index 80761aa..d9d971d 100644
--- a/src/Stampeded.RoslynLsp/RoslynLspServer.cs
+++ b/src/Stampeded.RoslynLsp/RoslynLspServer.cs
@@ -107,7 +107,7 @@ async Task HandleNotificationAsync(string method, JsonElement parameters, Cancel
case "textDocument/didChange":
// The client sends what the review is showing, which may be a revision that
// is not on disk. An overlay is how the workspace is told.
- Overlay(parameters);
+ await OverlayAsync(parameters, ct);
break;
case "initialized":
case "$/cancelRequest":
@@ -206,8 +206,14 @@ void WatchParent(JsonElement parameters)
});
}
+ // An event handler cannot await, and the client only needs the notification to arrive
+ // eventually: the write lock queues waiters in order, so states are still sent in the
+ // order they were reached.
void ReportState()
- => Notify("stampeded/state", new { state = head.State.ToString(), detail = head.StateDetail });
+ => NotifyAsync("stampeded/state", new { state = head.State.ToString(), detail = head.StateDetail })
+ .ContinueWith(
+ t => CliLog.Write("roslyn-lsp", $"state notification failed: {t.Exception?.GetBaseException().Message}"),
+ TaskContinuationOptions.OnlyOnFaulted);
/// Derives the base-side workspace from the head one, given the texts of the
/// revision being compared against.
@@ -242,7 +248,7 @@ static Dictionary ReadTexts(JsonElement parameters, string name)
return texts;
}
- void Overlay(JsonElement parameters)
+ async Task OverlayAsync(JsonElement parameters, CancellationToken ct)
{
if (!parameters.TryGetProperty("textDocument", out var document)
|| !document.TryGetProperty("uri", out var uri))
@@ -261,7 +267,7 @@ void Overlay(JsonElement parameters)
// Only when it differs from what the workspace has: the client opens a document to
// be able to ask about it at all, and re-stating the file on disk would throw away
// the compilation that already knows it.
- if (target.Service.GetDocumentTextAsync(target.RelPath, CancellationToken.None).Result is { } current
+ if (await target.Service.GetDocumentTextAsync(target.RelPath, ct) is { } current
&& string.Equals(current.ReplaceLineEndings("\n"), text.ReplaceLineEndings("\n"), StringComparison.Ordinal))
{
return;
@@ -619,11 +625,11 @@ async Task RespondAsync(JsonElement id, object? result)
await SendAsync(payload);
}
- void Notify(string method, object parameters)
+ Task NotifyAsync(string method, object parameters)
{
var payload = JsonSerializer.SerializeToUtf8Bytes(
new { jsonrpc = "2.0", method, @params = parameters }, Json);
- SendAsync(payload).GetAwaiter().GetResult();
+ return SendAsync(payload);
}
async Task SendAsync(byte[] payload)
diff --git a/tests/Stampeded.Core.Tests/GeneratedSourcesTests.cs b/tests/Stampeded.Core.Tests/GeneratedSourcesTests.cs
index b7eb357..080e3e8 100644
--- a/tests/Stampeded.Core.Tests/GeneratedSourcesTests.cs
+++ b/tests/Stampeded.Core.Tests/GeneratedSourcesTests.cs
@@ -54,13 +54,13 @@ public void KeepsOutputOfTheSameGeneratorInDifferentProjectsApart()
}
[Test]
- public void PairsTheTwoSidesEvenWhenTheyWereBuiltDifferently()
+ public async Task PairsTheTwoSidesEvenWhenTheyWereBuiltDifferently()
{
string baseTree = NewDirectory(), headTree = NewDirectory();
Write(baseTree, "src/Lib/obj/Debug/net10.0/generated/G/E/Thing.g.cs", "one\ntwo\n");
Write(headTree, "src/Lib/obj/Release/net11.0/generated/G/E/Thing.g.cs", "one\ntwo changed\n");
- var files = GeneratedSources.DiffAsync(baseTree, headTree).GetAwaiter().GetResult();
+ var files = await GeneratedSources.DiffAsync(baseTree, headTree);
Assert.That(files, Has.Count.EqualTo(1));
Assert.That(files[0].Kind, Is.EqualTo(FileChangeKind.Modified));
@@ -69,13 +69,13 @@ public void PairsTheTwoSidesEvenWhenTheyWereBuiltDifferently()
}
[Test]
- public void ReportsWhatOnlyOneSideGenerated()
+ public async Task ReportsWhatOnlyOneSideGenerated()
{
string baseTree = NewDirectory(), headTree = NewDirectory();
Write(baseTree, "src/Lib/obj/Debug/net10.0/generated/G/E/Gone.g.cs", "was here\n");
Write(headTree, "src/Lib/obj/Debug/net10.0/generated/G/E/New.g.cs", "is here\n");
- var files = GeneratedSources.DiffAsync(baseTree, headTree).GetAwaiter().GetResult();
+ var files = await GeneratedSources.DiffAsync(baseTree, headTree);
Assert.That(files.Select(f => (f.Path, f.Kind)), Is.EquivalentTo(new[] {
("src/Lib/generated/G/E/Gone.g.cs", FileChangeKind.Deleted),
@@ -84,24 +84,24 @@ public void ReportsWhatOnlyOneSideGenerated()
}
[Test]
- public void LeavesOutGeneratedFilesTheChangeDidNotMove()
+ public async Task LeavesOutGeneratedFilesTheChangeDidNotMove()
{
string baseTree = NewDirectory(), headTree = NewDirectory();
Write(baseTree, "src/Lib/obj/Debug/net10.0/generated/G/E/Same.g.cs", "unchanged\n");
Write(headTree, "src/Lib/obj/Debug/net10.0/generated/G/E/Same.g.cs", "unchanged\n");
- Assert.That(GeneratedSources.DiffAsync(baseTree, headTree).GetAwaiter().GetResult(), Is.Empty,
+ Assert.That(await GeneratedSources.DiffAsync(baseTree, headTree), Is.Empty,
"a generator whose output stands still is not part of the change");
}
[Test]
- public void CarriesWhereEachSideCanBeReadFrom()
+ public async Task CarriesWhereEachSideCanBeReadFrom()
{
string baseTree = NewDirectory(), headTree = NewDirectory();
Write(baseTree, "src/Lib/obj/Debug/net10.0/generated/G/E/Thing.g.cs", "before\n");
Write(headTree, "src/Lib/obj/Debug/net10.0/generated/G/E/Thing.g.cs", "after\n");
- var file = GeneratedSources.DiffAsync(baseTree, headTree).GetAwaiter().GetResult().Single();
+ var file = (await GeneratedSources.DiffAsync(baseTree, headTree)).Single();
// Nothing can read these out of a commit, so the diff has to say where they are.
Assert.That(file.IsGenerated, Is.True);