Skip to content

Commit e3179e4

Browse files
glennsartirjmholt
authored andcommitted
Fix folding with spanning regions (#805)
* Fix folding for regions with same end token Previously the token matching was broken; ``` foreach ($1 in $2) { <----- STARTS MATCH HERE (1) $x = @{ <----- STARTS MATCH HERE (2) 'abc' = 'def' } <----- ENDS MATCH HERE (1) (2) } ``` This was caused by two or more different token pairs sharing the same end token. This commit modifies the token pair matching to take an array of Start Tokens instead of a single. This has the added benefit of performance increase too. This commit also adds tests for this scenario. * Add support for braced variable names in folder Previously the code folder was not aware of braced variable name assignments. This commit adds detection and an appropriate test. * Add negative folding test for splatting This commit adds a splat command to the folding scenario to ensure that the folder is not confused by the (at) character for splatting.
1 parent 86b76fd commit e3179e4

File tree

2 files changed

+79
-27
lines changed

2 files changed

+79
-27
lines changed

src/PowerShellEditorServices/Language/TokenOperations.cs

+31-16
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
44
//
55

6+
using System;
67
using System.Collections.Generic;
78
using System.Management.Automation.Language;
89
using System.Text.RegularExpressions;
@@ -15,10 +16,26 @@ namespace Microsoft.PowerShell.EditorServices
1516
/// </summary>
1617
internal static class TokenOperations
1718
{
19+
// Region kinds to align with VSCode's region kinds
1820
private const string RegionKindComment = "comment";
1921
private const string RegionKindRegion = "region";
2022
private const string RegionKindNone = null;
2123

24+
// Opening tokens for { } and @{ }
25+
private static readonly TokenKind[] s_openingBraces = new []
26+
{
27+
TokenKind.LCurly,
28+
TokenKind.AtCurly
29+
};
30+
31+
// Opening tokens for ( ), @( ), $( )
32+
private static readonly TokenKind[] s_openingParens = new []
33+
{
34+
TokenKind.LParen,
35+
TokenKind.AtParen,
36+
TokenKind.DollarParen
37+
};
38+
2239
/// <summary>
2340
/// Extracts all of the unique foldable regions in a script given the list tokens
2441
/// </summary>
@@ -28,29 +45,27 @@ internal static FoldingReference[] FoldableRegions(
2845
{
2946
List<FoldingReference> foldableRegions = new List<FoldingReference>();
3047

31-
// Find matching braces { -> }
32-
foldableRegions.AddRange(
33-
MatchTokenElements(tokens, TokenKind.LCurly, TokenKind.RCurly, RegionKindNone)
34-
);
35-
36-
// Find matching braces ( -> )
48+
// Find matching braces { -> }
49+
// Find matching hashes @{ -> }
3750
foldableRegions.AddRange(
38-
MatchTokenElements(tokens, TokenKind.LParen, TokenKind.RParen, RegionKindNone)
51+
MatchTokenElements(tokens, s_openingBraces, TokenKind.RCurly, RegionKindNone)
3952
);
4053

41-
// Find matching arrays @( -> )
54+
// Find matching parentheses ( -> )
55+
// Find matching array literals @( -> )
56+
// Find matching subexpressions $( -> )
4257
foldableRegions.AddRange(
43-
MatchTokenElements(tokens, TokenKind.AtParen, TokenKind.RParen, RegionKindNone)
58+
MatchTokenElements(tokens, s_openingParens, TokenKind.RParen, RegionKindNone)
4459
);
4560

46-
// Find matching hashes @{ -> }
61+
// Find contiguous here strings @' -> '@
4762
foldableRegions.AddRange(
48-
MatchTokenElements(tokens, TokenKind.AtCurly, TokenKind.RParen, RegionKindNone)
63+
MatchTokenElement(tokens, TokenKind.HereStringLiteral, RegionKindNone)
4964
);
5065

51-
// Find contiguous here strings @' -> '@
66+
// Find unopinionated variable names ${ \n \n }
5267
foldableRegions.AddRange(
53-
MatchTokenElement(tokens, TokenKind.HereStringLiteral, RegionKindNone)
68+
MatchTokenElement(tokens, TokenKind.Variable, RegionKindNone)
5469
);
5570

5671
// Find contiguous here strings @" -> "@
@@ -146,19 +161,19 @@ static private FoldingReference CreateFoldingReference(
146161
}
147162

148163
/// <summary>
149-
/// Given an array of tokens, find matching regions which start and end with a different TokenKind
164+
/// Given an array of tokens, find matching regions which start (array of tokens) and end with a different TokenKind
150165
/// </summary>
151166
static private List<FoldingReference> MatchTokenElements(
152167
Token[] tokens,
153-
TokenKind startTokenKind,
168+
TokenKind[] startTokenKind,
154169
TokenKind endTokenKind,
155170
string matchKind)
156171
{
157172
List<FoldingReference> result = new List<FoldingReference>();
158173
Stack<Token> tokenStack = new Stack<Token>();
159174
foreach (Token token in tokens)
160175
{
161-
if (token.Kind == startTokenKind) {
176+
if (Array.IndexOf(startTokenKind, token.Kind) != -1) {
162177
tokenStack.Push(token);
163178
}
164179
if ((tokenStack.Count > 0) && (token.Kind == endTokenKind)) {

test/PowerShellEditorServices.Test/Language/TokenOperationsTests.cs

+48-11
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,9 @@ herestrings should fold
6767
6868
'@
6969
70+
# This won't confuse things
71+
Get-Command -Param @I
72+
7073
$I = @""
7174
double quoted herestrings should also fold
7275
@@ -111,23 +114,30 @@ double quoted herestrings should also fold
111114
# Comment Block 2
112115
# Comment Block 2
113116
$something = $true
114-
#endregion Comment Block 3";
117+
#endregion Comment Block 3
118+
119+
# What about anonymous variable assignment
120+
${this
121+
is
122+
valid} = 5
123+
";
115124
private FoldingReference[] expectedAllInOneScriptFolds = {
116125
CreateFoldingReference(0, 0, 3, 10, "region"),
117126
CreateFoldingReference(1, 0, 2, 2, "comment"),
118127
CreateFoldingReference(10, 0, 14, 2, "comment"),
119-
CreateFoldingReference(16, 30, 59, 1, null),
128+
CreateFoldingReference(16, 30, 62, 1, null),
120129
CreateFoldingReference(17, 0, 21, 2, "comment"),
121130
CreateFoldingReference(23, 7, 25, 2, null),
122-
CreateFoldingReference(28, 5, 30, 2, null),
123-
CreateFoldingReference(35, 2, 36, 0, "comment"),
124-
CreateFoldingReference(39, 2, 48, 14, "region"),
125-
CreateFoldingReference(41, 4, 44, 14, "region"),
126-
CreateFoldingReference(51, 7, 52, 3, null),
127-
CreateFoldingReference(56, 7, 58, 3, null),
128-
CreateFoldingReference(64, 0, 65, 0, "comment"),
129-
CreateFoldingReference(67, 0, 71, 26, "region"),
130-
CreateFoldingReference(68, 0, 69, 0, "comment")
131+
CreateFoldingReference(31, 5, 33, 2, null),
132+
CreateFoldingReference(38, 2, 39, 0, "comment"),
133+
CreateFoldingReference(42, 2, 51, 14, "region"),
134+
CreateFoldingReference(44, 4, 47, 14, "region"),
135+
CreateFoldingReference(54, 7, 55, 3, null),
136+
CreateFoldingReference(59, 7, 61, 3, null),
137+
CreateFoldingReference(67, 0, 68, 0, "comment"),
138+
CreateFoldingReference(70, 0, 74, 26, "region"),
139+
CreateFoldingReference(71, 0, 72, 0, "comment"),
140+
CreateFoldingReference(78, 0, 79, 6, null),
131141
};
132142

133143
/// <summary>
@@ -217,5 +227,32 @@ public void LaguageServiceFindsFoldablRegionsWithDuplicateRegions() {
217227
FoldingReference[] result = GetRegions(testString);
218228
AssertFoldingReferenceArrays(expectedFolds, result);
219229
}
230+
231+
// This tests that token matching { -> }, @{ -> } and
232+
// ( -> ), @( -> ) and $( -> ) does not confuse the folder
233+
[Fact]
234+
public void LaguageServiceFindsFoldablRegionsWithSameEndToken() {
235+
string testString =
236+
@"foreach ($1 in $2) {
237+
238+
$x = @{
239+
'abc' = 'def'
240+
}
241+
}
242+
243+
$y = $(
244+
$arr = @('1', '2'); Write-Host ($arr)
245+
)
246+
";
247+
FoldingReference[] expectedFolds = {
248+
CreateFoldingReference(0, 19, 4, 1, null),
249+
CreateFoldingReference(2, 9, 3, 5, null),
250+
CreateFoldingReference(7, 5, 8, 1, null)
251+
};
252+
253+
FoldingReference[] result = GetRegions(testString);
254+
255+
AssertFoldingReferenceArrays(expectedFolds, result);
256+
}
220257
}
221258
}

0 commit comments

Comments
 (0)