Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add document symbols for #region #1911

Closed
wants to merge 10 commits into from
Closed
6 changes: 6 additions & 0 deletions src/PowerShellEditorServices/Services/Symbols/SymbolType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,11 @@ internal enum SymbolType
/// The symbol is a type reference
/// </summary>
Type,

/// <summary>
/// The symbol is a region. Only used for navigation-features.
/// </summary>
Region
}

internal static class SymbolTypeUtils
Expand All @@ -97,6 +102,7 @@ internal static SymbolKind GetSymbolKind(SymbolType symbolType)
SymbolType.Variable or SymbolType.Parameter => SymbolKind.Variable,
SymbolType.HashtableKey => SymbolKind.Key,
SymbolType.Type => SymbolKind.TypeParameter,
SymbolType.Region => SymbolKind.String,
SymbolType.Unknown or _ => SymbolKind.Object,
};
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System;
using System.Collections.Generic;
using System.Management.Automation.Language;
using Microsoft.PowerShell.EditorServices.Services.TextDocument;

namespace Microsoft.PowerShell.EditorServices.Services.Symbols
{
internal static class RegionVisitor
{
internal static IEnumerable<SymbolReference> GetRegionsInDocument(ScriptFile file)
{
Stack<Token> tokenCommentRegionStack = new();
Token[] tokens = file.ScriptTokens;

for (int i = 0; i < tokens.Length; i++)
fflaten marked this conversation as resolved.
Show resolved Hide resolved
{
Token token = tokens[i];

// Exclude everything but single-line comments
if (token.Kind != TokenKind.Comment ||
token.Extent.StartLineNumber != token.Extent.EndLineNumber ||
!TokenOperations.IsBlockComment(i, tokens))
{
continue;
}

// Processing for #region -> #endregion
if (TokenOperations.s_startRegionTextRegex.IsMatch(token.Text))
{
tokenCommentRegionStack.Push(token);
continue;
}

if (TokenOperations.s_endRegionTextRegex.IsMatch(token.Text))
{
// Mismatched regions in the script can cause bad stacks.
if (tokenCommentRegionStack.Count > 0)
{
Token regionStart = tokenCommentRegionStack.Pop();
Token regionEnd = token;

BufferRange regionRange = new(
regionStart.Extent.StartLineNumber,
regionStart.Extent.StartColumnNumber,
regionEnd.Extent.EndLineNumber,
regionEnd.Extent.EndColumnNumber);

yield return new SymbolReference(
SymbolType.Region,
regionStart.Extent.Text.Trim().TrimStart('#'),
regionStart.Extent.Text.Trim(),
regionStart.Extent,
new ScriptExtent()
{
Text = string.Join(Environment.NewLine, file.GetLinesInRange(regionRange)),
StartLineNumber = regionStart.Extent.StartLineNumber,
StartColumnNumber = regionStart.Extent.StartColumnNumber,
StartOffset = regionStart.Extent.StartOffset,
EndLineNumber = regionEnd.Extent.EndLineNumber,
EndColumnNumber = regionEnd.Extent.EndColumnNumber,
EndOffset = regionEnd.Extent.EndOffset,
File = regionStart.Extent.File
},
file,
isDeclaration: true);
}
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,7 @@ public override async Task<SymbolInformationOrDocumentSymbolContainer> Handle(Do
ScriptFile scriptFile = _workspaceService.GetFile(request.TextDocument.Uri);

IEnumerable<SymbolReference> foundSymbols = ProvideDocumentSymbols(scriptFile);
if (foundSymbols is null)
{
return null;
}
foundSymbols = foundSymbols.Concat(RegionVisitor.GetRegionsInDocument(scriptFile));

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't see a reason to keep the null-check. The local method ProvideDocumentSymbols() always returns a list, right?

string containerName = Path.GetFileNameWithoutExtension(scriptFile.FilePath);

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

/// <summary>
Expand Down Expand Up @@ -199,7 +199,7 @@ private static FoldingReference CreateFoldingReference(
/// - Token text must start with a '#'.false This is because comment regions
/// start with '&lt;#' but have the same TokenKind
/// </summary>
private static bool IsBlockComment(int index, Token[] tokens)
internal static bool IsBlockComment(int index, Token[] tokens)
{
Token thisToken = tokens[index];
if (thisToken.Kind != TokenKind.Comment) { return false; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,16 @@ enum AEnum {
AFunction
1..3 | AFilter
AnAdvancedFunction

<#
#region don't find me inside comment block
abc
#endregion
#>

#region find me outer
#region find me inner

#endregion
#endregion
#region ignore this unclosed region
32 changes: 32 additions & 0 deletions test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,13 @@ private IEnumerable<SymbolReference> FindSymbolsInFile(ScriptRegion scriptRegion
.OrderBy(symbol => symbol.ScriptRegion.ToRange().Start);
}

private IEnumerable<SymbolReference> FindRegionsInFile(ScriptRegion scriptRegion)
{
return RegionVisitor
.GetRegionsInDocument(GetScriptFile(scriptRegion))
.OrderBy(symbol => symbol.ScriptRegion.ToRange().Start);
}

[Fact]
public async Task FindsParameterHintsOnCommand()
{
Expand Down Expand Up @@ -952,5 +959,30 @@ public void FindsSymbolsInNoSymbolsFile()
IEnumerable<SymbolReference> symbolsResult = FindSymbolsInFile(FindSymbolsInNoSymbolsFile.SourceDetails);
Assert.Empty(symbolsResult);
}

[Fact]
public void FindsRegionSymbolsInFile()
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This technically doesn't belong here as SymbolsService isn't used for DocumentSymbolHandler. Move to own test-file? Suggestion?

{
IEnumerable<SymbolReference> symbols = FindRegionsInFile(FindSymbolsInMultiSymbolFile.SourceDetails);
Assert.Collection(symbols,
(i) =>
{
Assert.Equal("region find me outer", i.Id);
Assert.Equal("#region find me outer", i.Name);
Assert.Equal(SymbolType.Region, i.Type);
Assert.True(i.IsDeclaration);
AssertIsRegion(i.NameRegion, 51, 1, 51, 22);
AssertIsRegion(i.ScriptRegion, 51, 1, 55, 11);
},
(i) =>
{
Assert.Equal("region find me inner", i.Id);
Assert.Equal("#region find me inner", i.Name);
Assert.Equal(SymbolType.Region, i.Type);
Assert.True(i.IsDeclaration);
AssertIsRegion(i.NameRegion, 52, 1, 52, 22);
AssertIsRegion(i.ScriptRegion, 52, 1, 54, 11);
});
}
}
}