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 #2003

Merged
merged 7 commits into from
Feb 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/PowerShellEditorServices/Server/PsesLanguageServer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,9 @@ public PsesLanguageServer(
/// cref="PsesServiceCollectionExtensions.AddPsesLanguageServices"/>.
/// </remarks>
/// <returns>A task that completes when the server is ready and listening.</returns>
#pragma warning disable CA1506 // Coupling complexity we don't care about
public async Task StartAsync()
#pragma warning restore CA1506
{
LanguageServer = await OmniSharp.Extensions.LanguageServer.Server.LanguageServer.From(options =>
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ private PssaCmdletAnalysisEngine(

/// <summary>
/// Format a script given its contents.
/// TODO: This needs to be cancellable.
Copy link
Member Author

Choose a reason for hiding this comment

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

This one is a big ol' "waiting on PSSA" right?

/// </summary>
/// <param name="scriptDefinition">The full text of a script.</param>
/// <param name="formatSettings">The formatter settings to use.</param>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.PowerShell.EditorServices.Services.TextDocument;
Expand All @@ -26,9 +27,8 @@ internal interface ICodeLensProvider
/// <param name="scriptFile">
/// The document for which CodeLenses should be provided.
/// </param>
/// <param name="cancellationToken"></param>
/// <returns>An array of CodeLenses.</returns>
CodeLens[] ProvideCodeLenses(ScriptFile scriptFile, CancellationToken cancellationToken);
/// <returns>An IEnumerable of CodeLenses.</returns>
IEnumerable<CodeLens> ProvideCodeLenses(ScriptFile scriptFile);

/// <summary>
/// Resolves a CodeLens that was created without a Command.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
Expand Down Expand Up @@ -97,21 +96,17 @@ private static CodeLens[] GetPesterLens(PesterSymbolReference pesterSymbol, Scri
/// Get all Pester CodeLenses for a given script file.
/// </summary>
/// <param name="scriptFile">The script file to get Pester CodeLenses for.</param>
/// <param name="cancellationToken"></param>
/// <returns>All Pester CodeLenses for the given script file.</returns>
public CodeLens[] ProvideCodeLenses(ScriptFile scriptFile, CancellationToken cancellationToken)
public IEnumerable<CodeLens> ProvideCodeLenses(ScriptFile scriptFile)
{
// Don't return anything if codelens setting is disabled
if (!_configurationService.CurrentSettings.Pester.CodeLens)
{
return Array.Empty<CodeLens>();
yield break;
}

List<CodeLens> lenses = new();
foreach (SymbolReference symbol in _symbolProvider.ProvideDocumentSymbols(scriptFile))
{
cancellationToken.ThrowIfCancellationRequested();

if (symbol is not PesterSymbolReference pesterSymbol)
{
continue;
Expand All @@ -129,10 +124,11 @@ public CodeLens[] ProvideCodeLenses(ScriptFile scriptFile, CancellationToken can
continue;
}

lenses.AddRange(GetPesterLens(pesterSymbol, scriptFile));
foreach (CodeLens codeLens in GetPesterLens(pesterSymbol, scriptFile))
{
yield return codeLens;
}
}

return lenses.ToArray();
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,34 +52,29 @@ public ReferencesCodeLensProvider(WorkspaceService workspaceService, SymbolsServ
/// Get all reference code lenses for a given script file.
/// </summary>
/// <param name="scriptFile">The PowerShell script file to get code lenses for.</param>
/// <param name="cancellationToken"></param>
/// <returns>An array of CodeLenses describing all functions, classes and enums in the given script file.</returns>
public CodeLens[] ProvideCodeLenses(ScriptFile scriptFile, CancellationToken cancellationToken)
/// <returns>An IEnumerable of CodeLenses describing all functions, classes and enums in the given script file.</returns>
public IEnumerable<CodeLens> ProvideCodeLenses(ScriptFile scriptFile)
{
List<CodeLens> acc = new();
foreach (SymbolReference symbol in _symbolProvider.ProvideDocumentSymbols(scriptFile))
{
cancellationToken.ThrowIfCancellationRequested();
// TODO: Can we support more here?
if (symbol.IsDeclaration &&
symbol.Type is
SymbolType.Function or
SymbolType.Class or
SymbolType.Enum)
{
acc.Add(new CodeLens
yield return new CodeLens
{
Data = JToken.FromObject(new
{
Uri = scriptFile.DocumentUri,
ProviderId = nameof(ReferencesCodeLensProvider)
}, LspSerializer.Instance.JsonSerializer),
Range = symbol.NameRegion.ToRange(),
});
};
}
}

return acc.ToArray();
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,11 @@ public static async Task<AliasMap> GetAliasesAsync(

foreach (AliasInfo aliasInfo in aliases.Cast<AliasInfo>())
{
if (cancellationToken.IsCancellationRequested)
{
break;
}

// TODO: When we move to netstandard2.1, we can use another overload which generates
// static delegates and thus reduces allocations.
s_cmdletToAliasCache.AddOrUpdate(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ commandAst.InvocationOperator is not (TokenKind.Dot or TokenKind.Ampersand) &&
/// <returns>true if the CommandAst represents a Pester command, false otherwise</returns>
private static bool IsPesterCommand(CommandAst commandAst)
{
if (commandAst == null)
if (commandAst is null)
{
return false;
}
Expand Down Expand Up @@ -94,7 +94,7 @@ private static PesterSymbolReference ConvertPesterAstToSymbolReference(ScriptFil

string commandName = CommandHelpers.StripModuleQualification(pesterCommandAst.GetCommandName(), out _);
PesterCommandType? commandType = PesterSymbolReference.GetCommandType(commandName);
if (commandType == null)
if (commandType is null)
{
return null;
}
Expand Down Expand Up @@ -247,10 +247,11 @@ internal PesterSymbolReference(

internal static PesterCommandType? GetCommandType(string commandName)
{
if (commandName == null || !PesterKeywords.TryGetValue(commandName, out PesterCommandType pesterCommandType))
if (commandName is null || !PesterKeywords.TryGetValue(commandName, out PesterCommandType pesterCommandType))
{
return null;
}

return pesterCommandType;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

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

namespace Microsoft.PowerShell.EditorServices.Services.Symbols
{
/// <summary>
/// Provides an IDocumentSymbolProvider implementation for
/// enumerating regions as symbols in script (.psd1, .psm1) files.
/// </summary>
internal class RegionDocumentSymbolProvider : IDocumentSymbolProvider
{
string IDocumentSymbolProvider.ProviderId => nameof(RegionDocumentSymbolProvider);

IEnumerable<SymbolReference> IDocumentSymbolProvider.ProvideDocumentSymbols(ScriptFile scriptFile)
{
Stack<Token> tokenCommentRegionStack = new();
Token[] tokens = scriptFile.ScriptTokens;

for (int i = 0; i < tokens.Length; i++)
{
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(System.Environment.NewLine, scriptFile.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
},
scriptFile,
isDeclaration: true);
}
}
}
}
}
}
10 changes: 7 additions & 3 deletions src/PowerShellEditorServices/Services/Symbols/SymbolDetails.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using System.Diagnostics;
using System.Management.Automation;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.PowerShell.EditorServices.Services.PowerShell;
using Microsoft.PowerShell.EditorServices.Services.PowerShell.Runspace;
Expand Down Expand Up @@ -37,7 +38,8 @@ internal class SymbolDetails
internal static async Task<SymbolDetails> CreateAsync(
SymbolReference symbolReference,
IRunspaceInfo currentRunspace,
IInternalPowerShellExecutionService executionService)
IInternalPowerShellExecutionService executionService,
CancellationToken cancellationToken)
{
SymbolDetails symbolDetails = new()
{
Expand All @@ -49,14 +51,16 @@ internal static async Task<SymbolDetails> CreateAsync(
CommandInfo commandInfo = await CommandHelpers.GetCommandInfoAsync(
symbolReference.Id,
currentRunspace,
executionService).ConfigureAwait(false);
executionService,
cancellationToken).ConfigureAwait(false);

if (commandInfo is not null)
{
symbolDetails.Documentation =
await CommandHelpers.GetCommandSynopsisAsync(
commandInfo,
executionService).ConfigureAwait(false);
executionService,
cancellationToken).ConfigureAwait(false);

if (commandInfo.CommandType == CommandTypes.Application)
{
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
18 changes: 13 additions & 5 deletions src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -77,13 +77,13 @@ public SymbolsService(
PesterCodeLensProvider pesterProvider = new(configurationService);
_ = _codeLensProviders.TryAdd(pesterProvider.ProviderId, pesterProvider);

// TODO: Is this complication so necessary?
_documentSymbolProviders = new ConcurrentDictionary<string, IDocumentSymbolProvider>();
IDocumentSymbolProvider[] documentSymbolProviders = new IDocumentSymbolProvider[]
{
new ScriptDocumentSymbolProvider(),
new PsdDocumentSymbolProvider(),
new PesterDocumentSymbolProvider(),
new PesterDocumentSymbolProvider()
// NOTE: This specifically does not include RegionDocumentSymbolProvider.
};

foreach (IDocumentSymbolProvider documentSymbolProvider in documentSymbolProviders)
Expand Down Expand Up @@ -187,7 +187,11 @@ public async Task<IEnumerable<SymbolReference>> ScanForReferencesOfSymbolAsync(
foreach (string targetIdentifier in allIdentifiers)
{
await Task.Yield();
cancellationToken.ThrowIfCancellationRequested();
if (cancellationToken.IsCancellationRequested)
{
break;
}

symbols.AddRange(file.References.TryGetReferences(symbol with { Id = targetIdentifier }));
}
}
Expand Down Expand Up @@ -218,12 +222,16 @@ public static IEnumerable<SymbolReference> FindOccurrencesInFile(
/// Finds the details of the symbol at the given script file location.
/// </summary>
public Task<SymbolDetails?> FindSymbolDetailsAtLocationAsync(
ScriptFile scriptFile, int line, int column)
ScriptFile scriptFile, int line, int column, CancellationToken cancellationToken)
{
SymbolReference? symbol = FindSymbolAtLocation(scriptFile, line, column);
return symbol is null
? Task.FromResult<SymbolDetails?>(null)
: SymbolDetails.CreateAsync(symbol, _runspaceContext.CurrentRunspace, _executionService);
: SymbolDetails.CreateAsync(
symbol,
_runspaceContext.CurrentRunspace,
_executionService,
cancellationToken);
}

/// <summary>
Expand Down
Loading