Skip to content

Commit

Permalink
Emulate suggestion mode in LSP completion by always soft-select
Browse files Browse the repository at this point in the history
  • Loading branch information
genlu committed Aug 2, 2023
1 parent 5024f3e commit 12cc813
Show file tree
Hide file tree
Showing 2 changed files with 55 additions and 12 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,15 @@ internal abstract class AbstractLspCompletionResultCreationService : ILspComplet
CompletionList list, bool isIncomplete, long resultId,
CancellationToken cancellationToken)
{
var isSuggestionMode = list.SuggestionModeItem is not null;
if (list.ItemsList.Count == 0)
{
return new LSP.VSInternalCompletionList
{
Items = Array.Empty<LSP.CompletionItem>(),
// If we have a suggestion mode item, we just need to keep the list in suggestion mode.
// We don't need to return the fake suggestion mode item.
SuggestionMode = list.SuggestionModeItem is not null,
SuggestionMode = isSuggestionMode,
IsIncomplete = isIncomplete,
};
}
Expand Down Expand Up @@ -132,18 +133,24 @@ internal abstract class AbstractLspCompletionResultCreationService : ILspComplet
lspItem.Kind = GetCompletionKind(item.Tags, capabilityHelper.SupportedItemKinds);
lspItem.Preselect = item.Rules.MatchPriority == MatchPriority.Preselect;

if (!lspItem.Preselect &&
!lspVSClientCapability &&
typedText.Length == 0 &&
item.Rules.SelectionBehavior != CompletionItemSelectionBehavior.HardSelection)
if (lspVSClientCapability)
{
// VSCode does not have the concept of soft selection, the list is always hard selected.
// In order to emulate soft selection behavior for things like argument completion, regex completion,
// datetime completion, etc. we create a completion item without any specific commit characters.
// This means only tab / enter will commit. VS supports soft selection, so we only do this for non-VS clients.
//
// Note this only applies when user hasn't actually typed anything and completion provider does not request the item
// to be hard-selected. Otherwise, we set its commit characters as normal. This also means we'd need to set IsIncomplete to true
lspItem.CommitCharacters = GetCommitCharacters(item, commitCharactersRuleCache);
return lspItem;
}

// VSCode does not have the concept of soft selection, the list is always hard selected.
// In order to emulate soft selection behavior for things like suggestion mode, argument completion, regex completion,
// datetime completion, etc. we create a completion item without any specific commit characters.
// This means only tab / enter will commit. VS supports soft selection, so we only do this for non-VS clients.
if (isSuggestionMode)
{
lspItem.CommitCharacters = Array.Empty<string>();
}
else if (!lspItem.Preselect && typedText.Length == 0 && item.Rules.SelectionBehavior != CompletionItemSelectionBehavior.HardSelection)
{
// Note this also applies when user hasn't actually typed anything and completion provider does not request the item
// to be hard-selected. Otherwise, we set its commit characters as normal. This means we'd need to set IsIncomplete to true
// to make sure the client will ask us again when user starts typing so we can provide items with proper commit characters.
lspItem.CommitCharacters = Array.Empty<string>();
isIncomplete = true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
using Microsoft.CodeAnalysis.Text;
using Microsoft.VisualStudio.LanguageServer.Protocol;
using Roslyn.Test.Utilities;
using Roslyn.Utilities;
using Xunit;
using Xunit.Abstractions;
using LSP = Microsoft.VisualStudio.LanguageServer.Protocol;
Expand Down Expand Up @@ -555,4 +556,39 @@ void M()
var expectedAdditionalEdit2 = new TextEdit() { NewText = "using Namespace2;\r\n\r\n", Range = new() { Start = new(1, 0), End = new(1, 0) } };
AssertJsonEquals(new[] { expectedAdditionalEdit2 }, resolvedItem2.AdditionalTextEdits);
}

[Theory, CombinatorialData, WorkItem("https://github.com/dotnet/vscode-csharp/issues/5732")]
public async Task TestEmptyCommitCharsInSuggestionMode(bool mutatingLspWorkspace)
{
var markup =
@"
using System.Collections.Generic;
using System.Linq;
public class C
{
public Foo(List<int> myList)
{
var foo = myList.Where(i{|caret:|})
}
}";
await using var testLspServer = await CreateTestLspServerAsync(markup, mutatingLspWorkspace, DefaultClientCapabilities);
var caret = testLspServer.GetLocations("caret").Single();
var completionParams = new LSP.CompletionParams()
{
TextDocument = CreateTextDocumentIdentifier(caret.Uri),
Position = caret.Range.Start,
Context = new LSP.CompletionContext()
{
TriggerKind = LSP.CompletionTriggerKind.Invoked,
}
};

var document = testLspServer.GetCurrentSolution().Projects.First().Documents.First();

var results = await testLspServer.ExecuteRequestAsync<LSP.CompletionParams, LSP.CompletionList>(LSP.Methods.TextDocumentCompletionName, completionParams, CancellationToken.None);
AssertEx.NotNull(results);
Assert.NotEmpty(results.Items);
Assert.Empty(results.ItemDefaults.CommitCharacters);
Assert.True(results.Items.All(item => item.CommitCharacters is null));
}
}

0 comments on commit 12cc813

Please sign in to comment.