Skip to content

Commit f087363

Browse files
Add RegionDocumentSymbolProvider.cs
Co-authored-by: Andy Jordan <[email protected]>
1 parent 91d5b88 commit f087363

File tree

6 files changed

+113
-7
lines changed

6 files changed

+113
-7
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
using System.Collections.Generic;
5+
using System.Management.Automation.Language;
6+
using Microsoft.PowerShell.EditorServices.Services.TextDocument;
7+
8+
namespace Microsoft.PowerShell.EditorServices.Services.Symbols
9+
{
10+
/// <summary>
11+
/// Provides an IDocumentSymbolProvider implementation for
12+
/// enumerating regions as symbols in script (.psd1, .psm1) files.
13+
/// </summary>
14+
internal class RegionDocumentSymbolProvider : IDocumentSymbolProvider
15+
{
16+
string IDocumentSymbolProvider.ProviderId => nameof(RegionDocumentSymbolProvider);
17+
18+
IEnumerable<SymbolReference> IDocumentSymbolProvider.ProvideDocumentSymbols(ScriptFile scriptFile)
19+
{
20+
Stack<Token> tokenCommentRegionStack = new();
21+
Token[] tokens = scriptFile.ScriptTokens;
22+
23+
for (int i = 0; i < tokens.Length; i++)
24+
{
25+
Token token = tokens[i];
26+
27+
// Exclude everything but single-line comments
28+
if (token.Kind != TokenKind.Comment ||
29+
token.Extent.StartLineNumber != token.Extent.EndLineNumber ||
30+
!TokenOperations.IsBlockComment(i, tokens))
31+
{
32+
continue;
33+
}
34+
35+
// Processing for #region -> #endregion
36+
if (TokenOperations.s_startRegionTextRegex.IsMatch(token.Text))
37+
{
38+
tokenCommentRegionStack.Push(token);
39+
continue;
40+
}
41+
42+
if (TokenOperations.s_endRegionTextRegex.IsMatch(token.Text))
43+
{
44+
// Mismatched regions in the script can cause bad stacks.
45+
if (tokenCommentRegionStack.Count > 0)
46+
{
47+
Token regionStart = tokenCommentRegionStack.Pop();
48+
Token regionEnd = token;
49+
50+
BufferRange regionRange = new(
51+
regionStart.Extent.StartLineNumber,
52+
regionStart.Extent.StartColumnNumber,
53+
regionEnd.Extent.EndLineNumber,
54+
regionEnd.Extent.EndColumnNumber);
55+
56+
yield return new SymbolReference(
57+
SymbolType.Region,
58+
regionStart.Extent.Text.Trim().TrimStart('#'),
59+
regionStart.Extent.Text.Trim(),
60+
regionStart.Extent,
61+
new ScriptExtent()
62+
{
63+
Text = string.Join(System.Environment.NewLine, scriptFile.GetLinesInRange(regionRange)),
64+
StartLineNumber = regionStart.Extent.StartLineNumber,
65+
StartColumnNumber = regionStart.Extent.StartColumnNumber,
66+
StartOffset = regionStart.Extent.StartOffset,
67+
EndLineNumber = regionEnd.Extent.EndLineNumber,
68+
EndColumnNumber = regionEnd.Extent.EndColumnNumber,
69+
EndOffset = regionEnd.Extent.EndOffset,
70+
File = regionStart.Extent.File
71+
},
72+
scriptFile,
73+
isDeclaration: true);
74+
}
75+
}
76+
}
77+
}
78+
}
79+
}

