Skip to content

Commit b5d0226

Browse files
authored
fix extra whitespace insertion for text edits (#77071)
fixes issue where whitespace gets added twice for completion items that have upfront text edits.
2 parents 8a3fb46 + bd44932 commit b5d0226

File tree

3 files changed

+65
-1
lines changed

3 files changed

+65
-1
lines changed

src/LanguageServer/Protocol/Handler/Completion/CompletionCapabilityHelper.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ internal sealed class CompletionCapabilityHelper
2828
public bool SupportsMarkdownDocumentation { get; }
2929
public ISet<CompletionItemKind> SupportedItemKinds { get; }
3030
public ISet<CompletionItemTag> SupportedItemTags { get; }
31+
public ISet<InsertTextMode> SupportedInsertTextModes { get; }
3132

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

4951
// internal VS LSP
5052
if (supportsVSExtensions)

src/LanguageServer/Protocol/Handler/Completion/CompletionResultFactory.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ internal static class CompletionResultFactory
8686
ItemDefaults = new LSP.CompletionListItemDefaults
8787
{
8888
EditRange = capabilityHelper.SupportDefaultEditRange ? ProtocolConversions.TextSpanToRange(defaultSpan, documentText) : null,
89-
Data = capabilityHelper.SupportCompletionListData ? resolveData : null
89+
Data = capabilityHelper.SupportCompletionListData ? resolveData : null,
9090
},
9191

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

100+
if (capabilityHelper.SupportedInsertTextModes.Contains(LSP.InsertTextMode.AsIs))
101+
{
102+
// 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).
103+
completionList.ItemDefaults.InsertTextMode = LSP.InsertTextMode.AsIs;
104+
}
105+
100106
PromoteCommonCommitCharactersOntoList();
101107

102108
if (completionList.ItemDefaults is { EditRange: null, CommitCharacters: null, Data: null })

src/LanguageServer/ProtocolUnitTests/Completion/CompletionTests.cs

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1471,6 +1471,62 @@ public async Task EditRangeShouldNotEndAtCursorPosition(bool mutatingLspWorkspac
14711471
Assert.Equal(new() { Start = new(2, 0), End = new(2, 8) }, results.ItemDefaults.EditRange.Value.First);
14721472
}
14731473

1474+
[Theory, CombinatorialData]
1475+
public async Task TestHasInsertTextModeIfSupportedAsync(bool mutatingLspWorkspace)
1476+
{
1477+
var markup =
1478+
@"class A
1479+
{
1480+
void M()
1481+
{
1482+
{|caret:|}
1483+
}
1484+
}";
1485+
var capabilities = CreateCoreCompletionCapabilities();
1486+
capabilities.TextDocument.Completion.CompletionItem = new LSP.CompletionItemSetting
1487+
{
1488+
InsertTextModeSupport = new LSP.InsertTextModeSupportSetting { ValueSet = [LSP.InsertTextMode.AsIs] }
1489+
};
1490+
1491+
await using var testLspServer = await CreateTestLspServerAsync(markup, mutatingLspWorkspace, capabilities);
1492+
var completionParams = CreateCompletionParams(
1493+
testLspServer.GetLocations("caret").Single(),
1494+
invokeKind: LSP.VSInternalCompletionInvokeKind.Explicit,
1495+
triggerCharacter: "\0",
1496+
triggerKind: LSP.CompletionTriggerKind.Invoked);
1497+
1498+
var results = await RunGetCompletionsAsync(testLspServer, completionParams).ConfigureAwait(false);
1499+
Assert.Equal(LSP.InsertTextMode.AsIs, results.ItemDefaults.InsertTextMode);
1500+
}
1501+
1502+
[Theory, CombinatorialData]
1503+
public async Task TestDoesNotHaveInsertTextModeIfNotSupportedAsync(bool mutatingLspWorkspace)
1504+
{
1505+
var markup =
1506+
@"class A
1507+
{
1508+
void M()
1509+
{
1510+
{|caret:|}
1511+
}
1512+
}";
1513+
var capabilities = CreateCoreCompletionCapabilities();
1514+
capabilities.TextDocument.Completion.CompletionItem = new LSP.CompletionItemSetting
1515+
{
1516+
InsertTextModeSupport = new LSP.InsertTextModeSupportSetting { ValueSet = [] }
1517+
};
1518+
1519+
await using var testLspServer = await CreateTestLspServerAsync(markup, mutatingLspWorkspace, capabilities);
1520+
var completionParams = CreateCompletionParams(
1521+
testLspServer.GetLocations("caret").Single(),
1522+
invokeKind: LSP.VSInternalCompletionInvokeKind.Explicit,
1523+
triggerCharacter: "\0",
1524+
triggerKind: LSP.CompletionTriggerKind.Invoked);
1525+
1526+
var results = await RunGetCompletionsAsync(testLspServer, completionParams).ConfigureAwait(false);
1527+
Assert.Null(results.ItemDefaults.InsertTextMode);
1528+
}
1529+
14741530
internal static Task<LSP.CompletionList> RunGetCompletionsAsync(TestLspServer testLspServer, LSP.CompletionParams completionParams)
14751531
{
14761532
return testLspServer.ExecuteRequestAsync<LSP.CompletionParams, LSP.CompletionList>(LSP.Methods.TextDocumentCompletionName,

0 commit comments

Comments
 (0)