提交 919c469d 编写于 作者: R Ravi Chande

Add a completion provider that suggests tuple names

Fixes #16565
上级 774d1975
......@@ -196,6 +196,7 @@
<Compile Include="CodeActions\ConvertNumericLiteral\ConvertNumericLiteralTests.cs" />
<Compile Include="CodeActions\UseNamedArguments\UseNamedArgumentsTests.cs" />
<Compile Include="Completion\CompletionProviders\OverrideCompletionProviderTests_ExpressionBody.cs" />
<Compile Include="Completion\CompletionProviders\TupleNameCompletionProviderTests.cs" />
<Compile Include="ConvertToInterpolatedString\ConvertConcatenationToInterpolatedStringTests.cs" />
<Compile Include="ConvertToInterpolatedString\ConvertPlaceholderToInterpolatedStringTests.cs" />
<Compile Include="CodeActions\EncapsulateField\EncapsulateFieldTests.cs" />
......
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Completion;
using Microsoft.CodeAnalysis.CSharp.Completion.Providers;
using Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Completion.CompletionProviders;
using Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces;
using Roslyn.Test.Utilities;
using Xunit;
namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Completion.CompletionSetSources
{
public class TupleNameCompletionProviderTests : AbstractCSharpCompletionProviderTests
{
public TupleNameCompletionProviderTests(CSharpTestWorkspaceFixture workspaceFixture) : base(workspaceFixture)
{
}
internal override CompletionProvider CreateCompletionProvider() => new TupleNameCompletionProvider();
[Fact, Trait(Traits.Feature, Traits.Features.Completion)]
public async Task AfterOpenParen()
{
await VerifyItemExistsAsync(@"
namespace ConsoleApp36
{
class Program
{
static void Main(string[] args)
{
(int word, int zword) t = ($$
}
}
}", "word:");
}
[Fact, Trait(Traits.Feature, Traits.Features.Completion)]
public async Task AfterOpenParenWithBraceCompletion()
{
await VerifyItemExistsAsync(@"
namespace ConsoleApp36
{
class Program
{
static void Main(string[] args)
{
(int word, int zword) t = ($$)
}
}
}", "word:");
}
[Fact, Trait(Traits.Feature, Traits.Features.Completion)]
public async Task AfterOpenParenInTupleExpression()
{
await VerifyItemExistsAsync(@"
namespace ConsoleApp36
{
class Program
{
static void Main(string[] args)
{
(int word, int zword) t = ($$, zword: 2
}
}
}", "word:");
}
[Fact, Trait(Traits.Feature, Traits.Features.Completion)]
public async Task AfterOpenParenInTupleExpressionWithBraceCompletion()
{
await VerifyItemExistsAsync(@"
namespace ConsoleApp36
{
class Program
{
static void Main(string[] args)
{
(int word, int zword) t = ($$, zword: 2
}
}
}", "word:");
}
[Fact, Trait(Traits.Feature, Traits.Features.Completion)]
public async Task AfterComma()
{
await VerifyItemExistsAsync(@"
namespace ConsoleApp36
{
class Program
{
static void Main(string[] args)
{
(int word, int zword) t = (1, $$
}
}
}", "zword:");
}
[Fact, Trait(Traits.Feature, Traits.Features.Completion)]
public async Task AfterCommaWithBraceCompletion()
{
await VerifyItemExistsAsync(@"
namespace ConsoleApp36
{
class Program
{
static void Main(string[] args)
{
(int word, int zword) t = (1, $$)
}
}
}", "zword:");
}
[Fact, Trait(Traits.Feature, Traits.Features.Completion)]
public async Task InTupleAsArgument()
{
await VerifyItemExistsAsync(@"
namespace ConsoleApp36
{
class Program
{
static void Main((int word, int zword) args)
{
Main(($$))
}
}
}", "word:");
}
[Fact, Trait(Traits.Feature, Traits.Features.Completion)]
public async Task MultiplePossibleTuples()
{
var markup = @"
namespace ConsoleApp36
{
class Program
{
static void Main((int number, int znumber) args) { }
static void Main((string word, int zword) args) {
Main(($$
}
}
}";
await VerifyItemExistsAsync(markup, "word:");
await VerifyItemExistsAsync(markup, "number:");
}
[Fact, Trait(Traits.Feature, Traits.Features.Completion)]
public async Task MultiplePossibleTuplesAfterComma()
{
var markup = @"
namespace ConsoleApp36
{
class Program
{
static void Main((int number, int znumber) args) { }
static void Main((string word, int zword) args) {
Main(($$
}
}
}";
await VerifyItemExistsAsync(markup, "zword:");
await VerifyItemExistsAsync(markup, "znumber:");
}
[Fact, Trait(Traits.Feature, Traits.Features.Completion)]
public async Task AtIndexGreaterThanNumberOfTupleElements()
{
var markup = @"
class Program
{
static void Main(string[] args)
{
(int word, int zword) t = (1, 2, 3, 4, $$
}
}";
await VerifyNoItemsExistAsync(markup);
}
}
}
......@@ -66,6 +66,7 @@
<Compile Include="ConvertIfToSwitch\CSharpConvertIfToSwitchCodeRefactoringProvider.Pattern.cs" />
<Compile Include="ConvertNumericLiteral\CSharpConvertNumericLiteralCodeRefactoringProvider.cs" />
<Compile Include="CodeRefactorings\UseNamedArguments\CSharpUseNamedArgumentsCodeRefactoringProvider.cs" />
<Compile Include="Completion\CompletionProviders\TupleNameCompletionProvider.cs" />
<Compile Include="CSharpFeaturesResources.Designer.cs">
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
......
......@@ -41,7 +41,8 @@ internal class CSharpCompletionService : CommonCompletionService
new OverrideCompletionProvider(),
new PartialMethodCompletionProvider(),
new PartialTypeCompletionProvider(),
new XmlDocCommentCompletionProvider()
new XmlDocCommentCompletionProvider(),
new TupleNameCompletionProvider()
);
private readonly Workspace _workspace;
......
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Completion;
using Microsoft.CodeAnalysis.Completion.Providers;
using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.LanguageServices;
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Shared.Extensions.ContextQuery;
using Microsoft.CodeAnalysis.SignatureHelp;
namespace Microsoft.CodeAnalysis.CSharp.Completion.Providers
{
internal class TupleNameCompletionProvider : CommonCompletionProvider
{
private static readonly CompletionItemRules _cachedRules = CompletionItemRules.Default
.WithCommitCharacterRule(CharacterSetModificationRule.Create(CharacterSetModificationKind.Remove, ':'));
public override async Task ProvideCompletionsAsync(CompletionContext completionContext)
{
var document = completionContext.Document;
var position = completionContext.Position;
var cancellationToken = completionContext.CancellationToken;
var semanticModel = await document.GetSemanticModelForSpanAsync(new Text.TextSpan(position, 0), cancellationToken).ConfigureAwait(false);
var workspace = document.Project.Solution.Workspace;
var context = CSharpSyntaxContext.CreateContext(workspace, semanticModel, position, cancellationToken);
var index = GetElementIndex(context);
if (index == null)
{
return;
}
var typeInferrer = document.GetLanguageService<ITypeInferenceService>();
var inferredTypes = typeInferrer.InferTypes(semanticModel, context.TargetToken.Parent.SpanStart, cancellationToken)
.Where(t => t.IsTupleType)
.Cast<INamedTypeSymbol>()
.ToImmutableArray();
AddItems(inferredTypes, index.Value, completionContext);
}
private int? GetElementIndex(CSharpSyntaxContext context)
{
var token = context.TargetToken;
if (token.IsPossibleTupleOpenParenOrComma())
{
if (token.IsKind(SyntaxKind.OpenParenToken))
{
return 0;
}
var tupleExpr = (TupleExpressionSyntax)context.TargetToken.Parent;
return (tupleExpr.Arguments.GetWithSeparators().IndexOf(context.TargetToken) + 1) / 2;
}
return null;
}
private void AddItems(ImmutableArray<INamedTypeSymbol> inferredTypes, int index, CompletionContext context)
{
foreach (var type in inferredTypes)
{
if (index > type.TupleElements.Length)
{
index = type.TupleElements.Length - 1;
}
var field = type.TupleElements[index];
var item = CommonCompletionItem.Create(
field.Name + ":",
Glyph.FieldPublic,
rules: _cachedRules);
context.AddItem(item);
}
}
}
}
......@@ -687,5 +687,35 @@ public static bool IsMandatoryNamedParameterPosition(this SyntaxToken token)
return false;
}
public static bool IsPossibleTupleOpenParenOrComma(this SyntaxToken possibleCommaOrParen)
{
if (!possibleCommaOrParen.IsKind(SyntaxKind.OpenParenToken, SyntaxKind.CommaToken))
{
return false;
}
if (possibleCommaOrParen.Parent.IsKind(
SyntaxKind.ParenthesizedExpression,
SyntaxKind.TupleExpression,
SyntaxKind.TupleType,
SyntaxKind.CastExpression))
{
return true;
}
// in script
if (possibleCommaOrParen.Parent.IsKind(SyntaxKind.ParameterList) &&
possibleCommaOrParen.Parent.IsParentKind(SyntaxKind.ParenthesizedLambdaExpression))
{
var parenthesizedLambda = (ParenthesizedLambdaExpressionSyntax)possibleCommaOrParen.Parent.Parent;
if (parenthesizedLambda.ArrowToken.IsMissing)
{
return true;
}
}
return false;
}
}
}
......@@ -1189,7 +1189,7 @@ public static bool IsPossibleTupleContext(this SyntaxTree syntaxTree, SyntaxToke
// ($$
// (a, $$
if (IsPossibleTupleOpenParenOrComma(leftToken))
if (leftToken.IsPossibleTupleOpenParenOrComma())
{
return true;
}
......@@ -1204,7 +1204,7 @@ public static bool IsPossibleTupleContext(this SyntaxTree syntaxTree, SyntaxToke
SyntaxKind.TupleType))
{
var possibleCommaOrParen = FindTokenOnLeftOfNode(leftToken.Parent);
if (IsPossibleTupleOpenParenOrComma(possibleCommaOrParen))
if (possibleCommaOrParen.IsPossibleTupleOpenParenOrComma())
{
return true;
}
......@@ -1216,7 +1216,7 @@ public static bool IsPossibleTupleContext(this SyntaxTree syntaxTree, SyntaxToke
if (leftToken.IsKind(SyntaxKind.IdentifierToken))
{
var possibleCommaOrParen = FindTokenOnLeftOfNode(leftToken.Parent);
if (IsPossibleTupleOpenParenOrComma(possibleCommaOrParen))
if (possibleCommaOrParen.IsPossibleTupleOpenParenOrComma())
{
return true;
}
......@@ -1229,7 +1229,7 @@ public static bool IsPossibleTupleContext(this SyntaxTree syntaxTree, SyntaxToke
leftToken.Parent.IsParentKind(SyntaxKind.QualifiedName, SyntaxKind.SimpleMemberAccessExpression))
{
var possibleCommaOrParen = FindTokenOnLeftOfNode(leftToken.Parent.Parent);
if (IsPossibleTupleOpenParenOrComma(possibleCommaOrParen))
if (possibleCommaOrParen.IsPossibleTupleOpenParenOrComma())
{
return true;
}
......@@ -1243,36 +1243,6 @@ private static SyntaxToken FindTokenOnLeftOfNode(SyntaxNode node)
return node.FindTokenOnLeftOfPosition(node.SpanStart);
}
private static bool IsPossibleTupleOpenParenOrComma(SyntaxToken possibleCommaOrParen)
{
if (!possibleCommaOrParen.IsKind(SyntaxKind.OpenParenToken, SyntaxKind.CommaToken))
{
return false;
}
if (possibleCommaOrParen.Parent.IsKind(
SyntaxKind.ParenthesizedExpression,
SyntaxKind.TupleExpression,
SyntaxKind.TupleType,
SyntaxKind.CastExpression))
{
return true;
}
// in script
if (possibleCommaOrParen.Parent.IsKind(SyntaxKind.ParameterList) &&
possibleCommaOrParen.Parent.IsParentKind(SyntaxKind.ParenthesizedLambdaExpression))
{
var parenthesizedLambda = (ParenthesizedLambdaExpressionSyntax)possibleCommaOrParen.Parent.Parent;
if (parenthesizedLambda.ArrowToken.IsMissing)
{
return true;
}
}
return false;
}
/// <summary>
/// Are you possibly in the designation part of a deconstruction?
/// This is used to enter suggestion mode (suggestions become soft-selected).
......@@ -1293,7 +1263,7 @@ private static bool IsPossibleTupleOpenParenOrComma(SyntaxToken possibleCommaOrP
// (var $$, var y)
// (var x, var y)
if (syntaxTree.IsPossibleTupleContext(leftToken, position) && !IsPossibleTupleOpenParenOrComma(leftToken))
if (syntaxTree.IsPossibleTupleContext(leftToken, position) && !leftToken.IsPossibleTupleOpenParenOrComma())
{
return true;
}
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册