Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Switch to new lower-overhead spell checking protocol #68426

Merged
merged 14 commits into from
Jun 7, 2023
2 changes: 1 addition & 1 deletion eng/Versions.props
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
<ILAsmPackageVersion>6.0.0-rtm.21518.12</ILAsmPackageVersion>
<ILDAsmPackageVersion>6.0.0-rtm.21518.12</ILDAsmPackageVersion>
<MicrosoftVisualStudioLanguageServerClientPackagesVersion>17.6.26-preview</MicrosoftVisualStudioLanguageServerClientPackagesVersion>
<MicrosoftVisualStudioLanguageServerProtocolPackagesVersion>17.6.22</MicrosoftVisualStudioLanguageServerProtocolPackagesVersion>
<MicrosoftVisualStudioLanguageServerProtocolPackagesVersion>17.7.8-preview-g8c33dc3a76</MicrosoftVisualStudioLanguageServerProtocolPackagesVersion>
<MicrosoftVisualStudioShellPackagesVersion>17.6.35829</MicrosoftVisualStudioShellPackagesVersion>
<RefOnlyMicrosoftBuildPackagesVersion>16.10.0</RefOnlyMicrosoftBuildPackagesVersion>
<!-- The version of Roslyn we build Source Generators against that are built in this
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,15 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Serialization;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.SpellCheck;
using Microsoft.CodeAnalysis.Text;
using Microsoft.CommonLanguageServerProtocol.Framework;
using Microsoft.VisualStudio.LanguageServer.Protocol;
using Roslyn.Utilities;
using LSP = Microsoft.VisualStudio.LanguageServer.Protocol;

