-
Notifications
You must be signed in to change notification settings - Fork 4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support code folding for argument lists #71064
Changes from all commits
5254fb4
babf187
5f4aaa5
de94736
6c6446a
d9319b4
49e5f85
5b91cd3
5d0196c
013dd11
3255b94
01ae12f
e558973
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -351,6 +351,59 @@ public void ReverseContents() | |
} | ||
} | ||
|
||
public void Sort(Comparison<T> compare) | ||
{ | ||
if (_builder is not null) | ||
{ | ||
_builder.Sort(compare); | ||
return; | ||
} | ||
|
||
switch (_count) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. can you use: https://learn.microsoft.com/en-us/dotnet/api/system.memoryextensions.sort?view=net-8.0 instead? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Basically:
? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not available in netstandard2.0 😕 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Gah. @stephentoub Any recommendations on how to best sort a span in netstandard2.0? |
||
{ | ||
case <= 1: | ||
return; | ||
case 2: | ||
if (compare(_item0, _item1) > 0) | ||
{ | ||
(_item0, _item1) = (_item1, _item0); | ||
} | ||
return; | ||
case 3: | ||
if (compare(_item0, _item1) > 0) | ||
(_item0, _item1) = (_item1, _item0); | ||
|
||
if (compare(_item1, _item2) > 0) | ||
{ | ||
(_item1, _item2) = (_item2, _item1); | ||
|
||
if (compare(_item0, _item1) > 0) | ||
(_item0, _item1) = (_item1, _item0); | ||
} | ||
return; | ||
case 4: | ||
|
||
if (compare(_item0, _item1) > 0) | ||
(_item0, _item1) = (_item1, _item0); | ||
|
||
if (compare(_item2, _item3) > 0) | ||
(_item2, _item3) = (_item3, _item2); | ||
|
||
if (compare(_item0, _item2) > 0) | ||
(_item0, _item2) = (_item2, _item0); | ||
|
||
if (compare(_item1, _item3) > 0) | ||
(_item1, _item3) = (_item3, _item1); | ||
|
||
if (compare(_item1, _item2) > 0) | ||
(_item1, _item2) = (_item2, _item1); | ||
|
||
return; | ||
default: | ||
throw ExceptionUtilities.Unreachable(); | ||
} | ||
} | ||
|
||
/// <summary> | ||
/// Throws <see cref="IndexOutOfRangeException"/>. | ||
/// </summary> | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
// Licensed to the .NET Foundation under one or more agreements. | ||
// The .NET Foundation licenses this file to you under the MIT license. | ||
// See the LICENSE file in the project root for more information. | ||
|
||
using System.Threading.Tasks; | ||
using Microsoft.CodeAnalysis.CSharp.Structure; | ||
using Microsoft.CodeAnalysis.CSharp.Syntax; | ||
using Microsoft.CodeAnalysis.Structure; | ||
using Microsoft.CodeAnalysis.Test.Utilities; | ||
using Xunit; | ||
|
||
namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Structure; | ||
|
||
[Trait(Traits.Feature, Traits.Features.Outlining)] | ||
public class ArgumentListSyntaxStructureTests : AbstractCSharpSyntaxNodeStructureTests<ArgumentListSyntax> | ||
{ | ||
internal override AbstractSyntaxStructureProvider CreateProvider() => new ArgumentListStructureProvider(); | ||
|
||
[Fact] | ||
public async Task TestInvocationExpressionSingleLine() | ||
{ | ||
var code = """ | ||
var x = M$$(); | ||
"""; | ||
|
||
await VerifyBlockSpansAsync(code); | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. test with just two args on different lines. |
||
|
||
[Fact] | ||
public async Task TestInvocationExpressionTwoArgumentsInTwoLines() | ||
{ | ||
var code = """ | ||
var x = M$$("Hello", | ||
"World"); | ||
"""; | ||
|
||
await VerifyBlockSpansAsync(code); | ||
} | ||
|
||
[Fact] | ||
public async Task TestInvocationExpressionThreeLines() | ||
{ | ||
var code = """ | ||
var x = M$${|span:( | ||
"", | ||
"")|}; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. what's the experience you have in the IDE if you have: var v = M(N(
"",
"")); In other words, how does nesting impact things, when the outer/inner are on the same start/end lines. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @CyrusNajmabadi I wasn't able to get RoslynDeployment work properly. https://www.youtube.com/watch?v=ND8dDO6oJ4U see around 38:00 |
||
"""; | ||
|
||
await VerifyBlockSpansAsync(code, | ||
Region("span", CSharpStructureHelpers.Ellipsis, autoCollapse: false)); | ||
} | ||
|
||
[Fact] | ||
public async Task TestTwoInvocationExpressionsThreeLines() | ||
{ | ||
// The inner argument list should be collapsible, but the outer one shouldn't. | ||
// While this test shows both as collapsible, they will be deduplicated by AbstractBlockStructureProvider | ||
// This test only tests ArgumentListStructureProvider specifically, so it doesn't show the deduplication. | ||
// Tests in BlockStructureServiceTests show the overall behavior accurately. | ||
var testInner = """ | ||
var x = M(M$${|span:( | ||
"", | ||
"")|}); | ||
"""; | ||
|
||
await VerifyBlockSpansAsync(testInner, | ||
Region("span", CSharpStructureHelpers.Ellipsis, autoCollapse: false)); | ||
|
||
var testOuter = """ | ||
var x = M$${|span:(M( | ||
"", | ||
""))|}; | ||
"""; | ||
|
||
await VerifyBlockSpansAsync(testOuter, | ||
Region("span", CSharpStructureHelpers.Ellipsis, autoCollapse: false)); | ||
} | ||
|
||
[Fact] | ||
public async Task TestObjectCreationSingleLine() | ||
{ | ||
var code = """ | ||
var x = new C$$(); | ||
"""; | ||
|
||
await VerifyBlockSpansAsync(code); | ||
} | ||
|
||
[Fact] | ||
public async Task TestObjectCreationThreeLines() | ||
{ | ||
var code = """ | ||
var x = new C$${|span:( | ||
"", | ||
"")|}; | ||
"""; | ||
|
||
await VerifyBlockSpansAsync(code, | ||
Region("span", CSharpStructureHelpers.Ellipsis, autoCollapse: false)); | ||
} | ||
|
||
[Fact] | ||
public async Task TestImplicitObjectCreationSingleLine() | ||
{ | ||
var code = """ | ||
C x = new$$(); | ||
"""; | ||
|
||
await VerifyBlockSpansAsync(code); | ||
} | ||
|
||
[Fact] | ||
public async Task TestImplicitObjectCreationThreeLines() | ||
{ | ||
var code = """ | ||
C x = new$${|span:( | ||
"", | ||
"")|}; | ||
"""; | ||
|
||
await VerifyBlockSpansAsync(code, | ||
Region("span", CSharpStructureHelpers.Ellipsis, autoCollapse: false)); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
// Licensed to the .NET Foundation under one or more agreements. | ||
// The .NET Foundation licenses this file to you under the MIT license. | ||
// See the LICENSE file in the project root for more information. | ||
|
||
using System.Diagnostics; | ||
using System.Linq; | ||
using System.Threading; | ||
using Microsoft.CodeAnalysis.CSharp.Syntax; | ||
using Microsoft.CodeAnalysis.Shared.Collections; | ||
using Microsoft.CodeAnalysis.Structure; | ||
|
||
namespace Microsoft.CodeAnalysis.CSharp.Structure; | ||
|
||
internal sealed class ArgumentListStructureProvider : AbstractSyntaxNodeStructureProvider<ArgumentListSyntax> | ||
{ | ||
protected override void CollectBlockSpans(SyntaxToken previousToken, ArgumentListSyntax node, ref TemporaryArray<BlockSpan> spans, BlockStructureOptions options, CancellationToken cancellationToken) | ||
{ | ||
if (!IsCandidate(node, cancellationToken)) | ||
{ | ||
return; | ||
} | ||
|
||
spans.Add(new BlockSpan( | ||
type: BlockTypes.Expression, | ||
isCollapsible: true, | ||
node.Span)); | ||
} | ||
|
||
private static bool IsCandidate(ArgumentListSyntax node, CancellationToken cancellationToken) | ||
{ | ||
var openToken = node.OpenParenToken; | ||
var closeToken = node.CloseParenToken; | ||
if (openToken.IsMissing || closeToken.IsMissing) | ||
{ | ||
return false; | ||
} | ||
|
||
var text = node.SyntaxTree.GetText(cancellationToken); | ||
var start = text.Lines.GetLinePosition(openToken.SpanStart).Line; | ||
var end = text.Lines.GetLinePosition(closeToken.SpanStart).Line; | ||
return end - start >= 2; | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i would take everything out related to sorting TempArray. We don't even relaly need temparray for block-structure. That's an array that's good when you commonly have <4 elements. But for block structure, we'd almost alwayshave more. So we can just pass an ArrayBuilder along instead).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
or, we can keep this. i'm on the fence.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@CyrusNajmabadi I had the same feeling the TemporaryArray might not be very suitable for this scenario, but I preferred to limit the scope of my changes.