From 28e9669b36aa49551e9f237b4c1a476c4c7fbb4b Mon Sep 17 00:00:00 2001 From: David Wilson Date: Tue, 4 Aug 2015 17:03:39 -0700 Subject: [PATCH 01/14] Add debugging support; refactor session management This change introduces debugging support to both the core .NET library and the out-of-process API host. It adds a new DebugService class to the API and all of the necessary messages that are specified by VS Code's debugging protocol. This change also marks a refactoring of runspace management logic in the core API. Previously the runspace used by a few different services was interacted with directly, which complicated the process for executing commands and scripts when the debugger was active. A new PowerShellSession class has been introduced to provide complete management of the runspace(s) needed for a single editing session. In the future, this class will expand to support remote sessions as well. --- .gitignore | 7 +- .../MessageLoop.cs} | 150 ++-- .../PowerShellEditorServices.Host.csproj | 25 + src/PowerShellEditorServices.Host/Program.cs | 4 +- .../StdioConsoleHost.cs | 27 +- .../packages.config | 4 + .../Event/ExitedEvent.cs | 14 + .../Event/InitializedEvent.cs | 9 + .../Event/StoppedEvent.cs | 34 + .../Message/IMessageProcessor.cs | 3 +- .../Message/MessageReader.cs | 1 + .../Model/Breakpoint.cs | 30 + .../Model/Message.cs | 17 + .../Model/Scope.cs | 23 + .../Model/Source.cs | 12 + .../Model/StackFrame.cs | 56 ++ .../Model/Thread.cs | 10 + .../Model/Variable.cs | 27 + ...ShellEditorServices.Transport.Stdio.csproj | 58 +- .../Request/AttachRequest.cs | 24 + .../Request/ChangeFileRequest.cs | 6 +- .../Request/CompletionDetailsRequest.cs | 6 +- .../Request/CompletionsRequest.cs | 5 +- .../Request/ContinueRequest.cs | 25 + .../Request/DeclarationRequest.cs | 5 +- .../Request/DisconnectRequest.cs | 20 + .../Request/ErrorRequest.cs | 7 +- .../Request/EvaluateRequest.cs | 68 ++ .../Request/InitializeRequest.cs | 42 ++ .../Request/LaunchRequest.cs | 54 ++ .../Request/NextRequest.cs | 28 + .../Request/OccurrencesRequest.cs | 6 +- .../Request/OpenFileRequest.cs | 6 +- .../Request/PauseRequest.cs | 20 + .../Request/QuickInfoRequest.cs | 59 ++ .../Request/ReferencesRequest.cs | 5 +- .../Request/ReplExecuteRequest.cs | 8 +- .../Request/RequestBase.cs | 3 +- .../Request/SetBreakpointsRequest.cs | 45 ++ .../Request/SetExceptionBreakpointsRequest.cs | 38 + .../Request/SignatureHelpRequest.cs | 5 +- .../Request/SourceRequest.cs | 26 + .../Request/StackTraceRequest.cs | 39 + .../Request/StepInRequest.cs | 25 + .../Request/StepOutRequest.cs | 25 + .../Request/ThreadsRequest.cs | 38 + .../Request/VariablesRequest.cs | 33 + .../Response/AttachResponse.cs | 14 + .../Response/ContinueResponse.cs | 9 + .../Response/DisconnectResponse.cs | 14 + .../Response/ErrorResponse.cs | 16 + .../Response/EvaluateResponse.cs | 17 + .../Response/InitializeResponse.cs | 10 + .../Response/LaunchResponse.cs | 9 + .../Response/NextResponse.cs | 9 + .../Response/QuickInfoResponse.cs | 46 ++ .../Response/ReplPromptChoiceResponse.cs | 12 +- .../Response/SetBreakpointsResponse.cs | 35 + .../SetExceptionBreakpointsResponse.cs | 9 + .../Response/SourceResponse.cs | 14 + .../Response/StackTraceResponse.cs | 31 + .../Response/StepInResponse.cs | 9 + .../Response/StepOutResponse.cs | 9 + .../Response/ThreadsResponse.cs | 15 + .../Response/VariablesResponse.cs | 41 ++ .../packages.config | 2 +- .../Analysis/AnalysisService.cs | 19 +- .../Console/BreakpointDetails.cs | 30 + .../Console/ConsoleService.cs | 98 +-- .../Console/ConsoleServicePSHost.cs | 26 +- .../Console/IConsoleHost.cs | 2 +- .../Console/PowerShellSession.cs | 683 ++++++++++++++++++ .../Console/StackFrameDetails.cs | 38 + .../Console/VariableDetails.cs | 148 ++++ src/PowerShellEditorServices/DebugService.cs | 289 ++++++++ .../Language/FindCommandVisitor.cs | 2 +- .../Language/FindSymbolVisitor.cs | 2 +- .../Language/LanguageService.cs | 151 ++-- .../Language/ParameterSetSignatures.cs | 2 +- .../Language/SymbolDetails.cs | 107 +++ .../Language/SymbolReference.cs | 32 +- .../Language/SymbolType.cs | 30 + .../PowerShellEditorServices.csproj | 19 +- .../Session/EditorSession.cs | 48 +- src/PowerShellEditorServices/packages.config | 2 +- .../LanguageServiceManager.cs | 2 +- .../PowerShellEditorServices.Test.Host.csproj | 22 +- .../ScenarioTests.cs | 3 +- .../packages.config | 11 +- .../Debugging/DebugTest.ps1 | 13 + ...owerShellEditorServices.Test.Shared.csproj | 3 + .../FindsDetailsForBuiltInCommand.cs | 20 + .../SymbolDetails/SymbolDetails.ps1 | 38 + .../Message/TestMessageTypes.cs | 5 +- ...EditorServices.Test.Transport.Stdio.csproj | 22 +- .../packages.config | 11 +- .../Console/ConsoleServiceTests.cs | 97 +-- .../Console/PowerShellSessionTests.cs | 227 ++++++ .../Console/TestConsoleHost.cs | 59 +- .../Debugging/DebugServiceTests.cs | 188 +++++ .../Language/LanguageServiceTests.cs | 92 ++- .../PowerShellEditorServices.Test.csproj | 36 +- .../packages.config | 12 +- 103 files changed, 3516 insertions(+), 576 deletions(-) rename src/{PowerShellEditorServices.Transport.Stdio/StdioHost.cs => PowerShellEditorServices.Host/MessageLoop.cs} (56%) rename src/{PowerShellEditorServices.Transport.Stdio => PowerShellEditorServices.Host}/StdioConsoleHost.cs (78%) create mode 100644 src/PowerShellEditorServices.Host/packages.config create mode 100644 src/PowerShellEditorServices.Transport.Stdio/Event/ExitedEvent.cs create mode 100644 src/PowerShellEditorServices.Transport.Stdio/Event/InitializedEvent.cs create mode 100644 src/PowerShellEditorServices.Transport.Stdio/Event/StoppedEvent.cs create mode 100644 src/PowerShellEditorServices.Transport.Stdio/Model/Breakpoint.cs create mode 100644 src/PowerShellEditorServices.Transport.Stdio/Model/Message.cs create mode 100644 src/PowerShellEditorServices.Transport.Stdio/Model/Scope.cs create mode 100644 src/PowerShellEditorServices.Transport.Stdio/Model/Source.cs create mode 100644 src/PowerShellEditorServices.Transport.Stdio/Model/StackFrame.cs create mode 100644 src/PowerShellEditorServices.Transport.Stdio/Model/Thread.cs create mode 100644 src/PowerShellEditorServices.Transport.Stdio/Model/Variable.cs create mode 100644 src/PowerShellEditorServices.Transport.Stdio/Request/AttachRequest.cs create mode 100644 src/PowerShellEditorServices.Transport.Stdio/Request/ContinueRequest.cs create mode 100644 src/PowerShellEditorServices.Transport.Stdio/Request/DisconnectRequest.cs create mode 100644 src/PowerShellEditorServices.Transport.Stdio/Request/EvaluateRequest.cs create mode 100644 src/PowerShellEditorServices.Transport.Stdio/Request/InitializeRequest.cs create mode 100644 src/PowerShellEditorServices.Transport.Stdio/Request/LaunchRequest.cs create mode 100644 src/PowerShellEditorServices.Transport.Stdio/Request/NextRequest.cs create mode 100644 src/PowerShellEditorServices.Transport.Stdio/Request/PauseRequest.cs create mode 100644 src/PowerShellEditorServices.Transport.Stdio/Request/QuickInfoRequest.cs create mode 100644 src/PowerShellEditorServices.Transport.Stdio/Request/SetBreakpointsRequest.cs create mode 100644 src/PowerShellEditorServices.Transport.Stdio/Request/SetExceptionBreakpointsRequest.cs create mode 100644 src/PowerShellEditorServices.Transport.Stdio/Request/SourceRequest.cs create mode 100644 src/PowerShellEditorServices.Transport.Stdio/Request/StackTraceRequest.cs create mode 100644 src/PowerShellEditorServices.Transport.Stdio/Request/StepInRequest.cs create mode 100644 src/PowerShellEditorServices.Transport.Stdio/Request/StepOutRequest.cs create mode 100644 src/PowerShellEditorServices.Transport.Stdio/Request/ThreadsRequest.cs create mode 100644 src/PowerShellEditorServices.Transport.Stdio/Request/VariablesRequest.cs create mode 100644 src/PowerShellEditorServices.Transport.Stdio/Response/AttachResponse.cs create mode 100644 src/PowerShellEditorServices.Transport.Stdio/Response/ContinueResponse.cs create mode 100644 src/PowerShellEditorServices.Transport.Stdio/Response/DisconnectResponse.cs create mode 100644 src/PowerShellEditorServices.Transport.Stdio/Response/ErrorResponse.cs create mode 100644 src/PowerShellEditorServices.Transport.Stdio/Response/EvaluateResponse.cs create mode 100644 src/PowerShellEditorServices.Transport.Stdio/Response/InitializeResponse.cs create mode 100644 src/PowerShellEditorServices.Transport.Stdio/Response/LaunchResponse.cs create mode 100644 src/PowerShellEditorServices.Transport.Stdio/Response/NextResponse.cs create mode 100644 src/PowerShellEditorServices.Transport.Stdio/Response/QuickInfoResponse.cs create mode 100644 src/PowerShellEditorServices.Transport.Stdio/Response/SetBreakpointsResponse.cs create mode 100644 src/PowerShellEditorServices.Transport.Stdio/Response/SetExceptionBreakpointsResponse.cs create mode 100644 src/PowerShellEditorServices.Transport.Stdio/Response/SourceResponse.cs create mode 100644 src/PowerShellEditorServices.Transport.Stdio/Response/StackTraceResponse.cs create mode 100644 src/PowerShellEditorServices.Transport.Stdio/Response/StepInResponse.cs create mode 100644 src/PowerShellEditorServices.Transport.Stdio/Response/StepOutResponse.cs create mode 100644 src/PowerShellEditorServices.Transport.Stdio/Response/ThreadsResponse.cs create mode 100644 src/PowerShellEditorServices.Transport.Stdio/Response/VariablesResponse.cs create mode 100644 src/PowerShellEditorServices/Console/BreakpointDetails.cs create mode 100644 src/PowerShellEditorServices/Console/PowerShellSession.cs create mode 100644 src/PowerShellEditorServices/Console/StackFrameDetails.cs create mode 100644 src/PowerShellEditorServices/Console/VariableDetails.cs create mode 100644 src/PowerShellEditorServices/DebugService.cs create mode 100644 src/PowerShellEditorServices/Language/SymbolDetails.cs create mode 100644 src/PowerShellEditorServices/Language/SymbolType.cs create mode 100644 test/PowerShellEditorServices.Test.Shared/Debugging/DebugTest.ps1 create mode 100644 test/PowerShellEditorServices.Test.Shared/SymbolDetails/FindsDetailsForBuiltInCommand.cs create mode 100644 test/PowerShellEditorServices.Test.Shared/SymbolDetails/SymbolDetails.ps1 create mode 100644 test/PowerShellEditorServices.Test/Console/PowerShellSessionTests.cs create mode 100644 test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs diff --git a/.gitignore b/.gitignore index 4e31936dd..975283fbe 100644 --- a/.gitignore +++ b/.gitignore @@ -44,9 +44,6 @@ ClientBin/ csx/ # Don't include ScriptAnalyzer binaries -PowerShellEditorServices/Microsoft.Windows.PowerShell.ScriptAnalyzer.dll -PowerShellEditorServices/Microsoft.Windows.PowerShell.ScriptAnalyzer.BuiltinRules.dll -PowerShellEditorServices/PSScriptAnalyzer.psd1 -PowerShellEditorServices/ScriptAnalyzer.format.ps1xml -PowerShellEditorServices/ScriptAnalyzer.types.ps1xml +PowerShellEditorServices/** + diff --git a/src/PowerShellEditorServices.Transport.Stdio/StdioHost.cs b/src/PowerShellEditorServices.Host/MessageLoop.cs similarity index 56% rename from src/PowerShellEditorServices.Transport.Stdio/StdioHost.cs rename to src/PowerShellEditorServices.Host/MessageLoop.cs index dcaf81863..e3e9f78d4 100644 --- a/src/PowerShellEditorServices.Transport.Stdio/StdioHost.cs +++ b/src/PowerShellEditorServices.Host/MessageLoop.cs @@ -8,22 +8,26 @@ using Microsoft.PowerShell.EditorServices.Session; using Microsoft.PowerShell.EditorServices.Transport.Stdio.Event; using Microsoft.PowerShell.EditorServices.Transport.Stdio.Message; +using Microsoft.PowerShell.EditorServices.Transport.Stdio.Model; using Microsoft.PowerShell.EditorServices.Transport.Stdio.Response; using Nito.AsyncEx; using System; +using System.Management.Automation; using System.Reflection; using System.Text; using System.Threading; using System.Threading.Tasks; -namespace Microsoft.PowerShell.EditorServices.Transport.Stdio +namespace Microsoft.PowerShell.EditorServices.Host { - public class StdioHost : IHost + public class MessageLoop : IHost { #region Private Fields private IConsoleHost consoleHost; private EditorSession editorSession; + private MessageReader messageReader; + private MessageWriter messageWriter; private SynchronizationContext syncContext; private AsyncContextThread messageLoopThread; @@ -41,12 +45,8 @@ Version IHost.Version get { throw new NotImplementedException(); } } - void IHost.Start() + public void Start() { - // Start a new EditorSession - // TODO: Allow multiple sessions? - this.editorSession = new EditorSession(); - // Start the main message loop AsyncContext.Run((Func)this.StartMessageLoop); } @@ -65,69 +65,6 @@ private async Task StartMessageLoop() await this.messageLoopThread.Factory.Run(() => this.ListenForMessages()); } - //private async Task ListenForMessages() - //{ - // // Ensure that the console is using UTF-8 encoding - // System.Console.InputEncoding = Encoding.UTF8; - // System.Console.OutputEncoding = Encoding.UTF8; - - // // Set up the reader and writer - // MessageReader messageReader = - // new MessageReader( - // System.Console.In, - // MessageFormat.WithoutContentLength); - - // MessageWriter messageWriter = - // new MessageWriter( - // System.Console.Out, - // MessageFormat.WithContentLength); - - // this.ConsoleHost = new StdioConsoleHost(messageWriter); - - // // Set up the PowerShell session - // // TODO: Do this elsewhere - // EditorSession editorSession = new EditorSession(); - // editorSession.StartSession(this.ConsoleHost); - - // // Send a "started" event - // messageWriter.WriteMessage( - // new Event - // { - // EventType = "started" - // }); - - // // Run the message loop - // bool isRunning = true; - // while(isRunning) - // { - // // Read a message - // Message newMessage = await messageReader.ReadMessage(); - - // // Is the message a request? - // IMessageProcessor messageProcessor = newMessage as IMessageProcessor; - // if (messageProcessor != null) - // { - // // Process the request on the host thread - // messageProcessor.ProcessMessage( - // editorSession, - // messageWriter); - // } - // else - // { - // if (newMessage != null) - // { - // // Return an error response to keep the client moving - // messageWriter.WriteMessage( - // new Response - // { - // Command = request != null ? request.Command : string.Empty, - // RequestSeq = newMessage.Seq, - // Success = false, - // }); - // } - // } - // } - //} async Task ListenForMessages() { // Ensure that the console is using UTF-8 encoding @@ -136,16 +73,16 @@ async Task ListenForMessages() // Find all message types in this assembly MessageTypeResolver messageTypeResolver = new MessageTypeResolver(); - messageTypeResolver.ScanForMessageTypes(Assembly.GetExecutingAssembly()); + messageTypeResolver.ScanForMessageTypes(typeof(StartedEvent).Assembly); // Set up the reader and writer - MessageReader messageReader = + this.messageReader = new MessageReader( - System.Console.In, + System.Console.In, MessageFormat.WithContentLength, messageTypeResolver); - MessageWriter messageWriter = + this.messageWriter = new MessageWriter( System.Console.Out, MessageFormat.WithContentLength, @@ -156,12 +93,16 @@ async Task ListenForMessages() this.consoleHost = new StdioConsoleHost(messageWriter); // Set up the PowerShell session - // TODO: Do this elsewhere - EditorSession editorSession = new EditorSession(); - editorSession.StartSession(this.consoleHost); + this.editorSession = new EditorSession(); + this.editorSession.StartSession(this.consoleHost); + + // Attach to events from the PowerShell session + this.editorSession.PowerShellSession.OutputWritten += PowerShellSession_OutputWritten; + this.editorSession.PowerShellSession.BreakpointUpdated += PowerShellSession_BreakpointUpdated; + this.editorSession.DebugService.DebuggerStopped += DebugService_DebuggerStopped; // Send a "started" event - messageWriter.WriteMessage( + this.messageWriter.WriteMessage( new StartedEvent()); // Run the message loop @@ -173,12 +114,12 @@ async Task ListenForMessages() try { // Read a message from stdin - newMessage = await messageReader.ReadMessage(); + newMessage = await this.messageReader.ReadMessage(); } catch (MessageParseException e) { // Write an error response - messageWriter.WriteMessage( + this.messageWriter.WriteMessage( MessageErrorResponse.CreateParseErrorResponse(e)); // Continue the loop @@ -191,16 +132,16 @@ async Task ListenForMessages() { // Process the message. The processor will take care // of writing responses throguh the messageWriter. - messageProcessor.ProcessMessage( - editorSession, - messageWriter); + await messageProcessor.ProcessMessage( + this.editorSession, + this.messageWriter); } else { if (newMessage != null) { // Return an error response to keep the client moving - messageWriter.WriteMessage( + this.messageWriter.WriteMessage( MessageErrorResponse.CreateUnhandledMessageResponse( newMessage)); } @@ -213,6 +154,47 @@ async Task ListenForMessages() } } + void DebugService_DebuggerStopped(object sender, DebuggerStopEventArgs e) + { + this.messageWriter.WriteMessage( + new StoppedEvent + { + Body = new StoppedEventBody + { + Source = new Source + { + Path = e.InvocationInfo.ScriptName, + }, + Line = e.InvocationInfo.ScriptLineNumber, + Column = e.InvocationInfo.OffsetInLine, + ThreadId = 1, // TODO: Change this based on context + Reason = "breakpoint" // TODO: Change this based on context + } + }); + } + + void PowerShellSession_BreakpointUpdated(object sender, BreakpointUpdatedEventArgs e) + { + } + + void PowerShellSession_OutputWritten(object sender, OutputWrittenEventArgs e) + { + // TODO: change this to use the OutputEvent! + + this.messageWriter.WriteMessage( + new ReplWriteOutputEvent + { + Body = new ReplWriteOutputEventBody + { + LineContents = e.OutputText, + LineType = e.OutputType, + IncludeNewLine = e.IncludeNewLine, + ForegroundColor = e.ForegroundColor, + BackgroundColor = e.BackgroundColor + } + }); + } + #endregion } } diff --git a/src/PowerShellEditorServices.Host/PowerShellEditorServices.Host.csproj b/src/PowerShellEditorServices.Host/PowerShellEditorServices.Host.csproj index e6ceeb9fa..3f1f8f1e6 100644 --- a/src/PowerShellEditorServices.Host/PowerShellEditorServices.Host.csproj +++ b/src/PowerShellEditorServices.Host/PowerShellEditorServices.Host.csproj @@ -11,6 +11,8 @@ Microsoft.PowerShell.EditorServices.Host v4.5 512 + ..\..\ + true AnyCPU @@ -34,6 +36,18 @@ 4 + + ..\..\packages\Nito.AsyncEx.3.0.1\lib\net45\Nito.AsyncEx.dll + True + + + ..\..\packages\Nito.AsyncEx.3.0.1\lib\net45\Nito.AsyncEx.Concurrent.dll + True + + + ..\..\packages\Nito.AsyncEx.3.0.1\lib\net45\Nito.AsyncEx.Enlightenment.dll + True + @@ -41,13 +55,17 @@ + + + + @@ -68,6 +86,13 @@ + + + + This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + +