Skip to content

Commit afc9fd1

Browse files
Merge pull request #1801 from PowerShell/andschwa/stdio
Re-enable stdio clients by fixing initialization sequence
2 parents 044c81e + c1dd1db commit afc9fd1

19 files changed

+106
-203
lines changed

src/PowerShellEditorServices.Hosting/EditorServicesLoader.cs

+4-3
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
using System.Management.Automation;
1313
using System.Management.Automation.Runspaces;
1414

15-
#if DEBUG
15+
#if ASSEMBLY_LOAD_STACKTRACE
1616
using System.Diagnostics;
1717
#endif
1818

@@ -98,7 +98,7 @@ public static EditorServicesLoader Create(
9898

9999
AssemblyLoadContext.Default.Resolving += (AssemblyLoadContext _, AssemblyName asmName) =>
100100
{
101-
#if DEBUG
101+
#if ASSEMBLY_LOAD_STACKTRACE
102102
logger.Log(PsesLogLevel.Diagnostic, $"Assembly resolve event fired for {asmName}. Stacktrace:\n{new StackTrace()}");
103103
#else
104104
logger.Log(PsesLogLevel.Diagnostic, $"Assembly resolve event fired for {asmName}");
@@ -138,7 +138,7 @@ public static EditorServicesLoader Create(
138138
// Unlike in .NET Core, we need to be look for all dependencies in .NET Framework, not just PSES.dll
139139
AppDomain.CurrentDomain.AssemblyResolve += (object sender, ResolveEventArgs args) =>
140140
{
141-
#if DEBUG
141+
#if ASSEMBLY_LOAD_STACKTRACE
142142
logger.Log(PsesLogLevel.Diagnostic, $"Assembly resolve event fired for {args.Name}. Stacktrace:\n{new StackTrace()}");
143143
#else
144144
logger.Log(PsesLogLevel.Diagnostic, $"Assembly resolve event fired for {args.Name}");
@@ -208,6 +208,7 @@ public Task LoadAndRunEditorServicesAsync()
208208
#endif
209209

210210
// Add the bundled modules to the PSModulePath
211+
// TODO: Why do we do this in addition to passing the bundled module path to the host?
211212
UpdatePSModulePath();
212213

213214
// Check to see if the configuration we have is valid

src/PowerShellEditorServices/Hosting/EditorServicesServerFactory.cs

+1
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,7 @@ public PsesDebugServer CreateDebugServerForTempSession(
152152
.AddSerilog()
153153
.SetMinimumLevel(LogLevel.Trace)) // TODO: Why randomly set to trace?
154154
.AddSingleton<ILanguageServerFacade>(_ => null)
155+
// TODO: Why add these for a debug server?!
155156
.AddPsesLanguageServices(hostStartupInfo)
156157
// For a Temp session, there is no LanguageServer so just set it to null
157158
.AddSingleton(

src/PowerShellEditorServices/Server/PsesDebugServer.cs

+6-13
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33

44
using System;
55
using System.IO;
6-
using System.Threading;
76
using System.Threading.Tasks;
87
using Microsoft.Extensions.DependencyInjection;
98
using Microsoft.Extensions.Logging;
@@ -23,15 +22,10 @@ internal class PsesDebugServer : IDisposable
2322
private readonly Stream _inputStream;
2423
private readonly Stream _outputStream;
2524
private readonly TaskCompletionSource<bool> _serverStopped;
26-
2725
private DebugAdapterServer _debugAdapterServer;
28-
2926
private PsesInternalHost _psesHost;
30-
3127
private bool _startedPses;
32-
3328
private readonly bool _isTemp;
34-
3529
protected readonly ILoggerFactory _loggerFactory;
3630

3731
public PsesDebugServer(
@@ -91,10 +85,12 @@ public async Task StartAsync()
9185
.WithHandler<DebugEvaluateHandler>()
9286
// The OnInitialize delegate gets run when we first receive the _Initialize_ request:
9387
// https://microsoft.github.io/debug-adapter-protocol/specification#Requests_Initialize
94-
.OnInitialize(async (server, _, _) =>
88+
.OnInitialize(async (server, _, cancellationToken) =>
9589
{
96-
// We need to make sure the host has been started
97-
_startedPses = !await _psesHost.TryStartAsync(new HostStartOptions(), CancellationToken.None).ConfigureAwait(false);
90+
// Start the host if not already started, and enable debug mode (required
91+
// for remote debugging).
92+
_startedPses = !await _psesHost.TryStartAsync(new HostStartOptions(), cancellationToken).ConfigureAwait(false);
93+
_psesHost.DebugContext.EnableDebugMode();
9894

9995
// We need to give the host a handle to the DAP so it can register
10096
// notifications (specifically for sendKeyPress).
@@ -103,11 +99,8 @@ public async Task StartAsync()
10399
_psesHost.DebugServer = server;
104100
}
105101

106-
// Ensure the debugger mode is set correctly - this is required for remote debugging to work
107-
_psesHost.DebugContext.EnableDebugMode();
108-
102+
// Clear any existing breakpoints before proceeding.
109103
BreakpointService breakpointService = server.GetService<BreakpointService>();
110-
// Clear any existing breakpoints before proceeding
111104
await breakpointService.RemoveAllBreakpointsAsync().ConfigureAwait(false);
112105
})
113106
// The OnInitialized delegate gets run right before the server responds to the _Initialize_ request:

src/PowerShellEditorServices/Server/PsesLanguageServer.cs

+19-23
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
// Copyright (c) Microsoft Corporation.
22
// Licensed under the MIT License.
33

4-
using System;
54
using System.IO;
65
using System.Threading.Tasks;
76
using Microsoft.Extensions.DependencyInjection;
@@ -12,6 +11,8 @@
1211
using Microsoft.PowerShell.EditorServices.Services.Extension;
1312
using Microsoft.PowerShell.EditorServices.Services.PowerShell.Host;
1413
using Microsoft.PowerShell.EditorServices.Services.Template;
14+
using Newtonsoft.Json.Linq;
15+
using OmniSharp.Extensions.LanguageServer.Protocol.Models;
1516
using OmniSharp.Extensions.LanguageServer.Protocol.Server;
1617
using OmniSharp.Extensions.LanguageServer.Server;
1718
using Serilog;
@@ -24,15 +25,12 @@ namespace Microsoft.PowerShell.EditorServices.Server
2425
internal class PsesLanguageServer
2526
{
2627
internal ILoggerFactory LoggerFactory { get; }
27-
2828
internal ILanguageServer LanguageServer { get; private set; }
29-
3029
private readonly LogLevel _minimumLogLevel;
3130
private readonly Stream _inputStream;
3231
private readonly Stream _outputStream;
3332
private readonly HostStartupInfo _hostDetails;
3433
private readonly TaskCompletionSource<bool> _serverStart;
35-
3634
private PsesInternalHost _psesHost;
3735

3836
/// <summary>
@@ -117,33 +115,32 @@ public async Task StartAsync()
117115
// _Initialize_ request:
118116
// https://microsoft.github.io/language-server-protocol/specifications/specification-current/#initialize
119117
.OnInitialize(
120-
(languageServer, request, _) =>
118+
(languageServer, initializeParams, cancellationToken) =>
121119
{
122-
Log.Logger.Debug("Initializing OmniSharp Language Server");
123-
124-
IServiceProvider serviceProvider = languageServer.Services;
125-
126-
_psesHost = serviceProvider.GetService<PsesInternalHost>();
127-
128-
WorkspaceService workspaceService = serviceProvider.GetService<WorkspaceService>();
129-
130-
// Grab the workspace path from the parameters
131-
if (request.RootUri != null)
132-
{
133-
workspaceService.WorkspacePath = request.RootUri.GetFileSystemPath();
134-
}
135-
else if (request.WorkspaceFolders != null)
120+
// Set the workspace path from the parameters.
121+
WorkspaceService workspaceService = languageServer.Services.GetService<WorkspaceService>();
122+
if (initializeParams.WorkspaceFolders is not null)
136123
{
137-
// If RootUri isn't set, try to use the first WorkspaceFolder.
138124
// TODO: Support multi-workspace.
139-
foreach (OmniSharp.Extensions.LanguageServer.Protocol.Models.WorkspaceFolder workspaceFolder in request.WorkspaceFolders)
125+
foreach (WorkspaceFolder workspaceFolder in initializeParams.WorkspaceFolders)
140126
{
141127
workspaceService.WorkspacePath = workspaceFolder.Uri.GetFileSystemPath();
142128
break;
143129
}
144130
}
145131

146-
return Task.CompletedTask;
132+
// Parse initialization options.
133+
JObject initializationOptions = initializeParams.InitializationOptions as JObject;
134+
HostStartOptions hostStartOptions = new()
135+
{
136+
LoadProfiles = initializationOptions?.GetValue("EnableProfileLoading")?.Value<bool>() ?? false,
137+
// TODO: Consider deprecating the setting which sets this and
138+
// instead use WorkspacePath exclusively.
139+
InitialWorkingDirectory = initializationOptions?.GetValue("InitialWorkingDirectory")?.Value<string>() ?? workspaceService.WorkspacePath
140+
};
141+
142+
_psesHost = languageServer.Services.GetService<PsesInternalHost>();
143+
return _psesHost.TryStartAsync(hostStartOptions, cancellationToken);
147144
});
148145
}).ConfigureAwait(false);
149146

@@ -156,7 +153,6 @@ public async Task StartAsync()
156153
/// <returns>A task that completes when the server is shut down.</returns>
157154
public async Task WaitForShutdown()
158155
{
159-
Log.Logger.Debug("Shutting down OmniSharp Language Server");
160156
await _serverStart.Task.ConfigureAwait(false);
161157
await LanguageServer.WaitForExit.ConfigureAwait(false);
162158

src/PowerShellEditorServices/Server/PsesServiceCollectionExtensions.cs

+4-5
Original file line numberDiff line numberDiff line change
@@ -44,11 +44,10 @@ public static IServiceCollection AddPsesLanguageServices(
4444
provider.GetService<EditorOperationsService>(),
4545
provider.GetService<IInternalPowerShellExecutionService>());
4646

47-
// This is where we create the $psEditor variable
48-
// so that when the console is ready, it will be available
49-
// TODO: Improve the sequencing here so that:
50-
// - The variable is guaranteed to be initialized when the console first appears
51-
// - Any errors that occur are handled rather than lost by the unawaited task
47+
// This is where we create the $psEditor variable so that when the console
48+
// is ready, it will be available. NOTE: We cannot await this because it
49+
// uses a lazy initialization to avoid a race with the dependency injection
50+
// framework, see the EditorObject class for that!
5251
extensionService.InitializeAsync();
5352

5453
return extensionService;

src/PowerShellEditorServices/Services/PowerShell/Host/NullPSHostRawUI.cs

+18-24
Original file line numberDiff line numberDiff line change
@@ -12,44 +12,38 @@ internal class NullPSHostRawUI : PSHostRawUserInterface
1212

1313
public NullPSHostRawUI() => _buffer = new BufferCell[0, 0];
1414

15-
public override ConsoleColor BackgroundColor { get; set; }
16-
public override Size BufferSize { get; set; }
17-
public override Coordinates CursorPosition { get; set; }
18-
public override int CursorSize { get; set; }
19-
public override ConsoleColor ForegroundColor { get; set; }
15+
public override Coordinates WindowPosition { get; set; }
2016

21-
public override bool KeyAvailable => false;
17+
public override Size MaxWindowSize => new() { Width = _buffer.GetLength(0), Height = _buffer.GetLength(1) };
2218

2319
public override Size MaxPhysicalWindowSize => MaxWindowSize;
2420

25-
public override Size MaxWindowSize => new() { Width = _buffer.GetLength(0), Height = _buffer.GetLength(1) };
21+
public override bool KeyAvailable => false;
22+
23+
public override ConsoleColor ForegroundColor { get; set; }
24+
25+
public override int CursorSize { get; set; }
26+
27+
public override Coordinates CursorPosition { get; set; }
28+
29+
public override Size BufferSize { get; set; }
30+
31+
public override ConsoleColor BackgroundColor { get; set; }
2632

27-
public override Coordinates WindowPosition { get; set; }
2833
public override Size WindowSize { get; set; }
34+
2935
public override string WindowTitle { get; set; }
3036

31-
public override void FlushInputBuffer()
32-
{
33-
// Do nothing
34-
}
37+
public override void FlushInputBuffer() { }
3538

3639
public override BufferCell[,] GetBufferContents(Rectangle rectangle) => _buffer;
3740

3841
public override KeyInfo ReadKey(ReadKeyOptions options) => default;
3942

40-
public override void ScrollBufferContents(Rectangle source, Coordinates destination, Rectangle clip, BufferCell fill)
41-
{
42-
// Do nothing
43-
}
43+
public override void ScrollBufferContents(Rectangle source, Coordinates destination, Rectangle clip, BufferCell fill) { }
4444

45-
public override void SetBufferContents(Coordinates origin, BufferCell[,] contents)
46-
{
47-
// Do nothing
48-
}
45+
public override void SetBufferContents(Coordinates origin, BufferCell[,] contents) { }
4946

50-
public override void SetBufferContents(Rectangle rectangle, BufferCell fill)
51-
{
52-
// Do nothing
53-
}
47+
public override void SetBufferContents(Rectangle rectangle, BufferCell fill) { }
5448
}
5549
}

src/PowerShellEditorServices/Services/PowerShell/Host/NullPSHostUI.cs

+23-39
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ internal class NullPSHostUI : PSHostUserInterface
1414
{
1515
public NullPSHostUI() => RawUI = new NullPSHostRawUI();
1616

17+
public override bool SupportsVirtualTerminal => false;
18+
1719
public override PSHostRawUserInterface RawUI { get; }
1820

1921
public override Dictionary<string, PSObject> Prompt(string caption, string message, Collection<FieldDescription> descriptions) => new();
@@ -29,44 +31,26 @@ public override PSCredential PromptForCredential(string caption, string message,
2931

3032
public override SecureString ReadLineAsSecureString() => new();
3133

32-
public override void Write(ConsoleColor foregroundColor, ConsoleColor backgroundColor, string value)
33-
{
34-
// Do nothing
35-
}
36-
37-
public override void Write(string value)
38-
{
39-
// Do nothing
40-
}
41-
42-
public override void WriteDebugLine(string message)
43-
{
44-
// Do nothing
45-
}
46-
47-
public override void WriteErrorLine(string value)
48-
{
49-
// Do nothing
50-
}
51-
52-
public override void WriteLine(string value)
53-
{
54-
// Do nothing
55-
}
56-
57-
public override void WriteProgress(long sourceId, ProgressRecord record)
58-
{
59-
// Do nothing
60-
}
61-
62-
public override void WriteVerboseLine(string message)
63-
{
64-
// Do nothing
65-
}
66-
67-
public override void WriteWarningLine(string message)
68-
{
69-
// Do nothing
70-
}
34+
public override void Write(ConsoleColor foregroundColor, ConsoleColor backgroundColor, string value) { }
35+
36+
public override void Write(string value) { }
37+
38+
public override void WriteDebugLine(string message) { }
39+
40+
public override void WriteErrorLine(string value) { }
41+
42+
public override void WriteInformation(InformationRecord record) { }
43+
44+
public override void WriteLine() { }
45+
46+
public override void WriteLine(ConsoleColor foregroundColor, ConsoleColor backgroundColor, string value) { }
47+
48+
public override void WriteLine(string value) { }
49+
50+
public override void WriteProgress(long sourceId, ProgressRecord record) { }
51+
52+
public override void WriteVerboseLine(string message) { }
53+
54+
public override void WriteWarningLine(string message) { }
7155
}
7256
}

0 commit comments

Comments
 (0)