Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT license. See License.txt in the project root for license information.

using System;
using System.Text.Json.Serialization;

namespace Roslyn.LanguageServer.Protocol;

/// <summary>
/// A replacement of the LSP protocol <see cref="CompletionParams"/> that ensures correct serialization between LSP servers.
/// </summary>
/// <remarks>
/// This is the same as the LSP protocol <see cref="CompletionParams"/> except that it strongly types the <see cref="Context"/> property as VSInternalCompletionContext,
/// because our custom message target gets handled by a JsonRpc connection set up by the editor, that has no Roslyn converters.
/// Without using this class, we lose VSInternalCompletionContext.InvokeKind property when calling Roslyn or HTML servers from Razor
/// which results in default value of "Invoked" to be used and can cause overly aggressive completion list.
///
/// See original CompletionParams here https://github.com/dotnet/roslyn/blob/98d41b80f6a192230c045a6576e2a283a407980b/src/LanguageServer/Protocol/Protocol/CompletionParams.cs
/// </remarks>
internal sealed class RazorVSInternalCompletionParams : TextDocumentPositionParams, IPartialResultParams<SumType<CompletionItem[], CompletionList>?>, IWorkDoneProgressOptions
{
public RazorVSInternalCompletionParams()
{
}

/// <summary>
/// The completion context. This is only available if the client specifies the
/// client capability <see cref="CompletionSetting.ContextSupport"/>.
/// </summary>
[JsonPropertyName("context")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public VSInternalCompletionContext? Context { get; set; }

/// <inheritdoc/>
[JsonPropertyName(Methods.PartialResultTokenName)]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public IProgress<SumType<CompletionItem[], CompletionList>?>? PartialResultToken { get; set; }

/// <inheritdoc/>
[JsonPropertyName("workDoneProgress")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
public bool WorkDoneProgress { get; init; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ internal partial class RazorCustomMessageTarget
return null;
}

var completionParams = new CompletionParams()
var completionParams = new RazorVSInternalCompletionParams()
{
Context = request.Context,
Position = request.ProjectedPosition,
Expand Down Expand Up @@ -134,7 +134,7 @@ internal partial class RazorCustomMessageTarget
ReinvocationResponse<RazorVSInternalCompletionList?>? response;
using (_telemetryReporter.TrackLspRequest(lspMethodName, languageServerName, TelemetryThresholds.CompletionSubLSPTelemetryThreshold, request.CorrelationId))
{
response = await _requestInvoker.ReinvokeRequestOnServerAsync<CompletionParams, RazorVSInternalCompletionList?>(
response = await _requestInvoker.ReinvokeRequestOnServerAsync<RazorVSInternalCompletionParams, RazorVSInternalCompletionList?>(
textBuffer,
lspMethodName,
languageServerName,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,50 @@ await VerifyTypeAndCommitCompletionAsync(
expectedSelectedItemLabel: "@onactivate");
}

// Regression test for https://devdiv.visualstudio.com/DevDiv/_workitems/edit/2505611
[IdeFact]
public async Task CompletionCommit_NoCommitOnTypingInDocComments()
{
await TestServices.SolutionExplorer.AddFileAsync(
RazorProjectConstants.BlazorProjectName,
"Test.razor",
"""
@page "/test"

<PageTitle>Test</PageTitle>

@{
///
}
""",
open: true,
ControlledHangMitigatingCancellationToken);

var textView = await TestServices.Editor.GetActiveTextViewAsync(HangMitigatingCancellationToken);
await TestServices.Editor.WaitForComponentClassificationAsync(ControlledHangMitigatingCancellationToken);

await TestServices.Editor.PlaceCaretAsync("/// ", charsOffset: 1, ControlledHangMitigatingCancellationToken);
TestServices.Input.Send("add a function");

// Make sure extra text didn't get commited from an unexpected completion list
var currentLineText = await TestServices.Editor.GetCurrentLineTextAsync(HangMitigatingCancellationToken);
Assert.Contains("/// add a function", currentLineText);

// Make sure completion doesn't come up for 15 seconds
var completionSession = await TestServices.Editor.WaitForCompletionSessionAsync(s_snippetTimeout, HangMitigatingCancellationToken);
var items = completionSession?.GetComputedItems(HangMitigatingCancellationToken);

if (items is null)
{
// No items to check, we're good
return;
}

// If completion did pop up with something like "Processing", make sure no doccomment items are present
Assert.DoesNotContain("summary", items.Items.Select(i => i.DisplayText));

}

[IdeFact]
public async Task CompletionCommit_HtmlTag()
{
Expand Down
Loading