Skip to content

Commit e2dc02b

Browse files
committed
(PowerShellGH-813) Refactor the FoldingReference arrays into it's own class
TODO .. Basically stop creating intermediate arrays and just use an extended Hash object
1 parent e33ba46 commit e2dc02b

File tree

7 files changed

+61
-55
lines changed

7 files changed

+61
-55
lines changed

src/PowerShellEditorServices.Protocol/Server/LanguageServer.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1388,7 +1388,7 @@ private FoldingRange[] Fold(
13881388
if (this.currentSettings.CodeFolding.ShowLastLine) { endLineOffset = -1; }
13891389
foreach (FoldingReference fold in FoldingOperations.FoldableRegions(
13901390
script.ScriptTokens,
1391-
script.ScriptAst))
1391+
script.ScriptAst).ToArray())
13921392
{
13931393
result.Add(new FoldingRange {
13941394
EndCharacter = fold.EndCharacter,

src/PowerShellEditorServices/Language/AstOperations.cs

+2-4
Original file line numberDiff line numberDiff line change
@@ -336,12 +336,10 @@ static public string[] FindDotSourcedIncludes(Ast scriptAst, string psScriptRoot
336336
/// </summary>
337337
/// <param name="scriptAst">The abstract syntax tree of the given script</param>
338338
/// <returns>A collection of FoldingReference objects</returns>
339-
public static IEnumerable<FoldingReference> FindFoldsInDocument(Ast scriptAst)
339+
public static void FindFoldsInDocument(Ast scriptAst, ref FoldingReferenceList refList)
340340
{
341-
FindFoldsVisitor findFoldsVisitor = new FindFoldsVisitor();
341+
FindFoldsVisitor findFoldsVisitor = new FindFoldsVisitor(ref refList);
342342
scriptAst.Visit(findFoldsVisitor);
343-
344-
return findFoldsVisitor.FoldableRegions;
345343
}
346344

347345
}

src/PowerShellEditorServices/Language/FindFoldsVisitor.cs

+11-11
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,11 @@ internal class FindFoldsVisitor : AstVisitor
1616
{
1717
private const string RegionKindNone = null;
1818

19-
public List<FoldingReference> FoldableRegions { get; }
19+
private FoldingReferenceList _refList;
2020

21-
public FindFoldsVisitor()
21+
public FindFoldsVisitor(ref FoldingReferenceList refList)
2222
{
23-
this.FoldableRegions = new List<FoldingReference>();
23+
this._refList = refList;
2424
}
2525

2626
/// <summary>
@@ -55,7 +55,7 @@ public override AstVisitAction VisitArrayExpression(ArrayExpressionAst objAst)
5555
{
5656
if (IsValidFoldingExtent(objAst.Extent))
5757
{
58-
this.FoldableRegions.Add(CreateFoldingReference(objAst.Extent, RegionKindNone));
58+
this._refList.SafeAdd(CreateFoldingReference(objAst.Extent, RegionKindNone));
5959
}
6060
return AstVisitAction.Continue;
6161
}
@@ -64,7 +64,7 @@ public override AstVisitAction VisitHashtable(HashtableAst objAst)
6464
{
6565
if (IsValidFoldingExtent(objAst.Extent))
6666
{
67-
this.FoldableRegions.Add(CreateFoldingReference(objAst.Extent, RegionKindNone));
67+
this._refList.SafeAdd(CreateFoldingReference(objAst.Extent, RegionKindNone));
6868
}
6969
return AstVisitAction.Continue;
7070
}
@@ -76,7 +76,7 @@ public override AstVisitAction VisitStatementBlock(StatementBlockAst objAst)
7676
if (objAst.Parent is ArrayExpressionAst) { return AstVisitAction.Continue; }
7777
if (IsValidFoldingExtent(objAst.Extent))
7878
{
79-
this.FoldableRegions.Add(CreateFoldingReference(objAst.Extent, RegionKindNone));
79+
this._refList.SafeAdd(CreateFoldingReference(objAst.Extent, RegionKindNone));
8080
}
8181
return AstVisitAction.Continue;
8282
}
@@ -89,7 +89,7 @@ public override AstVisitAction VisitScriptBlock(ScriptBlockAst objAst)
8989
if (objAst.Parent is ScriptBlockExpressionAst) { return AstVisitAction.Continue; }
9090
if (IsValidFoldingExtent(objAst.Extent))
9191
{
92-
this.FoldableRegions.Add(CreateFoldingReference(objAst.Extent, RegionKindNone));
92+
this._refList.SafeAdd(CreateFoldingReference(objAst.Extent, RegionKindNone));
9393
}
9494
return AstVisitAction.Continue;
9595
}
@@ -106,7 +106,7 @@ public override AstVisitAction VisitScriptBlockExpression(ScriptBlockExpressionA
106106
foldRef.StartCharacter--;
107107
foldRef.EndCharacter++;
108108
}
109-
this.FoldableRegions.Add(foldRef);
109+
this._refList.SafeAdd(foldRef);
110110
}
111111
return AstVisitAction.Continue;
112112
}
@@ -115,7 +115,7 @@ public override AstVisitAction VisitStringConstantExpression(StringConstantExpre
115115
{
116116
if (IsValidFoldingExtent(objAst.Extent))
117117
{
118-
this.FoldableRegions.Add(CreateFoldingReference(objAst.Extent, RegionKindNone));
118+
this._refList.SafeAdd(CreateFoldingReference(objAst.Extent, RegionKindNone));
119119
}
120120

121121
return AstVisitAction.Continue;
@@ -125,7 +125,7 @@ public override AstVisitAction VisitSubExpression(SubExpressionAst objAst)
125125
{
126126
if (IsValidFoldingExtent(objAst.Extent))
127127
{
128-
this.FoldableRegions.Add(CreateFoldingReference(objAst.Extent, RegionKindNone));
128+
this._refList.SafeAdd(CreateFoldingReference(objAst.Extent, RegionKindNone));
129129
}
130130
return AstVisitAction.Continue;
131131
}
@@ -134,7 +134,7 @@ public override AstVisitAction VisitVariableExpression(VariableExpressionAst obj
134134
{
135135
if (IsValidFoldingExtent(objAst.Extent))
136136
{
137-
this.FoldableRegions.Add(CreateFoldingReference(objAst.Extent, RegionKindNone));
137+
this._refList.SafeAdd(CreateFoldingReference(objAst.Extent, RegionKindNone));
138138
}
139139
return AstVisitAction.Continue;
140140
}

src/PowerShellEditorServices/Language/FoldingOperations.cs

+5-24
Original file line numberDiff line numberDiff line change
@@ -18,38 +18,19 @@ internal static class FoldingOperations
1818
/// Extracts all of the unique foldable regions in a script given a script AST and the list tokens
1919
/// used to generate the AST
2020
/// </summary>
21-
internal static FoldingReference[] FoldableRegions(
21+
internal static FoldingReferenceList FoldableRegions(
2222
Token[] tokens,
2323
Ast scriptAst)
2424
{
25-
List<FoldingReference> foldableRegions = new List<FoldingReference>();
25+
var foldableRegions = new FoldingReferenceList();
2626

2727
// Add regions from AST
28-
foldableRegions.AddRange(AstOperations.FindFoldsInDocument(scriptAst));
28+
AstOperations.FindFoldsInDocument(scriptAst, ref foldableRegions);
2929

3030
// Add regions from Tokens
31-
foldableRegions.AddRange(TokenOperations.FoldableRegions(tokens));
31+
TokenOperations.FoldableRegions(tokens, ref foldableRegions);
3232

33-
// Sort the FoldingReferences, starting at the top of the document,
34-
// and ensure that, in the case of multiple ranges starting the same line,
35-
// that the largest range (i.e. most number of lines spanned) is sorted
36-
// first. This is needed to detect duplicate regions. The first in the list
37-
// will be used and subsequent duplicates ignored.
38-
foldableRegions.Sort();
39-
40-
// It's possible to have duplicate or overlapping ranges, that is, regions which have the same starting
41-
// line number as the previous region. Therefore only emit ranges which have a different starting line
42-
// than the previous range.
43-
foldableRegions.RemoveAll( (FoldingReference item) => {
44-
// Note - I'm not happy with searching here, but as the RemoveAll
45-
// doesn't expose the index in the List, we need to calculate it. Fortunately the
46-
// list is sorted at this point, so we can use BinarySearch.
47-
int index = foldableRegions.BinarySearch(item);
48-
if (index == 0) { return false; }
49-
return (item.StartLine == foldableRegions[index - 1].StartLine);
50-
});
51-
52-
return foldableRegions.ToArray();
33+
return foldableRegions;
5334
}
5435
}
5536
}

src/PowerShellEditorServices/Language/FoldingReference.cs

+30
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
//
55

66
using System;
7+
using System.Collections.Generic;
78

89
namespace Microsoft.PowerShell.EditorServices
910
{
@@ -60,4 +61,33 @@ public int CompareTo(FoldingReference that) {
6061
return string.Compare(this.Kind, that.Kind);
6162
}
6263
}
64+
65+
/// <summary>
66+
/// A class that holds a list of FoldingReferences and ensures that when adding a reference that the
67+
/// folding rules are obeyed, e.g. Only one fold per start line
68+
/// </summary>
69+
public class FoldingReferenceList: System.Collections.Generic.Dictionary<int, FoldingReference>
70+
{
71+
/// <summary>
72+
/// Adds a FoldingReference to the list and enforces ordering rules e.g. Only one fold per start line
73+
/// </summary>
74+
public void SafeAdd(FoldingReference item)
75+
{
76+
if (item == null) { return; }
77+
FoldingReference currentItem;
78+
this.TryGetValue(item.StartLine, out currentItem);
79+
// Only add the item if it hasn't been seen before or it's the largest range
80+
if ((currentItem == null) || (currentItem.CompareTo(item) == 1)) { this[item.StartLine] = item; }
81+
}
82+
83+
/// <summary>
84+
/// Helper method to easily convert the Dictionary Values into an array
85+
/// </summary>
86+
public FoldingReference[] ToArray()
87+
{
88+
var result = new FoldingReference[this.Count];
89+
this.Values.CopyTo(result, 0);
90+
return result;
91+
}
92+
}
6393
}

src/PowerShellEditorServices/Language/TokenOperations.cs

+7-13
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,10 @@ internal static class TokenOperations
3333
/// <summary>
3434
/// Extracts all of the unique foldable regions in a script given the list tokens
3535
/// </summary>
36-
internal static FoldingReference[] FoldableRegions(
37-
Token[] tokens)
36+
internal static void FoldableRegions(
37+
Token[] tokens,
38+
ref FoldingReferenceList refList)
3839
{
39-
var foldableRegions = new List<FoldingReference>();
4040
var tokenCommentRegionStack = new Stack<Token>();
4141
Token blockStartToken = null;
4242
int blockNextLine = -1;
@@ -48,8 +48,7 @@ internal static FoldingReference[] FoldableRegions(
4848
// Processing for comment regions <# -> #>
4949
if (token.Extent.StartLineNumber != token.Extent.EndLineNumber)
5050
{
51-
FoldingReference foldRef = CreateFoldingReference(token, token, RegionKindComment);
52-
if (foldRef != null) { foldableRegions.Add(foldRef); }
51+
refList.SafeAdd(CreateFoldingReference(token, token, RegionKindComment));
5352
continue;
5453
}
5554

@@ -67,8 +66,7 @@ internal static FoldingReference[] FoldableRegions(
6766
// Mismatched regions in the script can cause bad stacks.
6867
if (tokenCommentRegionStack.Count > 0)
6968
{
70-
FoldingReference foldRef = CreateFoldingReference(tokenCommentRegionStack.Pop(), token, RegionKindRegion);
71-
if (foldRef != null) { foldableRegions.Add(foldRef); }
69+
refList.SafeAdd(CreateFoldingReference(tokenCommentRegionStack.Pop(), token, RegionKindRegion));
7270
}
7371
continue;
7472
}
@@ -77,8 +75,7 @@ internal static FoldingReference[] FoldableRegions(
7775
int thisLine = token.Extent.StartLineNumber - 1;
7876
if ((blockStartToken != null) && (thisLine != blockNextLine))
7977
{
80-
FoldingReference foldRef = CreateFoldingReference(blockStartToken, blockNextLine - 1, RegionKindComment);
81-
if (foldRef != null) { foldableRegions.Add(foldRef); }
78+
refList.SafeAdd(CreateFoldingReference(blockStartToken, blockNextLine - 1, RegionKindComment));
8279
blockStartToken = token;
8380
}
8481
if (blockStartToken == null) { blockStartToken = token; }
@@ -89,11 +86,8 @@ internal static FoldingReference[] FoldableRegions(
8986
// comment block simply ends at the end of document
9087
if (blockStartToken != null)
9188
{
92-
FoldingReference foldRef = CreateFoldingReference(blockStartToken, blockNextLine - 1, RegionKindComment);
93-
if (foldRef != null) { foldableRegions.Add(foldRef); }
89+
refList.SafeAdd(CreateFoldingReference(blockStartToken, blockNextLine - 1, RegionKindComment));
9490
}
95-
96-
return foldableRegions.ToArray();
9791
}
9892

9993
/// <summary>

test/PowerShellEditorServices.Test/Language/TokenOperationsTests.cs

+5-2
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,12 @@ private FoldingReference[] GetRegions(string text) {
2020
text,
2121
Version.Parse("5.0"));
2222

23-
return Microsoft.PowerShell.EditorServices.FoldingOperations.FoldableRegions(
23+
var result = Microsoft.PowerShell.EditorServices.FoldingOperations.FoldableRegions(
2424
scriptFile.ScriptTokens,
25-
scriptFile.ScriptAst);
25+
scriptFile.ScriptAst).ToArray();
26+
// The foldable regions need to be deterministic for testing so sort the array.
27+
Array.Sort(result);
28+
return result;
2629
}
2730

2831
/// <summary>

0 commit comments

Comments
 (0)