Skip to content

Commit

Permalink
fix extra whitespace insertion for text edits (#77071)
Browse files Browse the repository at this point in the history
fixes issue where whitespace gets added twice for completion items that
have upfront text edits.
  • Loading branch information
dibarbet authored Feb 6, 2025
2 parents 8a3fb46 + bd44932 commit b5d0226
Show file tree
Hide file tree
Showing 3 changed files with 65 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ internal sealed class CompletionCapabilityHelper
public bool SupportsMarkdownDocumentation { get; }
public ISet<CompletionItemKind> SupportedItemKinds { get; }
public ISet<CompletionItemTag> SupportedItemTags { get; }
public ISet<InsertTextMode> SupportedInsertTextModes { get; }

public CompletionCapabilityHelper(ClientCapabilities clientCapabilities)
: this(supportsVSExtensions: clientCapabilities.HasVisualStudioLspCapability(),
Expand All @@ -45,6 +46,7 @@ public CompletionCapabilityHelper(bool supportsVSExtensions, CompletionSetting?
SupportDefaultCommitCharacters = completionSetting?.CompletionListSetting?.ItemDefaults?.Contains(CommitCharactersPropertyName) == true;
SupportedItemKinds = completionSetting?.CompletionItemKind?.ValueSet?.ToSet() ?? SpecializedCollections.EmptySet<CompletionItemKind>();
SupportedItemTags = completionSetting?.CompletionItem?.TagSupport?.ValueSet?.ToSet() ?? SpecializedCollections.EmptySet<CompletionItemTag>();
SupportedInsertTextModes = completionSetting?.CompletionItem?.InsertTextModeSupport?.ValueSet?.ToSet() ?? SpecializedCollections.EmptySet<InsertTextMode>();

// internal VS LSP
if (supportsVSExtensions)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ internal static class CompletionResultFactory
ItemDefaults = new LSP.CompletionListItemDefaults
{
EditRange = capabilityHelper.SupportDefaultEditRange ? ProtocolConversions.TextSpanToRange(defaultSpan, documentText) : null,
Data = capabilityHelper.SupportCompletionListData ? resolveData : null
Data = capabilityHelper.SupportCompletionListData ? resolveData : null,
},

// VS internal
Expand All @@ -97,6 +97,12 @@ internal static class CompletionResultFactory
Data = capabilityHelper.SupportVSInternalCompletionListData ? resolveData : null,
};

if (capabilityHelper.SupportedInsertTextModes.Contains(LSP.InsertTextMode.AsIs))
{
// By default, all text edits we create include the appropriate whitespace, so tell the client to leave it as-is (if it supports the option).
completionList.ItemDefaults.InsertTextMode = LSP.InsertTextMode.AsIs;
}

PromoteCommonCommitCharactersOntoList();

if (completionList.ItemDefaults is { EditRange: null, CommitCharacters: null, Data: null })
Expand Down
56 changes: 56 additions & 0 deletions src/LanguageServer/ProtocolUnitTests/Completion/CompletionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1471,6 +1471,62 @@ public async Task EditRangeShouldNotEndAtCursorPosition(bool mutatingLspWorkspac
Assert.Equal(new() { Start = new(2, 0), End = new(2, 8) }, results.ItemDefaults.EditRange.Value.First);
}

[Theory, CombinatorialData]
public async Task TestHasInsertTextModeIfSupportedAsync(bool mutatingLspWorkspace)
{
var markup =
@"class A
{
void M()
{
{|caret:|}
}
}";
var capabilities = CreateCoreCompletionCapabilities();
capabilities.TextDocument.Completion.CompletionItem = new LSP.CompletionItemSetting
{
InsertTextModeSupport = new LSP.InsertTextModeSupportSetting { ValueSet = [LSP.InsertTextMode.AsIs] }
};

await using var testLspServer = await CreateTestLspServerAsync(markup, mutatingLspWorkspace, capabilities);
var completionParams = CreateCompletionParams(
testLspServer.GetLocations("caret").Single(),
invokeKind: LSP.VSInternalCompletionInvokeKind.Explicit,
triggerCharacter: "\0",
triggerKind: LSP.CompletionTriggerKind.Invoked);

var results = await RunGetCompletionsAsync(testLspServer, completionParams).ConfigureAwait(false);
Assert.Equal(LSP.InsertTextMode.AsIs, results.ItemDefaults.InsertTextMode);
}

[Theory, CombinatorialData]
public async Task TestDoesNotHaveInsertTextModeIfNotSupportedAsync(bool mutatingLspWorkspace)
{
var markup =
@"class A
{
void M()
{
{|caret:|}
}
}";
var capabilities = CreateCoreCompletionCapabilities();
capabilities.TextDocument.Completion.CompletionItem = new LSP.CompletionItemSetting
{
InsertTextModeSupport = new LSP.InsertTextModeSupportSetting { ValueSet = [] }
};

await using var testLspServer = await CreateTestLspServerAsync(markup, mutatingLspWorkspace, capabilities);
var completionParams = CreateCompletionParams(
testLspServer.GetLocations("caret").Single(),
invokeKind: LSP.VSInternalCompletionInvokeKind.Explicit,
triggerCharacter: "\0",
triggerKind: LSP.CompletionTriggerKind.Invoked);

var results = await RunGetCompletionsAsync(testLspServer, completionParams).ConfigureAwait(false);
Assert.Null(results.ItemDefaults.InsertTextMode);
}

internal static Task<LSP.CompletionList> RunGetCompletionsAsync(TestLspServer testLspServer, LSP.CompletionParams completionParams)
{
return testLspServer.ExecuteRequestAsync<LSP.CompletionParams, LSP.CompletionList>(LSP.Methods.TextDocumentCompletionName,
Expand Down

0 comments on commit b5d0226

Please sign in to comment.