Skip to content

Commit 12e63f8

Browse files
committed
add document symbol for #region
1 parent ba293c9 commit 12e63f8

File tree

6 files changed

+65
-7
lines changed

6 files changed

+65
-7
lines changed

src/PowerShellEditorServices/Services/Symbols/ScriptDocumentSymbolProvider.cs

+42-3
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System.Collections.Generic;
55
using System.Linq;
66
using System.Management.Automation.Language;
7+
using System.Text.RegularExpressions;
78
using Microsoft.PowerShell.EditorServices.Services.TextDocument;
89

910
namespace Microsoft.PowerShell.EditorServices.Services.Symbols
@@ -16,14 +17,26 @@ internal class ScriptDocumentSymbolProvider : IDocumentSymbolProvider
1617
{
1718
string IDocumentSymbolProvider.ProviderId => nameof(ScriptDocumentSymbolProvider);
1819

20+
// This regular expression are used to match lines which mark the start of a region comment in a script.
21+
// Based on the defaults in the VS Code Language Configuration at;
22+
// https://github.com/Microsoft/vscode/blob/64186b0a26/extensions/powershell/language-configuration.json#L26-L31
23+
// https://github.com/Microsoft/vscode/issues/49070
24+
private static readonly Regex s_startRegionTextRegex = new(
25+
@"^\s*#[rR]egion\b", RegexOptions.Compiled);
26+
1927
IEnumerable<ISymbolReference> IDocumentSymbolProvider.ProvideDocumentSymbols(
2028
ScriptFile scriptFile)
2129
{
30+
if (scriptFile?.ScriptAst is null)
31+
{
32+
return Enumerable.Empty<SymbolReference>();
33+
}
34+
2235
// If we have an AST, then we know it's a PowerShell file
2336
// so lets try to find symbols in the document.
24-
return scriptFile?.ScriptAst != null
25-
? FindSymbolsInDocument(scriptFile.ScriptAst)
26-
: Enumerable.Empty<SymbolReference>();
37+
IEnumerable<SymbolReference> astSymbols = FindSymbolsInDocument(scriptFile.ScriptAst);
38+
IEnumerable<SymbolReference> regions = FindRegionsInDocument(scriptFile.ScriptTokens);
39+
return astSymbols.Concat(regions);
2740
}
2841

2942
/// <summary>
@@ -48,5 +61,31 @@ public static IEnumerable<SymbolReference> FindSymbolsInDocument(Ast scriptAst)
4861
scriptAst.Visit(findSymbolsVisitor);
4962
return findSymbolsVisitor.SymbolReferences;
5063
}
64+
65+
private static IEnumerable<SymbolReference> FindRegionsInDocument(Token[] tokens)
66+
{
67+
for (int i = 0; i < tokens.Length; i++)
68+
{
69+
Token token = tokens[i];
70+
71+
// Exclude everything but single-line comments
72+
if (token.Kind != TokenKind.Comment ||
73+
token.Extent.StartLineNumber != token.Extent.EndLineNumber)
74+
{
75+
continue;
76+
}
77+
78+
// Look for <newline> #region <optional name>
79+
// Document symbols only care about the symbol start and regex is expensive,
80+
// so skip checking if region is actually closed with #endregion.
81+
if (TokenOperations.IsBlockComment(i, tokens) &&
82+
s_startRegionTextRegex.IsMatch(token.Text))
83+
{
84+
yield return new SymbolReference(
85+
SymbolType.Region,
86+
token.Extent);
87+
}
88+
}
89+
}
5190
}
5291
}

src/PowerShellEditorServices/Services/Symbols/SymbolType.cs

+6-1
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,11 @@ internal enum SymbolType
4141
/// <summary>
4242
/// The symbol is a hashtable key
4343
/// </summary>
44-
HashtableKey
44+
HashtableKey,
45+
46+
/// <summary>
47+
/// The symbol is a region
48+
/// </summary>
49+
Region
4550
}
4651
}

src/PowerShellEditorServices/Services/TextDocument/Handlers/DocumentSymbolHandler.cs

+2-1
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,8 @@ private static SymbolKind GetSymbolKind(SymbolType symbolType)
129129
return symbolType switch
130130
{
131131
SymbolType.Configuration or SymbolType.Function or SymbolType.Workflow => SymbolKind.Function,
132-
_ => SymbolKind.Variable,
132+
SymbolType.Region => SymbolKind.String,
133+
_ => SymbolKind.Variable
133134
};
134135
}
135136

src/PowerShellEditorServices/Services/TextDocument/TokenOperations.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,7 @@ private static FoldingReference CreateFoldingReference(
198198
/// - Token text must start with a '#'.false This is because comment regions
199199
/// start with '&lt;#' but have the same TokenKind
200200
/// </summary>
201-
private static bool IsBlockComment(int index, Token[] tokens)
201+
internal static bool IsBlockComment(int index, Token[] tokens)
202202
{
203203
Token thisToken = tokens[index];
204204
if (thisToken.Kind != TokenKind.Comment) { return false; }

test/PowerShellEditorServices.Test.Shared/Symbols/MultipleSymbols.ps1

+8-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,14 @@ workflow AWorkflow {}
2525
Configuration AConfiguration {
2626
Node "TEST-PC" {}
2727
}
28+
<#
29+
#region don't find me
30+
abc
31+
#endregion
32+
#>
33+
#region my region 123
34+
#endregion
2835

2936
AFunction
3037
1..3 | AFilter
31-
AnAdvancedFunction
38+
AnAdvancedFunction

test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs

+6
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,7 @@ public void FindsSymbolsInFile()
287287
Assert.Equal(4, symbolsResult.Count(symbolReference => symbolReference.SymbolType == SymbolType.Function));
288288
Assert.Equal(3, symbolsResult.Count(symbolReference => symbolReference.SymbolType == SymbolType.Variable));
289289
Assert.Single(symbolsResult.Where(symbolReference => symbolReference.SymbolType == SymbolType.Workflow));
290+
Assert.Single(symbolsResult.Where(symbolReference => symbolReference.SymbolType == SymbolType.Region));
290291

291292
SymbolReference firstFunctionSymbol = symbolsResult.First(r => r.SymbolType == SymbolType.Function);
292293
Assert.Equal("AFunction", firstFunctionSymbol.SymbolName);
@@ -303,6 +304,11 @@ public void FindsSymbolsInFile()
303304
Assert.Equal(23, firstWorkflowSymbol.ScriptRegion.StartLineNumber);
304305
Assert.Equal(1, firstWorkflowSymbol.ScriptRegion.StartColumnNumber);
305306

307+
SymbolReference firstRegionSymbol = symbolsResult.First(r => r.SymbolType == SymbolType.Region);
308+
Assert.Equal("#region my region 123", firstRegionSymbol.SymbolName);
309+
Assert.Equal(33, firstRegionSymbol.ScriptRegion.StartLineNumber);
310+
Assert.Equal(1, firstRegionSymbol.ScriptRegion.StartColumnNumber);
311+
306312
// TODO: Bring this back when we can use AstVisitor2 again (#276)
307313
//Assert.Equal(1, symbolsResult.FoundOccurrences.Where(r => r.SymbolType == SymbolType.Configuration).Count());
308314
//SymbolReference firstConfigurationSymbol = symbolsResult.FoundOccurrences.Where(r => r.SymbolType == SymbolType.Configuration).First();

0 commit comments

Comments
 (0)