Skip to content

Re-enable stdio clients by fixing initialization sequence #1801

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
May 17, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions src/PowerShellEditorServices.Hosting/EditorServicesLoader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
using System.Management.Automation;
using System.Management.Automation.Runspaces;

#if DEBUG
#if ASSEMBLY_LOAD_STACKTRACE
using System.Diagnostics;
#endif

Expand Down Expand Up @@ -98,7 +98,7 @@ public static EditorServicesLoader Create(

AssemblyLoadContext.Default.Resolving += (AssemblyLoadContext _, AssemblyName asmName) =>
{
#if DEBUG
#if ASSEMBLY_LOAD_STACKTRACE
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yesssssssssss

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was really bugging me, but didn't want to throw this away either.

logger.Log(PsesLogLevel.Diagnostic, $"Assembly resolve event fired for {asmName}. Stacktrace:\n{new StackTrace()}");
#else
logger.Log(PsesLogLevel.Diagnostic, $"Assembly resolve event fired for {asmName}");
Expand Down Expand Up @@ -138,7 +138,7 @@ public static EditorServicesLoader Create(
// Unlike in .NET Core, we need to be look for all dependencies in .NET Framework, not just PSES.dll
AppDomain.CurrentDomain.AssemblyResolve += (object sender, ResolveEventArgs args) =>
{
#if DEBUG
#if ASSEMBLY_LOAD_STACKTRACE
logger.Log(PsesLogLevel.Diagnostic, $"Assembly resolve event fired for {args.Name}. Stacktrace:\n{new StackTrace()}");
#else
logger.Log(PsesLogLevel.Diagnostic, $"Assembly resolve event fired for {args.Name}");
Expand Down Expand Up @@ -208,6 +208,7 @@ public Task LoadAndRunEditorServicesAsync()
#endif

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

// Check to see if the configuration we have is valid
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ public PsesDebugServer CreateDebugServerForTempSession(
.AddSerilog()
.SetMinimumLevel(LogLevel.Trace)) // TODO: Why randomly set to trace?
.AddSingleton<ILanguageServerFacade>(_ => null)
// TODO: Why add these for a debug server?!
.AddPsesLanguageServices(hostStartupInfo)
// For a Temp session, there is no LanguageServer so just set it to null
.AddSingleton(
Expand Down
19 changes: 6 additions & 13 deletions src/PowerShellEditorServices/Server/PsesDebugServer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@

using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
Expand All @@ -23,15 +22,10 @@ internal class PsesDebugServer : IDisposable
private readonly Stream _inputStream;
private readonly Stream _outputStream;
private readonly TaskCompletionSource<bool> _serverStopped;

private DebugAdapterServer _debugAdapterServer;

private PsesInternalHost _psesHost;

private bool _startedPses;

private readonly bool _isTemp;

protected readonly ILoggerFactory _loggerFactory;

public PsesDebugServer(
Expand Down Expand Up @@ -91,10 +85,12 @@ public async Task StartAsync()
.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, _, _) =>
.OnInitialize(async (server, _, cancellationToken) =>
{
// We need to make sure the host has been started
_startedPses = !await _psesHost.TryStartAsync(new HostStartOptions(), CancellationToken.None).ConfigureAwait(false);
// Start the host if not already started, and enable debug mode (required
// for remote debugging).
_startedPses = !await _psesHost.TryStartAsync(new HostStartOptions(), cancellationToken).ConfigureAwait(false);
_psesHost.DebugContext.EnableDebugMode();

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

// Ensure the debugger mode is set correctly - this is required for remote debugging to work
_psesHost.DebugContext.EnableDebugMode();

// Clear any existing breakpoints before proceeding.
BreakpointService 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:
Expand Down
42 changes: 19 additions & 23 deletions src/PowerShellEditorServices/Server/PsesLanguageServer.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System;
using System.IO;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
Expand All @@ -12,6 +11,8 @@
using Microsoft.PowerShell.EditorServices.Services.Extension;
using Microsoft.PowerShell.EditorServices.Services.PowerShell.Host;
using Microsoft.PowerShell.EditorServices.Services.Template;
using Newtonsoft.Json.Linq;
using OmniSharp.Extensions.LanguageServer.Protocol.Models;
using OmniSharp.Extensions.LanguageServer.Protocol.Server;
using OmniSharp.Extensions.LanguageServer.Server;
using Serilog;
Expand All @@ -24,15 +25,12 @@ namespace Microsoft.PowerShell.EditorServices.Server
internal class PsesLanguageServer
{
internal ILoggerFactory LoggerFactory { get; }

internal ILanguageServer LanguageServer { get; private set; }

private readonly LogLevel _minimumLogLevel;
private readonly Stream _inputStream;
private readonly Stream _outputStream;
private readonly HostStartupInfo _hostDetails;
private readonly TaskCompletionSource<bool> _serverStart;

private PsesInternalHost _psesHost;

/// <summary>
Expand Down Expand Up @@ -117,33 +115,32 @@ public async Task StartAsync()
// _Initialize_ request:
// https://microsoft.github.io/language-server-protocol/specifications/specification-current/#initialize
.OnInitialize(
(languageServer, request, _) =>
(languageServer, initializeParams, cancellationToken) =>
{
Log.Logger.Debug("Initializing OmniSharp Language Server");

IServiceProvider serviceProvider = languageServer.Services;

_psesHost = serviceProvider.GetService<PsesInternalHost>();

WorkspaceService workspaceService = serviceProvider.GetService<WorkspaceService>();

// Grab the workspace path from the parameters
if (request.RootUri != null)
{
workspaceService.WorkspacePath = request.RootUri.GetFileSystemPath();
}
else if (request.WorkspaceFolders != null)
// Set the workspace path from the parameters.
WorkspaceService workspaceService = languageServer.Services.GetService<WorkspaceService>();
if (initializeParams.WorkspaceFolders is not null)
{
// If RootUri isn't set, try to use the first WorkspaceFolder.
// TODO: Support multi-workspace.
foreach (OmniSharp.Extensions.LanguageServer.Protocol.Models.WorkspaceFolder workspaceFolder in request.WorkspaceFolders)
foreach (WorkspaceFolder workspaceFolder in initializeParams.WorkspaceFolders)
{
workspaceService.WorkspacePath = workspaceFolder.Uri.GetFileSystemPath();
break;
}
}

return Task.CompletedTask;
// Parse initialization options.
JObject initializationOptions = initializeParams.InitializationOptions as JObject;
HostStartOptions hostStartOptions = new()
{
LoadProfiles = initializationOptions?.GetValue("EnableProfileLoading")?.Value<bool>() ?? false,
// TODO: Consider deprecating the setting which sets this and
// instead use WorkspacePath exclusively.
InitialWorkingDirectory = initializationOptions?.GetValue("InitialWorkingDirectory")?.Value<string>() ?? workspaceService.WorkspacePath
};

_psesHost = languageServer.Services.GetService<PsesInternalHost>();
return _psesHost.TryStartAsync(hostStartOptions, cancellationToken);
});
}).ConfigureAwait(false);

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,10 @@ public static IServiceCollection AddPsesLanguageServices(
provider.GetService<EditorOperationsService>(),
provider.GetService<IInternalPowerShellExecutionService>());

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

return extensionService;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,44 +12,38 @@ internal class NullPSHostRawUI : PSHostRawUserInterface

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

public override ConsoleColor BackgroundColor { get; set; }
public override Size BufferSize { get; set; }
public override Coordinates CursorPosition { get; set; }
public override int CursorSize { get; set; }
public override ConsoleColor ForegroundColor { get; set; }
public override Coordinates WindowPosition { get; set; }

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

public override Size MaxPhysicalWindowSize => MaxWindowSize;

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

public override ConsoleColor ForegroundColor { get; set; }

public override int CursorSize { get; set; }

public override Coordinates CursorPosition { get; set; }

public override Size BufferSize { get; set; }

public override ConsoleColor BackgroundColor { get; set; }

public override Coordinates WindowPosition { get; set; }
public override Size WindowSize { get; set; }

public override string WindowTitle { get; set; }

public override void FlushInputBuffer()
{
// Do nothing
}
public override void FlushInputBuffer() { }

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

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

public override void ScrollBufferContents(Rectangle source, Coordinates destination, Rectangle clip, BufferCell fill)
{
// Do nothing
}
public override void ScrollBufferContents(Rectangle source, Coordinates destination, Rectangle clip, BufferCell fill) { }

public override void SetBufferContents(Coordinates origin, BufferCell[,] contents)
{
// Do nothing
}
public override void SetBufferContents(Coordinates origin, BufferCell[,] contents) { }

public override void SetBufferContents(Rectangle rectangle, BufferCell fill)
{
// Do nothing
}
public override void SetBufferContents(Rectangle rectangle, BufferCell fill) { }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ internal class NullPSHostUI : PSHostUserInterface
{
public NullPSHostUI() => RawUI = new NullPSHostRawUI();

public override bool SupportsVirtualTerminal => false;

public override PSHostRawUserInterface RawUI { get; }

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

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

public override void Write(ConsoleColor foregroundColor, ConsoleColor backgroundColor, string value)
{
// Do nothing
}

public override void Write(string value)
{
// Do nothing
}

public override void WriteDebugLine(string message)
{
// Do nothing
}

public override void WriteErrorLine(string value)
{
// Do nothing
}

public override void WriteLine(string value)
{
// Do nothing
}

public override void WriteProgress(long sourceId, ProgressRecord record)
{
// Do nothing
}

public override void WriteVerboseLine(string message)
{
// Do nothing
}

public override void WriteWarningLine(string message)
{
// Do nothing
}
public override void Write(ConsoleColor foregroundColor, ConsoleColor backgroundColor, string value) { }

public override void Write(string value) { }

public override void WriteDebugLine(string message) { }

public override void WriteErrorLine(string value) { }

public override void WriteInformation(InformationRecord record) { }

public override void WriteLine() { }

public override void WriteLine(ConsoleColor foregroundColor, ConsoleColor backgroundColor, string value) { }

public override void WriteLine(string value) { }

public override void WriteProgress(long sourceId, ProgressRecord record) { }

public override void WriteVerboseLine(string message) { }

public override void WriteWarningLine(string message) { }
}
}
Loading