-
Notifications
You must be signed in to change notification settings - Fork 234
/
Copy pathPsesDebugServer.cs
163 lines (144 loc) · 6.96 KB
/
PsesDebugServer.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using System;
using System.IO;
using System.Management.Automation;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.PowerShell.EditorServices.Handlers;
using Microsoft.PowerShell.EditorServices.Services;
using Microsoft.PowerShell.EditorServices.Utility;
using OmniSharp.Extensions.DebugAdapter.Protocol;
using OmniSharp.Extensions.DebugAdapter.Protocol.Serialization;
using OmniSharp.Extensions.DebugAdapter.Server;
using OmniSharp.Extensions.JsonRpc;
using OmniSharp.Extensions.LanguageServer.Server;
namespace Microsoft.PowerShell.EditorServices.Server
{
/// <summary>
/// Server for hosting debug sessions.
/// </summary>
internal class PsesDebugServer : IDisposable
{
/// <summary>
/// This is a bool but must be an int, since Interlocked.Exchange can't handle a bool
/// </summary>
private static int s_hasRunPsrlStaticCtor = 0;
private static readonly Lazy<CmdletInfo> s_lazyInvokeReadLineConstructorCmdletInfo = new Lazy<CmdletInfo>(() =>
{
var type = Type.GetType("Microsoft.PowerShell.EditorServices.Commands.InvokeReadLineConstructorCommand, Microsoft.PowerShell.EditorServices.Hosting");
return new CmdletInfo("__Invoke-ReadLineConstructor", type);
});
private readonly Stream _inputStream;
private readonly Stream _outputStream;
private readonly bool _useTempSession;
private readonly bool _usePSReadLine;
private readonly TaskCompletionSource<bool> _serverStopped;
private DebugAdapterServer _debugAdapterServer;
private PowerShellContextService _powerShellContextService;
protected readonly ILoggerFactory _loggerFactory;
public PsesDebugServer(
ILoggerFactory factory,
Stream inputStream,
Stream outputStream,
IServiceProvider serviceProvider,
bool useTempSession,
bool usePSReadLine)
{
_loggerFactory = factory;
_inputStream = inputStream;
_outputStream = outputStream;
ServiceProvider = serviceProvider;
_useTempSession = useTempSession;
_serverStopped = new TaskCompletionSource<bool>();
_usePSReadLine = usePSReadLine;
}
internal IServiceProvider ServiceProvider { get; }
/// <summary>
/// Start the debug server listening.
/// </summary>
/// <returns>A task that completes when the server is ready.</returns>
public async Task StartAsync()
{
_debugAdapterServer = await DebugAdapterServer.From(options =>
{
// We need to let the PowerShell Context Service know that we are in a debug session
// so that it doesn't send the powerShell/startDebugger message.
_powerShellContextService = ServiceProvider.GetService<PowerShellContextService>();
_powerShellContextService.IsDebugServerActive = true;
// Needed to make sure PSReadLine's static properties are initialized in the pipeline thread.
// This is only needed for Temp sessions who only have a debug server.
if (_usePSReadLine && _useTempSession && Interlocked.Exchange(ref s_hasRunPsrlStaticCtor, 1) == 0)
{
var command = new PSCommand()
.AddCommand(s_lazyInvokeReadLineConstructorCmdletInfo.Value);
// This must be run synchronously to ensure debugging works
_powerShellContextService
.ExecuteCommandAsync<object>(command, sendOutputToHost: true, sendErrorToHost: true)
.GetAwaiter()
.GetResult();
}
options
.WithInput(_inputStream)
.WithOutput(_outputStream)
.WithServices(serviceCollection => serviceCollection
.AddLogging()
.AddOptions()
.AddPsesDebugServices(ServiceProvider, this, _useTempSession))
.WithHandler<LaunchAndAttachHandler>()
.WithHandler<DisconnectHandler>()
.WithHandler<BreakpointHandlers>()
.WithHandler<ConfigurationDoneHandler>()
.WithHandler<ThreadsHandler>()
.WithHandler<StackTraceHandler>()
.WithHandler<ScopesHandler>()
.WithHandler<VariablesHandler>()
.WithHandler<DebuggerActionHandlers>()
.WithHandler<SourceHandler>()
.WithHandler<SetVariableHandler>()
.WithHandler<DebugEvaluateHandler>()
// The OnInitialize delegate gets run when we first receive the _Initialize_ request:
// https://microsoft.github.io/debug-adapter-protocol/specification#Requests_Initialize
.OnInitialize(async (server, request, cancellationToken) => {
var breakpointService = server.GetService<BreakpointService>();
// Clear any existing breakpoints before proceeding
await breakpointService.RemoveAllBreakpointsAsync().ConfigureAwait(false);
})
// The OnInitialized delegate gets run right before the server responds to the _Initialize_ request:
// https://microsoft.github.io/debug-adapter-protocol/specification#Requests_Initialize
.OnInitialized((server, request, response, cancellationToken) => {
response.SupportsConditionalBreakpoints = true;
response.SupportsConfigurationDoneRequest = true;
response.SupportsFunctionBreakpoints = true;
response.SupportsHitConditionalBreakpoints = true;
response.SupportsLogPoints = true;
response.SupportsSetVariable = true;
return Task.CompletedTask;
});
}).ConfigureAwait(false);
}
public void Dispose()
{
_powerShellContextService.IsDebugServerActive = false;
_debugAdapterServer.Dispose();
_inputStream.Dispose();
_outputStream.Dispose();
_serverStopped.SetResult(true);
}
public async Task WaitForShutdown()
{
await _serverStopped.Task.ConfigureAwait(false);
}
#region Events
public event EventHandler SessionEnded;
internal void OnSessionEnded()
{
SessionEnded?.Invoke(this, null);
}
#endregion
}
}