src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -77,13 +77,13 @@ public SymbolsService(
7777
PesterCodeLensProvider pesterProvider = new(configurationService);
7878
_ = _codeLensProviders.TryAdd(pesterProvider.ProviderId, pesterProvider);
7979

80-
// TODO: Is this complication so necessary?
8180
_documentSymbolProviders = new ConcurrentDictionary<string, IDocumentSymbolProvider>();
8281
IDocumentSymbolProvider[] documentSymbolProviders = new IDocumentSymbolProvider[]
8382
{
8483
new ScriptDocumentSymbolProvider(),
8584
new PsdDocumentSymbolProvider(),
86-
new PesterDocumentSymbolProvider(),
85+
new PesterDocumentSymbolProvider()
86+
// NOTE: This specifically does not include RegionDocumentSymbolProvider.
8787
};
8888

8989
foreach (IDocumentSymbolProvider documentSymbolProvider in documentSymbolProviders)

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

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright (c) Microsoft Corporation.
1+
// Copyright (c) Microsoft Corporation.
22
// Licensed under the MIT License.
33

44
using System;
@@ -35,7 +35,8 @@ public PsesDocumentSymbolHandler(ILoggerFactory factory, WorkspaceService worksp
3535
{
3636
new ScriptDocumentSymbolProvider(),
3737
new PsdDocumentSymbolProvider(),
38-
new PesterDocumentSymbolProvider()
38+
new PesterDocumentSymbolProvider(),
39+
new RegionDocumentSymbolProvider()
3940
};
4041
}
4142

src/PowerShellEditorServices/Services/TextDocument/TokenOperations.cs

+3-3
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,9 @@ internal static class TokenOperations
1717
// script. They are based on the defaults in the VS Code Language Configuration at;
1818
// https://github.com/Microsoft/vscode/blob/64186b0a26/extensions/powershell/language-configuration.json#L26-L31
1919
// https://github.com/Microsoft/vscode/issues/49070
20-
private static readonly Regex s_startRegionTextRegex = new(
20+
internal static readonly Regex s_startRegionTextRegex = new(
2121
@"^\s*#[rR]egion\b", RegexOptions.Compiled);
22-
private static readonly Regex s_endRegionTextRegex = new(
22+
internal static readonly Regex s_endRegionTextRegex = new(
2323
@"^\s*#[eE]nd[rR]egion\b", RegexOptions.Compiled);
2424

2525
/// <summary>
@@ -199,7 +199,7 @@ private static FoldingReference CreateFoldingReference(
199199
/// - Token text must start with a '#'.false This is because comment regions
200200
/// start with '&lt;#' but have the same TokenKind
201201
/// </summary>
202-
private static bool IsBlockComment(int index, Token[] tokens)
202+
internal static bool IsBlockComment(int index, Token[] tokens)
203203
{
204204
Token thisToken = tokens[index];
205205
if (thisToken.Kind != TokenKind.Comment) { return false; }

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

+10
Original file line numberDiff line numberDiff line change
@@ -41,3 +41,13 @@ enum AEnum {
4141
AFunction
4242
1..3 | AFilter
4343
AnAdvancedFunction
44+
45+
<#
46+
#region don't find me
47+
abc
48+
#endregion
49+
#>
50+
#region my region 123
51+
52+
#endregion
53+
#region unclosed region

test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs

+16
Original file line numberDiff line numberDiff line change
@@ -821,6 +821,22 @@ public void FindsSymbolsInFile()
821821
Assert.Equal("prop AValue", symbol.Id);
822822
Assert.Equal("AValue", symbol.Name);
823823
Assert.True(symbol.IsDeclaration);
824+
825+
// There should be no region symbols unless the provider has been registered.
826+
Assert.Empty(symbols.Where(i => i.Type == SymbolType.Region));
827+
}
828+
829+
[Fact]
830+
public void FindsRegionsInFile()
831+
{
832+
symbolsService.TryRegisterDocumentSymbolProvider(new RegionDocumentSymbolProvider());
833+
IEnumerable<SymbolReference> symbols = FindSymbolsInFile(FindSymbolsInMultiSymbolFile.SourceDetails);
834+
SymbolReference symbol = Assert.Single(symbols.Where(i => i.Type == SymbolType.Region));
835+
Assert.Equal("region my region 123", symbol.Id);
836+
Assert.Equal("#region my region 123", symbol.Name);
837+
AssertIsRegion(symbol.NameRegion, 50, 1, 50, 22);
838+
AssertIsRegion(symbol.ScriptRegion, 50, 1, 52, 11);
839+
Assert.True(symbol.IsDeclaration);
824840
}
825841

826842
[Fact]

0 commit comments

Comments
 (0)