Skip to content
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
115 changes: 103 additions & 12 deletions src/PowerShellEditorServices.Host/LanguageServer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using System.IO;

namespace Microsoft.PowerShell.EditorServices.Host
{
Expand Down Expand Up @@ -535,29 +536,119 @@ protected async Task HandleDocumentSymbolRequest(
EditorSession editorSession,
RequestContext<SymbolInformation[], object> requestContext)
{
// TODO: Implement this with Keith's changes.
ScriptFile scriptFile =
editorSession.Workspace.GetFile(
textDocumentIdentifier.Uri);

FindOccurrencesResult foundSymbols =
editorSession.LanguageService.FindSymbolsInFile(
scriptFile);

SymbolInformation[] symbols = null;

// NOTE SymbolInformation.Location's Start/End Position are
// zero-based while the LanguageService APIs are one-based.
// Make sure to subtract line/column positions by 1 when creating
// the result list.
string containerName = Path.GetFileNameWithoutExtension(scriptFile.FilePath);

if (foundSymbols != null)
{
symbols =
foundSymbols
.FoundOccurrences
.Select(r =>
{
return new SymbolInformation {
ContainerName = containerName,
Kind = GetSymbolKind(r.SymbolType),
Location = new Location {
Uri = new Uri(r.FilePath).AbsolutePath,
Range = GetRangeFromScriptRegion(r.ScriptRegion)
},
Name = GetDecoratedSymbolName(r)
};
})
.ToArray();
}
else
{
symbols = new SymbolInformation[0];
}

await requestContext.SendResult(new SymbolInformation[0]);
await requestContext.SendResult(symbols);
}

private SymbolKind GetSymbolKind(SymbolType symbolType)
{
switch (symbolType)
{
case SymbolType.Configuration:
case SymbolType.Function:
case SymbolType.Workflow:
return SymbolKind.Function;

default:
return SymbolKind.Variable;
}
}

private string GetDecoratedSymbolName(SymbolReference symbolReference)
{
string name = symbolReference.SymbolName;

if (symbolReference.SymbolType == SymbolType.Configuration ||
symbolReference.SymbolType == SymbolType.Function ||
symbolReference.SymbolType == SymbolType.Workflow)
{
name += " { }";
}

return name;
}

protected async Task HandleWorkspaceSymbolRequest(
WorkspaceSymbolParams workspaceSymbolParams,
EditorSession editorSession,
RequestContext<SymbolInformation[], object> requestContext)
{
// TODO: Implement this with Keith's changes
var symbols = new List<SymbolInformation>();

// NOTE SymbolInformation.Location's Start/End Position are
// zero-based while the LanguageService APIs are one-based.
// Make sure to subtract line/column positions by 1 when creating
// the result list.
foreach (ScriptFile scriptFile in editorSession.Workspace.GetOpenedFiles())
{
FindOccurrencesResult foundSymbols =
editorSession.LanguageService.FindSymbolsInFile(
scriptFile);

await requestContext.SendResult(new SymbolInformation[0]);
// TODO: Need to compute a relative path that is based on common path for all workspace files
string containerName = Path.GetFileNameWithoutExtension(scriptFile.FilePath);

if (foundSymbols != null)
{
var matchedSymbols =
foundSymbols
.FoundOccurrences
.Where(r => IsQueryMatch(workspaceSymbolParams.Query, r.SymbolName))
.Select(r =>
{
return new SymbolInformation
{
ContainerName = containerName,
Kind = r.SymbolType == SymbolType.Variable ? SymbolKind.Variable : SymbolKind.Function,
Location = new Location {
Uri = new Uri(r.FilePath).AbsoluteUri,
Range = GetRangeFromScriptRegion(r.ScriptRegion)
},
Name = GetDecoratedSymbolName(r)
};
});

symbols.AddRange(matchedSymbols);
}
}

await requestContext.SendResult(symbols.ToArray());
}

private bool IsQueryMatch(string query, string symbolName)
{
return symbolName.IndexOf(query, StringComparison.OrdinalIgnoreCase) >= 0;
}

protected async Task HandleEvaluateRequest(
Expand Down
15 changes: 14 additions & 1 deletion src/PowerShellEditorServices/Language/AstOperations.cs
Original file line number Diff line number Diff line change
Expand Up @@ -131,8 +131,8 @@ static public IEnumerable<SymbolReference> FindReferencesOfSymbol(
scriptAst.Visit(referencesVisitor);

return referencesVisitor.FoundReferences;

}

/// <summary>
/// Finds all references (not including aliases) in a script for the given symbol
/// </summary>
Expand Down Expand Up @@ -172,6 +172,19 @@ static public SymbolReference FindDefinitionOfSymbol(
return declarationVisitor.FoundDeclartion;
}

/// <summary>
/// Finds all symbols in a script
/// </summary>
/// <param name="scriptAst">The abstract syntax tree of the given script</param>
/// <returns>A collection of SymbolReference objects</returns>
static public IEnumerable<SymbolReference> FindSymbolsInDocument(Ast scriptAst)
{
FindSymbolsVisitor findSymbolsVisitor = new FindSymbolsVisitor();
scriptAst.Visit(findSymbolsVisitor);

return findSymbolsVisitor.SymbolReferences;
}

/// <summary>
/// Finds all files dot sourced in a script
/// </summary>
Expand Down
107 changes: 107 additions & 0 deletions src/PowerShellEditorServices/Language/FindSymbolsVisitor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//

using System.Collections.Generic;
using System.Management.Automation.Language;

namespace Microsoft.PowerShell.EditorServices
{
/// <summary>
/// The visitor used to find all the symbols (function and class defs) in the AST.
/// </summary>
internal class FindSymbolsVisitor : AstVisitor2
{
public List<SymbolReference> SymbolReferences { get; private set; }

public FindSymbolsVisitor()
{
this.SymbolReferences = new List<SymbolReference>();
}

/// <summary>
/// Adds each function defintion as a
/// </summary>
/// <param name="functionDefinitionAst">A functionDefinitionAst object in the script's AST</param>
/// <returns>A decision to stop searching if the right symbol was found,
/// or a decision to continue if it wasn't found</returns>
public override AstVisitAction VisitFunctionDefinition(FunctionDefinitionAst functionDefinitionAst)
{
IScriptExtent nameExtent = new ScriptExtent() {
Text = functionDefinitionAst.Name,
StartLineNumber = functionDefinitionAst.Extent.StartLineNumber,
EndLineNumber = functionDefinitionAst.Extent.EndLineNumber,
StartColumnNumber = functionDefinitionAst.Extent.StartColumnNumber,
EndColumnNumber = functionDefinitionAst.Extent.EndColumnNumber
};

SymbolType symbolType =
functionDefinitionAst.IsWorkflow ?
SymbolType.Workflow : SymbolType.Function;

this.SymbolReferences.Add(
new SymbolReference(
symbolType,
nameExtent));

return AstVisitAction.Continue;
}

/// <summary>
/// Checks to see if this variable expression is the symbol we are looking for.
/// </summary>
/// <param name="variableExpressionAst">A VariableExpressionAst object in the script's AST</param>
/// <returns>A descion to stop searching if the right symbol was found,
/// or a decision to continue if it wasn't found</returns>
public override AstVisitAction VisitVariableExpression(VariableExpressionAst variableExpressionAst)
{
if (!IsAssignedAtScriptScope(variableExpressionAst))
{
return AstVisitAction.Continue;
}

this.SymbolReferences.Add(
new SymbolReference(
SymbolType.Variable,
variableExpressionAst.Extent));

return AstVisitAction.Continue;
}

public override AstVisitAction VisitConfigurationDefinition(ConfigurationDefinitionAst configurationDefinitionAst)
{
IScriptExtent nameExtent = new ScriptExtent() {
Text = configurationDefinitionAst.InstanceName.Extent.Text,
StartLineNumber = configurationDefinitionAst.Extent.StartLineNumber,
EndLineNumber = configurationDefinitionAst.Extent.EndLineNumber,
StartColumnNumber = configurationDefinitionAst.Extent.StartColumnNumber,
EndColumnNumber = configurationDefinitionAst.Extent.EndColumnNumber
};

this.SymbolReferences.Add(
new SymbolReference(
SymbolType.Configuration,
nameExtent));

return AstVisitAction.Continue;
}

private bool IsAssignedAtScriptScope(VariableExpressionAst variableExpressionAst)
{
Ast parent = variableExpressionAst.Parent;
if (!(parent is AssignmentStatementAst))
{
return false;
}

parent = parent.Parent;
if (parent == null || parent.Parent == null || parent.Parent.Parent == null)
{
return true;
}

return false;
}
}
}
26 changes: 26 additions & 0 deletions src/PowerShellEditorServices/Language/LanguageService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,32 @@ public async Task<SymbolDetails> FindSymbolDetailsAtLocation(
return symbolDetails;
}

/// <summary>
/// Finds all the symbols in a file.
/// </summary>
/// <param name="scriptFile">The ScriptFile in which the symbol can be located.</param>
/// <returns></returns>
public FindOccurrencesResult FindSymbolsInFile(ScriptFile scriptFile)
{
Validate.IsNotNull("scriptFile", scriptFile);

IEnumerable<SymbolReference> symbolReferencesinFile =
AstOperations
.FindSymbolsInDocument(scriptFile.ScriptAst)
.Select(
reference => {
reference.SourceLine =
scriptFile.GetLine(reference.ScriptRegion.StartLineNumber);
reference.FilePath = scriptFile.FilePath;
return reference;
});

return
new FindOccurrencesResult {
FoundOccurrences = symbolReferencesinFile
};
}

/// <summary>
/// Finds all the references of a symbol
/// </summary>
Expand Down
13 changes: 11 additions & 2 deletions src/PowerShellEditorServices/Language/SymbolType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,17 @@ public enum SymbolType
/// <summary>
/// The symbol is a parameter
/// </summary>
Parameter
}
Parameter,

/// <summary>
/// The symbol is a DSC configuration
/// </summary>
Configuration,

/// <summary>
/// The symbol is a workflow
/// </summary>
Workflow,
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@
<Compile Include="Language\FindOccurrencesResult.cs" />
<Compile Include="Language\FindReferencesResult.cs" />
<Compile Include="Language\FindReferencesVisitor.cs" />
<Compile Include="Language\FindSymbolsVisitor.cs" />
<Compile Include="Language\FindSymbolVisitor.cs" />
<Compile Include="Language\GetDefinitionResult.cs" />
<Compile Include="Language\LanguageService.cs" />
Expand Down
7 changes: 7 additions & 0 deletions src/PowerShellEditorServices/Workspace/Workspace.cs
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,13 @@ public ScriptFile GetFileBuffer(string filePath, string initialBuffer)
return scriptFile;
}

