Skip to content

Commit 0e2dc4c

Browse files
committed
(todo) Use AST
1 parent e7d11d6 commit 0e2dc4c

File tree

5 files changed

+476
-76
lines changed

5 files changed

+476
-76
lines changed

src/PowerShellEditorServices/Language/AstOperations.cs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -330,5 +330,41 @@ static public string[] FindDotSourcedIncludes(Ast scriptAst, string psScriptRoot
330330

331331
return dotSourcedVisitor.DotSourcedFiles.ToArray();
332332
}
333+
334+
/// xxxxxxxxxxxxxxxxxxxxxxxxxxx
335+
/// <summary>
336+
/// Finds all symbols in a script
337+
/// </summary>
338+
/// <param name="scriptAst">The abstract syntax tree of the given script</param>
339+
/// <param name="powerShellVersion">The PowerShell version the Ast was generated from</param>
340+
/// <returns>A collection of SymbolReference objects</returns>
341+
static public IEnumerable<FoldingReference> FindFoldsInDocument(Ast scriptAst)
342+
{
343+
IEnumerable<FoldingReference> symbolReferences = null;
344+
345+
// TODO: Restore this when we figure out how to support multiple
346+
// PS versions in the new PSES-as-a-module world (issue #276)
347+
// if (powerShellVersion >= new Version(5,0))
348+
// {
349+
//#if PowerShellv5
350+
// FindSymbolsVisitor2 findSymbolsVisitor = new FindSymbolsVisitor2();
351+
// scriptAst.Visit(findSymbolsVisitor);
352+
// symbolReferences = findSymbolsVisitor.SymbolReferences;
353+
//#endif
354+
// }
355+
// else
356+
357+
FindFoldsVisitor findFoldsVisitor = new FindFoldsVisitor();
358+
scriptAst.Visit(findFoldsVisitor);
359+
360+
// findFoldsVisitor.FoldableRegions.ForEach( (FoldingReference item) => {
361+
// System.Console.ForegroundColor = ConsoleColor.White;
362+
// System.Console.WriteLine("---------------------------");
363+
364+
// });
365+
366+
return findFoldsVisitor.FoldableRegions;
367+
}
368+
333369
}
334370
}
Lines changed: 241 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,241 @@
1+
//
2+
// Copyright (c) Microsoft. All rights reserved.
3+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
4+
//
5+
6+
using System.Collections.Generic;
7+
using System.Management.Automation.Language;
8+
9+
using System;
10+
11+
namespace Microsoft.PowerShell.EditorServices
12+
{
13+
/// <summary>
14+
/// The visitor used to find the the symbol at a specfic location in the AST
15+
/// </summary>
16+
internal class FindFoldsVisitor : AstVisitor
17+
{
18+
private const Boolean DEBUGMODE = false; //true;
19+
20+
public List<FoldingReference> FoldableRegions { get; private set; }
21+
22+
// private int lineNumber;
23+
// private int columnNumber;
24+
// private bool includeFunctionDefinitions;
25+
26+
// public SymbolReference FoundSymbolReference { get; private set; }
27+
28+
public FindFoldsVisitor()
29+
{
30+
this.FoldableRegions = new List<FoldingReference>();
31+
}
32+
33+
/// <summary>
34+
/// Creates an instance of a FoldingReference object from a start and end langauge Token
35+
/// Returns null if the line range is invalid
36+
/// </summary>
37+
private bool IsValidFoldingExtent(
38+
IScriptExtent extent)
39+
{
40+
// if (DEBUGMODE) { return true; }
41+
if (extent.EndLineNumber == extent.StartLineNumber) { return false; }
42+
return true;
43+
}
44+
45+
/// <summary>
46+
/// Creates an instance of a FoldingReference object from a start and end langauge Token
47+
/// Returns null if the line range is invalid
48+
/// </summary>
49+
private FoldingReference CreateFoldingReference(
50+
IScriptExtent extent,
51+
string matchKind)
52+
{
53+
// Extents are base 1, but LSP is base 0, so minus 1 off all lines and character positions
54+
return new FoldingReference {
55+
StartLine = extent.StartLineNumber - 1,
56+
StartCharacter = extent.StartColumnNumber - 1,
57+
EndLine = extent.EndLineNumber - 1,
58+
EndCharacter = extent.EndColumnNumber - 1,
59+
Kind = matchKind
60+
};
61+
}
62+
63+
static private string debugKindName(object obj) {
64+
if (DEBUGMODE) { return obj.GetType().ToString(); }
65+
return null;
66+
}
67+
68+
public override AstVisitAction VisitArrayExpression(ArrayExpressionAst objAst)
69+
{
70+
if (IsValidFoldingExtent(objAst.Extent)) { this.FoldableRegions.Add(CreateFoldingReference(objAst.Extent, debugKindName(objAst))); }
71+
return AstVisitAction.Continue;
72+
}
73+
public override AstVisitAction VisitStringConstantExpression(StringConstantExpressionAst objAst)
74+
{
75+
if (IsValidFoldingExtent(objAst.Extent)) { this.FoldableRegions.Add(CreateFoldingReference(objAst.Extent, debugKindName(objAst))); }
76+
return AstVisitAction.Continue;
77+
}
78+
79+
public override AstVisitAction VisitHashtable(HashtableAst objAst)
80+
{
81+
if (IsValidFoldingExtent(objAst.Extent)) { this.FoldableRegions.Add(CreateFoldingReference(objAst.Extent, debugKindName(objAst))); }
82+
return AstVisitAction.Continue;
83+
}
84+
85+
public override AstVisitAction VisitStatementBlock(StatementBlockAst objAst)
86+
{
87+
// These parent visitors will get this AST Object. No need to process it
88+
if (objAst.Parent == null) { return AstVisitAction.Continue; }
89+
if (objAst.Parent is ArrayExpressionAst) { return AstVisitAction.Continue; }
90+
if (IsValidFoldingExtent(objAst.Extent)) { this.FoldableRegions.Add(CreateFoldingReference(objAst.Extent, debugKindName(objAst))); }
91+
return AstVisitAction.Continue;
92+
}
93+
94+
public override AstVisitAction VisitSubExpression(SubExpressionAst objAst)
95+
{
96+
if (IsValidFoldingExtent(objAst.Extent)) { this.FoldableRegions.Add(CreateFoldingReference(objAst.Extent, debugKindName(objAst))); }
97+
return AstVisitAction.Continue;
98+
}
99+
100+
public override AstVisitAction VisitScriptBlock(ScriptBlockAst objAst)
101+
{
102+
// If the Parent object is null then this represents the entire script. We don't want to fold that
103+
if (objAst.Parent == null) { return AstVisitAction.Continue; }
104+
// The ScriptBlockExpressionAst visitor will get this AST Object. No need to process it
105+
if (objAst.Parent is ScriptBlockExpressionAst) { return AstVisitAction.Continue; }
106+
if (IsValidFoldingExtent(objAst.Extent)) { this.FoldableRegions.Add(CreateFoldingReference(objAst.Extent, debugKindName(objAst))); }
107+
return AstVisitAction.Continue;
108+
}
109+
110+
public override AstVisitAction VisitScriptBlockExpression(ScriptBlockExpressionAst objAst)
111+
{
112+
if (IsValidFoldingExtent(objAst.Extent)) {
113+
FoldingReference foldRef = CreateFoldingReference(objAst.ScriptBlock.Extent, debugKindName(objAst));
114+
if (objAst.Parent == null) { return AstVisitAction.Continue; }
115+
if (objAst.Parent is InvokeMemberExpressionAst) {
116+
// This is a bit naive. The ScriptBlockExpressionAst Extent does not include the actual { and }
117+
// characters so the StartCharacter and EndCharacter indexes are out by one. This could be a bug in
118+
// PowerShell Parser. This is just a workaround
119+
foldRef.StartCharacter--;
120+
foldRef.EndCharacter++;
121+
}
122+
this.FoldableRegions.Add(foldRef);
123+
}
124+
return AstVisitAction.Continue;
125+
}
126+
127+
// /// <summary>
128+
// /// Checks to see if this command ast is the symbol we are looking for.
129+
// /// </summary>
130+
// /// <param name="commandAst">A CommandAst object in the script's AST</param>
131+
// /// <returns>A decision to stop searching if the right symbol was found,
132+
// /// or a decision to continue if it wasn't found</returns>
133+
// public override AstVisitAction VisitCommand(CommandAst commandAst)
134+
// {
135+
// Ast commandNameAst = commandAst.CommandElements[0];
136+
137+
// if (this.IsPositionInExtent(commandNameAst.Extent))
138+
// {
139+
// this.FoundSymbolReference =
140+
// new SymbolReference(
141+
// SymbolType.Function,
142+
// commandNameAst.Extent);
143+
144+
// return AstVisitAction.StopVisit;
145+
// }
146+
147+
// return base.VisitCommand(commandAst);
148+
// }
149+
150+
// /// <summary>
151+
// /// Checks to see if this function definition is the symbol we are looking for.
152+
// /// </summary>
153+
// /// <param name="functionDefinitionAst">A functionDefinitionAst object in the script's AST</param>
154+
// /// <returns>A decision to stop searching if the right symbol was found,
155+
// /// or a decision to continue if it wasn't found</returns>
156+
// public override AstVisitAction VisitFunctionDefinition(FunctionDefinitionAst functionDefinitionAst)
157+
// {
158+
// int startColumnNumber = 1;
159+
160+
// if (!this.includeFunctionDefinitions)
161+
// {
162+
// startColumnNumber =
163+
// functionDefinitionAst.Extent.Text.IndexOf(
164+
// functionDefinitionAst.Name) + 1;
165+
// }
166+
167+
// IScriptExtent nameExtent = new ScriptExtent()
168+
// {
169+
// Text = functionDefinitionAst.Name,
170+
// StartLineNumber = functionDefinitionAst.Extent.StartLineNumber,
171+
// EndLineNumber = functionDefinitionAst.Extent.EndLineNumber,
172+
// StartColumnNumber = startColumnNumber,
173+
// EndColumnNumber = startColumnNumber + functionDefinitionAst.Name.Length
174+
// };
175+
176+
// if (this.IsPositionInExtent(nameExtent))
177+
// {
178+
// this.FoundSymbolReference =
179+
// new SymbolReference(
180+
// SymbolType.Function,
181+
// nameExtent);
182+
183+
// return AstVisitAction.StopVisit;
184+
// }
185+
186+
// return base.VisitFunctionDefinition(functionDefinitionAst);
187+
// }
188+
189+
// /// <summary>
190+
// /// Checks to see if this command parameter is the symbol we are looking for.
191+
// /// </summary>
192+
// /// <param name="commandParameterAst">A CommandParameterAst object in the script's AST</param>
193+
// /// <returns>A decision to stop searching if the right symbol was found,
194+
// /// or a decision to continue if it wasn't found</returns>
195+
// public override AstVisitAction VisitCommandParameter(CommandParameterAst commandParameterAst)
196+
// {
197+
// if (this.IsPositionInExtent(commandParameterAst.Extent))
198+
// {
199+
// this.FoundSymbolReference =
200+
// new SymbolReference(
201+
// SymbolType.Parameter,
202+
// commandParameterAst.Extent);
203+
// return AstVisitAction.StopVisit;
204+
// }
205+
// return AstVisitAction.Continue;
206+
// }
207+
208+
// /// <summary>
209+
// /// Checks to see if this variable expression is the symbol we are looking for.
210+
// /// </summary>
211+
// /// <param name="variableExpressionAst">A VariableExpressionAst object in the script's AST</param>
212+
// /// <returns>A decision to stop searching if the right symbol was found,
213+
// /// or a decision to continue if it wasn't found</returns>
214+
// public override AstVisitAction VisitVariableExpression(VariableExpressionAst variableExpressionAst)
215+
// {
216+
// if (this.IsPositionInExtent(variableExpressionAst.Extent))
217+
// {
218+
// this.FoundSymbolReference =
219+
// new SymbolReference(
220+
// SymbolType.Variable,
221+
// variableExpressionAst.Extent);
222+
223+
// return AstVisitAction.StopVisit;
224+
// }
225+
226+
// return AstVisitAction.Continue;
227+
// }
228+
229+
// /// <summary>
230+
// /// Is the position of the given location is in the ast's extent
231+
// /// </summary>
232+
// /// <param name="extent">The script extent of the element</param>
233+
// /// <returns>True if the given position is in the range of the element's extent </returns>
234+
// private bool IsPositionInExtent(IScriptExtent extent)
235+
// {
236+
// return (extent.StartLineNumber == lineNumber &&
237+
// extent.StartColumnNumber <= columnNumber &&
238+
// extent.EndColumnNumber >= columnNumber);
239+
// }
240+
}
241+
}
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
//
2+
// Copyright (c) Microsoft. All rights reserved.
3+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
4+
//
5+
6+
using System;
7+
using System.Collections.Generic;
8+
using System.Management.Automation.Language;
9+
// using System.Text.RegularExpressions;
10+
11+
namespace Microsoft.PowerShell.EditorServices
12+
{
13+
14+
/// <summary>
15+
/// Provides common operations for the tokens of a parsed script.
16+
/// </summary>
17+
internal static class FoldingOperations
18+
{
19+
/// <summary>
20+
/// Extracts all of the unique foldable regions in a script given the list tokens
21+
/// </summary>
22+
internal static FoldingReference[] FoldableRegions(
23+
Token[] tokens,
24+
Ast scriptAst,
25+
bool ShowLastLine)
26+
{
27+
List<FoldingReference> foldableRegions = new List<FoldingReference>();
28+
29+
// System.Console.ForegroundColor = ConsoleColor.White;
30+
// System.Console.WriteLine("-###---------------------###-");
31+
// IEnumerable<Ast> xxx = scriptAst.FindAll(a => a is Ast, true);
32+
// foreach(Ast objAst in xxx) {
33+
// System.Console.ForegroundColor = ConsoleColor.White;
34+
// System.Console.WriteLine("&& " + objAst.Extent.StartColumnNumber + " " +
35+
// objAst.GetType().ToString() + " " + objAst.ToString());
36+
// }
37+
38+
// Add regions from AST
39+
foldableRegions.AddRange(Microsoft.PowerShell.EditorServices.AstOperations.FindFoldsInDocument(scriptAst));
40+
41+
// foldableRegions.ForEach( (FoldingReference item) => {
42+
// System.Console.ForegroundColor = ConsoleColor.White;
43+
// System.Console.WriteLine("---------------------------");
44+
// System.Console.ForegroundColor = ConsoleColor.White;
45+
// System.Console.WriteLine("(" +
46+
// item.StartLine.ToString() + ", " + item.StartCharacter.ToString() +
47+
// " -> " + item.EndLine.ToString() + ", " + item.EndCharacter.ToString() +
48+
// ") kind=" + item.Kind
49+
// );
50+
// });
51+
52+
// Add regions from Tokens
53+
foldableRegions.AddRange(Microsoft.PowerShell.EditorServices.TokenOperations.FoldableRegions2(tokens));
54+
55+
// Remove any null entries. Nulls appear if the folding reference is invalid
56+
// or missing
57+
foldableRegions.RemoveAll(item => item == null);
58+
59+
//
60+
61+
// Sort the FoldingReferences, starting at the top of the document,
62+
// and ensure that, in the case of multiple ranges starting the same line,
63+
// that the largest range (i.e. most number of lines spanned) is sorted
64+
// first. This is needed to detect duplicate regions. The first in the list
65+
// will be used and subsequent duplicates ignored.
66+
foldableRegions.Sort();
67+
68+
// It's possible to have duplicate or overlapping ranges, that is, regions which have the same starting
69+
// line number as the previous region. Therefore only emit ranges which have a different starting line
70+
// than the previous range.
71+
foldableRegions.RemoveAll( (FoldingReference item) => {
72+
// Note - I'm not happy with searching here, but as the RemoveAll
73+
// doesn't expose the index in the List, we need to calculate it. Fortunately the
74+
// list is sorted at this point, so we can use BinarySearch.
75+
int index = foldableRegions.BinarySearch(item);
76+
if (index == 0) { return false; }
77+
return (item.StartLine == foldableRegions[index - 1].StartLine);
78+
});
79+
80+
// Some editors have different folding UI, sometimes the lastline should be displayed
81+
// If we do want to show the last line, just change the region to be one line less
82+
if (ShowLastLine) {
83+
foldableRegions.ForEach( item => { item.EndLine--; });
84+
}
85+
86+
return foldableRegions.ToArray();
87+
}
88+
}
89+
}

0 commit comments

Comments
 (0)