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
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ internal void EnsureInitialized()
}

_parent.ScriptAst.Visit(new SymbolVisitor(_parent, AddReference));
RegionVisitor.GetRegionsInDocument(_parent, AddReference);
fflaten marked this conversation as resolved.
Show resolved Hide resolved
}

private AstVisitAction AddReference(SymbolReference symbol)
Expand Down
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
Expand Up @@ -160,7 +160,8 @@ public async Task<IEnumerable<SymbolReference>> ScanForReferencesOfSymbolAsync(
SymbolReference symbol,
CancellationToken cancellationToken = default)
{
if (symbol is null)
// Never scan for regions as they're only useful in current document
if (symbol is null || symbol.Type is SymbolType.Region)
fflaten marked this conversation as resolved.
Show resolved Hide resolved
{
return Enumerable.Empty<SymbolReference>();
}
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 void GetRegionsInDocument(ScriptFile file, Func<SymbolReference, AstVisitAction> action)
Copy link
Member

Choose a reason for hiding this comment

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

I like this a lot, makes reworking it much easier!

Copy link
Contributor Author

@fflaten fflaten Feb 24, 2023

Choose a reason for hiding this comment

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

@andschwa: I assume you meant the separate class.
Because the Func-parameter won't make much sense when moving out of ReferenceTable, would it? Unless you'd prefer Action<SymbolReference>-parameter over IEnumerable return type.

{
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);

action(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 @@ -53,6 +53,11 @@ public override Task<DocumentHighlightContainer> Handle(
List<DocumentHighlight> highlights = new();
foreach (SymbolReference occurrence in occurrences)
{
if (occurrence.Type is SymbolType.Region)
fflaten marked this conversation as resolved.
Show resolved Hide resolved
{
continue;
}

highlights.Add(new DocumentHighlight
{
Kind = DocumentHighlightKind.Write, // TODO: Which symbol types are writable?
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ await _symbolsService.FindSymbolDetailsAtLocationAsync(
request.Position.Line + 1,
request.Position.Character + 1).ConfigureAwait(false);

if (symbolDetails is null)
if (symbolDetails is null || symbolDetails.SymbolReference.Type is SymbolType.Region)
fflaten marked this conversation as resolved.
Show resolved Hide resolved
{
return null;
}
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,13 @@ enum AEnum {
AFunction
1..3 | AFilter
AnAdvancedFunction

<#
#region don't find me
abc
#endregion
#>
#region my region 123

#endregion
#region unclosed region
Original file line number Diff line number Diff line change
Expand Up @@ -821,6 +821,13 @@ public void FindsSymbolsInFile()
Assert.Equal("prop AValue", symbol.Id);
Assert.Equal("AValue", symbol.Name);
Assert.True(symbol.IsDeclaration);

symbol = Assert.Single(symbols.Where(i => i.Type == SymbolType.Region));
Assert.Equal("region my region 123", symbol.Id);
Assert.Equal("#region my region 123", symbol.Name);
AssertIsRegion(symbol.NameRegion, 50, 1, 50, 22);
AssertIsRegion(symbol.ScriptRegion, 50, 1, 52, 11);
Assert.True(symbol.IsDeclaration);
fflaten marked this conversation as resolved.
Show resolved Hide resolved
}

[Fact]
Expand Down