diff --git a/src/PowerShellEditorServices/Language/TokenOperations.cs b/src/PowerShellEditorServices/Language/TokenOperations.cs index 236c05f16..3003cd6e8 100644 --- a/src/PowerShellEditorServices/Language/TokenOperations.cs +++ b/src/PowerShellEditorServices/Language/TokenOperations.cs @@ -3,6 +3,7 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. // +using System; using System.Collections.Generic; using System.Management.Automation.Language; using System.Text.RegularExpressions; @@ -15,10 +16,26 @@ namespace Microsoft.PowerShell.EditorServices /// internal static class TokenOperations { + // Region kinds to align with VSCode's region kinds private const string RegionKindComment = "comment"; private const string RegionKindRegion = "region"; private const string RegionKindNone = null; + // Opening tokens for { } and @{ } + private static readonly TokenKind[] s_openingBraces = new [] + { + TokenKind.LCurly, + TokenKind.AtCurly + }; + + // Opening tokens for ( ), @( ), $( ) + private static readonly TokenKind[] s_openingParens = new [] + { + TokenKind.LParen, + TokenKind.AtParen, + TokenKind.DollarParen + }; + /// /// Extracts all of the unique foldable regions in a script given the list tokens /// @@ -28,29 +45,27 @@ internal static FoldingReference[] FoldableRegions( { List foldableRegions = new List(); - // Find matching braces { -> } - foldableRegions.AddRange( - MatchTokenElements(tokens, TokenKind.LCurly, TokenKind.RCurly, RegionKindNone) - ); - - // Find matching braces ( -> ) + // Find matching braces { -> } + // Find matching hashes @{ -> } foldableRegions.AddRange( - MatchTokenElements(tokens, TokenKind.LParen, TokenKind.RParen, RegionKindNone) + MatchTokenElements(tokens, s_openingBraces, TokenKind.RCurly, RegionKindNone) ); - // Find matching arrays @( -> ) + // Find matching parentheses ( -> ) + // Find matching array literals @( -> ) + // Find matching subexpressions $( -> ) foldableRegions.AddRange( - MatchTokenElements(tokens, TokenKind.AtParen, TokenKind.RParen, RegionKindNone) + MatchTokenElements(tokens, s_openingParens, TokenKind.RParen, RegionKindNone) ); - // Find matching hashes @{ -> } + // Find contiguous here strings @' -> '@ foldableRegions.AddRange( - MatchTokenElements(tokens, TokenKind.AtCurly, TokenKind.RParen, RegionKindNone) + MatchTokenElement(tokens, TokenKind.HereStringLiteral, RegionKindNone) ); - // Find contiguous here strings @' -> '@ + // Find unopinionated variable names ${ \n \n } foldableRegions.AddRange( - MatchTokenElement(tokens, TokenKind.HereStringLiteral, RegionKindNone) + MatchTokenElement(tokens, TokenKind.Variable, RegionKindNone) ); // Find contiguous here strings @" -> "@ @@ -146,11 +161,11 @@ static private FoldingReference CreateFoldingReference( } /// - /// Given an array of tokens, find matching regions which start and end with a different TokenKind + /// Given an array of tokens, find matching regions which start (array of tokens) and end with a different TokenKind /// static private List MatchTokenElements( Token[] tokens, - TokenKind startTokenKind, + TokenKind[] startTokenKind, TokenKind endTokenKind, string matchKind) { @@ -158,7 +173,7 @@ static private List MatchTokenElements( Stack tokenStack = new Stack(); foreach (Token token in tokens) { - if (token.Kind == startTokenKind) { + if (Array.IndexOf(startTokenKind, token.Kind) != -1) { tokenStack.Push(token); } if ((tokenStack.Count > 0) && (token.Kind == endTokenKind)) { diff --git a/test/PowerShellEditorServices.Test/Language/TokenOperationsTests.cs b/test/PowerShellEditorServices.Test/Language/TokenOperationsTests.cs index 9a192a666..3ce157837 100644 --- a/test/PowerShellEditorServices.Test/Language/TokenOperationsTests.cs +++ b/test/PowerShellEditorServices.Test/Language/TokenOperationsTests.cs @@ -67,6 +67,9 @@ herestrings should fold '@ +# This won't confuse things +Get-Command -Param @I + $I = @"" double quoted herestrings should also fold @@ -111,23 +114,30 @@ double quoted herestrings should also fold # Comment Block 2 # Comment Block 2 $something = $true -#endregion Comment Block 3"; +#endregion Comment Block 3 + +# What about anonymous variable assignment +${this +is +valid} = 5 +"; private FoldingReference[] expectedAllInOneScriptFolds = { CreateFoldingReference(0, 0, 3, 10, "region"), CreateFoldingReference(1, 0, 2, 2, "comment"), CreateFoldingReference(10, 0, 14, 2, "comment"), - CreateFoldingReference(16, 30, 59, 1, null), + CreateFoldingReference(16, 30, 62, 1, null), CreateFoldingReference(17, 0, 21, 2, "comment"), CreateFoldingReference(23, 7, 25, 2, null), - CreateFoldingReference(28, 5, 30, 2, null), - CreateFoldingReference(35, 2, 36, 0, "comment"), - CreateFoldingReference(39, 2, 48, 14, "region"), - CreateFoldingReference(41, 4, 44, 14, "region"), - CreateFoldingReference(51, 7, 52, 3, null), - CreateFoldingReference(56, 7, 58, 3, null), - CreateFoldingReference(64, 0, 65, 0, "comment"), - CreateFoldingReference(67, 0, 71, 26, "region"), - CreateFoldingReference(68, 0, 69, 0, "comment") + CreateFoldingReference(31, 5, 33, 2, null), + CreateFoldingReference(38, 2, 39, 0, "comment"), + CreateFoldingReference(42, 2, 51, 14, "region"), + CreateFoldingReference(44, 4, 47, 14, "region"), + CreateFoldingReference(54, 7, 55, 3, null), + CreateFoldingReference(59, 7, 61, 3, null), + CreateFoldingReference(67, 0, 68, 0, "comment"), + CreateFoldingReference(70, 0, 74, 26, "region"), + CreateFoldingReference(71, 0, 72, 0, "comment"), + CreateFoldingReference(78, 0, 79, 6, null), }; /// @@ -217,5 +227,32 @@ public void LaguageServiceFindsFoldablRegionsWithDuplicateRegions() { FoldingReference[] result = GetRegions(testString); AssertFoldingReferenceArrays(expectedFolds, result); } + + // This tests that token matching { -> }, @{ -> } and + // ( -> ), @( -> ) and $( -> ) does not confuse the folder + [Fact] + public void LaguageServiceFindsFoldablRegionsWithSameEndToken() { + string testString = +@"foreach ($1 in $2) { + + $x = @{ + 'abc' = 'def' + } +} + +$y = $( + $arr = @('1', '2'); Write-Host ($arr) +) +"; + FoldingReference[] expectedFolds = { + CreateFoldingReference(0, 19, 4, 1, null), + CreateFoldingReference(2, 9, 3, 5, null), + CreateFoldingReference(7, 5, 8, 1, null) + }; + + FoldingReference[] result = GetRegions(testString); + + AssertFoldingReferenceArrays(expectedFolds, result); + } } }