namespace Microsoft.CodeAnalysis.LanguageServer.Handler.SpellCheck
{
Expand Down Expand Up @@ -59,7 +57,7 @@ protected AbstractSpellCheckHandler()
/// Creates the <see cref="VSInternalSpellCheckableRangeReport"/> instance we'll report back to clients to let them know our
/// progress. Subclasses can fill in data specific to their needs as appropriate.
/// </summary>
protected abstract TReport CreateReport(TextDocumentIdentifier identifier, VSInternalSpellCheckableRange[]? ranges, string? resultId);
protected abstract TReport CreateReport(TextDocumentIdentifier identifier, int[]? ranges, string? resultId);

public async Task<TReport[]?> HandleRequestAsync(
TParams requestParams, RequestContext context, CancellationToken cancellationToken)
Expand Down Expand Up @@ -107,8 +105,11 @@ protected AbstractSpellCheckHandler()
if (newResultId != null)
{
context.TraceInformation($"Spans were changed for document: {document.FilePath}");
progress.Report(await ComputeAndReportCurrentSpansAsync(
document, languageService, newResultId, cancellationToken).ConfigureAwait(false));
await foreach (var report in ComputeAndReportCurrentSpansAsync(
document, languageService, newResultId, cancellationToken).ConfigureAwait(false))
{
progress.Report(report);
}
}
else
{
Expand Down Expand Up @@ -147,22 +148,62 @@ private static Dictionary<Document, PreviousPullResult> GetDocumentToPreviousPar
return result;
}

private async Task<TReport> ComputeAndReportCurrentSpansAsync(
private async IAsyncEnumerable<TReport> ComputeAndReportCurrentSpansAsync(
Document document,
ISpellCheckSpanService service,
string resultId,
CancellationToken cancellationToken)
[EnumeratorCancellation] CancellationToken cancellationToken)
{
var textDocumentIdentifier = ProtocolConversions.DocumentToTextDocumentIdentifier(document);

var text = await document.GetValueTextAsync(cancellationToken).ConfigureAwait(false);
var spans = await service.GetSpansAsync(document, cancellationToken).ConfigureAwait(false);

using var _ = ArrayBuilder<LSP.VSInternalSpellCheckableRange>.GetInstance(spans.Length, out var result);
// protocol requires the results be in sorted order
spans = spans.Sort(static (s1, s2) => s1.TextSpan.CompareTo(s1.TextSpan));

if (spans.Length == 0)
{
yield return CreateReport(textDocumentIdentifier, Array.Empty<int>(), resultId);
yield break;
}

// break things up into batches of 1000 items. That way we can send smaller messages to the client instead
// of one enormous one.
const int chunkSize = 1000;
var lastSpanEnd = 0;
for (var batchStart = 0; batchStart < spans.Length; batchStart += chunkSize)
{
var batchEnd = Math.Min(batchStart + chunkSize, spans.Length);
var batchSize = batchEnd - batchStart;

// Each span is encoded as a triple of ints. The 'kind', the 'relative start', and the 'length'.
// 'relative start' is the absolute-start for the first span, and then the offset from the end of the
// last span for all others.
var triples = new int[batchSize * 3];
var triplesIndex = 0;
for (var i = batchStart; i < batchEnd; i++)
{
var span = spans[i];

var kind = span.Kind switch
{
SpellCheckKind.Identifier => VSInternalSpellCheckableRangeKind.Identifier,
SpellCheckKind.Comment => VSInternalSpellCheckableRangeKind.Comment,
SpellCheckKind.String => VSInternalSpellCheckableRangeKind.String,
_ => throw ExceptionUtilities.UnexpectedValue(span.Kind),
};

triples[triplesIndex++] = (int)kind;
triples[triplesIndex++] = span.TextSpan.Start - lastSpanEnd;
triples[triplesIndex++] = span.TextSpan.Length;

foreach (var span in spans.Sort((s1, s2) => s1.TextSpan.CompareTo(s1.TextSpan)))
result.Add(ConvertSpan(text, span));
lastSpanEnd = span.TextSpan.End;
}

return CreateReport(ProtocolConversions.DocumentToTextDocumentIdentifier(document), result.ToArray(), resultId);
Contract.ThrowIfTrue(triplesIndex != triples.Length);
yield return CreateReport(textDocumentIdentifier, triples, resultId);
}
}

private void HandleRemovedDocuments(
Expand Down Expand Up @@ -200,22 +241,5 @@ private void HandleRemovedDocuments(

return (parseOptionsChecksum, textChecksum);
}

private static LSP.VSInternalSpellCheckableRange ConvertSpan(SourceText text, SpellCheckSpan spellCheckSpan)
{
var range = ProtocolConversions.TextSpanToRange(spellCheckSpan.TextSpan, text);
return new VSInternalSpellCheckableRange
{
Start = range.Start,
End = range.End,
Kind = spellCheckSpan.Kind switch
{
SpellCheckKind.Identifier => VSInternalSpellCheckableRangeKind.Identifier,
SpellCheckKind.Comment => VSInternalSpellCheckableRangeKind.Comment,
SpellCheckKind.String => VSInternalSpellCheckableRangeKind.String,
_ => throw ExceptionUtilities.UnexpectedValue(spellCheckSpan.Kind),
},
};
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@ internal class DocumentSpellCheckHandler : AbstractSpellCheckHandler<VSInternalD
public override TextDocumentIdentifier GetTextDocumentIdentifier(VSInternalDocumentSpellCheckableParams requestParams)
=> requestParams.TextDocument;

protected override VSInternalSpellCheckableRangeReport CreateReport(TextDocumentIdentifier identifier, VSInternalSpellCheckableRange[]? ranges, string? resultId)
protected override VSInternalSpellCheckableRangeReport CreateReport(TextDocumentIdentifier identifier, int[]? ranges, string? resultId)
=> new()
{
Ranges = ranges!,
Ranges = ranges,
ResultId = resultId,
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,11 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler.SpellCheck
[Method(VSInternalMethods.WorkspaceSpellCheckableRangesName)]
internal class WorkspaceSpellCheckHandler : AbstractSpellCheckHandler<VSInternalWorkspaceSpellCheckableParams, VSInternalWorkspaceSpellCheckableReport>
{
protected override VSInternalWorkspaceSpellCheckableReport CreateReport(TextDocumentIdentifier identifier, VSInternalSpellCheckableRange[]? ranges, string? resultId)
protected override VSInternalWorkspaceSpellCheckableReport CreateReport(TextDocumentIdentifier identifier, int[]? ranges, string? resultId)
=> new()
{
TextDocument = identifier,
Ranges = ranges!,
Ranges = ranges,
ResultId = resultId,
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ class B : A
Assert.Equal(@"public override void M()
{
throw new System.NotImplementedException();
}", results.TextEdit.NewText);
}", results.TextEdit.Value.First.NewText);
}

[Theory, CombinatorialData, WorkItem("https://github.com/dotnet/roslyn/issues/51125")]
Expand Down Expand Up @@ -161,7 +161,7 @@ class B : A
Assert.Equal(@"public override void M()
{
throw new System.NotImplementedException();$0
}", results.TextEdit.NewText);
}", results.TextEdit.Value.First.NewText);
}

[Theory, CombinatorialData, WorkItem("https://github.com/dotnet/roslyn/issues/51125")]
Expand Down
Loading