/
githubmirror
/
roslyn
Обзор
Документация
Войти
/
githubmirror
/
roslyn
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/Features/CSharp/Portable/Structure/Providers/BlockSyntaxStructureProvider.cs
228 строк
9 KB
Ankita Khera
Allow try/catch/finally blocks to collapse independently (#84507)
14 июл 2026, 23:12
Не верифицирован
14 июл 2026, 23:12
4a84e9c
Код
Авторство
О чём код?
// 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; using System.Threading; using Microsoft.CodeAnalysis.Collections; using Microsoft.CodeAnalysis.CSharp.Extensions; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Collections; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Structure; using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.CSharp.Structure; internal sealed class BlockSyntaxStructureProvider : AbstractSyntaxNodeStructureProvider<BlockSyntax> { protected override void CollectBlockSpans( SyntaxToken previousToken, BlockSyntax node, ArrayBuilder<BlockSpan> spans, BlockStructureOptions options, CancellationToken cancellationToken) { var parent = node.GetRequiredParent(); var parentKind = parent.Kind(); // For most types of statements, just consider the block 'attached' to the parent node. That means we'll // show the parent node header when doing things like hovering over the indent guide. // // This also works nicely as the close brace for these constructs will always align with the start of these // statements. // If we're on the topmost if-statement in a chain if/else-if/else-if/else, then all the else-if and // final-else blocks as subheadings as well. This is so that sticky scroll knows what to show when we're // nested within one of the deeper elses. if (parent is IfStatementSyntax { Parent: not ElseClauseSyntax } ifStatement) { using var subHeadings = TemporaryArray<(TextSpan textSpan, TextSpan hintSpan, string type)>.Empty; for (var currentElse = ifStatement.Else; currentElse != null;) { StatementSyntax elseStatement; if (currentElse.Statement is IfStatementSyntax nextIfStatement) { elseStatement = nextIfStatement.Statement; currentElse = nextIfStatement.Else; } else { elseStatement = currentElse.Statement; currentElse = null; } if (elseStatement is BlockSyntax { IsMissing: false } elseBlock) subHeadings.Add((GetTextSpan(elseBlock), GetHintSpan(elseBlock), BlockTypes.Conditional)); } spans.Add(new BlockSpan( BlockTypes.Conditional, isCollapsible: true, GetTextSpan(node), GetHintSpan(node), subHeadings.ToImmutableAndClear(), autoCollapse: false)); } else if (parent is TryStatementSyntax tryStatement) { using var subHeadings = TemporaryArray<(TextSpan textSpan, TextSpan hintSpan, string type)>.Empty; foreach (var catchClause in tryStatement.Catches) { if (!catchClause.Block.IsMissing) subHeadings.Add((GetTextSpan(catchClause.Block), GetHintSpan(catchClause.Block), BlockTypes.Statement)); } if (tryStatement.Finally?.Block is { IsMissing: false } finallyBlock) subHeadings.Add((GetTextSpan(finallyBlock), GetHintSpan(finallyBlock), BlockTypes.Statement)); spans.Add(new BlockSpan( BlockTypes.Statement, isCollapsible: true, GetTextSpan(node), GetHintSpan(node), subHeadings.ToImmutableAndClear(), autoCollapse: false)); } else if (parentKind is SyntaxKind.ElseClause or SyntaxKind.CatchClause or SyntaxKind.FinallyClause || IsNonBlockStatement(parent)) { var autoCollapse = false; // Treat a local function at the top level as if it was a definition. Similarly, if the user has asked // to always collapse local functions, then respect that option. if (parentKind == SyntaxKind.LocalFunctionStatement) { autoCollapse = options.CollapseLocalFunctionsWhenCollapsingToDefinitions || parent.IsParentKind(SyntaxKind.GlobalStatement); } var type = GetType(parent); if (type != null) { spans.Add(new BlockSpan( isCollapsible: true, textSpan: GetTextSpan(node), hintSpan: GetHintSpan(node), type: type, autoCollapse: autoCollapse)); } } // Nested blocks aren't attached to anything. Just collapse them as is. // Switch sections are also special. Say you have the following: // // case 0: // { // // } // // We don't want to consider the block parented by the case, because // that would cause us to draw the following: // // case 0: // | { // | // | } // // Which would obviously be wonky. So in this case, we just use the // spanof the block alone, without consideration for the case clause. if (parentKind is SyntaxKind.Block or SyntaxKind.SwitchSection) { var type = GetType(parent); if (type != null) { spans.Add(new BlockSpan( isCollapsible: true, textSpan: node.Span, hintSpan: node.Span, type: type)); } } // Add any leading comments before the end of the block CSharpStructureHelpers.CollectCommentBlockSpans(node.CloseBraceToken.LeadingTrivia, spans); } private static bool IsNonBlockStatement(SyntaxNode node) => node is StatementSyntax(kind: not SyntaxKind.Block); private static TextSpan GetHintSpan(BlockSyntax node) { var parent = node.GetRequiredParent(); if (parent.IsKind(SyntaxKind.IfStatement) && parent.IsParentKind(SyntaxKind.ElseClause)) { parent = parent.GetRequiredParent(); } var start = parent.Span.Start; var end = GetEnd(node); return TextSpan.FromBounds(start, end); } private static TextSpan GetTextSpan(BlockSyntax node) { var previousToken = node.GetFirstToken().GetPreviousToken(); if (previousToken.IsKind(SyntaxKind.None)) { return node.Span; } return TextSpan.FromBounds(previousToken.Span.End, GetEnd(node)); } private static int GetEnd(BlockSyntax node) { if (node.Parent.IsKind(SyntaxKind.IfStatement) || node.Parent.IsKind(SyntaxKind.TryStatement)) { // For an if-statement or try-statement, just collapse up to the end of the block. // We don't want to collapse the whole statement just for the 'true' portion (of an // 'if'), or just the 'try' portion (of a 'try/catch/finally'). Each attached // clause (else/catch/finally) gets its own collapsible region instead. // Also, while outlining might be ok, the Indent-Guide would look very strange for nodes // like: // // if (goo) // { // } // else // return a || // b; return node.Span.End; } else { // For all other constructs, we collapse up to the end of the parent // construct. return node.GetRequiredParent().Span.End; } } private static string? GetType(SyntaxNode? parent) => (parent?.Kind()) switch { SyntaxKind.ForStatement => BlockTypes.Loop, SyntaxKind.ForEachStatement => BlockTypes.Loop, SyntaxKind.ForEachVariableStatement => BlockTypes.Loop, SyntaxKind.WhileStatement => BlockTypes.Loop, SyntaxKind.DoStatement => BlockTypes.Loop, SyntaxKind.TryStatement => BlockTypes.Statement, SyntaxKind.CatchClause => BlockTypes.Statement, SyntaxKind.FinallyClause => BlockTypes.Statement, SyntaxKind.UnsafeStatement => BlockTypes.Statement, SyntaxKind.FixedStatement => BlockTypes.Statement, SyntaxKind.LockStatement => BlockTypes.Statement, SyntaxKind.UsingStatement => BlockTypes.Statement, SyntaxKind.IfStatement => BlockTypes.Conditional, SyntaxKind.ElseClause => BlockTypes.Conditional, SyntaxKind.SwitchSection => BlockTypes.Conditional, SyntaxKind.Block => BlockTypes.Statement, SyntaxKind.LocalFunctionStatement => BlockTypes.Statement, _ => null, }; }