Skip to content

Commit f17d929

Browse files
committed
(todo) Use AST
1 parent f5f46ae commit f17d929

File tree

6 files changed

+360
-86
lines changed

6 files changed

+360
-86
lines changed

src/PowerShellEditorServices.Protocol/Server/LanguageServer.cs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -535,7 +535,7 @@ private async Task HandleGetCommandRequestAsync(
535535
{
536536
PSCommand psCommand = new PSCommand();
537537
if (!string.IsNullOrEmpty(param))
538-
{
538+
{
539539
psCommand.AddCommand("Microsoft.PowerShell.Core\\Get-Command").AddArgument(param);
540540
}
541541
else
@@ -1267,7 +1267,7 @@ protected async Task HandleCodeActionRequest(
12671267
}
12681268
}
12691269

1270-
// Add "show documentation" commands last so they appear at the bottom of the client UI.
1270+
// Add "show documentation" commands last so they appear at the bottom of the client UI.
12711271
// These commands do not require code fixes. Sometimes we get a batch of diagnostics
12721272
// to create commands for. No need to create multiple show doc commands for the same rule.
12731273
var ruleNamesProcessed = new HashSet<string>();
@@ -1382,8 +1382,10 @@ private FoldingRange[] Fold(
13821382
// TODO Should be using dynamic registrations
13831383
if (!this.currentSettings.CodeFolding.Enable) { return null; }
13841384
var result = new List<FoldingRange>();
1385-
foreach (FoldingReference fold in TokenOperations.FoldableRegions(
1386-
editorSession.Workspace.GetFile(documentUri).ScriptTokens,
1385+
ScriptFile script = editorSession.Workspace.GetFile(documentUri);
1386+
foreach (FoldingReference fold in FoldingOperations.FoldableRegions(
1387+
script.ScriptTokens,
1388+
script.ScriptAst,
13871389
this.currentSettings.CodeFolding.ShowLastLine))
13881390
{
13891391
result.Add(new FoldingRange {
@@ -1734,7 +1736,7 @@ await eventSender(
17341736
});
17351737
}
17361738

1737-
// Generate a unique id that is used as a key to look up the associated code action (code fix) when
1739+
// Generate a unique id that is used as a key to look up the associated code action (code fix) when
17381740
// we receive and process the textDocument/codeAction message.
17391741
private static string GetUniqueIdFromDiagnostic(Diagnostic diagnostic)
17401742
{

src/PowerShellEditorServices/Language/AstOperations.cs

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

331331
return dotSourcedVisitor.DotSourcedFiles.ToArray();
332332
}
333+
334+
/// <summary>
335+
/// Finds all foldable regions in a script based on AST
336+
/// </summary>
337+
/// <param name="scriptAst">The abstract syntax tree of the given script</param>
338+
/// <returns>A collection of SymbolReference objects</returns>
339+
static public IEnumerable<FoldingReference> FindFoldsInDocument(Ast scriptAst)
340+
{
341+
FindFoldsVisitor findFoldsVisitor = new FindFoldsVisitor();
342+
scriptAst.Visit(findFoldsVisitor);
343+
344+
// findFoldsVisitor.FoldableRegions.ForEach( (FoldingReference item) => {
345+
// System.Console.ForegroundColor = ConsoleColor.White;
346+
// System.Console.WriteLine("---------------------------");
347+
348+
// });
349+
350+
return findFoldsVisitor.FoldableRegions;
351+
}
352+
333353
}
334354
}
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
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+
public FindFoldsVisitor()
23+
{
24+
this.FoldableRegions = new List<FoldingReference>();
25+
}
26+
27+
/// <summary>
28+
/// Creates an instance of a FoldingReference object from a start and end langauge Token
29+
/// Returns null if the line range is invalid
30+
/// </summary>
31+
private bool IsValidFoldingExtent(
32+
IScriptExtent extent)
33+
{
34+
// if (DEBUGMODE) { return true; }
35+
if (extent.EndLineNumber == extent.StartLineNumber) { return false; }
36+
return true;
37+
}
38+
39+
/// <summary>
40+
/// Creates an instance of a FoldingReference object from a start and end langauge Token
41+
/// Returns null if the line range is invalid
42+
/// </summary>
43+
private FoldingReference CreateFoldingReference(
44+
IScriptExtent extent,
45+
string matchKind)
46+
{
47+
// Extents are base 1, but LSP is base 0, so minus 1 off all lines and character positions
48+
return new FoldingReference {
49+
StartLine = extent.StartLineNumber - 1,
50+
StartCharacter = extent.StartColumnNumber - 1,
51+
EndLine = extent.EndLineNumber - 1,
52+
EndCharacter = extent.EndColumnNumber - 1,
53+
Kind = matchKind
54+
};
55+
}
56+
57+
static private string debugKindName(object obj) {
58+
if (DEBUGMODE) { return obj.GetType().ToString(); }
59+
return null;
60+
}
61+
62+
public override AstVisitAction VisitArrayExpression(ArrayExpressionAst objAst)
63+
{
64+
if (IsValidFoldingExtent(objAst.Extent)) { this.FoldableRegions.Add(CreateFoldingReference(objAst.Extent, debugKindName(objAst))); }
65+
return AstVisitAction.Continue;
66+
}
67+
public override AstVisitAction VisitStringConstantExpression(StringConstantExpressionAst objAst)
68+
{
69+
if (IsValidFoldingExtent(objAst.Extent)) { this.FoldableRegions.Add(CreateFoldingReference(objAst.Extent, debugKindName(objAst))); }
70+
return AstVisitAction.Continue;
71+
}
72+
73+
public override AstVisitAction VisitHashtable(HashtableAst 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 VisitStatementBlock(StatementBlockAst objAst)
80+
{
81+
// These parent visitors will get this AST Object. No need to process it
82+
if (objAst.Parent == null) { return AstVisitAction.Continue; }
83+
if (objAst.Parent is ArrayExpressionAst) { return AstVisitAction.Continue; }
84+
if (IsValidFoldingExtent(objAst.Extent)) { this.FoldableRegions.Add(CreateFoldingReference(objAst.Extent, debugKindName(objAst))); }
85+
return AstVisitAction.Continue;
86+
}
87+
88+
public override AstVisitAction VisitSubExpression(SubExpressionAst objAst)
89+
{
90+
if (IsValidFoldingExtent(objAst.Extent)) { this.FoldableRegions.Add(CreateFoldingReference(objAst.Extent, debugKindName(objAst))); }
91+
return AstVisitAction.Continue;
92+
}
93+
94+
public override AstVisitAction VisitScriptBlock(ScriptBlockAst objAst)
95+
{
96+
// If the Parent object is null then this represents the entire script. We don't want to fold that
97+
if (objAst.Parent == null) { return AstVisitAction.Continue; }
98+
// The ScriptBlockExpressionAst visitor will get this AST Object. No need to process it
99+
if (objAst.Parent is ScriptBlockExpressionAst) { return AstVisitAction.Continue; }
100+
if (IsValidFoldingExtent(objAst.Extent)) { this.FoldableRegions.Add(CreateFoldingReference(objAst.Extent, debugKindName(objAst))); }
101+
return AstVisitAction.Continue;
102+
}
103+
104+
public override AstVisitAction VisitScriptBlockExpression(ScriptBlockExpressionAst objAst)
105+
{
106+
if (IsValidFoldingExtent(objAst.Extent)) {
107+
FoldingReference foldRef = CreateFoldingReference(objAst.ScriptBlock.Extent, debugKindName(objAst));
108+
if (objAst.Parent == null) { return AstVisitAction.Continue; }
109+
if (objAst.Parent is InvokeMemberExpressionAst) {
110+
// This is a bit naive. The ScriptBlockExpressionAst Extent does not include the actual { and }
111+
// characters so the StartCharacter and EndCharacter indexes are out by one. This could be a bug in
112+
// PowerShell Parser. This is just a workaround
113+
foldRef.StartCharacter--;
114+
foldRef.EndCharacter++;
115+
}
116+
this.FoldableRegions.Add(foldRef);
117+
}
118+
return AstVisitAction.Continue;
119+
}
120+
121+
public override AstVisitAction VisitVariableExpression(VariableExpressionAst objAst)
122+
{
123+
if (IsValidFoldingExtent(objAst.Extent)) { this.FoldableRegions.Add(CreateFoldingReference(objAst.Extent, debugKindName(objAst))); }
124+
return AstVisitAction.Continue;
125+
}
126+
}
127+
}
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)