提交 b086c6f4 编写于 作者: P Petr Houška

Add suport for TextSpan stripping & wire it within refactoring.

上级 3ceb7233
......@@ -555,7 +555,7 @@ void M()
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsConvertLocalFunctionToMethod)]
public async Task TestWholeMethodSelection1()
public async Task TestMethodBlockSelection1()
{
await TestInRegularAndScriptAsync(
@"class C
......@@ -582,7 +582,7 @@ private static C LocalFunction(C c)
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsConvertLocalFunctionToMethod)]
public async Task TestWholeMethodSelection2()
public async Task TestMethodBlockSelection2()
{
await TestInRegularAndScriptAsync(
......@@ -610,7 +610,7 @@ private static C LocalFunction(C c)
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsConvertLocalFunctionToMethod)]
public async Task TestWholeMethodSelection3()
public async Task TestMethodBlockSelection3()
{
await this.TestMissingAsync(
......@@ -629,5 +629,77 @@ C LocalFunction(C c)
}");
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsConvertLocalFunctionToMethod)]
public async Task TestMethodBlockSelection4()
{
await TestInRegularAndScriptAsync(
@"class C
{
void M()
{
[|
C LocalFunction(C c)
{
return null;
}
|]
}
}",
@"class C
{
void M()
{
}
private static C LocalFunction(C c)
{
return null;
}
}");
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsConvertLocalFunctionToMethod)]
public async Task TestMethodBlockSelection5()
{
await this.TestMissingAsync(
@"class C
{
void M()
{
object a = null[|;
C LocalFunction(C c)
{
return null;
}|]
}
}");
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsConvertLocalFunctionToMethod)]
public async Task TestMethodBlockSelection6()
{
await this.TestMissingAsync(
@"class C
{
void M()
{
[|;
C LocalFunction(C c)
{
return null;
}
object|] a = null
}
}");
}
}
}
......@@ -41,9 +41,14 @@ public override async Task ComputeRefactoringsAsync(CodeRefactoringContext conte
}
var cancellationToken = context.CancellationToken;
var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
var localFunction = await CodeRefactoringHelpers.TryGetSelectedNode<LocalFunctionStatementSyntax>(context.Span, root, cancellationToken).ConfigureAwait(false);
var localFunction = await CodeRefactoringHelpers.TryGetSelectedNode<LocalFunctionStatementSyntax>(document, context.Span, cancellationToken).ConfigureAwait(false);
if (localFunction == default)
{
var block = await CodeRefactoringHelpers.TryGetSelectedNode<BlockSyntax>(document, context.Span, cancellationToken).ConfigureAwait(false);
localFunction = block.Parent as LocalFunctionStatementSyntax;
}
if (localFunction == default)
{
return;
......@@ -54,6 +59,8 @@ public override async Task ComputeRefactoringsAsync(CodeRefactoringContext conte
return;
}
var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
context.RegisterRefactoring(new MyCodeAction(CSharpFeaturesResources.Convert_to_method,
c => UpdateDocumentAsync(root, document, parentBlock, localFunction, c)));
}
......
......@@ -21,19 +21,22 @@ internal static class CodeRefactoringHelpers
/// direct parent is of type <typeparamref name="TSyntaxNode"/> or if a Node of said type is the smallest Node containing
/// the whole <paramref name="selection"/>. Otherwise returns <code>null</code>.
/// </para>
/// <para>
/// Note: this function strips all whitespace from both the beginning and end given <paramref name="selection"/>
/// and the stripped selection to determine the relevant Node.
/// </para>
/// </summary>
public static async Task<TSyntaxNode> TryGetSelectedNode<TSyntaxNode>(
TextSpan selection, SyntaxNode root, CancellationToken cancellationToken) where TSyntaxNode : SyntaxNode
Document document, TextSpan selection, CancellationToken cancellationToken) where TSyntaxNode : SyntaxNode
{
var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
var selectionStripped = await GetStrippedTextSpan(document, selection, cancellationToken).ConfigureAwait(false);
// TODO: Add handling selected whitespace before / after the same way as helper methods below -> refactor to public
// helper.
var node = root.FindNode(selection) as TSyntaxNode;
var node = root.FindNode(selectionStripped) as TSyntaxNode;
if (node == null)
{
// e.g. "C LocalFunction[||](C c)" -> root.FindNode return ParameterList but we still want to return LocalFunctionNode
var identifier = await root.SyntaxTree.GetTouchingTokenAsync(selection.Start,
var identifier = await root.SyntaxTree.GetTouchingTokenAsync(selectionStripped.Start,
token => token.Parent is TSyntaxNode, cancellationToken).ConfigureAwait(false);
node = identifier.Parent as TSyntaxNode;
}
......@@ -131,14 +134,19 @@ internal static class CodeRefactoringHelpers
return true;
}
private static async Task<TextSpan> GetExpandedNodeSpan(
private static Task<TextSpan> GetExpandedNodeSpan(
Document document,
SyntaxNode node,
CancellationToken cancellationToken)
{
return GetExpandedTextSpan(document, node.Span, cancellationToken);
}
private static async Task<TextSpan> GetExpandedTextSpan(Document document, TextSpan span, CancellationToken cancellationToken)
{
var sourceText = await document.GetTextAsync(cancellationToken).ConfigureAwait(false);
var nodeStartLine = sourceText.Lines.GetLineFromPosition(node.SpanStart);
var nodeStartLine = sourceText.Lines.GetLineFromPosition(span.Start);
// Enable vertical selections that catch the previous line break and perhaps some whitespace.
if (nodeStartLine.LineNumber != 0)
......@@ -146,10 +154,10 @@ internal static class CodeRefactoringHelpers
nodeStartLine = sourceText.Lines[nodeStartLine.LineNumber - 1];
}
var nodeEndLine = sourceText.Lines.GetLineFromPosition(node.Span.End);
var nodeEndLine = sourceText.Lines.GetLineFromPosition(span.End);
var start = node.SpanStart;
var end = node.Span.End;
var start = span.Start;
var end = span.End;
while (start > nodeStartLine.Start && char.IsWhiteSpace(sourceText[start - 1]))
{
......@@ -163,5 +171,33 @@ internal static class CodeRefactoringHelpers
return TextSpan.FromBounds(start, end);
}
private static async Task<TextSpan> GetStrippedTextSpan(
Document document,
TextSpan span,
CancellationToken cancellationToken
)
{
var sourceText = await document.GetTextAsync(cancellationToken).ConfigureAwait(false);
var start = span.Start;
var end = span.End;
if (span.IsEmpty)
{
return span;
}
while (start < end && char.IsWhiteSpace(sourceText[start]))
{
start++;
}
while (start < end && char.IsWhiteSpace(sourceText[end - 1]) && char.IsWhiteSpace(sourceText[end]))
{
end--;
}
return TextSpan.FromBounds(start, end);
}
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册