public ScriptFile[] GetOpenedFiles()
{
var scriptFiles = new ScriptFile[workspaceFiles.Count];
workspaceFiles.Values.CopyTo(scriptFiles, 0);
return scriptFiles;
}

/// <summary>
/// Closes a currently open script file with the given file path.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,13 +55,17 @@
<Compile Include="References\FindsReferencesOnFunctionMultiFileDotSource.cs" />
<Compile Include="References\FindsReferencesOnVariable.cs" />
<Compile Include="SymbolDetails\FindsDetailsForBuiltInCommand.cs" />
<Compile Include="Symbols\FindSymbolsInMultiSymbolFile.cs" />
<Compile Include="Symbols\FindSymbolsInNoSymbolsFile.cs" />
</ItemGroup>
<ItemGroup>
<None Include="Completion\CompletionExamples.psm1">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="Debugging\VariableTest.ps1" />
<None Include="SymbolDetails\SymbolDetails.ps1" />
<None Include="Symbols\MultipleSymbols.ps1" />
<None Include="Symbols\NoSymbols.ps1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\PowerShellEditorServices\PowerShellEditorServices.csproj">
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//

namespace Microsoft.PowerShell.EditorServices.Test.Shared.Symbols
{
public class FindSymbolsInMultiSymbolFile
{
public static readonly ScriptRegion SourceDetails =
new ScriptRegion {
File = @"Symbols\MultipleSymbols.ps1"
};
}
}
Loading