diff --git a/src/OmniSharp.Roslyn.CSharp/Services/Completion/CompletionListBuilder_Async.cs b/src/OmniSharp.Roslyn.CSharp/Services/Completion/CompletionListBuilder_Async.cs index a66cf5d1db..dc80d4ba65 100644 --- a/src/OmniSharp.Roslyn.CSharp/Services/Completion/CompletionListBuilder_Async.cs +++ b/src/OmniSharp.Roslyn.CSharp/Services/Completion/CompletionListBuilder_Async.cs @@ -1,15 +1,16 @@ #nullable enable +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics; +using System.Text; +using System.Threading.Tasks; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Completion; using Microsoft.CodeAnalysis.ExternalAccess.OmniSharp.Completion; using Microsoft.CodeAnalysis.Text; using OmniSharp.Models; using OmniSharp.Models.v1.Completion; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Diagnostics; -using System.Threading.Tasks; using CompletionItem = OmniSharp.Models.v1.Completion.CompletionItem; using CSharpCompletionList = Microsoft.CodeAnalysis.Completion.CompletionList; using CSharpCompletionService = Microsoft.CodeAnalysis.Completion.CompletionService; @@ -95,7 +96,15 @@ await completionService.GetChangeAsync(document, completion), out insertText, out filterText, out sortText, out insertTextFormat, out changeSpan, out additionalTextEdits); } - var commitCharacters = BuildCommitCharacters(completion.Rules.CommitCharacterRules, isSuggestionMode, commitCharacterRuleCache, commitCharacterRuleBuilder); + var treatAsASuggestion = isSuggestionMode || + ( + // The user hasn't actually typed anything and completion provider does + // not request the item be hard-selected. + completion.Rules.MatchPriority != MatchPriority.Preselect && + typedSpan.Length == 0 && + completion.Rules.SelectionBehavior != CompletionItemSelectionBehavior.HardSelection + ); + var commitCharacters = BuildCommitCharacters(completion.Rules.CommitCharacterRules, treatAsASuggestion, commitCharacterRuleCache, commitCharacterRuleBuilder); completionsBuilder.Add(new CompletionItem { @@ -108,7 +117,7 @@ await completionService.GetChangeAsync(document, completion), Kind = GetCompletionItemKind(completion.Tags), Detail = completion.InlineDescription, Data = (cacheId, i), - Preselect = completion.Rules.SelectionBehavior == CompletionItemSelectionBehavior.HardSelection, + Preselect = completion.Rules.MatchPriority == MatchPriority.Preselect, CommitCharacters = commitCharacters, HasAfterInsertStep = hasAfterInsertStep, }); diff --git a/src/OmniSharp.Roslyn.CSharp/Services/Completion/CompletionListBuilder_Sync.cs b/src/OmniSharp.Roslyn.CSharp/Services/Completion/CompletionListBuilder_Sync.cs index d6bbe0e777..9d64fa2650 100644 --- a/src/OmniSharp.Roslyn.CSharp/Services/Completion/CompletionListBuilder_Sync.cs +++ b/src/OmniSharp.Roslyn.CSharp/Services/Completion/CompletionListBuilder_Sync.cs @@ -1,8 +1,13 @@ #nullable enable +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics; +using System.Linq; +using System.Threading.Tasks; using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.ExternalAccess.OmniSharp.Completion; using Microsoft.CodeAnalysis.Completion; +using Microsoft.CodeAnalysis.ExternalAccess.OmniSharp.Completion; using Microsoft.CodeAnalysis.Text; using OmniSharp.Extensions; using OmniSharp.Models; @@ -10,11 +15,6 @@ using OmniSharp.Roslyn.CSharp.Helpers; using OmniSharp.Roslyn.Utilities; using OmniSharp.Utilities; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Diagnostics; -using System.Linq; -using System.Threading.Tasks; using CompletionItem = OmniSharp.Models.v1.Completion.CompletionItem; using CSharpCompletionItem = Microsoft.CodeAnalysis.Completion.CompletionItem; using CSharpCompletionList = Microsoft.CodeAnalysis.Completion.CompletionList; @@ -110,7 +110,15 @@ internal static partial class CompletionListBuilder } } - var commitCharacters = BuildCommitCharacters(completion.Rules.CommitCharacterRules, isSuggestionMode, commitCharacterRuleCache, commitCharacterRuleBuilder); + var treatAsASuggestion = isSuggestionMode || + ( + // The user hasn't actually typed anything and completion provider does + // not request the item be hard-selected. + completion.Rules.MatchPriority != MatchPriority.Preselect && + typedSpan.Length == 0 && + completion.Rules.SelectionBehavior != CompletionItemSelectionBehavior.HardSelection + ); + var commitCharacters = BuildCommitCharacters(completion.Rules.CommitCharacterRules, treatAsASuggestion, commitCharacterRuleCache, commitCharacterRuleBuilder); completionsBuilder.Add(new CompletionItem { @@ -123,7 +131,7 @@ internal static partial class CompletionListBuilder Kind = GetCompletionItemKind(completion.Tags), Detail = completion.InlineDescription, Data = (cacheId, i), - Preselect = completion.Rules.SelectionBehavior == CompletionItemSelectionBehavior.HardSelection, + Preselect = completion.Rules.MatchPriority == MatchPriority.Preselect, CommitCharacters = commitCharacters, }); } diff --git a/src/OmniSharp.Roslyn.CSharp/Services/Completion/CompletionService.cs b/src/OmniSharp.Roslyn.CSharp/Services/Completion/CompletionService.cs index 5ff3d57078..e4b1e323a3 100644 --- a/src/OmniSharp.Roslyn.CSharp/Services/Completion/CompletionService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Services/Completion/CompletionService.cs @@ -8,8 +8,8 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.ExternalAccess.OmniSharp.Completion; using Microsoft.CodeAnalysis.Completion; +using Microsoft.CodeAnalysis.ExternalAccess.OmniSharp.Completion; using Microsoft.CodeAnalysis.Text; using Microsoft.Extensions.Logging; using OmniSharp.Extensions; diff --git a/tests/OmniSharp.Roslyn.CSharp.Tests/CompletionFacts.cs b/tests/OmniSharp.Roslyn.CSharp.Tests/CompletionFacts.cs index 5c19d059dc..021cf68659 100644 --- a/tests/OmniSharp.Roslyn.CSharp.Tests/CompletionFacts.cs +++ b/tests/OmniSharp.Roslyn.CSharp.Tests/CompletionFacts.cs @@ -2268,6 +2268,34 @@ public async Task ReplacesUpUntilCursorInMiddleOfWord(string filename) }); } + [Theory] + [InlineData("dummy.cs", true)] + [InlineData("dummy.cs", false)] + [InlineData("dummy.csx", true)] + [InlineData("dummy.csx", false)] + public async Task SoftSelectionWhenFilterTextIsEmpty(string filename, bool useAsyncCompletion) + { + const string input = @" +using System; +using System.Text; +public class A +{ + public void M(string someText) + { + var x = new StringBuilder(); + x.Append($$ + } +}"; + + using var host = useAsyncCompletion ? GetAsyncCompletionAndImportCompletionHost() : GetImportCompletionHost(); + + var completions = await FindCompletionsAsync(filename, input, host, '('); + var someTextItem = completions.Items.First(item => item.Label == "someText"); + + Assert.Null(someTextItem.CommitCharacters); + Assert.False(someTextItem.Preselect); + } + private CompletionService GetCompletionService(OmniSharpTestHost host) => host.GetRequestHandler(EndpointName);