Skip to content

Commit 28e9669

Browse files
committed
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.
1 parent 237dc0e commit 28e9669

File tree

103 files changed

+3516
-576
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

103 files changed

+3516
-576
lines changed

.gitignore

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,6 @@ ClientBin/
4444
csx/
4545

4646
# Don't include ScriptAnalyzer binaries
47-
PowerShellEditorServices/Microsoft.Windows.PowerShell.ScriptAnalyzer.dll
48-
PowerShellEditorServices/Microsoft.Windows.PowerShell.ScriptAnalyzer.BuiltinRules.dll
49-
PowerShellEditorServices/PSScriptAnalyzer.psd1
50-
PowerShellEditorServices/ScriptAnalyzer.format.ps1xml
51-
PowerShellEditorServices/ScriptAnalyzer.types.ps1xml
47+
PowerShellEditorServices/**
48+
5249

src/PowerShellEditorServices.Transport.Stdio/StdioHost.cs renamed to src/PowerShellEditorServices.Host/MessageLoop.cs

Lines changed: 66 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -8,22 +8,26 @@
88
using Microsoft.PowerShell.EditorServices.Session;
99
using Microsoft.PowerShell.EditorServices.Transport.Stdio.Event;
1010
using Microsoft.PowerShell.EditorServices.Transport.Stdio.Message;
11+
using Microsoft.PowerShell.EditorServices.Transport.Stdio.Model;
1112
using Microsoft.PowerShell.EditorServices.Transport.Stdio.Response;
1213
using Nito.AsyncEx;
1314
using System;
15+
using System.Management.Automation;
1416
using System.Reflection;
1517
using System.Text;
1618
using System.Threading;
1719
using System.Threading.Tasks;
1820

19-
namespace Microsoft.PowerShell.EditorServices.Transport.Stdio
21+
namespace Microsoft.PowerShell.EditorServices.Host
2022
{
21-
public class StdioHost : IHost
23+
public class MessageLoop : IHost
2224
{
2325
#region Private Fields
2426

2527
private IConsoleHost consoleHost;
2628
private EditorSession editorSession;
29+
private MessageReader messageReader;
30+
private MessageWriter messageWriter;
2731
private SynchronizationContext syncContext;
2832
private AsyncContextThread messageLoopThread;
2933

@@ -41,12 +45,8 @@ Version IHost.Version
4145
get { throw new NotImplementedException(); }
4246
}
4347

44-
void IHost.Start()
48+
public void Start()
4549
{
46-
// Start a new EditorSession
47-
// TODO: Allow multiple sessions?
48-
this.editorSession = new EditorSession();
49-
5050
// Start the main message loop
5151
AsyncContext.Run((Func<Task>)this.StartMessageLoop);
5252
}
@@ -65,69 +65,6 @@ private async Task StartMessageLoop()
6565
await this.messageLoopThread.Factory.Run(() => this.ListenForMessages());
6666
}
6767

68-
//private async Task ListenForMessages()
69-
//{
70-
// // Ensure that the console is using UTF-8 encoding
71-
// System.Console.InputEncoding = Encoding.UTF8;
72-
// System.Console.OutputEncoding = Encoding.UTF8;
73-
74-
// // Set up the reader and writer
75-
// MessageReader messageReader =
76-
// new MessageReader(
77-
// System.Console.In,
78-
// MessageFormat.WithoutContentLength);
79-
80-
// MessageWriter messageWriter =
81-
// new MessageWriter(
82-
// System.Console.Out,
83-
// MessageFormat.WithContentLength);
84-
85-
// this.ConsoleHost = new StdioConsoleHost(messageWriter);
86-
87-
// // Set up the PowerShell session
88-
// // TODO: Do this elsewhere
89-
// EditorSession editorSession = new EditorSession();
90-
// editorSession.StartSession(this.ConsoleHost);
91-
92-
// // Send a "started" event
93-
// messageWriter.WriteMessage(
94-
// new Event<object>
95-
// {
96-
// EventType = "started"
97-
// });
98-
99-
// // Run the message loop
100-
// bool isRunning = true;
101-
// while(isRunning)
102-
// {
103-
// // Read a message
104-
// Message newMessage = await messageReader.ReadMessage();
105-
106-
// // Is the message a request?
107-
// IMessageProcessor messageProcessor = newMessage as IMessageProcessor;
108-
// if (messageProcessor != null)
109-
// {
110-
// // Process the request on the host thread
111-
// messageProcessor.ProcessMessage(
112-
// editorSession,
113-
// messageWriter);
114-
// }
115-
// else
116-
// {
117-
// if (newMessage != null)
118-
// {
119-
// // Return an error response to keep the client moving
120-
// messageWriter.WriteMessage(
121-
// new Response<object>
122-
// {
123-
// Command = request != null ? request.Command : string.Empty,
124-
// RequestSeq = newMessage.Seq,
125-
// Success = false,
126-
// });
127-
// }
128-
// }
129-
// }
130-
//}
13168
async Task ListenForMessages()
13269
{
13370
// Ensure that the console is using UTF-8 encoding
@@ -136,16 +73,16 @@ async Task ListenForMessages()
13673

13774
// Find all message types in this assembly
13875
MessageTypeResolver messageTypeResolver = new MessageTypeResolver();
139-
messageTypeResolver.ScanForMessageTypes(Assembly.GetExecutingAssembly());
76+
messageTypeResolver.ScanForMessageTypes(typeof(StartedEvent).Assembly);
14077

14178
// Set up the reader and writer
142-
MessageReader messageReader =
79+
this.messageReader =
14380
new MessageReader(
144-
System.Console.In,
81+
System.Console.In,
14582
MessageFormat.WithContentLength,
14683
messageTypeResolver);
14784

148-
MessageWriter messageWriter =
85+
this.messageWriter =
14986
new MessageWriter(
15087
System.Console.Out,
15188
MessageFormat.WithContentLength,
@@ -156,12 +93,16 @@ async Task ListenForMessages()
15693
this.consoleHost = new StdioConsoleHost(messageWriter);
15794

15895
// Set up the PowerShell session
159-
// TODO: Do this elsewhere
160-
EditorSession editorSession = new EditorSession();
161-
editorSession.StartSession(this.consoleHost);
96+
this.editorSession = new EditorSession();
97+
this.editorSession.StartSession(this.consoleHost);
98+
99+
// Attach to events from the PowerShell session
100+
this.editorSession.PowerShellSession.OutputWritten += PowerShellSession_OutputWritten;
101+
this.editorSession.PowerShellSession.BreakpointUpdated += PowerShellSession_BreakpointUpdated;
102+
this.editorSession.DebugService.DebuggerStopped += DebugService_DebuggerStopped;
162103

163104
// Send a "started" event
164-
messageWriter.WriteMessage(
105+
this.messageWriter.WriteMessage(
165106
new StartedEvent());
166107

167108
// Run the message loop
@@ -173,12 +114,12 @@ async Task ListenForMessages()
173114
try
174115
{
175116
// Read a message from stdin
176-
newMessage = await messageReader.ReadMessage();
117+
newMessage = await this.messageReader.ReadMessage();
177118
}
178119
catch (MessageParseException e)
179120
{
180121
// Write an error response
181-
messageWriter.WriteMessage(
122+
this.messageWriter.WriteMessage(
182123
MessageErrorResponse.CreateParseErrorResponse(e));
183124

184125
// Continue the loop
@@ -191,16 +132,16 @@ async Task ListenForMessages()
191132
{
192133
// Process the message. The processor will take care
193134
// of writing responses throguh the messageWriter.
194-
messageProcessor.ProcessMessage(
195-
editorSession,
196-
messageWriter);
135+
await messageProcessor.ProcessMessage(
136+
this.editorSession,
137+
this.messageWriter);
197138
}
198139
else
199140
{
200141
if (newMessage != null)
201142
{
202143
// Return an error response to keep the client moving
203-
messageWriter.WriteMessage(
144+
this.messageWriter.WriteMessage(
204145
MessageErrorResponse.CreateUnhandledMessageResponse(
205146
newMessage));
206147
}
@@ -213,6 +154,47 @@ async Task ListenForMessages()
213154
}
214155
}
215156

157+
void DebugService_DebuggerStopped(object sender, DebuggerStopEventArgs e)
158+
{
159+
this.messageWriter.WriteMessage(
160+
new StoppedEvent
161+
{
162+
Body = new StoppedEventBody
163+
{
164+
Source = new Source
165+
{
166+
Path = e.InvocationInfo.ScriptName,
167+
},
168+
Line = e.InvocationInfo.ScriptLineNumber,
169+
Column = e.InvocationInfo.OffsetInLine,
170+
ThreadId = 1, // TODO: Change this based on context
171+
Reason = "breakpoint" // TODO: Change this based on context
172+
}
173+
});
174+
}
175+
176+
void PowerShellSession_BreakpointUpdated(object sender, BreakpointUpdatedEventArgs e)
177+
{
178+
}
179+
180+
void PowerShellSession_OutputWritten(object sender, OutputWrittenEventArgs e)
181+
{
182+
// TODO: change this to use the OutputEvent!
183+
184+
this.messageWriter.WriteMessage(
185+
new ReplWriteOutputEvent
186+
{
187+
Body = new ReplWriteOutputEventBody
188+
{
189+
LineContents = e.OutputText,
190+
LineType = e.OutputType,
191+
IncludeNewLine = e.IncludeNewLine,
192+
ForegroundColor = e.ForegroundColor,
193+
BackgroundColor = e.BackgroundColor
194+
}
195+
});
196+
}
197+
216198
#endregion
217199
}
218200
}

src/PowerShellEditorServices.Host/PowerShellEditorServices.Host.csproj

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
<AssemblyName>Microsoft.PowerShell.EditorServices.Host</AssemblyName>
1212
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
1313
<FileAlignment>512</FileAlignment>
14+
<SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\..\</SolutionDir>
15+
<RestorePackages>true</RestorePackages>
1416
</PropertyGroup>
1517
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
1618
<PlatformTarget>AnyCPU</PlatformTarget>
@@ -34,20 +36,36 @@
3436
<WarningLevel>4</WarningLevel>
3537
</PropertyGroup>
3638
<ItemGroup>
39+
<Reference Include="Nito.AsyncEx, Version=3.0.1.0, Culture=neutral, processorArchitecture=MSIL">
40+
<HintPath>..\..\packages\Nito.AsyncEx.3.0.1\lib\net45\Nito.AsyncEx.dll</HintPath>
41+
<Private>True</Private>
42+
</Reference>
43+
<Reference Include="Nito.AsyncEx.Concurrent, Version=3.0.1.0, Culture=neutral, processorArchitecture=MSIL">
44+
<HintPath>..\..\packages\Nito.AsyncEx.3.0.1\lib\net45\Nito.AsyncEx.Concurrent.dll</HintPath>
45+
<Private>True</Private>
46+
</Reference>
47+
<Reference Include="Nito.AsyncEx.Enlightenment, Version=3.0.1.0, Culture=neutral, processorArchitecture=MSIL">
48+
<HintPath>..\..\packages\Nito.AsyncEx.3.0.1\lib\net45\Nito.AsyncEx.Enlightenment.dll</HintPath>
49+
<Private>True</Private>
50+
</Reference>
3751
<Reference Include="System" />
3852
<Reference Include="System.Core" />
3953
<Reference Include="System.Xml.Linq" />
4054
<Reference Include="System.Data.DataSetExtensions" />
4155
<Reference Include="Microsoft.CSharp" />
4256
<Reference Include="System.Data" />
4357
<Reference Include="System.Xml" />
58+
<Reference Include="System.Management.Automation" />
4459
</ItemGroup>
4560
<ItemGroup>
61+
<Compile Include="MessageLoop.cs" />
4662
<Compile Include="Program.cs" />
4763
<Compile Include="Properties\AssemblyInfo.cs" />
64+
<Compile Include="StdioConsoleHost.cs" />
4865
</ItemGroup>
4966
<ItemGroup>
5067
<None Include="App.config" />
68+
<None Include="packages.config" />
5169
</ItemGroup>
5270
<ItemGroup>
5371
<ProjectReference Include="..\..\submodules\PSScriptAnalyzer\Engine\ScriptAnalyzerEngine.csproj">
@@ -68,6 +86,13 @@
6886
</ProjectReference>
6987
</ItemGroup>
7088
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
89+
<Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition="Exists('$(SolutionDir)\.nuget\NuGet.targets')" />
90+
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
91+
<PropertyGroup>
92+
<ErrorText>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}.</ErrorText>
93+
</PropertyGroup>
94+
<Error Condition="!Exists('$(SolutionDir)\.nuget\NuGet.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(SolutionDir)\.nuget\NuGet.targets'))" />
95+
</Target>
7196
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
7297
Other similar extension points exist, see Microsoft.Common.targets.
7398
<Target Name="BeforeBuild">

src/PowerShellEditorServices.Host/Program.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,8 @@ static void Main(string[] args)
4949

5050
// TODO: Select host, console host, and transport based on command line arguments
5151

52-
IHost host = new StdioHost();
53-
host.Start();
52+
MessageLoop messageLoop = new MessageLoop();
53+
messageLoop.Start();
5454
}
5555

5656
static void CurrentDomain_UnhandledException(

src/PowerShellEditorServices.Transport.Stdio/StdioConsoleHost.cs renamed to src/PowerShellEditorServices.Host/StdioConsoleHost.cs

Lines changed: 5 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
using System.Text;
1414
using System.Threading.Tasks;
1515

16-
namespace Microsoft.PowerShell.EditorServices.Transport.Stdio
16+
namespace Microsoft.PowerShell.EditorServices.Host
1717
{
1818
public class StdioConsoleHost : IConsoleHost
1919
{
@@ -38,27 +38,6 @@ public StdioConsoleHost(MessageWriter messageWriter)
3838

3939
#region IConsoleHost Implementation
4040

41-
void IConsoleHost.WriteOutput(
42-
string outputString,
43-
bool includeNewLine,
44-
OutputType outputType,
45-
ConsoleColor foregroundColor,
46-
ConsoleColor backgroundColor)
47-
{
48-
this.messageWriter.WriteMessage(
49-
new ReplWriteOutputEvent
50-
{
51-
Body = new ReplWriteOutputEventBody
52-
{
53-
LineContents = outputString,
54-
LineType = outputType,
55-
IncludeNewLine = includeNewLine,
56-
ForegroundColor = foregroundColor,
57-
BackgroundColor = backgroundColor
58-
}
59-
});
60-
}
61-
6241
Task<int> IConsoleHost.PromptForChoice(
6342
string caption,
6443
string message,
@@ -113,5 +92,9 @@ void IConsoleHost.ExitSession(int exitCode)
11392
}
11493

11594
#endregion
95+
96+
public void WriteOutput(string outputString, bool includeNewLine = true, OutputType outputType = OutputType.Normal, ConsoleColor foregroundColor = ConsoleColor.White, ConsoleColor backgroundColor = ConsoleColor.Black)
97+
{
98+
}
11699
}
117100
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<packages>
3+
<package id="Nito.AsyncEx" version="3.0.1" targetFramework="net45" />
4+
</packages>
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
using Microsoft.PowerShell.EditorServices.Transport.Stdio.Message;
2+
3+
namespace Microsoft.PowerShell.EditorServices.Transport.Stdio.Event
4+
{
5+
[MessageTypeName("exited")]
6+
public class ExitedEvent : EventBase<ExitedEventBody>
7+
{
8+
}
9+
10+
public class ExitedEventBody
11+
{
12+
public int ExitCode { get; set; }
13+
}
14+
}

0 commit comments

Comments
 (0)