From bd449326d80a626b8e102edee2e63f98d7600eef Mon Sep 17 00:00:00 2001 From: David Barbet Date: Wed, 5 Feb 2025 15:11:36 -0800 Subject: [PATCH] fix extra whitespace insertion for text edits --- .../Completion/CompletionCapabilityHelper.cs | 2 + .../Completion/CompletionResultFactory.cs | 8 ++- .../Completion/CompletionTests.cs | 56 +++++++++++++++++++ 3 files changed, 65 insertions(+), 1 deletion(-) diff --git a/src/LanguageServer/Protocol/Handler/Completion/CompletionCapabilityHelper.cs b/src/LanguageServer/Protocol/Handler/Completion/CompletionCapabilityHelper.cs index 6ed2dfe1ef6f1..aefec686e77a4 100644 --- a/src/LanguageServer/Protocol/Handler/Completion/CompletionCapabilityHelper.cs +++ b/src/LanguageServer/Protocol/Handler/Completion/CompletionCapabilityHelper.cs @@ -28,6 +28,7 @@ internal sealed class CompletionCapabilityHelper public bool SupportsMarkdownDocumentation { get; } public ISet SupportedItemKinds { get; } public ISet SupportedItemTags { get; } + public ISet SupportedInsertTextModes { get; } public CompletionCapabilityHelper(ClientCapabilities clientCapabilities) : this(supportsVSExtensions: clientCapabilities.HasVisualStudioLspCapability(), @@ -45,6 +46,7 @@ public CompletionCapabilityHelper(bool supportsVSExtensions, CompletionSetting? SupportDefaultCommitCharacters = completionSetting?.CompletionListSetting?.ItemDefaults?.Contains(CommitCharactersPropertyName) == true; SupportedItemKinds = completionSetting?.CompletionItemKind?.ValueSet?.ToSet() ?? SpecializedCollections.EmptySet(); SupportedItemTags = completionSetting?.CompletionItem?.TagSupport?.ValueSet?.ToSet() ?? SpecializedCollections.EmptySet(); + SupportedInsertTextModes = completionSetting?.CompletionItem?.InsertTextModeSupport?.ValueSet?.ToSet() ?? SpecializedCollections.EmptySet(); // internal VS LSP if (supportsVSExtensions) diff --git a/src/LanguageServer/Protocol/Handler/Completion/CompletionResultFactory.cs b/src/LanguageServer/Protocol/Handler/Completion/CompletionResultFactory.cs index ae0bf6c7d66df..9fa30b7023723 100644 --- a/src/LanguageServer/Protocol/Handler/Completion/CompletionResultFactory.cs +++ b/src/LanguageServer/Protocol/Handler/Completion/CompletionResultFactory.cs @@ -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 @@ -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 }) diff --git a/src/LanguageServer/ProtocolUnitTests/Completion/CompletionTests.cs b/src/LanguageServer/ProtocolUnitTests/Completion/CompletionTests.cs index bf88d490792f3..ad00091a58b60 100644 --- a/src/LanguageServer/ProtocolUnitTests/Completion/CompletionTests.cs +++ b/src/LanguageServer/ProtocolUnitTests/Completion/CompletionTests.cs @@ -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 RunGetCompletionsAsync(TestLspServer testLspServer, LSP.CompletionParams completionParams) { return testLspServer.ExecuteRequestAsync(LSP.Methods.TextDocumentCompletionName,