diff --git a/src/PowerShellEditorServices.Protocol/MessageProtocol/MessageDispatcher.cs b/src/PowerShellEditorServices.Protocol/MessageProtocol/MessageDispatcher.cs index f2da7a51d..df28bd2a5 100644 --- a/src/PowerShellEditorServices.Protocol/MessageProtocol/MessageDispatcher.cs +++ b/src/PowerShellEditorServices.Protocol/MessageProtocol/MessageDispatcher.cs @@ -79,7 +79,8 @@ public void Stop() { // By disposing the thread we cancel all existing work // and cause the thread to be destroyed. - this.messageLoopThread.Dispose(); + if (this.messageLoopThread != null) + this.messageLoopThread.Dispose(); } public void SetRequestHandler( diff --git a/src/PowerShellEditorServices.Protocol/Server/ProtocolServer.cs b/src/PowerShellEditorServices.Protocol/Server/ProtocolServer.cs index 0d3165bb7..54de0219f 100644 --- a/src/PowerShellEditorServices.Protocol/Server/ProtocolServer.cs +++ b/src/PowerShellEditorServices.Protocol/Server/ProtocolServer.cs @@ -70,7 +70,8 @@ public void Stop() this.OnStop(); this.serverChannel.Stop(); - this.serverExitedTask.SetResult(true); + if (this.serverExitedTask != null) + this.serverExitedTask.SetResult(true); } } diff --git a/src/PowerShellEditorServices/Language/AstOperations.cs b/src/PowerShellEditorServices/Language/AstOperations.cs index a73f550c1..c5ef11f0a 100644 --- a/src/PowerShellEditorServices/Language/AstOperations.cs +++ b/src/PowerShellEditorServices/Language/AstOperations.cs @@ -176,13 +176,22 @@ static public SymbolReference FindDefinitionOfSymbol( /// Finds all symbols in a script /// /// The abstract syntax tree of the given script + /// The PowerShell version the Ast was generated from /// A collection of SymbolReference objects - static public IEnumerable FindSymbolsInDocument(Ast scriptAst) + static public IEnumerable FindSymbolsInDocument(Ast scriptAst, Version powerShellVersion) { - FindSymbolsVisitor findSymbolsVisitor = new FindSymbolsVisitor(); - scriptAst.Visit(findSymbolsVisitor); - - return findSymbolsVisitor.SymbolReferences; + if (powerShellVersion >= new Version(5,0)) + { + FindSymbolsVisitor2 findSymbolsVisitor = new FindSymbolsVisitor2(); + scriptAst.Visit(findSymbolsVisitor); + return findSymbolsVisitor.SymbolReferences; + } + else + { + FindSymbolsVisitor findSymbolsVisitor = new FindSymbolsVisitor(); + scriptAst.Visit(findSymbolsVisitor); + return findSymbolsVisitor.SymbolReferences; + } } /// diff --git a/src/PowerShellEditorServices/Language/CompletionResults.cs b/src/PowerShellEditorServices/Language/CompletionResults.cs index 9a01c2446..031d83cde 100644 --- a/src/PowerShellEditorServices/Language/CompletionResults.cs +++ b/src/PowerShellEditorServices/Language/CompletionResults.cs @@ -275,8 +275,10 @@ private static CompletionType ConvertCompletionResultType( case CompletionResultType.Type: return CompletionType.Type; +#if !PowerShellv3 case CompletionResultType.Keyword: return CompletionType.Keyword; +#endif case CompletionResultType.ProviderContainer: case CompletionResultType.ProviderItem: diff --git a/src/PowerShellEditorServices/Language/FindSymbolVisitor.cs b/src/PowerShellEditorServices/Language/FindSymbolVisitor.cs index 90df2810b..aeef67abb 100644 --- a/src/PowerShellEditorServices/Language/FindSymbolVisitor.cs +++ b/src/PowerShellEditorServices/Language/FindSymbolVisitor.cs @@ -10,7 +10,7 @@ namespace Microsoft.PowerShell.EditorServices /// /// The visitor used to find the the symbol at a specfic location in the AST /// - internal class FindSymbolVisitor : AstVisitor2 + internal class FindSymbolVisitor : AstVisitor { private int lineNumber; private int columnNumber; diff --git a/src/PowerShellEditorServices/Language/FindSymbolsVisitor.cs b/src/PowerShellEditorServices/Language/FindSymbolsVisitor.cs index fdafa593a..06f5f6cec 100644 --- a/src/PowerShellEditorServices/Language/FindSymbolsVisitor.cs +++ b/src/PowerShellEditorServices/Language/FindSymbolsVisitor.cs @@ -11,7 +11,10 @@ namespace Microsoft.PowerShell.EditorServices /// /// The visitor used to find all the symbols (function and class defs) in the AST. /// - internal class FindSymbolsVisitor : AstVisitor2 + /// + /// Requires PowerShell v3 or higher + /// + internal class FindSymbolsVisitor : AstVisitor { public List SymbolReferences { get; private set; } @@ -68,25 +71,7 @@ public override AstVisitAction VisitVariableExpression(VariableExpressionAst var 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; diff --git a/src/PowerShellEditorServices/Language/FindSymbolsVisitor2.cs b/src/PowerShellEditorServices/Language/FindSymbolsVisitor2.cs new file mode 100644 index 000000000..97837ae06 --- /dev/null +++ b/src/PowerShellEditorServices/Language/FindSymbolsVisitor2.cs @@ -0,0 +1,70 @@ +using System.Collections.Generic; +using System.Management.Automation.Language; + +namespace Microsoft.PowerShell.EditorServices +{ + /// + /// The visitor used to find all the symbols (function and class defs) in the AST. + /// + /// + /// Requires PowerShell v5 or higher + /// + internal class FindSymbolsVisitor2 : AstVisitor2 + { + private FindSymbolsVisitor findSymbolsVisitor; + + public List SymbolReferences + { + get + { + return this.findSymbolsVisitor.SymbolReferences; + } + } + + public FindSymbolsVisitor2() + { + this.findSymbolsVisitor = new FindSymbolsVisitor(); + } + + /// + /// Adds each function defintion as a + /// + /// A functionDefinitionAst object in the script's AST + /// A decision to stop searching if the right symbol was found, + /// or a decision to continue if it wasn't found + public override AstVisitAction VisitFunctionDefinition(FunctionDefinitionAst functionDefinitionAst) + { + return this.findSymbolsVisitor.VisitFunctionDefinition(functionDefinitionAst); + } + + /// + /// Checks to see if this variable expression is the symbol we are looking for. + /// + /// A VariableExpressionAst object in the script's AST + /// A descion to stop searching if the right symbol was found, + /// or a decision to continue if it wasn't found + public override AstVisitAction VisitVariableExpression(VariableExpressionAst variableExpressionAst) + { + return this.findSymbolsVisitor.VisitVariableExpression(variableExpressionAst); + } + + 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.findSymbolsVisitor.SymbolReferences.Add( + new SymbolReference( + SymbolType.Configuration, + nameExtent)); + + return AstVisitAction.Continue; + } + } +} diff --git a/src/PowerShellEditorServices/Language/LanguageService.cs b/src/PowerShellEditorServices/Language/LanguageService.cs index c68b4b62c..23e8fd56d 100644 --- a/src/PowerShellEditorServices/Language/LanguageService.cs +++ b/src/PowerShellEditorServices/Language/LanguageService.cs @@ -221,7 +221,7 @@ public FindOccurrencesResult FindSymbolsInFile(ScriptFile scriptFile) IEnumerable symbolReferencesinFile = AstOperations - .FindSymbolsInDocument(scriptFile.ScriptAst) + .FindSymbolsInDocument(scriptFile.ScriptAst, this.powerShellContext.PowerShellVersion) .Select( reference => { reference.SourceLine = diff --git a/src/PowerShellEditorServices/PowerShellEditorServices.csproj b/src/PowerShellEditorServices/PowerShellEditorServices.csproj index e3199ebef..630476863 100644 --- a/src/PowerShellEditorServices/PowerShellEditorServices.csproj +++ b/src/PowerShellEditorServices/PowerShellEditorServices.csproj @@ -13,13 +13,16 @@ 512 ..\..\ true + PowerShellv3 + PowerShellv4 + PowerShellv5 true full false bin\Debug\ - DEBUG;TRACE + TRACE;DEBUG;$(DefineConstants) prompt 4 bin\Debug\Microsoft.PowerShell.EditorServices.XML @@ -28,7 +31,7 @@ pdbonly true bin\Release\ - TRACE + TRACE;$(DefineConstants) prompt 4 1591,1573,1572 @@ -49,12 +52,12 @@ + - @@ -76,6 +79,7 @@ + diff --git a/src/PowerShellEditorServices/Session/PowerShellContext.cs b/src/PowerShellEditorServices/Session/PowerShellContext.cs index 3fbde817f..399ffc577 100644 --- a/src/PowerShellEditorServices/Session/PowerShellContext.cs +++ b/src/PowerShellEditorServices/Session/PowerShellContext.cs @@ -15,6 +15,7 @@ namespace Microsoft.PowerShell.EditorServices { + using System.Collections; using System.Management.Automation; using System.Management.Automation.Host; using System.Management.Automation.Runspaces; @@ -68,6 +69,14 @@ public PowerShellContextState SessionState private set; } + /// + /// PowerShell Version of the current runspace. + /// + public Version PowerShellVersion + { + get; private set; + } + #endregion #region Constructors @@ -109,7 +118,7 @@ private void Initialize(Runspace initialRunspace) this.initialRunspace = initialRunspace; this.currentRunspace = initialRunspace; - this.currentRunspace.Debugger.SetDebugMode(DebugModes.LocalScript | DebugModes.RemoteScript); + this.currentRunspace.Debugger.BreakpointUpdated += OnBreakpointUpdated; this.currentRunspace.Debugger.DebuggerStop += OnDebuggerStop; @@ -120,9 +129,38 @@ private void Initialize(Runspace initialRunspace) // TODO: Should this be configurable? this.SetExecutionPolicy(ExecutionPolicy.RemoteSigned); + PowerShellVersion = GetPowerShellVersion(); + +#if !PowerShellv3 + if (PowerShellVersion > new Version(3,0)) + { + this.currentRunspace.Debugger.SetDebugMode(DebugModes.LocalScript | DebugModes.RemoteScript); + } +#endif + this.SessionState = PowerShellContextState.Ready; } + private Version GetPowerShellVersion() + { + try + { + var psVersionTable = this.currentRunspace.SessionStateProxy.GetVariable("PSVersionTable") as Hashtable; + if (psVersionTable != null) + { + var version = psVersionTable["PSVersion"] as Version; + if (version == null) return new Version(5, 0); + return version; + } + } + catch (Exception ex) + { + Logger.Write(LogLevel.Warning, "Failed to look up PowerShell version. Defaulting to version 5. " + ex.Message); + } + + return new Version(5, 0); + } + #endregion #region Public Methods @@ -370,7 +408,12 @@ internal void BreakExecution() { Logger.Write(LogLevel.Verbose, "Debugger break requested..."); - this.currentRunspace.Debugger.SetDebuggerStepMode(true); +#if PowerShellv5 + if (PowerShellVersion >= new Version(5, 0)) + { + this.currentRunspace.Debugger.SetDebuggerStepMode(true); + } +#endif } internal void ResumeDebugger(DebuggerResumeAction resumeAction) @@ -568,7 +611,7 @@ private static string GetStringForPSCommand(PSCommand psCommand) return stringBuilder.ToString(); } - + private void SetExecutionPolicy(ExecutionPolicy desiredExecutionPolicy) { var currentPolicy = ExecutionPolicy.Undefined; diff --git a/test/PowerShellEditorServices.Test/Language/LanguageServiceTests.cs b/test/PowerShellEditorServices.Test/Language/LanguageServiceTests.cs index 86e1033fb..24fbd7aa8 100644 --- a/test/PowerShellEditorServices.Test/Language/LanguageServiceTests.cs +++ b/test/PowerShellEditorServices.Test/Language/LanguageServiceTests.cs @@ -10,12 +10,15 @@ using Microsoft.PowerShell.EditorServices.Test.Shared.References; using Microsoft.PowerShell.EditorServices.Test.Shared.SymbolDetails; using Microsoft.PowerShell.EditorServices.Test.Shared.Symbols; +using Microsoft.Win32; using System; +using System.Diagnostics; using System.IO; using System.Linq; using System.Management.Automation.Runspaces; using System.Threading; using System.Threading.Tasks; +using System.Xml.Linq; using Xunit; namespace Microsoft.PowerShell.EditorServices.Test.Language @@ -25,6 +28,7 @@ public class LanguageServiceTests : IDisposable private Workspace workspace; private LanguageService languageService; private PowerShellContext powerShellContext; + public LanguageServiceTests() { @@ -262,6 +266,7 @@ public void LanguageServiceFindsSymbolsInFile() Assert.Equal(4, symbolsResult.FoundOccurrences.Where(r => r.SymbolType == SymbolType.Function).Count()); Assert.Equal(3, symbolsResult.FoundOccurrences.Where(r => r.SymbolType == SymbolType.Variable).Count()); Assert.Equal(1, symbolsResult.FoundOccurrences.Where(r => r.SymbolType == SymbolType.Workflow).Count()); + Assert.Equal(1, symbolsResult.FoundOccurrences.Where(r => r.SymbolType == SymbolType.Configuration).Count()); SymbolReference firstFunctionSymbol = symbolsResult.FoundOccurrences.Where(r => r.SymbolType == SymbolType.Function).First(); Assert.Equal("AFunction", firstFunctionSymbol.SymbolName); @@ -277,6 +282,11 @@ public void LanguageServiceFindsSymbolsInFile() Assert.Equal("AWorkflow", firstWorkflowSymbol.SymbolName); Assert.Equal(23, firstWorkflowSymbol.ScriptRegion.StartLineNumber); Assert.Equal(1, firstWorkflowSymbol.ScriptRegion.StartColumnNumber); + + SymbolReference firstConfigurationSymbol = symbolsResult.FoundOccurrences.Where(r => r.SymbolType == SymbolType.Configuration).First(); + Assert.Equal("AConfiguration", firstConfigurationSymbol.SymbolName); + Assert.Equal(25, firstConfigurationSymbol.ScriptRegion.StartLineNumber); + Assert.Equal(1, firstConfigurationSymbol.ScriptRegion.StartColumnNumber); } [Fact] @@ -289,6 +299,7 @@ public void LanguageServiceFindsSymbolsInNoSymbolsFile() Assert.Equal(0, symbolsResult.FoundOccurrences.Count()); } + private ScriptFile GetScriptFile(ScriptRegion scriptRegion) { const string baseSharedScriptPath = diff --git a/test/PowerShellEditorServices.Test/Language/PowerShellVersionTests.cs b/test/PowerShellEditorServices.Test/Language/PowerShellVersionTests.cs new file mode 100644 index 000000000..aa21cfb26 --- /dev/null +++ b/test/PowerShellEditorServices.Test/Language/PowerShellVersionTests.cs @@ -0,0 +1,73 @@ +using Microsoft.Win32; +using System; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Xml.Linq; +using Xunit; + +namespace Microsoft.PowerShell.EditorServices.Test.Language +{ + public class PowerShellVersionTests + { + [Theory] + [InlineData("3")] + [InlineData("4")] + [InlineData("5")] + public void CompilesWithPowerShellVersion(string version) + { + var assemblyPath = string.Format(@"..\..\..\..\packages\Microsoft.PowerShell.{0}.ReferenceAssemblies.1.0.0\lib\net4\System.Management.Automation.dll", version); + var projectPath = @"..\..\..\..\src\PowerShellEditorServices\PowerShellEditorServices.csproj"; + FileInfo fi = new FileInfo(projectPath); + var projectVersion = Path.Combine(fi.DirectoryName, version + ".PowerShellEditorServices.csproj"); + + var doc = XDocument.Load(projectPath); + var references = doc.Root.Descendants().Where(m => m.Name.LocalName == "Reference"); + var reference = references.First(m => m.Attribute("Include").Value.StartsWith("System.Management.Automation")); + reference.Add(new XElement("{http://schemas.microsoft.com/developer/msbuild/2003}HintPath", assemblyPath)); + + doc.Save(projectVersion); + + try + { + Compile(projectVersion, version); + } + finally + { + File.Delete(projectVersion); + } + } + + private void Compile(string project, string version) + { + string msbuild; + using (var key = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\MSBuild\ToolsVersions\14.0")) + { + var root = key.GetValue("MSBuildToolsPath") as string; + msbuild = Path.Combine(root, "MSBuild.exe"); + } + + FileInfo fi = new FileInfo(project); + var outPath = Path.GetTempPath(); + + var p = new Process(); + p.StartInfo.FileName = msbuild; + p.StartInfo.Arguments = string.Format(@" {0} /p:Configuration=Debug /t:Build /fileLogger /flp1:logfile=errors.txt;errorsonly /p:SolutionDir={1} /p:SolutionName=PowerShellEditorServices /p:DefineConstants=PowerShellv{2} /p:OutDir={3}", project, fi.Directory.Parent.Parent.FullName, version, outPath); + p.StartInfo.UseShellExecute = false; + p.StartInfo.CreateNoWindow = true; + p.Start(); + p.WaitForExit(60000); + if (!p.HasExited) + { + p.Kill(); + throw new Exception("Compilation didn't complete in 60 seconds."); + } + + if (p.ExitCode != 0) + { + var errors = File.ReadAllText("errors.txt"); + throw new Exception(errors); + } + } + } +} diff --git a/test/PowerShellEditorServices.Test/PowerShellEditorServices.Test.csproj b/test/PowerShellEditorServices.Test/PowerShellEditorServices.Test.csproj index 9abd890df..9abd08904 100644 --- a/test/PowerShellEditorServices.Test/PowerShellEditorServices.Test.csproj +++ b/test/PowerShellEditorServices.Test/PowerShellEditorServices.Test.csproj @@ -51,12 +51,12 @@ + - ..\..\packages\xunit.abstractions.2.0.0\lib\net35\xunit.abstractions.dll True @@ -79,6 +79,7 @@ + @@ -104,7 +105,6 @@ - diff --git a/test/PowerShellEditorServices.Test/packages.config b/test/PowerShellEditorServices.Test/packages.config index 939eece9c..e28241059 100644 --- a/test/PowerShellEditorServices.Test/packages.config +++ b/test/PowerShellEditorServices.Test/packages.config @@ -1,5 +1,8 @@  + + +