diff --git a/src/PowerShellEditorServices.Host/CodeLens/CodeLensFeature.cs b/src/PowerShellEditorServices.Host/CodeLens/CodeLensFeature.cs
index 4392523ac..ac5f388a3 100644
--- a/src/PowerShellEditorServices.Host/CodeLens/CodeLensFeature.cs
+++ b/src/PowerShellEditorServices.Host/CodeLens/CodeLensFeature.cs
@@ -9,6 +9,7 @@
using Microsoft.PowerShell.EditorServices.Utility;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
+using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
@@ -17,33 +18,23 @@
namespace Microsoft.PowerShell.EditorServices.CodeLenses
{
+ ///
+ /// Implements the CodeLens feature for EditorServices.
+ ///
internal class CodeLensFeature :
FeatureComponentBase,
ICodeLenses
{
- private EditorSession editorSession;
-
- private JsonSerializer jsonSerializer =
- JsonSerializer.Create(
- Constants.JsonSerializerSettings);
-
- public CodeLensFeature(
- EditorSession editorSession,
- IMessageHandlers messageHandlers,
- ILogger logger)
- : base(logger)
- {
- this.editorSession = editorSession;
-
- messageHandlers.SetRequestHandler(
- CodeLensRequest.Type,
- this.HandleCodeLensRequest);
-
- messageHandlers.SetRequestHandler(
- CodeLensResolveRequest.Type,
- this.HandleCodeLensResolveRequest);
- }
+ ///
+ /// Create a new CodeLens instance around a given editor session
+ /// from the component registry.
+ ///
+ ///
+ /// The component registry to provider other components and to register the CodeLens provider in.
+ ///
+ /// The editor session context of the CodeLens provider.
+ /// A new CodeLens provider for the given editor session.
public static CodeLensFeature Create(
IComponentRegistry components,
EditorSession editorSession)
@@ -51,9 +42,19 @@ public static CodeLensFeature Create(
var codeLenses =
new CodeLensFeature(
editorSession,
- components.Get(),
+ JsonSerializer.Create(Constants.JsonSerializerSettings),
components.Get());
+ var messageHandlers = components.Get();
+
+ messageHandlers.SetRequestHandler(
+ CodeLensRequest.Type,
+ codeLenses.HandleCodeLensRequest);
+
+ messageHandlers.SetRequestHandler(
+ CodeLensResolveRequest.Type,
+ codeLenses.HandleCodeLensResolveRequest);
+
codeLenses.Providers.Add(
new ReferencesCodeLensProvider(
editorSession));
@@ -67,42 +68,78 @@ public static CodeLensFeature Create(
return codeLenses;
}
+ ///
+ /// The editor session context to get workspace and language server data from.
+ ///
+ private readonly EditorSession _editorSession;
+
+ ///
+ /// The json serializer instance for CodeLens object translation.
+ ///
+ private readonly JsonSerializer _jsonSerializer;
+
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ private CodeLensFeature(
+ EditorSession editorSession,
+ JsonSerializer jsonSerializer,
+ ILogger logger)
+ : base(logger)
+ {
+ _editorSession = editorSession;
+ _jsonSerializer = jsonSerializer;
+ }
+
+ ///
+ /// Get all the CodeLenses for a given script file.
+ ///
+ /// The PowerShell script file to get CodeLenses for.
+ /// All generated CodeLenses for the given script file.
public CodeLens[] ProvideCodeLenses(ScriptFile scriptFile)
{
- return
- this.InvokeProviders(p => p.ProvideCodeLenses(scriptFile))
- .SelectMany(r => r)
- .ToArray();
+ return InvokeProviders(provider => provider.ProvideCodeLenses(scriptFile))
+ .SelectMany(codeLens => codeLens)
+ .ToArray();
}
+ ///
+ /// Handles a request for CodeLenses from VSCode.
+ ///
+ /// Parameters on the CodeLens request that was received.
+ ///
private async Task HandleCodeLensRequest(
CodeLensRequest codeLensParams,
RequestContext requestContext)
{
- JsonSerializer jsonSerializer =
- JsonSerializer.Create(
- Constants.JsonSerializerSettings);
+ ScriptFile scriptFile = _editorSession.Workspace.GetFile(
+ codeLensParams.TextDocument.Uri);
- var scriptFile =
- this.editorSession.Workspace.GetFile(
- codeLensParams.TextDocument.Uri);
+ CodeLens[] codeLensResults = ProvideCodeLenses(scriptFile);
- var codeLenses =
- this.ProvideCodeLenses(scriptFile)
- .Select(
- codeLens =>
- codeLens.ToProtocolCodeLens(
- new CodeLensData
- {
- Uri = codeLens.File.ClientFilePath,
- ProviderId = codeLens.Provider.ProviderId
- },
- this.jsonSerializer))
- .ToArray();
-
- await requestContext.SendResult(codeLenses);
+ var codeLensResponse = new LanguageServer.CodeLens[codeLensResults.Length];
+ for (int i = 0; i < codeLensResults.Length; i++)
+ {
+ codeLensResponse[i] = codeLensResults[i].ToProtocolCodeLens(
+ new CodeLensData
+ {
+ Uri = codeLensResults[i].File.ClientFilePath,
+ ProviderId = codeLensResults[i].Provider.ProviderId
+ },
+ _jsonSerializer);
+ }
+
+ await requestContext.SendResult(codeLensResponse);
}
+ ///
+ /// Handle a CodeLens resolve request from VSCode.
+ ///
+ /// The CodeLens to be resolved/updated.
+ ///
private async Task HandleCodeLensResolveRequest(
LanguageServer.CodeLens codeLens,
RequestContext requestContext)
@@ -113,13 +150,13 @@ private async Task HandleCodeLensResolveRequest(
CodeLensData codeLensData = codeLens.Data.ToObject();
ICodeLensProvider originalProvider =
- this.Providers.FirstOrDefault(
+ Providers.FirstOrDefault(
provider => provider.ProviderId.Equals(codeLensData.ProviderId));
if (originalProvider != null)
{
ScriptFile scriptFile =
- this.editorSession.Workspace.GetFile(
+ _editorSession.Workspace.GetFile(
codeLensData.Uri);
ScriptRegion region = new ScriptRegion
@@ -143,7 +180,7 @@ await originalProvider.ResolveCodeLensAsync(
await requestContext.SendResult(
resolvedCodeLens.ToProtocolCodeLens(
- this.jsonSerializer));
+ _jsonSerializer));
}
else
{
@@ -153,6 +190,9 @@ await requestContext.SendError(
}
}
+ ///
+ /// Represents data expected back in an LSP CodeLens response.
+ ///
private class CodeLensData
{
public string Uri { get; set; }
diff --git a/src/PowerShellEditorServices.Host/CodeLens/PesterCodeLensProvider.cs b/src/PowerShellEditorServices.Host/CodeLens/PesterCodeLensProvider.cs
index 61d700bb9..619ca0a49 100644
--- a/src/PowerShellEditorServices.Host/CodeLens/PesterCodeLensProvider.cs
+++ b/src/PowerShellEditorServices.Host/CodeLens/PesterCodeLensProvider.cs
@@ -15,71 +15,90 @@ namespace Microsoft.PowerShell.EditorServices.CodeLenses
{
internal class PesterCodeLensProvider : FeatureProviderBase, ICodeLensProvider
{
- private static char[] QuoteChars = new char[] { '\'', '"'};
+ ///
+ /// The editor session context to provide CodeLenses for.
+ ///
+ private EditorSession _editorSession;
- private EditorSession editorSession;
- private IDocumentSymbolProvider symbolProvider;
+ ///
+ /// The symbol provider to get symbols from to build code lenses with.
+ ///
+ private IDocumentSymbolProvider _symbolProvider;
+ ///
+ /// Create a new Pester CodeLens provider for a given editor session.
+ ///
+ /// The editor session context for which to provide Pester CodeLenses.
public PesterCodeLensProvider(EditorSession editorSession)
{
- this.editorSession = editorSession;
- this.symbolProvider = new PesterDocumentSymbolProvider();
+ _editorSession = editorSession;
+ _symbolProvider = new PesterDocumentSymbolProvider();
}
- private IEnumerable GetPesterLens(
+ ///
+ /// Get the Pester CodeLenses for a given Pester symbol.
+ ///
+ /// The Pester symbol to get CodeLenses for.
+ /// The script file the Pester symbol comes from.
+ /// All CodeLenses for the given Pester symbol.
+ private CodeLens[] GetPesterLens(
PesterSymbolReference pesterSymbol,
ScriptFile scriptFile)
{
- var clientCommands = new ClientCommand[]
+ var codeLensResults = new CodeLens[]
{
- new ClientCommand(
- "PowerShell.RunPesterTests",
- "Run tests",
- new object[]
- {
- scriptFile.ClientFilePath,
- false, // Don't debug
- pesterSymbol.TestName,
- }),
+ new CodeLens(
+ this,
+ scriptFile,
+ pesterSymbol.ScriptRegion,
+ new ClientCommand(
+ "PowerShell.RunPesterTests",
+ "Run tests",
+ new object[] { scriptFile.ClientFilePath, false /* No debug */, pesterSymbol.TestName })),
- new ClientCommand(
- "PowerShell.RunPesterTests",
- "Debug tests",
- new object[]
- {
- scriptFile.ClientFilePath,
- true, // Run in debugger
- pesterSymbol.TestName,
- }),
+ new CodeLens(
+ this,
+ scriptFile,
+ pesterSymbol.ScriptRegion,
+ new ClientCommand(
+ "PowerShell.RunPesterTests",
+ "Debug tests",
+ new object[] { scriptFile.ClientFilePath, true /* Run in debugger */, pesterSymbol.TestName })),
};
- return
- clientCommands.Select(
- command =>
- new CodeLens(
- this,
- scriptFile,
- pesterSymbol.ScriptRegion,
- command));
+ return codeLensResults;
}
+ ///
+ /// Get all Pester CodeLenses for a given script file.
+ ///
+ /// The script file to get Pester CodeLenses for.
+ /// All Pester CodeLenses for the given script file.
public CodeLens[] ProvideCodeLenses(ScriptFile scriptFile)
{
- var symbols =
- this.symbolProvider
- .ProvideDocumentSymbols(scriptFile);
+ var lenses = new List();
+ foreach (SymbolReference symbol in _symbolProvider.ProvideDocumentSymbols(scriptFile))
+ {
+ if (symbol is PesterSymbolReference pesterSymbol)
+ {
+ if (pesterSymbol.Command != PesterCommandType.Describe)
+ {
+ continue;
+ }
- var lenses =
- symbols
- .OfType()
- .Where(s => s.Command == PesterCommandType.Describe)
- .SelectMany(s => this.GetPesterLens(s, scriptFile))
- .Where(codeLens => codeLens != null)
- .ToArray();
+ lenses.AddRange(GetPesterLens(pesterSymbol, scriptFile));
+ }
+ }
- return lenses;
+ return lenses.ToArray();
}
+ ///
+ /// Resolve the CodeLens provision asynchronously -- just wraps the CodeLens argument in a task.
+ ///
+ /// The code lens to resolve.
+ ///
+ /// The given CodeLens, wrapped in a task.
public Task ResolveCodeLensAsync(
CodeLens codeLens,
CancellationToken cancellationToken)
diff --git a/src/PowerShellEditorServices.Host/CodeLens/ReferencesCodeLensProvider.cs b/src/PowerShellEditorServices.Host/CodeLens/ReferencesCodeLensProvider.cs
index abe29a997..c833aec27 100644
--- a/src/PowerShellEditorServices.Host/CodeLens/ReferencesCodeLensProvider.cs
+++ b/src/PowerShellEditorServices.Host/CodeLens/ReferencesCodeLensProvider.cs
@@ -6,6 +6,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
+using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.PowerShell.EditorServices.Commands;
@@ -14,87 +15,122 @@
namespace Microsoft.PowerShell.EditorServices.CodeLenses
{
+ ///
+ /// Provides the "reference" code lens by extracting document symbols.
+ ///
internal class ReferencesCodeLensProvider : FeatureProviderBase, ICodeLensProvider
{
- private EditorSession editorSession;
- private IDocumentSymbolProvider symbolProvider;
+ private static readonly Location[] s_emptyLocationArray = new Location[0];
+ ///
+ /// The editor session code lenses are being provided from.
+ ///
+ private EditorSession _editorSession;
+
+ ///
+ /// The document symbol provider to supply symbols to generate the code lenses.
+ ///
+ private IDocumentSymbolProvider _symbolProvider;
+
+ ///
+ /// Construct a new ReferencesCodeLensProvider for a given EditorSession.
+ ///
+ ///
public ReferencesCodeLensProvider(EditorSession editorSession)
{
- this.editorSession = editorSession;
+ _editorSession = editorSession;
// TODO: Pull this from components
- this.symbolProvider =
- new ScriptDocumentSymbolProvider(
- editorSession.PowerShellContext.LocalPowerShellVersion.Version);
+ _symbolProvider = new ScriptDocumentSymbolProvider(
+ editorSession.PowerShellContext.LocalPowerShellVersion.Version);
}
+ ///
+ /// Get all reference code lenses for a given script file.
+ ///
+ /// The PowerShell script file to get code lenses for.
+ /// An array of CodeLenses describing all functions in the given script file.
public CodeLens[] ProvideCodeLenses(ScriptFile scriptFile)
{
- return
- this.symbolProvider
- .ProvideDocumentSymbols(scriptFile)
- .Where(symbol => symbol.SymbolType == SymbolType.Function)
- .Select(
- symbol =>
- new CodeLens(
- this,
- scriptFile,
- symbol.ScriptRegion))
- .ToArray();
+ var acc = new List();
+ foreach (SymbolReference sym in _symbolProvider.ProvideDocumentSymbols(scriptFile))
+ {
+ if (sym.SymbolType == SymbolType.Function)
+ {
+ acc.Add(new CodeLens(this, scriptFile, sym.ScriptRegion));
+ }
+ }
+
+ return acc.ToArray();
}
+ ///
+ /// Take a codelens and create a new codelens object with updated references.
+ ///
+ /// The old code lens to get updated references for.
+ /// The cancellation token for this request.
+ /// A new code lens object describing the same data as the old one but with updated references.
public async Task ResolveCodeLensAsync(
CodeLens codeLens,
CancellationToken cancellationToken)
{
- ScriptFile[] references =
- editorSession.Workspace.ExpandScriptReferences(
- codeLens.File);
-
- var foundSymbol =
- this.editorSession.LanguageService.FindFunctionDefinitionAtLocation(
- codeLens.File,
- codeLens.ScriptExtent.StartLineNumber,
- codeLens.ScriptExtent.StartColumnNumber);
-
- FindReferencesResult referencesResult =
- await editorSession.LanguageService.FindReferencesOfSymbol(
- foundSymbol,
- references,
- editorSession.Workspace);
-
- Location[] referenceLocations =
- referencesResult == null
- ? new Location[0]
- : referencesResult
- .FoundReferences
- .Where(r => NotReferenceDefinition(foundSymbol, r))
- .Select(
- r => new Location
- {
- Uri = GetFileUri(r.FilePath),
- Range = r.ScriptRegion.ToRange()
- })
- .ToArray();
+ ScriptFile[] references = _editorSession.Workspace.ExpandScriptReferences(
+ codeLens.File);
- return
- new CodeLens(
- codeLens,
- new ClientCommand(
- "editor.action.showReferences",
- referenceLocations.Length == 1
- ? "1 reference"
- : $"{referenceLocations.Length} references",
- new object[]
- {
- codeLens.File.ClientFilePath,
- codeLens.ScriptExtent.ToRange().Start,
- referenceLocations,
- }
+ SymbolReference foundSymbol = _editorSession.LanguageService.FindFunctionDefinitionAtLocation(
+ codeLens.File,
+ codeLens.ScriptExtent.StartLineNumber,
+ codeLens.ScriptExtent.StartColumnNumber);
+
+ FindReferencesResult referencesResult = await _editorSession.LanguageService.FindReferencesOfSymbol(
+ foundSymbol,
+ references,
+ _editorSession.Workspace);
+
+ Location[] referenceLocations;
+ if (referencesResult == null)
+ {
+ referenceLocations = s_emptyLocationArray;
+ }
+ else
+ {
+ var acc = new List();
+ foreach (SymbolReference foundReference in referencesResult.FoundReferences)
+ {
+ if (!NotReferenceDefinition(foundSymbol, foundReference))
+ {
+ continue;
+ }
+
+ acc.Add(new Location
+ {
+ Uri = GetFileUri(foundReference.FilePath),
+ Range = foundReference.ScriptRegion.ToRange()
+ });
+ }
+ referenceLocations = acc.ToArray();
+ }
+
+ return new CodeLens(
+ codeLens,
+ new ClientCommand(
+ "editor.action.showReferences",
+ GetReferenceCountHeader(referenceLocations.Length),
+ new object[]
+ {
+ codeLens.File.ClientFilePath,
+ codeLens.ScriptExtent.ToRange().Start,
+ referenceLocations,
+ }
));
}
+ ///
+ /// Check whether a SymbolReference is not a reference to another defined symbol.
+ ///
+ /// The symbol definition that may be referenced.
+ /// The reference symbol to check.
+ /// True if the reference is not a reference to the definition, false otherwise.
private static bool NotReferenceDefinition(
SymbolReference definition,
SymbolReference reference)
@@ -105,6 +141,11 @@ private static bool NotReferenceDefinition(
|| !string.Equals(definition.SymbolName, reference.SymbolName, StringComparison.OrdinalIgnoreCase);
}
+ ///
+ /// Get a URI for a given file path.
+ ///
+ /// A file path that may be prefixed with URI scheme already.
+ /// A URI to the file.
private static string GetFileUri(string filePath)
{
// If the file isn't untitled, return a URI-style path
@@ -113,5 +154,24 @@ private static string GetFileUri(string filePath)
? new Uri("file://" + filePath).AbsoluteUri
: filePath;
}
+
+ ///
+ /// Get the code lens header for the number of references on a definition,
+ /// given the number of references.
+ ///
+ /// The number of references found for a given definition.
+ /// The header string for the reference code lens.
+ private static string GetReferenceCountHeader(int referenceCount)
+ {
+ if (referenceCount == 1)
+ {
+ return "1 reference";
+ }
+
+ var sb = new StringBuilder(14); // "100 references".Length = 14
+ sb.Append(referenceCount);
+ sb.Append(" references");
+ return sb.ToString();
+ }
}
}