From 1104e7b0c50030338b06c7dce124308f9c1a448e Mon Sep 17 00:00:00 2001 From: David Wilson Date: Sun, 4 Jun 2017 13:35:29 -0700 Subject: [PATCH] Decouple ProtocolEndpoint from client and server implementations This change decouples the ProtocolEndpoint class from the existing client and server implementations for the language and debug services. The goal here is to eventually decentralize all editor feature implementations into individual classes, so separating out the ProtocolEndpoint is the first step in achieving this. This class will now be exposed in the framework as the IMessageSender interface. This change also simplifies the LanguageServer/DebugAdapter client and server implementations, removing some unnecessary abstraction and base classes. --- .../EditorServicesHost.cs | 39 ++++-- .../Client/DebugAdapterClientBase.cs | 82 ++++++++--- .../Client/LanguageClientBase.cs | 75 ++++++++-- .../Client/LanguageServiceClient.cs | 3 +- .../MessageProtocol/IMessageHandlers.cs | 25 ++++ .../MessageProtocol/IMessageSender.cs | 5 +- .../MessageProtocol/MessageDispatcher.cs | 16 ++- .../MessageProtocol/ProtocolEndpoint.cs | 59 +------- .../Server/DebugAdapter.cs | 111 ++++++++++----- .../Server/DebugAdapterBase.cs | 76 ---------- .../Server/LanguageServer.cs | 130 +++++++++++------- .../Server/LanguageServerBase.cs | 83 ----------- .../DebugAdapterTests.cs | 24 ++-- .../LanguageServerTests.cs | 28 ++-- .../OutputReader.cs | 6 +- .../ServerTestsBase.cs | 19 +-- .../Server/OutputDebouncerTests.cs | 6 + 17 files changed, 409 insertions(+), 378 deletions(-) create mode 100644 src/PowerShellEditorServices.Protocol/MessageProtocol/IMessageHandlers.cs delete mode 100644 src/PowerShellEditorServices.Protocol/Server/DebugAdapterBase.cs delete mode 100644 src/PowerShellEditorServices.Protocol/Server/LanguageServerBase.cs diff --git a/src/PowerShellEditorServices.Host/EditorServicesHost.cs b/src/PowerShellEditorServices.Host/EditorServicesHost.cs index ccabf9ba8..0904b3c60 100644 --- a/src/PowerShellEditorServices.Host/EditorServicesHost.cs +++ b/src/PowerShellEditorServices.Host/EditorServicesHost.cs @@ -183,6 +183,14 @@ private async void OnLanguageServiceClientConnect( object sender, TcpSocketServerChannel serverChannel) { + MessageDispatcher messageDispatcher = new MessageDispatcher(this.logger); + + ProtocolEndpoint protocolEndpoint = + new ProtocolEndpoint( + serverChannel, + messageDispatcher, + this.logger); + this.editorSession = CreateSession( this.hostDetails, @@ -192,7 +200,8 @@ private async void OnLanguageServiceClientConnect( this.languageServer = new LanguageServer( this.editorSession, - serverChannel, + messageDispatcher, + protocolEndpoint, this.logger); await this.editorSession.PowerShellContext.ImportCommandsModule( @@ -200,7 +209,8 @@ await this.editorSession.PowerShellContext.ImportCommandsModule( Path.GetDirectoryName(this.GetType().GetTypeInfo().Assembly.Location), @"..\..\Commands")); - await this.languageServer.Start(); + this.languageServer.Start(); + protocolEndpoint.Start(); } /// @@ -214,7 +224,7 @@ public void StartDebugService( { this.debugServiceListener = new TcpSocketServerListener( - MessageProtocolType.LanguageServer, + MessageProtocolType.DebugAdapter, debugServicePort, this.logger); @@ -228,15 +238,24 @@ public void StartDebugService( debugServicePort)); } - private async void OnDebugServiceClientConnect(object sender, TcpSocketServerChannel serverChannel) + private void OnDebugServiceClientConnect(object sender, TcpSocketServerChannel serverChannel) { + MessageDispatcher messageDispatcher = new MessageDispatcher(this.logger); + + ProtocolEndpoint protocolEndpoint = + new ProtocolEndpoint( + serverChannel, + messageDispatcher, + this.logger); + if (this.enableConsoleRepl) { this.debugAdapter = new DebugAdapter( this.editorSession, - serverChannel, false, + messageDispatcher, + protocolEndpoint, this.logger); } else @@ -250,8 +269,9 @@ private async void OnDebugServiceClientConnect(object sender, TcpSocketServerCha this.debugAdapter = new DebugAdapter( debugSession, - serverChannel, true, + messageDispatcher, + protocolEndpoint, this.logger); } @@ -265,7 +285,8 @@ private async void OnDebugServiceClientConnect(object sender, TcpSocketServerCha this.debugServiceListener.Start(); }; - await this.debugAdapter.Start(); + this.debugAdapter.Start(); + protocolEndpoint.Start(); } /// @@ -273,10 +294,10 @@ private async void OnDebugServiceClientConnect(object sender, TcpSocketServerCha /// public void StopServices() { - this.languageServer?.Stop().Wait(); + // TODO: Need a new way to shut down the services + this.languageServer = null; - this.debugAdapter?.Stop().Wait(); this.debugAdapter = null; } diff --git a/src/PowerShellEditorServices.Protocol/Client/DebugAdapterClientBase.cs b/src/PowerShellEditorServices.Protocol/Client/DebugAdapterClientBase.cs index aeab98bd8..c90730a6d 100644 --- a/src/PowerShellEditorServices.Protocol/Client/DebugAdapterClientBase.cs +++ b/src/PowerShellEditorServices.Protocol/Client/DebugAdapterClientBase.cs @@ -6,20 +6,46 @@ using Microsoft.PowerShell.EditorServices.Protocol.DebugAdapter; using Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol; using Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol.Channel; -using System.Threading.Tasks; using Microsoft.PowerShell.EditorServices.Utility; +using System.Threading.Tasks; +using System; namespace Microsoft.PowerShell.EditorServices.Protocol.Client { - public class DebugAdapterClient : ProtocolEndpoint + public class DebugAdapterClient : IMessageSender, IMessageHandlers { + private ILogger logger; + private ProtocolEndpoint protocolEndpoint; + private MessageDispatcher messageDispatcher; + public DebugAdapterClient(ChannelBase clientChannel, ILogger logger) - : base( + { + this.logger = logger; + this.messageDispatcher = new MessageDispatcher(logger); + this.protocolEndpoint = new ProtocolEndpoint( clientChannel, - new MessageDispatcher(logger), - MessageProtocolType.DebugAdapter, - logger) + messageDispatcher, + logger); + } + + public async Task Start() + { + this.protocolEndpoint.Start(); + + // Initialize the debug adapter + await this.SendRequest( + InitializeRequest.Type, + new InitializeRequestArguments + { + LinesStartAt1 = true, + ColumnsStartAt1 = true + }, + true); + } + + public void Stop() { + this.protocolEndpoint.Stop(); } public async Task LaunchScript(string scriptFilePath) @@ -28,21 +54,43 @@ await this.SendRequest( LaunchRequest.Type, new LaunchRequestArguments { Script = scriptFilePath - }); + }, + true); - await this.SendRequest(ConfigurationDoneRequest.Type, null); + await this.SendRequest( + ConfigurationDoneRequest.Type, + null, + true); } - protected override Task OnStart() + public Task SendEvent(NotificationType eventType, TParams eventParams) { - // Initialize the debug adapter - return this.SendRequest( - InitializeRequest.Type, - new InitializeRequestArguments - { - LinesStartAt1 = true, - ColumnsStartAt1 = true - }); + return ((IMessageSender)protocolEndpoint).SendEvent(eventType, eventParams); + } + + public Task SendRequest(RequestType requestType, TParams requestParams, bool waitForResponse) + { + return ((IMessageSender)protocolEndpoint).SendRequest(requestType, requestParams, waitForResponse); + } + + public Task SendRequest(RequestType0 requestType0) + { + return ((IMessageSender)protocolEndpoint).SendRequest(requestType0); + } + + public void SetRequestHandler(RequestType requestType, Func, Task> requestHandler) + { + ((IMessageHandlers)messageDispatcher).SetRequestHandler(requestType, requestHandler); + } + + public void SetRequestHandler(RequestType0 requestType0, Func, Task> requestHandler) + { + ((IMessageHandlers)messageDispatcher).SetRequestHandler(requestType0, requestHandler); + } + + public void SetEventHandler(NotificationType eventType, Func eventHandler) + { + ((IMessageHandlers)messageDispatcher).SetEventHandler(eventType, eventHandler); } } } diff --git a/src/PowerShellEditorServices.Protocol/Client/LanguageClientBase.cs b/src/PowerShellEditorServices.Protocol/Client/LanguageClientBase.cs index e7d780263..c8dc383dc 100644 --- a/src/PowerShellEditorServices.Protocol/Client/LanguageClientBase.cs +++ b/src/PowerShellEditorServices.Protocol/Client/LanguageClientBase.cs @@ -6,44 +6,97 @@ using Microsoft.PowerShell.EditorServices.Protocol.LanguageServer; using Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol; using Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol.Channel; -using System.Threading.Tasks; using Microsoft.PowerShell.EditorServices.Utility; +using System; +using System.Threading.Tasks; namespace Microsoft.PowerShell.EditorServices.Protocol.Client { /// /// Provides a base implementation for language server clients. /// - public abstract class LanguageClientBase : ProtocolEndpoint + public abstract class LanguageClientBase : IMessageHandlers, IMessageSender { + ILogger logger; + private ProtocolEndpoint protocolEndpoint; + private MessageDispatcher messageDispatcher; + /// /// Initializes an instance of the language client using the /// specified channel for communication. /// /// The channel to use for communication with the server. public LanguageClientBase(ChannelBase clientChannel, ILogger logger) - : base( - clientChannel, - new MessageDispatcher(logger), - MessageProtocolType.LanguageServer, - logger) { + this.logger = logger; + this.messageDispatcher = new MessageDispatcher(logger); + this.protocolEndpoint = new ProtocolEndpoint( + clientChannel, + messageDispatcher, + logger); } - protected override Task OnStart() + public Task Start() { + this.protocolEndpoint.Start(); + // Initialize the implementation class return this.Initialize(); } - protected override async Task OnStop() + public async Task Stop() { + await this.OnStop(); + // First, notify the language server that we're stopping - var response = await this.SendRequest(ShutdownRequest.Type); + var response = + await this.SendRequest( + ShutdownRequest.Type); + await this.SendEvent(ExitNotification.Type, new object()); + + this.protocolEndpoint.Stop(); + } + + protected virtual Task OnStop() + { + return Task.FromResult(true); + } + + protected virtual Task Initialize() + { + return Task.FromResult(true); + } + + public Task SendEvent(NotificationType eventType, TParams eventParams) + { + return ((IMessageSender)protocolEndpoint).SendEvent(eventType, eventParams); + } + + public Task SendRequest(RequestType requestType, TParams requestParams, bool waitForResponse) + { + return ((IMessageSender)protocolEndpoint).SendRequest(requestType, requestParams, waitForResponse); + } + + public Task SendRequest(RequestType0 requestType0) + { + return ((IMessageSender)protocolEndpoint).SendRequest(requestType0); } - protected abstract Task Initialize(); + public void SetRequestHandler(RequestType requestType, Func, Task> requestHandler) + { + ((IMessageHandlers)messageDispatcher).SetRequestHandler(requestType, requestHandler); + } + + public void SetRequestHandler(RequestType0 requestType0, Func, Task> requestHandler) + { + ((IMessageHandlers)messageDispatcher).SetRequestHandler(requestType0, requestHandler); + } + + public void SetEventHandler(NotificationType eventType, Func eventHandler) + { + ((IMessageHandlers)messageDispatcher).SetEventHandler(eventType, eventHandler); + } } } diff --git a/src/PowerShellEditorServices.Protocol/Client/LanguageServiceClient.cs b/src/PowerShellEditorServices.Protocol/Client/LanguageServiceClient.cs index 119fd09f8..8878814dd 100644 --- a/src/PowerShellEditorServices.Protocol/Client/LanguageServiceClient.cs +++ b/src/PowerShellEditorServices.Protocol/Client/LanguageServiceClient.cs @@ -38,7 +38,8 @@ protected override Task Initialize() return this.SendRequest( InitializeRequest.Type, - initializeParams); + initializeParams, + true); } #region Events diff --git a/src/PowerShellEditorServices.Protocol/MessageProtocol/IMessageHandlers.cs b/src/PowerShellEditorServices.Protocol/MessageProtocol/IMessageHandlers.cs new file mode 100644 index 000000000..04e1ba9d6 --- /dev/null +++ b/src/PowerShellEditorServices.Protocol/MessageProtocol/IMessageHandlers.cs @@ -0,0 +1,25 @@ +// +// 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.Threading.Tasks; + +namespace Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol +{ + public interface IMessageHandlers + { + void SetRequestHandler( + RequestType requestType, + Func, Task> requestHandler); + + void SetRequestHandler( + RequestType0 requestType0, + Func, Task> requestHandler); + + void SetEventHandler( + NotificationType eventType, + Func eventHandler); + } +} diff --git a/src/PowerShellEditorServices.Protocol/MessageProtocol/IMessageSender.cs b/src/PowerShellEditorServices.Protocol/MessageProtocol/IMessageSender.cs index 5b4da2452..e32556cdc 100644 --- a/src/PowerShellEditorServices.Protocol/MessageProtocol/IMessageSender.cs +++ b/src/PowerShellEditorServices.Protocol/MessageProtocol/IMessageSender.cs @@ -7,7 +7,7 @@ namespace Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol { - internal interface IMessageSender + public interface IMessageSender { Task SendEvent( NotificationType eventType, @@ -17,6 +17,9 @@ Task SendRequest( RequestType requestType, TParams requestParams, bool waitForResponse); + + Task SendRequest( + RequestType0 requestType0); } } diff --git a/src/PowerShellEditorServices.Protocol/MessageProtocol/MessageDispatcher.cs b/src/PowerShellEditorServices.Protocol/MessageProtocol/MessageDispatcher.cs index 4338ca3b3..8ad9557c7 100644 --- a/src/PowerShellEditorServices.Protocol/MessageProtocol/MessageDispatcher.cs +++ b/src/PowerShellEditorServices.Protocol/MessageProtocol/MessageDispatcher.cs @@ -13,7 +13,7 @@ namespace Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol { - public class MessageDispatcher + public class MessageDispatcher : IMessageHandlers { #region Fields @@ -79,6 +79,18 @@ public void SetRequestHandler( }); } + public void SetRequestHandler( + RequestType0 requestType0, + Func, Task> requestHandler) + { + this.SetRequestHandler( + RequestType.ConvertToRequestType(requestType0), + (param1, requestContext) => + { + return requestHandler(requestContext); + }); + } + public void SetEventHandler( NotificationType eventType, Func eventHandler) @@ -86,7 +98,7 @@ public void SetEventHandler( this.SetEventHandler( eventType, eventHandler, - false); + true); } public void SetEventHandler( diff --git a/src/PowerShellEditorServices.Protocol/MessageProtocol/ProtocolEndpoint.cs b/src/PowerShellEditorServices.Protocol/MessageProtocol/ProtocolEndpoint.cs index 4a11d4eec..037d70290 100644 --- a/src/PowerShellEditorServices.Protocol/MessageProtocol/ProtocolEndpoint.cs +++ b/src/PowerShellEditorServices.Protocol/MessageProtocol/ProtocolEndpoint.cs @@ -30,7 +30,6 @@ private enum ProtocolEndpointState private int currentMessageId; private ChannelBase protocolChannel; private AsyncContextThread messageLoopThread; - private MessageProtocolType messageProtocolType; private TaskCompletionSource endpointExitedTask; private SynchronizationContext originalSynchronizationContext; private CancellationTokenSource messageLoopCancellationToken = @@ -74,12 +73,10 @@ private bool InMessageLoopThread public ProtocolEndpoint( ChannelBase protocolChannel, MessageDispatcher messageDispatcher, - MessageProtocolType messageProtocolType, ILogger logger) { this.protocolChannel = protocolChannel; this.MessageDispatcher = messageDispatcher; - this.messageProtocolType = messageProtocolType; this.originalSynchronizationContext = SynchronizationContext.Current; this.Logger = logger; } @@ -87,23 +84,16 @@ public ProtocolEndpoint( /// /// Starts the language server client and sends the Initialize method. /// - /// A Task that can be awaited for initialization to complete. - public async Task Start() + public void Start() { if (this.currentState == ProtocolEndpointState.NotStarted) { - // Start the provided protocol channel - this.protocolChannel.Start(this.messageProtocolType); - // Listen for unhandled exceptions from the message loop this.UnhandledException += MessageDispatcher_UnhandledException; // Start the message loop this.StartMessageLoop(); - // Notify implementation about endpoint start - await this.OnStart(); - // Endpoint is now started this.currentState = ProtocolEndpointState.Started; } @@ -115,22 +105,18 @@ public void WaitForExit() this.endpointExitedTask.Task.Wait(); } - public async Task Stop() + public void Stop() { if (this.currentState == ProtocolEndpointState.Started) { // Make sure no future calls try to stop the endpoint during shutdown this.currentState = ProtocolEndpointState.Shutdown; - // Stop the implementation first - await this.OnStop(); - // Stop the message loop and channel this.StopMessageLoop(); this.protocolChannel.Stop(); // Notify anyone waiting for exit - this.OnSessionEnded(); if (this.endpointExitedTask != null) { this.endpointExitedTask.SetResult(true); @@ -261,18 +247,6 @@ await this.protocolChannel.MessageWriter.WriteEvent( #region Message Handling - public void SetRequestHandler( - RequestType0 requestType0, - Func, Task> requestHandler) - { - SetRequestHandler( - RequestType.ConvertToRequestType(requestType0), - (param1, requestContext) => - { - return requestHandler(requestContext); - }); - } - public void SetRequestHandler( RequestType requestType, Func, Task> requestHandler) @@ -340,29 +314,8 @@ private void StopMessageLoop() #endregion - #region Subclass Lifetime Methods - - protected virtual Task OnStart() - { - return Task.FromResult(true); - } - - protected virtual Task OnStop() - { - return Task.FromResult(true); - } - - #endregion - #region Events - public event EventHandler SessionEnded; - - protected virtual void OnSessionEnded() - { - this.SessionEnded?.Invoke(this, null); - } - public event EventHandler UnhandledException; protected void OnUnhandledException(Exception unhandledException) @@ -442,11 +395,9 @@ private async Task ListenForMessages(CancellationToken cancellationToken) } catch (Exception e) { - Logger.Write( - LogLevel.Verbose, - "Caught unexpected exception '{0}' in MessageDispatcher loop:\r\n{1}", - e.GetType().Name, - e.Message); + Logger.WriteException( + "Caught unhandled exception in ProtocolEndpoint message loop", + e); } // The message could be null if there was an error parsing the diff --git a/src/PowerShellEditorServices.Protocol/Server/DebugAdapter.cs b/src/PowerShellEditorServices.Protocol/Server/DebugAdapter.cs index 56200d1db..024b06dc2 100644 --- a/src/PowerShellEditorServices.Protocol/Server/DebugAdapter.cs +++ b/src/PowerShellEditorServices.Protocol/Server/DebugAdapter.cs @@ -20,12 +20,13 @@ namespace Microsoft.PowerShell.EditorServices.Protocol.Server { - public class DebugAdapter : DebugAdapterBase + public class DebugAdapter { private EditorSession editorSession; private OutputDebouncer outputDebouncer; private bool noDebug; + private ILogger Logger; private string arguments; private bool isRemoteAttach; private bool isAttachSession; @@ -34,47 +35,53 @@ public class DebugAdapter : DebugAdapterBase private bool enableConsoleRepl; private bool ownsEditorSession; private bool executionCompleted; + private IMessageSender messageSender; + private IMessageHandlers messageHandlers; private bool isInteractiveDebugSession; private RequestContext disconnectRequestContext = null; public DebugAdapter( EditorSession editorSession, - ChannelBase serverChannel, bool ownsEditorSession, + IMessageHandlers messageHandlers, + IMessageSender messageSender, ILogger logger) - : base(serverChannel, new MessageDispatcher(logger), logger) { + this.Logger = logger; this.editorSession = editorSession; + this.messageSender = messageSender; + this.messageHandlers = messageHandlers; this.ownsEditorSession = ownsEditorSession; this.enableConsoleRepl = editorSession.UsesConsoleHost; } - protected override void Initialize() + public void Start() { // Register all supported message types - - this.SetRequestHandler(LaunchRequest.Type, this.HandleLaunchRequest); - this.SetRequestHandler(AttachRequest.Type, this.HandleAttachRequest); - this.SetRequestHandler(ConfigurationDoneRequest.Type, this.HandleConfigurationDoneRequest); - this.SetRequestHandler(DisconnectRequest.Type, this.HandleDisconnectRequest); - - this.SetRequestHandler(SetBreakpointsRequest.Type, this.HandleSetBreakpointsRequest); - this.SetRequestHandler(SetExceptionBreakpointsRequest.Type, this.HandleSetExceptionBreakpointsRequest); - this.SetRequestHandler(SetFunctionBreakpointsRequest.Type, this.HandleSetFunctionBreakpointsRequest); - - this.SetRequestHandler(ContinueRequest.Type, this.HandleContinueRequest); - this.SetRequestHandler(NextRequest.Type, this.HandleNextRequest); - this.SetRequestHandler(StepInRequest.Type, this.HandleStepInRequest); - this.SetRequestHandler(StepOutRequest.Type, this.HandleStepOutRequest); - this.SetRequestHandler(PauseRequest.Type, this.HandlePauseRequest); - - this.SetRequestHandler(ThreadsRequest.Type, this.HandleThreadsRequest); - this.SetRequestHandler(StackTraceRequest.Type, this.HandleStackTraceRequest); - this.SetRequestHandler(ScopesRequest.Type, this.HandleScopesRequest); - this.SetRequestHandler(VariablesRequest.Type, this.HandleVariablesRequest); - this.SetRequestHandler(SetVariableRequest.Type, this.HandleSetVariablesRequest); - this.SetRequestHandler(SourceRequest.Type, this.HandleSourceRequest); - this.SetRequestHandler(EvaluateRequest.Type, this.HandleEvaluateRequest); + this.messageHandlers.SetRequestHandler(InitializeRequest.Type, this.HandleInitializeRequest); + + this.messageHandlers.SetRequestHandler(LaunchRequest.Type, this.HandleLaunchRequest); + this.messageHandlers.SetRequestHandler(AttachRequest.Type, this.HandleAttachRequest); + this.messageHandlers.SetRequestHandler(ConfigurationDoneRequest.Type, this.HandleConfigurationDoneRequest); + this.messageHandlers.SetRequestHandler(DisconnectRequest.Type, this.HandleDisconnectRequest); + + this.messageHandlers.SetRequestHandler(SetBreakpointsRequest.Type, this.HandleSetBreakpointsRequest); + this.messageHandlers.SetRequestHandler(SetExceptionBreakpointsRequest.Type, this.HandleSetExceptionBreakpointsRequest); + this.messageHandlers.SetRequestHandler(SetFunctionBreakpointsRequest.Type, this.HandleSetFunctionBreakpointsRequest); + + this.messageHandlers.SetRequestHandler(ContinueRequest.Type, this.HandleContinueRequest); + this.messageHandlers.SetRequestHandler(NextRequest.Type, this.HandleNextRequest); + this.messageHandlers.SetRequestHandler(StepInRequest.Type, this.HandleStepInRequest); + this.messageHandlers.SetRequestHandler(StepOutRequest.Type, this.HandleStepOutRequest); + this.messageHandlers.SetRequestHandler(PauseRequest.Type, this.HandlePauseRequest); + + this.messageHandlers.SetRequestHandler(ThreadsRequest.Type, this.HandleThreadsRequest); + this.messageHandlers.SetRequestHandler(StackTraceRequest.Type, this.HandleStackTraceRequest); + this.messageHandlers.SetRequestHandler(ScopesRequest.Type, this.HandleScopesRequest); + this.messageHandlers.SetRequestHandler(VariablesRequest.Type, this.HandleVariablesRequest); + this.messageHandlers.SetRequestHandler(SetVariableRequest.Type, this.HandleSetVariablesRequest); + this.messageHandlers.SetRequestHandler(SourceRequest.Type, this.HandleSourceRequest); + this.messageHandlers.SetRequestHandler(EvaluateRequest.Type, this.HandleEvaluateRequest); } protected Task LaunchScript(RequestContext requestContext) @@ -146,17 +153,17 @@ private async Task OnExecutionCompleted(Task executeTask) { // Respond to the disconnect request and stop the server await this.disconnectRequestContext.SendResult(null); - await this.Stop(); + this.Stop(); } else { - await this.SendEvent( + await this.messageSender.SendEvent( TerminatedEvent.Type, new TerminatedEvent()); } } - protected override void Shutdown() + protected void Stop() { Logger.Write(LogLevel.Normal, "Debug adapter is shutting down..."); @@ -179,10 +186,27 @@ protected override void Shutdown() this.editorSession = null; } + + this.OnSessionEnded(); } #region Built-in Message Handlers + private async Task HandleInitializeRequest( + object shutdownParams, + RequestContext requestContext) + { + // Now send the Initialize response to continue setup + await requestContext.SendResult( + new InitializeResponseBody { + SupportsConfigurationDoneRequest = true, + SupportsFunctionBreakpoints = true, + SupportsConditionalBreakpoints = true, + SupportsHitConditionalBreakpoints = true, + SupportsSetVariable = true + }); + } + protected async Task HandleConfigurationDoneRequest( object args, RequestContext requestContext) @@ -309,7 +333,7 @@ protected async Task HandleLaunchRequest( // Send the InitializedEvent so that the debugger will continue // sending configuration requests - await this.SendEvent( + await this.messageSender.SendEvent( InitializedEvent.Type, null); } @@ -449,7 +473,7 @@ protected async Task HandleDisconnectRequest( this.UnregisterEventHandlers(); await requestContext.SendResult(null); - await this.Stop(); + this.Stop(); } } } @@ -824,7 +848,7 @@ await requestContext.SendResult( private async Task WriteUseIntegratedConsoleMessage() { - await this.SendEvent( + await this.messageSender.SendEvent( OutputEvent.Type, new OutputEventBody { @@ -842,7 +866,7 @@ private void RegisterEventHandlers() if (!this.enableConsoleRepl) { this.editorSession.ConsoleService.OutputWritten += this.powerShellContext_OutputWritten; - this.outputDebouncer = new OutputDebouncer(this); + this.outputDebouncer = new OutputDebouncer(this.messageSender); } } @@ -888,7 +912,7 @@ e.OriginalEvent.Breakpoints[0] is CommandBreakpoint : "breakpoint"; } - await this.SendEvent( + await this.messageSender.SendEvent( StoppedEvent.Type, new StoppedEventBody { @@ -910,7 +934,7 @@ async void powerShellContext_RunspaceChanged(object sender, RunspaceChangedEvent // Send the InitializedEvent so that the debugger will continue // sending configuration requests this.waitingForAttach = false; - await this.SendEvent(InitializedEvent.Type, null); + await this.messageSender.SendEvent(InitializedEvent.Type, null); } else if ( e.ChangeAction == RunspaceChangeAction.Exit && @@ -920,7 +944,7 @@ async void powerShellContext_RunspaceChanged(object sender, RunspaceChangedEvent // Exited the session while the debugger is stopped, // send a ContinuedEvent so that the client changes the // UI to appear to be running again - await this.SendEvent( + await this.messageSender.SendEvent( ContinuedEvent.Type, new ContinuedEvent { @@ -932,7 +956,7 @@ await this.SendEvent( private async void powerShellContext_DebuggerResumed(object sender, DebuggerResumeAction e) { - await this.SendEvent( + await this.messageSender.SendEvent( ContinuedEvent.Type, new ContinuedEvent { @@ -942,5 +966,16 @@ await this.SendEvent( } #endregion + + #region Events + + public event EventHandler SessionEnded; + + protected virtual void OnSessionEnded() + { + this.SessionEnded?.Invoke(this, null); + } + + #endregion } } diff --git a/src/PowerShellEditorServices.Protocol/Server/DebugAdapterBase.cs b/src/PowerShellEditorServices.Protocol/Server/DebugAdapterBase.cs deleted file mode 100644 index b804ed0fe..000000000 --- a/src/PowerShellEditorServices.Protocol/Server/DebugAdapterBase.cs +++ /dev/null @@ -1,76 +0,0 @@ -// -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. -// - -using Microsoft.PowerShell.EditorServices.Protocol.DebugAdapter; -using Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol; -using Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol.Channel; -using System.Threading.Tasks; -using Microsoft.PowerShell.EditorServices.Utility; - -namespace Microsoft.PowerShell.EditorServices.Protocol.Server -{ - public abstract class DebugAdapterBase : ProtocolEndpoint - { - public DebugAdapterBase( - ChannelBase serverChannel, - MessageDispatcher messageDispatcher, - ILogger logger) - : base( - serverChannel, - messageDispatcher, - MessageProtocolType.DebugAdapter, - logger) - { - } - - /// - /// Overridden by the subclass to provide initialization - /// logic after the server channel is started. - /// - protected abstract void Initialize(); - - /// - /// Can be overridden by the subclass to provide shutdown - /// logic before the server exits. - /// - protected virtual void Shutdown() - { - // No default implementation yet. - } - - protected override Task OnStart() - { - // Register handlers for server lifetime messages - this.SetRequestHandler(InitializeRequest.Type, this.HandleInitializeRequest); - - // Initialize the implementation class - this.Initialize(); - - return Task.FromResult(true); - } - - protected override Task OnStop() - { - this.Shutdown(); - - return Task.FromResult(true); - } - - private async Task HandleInitializeRequest( - object shutdownParams, - RequestContext requestContext) - { - // Now send the Initialize response to continue setup - await requestContext.SendResult( - new InitializeResponseBody { - SupportsConfigurationDoneRequest = true, - SupportsFunctionBreakpoints = true, - SupportsConditionalBreakpoints = true, - SupportsHitConditionalBreakpoints = true, - SupportsSetVariable = true - }); - } - } -} diff --git a/src/PowerShellEditorServices.Protocol/Server/LanguageServer.cs b/src/PowerShellEditorServices.Protocol/Server/LanguageServer.cs index a528aad2f..f52b95661 100644 --- a/src/PowerShellEditorServices.Protocol/Server/LanguageServer.cs +++ b/src/PowerShellEditorServices.Protocol/Server/LanguageServer.cs @@ -25,13 +25,16 @@ namespace Microsoft.PowerShell.EditorServices.Protocol.Server { - public class LanguageServer : LanguageServerBase + public class LanguageServer { private static CancellationTokenSource existingRequestCancellation; + private ILogger Logger; private bool profilesLoaded; private bool consoleReplStarted; private EditorSession editorSession; + private IMessageSender messageSender; + private IMessageHandlers messageHandlers; private OutputDebouncer outputDebouncer; private LanguageServerEditorOperations editorOperations; private LanguageServerSettings currentSettings = new LanguageServerSettings(); @@ -49,10 +52,11 @@ public IEditorOperations EditorOperations /// public LanguageServer( EditorSession editorSession, - ChannelBase serverChannel, + IMessageHandlers messageHandlers, + IMessageSender messageSender, ILogger logger) - : base(serverChannel, new MessageDispatcher(logger), logger) { + this.Logger = logger; this.editorSession = editorSession; this.editorSession.PowerShellContext.RunspaceChanged += PowerShellContext_RunspaceChanged; @@ -61,11 +65,14 @@ public LanguageServer( this.editorSession.ExtensionService.CommandUpdated += ExtensionService_ExtensionUpdated; this.editorSession.ExtensionService.CommandRemoved += ExtensionService_ExtensionRemoved; + this.messageSender = messageSender; + this.messageHandlers = messageHandlers; + // Create the IEditorOperations implementation this.editorOperations = new LanguageServerEditorOperations( this.editorSession, - this); + this.messageSender); this.editorSession.StartDebugService(this.editorOperations); this.editorSession.DebugService.DebuggerStopped += DebugService_DebuggerStopped; @@ -78,67 +85,75 @@ public LanguageServer( // Always send console prompts through the UI in the language service this.editorSession.ConsoleService.PushPromptHandlerContext( new ProtocolPromptHandlerContext( - this, + this.messageSender, this.editorSession.ConsoleService)); } // Set up the output debouncer to throttle output event writes - this.outputDebouncer = new OutputDebouncer(this); + this.outputDebouncer = new OutputDebouncer(this.messageSender); } - protected override void Initialize() + /// + /// Starts the language server client and sends the Initialize method. + /// + /// A Task that can be awaited for initialization to complete. + public void Start() { // Register all supported message types - this.SetRequestHandler(InitializeRequest.Type, this.HandleInitializeRequest); + this.messageHandlers.SetRequestHandler(ShutdownRequest.Type, this.HandleShutdownRequest); + this.messageHandlers.SetEventHandler(ExitNotification.Type, this.HandleExitNotification); + + this.messageHandlers.SetRequestHandler(InitializeRequest.Type, this.HandleInitializeRequest); - this.SetEventHandler(DidOpenTextDocumentNotification.Type, this.HandleDidOpenTextDocumentNotification); - this.SetEventHandler(DidCloseTextDocumentNotification.Type, this.HandleDidCloseTextDocumentNotification); - this.SetEventHandler(DidSaveTextDocumentNotification.Type, this.HandleDidSaveTextDocumentNotification); - this.SetEventHandler(DidChangeTextDocumentNotification.Type, this.HandleDidChangeTextDocumentNotification); - this.SetEventHandler(DidChangeConfigurationNotification.Type, this.HandleDidChangeConfigurationNotification); + this.messageHandlers.SetEventHandler(DidOpenTextDocumentNotification.Type, this.HandleDidOpenTextDocumentNotification); + this.messageHandlers.SetEventHandler(DidCloseTextDocumentNotification.Type, this.HandleDidCloseTextDocumentNotification); + this.messageHandlers.SetEventHandler(DidSaveTextDocumentNotification.Type, this.HandleDidSaveTextDocumentNotification); + this.messageHandlers.SetEventHandler(DidChangeTextDocumentNotification.Type, this.HandleDidChangeTextDocumentNotification); + this.messageHandlers.SetEventHandler(DidChangeConfigurationNotification.Type, this.HandleDidChangeConfigurationNotification); - this.SetRequestHandler(DefinitionRequest.Type, this.HandleDefinitionRequest); - this.SetRequestHandler(ReferencesRequest.Type, this.HandleReferencesRequest); - this.SetRequestHandler(CompletionRequest.Type, this.HandleCompletionRequest); - this.SetRequestHandler(CompletionResolveRequest.Type, this.HandleCompletionResolveRequest); - this.SetRequestHandler(SignatureHelpRequest.Type, this.HandleSignatureHelpRequest); - this.SetRequestHandler(DocumentHighlightRequest.Type, this.HandleDocumentHighlightRequest); - this.SetRequestHandler(HoverRequest.Type, this.HandleHoverRequest); - this.SetRequestHandler(DocumentSymbolRequest.Type, this.HandleDocumentSymbolRequest); - this.SetRequestHandler(WorkspaceSymbolRequest.Type, this.HandleWorkspaceSymbolRequest); - this.SetRequestHandler(CodeActionRequest.Type, this.HandleCodeActionRequest); + this.messageHandlers.SetRequestHandler(DefinitionRequest.Type, this.HandleDefinitionRequest); + this.messageHandlers.SetRequestHandler(ReferencesRequest.Type, this.HandleReferencesRequest); + this.messageHandlers.SetRequestHandler(CompletionRequest.Type, this.HandleCompletionRequest); + this.messageHandlers.SetRequestHandler(CompletionResolveRequest.Type, this.HandleCompletionResolveRequest); + this.messageHandlers.SetRequestHandler(SignatureHelpRequest.Type, this.HandleSignatureHelpRequest); + this.messageHandlers.SetRequestHandler(DocumentHighlightRequest.Type, this.HandleDocumentHighlightRequest); + this.messageHandlers.SetRequestHandler(HoverRequest.Type, this.HandleHoverRequest); + this.messageHandlers.SetRequestHandler(DocumentSymbolRequest.Type, this.HandleDocumentSymbolRequest); + this.messageHandlers.SetRequestHandler(WorkspaceSymbolRequest.Type, this.HandleWorkspaceSymbolRequest); + this.messageHandlers.SetRequestHandler(CodeActionRequest.Type, this.HandleCodeActionRequest); - this.SetRequestHandler(ShowOnlineHelpRequest.Type, this.HandleShowOnlineHelpRequest); - this.SetRequestHandler(ExpandAliasRequest.Type, this.HandleExpandAliasRequest); + this.messageHandlers.SetRequestHandler(ShowOnlineHelpRequest.Type, this.HandleShowOnlineHelpRequest); + this.messageHandlers.SetRequestHandler(ExpandAliasRequest.Type, this.HandleExpandAliasRequest); - this.SetRequestHandler(FindModuleRequest.Type, this.HandleFindModuleRequest); - this.SetRequestHandler(InstallModuleRequest.Type, this.HandleInstallModuleRequest); + this.messageHandlers.SetRequestHandler(FindModuleRequest.Type, this.HandleFindModuleRequest); + this.messageHandlers.SetRequestHandler(InstallModuleRequest.Type, this.HandleInstallModuleRequest); - this.SetRequestHandler(InvokeExtensionCommandRequest.Type, this.HandleInvokeExtensionCommandRequest); + this.messageHandlers.SetRequestHandler(InvokeExtensionCommandRequest.Type, this.HandleInvokeExtensionCommandRequest); - this.SetRequestHandler(PowerShellVersionRequest.Type, this.HandlePowerShellVersionRequest); + this.messageHandlers.SetRequestHandler(PowerShellVersionRequest.Type, this.HandlePowerShellVersionRequest); - this.SetRequestHandler(NewProjectFromTemplateRequest.Type, this.HandleNewProjectFromTemplateRequest); - this.SetRequestHandler(GetProjectTemplatesRequest.Type, this.HandleGetProjectTemplatesRequest); + this.messageHandlers.SetRequestHandler(NewProjectFromTemplateRequest.Type, this.HandleNewProjectFromTemplateRequest); + this.messageHandlers.SetRequestHandler(GetProjectTemplatesRequest.Type, this.HandleGetProjectTemplatesRequest); - this.SetRequestHandler(DebugAdapterMessages.EvaluateRequest.Type, this.HandleEvaluateRequest); + this.messageHandlers.SetRequestHandler(DebugAdapterMessages.EvaluateRequest.Type, this.HandleEvaluateRequest); - this.SetRequestHandler(GetPSSARulesRequest.Type, this.HandleGetPSSARulesRequest); - this.SetRequestHandler(SetPSSARulesRequest.Type, this.HandleSetPSSARulesRequest); + this.messageHandlers.SetRequestHandler(GetPSSARulesRequest.Type, this.HandleGetPSSARulesRequest); + this.messageHandlers.SetRequestHandler(SetPSSARulesRequest.Type, this.HandleSetPSSARulesRequest); - this.SetRequestHandler(ScriptFileMarkersRequest.Type, this.HandleScriptFileMarkersRequest); - this.SetRequestHandler(ScriptRegionRequest.Type, this.HandleGetFormatScriptRegionRequest); + this.messageHandlers.SetRequestHandler(ScriptFileMarkersRequest.Type, this.HandleScriptFileMarkersRequest); + this.messageHandlers.SetRequestHandler(ScriptRegionRequest.Type, this.HandleGetFormatScriptRegionRequest); + + this.messageHandlers.SetRequestHandler(GetPSHostProcessesRequest.Type, this.HandleGetPSHostProcessesRequest); + this.messageHandlers.SetRequestHandler(CommentHelpRequest.Type, this.HandleCommentHelpRequest); - this.SetRequestHandler(GetPSHostProcessesRequest.Type, this.HandleGetPSHostProcessesRequest); - this.SetRequestHandler(CommentHelpRequest.Type, this.HandleCommentHelpRequest); // Initialize the extension service // TODO: This should be made awaited once Initialize is async! this.editorSession.ExtensionService.Initialize( this.editorOperations).Wait(); } - protected override async Task Shutdown() + protected async Task Stop() { // Stop the interactive terminal // TODO: This can happen at the host level @@ -149,15 +164,28 @@ protected override async Task Shutdown() Logger.Write(LogLevel.Normal, "Language service is shutting down..."); - if (this.editorSession != null) - { - this.editorSession.Dispose(); - this.editorSession = null; - } + // TODO: Raise an event so that the host knows to shut down } #region Built-in Message Handlers + private async Task HandleShutdownRequest( + RequestContext requestContext) + { + // Allow the implementor to shut down gracefully + await this.Stop(); + + await requestContext.SendResult(new object()); + } + + private async Task HandleExitNotification( + object exitParams, + EventContext eventContext) + { + // Stop the server channel + await this.Stop(); + } + protected async Task HandleInitializeRequest( InitializeParams initializeParams, RequestContext requestContext) @@ -239,7 +267,7 @@ private async Task HandleSetPSSARulesRequest( await RunScriptDiagnostics( new ScriptFile[] { scripFile }, editorSession, - this.SendEvent); + this.messageSender.SendEvent); await sendresult; } @@ -1193,7 +1221,7 @@ protected Task HandleEvaluateRequest( private async void PowerShellContext_RunspaceChanged(object sender, Session.RunspaceChangedEventArgs e) { - await this.SendEvent( + await this.messageSender.SendEvent( RunspaceChangedEvent.Type, new Protocol.LanguageServer.RunspaceDetails(e.NewRunspace)); } @@ -1206,7 +1234,7 @@ private async void powerShellContext_OutputWritten(object sender, OutputWrittenE private async void ExtensionService_ExtensionAdded(object sender, EditorCommand e) { - await this.SendEvent( + await this.messageSender.SendEvent( ExtensionCommandAddedNotification.Type, new ExtensionCommandAddedNotification { @@ -1217,7 +1245,7 @@ await this.SendEvent( private async void ExtensionService_ExtensionUpdated(object sender, EditorCommand e) { - await this.SendEvent( + await this.messageSender.SendEvent( ExtensionCommandUpdatedNotification.Type, new ExtensionCommandUpdatedNotification { @@ -1227,7 +1255,7 @@ await this.SendEvent( private async void ExtensionService_ExtensionRemoved(object sender, EditorCommand e) { - await this.SendEvent( + await this.messageSender.SendEvent( ExtensionCommandRemovedNotification.Type, new ExtensionCommandRemovedNotification { @@ -1239,7 +1267,7 @@ private async void DebugService_DebuggerStopped(object sender, DebuggerStoppedEv { if (!this.editorSession.DebugService.IsClientAttached) { - await this.SendEvent( + await this.messageSender.SendEvent( StartDebuggerEvent.Type, new StartDebuggerEvent()); } @@ -1294,7 +1322,7 @@ private Task RunScriptDiagnostics( EditorSession editorSession, EventContext eventContext) { - return RunScriptDiagnostics(filesToAnalyze, editorSession, this.SendEvent); + return RunScriptDiagnostics(filesToAnalyze, editorSession, this.messageSender.SendEvent); } private Task RunScriptDiagnostics( diff --git a/src/PowerShellEditorServices.Protocol/Server/LanguageServerBase.cs b/src/PowerShellEditorServices.Protocol/Server/LanguageServerBase.cs deleted file mode 100644 index 869895e43..000000000 --- a/src/PowerShellEditorServices.Protocol/Server/LanguageServerBase.cs +++ /dev/null @@ -1,83 +0,0 @@ -// -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. -// - -using Microsoft.PowerShell.EditorServices.Protocol.LanguageServer; -using Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol; -using Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol.Channel; -using Microsoft.PowerShell.EditorServices.Utility; -using System.Threading.Tasks; - -namespace Microsoft.PowerShell.EditorServices.Protocol.Server -{ - public abstract class LanguageServerBase : ProtocolEndpoint - { - private ChannelBase serverChannel; - - public LanguageServerBase( - ChannelBase serverChannel, - MessageDispatcher messageDispatcher, - ILogger logger) - : base( - serverChannel, - messageDispatcher, - MessageProtocolType.LanguageServer, - logger) - { - this.serverChannel = serverChannel; - } - - protected override Task OnStart() - { - // Register handlers for server lifetime messages - this.SetRequestHandler(ShutdownRequest.Type, this.HandleShutdownRequest); - this.SetEventHandler(ExitNotification.Type, this.HandleExitNotification); - - // Initialize the implementation class - this.Initialize(); - - return Task.FromResult(true); - } - - protected override async Task OnStop() - { - await this.Shutdown(); - } - - /// - /// Overridden by the subclass to provide initialization - /// logic after the server channel is started. - /// - protected abstract void Initialize(); - - /// - /// Can be overridden by the subclass to provide shutdown - /// logic before the server exits. Subclasses do not need - /// to invoke or return the value of the base implementation. - /// - protected virtual Task Shutdown() - { - // No default implementation yet. - return Task.FromResult(true); - } - - private async Task HandleShutdownRequest( - RequestContext requestContext) - { - // Allow the implementor to shut down gracefully - await this.Shutdown(); - - await requestContext.SendResult(new object()); - } - - private async Task HandleExitNotification( - object exitParams, - EventContext eventContext) - { - // Stop the server channel - await this.Stop(); - } - } -} - diff --git a/test/PowerShellEditorServices.Test.Host/DebugAdapterTests.cs b/test/PowerShellEditorServices.Test.Host/DebugAdapterTests.cs index 071a776d0..b6909a036 100644 --- a/test/PowerShellEditorServices.Test.Host/DebugAdapterTests.cs +++ b/test/PowerShellEditorServices.Test.Host/DebugAdapterTests.cs @@ -51,22 +51,26 @@ await this.LaunchService( waitForDebugger: false); //waitForDebugger: true); - this.protocolClient = - this.debugAdapterClient = - new DebugAdapterClient( - await TcpSocketClientChannel.Connect( - portNumbers.Item2, - MessageProtocolType.DebugAdapter, - this.logger), - this.logger); + this.debugAdapterClient = + new DebugAdapterClient( + await TcpSocketClientChannel.Connect( + portNumbers.Item2, + MessageProtocolType.DebugAdapter, + this.logger), + this.logger); + + this.messageSender = this.debugAdapterClient; + this.messageHandlers = this.debugAdapterClient; await this.debugAdapterClient.Start(); } - public async Task DisposeAsync() + public Task DisposeAsync() { - await this.debugAdapterClient.Stop(); + this.debugAdapterClient.Stop(); this.KillService(); + + return Task.FromResult(true); } [Fact] diff --git a/test/PowerShellEditorServices.Test.Host/LanguageServerTests.cs b/test/PowerShellEditorServices.Test.Host/LanguageServerTests.cs index ee32eb63f..abe684242 100644 --- a/test/PowerShellEditorServices.Test.Host/LanguageServerTests.cs +++ b/test/PowerShellEditorServices.Test.Host/LanguageServerTests.cs @@ -54,14 +54,16 @@ await this.LaunchService( waitForDebugger: false); //waitForDebugger: true); - this.protocolClient = - this.languageServiceClient = - new LanguageServiceClient( - await TcpSocketClientChannel.Connect( - portNumbers.Item1, - MessageProtocolType.LanguageServer, - this.logger), - this.logger); + this.languageServiceClient = + new LanguageServiceClient( + await TcpSocketClientChannel.Connect( + portNumbers.Item1, + MessageProtocolType.LanguageServer, + this.logger), + this.logger); + + this.messageSender = this.languageServiceClient; + this.messageHandlers = this.languageServiceClient; await this.languageServiceClient.Start(); } @@ -580,7 +582,7 @@ await this.SendRequest( [Fact] public async Task ServiceExecutesReplCommandAndReceivesOutput() { - OutputReader outputReader = new OutputReader(this.protocolClient); + OutputReader outputReader = new OutputReader(this.messageHandlers); await this.SendRequest( @@ -609,7 +611,7 @@ await this.SendRequest( [Fact] public async Task ServiceExecutesReplCommandAndReceivesChoicePrompt() { - OutputReader outputReader = new OutputReader(this.protocolClient); + OutputReader outputReader = new OutputReader(this.messageHandlers); string choiceScript = @" @@ -659,7 +661,7 @@ await requestContext.SendResult( [Fact] public async Task ServiceExecutesReplCommandAndReceivesInputPrompt() { - OutputReader outputReader = new OutputReader(this.protocolClient); + OutputReader outputReader = new OutputReader(this.messageHandlers); string promptScript = @" @@ -716,7 +718,7 @@ await requestContext.SendResult( [Fact(Skip = "Native command output in the legacy host has been disabled for now, may re-enable later")] public async Task ServiceExecutesNativeCommandAndReceivesCommand() { - OutputReader outputReader = new OutputReader(this.protocolClient); + OutputReader outputReader = new OutputReader(this.messageHandlers); // Execute the script but don't await the task yet because // the choice prompt will block execution from completing @@ -788,7 +790,7 @@ await this.languageServiceClient.SendEvent( } }); - OutputReader outputReader = new OutputReader(this.protocolClient); + OutputReader outputReader = new OutputReader(this.messageHandlers); Task evaluateTask = this.SendRequest( diff --git a/test/PowerShellEditorServices.Test.Host/OutputReader.cs b/test/PowerShellEditorServices.Test.Host/OutputReader.cs index 47caac118..989d193e5 100644 --- a/test/PowerShellEditorServices.Test.Host/OutputReader.cs +++ b/test/PowerShellEditorServices.Test.Host/OutputReader.cs @@ -23,9 +23,9 @@ internal class OutputReader private string currentOutputCategory; private Queue> bufferedOutput = new Queue>(); - public OutputReader(ProtocolEndpoint protocolClient) + public OutputReader(IMessageHandlers messageHandlers) { - protocolClient.SetEventHandler( + messageHandlers.SetEventHandler( OutputEvent.Type, this.OnOutputEvent); } @@ -66,7 +66,7 @@ public async Task ReadLine(string expectedOutputCategory = "stdout") this.currentOutputCategory = nextOutputEvent.Category; // Split up the output into multiple lines - outputLines = + outputLines = nextOutputEvent.Output.Split( new string[] { "\n", "\r\n" }, StringSplitOptions.None); diff --git a/test/PowerShellEditorServices.Test.Host/ServerTestsBase.cs b/test/PowerShellEditorServices.Test.Host/ServerTestsBase.cs index db6a7c56a..253f2da11 100644 --- a/test/PowerShellEditorServices.Test.Host/ServerTestsBase.cs +++ b/test/PowerShellEditorServices.Test.Host/ServerTestsBase.cs @@ -18,7 +18,8 @@ namespace Microsoft.PowerShell.EditorServices.Test.Host public class ServerTestsBase { private Process serviceProcess; - protected ProtocolEndpoint protocolClient; + protected IMessageSender messageSender; + protected IMessageHandlers messageHandlers; private ConcurrentDictionary> eventQueuePerType = new ConcurrentDictionary>(); @@ -136,15 +137,16 @@ protected Task SendRequest(NotificationType eventType, TParams eventParams) { return - this.protocolClient.SendEvent( + this.messageSender.SendEvent( eventType, eventParams); } @@ -157,7 +159,7 @@ protected void QueueEventsForType(NotificationTyp new AsyncQueue(), (key, queue) => queue); - this.protocolClient.SetEventHandler( + this.messageHandlers.SetEventHandler( eventType, (p, ctx) => { @@ -185,7 +187,7 @@ protected async Task WaitForEvent( { TaskCompletionSource eventTaskSource = new TaskCompletionSource(); - this.protocolClient.SetEventHandler( + this.messageHandlers.SetEventHandler( eventType, (p, ctx) => { @@ -195,8 +197,7 @@ protected async Task WaitForEvent( } return Task.FromResult(true); - }, - true); // Override any existing handler + }); eventTask = eventTaskSource.Task; } @@ -238,7 +239,7 @@ protected async Task>> WaitForRequest>>(); - this.protocolClient.SetRequestHandler( + this.messageHandlers.SetRequestHandler( requestType, (p, ctx) => { diff --git a/test/PowerShellEditorServices.Test.Protocol/Server/OutputDebouncerTests.cs b/test/PowerShellEditorServices.Test.Protocol/Server/OutputDebouncerTests.cs index bd15d1a75..50aad110e 100644 --- a/test/PowerShellEditorServices.Test.Protocol/Server/OutputDebouncerTests.cs +++ b/test/PowerShellEditorServices.Test.Protocol/Server/OutputDebouncerTests.cs @@ -120,6 +120,12 @@ public Task SendRequest // Legitimately not implemented for these tests. throw new NotImplementedException(); } + + public Task SendRequest(RequestType0 requestType0) + { + // Legitimately not implemented for these tests. + throw new NotImplementedException(); + } } }