mirror of
https://github.com/ckaczor/sqltoolsservice.git
synced 2026-02-16 10:58:30 -05:00
Initial commit of SqlTools Service API
This commit is contained in:
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
bin
|
||||||
|
obj
|
||||||
|
project.lock.json
|
||||||
@@ -1,2 +1,2 @@
|
|||||||
# sqltoolsservice
|
# sqltoolsservice
|
||||||
SQL Tools API service repo.
|
SQL Tools Service host
|
||||||
|
|||||||
22
ServiceHost/.vscode/launch.json
vendored
Normal file
22
ServiceHost/.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"name": ".NET Core Launch (console)",
|
||||||
|
"type": "coreclr",
|
||||||
|
"request": "launch",
|
||||||
|
"preLaunchTask": "build",
|
||||||
|
"program": "${workspaceRoot}/bin/Debug/netcoreapp1.0/servicehost.dll",
|
||||||
|
"args": [],
|
||||||
|
"cwd": "${workspaceRoot}",
|
||||||
|
"externalConsole": true,
|
||||||
|
"stopAtEntry": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": ".NET Core Attach",
|
||||||
|
"type": "coreclr",
|
||||||
|
"request": "attach",
|
||||||
|
"processId": 16900
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
14
ServiceHost/.vscode/tasks.json
vendored
Normal file
14
ServiceHost/.vscode/tasks.json
vendored
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"version": "0.1.0",
|
||||||
|
"command": "dotnet",
|
||||||
|
"isShellCommand": true,
|
||||||
|
"args": [],
|
||||||
|
"tasks": [
|
||||||
|
{
|
||||||
|
"taskName": "build",
|
||||||
|
"args": [],
|
||||||
|
"isBuildCommand": true,
|
||||||
|
"problemMatcher": "$msCompile"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
48
ServiceHost/Client/DebugAdapterClientBase.cs
Normal file
48
ServiceHost/Client/DebugAdapterClientBase.cs
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
//
|
||||||
|
// 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;
|
||||||
|
|
||||||
|
namespace Microsoft.PowerShell.EditorServices.Protocol.Client
|
||||||
|
{
|
||||||
|
public class DebugAdapterClient : ProtocolEndpoint
|
||||||
|
{
|
||||||
|
public DebugAdapterClient(ChannelBase clientChannel)
|
||||||
|
: base(clientChannel, MessageProtocolType.DebugAdapter)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task LaunchScript(string scriptFilePath)
|
||||||
|
{
|
||||||
|
await this.SendRequest(
|
||||||
|
LaunchRequest.Type,
|
||||||
|
new LaunchRequestArguments {
|
||||||
|
Program = scriptFilePath
|
||||||
|
});
|
||||||
|
|
||||||
|
await this.SendRequest(ConfigurationDoneRequest.Type, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override Task OnStart()
|
||||||
|
{
|
||||||
|
return Task.FromResult(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override Task OnConnect()
|
||||||
|
{
|
||||||
|
// Initialize the debug adapter
|
||||||
|
return this.SendRequest(
|
||||||
|
InitializeRequest.Type,
|
||||||
|
new InitializeRequestArguments
|
||||||
|
{
|
||||||
|
LinesStartAt1 = true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
44
ServiceHost/Client/LanguageClientBase.cs
Normal file
44
ServiceHost/Client/LanguageClientBase.cs
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
//
|
||||||
|
// 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 System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Microsoft.PowerShell.EditorServices.Protocol.Client
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Provides a base implementation for language server clients.
|
||||||
|
/// </summary>
|
||||||
|
public abstract class LanguageClientBase : ProtocolEndpoint
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes an instance of the language client using the
|
||||||
|
/// specified channel for communication.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="clientChannel">The channel to use for communication with the server.</param>
|
||||||
|
public LanguageClientBase(ChannelBase clientChannel)
|
||||||
|
: base(clientChannel, MessageProtocolType.LanguageServer)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override Task OnStart()
|
||||||
|
{
|
||||||
|
// Initialize the implementation class
|
||||||
|
return this.Initialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override async Task OnStop()
|
||||||
|
{
|
||||||
|
// First, notify the language server that we're stopping
|
||||||
|
var response = await this.SendRequest(ShutdownRequest.Type, new object());
|
||||||
|
await this.SendEvent(ExitNotification.Type, new object());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract Task Initialize();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
121
ServiceHost/Client/LanguageServiceClient.cs
Normal file
121
ServiceHost/Client/LanguageServiceClient.cs
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
//
|
||||||
|
// 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 System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Microsoft.PowerShell.EditorServices.Protocol.Client
|
||||||
|
{
|
||||||
|
public class LanguageServiceClient : LanguageClientBase
|
||||||
|
{
|
||||||
|
private Dictionary<string, ScriptFileMarker[]> cachedDiagnostics =
|
||||||
|
new Dictionary<string, ScriptFileMarker[]>();
|
||||||
|
|
||||||
|
public LanguageServiceClient(ChannelBase clientChannel)
|
||||||
|
: base(clientChannel)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override Task Initialize()
|
||||||
|
{
|
||||||
|
// Add handlers for common events
|
||||||
|
this.SetEventHandler(PublishDiagnosticsNotification.Type, HandlePublishDiagnosticsEvent);
|
||||||
|
|
||||||
|
return Task.FromResult(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override Task OnConnect()
|
||||||
|
{
|
||||||
|
// Send the 'initialize' request and wait for the response
|
||||||
|
var initializeRequest = new InitializeRequest
|
||||||
|
{
|
||||||
|
RootPath = "",
|
||||||
|
Capabilities = new ClientCapabilities()
|
||||||
|
};
|
||||||
|
|
||||||
|
return this.SendRequest(
|
||||||
|
InitializeRequest.Type,
|
||||||
|
initializeRequest);
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Events
|
||||||
|
|
||||||
|
public event EventHandler<string> DiagnosticsReceived;
|
||||||
|
|
||||||
|
protected void OnDiagnosticsReceived(string filePath)
|
||||||
|
{
|
||||||
|
if (this.DiagnosticsReceived != null)
|
||||||
|
{
|
||||||
|
this.DiagnosticsReceived(this, filePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Private Methods
|
||||||
|
|
||||||
|
private Task HandlePublishDiagnosticsEvent(
|
||||||
|
PublishDiagnosticsNotification diagnostics,
|
||||||
|
EventContext eventContext)
|
||||||
|
{
|
||||||
|
string normalizedPath = diagnostics.Uri.ToLower();
|
||||||
|
|
||||||
|
this.cachedDiagnostics[normalizedPath] =
|
||||||
|
diagnostics.Diagnostics
|
||||||
|
.Select(GetMarkerFromDiagnostic)
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
this.OnDiagnosticsReceived(normalizedPath);
|
||||||
|
|
||||||
|
return Task.FromResult(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ScriptFileMarker GetMarkerFromDiagnostic(Diagnostic diagnostic)
|
||||||
|
{
|
||||||
|
DiagnosticSeverity severity =
|
||||||
|
diagnostic.Severity.GetValueOrDefault(
|
||||||
|
DiagnosticSeverity.Error);
|
||||||
|
|
||||||
|
return new ScriptFileMarker
|
||||||
|
{
|
||||||
|
Level = MapDiagnosticSeverityToLevel(severity),
|
||||||
|
Message = diagnostic.Message,
|
||||||
|
ScriptRegion = new ScriptRegion
|
||||||
|
{
|
||||||
|
StartLineNumber = diagnostic.Range.Start.Line + 1,
|
||||||
|
StartColumnNumber = diagnostic.Range.Start.Character + 1,
|
||||||
|
EndLineNumber = diagnostic.Range.End.Line + 1,
|
||||||
|
EndColumnNumber = diagnostic.Range.End.Character + 1
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ScriptFileMarkerLevel MapDiagnosticSeverityToLevel(DiagnosticSeverity severity)
|
||||||
|
{
|
||||||
|
switch (severity)
|
||||||
|
{
|
||||||
|
case DiagnosticSeverity.Hint:
|
||||||
|
case DiagnosticSeverity.Information:
|
||||||
|
return ScriptFileMarkerLevel.Information;
|
||||||
|
|
||||||
|
case DiagnosticSeverity.Warning:
|
||||||
|
return ScriptFileMarkerLevel.Warning;
|
||||||
|
|
||||||
|
case DiagnosticSeverity.Error:
|
||||||
|
return ScriptFileMarkerLevel.Error;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return ScriptFileMarkerLevel.Error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
23
ServiceHost/DebugAdapter/AttachRequest.cs
Normal file
23
ServiceHost/DebugAdapter/AttachRequest.cs
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
//
|
||||||
|
// 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.MessageProtocol;
|
||||||
|
|
||||||
|
namespace Microsoft.PowerShell.EditorServices.Protocol.DebugAdapter
|
||||||
|
{
|
||||||
|
public class AttachRequest
|
||||||
|
{
|
||||||
|
public static readonly
|
||||||
|
RequestType<AttachRequestArguments, object> Type =
|
||||||
|
RequestType<AttachRequestArguments, object>.Create("attach");
|
||||||
|
}
|
||||||
|
|
||||||
|
public class AttachRequestArguments
|
||||||
|
{
|
||||||
|
public string Address { get; set; }
|
||||||
|
|
||||||
|
public int Port { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
81
ServiceHost/DebugAdapter/Breakpoint.cs
Normal file
81
ServiceHost/DebugAdapter/Breakpoint.cs
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
//
|
||||||
|
// 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.Utility;
|
||||||
|
|
||||||
|
namespace Microsoft.PowerShell.EditorServices.Protocol.DebugAdapter
|
||||||
|
{
|
||||||
|
public class Breakpoint
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets an boolean indicator that if true, breakpoint could be set
|
||||||
|
/// (but not necessarily at the desired location).
|
||||||
|
/// </summary>
|
||||||
|
public bool Verified { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets an optional message about the state of the breakpoint. This is shown to the user
|
||||||
|
/// and can be used to explain why a breakpoint could not be verified.
|
||||||
|
/// </summary>
|
||||||
|
public string Message { get; set; }
|
||||||
|
|
||||||
|
public string Source { get; set; }
|
||||||
|
|
||||||
|
public int? Line { get; set; }
|
||||||
|
|
||||||
|
public int? Column { get; set; }
|
||||||
|
|
||||||
|
private Breakpoint()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
#if false
|
||||||
|
public static Breakpoint Create(
|
||||||
|
BreakpointDetails breakpointDetails)
|
||||||
|
{
|
||||||
|
//Validate.IsNotNull(nameof(breakpointDetails), breakpointDetails);
|
||||||
|
|
||||||
|
return new Breakpoint
|
||||||
|
{
|
||||||
|
Verified = breakpointDetails.Verified,
|
||||||
|
Message = breakpointDetails.Message,
|
||||||
|
Source = breakpointDetails.Source,
|
||||||
|
Line = breakpointDetails.LineNumber,
|
||||||
|
Column = breakpointDetails.ColumnNumber
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Breakpoint Create(
|
||||||
|
CommandBreakpointDetails breakpointDetails)
|
||||||
|
{
|
||||||
|
//Validate.IsNotNull(nameof(breakpointDetails), breakpointDetails);
|
||||||
|
|
||||||
|
return new Breakpoint {
|
||||||
|
Verified = breakpointDetails.Verified,
|
||||||
|
Message = breakpointDetails.Message
|
||||||
|
};
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
public static Breakpoint Create(
|
||||||
|
SourceBreakpoint sourceBreakpoint,
|
||||||
|
string source,
|
||||||
|
string message,
|
||||||
|
bool verified = false)
|
||||||
|
{
|
||||||
|
Validate.IsNotNull(nameof(sourceBreakpoint), sourceBreakpoint);
|
||||||
|
Validate.IsNotNull(nameof(source), source);
|
||||||
|
Validate.IsNotNull(nameof(message), message);
|
||||||
|
|
||||||
|
return new Breakpoint {
|
||||||
|
Verified = verified,
|
||||||
|
Message = message,
|
||||||
|
Source = source,
|
||||||
|
Line = sourceBreakpoint.Line,
|
||||||
|
Column = sourceBreakpoint.Column
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
16
ServiceHost/DebugAdapter/ConfigurationDoneRequest.cs
Normal file
16
ServiceHost/DebugAdapter/ConfigurationDoneRequest.cs
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
//
|
||||||
|
// 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.MessageProtocol;
|
||||||
|
|
||||||
|
namespace Microsoft.PowerShell.EditorServices.Protocol.DebugAdapter
|
||||||
|
{
|
||||||
|
public class ConfigurationDoneRequest
|
||||||
|
{
|
||||||
|
public static readonly
|
||||||
|
RequestType<object, object> Type =
|
||||||
|
RequestType<object, object>.Create("configurationDone");
|
||||||
|
}
|
||||||
|
}
|
||||||
17
ServiceHost/DebugAdapter/ContinueRequest.cs
Normal file
17
ServiceHost/DebugAdapter/ContinueRequest.cs
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
//
|
||||||
|
// 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.MessageProtocol;
|
||||||
|
|
||||||
|
namespace Microsoft.PowerShell.EditorServices.Protocol.DebugAdapter
|
||||||
|
{
|
||||||
|
public class ContinueRequest
|
||||||
|
{
|
||||||
|
public static readonly
|
||||||
|
RequestType<object, object> Type =
|
||||||
|
RequestType<object, object>.Create("continue");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
17
ServiceHost/DebugAdapter/DisconnectRequest.cs
Normal file
17
ServiceHost/DebugAdapter/DisconnectRequest.cs
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
//
|
||||||
|
// 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.MessageProtocol;
|
||||||
|
|
||||||
|
namespace Microsoft.PowerShell.EditorServices.Protocol.DebugAdapter
|
||||||
|
{
|
||||||
|
public class DisconnectRequest
|
||||||
|
{
|
||||||
|
public static readonly
|
||||||
|
RequestType<object, object> Type =
|
||||||
|
RequestType<object, object>.Create("disconnect");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
53
ServiceHost/DebugAdapter/EvaluateRequest.cs
Normal file
53
ServiceHost/DebugAdapter/EvaluateRequest.cs
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
//
|
||||||
|
// 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.MessageProtocol;
|
||||||
|
|
||||||
|
namespace Microsoft.PowerShell.EditorServices.Protocol.DebugAdapter
|
||||||
|
{
|
||||||
|
public class EvaluateRequest
|
||||||
|
{
|
||||||
|
public static readonly
|
||||||
|
RequestType<EvaluateRequestArguments, EvaluateResponseBody> Type =
|
||||||
|
RequestType<EvaluateRequestArguments, EvaluateResponseBody>.Create("evaluate");
|
||||||
|
}
|
||||||
|
|
||||||
|
public class EvaluateRequestArguments
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The expression to evaluate.
|
||||||
|
/// </summary>
|
||||||
|
public string Expression { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The context in which the evaluate request is run. Possible
|
||||||
|
/// values are 'watch' if evaluate is run in a watch or 'repl'
|
||||||
|
/// if run from the REPL console.
|
||||||
|
/// </summary>
|
||||||
|
public string Context { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Evaluate the expression in the context of this stack frame.
|
||||||
|
/// If not specified, the top most frame is used.
|
||||||
|
/// </summary>
|
||||||
|
public int FrameId { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class EvaluateResponseBody
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The evaluation result.
|
||||||
|
/// </summary>
|
||||||
|
public string Result { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// If variablesReference is > 0, the evaluate result is
|
||||||
|
/// structured and its children can be retrieved by passing
|
||||||
|
/// variablesReference to the VariablesRequest
|
||||||
|
/// </summary>
|
||||||
|
public int VariablesReference { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
22
ServiceHost/DebugAdapter/ExitedEvent.cs
Normal file
22
ServiceHost/DebugAdapter/ExitedEvent.cs
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
//
|
||||||
|
// 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.MessageProtocol;
|
||||||
|
|
||||||
|
namespace Microsoft.PowerShell.EditorServices.Protocol.DebugAdapter
|
||||||
|
{
|
||||||
|
public class ExitedEvent
|
||||||
|
{
|
||||||
|
public static readonly
|
||||||
|
EventType<ExitedEventBody> Type =
|
||||||
|
EventType<ExitedEventBody>.Create("exited");
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ExitedEventBody
|
||||||
|
{
|
||||||
|
public int ExitCode { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
56
ServiceHost/DebugAdapter/InitializeRequest.cs
Normal file
56
ServiceHost/DebugAdapter/InitializeRequest.cs
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
//
|
||||||
|
// 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.MessageProtocol;
|
||||||
|
|
||||||
|
namespace Microsoft.PowerShell.EditorServices.Protocol.DebugAdapter
|
||||||
|
{
|
||||||
|
public class InitializeRequest
|
||||||
|
{
|
||||||
|
public static readonly
|
||||||
|
RequestType<InitializeRequestArguments, InitializeResponseBody> Type =
|
||||||
|
RequestType<InitializeRequestArguments, InitializeResponseBody>.Create("initialize");
|
||||||
|
}
|
||||||
|
|
||||||
|
public class InitializeRequestArguments
|
||||||
|
{
|
||||||
|
public string AdapterId { get; set; }
|
||||||
|
|
||||||
|
public bool LinesStartAt1 { get; set; }
|
||||||
|
|
||||||
|
public string PathFormat { get; set; }
|
||||||
|
|
||||||
|
public bool SourceMaps { get; set; }
|
||||||
|
|
||||||
|
public string GeneratedCodeDirectory { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class InitializeResponseBody
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a boolean value that determines whether the debug adapter
|
||||||
|
/// supports the configurationDoneRequest.
|
||||||
|
/// </summary>
|
||||||
|
public bool SupportsConfigurationDoneRequest { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a boolean value that determines whether the debug adapter
|
||||||
|
/// supports functionBreakpoints.
|
||||||
|
/// </summary>
|
||||||
|
public bool SupportsFunctionBreakpoints { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a boolean value that determines whether the debug adapter
|
||||||
|
/// supports conditionalBreakpoints.
|
||||||
|
/// </summary>
|
||||||
|
public bool SupportsConditionalBreakpoints { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a boolean value that determines whether the debug adapter
|
||||||
|
/// supports a (side effect free) evaluate request for data hovers.
|
||||||
|
/// </summary>
|
||||||
|
public bool SupportsEvaluateForHovers { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
16
ServiceHost/DebugAdapter/InitializedEvent.cs
Normal file
16
ServiceHost/DebugAdapter/InitializedEvent.cs
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
//
|
||||||
|
// 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.MessageProtocol;
|
||||||
|
|
||||||
|
namespace Microsoft.PowerShell.EditorServices.Protocol.DebugAdapter
|
||||||
|
{
|
||||||
|
public class InitializedEvent
|
||||||
|
{
|
||||||
|
public static readonly
|
||||||
|
EventType<object> Type =
|
||||||
|
EventType<object>.Create("initialized");
|
||||||
|
}
|
||||||
|
}
|
||||||
66
ServiceHost/DebugAdapter/LaunchRequest.cs
Normal file
66
ServiceHost/DebugAdapter/LaunchRequest.cs
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
//
|
||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
//
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol;
|
||||||
|
|
||||||
|
namespace Microsoft.PowerShell.EditorServices.Protocol.DebugAdapter
|
||||||
|
{
|
||||||
|
public class LaunchRequest
|
||||||
|
{
|
||||||
|
public static readonly
|
||||||
|
RequestType<LaunchRequestArguments, object> Type =
|
||||||
|
RequestType<LaunchRequestArguments, object>.Create("launch");
|
||||||
|
}
|
||||||
|
|
||||||
|
public class LaunchRequestArguments
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the absolute path to the program to debug.
|
||||||
|
/// </summary>
|
||||||
|
public string Program { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a boolean value that indicates whether the script should be
|
||||||
|
/// run with (false) or without (true) debugging support.
|
||||||
|
/// </summary>
|
||||||
|
public bool NoDebug { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a boolean value that determines whether to automatically stop
|
||||||
|
/// target after launch. If not specified, target does not stop.
|
||||||
|
/// </summary>
|
||||||
|
public bool StopOnEntry { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets optional arguments passed to the debuggee.
|
||||||
|
/// </summary>
|
||||||
|
public string[] Args { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the working directory of the launched debuggee (specified as an absolute path).
|
||||||
|
/// If omitted the debuggee is lauched in its own directory.
|
||||||
|
/// </summary>
|
||||||
|
public string Cwd { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the absolute path to the runtime executable to be used.
|
||||||
|
/// Default is the runtime executable on the PATH.
|
||||||
|
/// </summary>
|
||||||
|
public string RuntimeExecutable { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the optional arguments passed to the runtime executable.
|
||||||
|
/// </summary>
|
||||||
|
public string[] RuntimeArgs { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets optional environment variables to pass to the debuggee. The string valued
|
||||||
|
/// properties of the 'environmentVariables' are used as key/value pairs.
|
||||||
|
/// </summary>
|
||||||
|
public Dictionary<string, string> Env { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
20
ServiceHost/DebugAdapter/NextRequest.cs
Normal file
20
ServiceHost/DebugAdapter/NextRequest.cs
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
//
|
||||||
|
// 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.MessageProtocol;
|
||||||
|
|
||||||
|
namespace Microsoft.PowerShell.EditorServices.Protocol.DebugAdapter
|
||||||
|
{
|
||||||
|
// /** StepOver request; value of command field is "next".
|
||||||
|
// he request starts the debuggee to run again for one step.
|
||||||
|
// penDebug will respond with a StoppedEvent (event type 'step') after running the step.
|
||||||
|
public class NextRequest
|
||||||
|
{
|
||||||
|
public static readonly
|
||||||
|
RequestType<object, object> Type =
|
||||||
|
RequestType<object, object>.Create("next");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
24
ServiceHost/DebugAdapter/OutputEvent.cs
Normal file
24
ServiceHost/DebugAdapter/OutputEvent.cs
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
//
|
||||||
|
// 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.MessageProtocol;
|
||||||
|
|
||||||
|
namespace Microsoft.PowerShell.EditorServices.Protocol.DebugAdapter
|
||||||
|
{
|
||||||
|
public class OutputEvent
|
||||||
|
{
|
||||||
|
public static readonly
|
||||||
|
EventType<OutputEventBody> Type =
|
||||||
|
EventType<OutputEventBody>.Create("output");
|
||||||
|
}
|
||||||
|
|
||||||
|
public class OutputEventBody
|
||||||
|
{
|
||||||
|
public string Category { get; set; }
|
||||||
|
|
||||||
|
public string Output { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
17
ServiceHost/DebugAdapter/PauseRequest.cs
Normal file
17
ServiceHost/DebugAdapter/PauseRequest.cs
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
//
|
||||||
|
// 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.MessageProtocol;
|
||||||
|
|
||||||
|
namespace Microsoft.PowerShell.EditorServices.Protocol.DebugAdapter
|
||||||
|
{
|
||||||
|
public class PauseRequest
|
||||||
|
{
|
||||||
|
public static readonly
|
||||||
|
RequestType<object, object> Type =
|
||||||
|
RequestType<object, object>.Create("pause");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
40
ServiceHost/DebugAdapter/Scope.cs
Normal file
40
ServiceHost/DebugAdapter/Scope.cs
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
//
|
||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
//
|
||||||
|
|
||||||
|
namespace Microsoft.PowerShell.EditorServices.Protocol.DebugAdapter
|
||||||
|
{
|
||||||
|
public class Scope
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the name of the scope (as such 'Arguments', 'Locals')
|
||||||
|
/// </summary>
|
||||||
|
public string Name { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the variables of this scope can be retrieved by passing the
|
||||||
|
/// value of variablesReference to the VariablesRequest.
|
||||||
|
/// </summary>
|
||||||
|
public int VariablesReference { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a boolean value indicating if number of variables in
|
||||||
|
/// this scope is large or expensive to retrieve.
|
||||||
|
/// </summary>
|
||||||
|
public bool Expensive { get; set; }
|
||||||
|
|
||||||
|
#if false
|
||||||
|
public static Scope Create(VariableScope scope)
|
||||||
|
{
|
||||||
|
return new Scope {
|
||||||
|
Name = scope.Name,
|
||||||
|
VariablesReference = scope.Id,
|
||||||
|
// Temporary fix for #95 to get debug hover tips to work well at least for the local scope.
|
||||||
|
Expensive = (scope.Name != VariableContainerDetails.LocalScopeName)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
29
ServiceHost/DebugAdapter/ScopesRequest.cs
Normal file
29
ServiceHost/DebugAdapter/ScopesRequest.cs
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
//
|
||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
//
|
||||||
|
|
||||||
|
using System.Diagnostics;
|
||||||
|
using Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol;
|
||||||
|
|
||||||
|
namespace Microsoft.PowerShell.EditorServices.Protocol.DebugAdapter
|
||||||
|
{
|
||||||
|
public class ScopesRequest
|
||||||
|
{
|
||||||
|
public static readonly
|
||||||
|
RequestType<ScopesRequestArguments, ScopesResponseBody> Type =
|
||||||
|
RequestType<ScopesRequestArguments, ScopesResponseBody>.Create("scopes");
|
||||||
|
}
|
||||||
|
|
||||||
|
[DebuggerDisplay("FrameId = {FrameId}")]
|
||||||
|
public class ScopesRequestArguments
|
||||||
|
{
|
||||||
|
public int FrameId { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ScopesResponseBody
|
||||||
|
{
|
||||||
|
public Scope[] Scopes { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
43
ServiceHost/DebugAdapter/SetBreakpointsRequest.cs
Normal file
43
ServiceHost/DebugAdapter/SetBreakpointsRequest.cs
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
//
|
||||||
|
// 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.MessageProtocol;
|
||||||
|
|
||||||
|
namespace Microsoft.PowerShell.EditorServices.Protocol.DebugAdapter
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// SetBreakpoints request; value of command field is "setBreakpoints".
|
||||||
|
/// Sets multiple breakpoints for a single source and clears all previous breakpoints in that source.
|
||||||
|
/// To clear all breakpoint for a source, specify an empty array.
|
||||||
|
/// When a breakpoint is hit, a StoppedEvent (event type 'breakpoint') is generated.
|
||||||
|
/// </summary>
|
||||||
|
public class SetBreakpointsRequest
|
||||||
|
{
|
||||||
|
public static readonly
|
||||||
|
RequestType<SetBreakpointsRequestArguments, SetBreakpointsResponseBody> Type =
|
||||||
|
RequestType<SetBreakpointsRequestArguments, SetBreakpointsResponseBody>.Create("setBreakpoints");
|
||||||
|
}
|
||||||
|
|
||||||
|
public class SetBreakpointsRequestArguments
|
||||||
|
{
|
||||||
|
public Source Source { get; set; }
|
||||||
|
|
||||||
|
public SourceBreakpoint[] Breakpoints { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class SourceBreakpoint
|
||||||
|
{
|
||||||
|
public int Line { get; set; }
|
||||||
|
|
||||||
|
public int? Column { get; set; }
|
||||||
|
|
||||||
|
public string Condition { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class SetBreakpointsResponseBody
|
||||||
|
{
|
||||||
|
public Breakpoint[] Breakpoints { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
31
ServiceHost/DebugAdapter/SetExceptionBreakpointsRequest.cs
Normal file
31
ServiceHost/DebugAdapter/SetExceptionBreakpointsRequest.cs
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
//
|
||||||
|
// 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.MessageProtocol;
|
||||||
|
|
||||||
|
namespace Microsoft.PowerShell.EditorServices.Protocol.DebugAdapter
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// SetExceptionBreakpoints request; value of command field is "setExceptionBreakpoints".
|
||||||
|
/// Enable that the debuggee stops on exceptions with a StoppedEvent (event type 'exception').
|
||||||
|
/// </summary>
|
||||||
|
public class SetExceptionBreakpointsRequest
|
||||||
|
{
|
||||||
|
public static readonly
|
||||||
|
RequestType<SetExceptionBreakpointsRequestArguments, object> Type =
|
||||||
|
RequestType<SetExceptionBreakpointsRequestArguments, object>.Create("setExceptionBreakpoints");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Arguments for "setExceptionBreakpoints" request.
|
||||||
|
/// </summary>
|
||||||
|
public class SetExceptionBreakpointsRequestArguments
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the names of enabled exception breakpoints.
|
||||||
|
/// </summary>
|
||||||
|
public string[] Filters { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
31
ServiceHost/DebugAdapter/SetFunctionBreakpointsRequest.cs
Normal file
31
ServiceHost/DebugAdapter/SetFunctionBreakpointsRequest.cs
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
//
|
||||||
|
// 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.MessageProtocol;
|
||||||
|
|
||||||
|
namespace Microsoft.PowerShell.EditorServices.Protocol.DebugAdapter
|
||||||
|
{
|
||||||
|
public class SetFunctionBreakpointsRequest
|
||||||
|
{
|
||||||
|
public static readonly
|
||||||
|
RequestType<SetFunctionBreakpointsRequestArguments, SetBreakpointsResponseBody> Type =
|
||||||
|
RequestType<SetFunctionBreakpointsRequestArguments, SetBreakpointsResponseBody>.Create("setFunctionBreakpoints");
|
||||||
|
}
|
||||||
|
|
||||||
|
public class SetFunctionBreakpointsRequestArguments
|
||||||
|
{
|
||||||
|
public FunctionBreakpoint[] Breakpoints { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class FunctionBreakpoint
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the name of the function to break on when it is invoked.
|
||||||
|
/// </summary>
|
||||||
|
public string Name { get; set; }
|
||||||
|
|
||||||
|
public string Condition { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
17
ServiceHost/DebugAdapter/Source.cs
Normal file
17
ServiceHost/DebugAdapter/Source.cs
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
//
|
||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
//
|
||||||
|
|
||||||
|
namespace Microsoft.PowerShell.EditorServices.Protocol.DebugAdapter
|
||||||
|
{
|
||||||
|
public class Source
|
||||||
|
{
|
||||||
|
public string Name { get; set; }
|
||||||
|
|
||||||
|
public string Path { get; set; }
|
||||||
|
|
||||||
|
public int? SourceReference { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
29
ServiceHost/DebugAdapter/SourceRequest.cs
Normal file
29
ServiceHost/DebugAdapter/SourceRequest.cs
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
//
|
||||||
|
// 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.MessageProtocol;
|
||||||
|
|
||||||
|
namespace Microsoft.PowerShell.EditorServices.Protocol.DebugAdapter
|
||||||
|
{
|
||||||
|
public class SourceRequest
|
||||||
|
{
|
||||||
|
public static readonly
|
||||||
|
RequestType<SourceRequestArguments, SourceResponseBody> Type =
|
||||||
|
RequestType<SourceRequestArguments, SourceResponseBody>.Create("source");
|
||||||
|
}
|
||||||
|
|
||||||
|
public class SourceRequestArguments
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the reference to the source. This is the value received in Source.reference.
|
||||||
|
/// </summary>
|
||||||
|
public int SourceReference { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class SourceResponseBody
|
||||||
|
{
|
||||||
|
public string Content { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
53
ServiceHost/DebugAdapter/StackFrame.cs
Normal file
53
ServiceHost/DebugAdapter/StackFrame.cs
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
//
|
||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
//
|
||||||
|
|
||||||
|
namespace Microsoft.PowerShell.EditorServices.Protocol.DebugAdapter
|
||||||
|
{
|
||||||
|
public class StackFrame
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
|
||||||
|
public string Name { get; set; }
|
||||||
|
|
||||||
|
public Source Source { get; set; }
|
||||||
|
|
||||||
|
public int Line { get; set; }
|
||||||
|
|
||||||
|
public int Column { get; set; }
|
||||||
|
|
||||||
|
// /** An identifier for the stack frame. */
|
||||||
|
//id: number;
|
||||||
|
///** The name of the stack frame, typically a method name */
|
||||||
|
//name: string;
|
||||||
|
///** The source of the frame. */
|
||||||
|
//source: Source;
|
||||||
|
///** The line within the file of the frame. */
|
||||||
|
//line: number;
|
||||||
|
///** The column within the line. */
|
||||||
|
//column: number;
|
||||||
|
///** All arguments and variables declared in this stackframe. */
|
||||||
|
//scopes: Scope[];
|
||||||
|
|
||||||
|
#if false
|
||||||
|
public static StackFrame Create(
|
||||||
|
StackFrameDetails stackFrame,
|
||||||
|
int id)
|
||||||
|
{
|
||||||
|
return new StackFrame
|
||||||
|
{
|
||||||
|
Id = id,
|
||||||
|
Name = stackFrame.FunctionName,
|
||||||
|
Line = stackFrame.LineNumber,
|
||||||
|
Column = stackFrame.ColumnNumber,
|
||||||
|
Source = new Source
|
||||||
|
{
|
||||||
|
Path = stackFrame.ScriptPath
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
33
ServiceHost/DebugAdapter/StackTraceRequest.cs
Normal file
33
ServiceHost/DebugAdapter/StackTraceRequest.cs
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
//
|
||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
//
|
||||||
|
|
||||||
|
using System.Diagnostics;
|
||||||
|
using Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol;
|
||||||
|
|
||||||
|
namespace Microsoft.PowerShell.EditorServices.Protocol.DebugAdapter
|
||||||
|
{
|
||||||
|
public class StackTraceRequest
|
||||||
|
{
|
||||||
|
public static readonly
|
||||||
|
RequestType<StackTraceRequestArguments, StackTraceResponseBody> Type =
|
||||||
|
RequestType<StackTraceRequestArguments, StackTraceResponseBody>.Create("stackTrace");
|
||||||
|
}
|
||||||
|
|
||||||
|
[DebuggerDisplay("ThreadId = {ThreadId}, Levels = {Levels}")]
|
||||||
|
public class StackTraceRequestArguments
|
||||||
|
{
|
||||||
|
public int ThreadId { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the maximum number of frames to return. If levels is not specified or 0, all frames are returned.
|
||||||
|
/// </summary>
|
||||||
|
public int Levels { get; private set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class StackTraceResponseBody
|
||||||
|
{
|
||||||
|
public StackFrame[] StackFrames { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
16
ServiceHost/DebugAdapter/StartedEvent.cs
Normal file
16
ServiceHost/DebugAdapter/StartedEvent.cs
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
//
|
||||||
|
// 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.MessageProtocol;
|
||||||
|
|
||||||
|
namespace Microsoft.PowerShell.EditorServices.Protocol.DebugAdapter
|
||||||
|
{
|
||||||
|
public class StartedEvent
|
||||||
|
{
|
||||||
|
public static readonly
|
||||||
|
EventType<object> Type =
|
||||||
|
EventType<object>.Create("started");
|
||||||
|
}
|
||||||
|
}
|
||||||
17
ServiceHost/DebugAdapter/StepInRequest.cs
Normal file
17
ServiceHost/DebugAdapter/StepInRequest.cs
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
//
|
||||||
|
// 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.MessageProtocol;
|
||||||
|
|
||||||
|
namespace Microsoft.PowerShell.EditorServices.Protocol.DebugAdapter
|
||||||
|
{
|
||||||
|
public class StepInRequest
|
||||||
|
{
|
||||||
|
public static readonly
|
||||||
|
RequestType<object, object> Type =
|
||||||
|
RequestType<object, object>.Create("stepIn");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
16
ServiceHost/DebugAdapter/StepOutRequest.cs
Normal file
16
ServiceHost/DebugAdapter/StepOutRequest.cs
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
//
|
||||||
|
// 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.MessageProtocol;
|
||||||
|
|
||||||
|
namespace Microsoft.PowerShell.EditorServices.Protocol.DebugAdapter
|
||||||
|
{
|
||||||
|
public class StepOutRequest
|
||||||
|
{
|
||||||
|
public static readonly
|
||||||
|
RequestType<object, object> Type =
|
||||||
|
RequestType<object, object>.Create("stepOut");
|
||||||
|
}
|
||||||
|
}
|
||||||
41
ServiceHost/DebugAdapter/StoppedEvent.cs
Normal file
41
ServiceHost/DebugAdapter/StoppedEvent.cs
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
//
|
||||||
|
// 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.MessageProtocol;
|
||||||
|
|
||||||
|
namespace Microsoft.PowerShell.EditorServices.Protocol.DebugAdapter
|
||||||
|
{
|
||||||
|
public class StoppedEvent
|
||||||
|
{
|
||||||
|
public static readonly
|
||||||
|
EventType<StoppedEventBody> Type =
|
||||||
|
EventType<StoppedEventBody>.Create("stopped");
|
||||||
|
}
|
||||||
|
|
||||||
|
public class StoppedEventBody
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A value such as "step", "breakpoint", "exception", or "pause"
|
||||||
|
/// </summary>
|
||||||
|
public string Reason { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the current thread ID, if any.
|
||||||
|
/// </summary>
|
||||||
|
public int? ThreadId { get; set; }
|
||||||
|
|
||||||
|
public Source Source { get; set; }
|
||||||
|
|
||||||
|
public int Line { get; set; }
|
||||||
|
|
||||||
|
public int Column { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets additional information such as an error message.
|
||||||
|
/// </summary>
|
||||||
|
public string Text { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
17
ServiceHost/DebugAdapter/TerminatedEvent.cs
Normal file
17
ServiceHost/DebugAdapter/TerminatedEvent.cs
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
//
|
||||||
|
// 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.MessageProtocol;
|
||||||
|
|
||||||
|
namespace Microsoft.PowerShell.EditorServices.Protocol.DebugAdapter
|
||||||
|
{
|
||||||
|
public class TerminatedEvent
|
||||||
|
{
|
||||||
|
public static readonly
|
||||||
|
EventType<object> Type =
|
||||||
|
EventType<object>.Create("terminated");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
15
ServiceHost/DebugAdapter/Thread.cs
Normal file
15
ServiceHost/DebugAdapter/Thread.cs
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
//
|
||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
//
|
||||||
|
|
||||||
|
namespace Microsoft.PowerShell.EditorServices.Protocol.DebugAdapter
|
||||||
|
{
|
||||||
|
public class Thread
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
|
||||||
|
public string Name { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
22
ServiceHost/DebugAdapter/ThreadsRequest.cs
Normal file
22
ServiceHost/DebugAdapter/ThreadsRequest.cs
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
//
|
||||||
|
// 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.MessageProtocol;
|
||||||
|
|
||||||
|
namespace Microsoft.PowerShell.EditorServices.Protocol.DebugAdapter
|
||||||
|
{
|
||||||
|
public class ThreadsRequest
|
||||||
|
{
|
||||||
|
public static readonly
|
||||||
|
RequestType<object, ThreadsResponseBody> Type =
|
||||||
|
RequestType<object, ThreadsResponseBody>.Create("threads");
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ThreadsResponseBody
|
||||||
|
{
|
||||||
|
public Thread[] Threads { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
33
ServiceHost/DebugAdapter/Variable.cs
Normal file
33
ServiceHost/DebugAdapter/Variable.cs
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
//
|
||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
//
|
||||||
|
|
||||||
|
namespace Microsoft.PowerShell.EditorServices.Protocol.DebugAdapter
|
||||||
|
{
|
||||||
|
public class Variable
|
||||||
|
{
|
||||||
|
public string Name { get; set; }
|
||||||
|
|
||||||
|
// /** The variable's value. For structured objects this can be a multi line text, e.g. for a function the body of a function. */
|
||||||
|
public string Value { get; set; }
|
||||||
|
|
||||||
|
// /** If variablesReference is > 0, the variable is structured and its children can be retrieved by passing variablesReference to the VariablesRequest. */
|
||||||
|
public int VariablesReference { get; set; }
|
||||||
|
|
||||||
|
#if false
|
||||||
|
public static Variable Create(VariableDetailsBase variable)
|
||||||
|
{
|
||||||
|
return new Variable
|
||||||
|
{
|
||||||
|
Name = variable.Name,
|
||||||
|
Value = variable.ValueString ?? string.Empty,
|
||||||
|
VariablesReference =
|
||||||
|
variable.IsExpandable ?
|
||||||
|
variable.Id : 0
|
||||||
|
};
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
29
ServiceHost/DebugAdapter/VariablesRequest.cs
Normal file
29
ServiceHost/DebugAdapter/VariablesRequest.cs
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
//
|
||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
//
|
||||||
|
|
||||||
|
using System.Diagnostics;
|
||||||
|
using Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol;
|
||||||
|
|
||||||
|
namespace Microsoft.PowerShell.EditorServices.Protocol.DebugAdapter
|
||||||
|
{
|
||||||
|
public class VariablesRequest
|
||||||
|
{
|
||||||
|
public static readonly
|
||||||
|
RequestType<VariablesRequestArguments, VariablesResponseBody> Type =
|
||||||
|
RequestType<VariablesRequestArguments, VariablesResponseBody>.Create("variables");
|
||||||
|
}
|
||||||
|
|
||||||
|
[DebuggerDisplay("VariablesReference = {VariablesReference}")]
|
||||||
|
public class VariablesRequestArguments
|
||||||
|
{
|
||||||
|
public int VariablesReference { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class VariablesResponseBody
|
||||||
|
{
|
||||||
|
public Variable[] Variables { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
23
ServiceHost/LanguageServer/ClientCapabilities.cs
Normal file
23
ServiceHost/LanguageServer/ClientCapabilities.cs
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
//
|
||||||
|
// 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.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Microsoft.PowerShell.EditorServices.Protocol.LanguageServer
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Defines a class that describes the capabilities of a language
|
||||||
|
/// client. At this time no specific capabilities are listed for
|
||||||
|
/// clients.
|
||||||
|
/// </summary>
|
||||||
|
public class ClientCapabilities
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
86
ServiceHost/LanguageServer/Completion.cs
Normal file
86
ServiceHost/LanguageServer/Completion.cs
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
//
|
||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
//
|
||||||
|
|
||||||
|
using System.Diagnostics;
|
||||||
|
using Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol;
|
||||||
|
|
||||||
|
namespace Microsoft.PowerShell.EditorServices.Protocol.LanguageServer
|
||||||
|
{
|
||||||
|
public class CompletionRequest
|
||||||
|
{
|
||||||
|
public static readonly
|
||||||
|
RequestType<TextDocumentPosition, CompletionItem[]> Type =
|
||||||
|
RequestType<TextDocumentPosition, CompletionItem[]>.Create("textDocument/completion");
|
||||||
|
}
|
||||||
|
|
||||||
|
public class CompletionResolveRequest
|
||||||
|
{
|
||||||
|
public static readonly
|
||||||
|
RequestType<CompletionItem, CompletionItem> Type =
|
||||||
|
RequestType<CompletionItem, CompletionItem>.Create("completionItem/resolve");
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum CompletionItemKind
|
||||||
|
{
|
||||||
|
Text = 1,
|
||||||
|
Method = 2,
|
||||||
|
Function = 3,
|
||||||
|
Constructor = 4,
|
||||||
|
Field = 5,
|
||||||
|
Variable = 6,
|
||||||
|
Class = 7,
|
||||||
|
Interface = 8,
|
||||||
|
Module = 9,
|
||||||
|
Property = 10,
|
||||||
|
Unit = 11,
|
||||||
|
Value = 12,
|
||||||
|
Enum = 13,
|
||||||
|
Keyword = 14,
|
||||||
|
Snippet = 15,
|
||||||
|
Color = 16,
|
||||||
|
File = 17,
|
||||||
|
Reference = 18
|
||||||
|
}
|
||||||
|
|
||||||
|
[DebuggerDisplay("NewText = {NewText}, Range = {Range.Start.Line}:{Range.Start.Character} - {Range.End.Line}:{Range.End.Character}")]
|
||||||
|
public class TextEdit
|
||||||
|
{
|
||||||
|
public Range Range { get; set; }
|
||||||
|
|
||||||
|
public string NewText { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[DebuggerDisplay("Kind = {Kind.ToString()}, Label = {Label}, Detail = {Detail}")]
|
||||||
|
public class CompletionItem
|
||||||
|
{
|
||||||
|
public string Label { get; set; }
|
||||||
|
|
||||||
|
public CompletionItemKind? Kind { get; set; }
|
||||||
|
|
||||||
|
public string Detail { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the documentation string for the completion item.
|
||||||
|
/// </summary>
|
||||||
|
public string Documentation { get; set; }
|
||||||
|
|
||||||
|
public string SortText { get; set; }
|
||||||
|
|
||||||
|
public string FilterText { get; set; }
|
||||||
|
|
||||||
|
public string InsertText { get; set; }
|
||||||
|
|
||||||
|
public TextEdit TextEdit { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a custom data field that allows the server to mark
|
||||||
|
/// each completion item with an identifier that will help correlate
|
||||||
|
/// the item to the previous completion request during a completion
|
||||||
|
/// resolve request.
|
||||||
|
/// </summary>
|
||||||
|
public object Data { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
21
ServiceHost/LanguageServer/Configuration.cs
Normal file
21
ServiceHost/LanguageServer/Configuration.cs
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
//
|
||||||
|
// 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.MessageProtocol;
|
||||||
|
|
||||||
|
namespace Microsoft.PowerShell.EditorServices.Protocol.LanguageServer
|
||||||
|
{
|
||||||
|
public class DidChangeConfigurationNotification<TConfig>
|
||||||
|
{
|
||||||
|
public static readonly
|
||||||
|
EventType<DidChangeConfigurationParams<TConfig>> Type =
|
||||||
|
EventType<DidChangeConfigurationParams<TConfig>>.Create("workspace/didChangeConfiguration");
|
||||||
|
}
|
||||||
|
|
||||||
|
public class DidChangeConfigurationParams<TConfig>
|
||||||
|
{
|
||||||
|
public TConfig Settings { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
17
ServiceHost/LanguageServer/Definition.cs
Normal file
17
ServiceHost/LanguageServer/Definition.cs
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
//
|
||||||
|
// 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.MessageProtocol;
|
||||||
|
|
||||||
|
namespace Microsoft.PowerShell.EditorServices.Protocol.LanguageServer
|
||||||
|
{
|
||||||
|
public class DefinitionRequest
|
||||||
|
{
|
||||||
|
public static readonly
|
||||||
|
RequestType<TextDocumentPosition, Location[]> Type =
|
||||||
|
RequestType<TextDocumentPosition, Location[]>.Create("textDocument/definition");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
76
ServiceHost/LanguageServer/Diagnostics.cs
Normal file
76
ServiceHost/LanguageServer/Diagnostics.cs
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
//
|
||||||
|
// 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.MessageProtocol;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Microsoft.PowerShell.EditorServices.Protocol.LanguageServer
|
||||||
|
{
|
||||||
|
public class PublishDiagnosticsNotification
|
||||||
|
{
|
||||||
|
public static readonly
|
||||||
|
EventType<PublishDiagnosticsNotification> Type =
|
||||||
|
EventType<PublishDiagnosticsNotification>.Create("textDocument/publishDiagnostics");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the URI for which diagnostic information is reported.
|
||||||
|
/// </summary>
|
||||||
|
public string Uri { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the array of diagnostic information items.
|
||||||
|
/// </summary>
|
||||||
|
public Diagnostic[] Diagnostics { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum DiagnosticSeverity
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates that the diagnostic represents an error.
|
||||||
|
/// </summary>
|
||||||
|
Error = 1,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates that the diagnostic represents a warning.
|
||||||
|
/// </summary>
|
||||||
|
Warning = 2,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates that the diagnostic represents an informational message.
|
||||||
|
/// </summary>
|
||||||
|
Information = 3,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates that the diagnostic represents a hint.
|
||||||
|
/// </summary>
|
||||||
|
Hint = 4
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Diagnostic
|
||||||
|
{
|
||||||
|
public Range Range { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the severity of the diagnostic. If omitted, the
|
||||||
|
/// client should interpret the severity.
|
||||||
|
/// </summary>
|
||||||
|
public DiagnosticSeverity? Severity { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the diagnostic's code (optional).
|
||||||
|
/// </summary>
|
||||||
|
public string Code { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the diagnostic message.
|
||||||
|
/// </summary>
|
||||||
|
public string Message { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
31
ServiceHost/LanguageServer/DocumentHighlight.cs
Normal file
31
ServiceHost/LanguageServer/DocumentHighlight.cs
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
//
|
||||||
|
// 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.MessageProtocol;
|
||||||
|
|
||||||
|
namespace Microsoft.PowerShell.EditorServices.Protocol.LanguageServer
|
||||||
|
{
|
||||||
|
public enum DocumentHighlightKind
|
||||||
|
{
|
||||||
|
Text = 1,
|
||||||
|
Read = 2,
|
||||||
|
Write = 3
|
||||||
|
}
|
||||||
|
|
||||||
|
public class DocumentHighlight
|
||||||
|
{
|
||||||
|
public Range Range { get; set; }
|
||||||
|
|
||||||
|
public DocumentHighlightKind Kind { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class DocumentHighlightRequest
|
||||||
|
{
|
||||||
|
public static readonly
|
||||||
|
RequestType<TextDocumentPosition, DocumentHighlight[]> Type =
|
||||||
|
RequestType<TextDocumentPosition, DocumentHighlight[]>.Create("textDocument/documentHighlight");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
111
ServiceHost/LanguageServer/EditorCommands.cs
Normal file
111
ServiceHost/LanguageServer/EditorCommands.cs
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
//
|
||||||
|
// 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.MessageProtocol;
|
||||||
|
|
||||||
|
namespace Microsoft.PowerShell.EditorServices.Protocol.LanguageServer
|
||||||
|
{
|
||||||
|
public class ExtensionCommandAddedNotification
|
||||||
|
{
|
||||||
|
public static readonly
|
||||||
|
EventType<ExtensionCommandAddedNotification> Type =
|
||||||
|
EventType<ExtensionCommandAddedNotification>.Create("powerShell/extensionCommandAdded");
|
||||||
|
|
||||||
|
public string Name { get; set; }
|
||||||
|
|
||||||
|
public string DisplayName { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ExtensionCommandUpdatedNotification
|
||||||
|
{
|
||||||
|
public static readonly
|
||||||
|
EventType<ExtensionCommandUpdatedNotification> Type =
|
||||||
|
EventType<ExtensionCommandUpdatedNotification>.Create("powerShell/extensionCommandUpdated");
|
||||||
|
|
||||||
|
public string Name { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ExtensionCommandRemovedNotification
|
||||||
|
{
|
||||||
|
public static readonly
|
||||||
|
EventType<ExtensionCommandRemovedNotification> Type =
|
||||||
|
EventType<ExtensionCommandRemovedNotification>.Create("powerShell/extensionCommandRemoved");
|
||||||
|
|
||||||
|
public string Name { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ClientEditorContext
|
||||||
|
{
|
||||||
|
public string CurrentFilePath { get; set; }
|
||||||
|
|
||||||
|
public Position CursorPosition { get; set; }
|
||||||
|
|
||||||
|
public Range SelectionRange { get; set; }
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public class InvokeExtensionCommandRequest
|
||||||
|
{
|
||||||
|
public static readonly
|
||||||
|
RequestType<InvokeExtensionCommandRequest, string> Type =
|
||||||
|
RequestType<InvokeExtensionCommandRequest, string>.Create("powerShell/invokeExtensionCommand");
|
||||||
|
|
||||||
|
public string Name { get; set; }
|
||||||
|
|
||||||
|
public ClientEditorContext Context { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class GetEditorContextRequest
|
||||||
|
{
|
||||||
|
public static readonly
|
||||||
|
RequestType<GetEditorContextRequest, ClientEditorContext> Type =
|
||||||
|
RequestType<GetEditorContextRequest, ClientEditorContext>.Create("editor/getEditorContext");
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum EditorCommandResponse
|
||||||
|
{
|
||||||
|
Unsupported,
|
||||||
|
OK
|
||||||
|
}
|
||||||
|
|
||||||
|
public class InsertTextRequest
|
||||||
|
{
|
||||||
|
public static readonly
|
||||||
|
RequestType<InsertTextRequest, EditorCommandResponse> Type =
|
||||||
|
RequestType<InsertTextRequest, EditorCommandResponse>.Create("editor/insertText");
|
||||||
|
|
||||||
|
public string FilePath { get; set; }
|
||||||
|
|
||||||
|
public string InsertText { get; set; }
|
||||||
|
|
||||||
|
public Range InsertRange { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class SetSelectionRequest
|
||||||
|
{
|
||||||
|
public static readonly
|
||||||
|
RequestType<SetSelectionRequest, EditorCommandResponse> Type =
|
||||||
|
RequestType<SetSelectionRequest, EditorCommandResponse>.Create("editor/setSelection");
|
||||||
|
|
||||||
|
public Range SelectionRange { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class SetCursorPositionRequest
|
||||||
|
{
|
||||||
|
public static readonly
|
||||||
|
RequestType<SetCursorPositionRequest, EditorCommandResponse> Type =
|
||||||
|
RequestType<SetCursorPositionRequest, EditorCommandResponse>.Create("editor/setCursorPosition");
|
||||||
|
|
||||||
|
public Position CursorPosition { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class OpenFileRequest
|
||||||
|
{
|
||||||
|
public static readonly
|
||||||
|
RequestType<string, EditorCommandResponse> Type =
|
||||||
|
RequestType<string, EditorCommandResponse>.Create("editor/openFile");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
16
ServiceHost/LanguageServer/ExpandAliasRequest.cs
Normal file
16
ServiceHost/LanguageServer/ExpandAliasRequest.cs
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
//
|
||||||
|
// 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.MessageProtocol;
|
||||||
|
|
||||||
|
namespace Microsoft.PowerShell.EditorServices.Protocol.LanguageServer
|
||||||
|
{
|
||||||
|
public class ExpandAliasRequest
|
||||||
|
{
|
||||||
|
public static readonly
|
||||||
|
RequestType<string, string> Type =
|
||||||
|
RequestType<string, string>.Create("powerShell/expandAlias");
|
||||||
|
}
|
||||||
|
}
|
||||||
24
ServiceHost/LanguageServer/FindModuleRequest.cs
Normal file
24
ServiceHost/LanguageServer/FindModuleRequest.cs
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
//
|
||||||
|
// 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.MessageProtocol;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Microsoft.PowerShell.EditorServices.Protocol.LanguageServer
|
||||||
|
{
|
||||||
|
public class FindModuleRequest
|
||||||
|
{
|
||||||
|
public static readonly
|
||||||
|
RequestType<List<PSModuleMessage>, object> Type =
|
||||||
|
RequestType<List<PSModuleMessage>, object>.Create("powerShell/findModule");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public class PSModuleMessage
|
||||||
|
{
|
||||||
|
public string Name { get; set; }
|
||||||
|
public string Description { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
37
ServiceHost/LanguageServer/Hover.cs
Normal file
37
ServiceHost/LanguageServer/Hover.cs
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
//
|
||||||
|
// 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.MessageProtocol;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Microsoft.PowerShell.EditorServices.Protocol.LanguageServer
|
||||||
|
{
|
||||||
|
public class MarkedString
|
||||||
|
{
|
||||||
|
public string Language { get; set; }
|
||||||
|
|
||||||
|
public string Value { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Hover
|
||||||
|
{
|
||||||
|
public MarkedString[] Contents { get; set; }
|
||||||
|
|
||||||
|
public Range? Range { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class HoverRequest
|
||||||
|
{
|
||||||
|
public static readonly
|
||||||
|
RequestType<TextDocumentPosition, Hover> Type =
|
||||||
|
RequestType<TextDocumentPosition, Hover>.Create("textDocument/hover");
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
46
ServiceHost/LanguageServer/Initialize.cs
Normal file
46
ServiceHost/LanguageServer/Initialize.cs
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
//
|
||||||
|
// 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.MessageProtocol;
|
||||||
|
|
||||||
|
namespace Microsoft.PowerShell.EditorServices.Protocol.LanguageServer
|
||||||
|
{
|
||||||
|
public class InitializeRequest
|
||||||
|
{
|
||||||
|
public static readonly
|
||||||
|
RequestType<InitializeRequest, InitializeResult> Type =
|
||||||
|
RequestType<InitializeRequest, InitializeResult>.Create("initialize");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the root path of the editor's open workspace.
|
||||||
|
/// If null it is assumed that a file was opened without having
|
||||||
|
/// a workspace open.
|
||||||
|
/// </summary>
|
||||||
|
public string RootPath { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the capabilities provided by the client (editor).
|
||||||
|
/// </summary>
|
||||||
|
public ClientCapabilities Capabilities { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class InitializeResult
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the capabilities provided by the language server.
|
||||||
|
/// </summary>
|
||||||
|
public ServerCapabilities Capabilities { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class InitializeError
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a boolean indicating whether the client should retry
|
||||||
|
/// sending the Initialize request after showing the error to the user.
|
||||||
|
/// </summary>
|
||||||
|
public bool Retry { get; set;}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
16
ServiceHost/LanguageServer/InstallModuleRequest.cs
Normal file
16
ServiceHost/LanguageServer/InstallModuleRequest.cs
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
//
|
||||||
|
// 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.MessageProtocol;
|
||||||
|
|
||||||
|
namespace Microsoft.PowerShell.EditorServices.Protocol.LanguageServer
|
||||||
|
{
|
||||||
|
class InstallModuleRequest
|
||||||
|
{
|
||||||
|
public static readonly
|
||||||
|
RequestType<string, object> Type =
|
||||||
|
RequestType<string, object>.Create("powerShell/installModule");
|
||||||
|
}
|
||||||
|
}
|
||||||
27
ServiceHost/LanguageServer/References.cs
Normal file
27
ServiceHost/LanguageServer/References.cs
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
//
|
||||||
|
// 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.MessageProtocol;
|
||||||
|
|
||||||
|
namespace Microsoft.PowerShell.EditorServices.Protocol.LanguageServer
|
||||||
|
{
|
||||||
|
public class ReferencesRequest
|
||||||
|
{
|
||||||
|
public static readonly
|
||||||
|
RequestType<ReferencesParams, Location[]> Type =
|
||||||
|
RequestType<ReferencesParams, Location[]>.Create("textDocument/references");
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ReferencesParams : TextDocumentPosition
|
||||||
|
{
|
||||||
|
public ReferencesContext Context { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ReferencesContext
|
||||||
|
{
|
||||||
|
public bool IncludeDeclaration { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
63
ServiceHost/LanguageServer/ServerCapabilities.cs
Normal file
63
ServiceHost/LanguageServer/ServerCapabilities.cs
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
//
|
||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
//
|
||||||
|
|
||||||
|
namespace Microsoft.PowerShell.EditorServices.Protocol.LanguageServer
|
||||||
|
{
|
||||||
|
public class ServerCapabilities
|
||||||
|
{
|
||||||
|
public TextDocumentSyncKind? TextDocumentSync { get; set; }
|
||||||
|
|
||||||
|
public bool? HoverProvider { get; set; }
|
||||||
|
|
||||||
|
public CompletionOptions CompletionProvider { get; set; }
|
||||||
|
|
||||||
|
public SignatureHelpOptions SignatureHelpProvider { get; set; }
|
||||||
|
|
||||||
|
public bool? DefinitionProvider { get; set; }
|
||||||
|
|
||||||
|
public bool? ReferencesProvider { get; set; }
|
||||||
|
|
||||||
|
public bool? DocumentHighlightProvider { get; set; }
|
||||||
|
|
||||||
|
public bool? DocumentSymbolProvider { get; set; }
|
||||||
|
|
||||||
|
public bool? WorkspaceSymbolProvider { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Defines the document synchronization strategies that a server may support.
|
||||||
|
/// </summary>
|
||||||
|
public enum TextDocumentSyncKind
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates that documents should not be synced at all.
|
||||||
|
/// </summary>
|
||||||
|
None = 0,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates that document changes are always sent with the full content.
|
||||||
|
/// </summary>
|
||||||
|
Full,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates that document changes are sent as incremental changes after
|
||||||
|
/// the initial document content has been sent.
|
||||||
|
/// </summary>
|
||||||
|
Incremental
|
||||||
|
}
|
||||||
|
|
||||||
|
public class CompletionOptions
|
||||||
|
{
|
||||||
|
public bool? ResolveProvider { get; set; }
|
||||||
|
|
||||||
|
public string[] TriggerCharacters { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class SignatureHelpOptions
|
||||||
|
{
|
||||||
|
public string[] TriggerCharacters { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
16
ServiceHost/LanguageServer/ShowOnlineHelpRequest.cs
Normal file
16
ServiceHost/LanguageServer/ShowOnlineHelpRequest.cs
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
//
|
||||||
|
// 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.MessageProtocol;
|
||||||
|
|
||||||
|
namespace Microsoft.PowerShell.EditorServices.Protocol.LanguageServer
|
||||||
|
{
|
||||||
|
public class ShowOnlineHelpRequest
|
||||||
|
{
|
||||||
|
public static readonly
|
||||||
|
RequestType<string, object> Type =
|
||||||
|
RequestType<string, object>.Create("powerShell/showOnlineHelp");
|
||||||
|
}
|
||||||
|
}
|
||||||
32
ServiceHost/LanguageServer/Shutdown.cs
Normal file
32
ServiceHost/LanguageServer/Shutdown.cs
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
//
|
||||||
|
// 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.MessageProtocol;
|
||||||
|
|
||||||
|
namespace Microsoft.PowerShell.EditorServices.Protocol.LanguageServer
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Defines a message that is sent from the client to request
|
||||||
|
/// that the server shut down.
|
||||||
|
/// </summary>
|
||||||
|
public class ShutdownRequest
|
||||||
|
{
|
||||||
|
public static readonly
|
||||||
|
RequestType<object, object> Type =
|
||||||
|
RequestType<object, object>.Create("shutdown");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Defines an event that is sent from the client to notify that
|
||||||
|
/// the client is exiting and the server should as well.
|
||||||
|
/// </summary>
|
||||||
|
public class ExitNotification
|
||||||
|
{
|
||||||
|
public static readonly
|
||||||
|
EventType<object> Type =
|
||||||
|
EventType<object>.Create("exit");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
42
ServiceHost/LanguageServer/SignatureHelp.cs
Normal file
42
ServiceHost/LanguageServer/SignatureHelp.cs
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
//
|
||||||
|
// 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.MessageProtocol;
|
||||||
|
|
||||||
|
namespace Microsoft.PowerShell.EditorServices.Protocol.LanguageServer
|
||||||
|
{
|
||||||
|
public class SignatureHelpRequest
|
||||||
|
{
|
||||||
|
public static readonly
|
||||||
|
RequestType<TextDocumentPosition, SignatureHelp> Type =
|
||||||
|
RequestType<TextDocumentPosition, SignatureHelp>.Create("textDocument/signatureHelp");
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ParameterInformation
|
||||||
|
{
|
||||||
|
public string Label { get; set; }
|
||||||
|
|
||||||
|
public string Documentation { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class SignatureInformation
|
||||||
|
{
|
||||||
|
public string Label { get; set; }
|
||||||
|
|
||||||
|
public string Documentation { get; set; }
|
||||||
|
|
||||||
|
public ParameterInformation[] Parameters { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class SignatureHelp
|
||||||
|
{
|
||||||
|
public SignatureInformation[] Signatures { get; set; }
|
||||||
|
|
||||||
|
public int? ActiveSignature { get; set; }
|
||||||
|
|
||||||
|
public int? ActiveParameter { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
149
ServiceHost/LanguageServer/TextDocument.cs
Normal file
149
ServiceHost/LanguageServer/TextDocument.cs
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
//
|
||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
//
|
||||||
|
|
||||||
|
using System.Diagnostics;
|
||||||
|
using Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol;
|
||||||
|
|
||||||
|
namespace Microsoft.PowerShell.EditorServices.Protocol.LanguageServer
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Defines a base parameter class for identifying a text document.
|
||||||
|
/// </summary>
|
||||||
|
[DebuggerDisplay("TextDocumentIdentifier = {Uri}")]
|
||||||
|
public class TextDocumentIdentifier
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the URI which identifies the path of the
|
||||||
|
/// text document.
|
||||||
|
/// </summary>
|
||||||
|
public string Uri { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Defines a position in a text document.
|
||||||
|
/// </summary>
|
||||||
|
[DebuggerDisplay("TextDocumentPosition = {Position.Line}:{Position.Character}")]
|
||||||
|
public class TextDocumentPosition : TextDocumentIdentifier
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the position in the document.
|
||||||
|
/// </summary>
|
||||||
|
public Position Position { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class DidOpenTextDocumentNotification : TextDocumentIdentifier
|
||||||
|
{
|
||||||
|
public static readonly
|
||||||
|
EventType<DidOpenTextDocumentNotification> Type =
|
||||||
|
EventType<DidOpenTextDocumentNotification>.Create("textDocument/didOpen");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the full content of the opened document.
|
||||||
|
/// </summary>
|
||||||
|
public string Text { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class DidCloseTextDocumentNotification
|
||||||
|
{
|
||||||
|
public static readonly
|
||||||
|
EventType<TextDocumentIdentifier> Type =
|
||||||
|
EventType<TextDocumentIdentifier>.Create("textDocument/didClose");
|
||||||
|
}
|
||||||
|
|
||||||
|
public class DidChangeTextDocumentNotification
|
||||||
|
{
|
||||||
|
public static readonly
|
||||||
|
EventType<DidChangeTextDocumentParams> Type =
|
||||||
|
EventType<DidChangeTextDocumentParams>.Create("textDocument/didChange");
|
||||||
|
}
|
||||||
|
|
||||||
|
public class DidChangeTextDocumentParams : TextDocumentIdentifier
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the list of changes to the document content.
|
||||||
|
/// </summary>
|
||||||
|
public TextDocumentChangeEvent[] ContentChanges { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class TextDocumentChangeEvent
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the Range where the document was changed. Will
|
||||||
|
/// be null if the server's TextDocumentSyncKind is Full.
|
||||||
|
/// </summary>
|
||||||
|
public Range? Range { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the length of the Range being replaced in the
|
||||||
|
/// document. Will be null if the server's TextDocumentSyncKind is
|
||||||
|
/// Full.
|
||||||
|
/// </summary>
|
||||||
|
public int? RangeLength { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the new text of the document.
|
||||||
|
/// </summary>
|
||||||
|
public string Text { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[DebuggerDisplay("Position = {Line}:{Character}")]
|
||||||
|
public class Position
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the zero-based line number.
|
||||||
|
/// </summary>
|
||||||
|
public int Line { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the zero-based column number.
|
||||||
|
/// </summary>
|
||||||
|
public int Character { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[DebuggerDisplay("Start = {Start.Line}:{Start.Character}, End = {End.Line}:{End.Character}")]
|
||||||
|
public struct Range
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the starting position of the range.
|
||||||
|
/// </summary>
|
||||||
|
public Position Start { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the ending position of the range.
|
||||||
|
/// </summary>
|
||||||
|
public Position End { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[DebuggerDisplay("Range = {Range.Start.Line}:{Range.Start.Character} - {Range.End.Line}:{Range.End.Character}, Uri = {Uri}")]
|
||||||
|
public class Location
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the URI indicating the file in which the location refers.
|
||||||
|
/// </summary>
|
||||||
|
public string Uri { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the Range indicating the range in which location refers.
|
||||||
|
/// </summary>
|
||||||
|
public Range Range { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum FileChangeType
|
||||||
|
{
|
||||||
|
Created = 1,
|
||||||
|
|
||||||
|
Changed,
|
||||||
|
|
||||||
|
Deleted
|
||||||
|
}
|
||||||
|
|
||||||
|
public class FileEvent
|
||||||
|
{
|
||||||
|
public string Uri { get; set; }
|
||||||
|
|
||||||
|
public FileChangeType Type { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
62
ServiceHost/LanguageServer/WorkspaceSymbols.cs
Normal file
62
ServiceHost/LanguageServer/WorkspaceSymbols.cs
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
//
|
||||||
|
// 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.MessageProtocol;
|
||||||
|
|
||||||
|
namespace Microsoft.PowerShell.EditorServices.Protocol.LanguageServer
|
||||||
|
{
|
||||||
|
public enum SymbolKind
|
||||||
|
{
|
||||||
|
File = 1,
|
||||||
|
Module = 2,
|
||||||
|
Namespace = 3,
|
||||||
|
Package = 4,
|
||||||
|
Class = 5,
|
||||||
|
Method = 6,
|
||||||
|
Property = 7,
|
||||||
|
Field = 8,
|
||||||
|
Constructor = 9,
|
||||||
|
Enum = 10,
|
||||||
|
Interface = 11,
|
||||||
|
Function = 12,
|
||||||
|
Variable = 13,
|
||||||
|
Constant = 14,
|
||||||
|
String = 15,
|
||||||
|
Number = 16,
|
||||||
|
Boolean = 17,
|
||||||
|
Array = 18,
|
||||||
|
}
|
||||||
|
|
||||||
|
public class SymbolInformation
|
||||||
|
{
|
||||||
|
public string Name { get; set; }
|
||||||
|
|
||||||
|
public SymbolKind Kind { get; set; }
|
||||||
|
|
||||||
|
public Location Location { get; set; }
|
||||||
|
|
||||||
|
public string ContainerName { get; set;}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class DocumentSymbolRequest
|
||||||
|
{
|
||||||
|
public static readonly
|
||||||
|
RequestType<TextDocumentIdentifier, SymbolInformation[]> Type =
|
||||||
|
RequestType<TextDocumentIdentifier, SymbolInformation[]>.Create("textDocument/documentSymbol");
|
||||||
|
}
|
||||||
|
|
||||||
|
public class WorkspaceSymbolRequest
|
||||||
|
{
|
||||||
|
public static readonly
|
||||||
|
RequestType<WorkspaceSymbolParams, SymbolInformation[]> Type =
|
||||||
|
RequestType<WorkspaceSymbolParams, SymbolInformation[]>.Create("workspace/symbol");
|
||||||
|
}
|
||||||
|
|
||||||
|
public class WorkspaceSymbolParams
|
||||||
|
{
|
||||||
|
public string Query { get; set;}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
81
ServiceHost/MessageProtocol/Channel/ChannelBase.cs
Normal file
81
ServiceHost/MessageProtocol/Channel/ChannelBase.cs
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
//
|
||||||
|
// 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.MessageProtocol.Serializers;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol.Channel
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Defines a base implementation for servers and their clients over a
|
||||||
|
/// single kind of communication channel.
|
||||||
|
/// </summary>
|
||||||
|
public abstract class ChannelBase
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a boolean that is true if the channel is connected or false if not.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsConnected { get; protected set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the MessageReader for reading messages from the channel.
|
||||||
|
/// </summary>
|
||||||
|
public MessageReader MessageReader { get; protected set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the MessageWriter for writing messages to the channel.
|
||||||
|
/// </summary>
|
||||||
|
public MessageWriter MessageWriter { get; protected set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Starts the channel and initializes the MessageDispatcher.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="messageProtocolType">The type of message protocol used by the channel.</param>
|
||||||
|
public void Start(MessageProtocolType messageProtocolType)
|
||||||
|
{
|
||||||
|
IMessageSerializer messageSerializer = null;
|
||||||
|
if (messageProtocolType == MessageProtocolType.LanguageServer)
|
||||||
|
{
|
||||||
|
messageSerializer = new JsonRpcMessageSerializer();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
messageSerializer = new V8MessageSerializer();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.Initialize(messageSerializer);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns a Task that allows the consumer of the ChannelBase
|
||||||
|
/// implementation to wait until a connection has been made to
|
||||||
|
/// the opposite endpoint whether it's a client or server.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>A Task to be awaited until a connection is made.</returns>
|
||||||
|
public abstract Task WaitForConnection();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Stops the channel.
|
||||||
|
/// </summary>
|
||||||
|
public void Stop()
|
||||||
|
{
|
||||||
|
this.Shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A method to be implemented by subclasses to handle the
|
||||||
|
/// actual initialization of the channel and the creation and
|
||||||
|
/// assignment of the MessageReader and MessageWriter properties.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="messageSerializer">The IMessageSerializer to use for message serialization.</param>
|
||||||
|
protected abstract void Initialize(IMessageSerializer messageSerializer);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A method to be implemented by subclasses to handle shutdown
|
||||||
|
/// of the channel once Stop is called.
|
||||||
|
/// </summary>
|
||||||
|
protected abstract void Shutdown();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
//
|
||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
//
|
||||||
|
|
||||||
|
#if false
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.IO.Pipes;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol.Channel
|
||||||
|
{
|
||||||
|
public class NamedPipeClientChannel : ChannelBase
|
||||||
|
{
|
||||||
|
private string pipeName;
|
||||||
|
private bool isClientConnected;
|
||||||
|
private NamedPipeClientStream pipeClient;
|
||||||
|
|
||||||
|
public NamedPipeClientChannel(string pipeName)
|
||||||
|
{
|
||||||
|
this.pipeName = pipeName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task WaitForConnection()
|
||||||
|
{
|
||||||
|
#if NanoServer
|
||||||
|
await this.pipeClient.ConnectAsync();
|
||||||
|
#else
|
||||||
|
this.IsConnected = false;
|
||||||
|
|
||||||
|
while (!this.IsConnected)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Wait for 500 milliseconds so that we don't tie up the thread
|
||||||
|
this.pipeClient.Connect(500);
|
||||||
|
this.IsConnected = this.pipeClient.IsConnected;
|
||||||
|
}
|
||||||
|
catch (TimeoutException)
|
||||||
|
{
|
||||||
|
// Connect timed out, wait and try again
|
||||||
|
await Task.Delay(1000);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// If we've reached this point, we're connected
|
||||||
|
this.IsConnected = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Initialize(IMessageSerializer messageSerializer)
|
||||||
|
{
|
||||||
|
this.pipeClient =
|
||||||
|
new NamedPipeClientStream(
|
||||||
|
".",
|
||||||
|
this.pipeName,
|
||||||
|
PipeDirection.InOut,
|
||||||
|
PipeOptions.Asynchronous);
|
||||||
|
|
||||||
|
this.MessageReader =
|
||||||
|
new MessageReader(
|
||||||
|
this.pipeClient,
|
||||||
|
messageSerializer);
|
||||||
|
|
||||||
|
this.MessageWriter =
|
||||||
|
new MessageWriter(
|
||||||
|
this.pipeClient,
|
||||||
|
messageSerializer);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Shutdown()
|
||||||
|
{
|
||||||
|
if (this.pipeClient != null)
|
||||||
|
{
|
||||||
|
this.pipeClient.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
//
|
||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
//
|
||||||
|
|
||||||
|
#if false
|
||||||
|
using System;
|
||||||
|
using System.IO.Pipes;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol.Channel
|
||||||
|
{
|
||||||
|
public class NamedPipeServerChannel : ChannelBase
|
||||||
|
{
|
||||||
|
private string pipeName;
|
||||||
|
private NamedPipeServerStream pipeServer;
|
||||||
|
|
||||||
|
public NamedPipeServerChannel(string pipeName)
|
||||||
|
{
|
||||||
|
this.pipeName = pipeName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task WaitForConnection()
|
||||||
|
{
|
||||||
|
#if NanoServer
|
||||||
|
await this.pipeServer.WaitForConnectionAsync();
|
||||||
|
#else
|
||||||
|
await Task.Factory.FromAsync(this.pipeServer.BeginWaitForConnection, this.pipeServer.EndWaitForConnection, null);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
this.IsConnected = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Initialize(IMessageSerializer messageSerializer)
|
||||||
|
{
|
||||||
|
this.pipeServer =
|
||||||
|
new NamedPipeServerStream(
|
||||||
|
pipeName,
|
||||||
|
PipeDirection.InOut,
|
||||||
|
1,
|
||||||
|
PipeTransmissionMode.Byte,
|
||||||
|
PipeOptions.Asynchronous);
|
||||||
|
|
||||||
|
this.MessageReader =
|
||||||
|
new MessageReader(
|
||||||
|
this.pipeServer,
|
||||||
|
messageSerializer);
|
||||||
|
|
||||||
|
this.MessageWriter =
|
||||||
|
new MessageWriter(
|
||||||
|
this.pipeServer,
|
||||||
|
messageSerializer);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Shutdown()
|
||||||
|
{
|
||||||
|
if (this.pipeServer != null)
|
||||||
|
{
|
||||||
|
this.pipeServer.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
127
ServiceHost/MessageProtocol/Channel/StdioClientChannel.cs
Normal file
127
ServiceHost/MessageProtocol/Channel/StdioClientChannel.cs
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
//
|
||||||
|
// 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.MessageProtocol.Serializers;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.IO;
|
||||||
|
using System.Text;
|
||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol.Channel
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Provides a client implementation for the standard I/O channel.
|
||||||
|
/// Launches the server process and then attaches to its console
|
||||||
|
/// streams.
|
||||||
|
/// </summary>
|
||||||
|
public class StdioClientChannel : ChannelBase
|
||||||
|
{
|
||||||
|
private string serviceProcessPath;
|
||||||
|
private string serviceProcessArguments;
|
||||||
|
|
||||||
|
private Stream inputStream;
|
||||||
|
private Stream outputStream;
|
||||||
|
private Process serviceProcess;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the process ID of the server process.
|
||||||
|
/// </summary>
|
||||||
|
public int ProcessId { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes an instance of the StdioClient.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="serverProcessPath">The full path to the server process executable.</param>
|
||||||
|
/// <param name="serverProcessArguments">Optional arguments to pass to the service process executable.</param>
|
||||||
|
public StdioClientChannel(
|
||||||
|
string serverProcessPath,
|
||||||
|
params string[] serverProcessArguments)
|
||||||
|
{
|
||||||
|
this.serviceProcessPath = serverProcessPath;
|
||||||
|
|
||||||
|
if (serverProcessArguments != null)
|
||||||
|
{
|
||||||
|
this.serviceProcessArguments =
|
||||||
|
string.Join(
|
||||||
|
" ",
|
||||||
|
serverProcessArguments);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Initialize(IMessageSerializer messageSerializer)
|
||||||
|
{
|
||||||
|
this.serviceProcess = new Process
|
||||||
|
{
|
||||||
|
StartInfo = new ProcessStartInfo
|
||||||
|
{
|
||||||
|
FileName = this.serviceProcessPath,
|
||||||
|
Arguments = this.serviceProcessArguments,
|
||||||
|
CreateNoWindow = true,
|
||||||
|
UseShellExecute = false,
|
||||||
|
RedirectStandardInput = true,
|
||||||
|
RedirectStandardOutput = true,
|
||||||
|
RedirectStandardError = true,
|
||||||
|
StandardOutputEncoding = Encoding.UTF8,
|
||||||
|
},
|
||||||
|
EnableRaisingEvents = true,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Start the process
|
||||||
|
this.serviceProcess.Start();
|
||||||
|
this.ProcessId = this.serviceProcess.Id;
|
||||||
|
|
||||||
|
// Open the standard input/output streams
|
||||||
|
this.inputStream = this.serviceProcess.StandardOutput.BaseStream;
|
||||||
|
this.outputStream = this.serviceProcess.StandardInput.BaseStream;
|
||||||
|
|
||||||
|
// Set up the message reader and writer
|
||||||
|
this.MessageReader =
|
||||||
|
new MessageReader(
|
||||||
|
this.inputStream,
|
||||||
|
messageSerializer);
|
||||||
|
|
||||||
|
this.MessageWriter =
|
||||||
|
new MessageWriter(
|
||||||
|
this.outputStream,
|
||||||
|
messageSerializer);
|
||||||
|
|
||||||
|
this.IsConnected = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Task WaitForConnection()
|
||||||
|
{
|
||||||
|
// We're always connected immediately in the stdio channel
|
||||||
|
return Task.FromResult(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Shutdown()
|
||||||
|
{
|
||||||
|
if (this.inputStream != null)
|
||||||
|
{
|
||||||
|
this.inputStream.Dispose();
|
||||||
|
this.inputStream = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.outputStream != null)
|
||||||
|
{
|
||||||
|
this.outputStream.Dispose();
|
||||||
|
this.outputStream = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.MessageReader != null)
|
||||||
|
{
|
||||||
|
this.MessageReader = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.MessageWriter != null)
|
||||||
|
{
|
||||||
|
this.MessageWriter = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.serviceProcess.Kill();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
62
ServiceHost/MessageProtocol/Channel/StdioServerChannel.cs
Normal file
62
ServiceHost/MessageProtocol/Channel/StdioServerChannel.cs
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
//
|
||||||
|
// 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.MessageProtocol.Serializers;
|
||||||
|
using System.IO;
|
||||||
|
using System.Text;
|
||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol.Channel
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Provides a server implementation for the standard I/O channel.
|
||||||
|
/// When started in a process, attaches to the console I/O streams
|
||||||
|
/// to communicate with the client that launched the process.
|
||||||
|
/// </summary>
|
||||||
|
public class StdioServerChannel : ChannelBase
|
||||||
|
{
|
||||||
|
private Stream inputStream;
|
||||||
|
private Stream outputStream;
|
||||||
|
|
||||||
|
protected override void Initialize(IMessageSerializer messageSerializer)
|
||||||
|
{
|
||||||
|
#if !NanoServer
|
||||||
|
// Ensure that the console is using UTF-8 encoding
|
||||||
|
System.Console.InputEncoding = Encoding.UTF8;
|
||||||
|
System.Console.OutputEncoding = Encoding.UTF8;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Open the standard input/output streams
|
||||||
|
this.inputStream = System.Console.OpenStandardInput();
|
||||||
|
this.outputStream = System.Console.OpenStandardOutput();
|
||||||
|
|
||||||
|
// Set up the reader and writer
|
||||||
|
this.MessageReader =
|
||||||
|
new MessageReader(
|
||||||
|
this.inputStream,
|
||||||
|
messageSerializer);
|
||||||
|
|
||||||
|
this.MessageWriter =
|
||||||
|
new MessageWriter(
|
||||||
|
this.outputStream,
|
||||||
|
messageSerializer);
|
||||||
|
|
||||||
|
this.IsConnected = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Task WaitForConnection()
|
||||||
|
{
|
||||||
|
// We're always connected immediately in the stdio channel
|
||||||
|
return Task.FromResult(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Shutdown()
|
||||||
|
{
|
||||||
|
// No default implementation needed, streams will be
|
||||||
|
// disposed on process shutdown.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
31
ServiceHost/MessageProtocol/Constants.cs
Normal file
31
ServiceHost/MessageProtocol/Constants.cs
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
//
|
||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
//
|
||||||
|
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using Newtonsoft.Json.Converters;
|
||||||
|
using Newtonsoft.Json.Serialization;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol
|
||||||
|
{
|
||||||
|
public static class Constants
|
||||||
|
{
|
||||||
|
public const string ContentLengthFormatString = "Content-Length: {0}\r\n\r\n";
|
||||||
|
public static readonly JsonSerializerSettings JsonSerializerSettings;
|
||||||
|
|
||||||
|
static Constants()
|
||||||
|
{
|
||||||
|
JsonSerializerSettings = new JsonSerializerSettings();
|
||||||
|
|
||||||
|
// Camel case all object properties
|
||||||
|
JsonSerializerSettings.ContractResolver =
|
||||||
|
new CamelCasePropertyNamesContractResolver();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
34
ServiceHost/MessageProtocol/EventContext.cs
Normal file
34
ServiceHost/MessageProtocol/EventContext.cs
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
//
|
||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
//
|
||||||
|
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Provides context for a received event so that handlers
|
||||||
|
/// can write events back to the channel.
|
||||||
|
/// </summary>
|
||||||
|
public class EventContext
|
||||||
|
{
|
||||||
|
private MessageWriter messageWriter;
|
||||||
|
|
||||||
|
public EventContext(MessageWriter messageWriter)
|
||||||
|
{
|
||||||
|
this.messageWriter = messageWriter;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task SendEvent<TParams>(
|
||||||
|
EventType<TParams> eventType,
|
||||||
|
TParams eventParams)
|
||||||
|
{
|
||||||
|
await this.messageWriter.WriteEvent(
|
||||||
|
eventType,
|
||||||
|
eventParams);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
33
ServiceHost/MessageProtocol/EventType.cs
Normal file
33
ServiceHost/MessageProtocol/EventType.cs
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
//
|
||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
//
|
||||||
|
|
||||||
|
namespace Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Defines an event type with a particular method name.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TParams">The parameter type for this event.</typeparam>
|
||||||
|
public class EventType<TParams>
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the method name for the event type.
|
||||||
|
/// </summary>
|
||||||
|
public string MethodName { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates an EventType instance with the given parameter type and method name.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="methodName">The method name of the event.</param>
|
||||||
|
/// <returns>A new EventType instance for the defined type.</returns>
|
||||||
|
public static EventType<TParams> Create(string methodName)
|
||||||
|
{
|
||||||
|
return new EventType<TParams>()
|
||||||
|
{
|
||||||
|
MethodName = methodName
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
22
ServiceHost/MessageProtocol/IMessageSender.cs
Normal file
22
ServiceHost/MessageProtocol/IMessageSender.cs
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
//
|
||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
//
|
||||||
|
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol
|
||||||
|
{
|
||||||
|
internal interface IMessageSender
|
||||||
|
{
|
||||||
|
Task SendEvent<TParams>(
|
||||||
|
EventType<TParams> eventType,
|
||||||
|
TParams eventParams);
|
||||||
|
|
||||||
|
Task<TResult> SendRequest<TParams, TResult>(
|
||||||
|
RequestType<TParams, TResult> requestType,
|
||||||
|
TParams requestParams,
|
||||||
|
bool waitForResponse);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
30
ServiceHost/MessageProtocol/IMessageSerializer.cs
Normal file
30
ServiceHost/MessageProtocol/IMessageSerializer.cs
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
//
|
||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
//
|
||||||
|
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
|
||||||
|
namespace Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Defines a common interface for message serializers.
|
||||||
|
/// </summary>
|
||||||
|
public interface IMessageSerializer
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Serializes a Message to a JObject.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="message">The message to be serialized.</param>
|
||||||
|
/// <returns>A JObject which contains the JSON representation of the message.</returns>
|
||||||
|
JObject SerializeMessage(Message message);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Deserializes a JObject to a Messsage.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="messageJson">The JObject containing the JSON representation of the message.</param>
|
||||||
|
/// <returns>The Message that was represented by the JObject.</returns>
|
||||||
|
Message DeserializeMessage(JObject messageJson);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
136
ServiceHost/MessageProtocol/Message.cs
Normal file
136
ServiceHost/MessageProtocol/Message.cs
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
//
|
||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
//
|
||||||
|
|
||||||
|
using System.Diagnostics;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
|
||||||
|
namespace Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Defines all possible message types.
|
||||||
|
/// </summary>
|
||||||
|
public enum MessageType
|
||||||
|
{
|
||||||
|
Unknown,
|
||||||
|
Request,
|
||||||
|
Response,
|
||||||
|
Event
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Provides common details for protocol messages of any format.
|
||||||
|
/// </summary>
|
||||||
|
[DebuggerDisplay("MessageType = {MessageType.ToString()}, Method = {Method}, Id = {Id}")]
|
||||||
|
public class Message
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the message type.
|
||||||
|
/// </summary>
|
||||||
|
public MessageType MessageType { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the message's sequence ID.
|
||||||
|
/// </summary>
|
||||||
|
public string Id { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the message's method/command name.
|
||||||
|
/// </summary>
|
||||||
|
public string Method { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a JToken containing the contents of the message.
|
||||||
|
/// </summary>
|
||||||
|
public JToken Contents { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a JToken containing error details.
|
||||||
|
/// </summary>
|
||||||
|
public JToken Error { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a message with an Unknown type.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>A message with Unknown type.</returns>
|
||||||
|
public static Message Unknown()
|
||||||
|
{
|
||||||
|
return new Message
|
||||||
|
{
|
||||||
|
MessageType = MessageType.Unknown
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a message with a Request type.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="id">The sequence ID of the request.</param>
|
||||||
|
/// <param name="method">The method name of the request.</param>
|
||||||
|
/// <param name="contents">The contents of the request.</param>
|
||||||
|
/// <returns>A message with a Request type.</returns>
|
||||||
|
public static Message Request(string id, string method, JToken contents)
|
||||||
|
{
|
||||||
|
return new Message
|
||||||
|
{
|
||||||
|
MessageType = MessageType.Request,
|
||||||
|
Id = id,
|
||||||
|
Method = method,
|
||||||
|
Contents = contents
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a message with a Response type.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="id">The sequence ID of the original request.</param>
|
||||||
|
/// <param name="method">The method name of the original request.</param>
|
||||||
|
/// <param name="contents">The contents of the response.</param>
|
||||||
|
/// <returns>A message with a Response type.</returns>
|
||||||
|
public static Message Response(string id, string method, JToken contents)
|
||||||
|
{
|
||||||
|
return new Message
|
||||||
|
{
|
||||||
|
MessageType = MessageType.Response,
|
||||||
|
Id = id,
|
||||||
|
Method = method,
|
||||||
|
Contents = contents
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a message with a Response type and error details.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="id">The sequence ID of the original request.</param>
|
||||||
|
/// <param name="method">The method name of the original request.</param>
|
||||||
|
/// <param name="error">The error details of the response.</param>
|
||||||
|
/// <returns>A message with a Response type and error details.</returns>
|
||||||
|
public static Message ResponseError(string id, string method, JToken error)
|
||||||
|
{
|
||||||
|
return new Message
|
||||||
|
{
|
||||||
|
MessageType = MessageType.Response,
|
||||||
|
Id = id,
|
||||||
|
Method = method,
|
||||||
|
Error = error
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a message with an Event type.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="method">The method name of the event.</param>
|
||||||
|
/// <param name="contents">The contents of the event.</param>
|
||||||
|
/// <returns>A message with an Event type.</returns>
|
||||||
|
public static Message Event(string method, JToken contents)
|
||||||
|
{
|
||||||
|
return new Message
|
||||||
|
{
|
||||||
|
MessageType = MessageType.Event,
|
||||||
|
Method = method,
|
||||||
|
Contents = contents
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
325
ServiceHost/MessageProtocol/MessageDispatcher.cs
Normal file
325
ServiceHost/MessageProtocol/MessageDispatcher.cs
Normal file
@@ -0,0 +1,325 @@
|
|||||||
|
//
|
||||||
|
// 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.MessageProtocol.Channel;
|
||||||
|
using Microsoft.PowerShell.EditorServices.Utility;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol
|
||||||
|
{
|
||||||
|
public class MessageDispatcher
|
||||||
|
{
|
||||||
|
#region Fields
|
||||||
|
|
||||||
|
private ChannelBase protocolChannel;
|
||||||
|
// private AsyncQueue<Message> messagesToWrite;
|
||||||
|
private AsyncContextThread messageLoopThread;
|
||||||
|
|
||||||
|
private Dictionary<string, Func<Message, MessageWriter, Task>> requestHandlers =
|
||||||
|
new Dictionary<string, Func<Message, MessageWriter, Task>>();
|
||||||
|
|
||||||
|
private Dictionary<string, Func<Message, MessageWriter, Task>> eventHandlers =
|
||||||
|
new Dictionary<string, Func<Message, MessageWriter, Task>>();
|
||||||
|
|
||||||
|
private Action<Message> responseHandler;
|
||||||
|
|
||||||
|
private CancellationTokenSource messageLoopCancellationToken =
|
||||||
|
new CancellationTokenSource();
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Properties
|
||||||
|
|
||||||
|
public SynchronizationContext SynchronizationContext { get; private set; }
|
||||||
|
|
||||||
|
public bool InMessageLoopThread
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
// We're in the same thread as the message loop if the
|
||||||
|
// current synchronization context equals the one we
|
||||||
|
// know.
|
||||||
|
return SynchronizationContext.Current == this.SynchronizationContext;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected MessageReader MessageReader { get; private set; }
|
||||||
|
|
||||||
|
protected MessageWriter MessageWriter { get; private set; }
|
||||||
|
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Constructors
|
||||||
|
|
||||||
|
public MessageDispatcher(ChannelBase protocolChannel)
|
||||||
|
{
|
||||||
|
this.protocolChannel = protocolChannel;
|
||||||
|
this.MessageReader = protocolChannel.MessageReader;
|
||||||
|
this.MessageWriter = protocolChannel.MessageWriter;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Public Methods
|
||||||
|
|
||||||
|
public void Start()
|
||||||
|
{
|
||||||
|
// Start the main message loop thread. The Task is
|
||||||
|
// not explicitly awaited because it is running on
|
||||||
|
// an independent background thread.
|
||||||
|
this.messageLoopThread = new AsyncContextThread("Message Dispatcher");
|
||||||
|
this.messageLoopThread
|
||||||
|
.Run(() => this.ListenForMessages(this.messageLoopCancellationToken.Token))
|
||||||
|
.ContinueWith(this.OnListenTaskCompleted);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Stop()
|
||||||
|
{
|
||||||
|
// Stop the message loop thread
|
||||||
|
if (this.messageLoopThread != null)
|
||||||
|
{
|
||||||
|
this.messageLoopCancellationToken.Cancel();
|
||||||
|
this.messageLoopThread.Stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetRequestHandler<TParams, TResult>(
|
||||||
|
RequestType<TParams, TResult> requestType,
|
||||||
|
Func<TParams, RequestContext<TResult>, Task> requestHandler)
|
||||||
|
{
|
||||||
|
this.SetRequestHandler(
|
||||||
|
requestType,
|
||||||
|
requestHandler,
|
||||||
|
false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetRequestHandler<TParams, TResult>(
|
||||||
|
RequestType<TParams, TResult> requestType,
|
||||||
|
Func<TParams, RequestContext<TResult>, Task> requestHandler,
|
||||||
|
bool overrideExisting)
|
||||||
|
{
|
||||||
|
if (overrideExisting)
|
||||||
|
{
|
||||||
|
// Remove the existing handler so a new one can be set
|
||||||
|
this.requestHandlers.Remove(requestType.MethodName);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.requestHandlers.Add(
|
||||||
|
requestType.MethodName,
|
||||||
|
(requestMessage, messageWriter) =>
|
||||||
|
{
|
||||||
|
var requestContext =
|
||||||
|
new RequestContext<TResult>(
|
||||||
|
requestMessage,
|
||||||
|
messageWriter);
|
||||||
|
|
||||||
|
TParams typedParams = default(TParams);
|
||||||
|
if (requestMessage.Contents != null)
|
||||||
|
{
|
||||||
|
// TODO: Catch parse errors!
|
||||||
|
typedParams = requestMessage.Contents.ToObject<TParams>();
|
||||||
|
}
|
||||||
|
|
||||||
|
return requestHandler(typedParams, requestContext);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetEventHandler<TParams>(
|
||||||
|
EventType<TParams> eventType,
|
||||||
|
Func<TParams, EventContext, Task> eventHandler)
|
||||||
|
{
|
||||||
|
this.SetEventHandler(
|
||||||
|
eventType,
|
||||||
|
eventHandler,
|
||||||
|
false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetEventHandler<TParams>(
|
||||||
|
EventType<TParams> eventType,
|
||||||
|
Func<TParams, EventContext, Task> eventHandler,
|
||||||
|
bool overrideExisting)
|
||||||
|
{
|
||||||
|
if (overrideExisting)
|
||||||
|
{
|
||||||
|
// Remove the existing handler so a new one can be set
|
||||||
|
this.eventHandlers.Remove(eventType.MethodName);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.eventHandlers.Add(
|
||||||
|
eventType.MethodName,
|
||||||
|
(eventMessage, messageWriter) =>
|
||||||
|
{
|
||||||
|
var eventContext = new EventContext(messageWriter);
|
||||||
|
|
||||||
|
TParams typedParams = default(TParams);
|
||||||
|
if (eventMessage.Contents != null)
|
||||||
|
{
|
||||||
|
// TODO: Catch parse errors!
|
||||||
|
typedParams = eventMessage.Contents.ToObject<TParams>();
|
||||||
|
}
|
||||||
|
|
||||||
|
return eventHandler(typedParams, eventContext);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetResponseHandler(Action<Message> responseHandler)
|
||||||
|
{
|
||||||
|
this.responseHandler = responseHandler;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Events
|
||||||
|
|
||||||
|
public event EventHandler<Exception> UnhandledException;
|
||||||
|
|
||||||
|
protected void OnUnhandledException(Exception unhandledException)
|
||||||
|
{
|
||||||
|
if (this.UnhandledException != null)
|
||||||
|
{
|
||||||
|
this.UnhandledException(this, unhandledException);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Private Methods
|
||||||
|
|
||||||
|
private async Task ListenForMessages(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
this.SynchronizationContext = SynchronizationContext.Current;
|
||||||
|
|
||||||
|
// Run the message loop
|
||||||
|
bool isRunning = true;
|
||||||
|
while (isRunning && !cancellationToken.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
Message newMessage = null;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Read a message from the channel
|
||||||
|
newMessage = await this.MessageReader.ReadMessage();
|
||||||
|
}
|
||||||
|
catch (MessageParseException e)
|
||||||
|
{
|
||||||
|
// TODO: Write an error response
|
||||||
|
|
||||||
|
Logger.Write(
|
||||||
|
LogLevel.Error,
|
||||||
|
"Could not parse a message that was received:\r\n\r\n" +
|
||||||
|
e.ToString());
|
||||||
|
|
||||||
|
// Continue the loop
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
catch (EndOfStreamException)
|
||||||
|
{
|
||||||
|
// The stream has ended, end the message loop
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
var b = e.Message;
|
||||||
|
newMessage = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The message could be null if there was an error parsing the
|
||||||
|
// previous message. In this case, do not try to dispatch it.
|
||||||
|
if (newMessage != null)
|
||||||
|
{
|
||||||
|
// Process the message
|
||||||
|
await this.DispatchMessage(
|
||||||
|
newMessage,
|
||||||
|
this.MessageWriter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async Task DispatchMessage(
|
||||||
|
Message messageToDispatch,
|
||||||
|
MessageWriter messageWriter)
|
||||||
|
{
|
||||||
|
Task handlerToAwait = null;
|
||||||
|
|
||||||
|
if (messageToDispatch.MessageType == MessageType.Request)
|
||||||
|
{
|
||||||
|
Func<Message, MessageWriter, Task> requestHandler = null;
|
||||||
|
if (this.requestHandlers.TryGetValue(messageToDispatch.Method, out requestHandler))
|
||||||
|
{
|
||||||
|
handlerToAwait = requestHandler(messageToDispatch, messageWriter);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// TODO: Message not supported error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (messageToDispatch.MessageType == MessageType.Response)
|
||||||
|
{
|
||||||
|
if (this.responseHandler != null)
|
||||||
|
{
|
||||||
|
this.responseHandler(messageToDispatch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (messageToDispatch.MessageType == MessageType.Event)
|
||||||
|
{
|
||||||
|
Func<Message, MessageWriter, Task> eventHandler = null;
|
||||||
|
if (this.eventHandlers.TryGetValue(messageToDispatch.Method, out eventHandler))
|
||||||
|
{
|
||||||
|
handlerToAwait = eventHandler(messageToDispatch, messageWriter);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// TODO: Message not supported error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// TODO: Return message not supported
|
||||||
|
}
|
||||||
|
|
||||||
|
if (handlerToAwait != null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await handlerToAwait;
|
||||||
|
}
|
||||||
|
catch (TaskCanceledException)
|
||||||
|
{
|
||||||
|
// Some tasks may be cancelled due to legitimate
|
||||||
|
// timeouts so don't let those exceptions go higher.
|
||||||
|
}
|
||||||
|
catch (AggregateException e)
|
||||||
|
{
|
||||||
|
if (!(e.InnerExceptions[0] is TaskCanceledException))
|
||||||
|
{
|
||||||
|
// Cancelled tasks aren't a problem, so rethrow
|
||||||
|
// anything that isn't a TaskCanceledException
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnListenTaskCompleted(Task listenTask)
|
||||||
|
{
|
||||||
|
if (listenTask.IsFaulted)
|
||||||
|
{
|
||||||
|
this.OnUnhandledException(listenTask.Exception);
|
||||||
|
}
|
||||||
|
else if (listenTask.IsCompleted || listenTask.IsCanceled)
|
||||||
|
{
|
||||||
|
// TODO: Dispose of anything?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
23
ServiceHost/MessageProtocol/MessageParseException.cs
Normal file
23
ServiceHost/MessageProtocol/MessageParseException.cs
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
//
|
||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
//
|
||||||
|
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol
|
||||||
|
{
|
||||||
|
public class MessageParseException : Exception
|
||||||
|
{
|
||||||
|
public string OriginalMessageText { get; private set; }
|
||||||
|
|
||||||
|
public MessageParseException(
|
||||||
|
string originalMessageText,
|
||||||
|
string errorMessage,
|
||||||
|
params object[] errorMessageArgs)
|
||||||
|
: base(string.Format(errorMessage, errorMessageArgs))
|
||||||
|
{
|
||||||
|
this.OriginalMessageText = originalMessageText;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
23
ServiceHost/MessageProtocol/MessageProtocolType.cs
Normal file
23
ServiceHost/MessageProtocol/MessageProtocolType.cs
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
//
|
||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
//
|
||||||
|
|
||||||
|
namespace Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Defines the possible message protocol types.
|
||||||
|
/// </summary>
|
||||||
|
public enum MessageProtocolType
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Identifies the language server message protocol.
|
||||||
|
/// </summary>
|
||||||
|
LanguageServer,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Identifies the debug adapter message protocol.
|
||||||
|
/// </summary>
|
||||||
|
DebugAdapter
|
||||||
|
}
|
||||||
|
}
|
||||||
262
ServiceHost/MessageProtocol/MessageReader.cs
Normal file
262
ServiceHost/MessageProtocol/MessageReader.cs
Normal file
@@ -0,0 +1,262 @@
|
|||||||
|
//
|
||||||
|
// 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.Utility;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol
|
||||||
|
{
|
||||||
|
public class MessageReader
|
||||||
|
{
|
||||||
|
#region Private Fields
|
||||||
|
|
||||||
|
public const int DefaultBufferSize = 8192;
|
||||||
|
public const double BufferResizeTrigger = 0.25;
|
||||||
|
|
||||||
|
private const int CR = 0x0D;
|
||||||
|
private const int LF = 0x0A;
|
||||||
|
private static string[] NewLineDelimiters = new string[] { Environment.NewLine };
|
||||||
|
|
||||||
|
private Stream inputStream;
|
||||||
|
private IMessageSerializer messageSerializer;
|
||||||
|
private Encoding messageEncoding;
|
||||||
|
|
||||||
|
private ReadState readState;
|
||||||
|
private bool needsMoreData = true;
|
||||||
|
private int readOffset;
|
||||||
|
private int bufferEndOffset;
|
||||||
|
private byte[] messageBuffer = new byte[DefaultBufferSize];
|
||||||
|
|
||||||
|
private int expectedContentLength;
|
||||||
|
private Dictionary<string, string> messageHeaders;
|
||||||
|
|
||||||
|
enum ReadState
|
||||||
|
{
|
||||||
|
Headers,
|
||||||
|
Content
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Constructors
|
||||||
|
|
||||||
|
public MessageReader(
|
||||||
|
Stream inputStream,
|
||||||
|
IMessageSerializer messageSerializer,
|
||||||
|
Encoding messageEncoding = null)
|
||||||
|
{
|
||||||
|
Validate.IsNotNull("streamReader", inputStream);
|
||||||
|
Validate.IsNotNull("messageSerializer", messageSerializer);
|
||||||
|
|
||||||
|
this.inputStream = inputStream;
|
||||||
|
this.messageSerializer = messageSerializer;
|
||||||
|
|
||||||
|
this.messageEncoding = messageEncoding;
|
||||||
|
if (messageEncoding == null)
|
||||||
|
{
|
||||||
|
this.messageEncoding = Encoding.UTF8;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.messageBuffer = new byte[DefaultBufferSize];
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Public Methods
|
||||||
|
|
||||||
|
public async Task<Message> ReadMessage()
|
||||||
|
{
|
||||||
|
string messageContent = null;
|
||||||
|
|
||||||
|
// Do we need to read more data or can we process the existing buffer?
|
||||||
|
while (!this.needsMoreData || await this.ReadNextChunk())
|
||||||
|
{
|
||||||
|
// Clear the flag since we should have what we need now
|
||||||
|
this.needsMoreData = false;
|
||||||
|
|
||||||
|
// Do we need to look for message headers?
|
||||||
|
if (this.readState == ReadState.Headers &&
|
||||||
|
!this.TryReadMessageHeaders())
|
||||||
|
{
|
||||||
|
// If we don't have enough data to read headers yet, keep reading
|
||||||
|
this.needsMoreData = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do we need to look for message content?
|
||||||
|
if (this.readState == ReadState.Content &&
|
||||||
|
!this.TryReadMessageContent(out messageContent))
|
||||||
|
{
|
||||||
|
// If we don't have enough data yet to construct the content, keep reading
|
||||||
|
this.needsMoreData = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We've read a message now, break out of the loop
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the JObject for the JSON content
|
||||||
|
JObject messageObject = JObject.Parse(messageContent);
|
||||||
|
|
||||||
|
// Load the message
|
||||||
|
Logger.Write(
|
||||||
|
LogLevel.Verbose,
|
||||||
|
string.Format(
|
||||||
|
"READ MESSAGE:\r\n\r\n{0}",
|
||||||
|
messageObject.ToString(Formatting.Indented)));
|
||||||
|
|
||||||
|
// Return the parsed message
|
||||||
|
return this.messageSerializer.DeserializeMessage(messageObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Private Methods
|
||||||
|
|
||||||
|
private async Task<bool> ReadNextChunk()
|
||||||
|
{
|
||||||
|
// Do we need to resize the buffer? See if less than 1/4 of the space is left.
|
||||||
|
if (((double)(this.messageBuffer.Length - this.bufferEndOffset) / this.messageBuffer.Length) < 0.25)
|
||||||
|
{
|
||||||
|
// Double the size of the buffer
|
||||||
|
Array.Resize(
|
||||||
|
ref this.messageBuffer,
|
||||||
|
this.messageBuffer.Length * 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read the next chunk into the message buffer
|
||||||
|
int readLength =
|
||||||
|
await this.inputStream.ReadAsync(
|
||||||
|
this.messageBuffer,
|
||||||
|
this.bufferEndOffset,
|
||||||
|
this.messageBuffer.Length - this.bufferEndOffset);
|
||||||
|
|
||||||
|
this.bufferEndOffset += readLength;
|
||||||
|
|
||||||
|
if (readLength == 0)
|
||||||
|
{
|
||||||
|
// If ReadAsync returns 0 then it means that the stream was
|
||||||
|
// closed unexpectedly (usually due to the client application
|
||||||
|
// ending suddenly). For now, just terminate the language
|
||||||
|
// server immediately.
|
||||||
|
// TODO: Provide a more graceful shutdown path
|
||||||
|
throw new EndOfStreamException(
|
||||||
|
"MessageReader's input stream ended unexpectedly, terminating.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool TryReadMessageHeaders()
|
||||||
|
{
|
||||||
|
int scanOffset = this.readOffset;
|
||||||
|
|
||||||
|
// Scan for the final double-newline that marks the
|
||||||
|
// end of the header lines
|
||||||
|
while (scanOffset + 3 < this.bufferEndOffset &&
|
||||||
|
(this.messageBuffer[scanOffset] != CR ||
|
||||||
|
this.messageBuffer[scanOffset + 1] != LF ||
|
||||||
|
this.messageBuffer[scanOffset + 2] != CR ||
|
||||||
|
this.messageBuffer[scanOffset + 3] != LF))
|
||||||
|
{
|
||||||
|
scanOffset++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// No header or body separator found (e.g CRLFCRLF)
|
||||||
|
if (scanOffset + 3 >= this.bufferEndOffset)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.messageHeaders = new Dictionary<string, string>();
|
||||||
|
|
||||||
|
var headers =
|
||||||
|
Encoding.ASCII
|
||||||
|
.GetString(this.messageBuffer, this.readOffset, scanOffset)
|
||||||
|
.Split(NewLineDelimiters, StringSplitOptions.RemoveEmptyEntries);
|
||||||
|
|
||||||
|
// Read each header and store it in the dictionary
|
||||||
|
foreach (var header in headers)
|
||||||
|
{
|
||||||
|
int currentLength = header.IndexOf(':');
|
||||||
|
if (currentLength == -1)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Message header must separate key and value using :");
|
||||||
|
}
|
||||||
|
|
||||||
|
var key = header.Substring(0, currentLength);
|
||||||
|
var value = header.Substring(currentLength + 1).Trim();
|
||||||
|
this.messageHeaders[key] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure a Content-Length header was present, otherwise it
|
||||||
|
// is a fatal error
|
||||||
|
string contentLengthString = null;
|
||||||
|
if (!this.messageHeaders.TryGetValue("Content-Length", out contentLengthString))
|
||||||
|
{
|
||||||
|
throw new MessageParseException("", "Fatal error: Content-Length header must be provided.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse the content length to an integer
|
||||||
|
if (!int.TryParse(contentLengthString, out this.expectedContentLength))
|
||||||
|
{
|
||||||
|
throw new MessageParseException("", "Fatal error: Content-Length value is not an integer.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip past the headers plus the newline characters
|
||||||
|
this.readOffset += scanOffset + 4;
|
||||||
|
|
||||||
|
// Done reading headers, now read content
|
||||||
|
this.readState = ReadState.Content;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool TryReadMessageContent(out string messageContent)
|
||||||
|
{
|
||||||
|
messageContent = null;
|
||||||
|
|
||||||
|
// Do we have enough bytes to reach the expected length?
|
||||||
|
if ((this.bufferEndOffset - this.readOffset) < this.expectedContentLength)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert the message contents to a string using the specified encoding
|
||||||
|
messageContent =
|
||||||
|
this.messageEncoding.GetString(
|
||||||
|
this.messageBuffer,
|
||||||
|
this.readOffset,
|
||||||
|
this.expectedContentLength);
|
||||||
|
|
||||||
|
// Move the remaining bytes to the front of the buffer for the next message
|
||||||
|
var remainingByteCount = this.bufferEndOffset - (this.expectedContentLength + this.readOffset);
|
||||||
|
Buffer.BlockCopy(
|
||||||
|
this.messageBuffer,
|
||||||
|
this.expectedContentLength + this.readOffset,
|
||||||
|
this.messageBuffer,
|
||||||
|
0,
|
||||||
|
remainingByteCount);
|
||||||
|
|
||||||
|
// Reset the offsets for the next read
|
||||||
|
this.readOffset = 0;
|
||||||
|
this.bufferEndOffset = remainingByteCount;
|
||||||
|
|
||||||
|
// Done reading content, now look for headers
|
||||||
|
this.readState = ReadState.Headers;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
141
ServiceHost/MessageProtocol/MessageWriter.cs
Normal file
141
ServiceHost/MessageProtocol/MessageWriter.cs
Normal file
@@ -0,0 +1,141 @@
|
|||||||
|
//
|
||||||
|
// 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.Utility;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
using System.IO;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol
|
||||||
|
{
|
||||||
|
public class MessageWriter
|
||||||
|
{
|
||||||
|
#region Private Fields
|
||||||
|
|
||||||
|
private Stream outputStream;
|
||||||
|
private IMessageSerializer messageSerializer;
|
||||||
|
private AsyncLock writeLock = new AsyncLock();
|
||||||
|
|
||||||
|
private JsonSerializer contentSerializer =
|
||||||
|
JsonSerializer.Create(
|
||||||
|
Constants.JsonSerializerSettings);
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Constructors
|
||||||
|
|
||||||
|
public MessageWriter(
|
||||||
|
Stream outputStream,
|
||||||
|
IMessageSerializer messageSerializer)
|
||||||
|
{
|
||||||
|
Validate.IsNotNull("streamWriter", outputStream);
|
||||||
|
Validate.IsNotNull("messageSerializer", messageSerializer);
|
||||||
|
|
||||||
|
this.outputStream = outputStream;
|
||||||
|
this.messageSerializer = messageSerializer;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Public Methods
|
||||||
|
|
||||||
|
// TODO: This method should be made protected or private
|
||||||
|
|
||||||
|
public async Task WriteMessage(Message messageToWrite)
|
||||||
|
{
|
||||||
|
Validate.IsNotNull("messageToWrite", messageToWrite);
|
||||||
|
|
||||||
|
// Serialize the message
|
||||||
|
JObject messageObject =
|
||||||
|
this.messageSerializer.SerializeMessage(
|
||||||
|
messageToWrite);
|
||||||
|
|
||||||
|
// Log the JSON representation of the message
|
||||||
|
Logger.Write(
|
||||||
|
LogLevel.Verbose,
|
||||||
|
string.Format(
|
||||||
|
"WRITE MESSAGE:\r\n\r\n{0}",
|
||||||
|
JsonConvert.SerializeObject(
|
||||||
|
messageObject,
|
||||||
|
Formatting.Indented,
|
||||||
|
Constants.JsonSerializerSettings)));
|
||||||
|
|
||||||
|
string serializedMessage =
|
||||||
|
JsonConvert.SerializeObject(
|
||||||
|
messageObject,
|
||||||
|
Constants.JsonSerializerSettings);
|
||||||
|
|
||||||
|
byte[] messageBytes = Encoding.UTF8.GetBytes(serializedMessage);
|
||||||
|
byte[] headerBytes =
|
||||||
|
Encoding.ASCII.GetBytes(
|
||||||
|
string.Format(
|
||||||
|
Constants.ContentLengthFormatString,
|
||||||
|
messageBytes.Length));
|
||||||
|
|
||||||
|
// Make sure only one call is writing at a time. You might be thinking
|
||||||
|
// "Why not use a normal lock?" We use an AsyncLock here so that the
|
||||||
|
// message loop doesn't get blocked while waiting for I/O to complete.
|
||||||
|
using (await this.writeLock.LockAsync())
|
||||||
|
{
|
||||||
|
// Send the message
|
||||||
|
await this.outputStream.WriteAsync(headerBytes, 0, headerBytes.Length);
|
||||||
|
await this.outputStream.WriteAsync(messageBytes, 0, messageBytes.Length);
|
||||||
|
await this.outputStream.FlushAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task WriteRequest<TParams, TResult>(
|
||||||
|
RequestType<TParams, TResult> requestType,
|
||||||
|
TParams requestParams,
|
||||||
|
int requestId)
|
||||||
|
{
|
||||||
|
// Allow null content
|
||||||
|
JToken contentObject =
|
||||||
|
requestParams != null ?
|
||||||
|
JToken.FromObject(requestParams, contentSerializer) :
|
||||||
|
null;
|
||||||
|
|
||||||
|
await this.WriteMessage(
|
||||||
|
Message.Request(
|
||||||
|
requestId.ToString(),
|
||||||
|
requestType.MethodName,
|
||||||
|
contentObject));
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task WriteResponse<TResult>(TResult resultContent, string method, string requestId)
|
||||||
|
{
|
||||||
|
// Allow null content
|
||||||
|
JToken contentObject =
|
||||||
|
resultContent != null ?
|
||||||
|
JToken.FromObject(resultContent, contentSerializer) :
|
||||||
|
null;
|
||||||
|
|
||||||
|
await this.WriteMessage(
|
||||||
|
Message.Response(
|
||||||
|
requestId,
|
||||||
|
method,
|
||||||
|
contentObject));
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task WriteEvent<TParams>(EventType<TParams> eventType, TParams eventParams)
|
||||||
|
{
|
||||||
|
// Allow null content
|
||||||
|
JToken contentObject =
|
||||||
|
eventParams != null ?
|
||||||
|
JToken.FromObject(eventParams, contentSerializer) :
|
||||||
|
null;
|
||||||
|
|
||||||
|
await this.WriteMessage(
|
||||||
|
Message.Event(
|
||||||
|
eventType.MethodName,
|
||||||
|
contentObject));
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
313
ServiceHost/MessageProtocol/ProtocolEndpoint.cs
Normal file
313
ServiceHost/MessageProtocol/ProtocolEndpoint.cs
Normal file
@@ -0,0 +1,313 @@
|
|||||||
|
//
|
||||||
|
// 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.MessageProtocol.Channel;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Provides behavior for a client or server endpoint that
|
||||||
|
/// communicates using the specified protocol.
|
||||||
|
/// </summary>
|
||||||
|
public class ProtocolEndpoint : IMessageSender
|
||||||
|
{
|
||||||
|
private bool isStarted;
|
||||||
|
private int currentMessageId;
|
||||||
|
private ChannelBase protocolChannel;
|
||||||
|
private MessageProtocolType messageProtocolType;
|
||||||
|
private TaskCompletionSource<bool> endpointExitedTask;
|
||||||
|
private SynchronizationContext originalSynchronizationContext;
|
||||||
|
|
||||||
|
private Dictionary<string, TaskCompletionSource<Message>> pendingRequests =
|
||||||
|
new Dictionary<string, TaskCompletionSource<Message>>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the MessageDispatcher which allows registration of
|
||||||
|
/// handlers for requests, responses, and events that are
|
||||||
|
/// transmitted through the channel.
|
||||||
|
/// </summary>
|
||||||
|
protected MessageDispatcher MessageDispatcher { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes an instance of the protocol server using the
|
||||||
|
/// specified channel for communication.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="protocolChannel">
|
||||||
|
/// The channel to use for communication with the connected endpoint.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="messageProtocolType">
|
||||||
|
/// The type of message protocol used by the endpoint.
|
||||||
|
/// </param>
|
||||||
|
public ProtocolEndpoint(
|
||||||
|
ChannelBase protocolChannel,
|
||||||
|
MessageProtocolType messageProtocolType)
|
||||||
|
{
|
||||||
|
this.protocolChannel = protocolChannel;
|
||||||
|
this.messageProtocolType = messageProtocolType;
|
||||||
|
this.originalSynchronizationContext = SynchronizationContext.Current;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Starts the language server client and sends the Initialize method.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>A Task that can be awaited for initialization to complete.</returns>
|
||||||
|
public async Task Start()
|
||||||
|
{
|
||||||
|
if (!this.isStarted)
|
||||||
|
{
|
||||||
|
// Start the provided protocol channel
|
||||||
|
this.protocolChannel.Start(this.messageProtocolType);
|
||||||
|
|
||||||
|
// Start the message dispatcher
|
||||||
|
this.MessageDispatcher = new MessageDispatcher(this.protocolChannel);
|
||||||
|
|
||||||
|
// Set the handler for any message responses that come back
|
||||||
|
this.MessageDispatcher.SetResponseHandler(this.HandleResponse);
|
||||||
|
|
||||||
|
// Listen for unhandled exceptions from the dispatcher
|
||||||
|
this.MessageDispatcher.UnhandledException += MessageDispatcher_UnhandledException;
|
||||||
|
|
||||||
|
// Notify implementation about endpoint start
|
||||||
|
await this.OnStart();
|
||||||
|
|
||||||
|
// Wait for connection and notify the implementor
|
||||||
|
// NOTE: This task is not meant to be awaited.
|
||||||
|
Task waitTask =
|
||||||
|
this.protocolChannel
|
||||||
|
.WaitForConnection()
|
||||||
|
.ContinueWith(
|
||||||
|
async (t) =>
|
||||||
|
{
|
||||||
|
// Start the MessageDispatcher
|
||||||
|
this.MessageDispatcher.Start();
|
||||||
|
await this.OnConnect();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Endpoint is now started
|
||||||
|
this.isStarted = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void WaitForExit()
|
||||||
|
{
|
||||||
|
this.endpointExitedTask = new TaskCompletionSource<bool>();
|
||||||
|
this.endpointExitedTask.Task.Wait();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Stop()
|
||||||
|
{
|
||||||
|
if (this.isStarted)
|
||||||
|
{
|
||||||
|
// Make sure no future calls try to stop the endpoint during shutdown
|
||||||
|
this.isStarted = false;
|
||||||
|
|
||||||
|
// Stop the implementation first
|
||||||
|
await this.OnStop();
|
||||||
|
|
||||||
|
// Stop the dispatcher and channel
|
||||||
|
this.MessageDispatcher.Stop();
|
||||||
|
this.protocolChannel.Stop();
|
||||||
|
|
||||||
|
// Notify anyone waiting for exit
|
||||||
|
if (this.endpointExitedTask != null)
|
||||||
|
{
|
||||||
|
this.endpointExitedTask.SetResult(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Message Sending
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sends a request to the server
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TParams"></typeparam>
|
||||||
|
/// <typeparam name="TResult"></typeparam>
|
||||||
|
/// <param name="requestType"></param>
|
||||||
|
/// <param name="requestParams"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public Task<TResult> SendRequest<TParams, TResult>(
|
||||||
|
RequestType<TParams, TResult> requestType,
|
||||||
|
TParams requestParams)
|
||||||
|
{
|
||||||
|
return this.SendRequest(requestType, requestParams, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<TResult> SendRequest<TParams, TResult>(
|
||||||
|
RequestType<TParams, TResult> requestType,
|
||||||
|
TParams requestParams,
|
||||||
|
bool waitForResponse)
|
||||||
|
{
|
||||||
|
if (!this.protocolChannel.IsConnected)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("SendRequest called when ProtocolChannel was not yet connected");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.currentMessageId++;
|
||||||
|
|
||||||
|
TaskCompletionSource<Message> responseTask = null;
|
||||||
|
|
||||||
|
if (waitForResponse)
|
||||||
|
{
|
||||||
|
responseTask = new TaskCompletionSource<Message>();
|
||||||
|
this.pendingRequests.Add(
|
||||||
|
this.currentMessageId.ToString(),
|
||||||
|
responseTask);
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.protocolChannel.MessageWriter.WriteRequest<TParams, TResult>(
|
||||||
|
requestType,
|
||||||
|
requestParams,
|
||||||
|
this.currentMessageId);
|
||||||
|
|
||||||
|
if (responseTask != null)
|
||||||
|
{
|
||||||
|
var responseMessage = await responseTask.Task;
|
||||||
|
|
||||||
|
return
|
||||||
|
responseMessage.Contents != null ?
|
||||||
|
responseMessage.Contents.ToObject<TResult>() :
|
||||||
|
default(TResult);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// TODO: Better default value here?
|
||||||
|
return default(TResult);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sends an event to the channel's endpoint.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TParams">The event parameter type.</typeparam>
|
||||||
|
/// <param name="eventType">The type of event being sent.</param>
|
||||||
|
/// <param name="eventParams">The event parameters being sent.</param>
|
||||||
|
/// <returns>A Task that tracks completion of the send operation.</returns>
|
||||||
|
public Task SendEvent<TParams>(
|
||||||
|
EventType<TParams> eventType,
|
||||||
|
TParams eventParams)
|
||||||
|
{
|
||||||
|
if (!this.protocolChannel.IsConnected)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("SendEvent called when ProtocolChannel was not yet connected");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Some events could be raised from a different thread.
|
||||||
|
// To ensure that messages are written serially, dispatch
|
||||||
|
// dispatch the SendEvent call to the message loop thread.
|
||||||
|
|
||||||
|
if (!this.MessageDispatcher.InMessageLoopThread)
|
||||||
|
{
|
||||||
|
TaskCompletionSource<bool> writeTask = new TaskCompletionSource<bool>();
|
||||||
|
|
||||||
|
this.MessageDispatcher.SynchronizationContext.Post(
|
||||||
|
async (obj) =>
|
||||||
|
{
|
||||||
|
await this.protocolChannel.MessageWriter.WriteEvent(
|
||||||
|
eventType,
|
||||||
|
eventParams);
|
||||||
|
|
||||||
|
writeTask.SetResult(true);
|
||||||
|
}, null);
|
||||||
|
|
||||||
|
return writeTask.Task;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return this.protocolChannel.MessageWriter.WriteEvent(
|
||||||
|
eventType,
|
||||||
|
eventParams);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Message Handling
|
||||||
|
|
||||||
|
public void SetRequestHandler<TParams, TResult>(
|
||||||
|
RequestType<TParams, TResult> requestType,
|
||||||
|
Func<TParams, RequestContext<TResult>, Task> requestHandler)
|
||||||
|
{
|
||||||
|
this.MessageDispatcher.SetRequestHandler(
|
||||||
|
requestType,
|
||||||
|
requestHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetEventHandler<TParams>(
|
||||||
|
EventType<TParams> eventType,
|
||||||
|
Func<TParams, EventContext, Task> eventHandler)
|
||||||
|
{
|
||||||
|
this.MessageDispatcher.SetEventHandler(
|
||||||
|
eventType,
|
||||||
|
eventHandler,
|
||||||
|
false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetEventHandler<TParams>(
|
||||||
|
EventType<TParams> eventType,
|
||||||
|
Func<TParams, EventContext, Task> eventHandler,
|
||||||
|
bool overrideExisting)
|
||||||
|
{
|
||||||
|
this.MessageDispatcher.SetEventHandler(
|
||||||
|
eventType,
|
||||||
|
eventHandler,
|
||||||
|
overrideExisting);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandleResponse(Message responseMessage)
|
||||||
|
{
|
||||||
|
TaskCompletionSource<Message> pendingRequestTask = null;
|
||||||
|
|
||||||
|
if (this.pendingRequests.TryGetValue(responseMessage.Id, out pendingRequestTask))
|
||||||
|
{
|
||||||
|
pendingRequestTask.SetResult(responseMessage);
|
||||||
|
this.pendingRequests.Remove(responseMessage.Id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Subclass Lifetime Methods
|
||||||
|
|
||||||
|
protected virtual Task OnStart()
|
||||||
|
{
|
||||||
|
return Task.FromResult(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual Task OnConnect()
|
||||||
|
{
|
||||||
|
return Task.FromResult(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual Task OnStop()
|
||||||
|
{
|
||||||
|
return Task.FromResult(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Event Handlers
|
||||||
|
|
||||||
|
private void MessageDispatcher_UnhandledException(object sender, Exception e)
|
||||||
|
{
|
||||||
|
if (this.endpointExitedTask != null)
|
||||||
|
{
|
||||||
|
this.endpointExitedTask.SetException(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (this.originalSynchronizationContext != null)
|
||||||
|
{
|
||||||
|
this.originalSynchronizationContext.Post(o => { throw e; }, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
47
ServiceHost/MessageProtocol/RequestContext.cs
Normal file
47
ServiceHost/MessageProtocol/RequestContext.cs
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
//
|
||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
//
|
||||||
|
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol
|
||||||
|
{
|
||||||
|
public class RequestContext<TResult>
|
||||||
|
{
|
||||||
|
private Message requestMessage;
|
||||||
|
private MessageWriter messageWriter;
|
||||||
|
|
||||||
|
public RequestContext(Message requestMessage, MessageWriter messageWriter)
|
||||||
|
{
|
||||||
|
this.requestMessage = requestMessage;
|
||||||
|
this.messageWriter = messageWriter;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task SendResult(TResult resultDetails)
|
||||||
|
{
|
||||||
|
await this.messageWriter.WriteResponse<TResult>(
|
||||||
|
resultDetails,
|
||||||
|
requestMessage.Method,
|
||||||
|
requestMessage.Id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task SendEvent<TParams>(EventType<TParams> eventType, TParams eventParams)
|
||||||
|
{
|
||||||
|
await this.messageWriter.WriteEvent(
|
||||||
|
eventType,
|
||||||
|
eventParams);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task SendError(object errorDetails)
|
||||||
|
{
|
||||||
|
await this.messageWriter.WriteMessage(
|
||||||
|
Message.ResponseError(
|
||||||
|
requestMessage.Id,
|
||||||
|
requestMessage.Method,
|
||||||
|
JToken.FromObject(errorDetails)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
24
ServiceHost/MessageProtocol/RequestType.cs
Normal file
24
ServiceHost/MessageProtocol/RequestType.cs
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
//
|
||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
//
|
||||||
|
|
||||||
|
using System.Diagnostics;
|
||||||
|
|
||||||
|
namespace Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol
|
||||||
|
{
|
||||||
|
[DebuggerDisplay("RequestType MethodName = {MethodName}")]
|
||||||
|
public class RequestType<TParams, TResult>
|
||||||
|
{
|
||||||
|
public string MethodName { get; private set; }
|
||||||
|
|
||||||
|
public static RequestType<TParams, TResult> Create(string typeName)
|
||||||
|
{
|
||||||
|
return new RequestType<TParams, TResult>()
|
||||||
|
{
|
||||||
|
MethodName = typeName
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,102 @@
|
|||||||
|
//
|
||||||
|
// 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.MessageProtocol;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol.Serializers
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Serializes messages in the JSON RPC format. Used primarily
|
||||||
|
/// for language servers.
|
||||||
|
/// </summary>
|
||||||
|
public class JsonRpcMessageSerializer : IMessageSerializer
|
||||||
|
{
|
||||||
|
public JObject SerializeMessage(Message message)
|
||||||
|
{
|
||||||
|
JObject messageObject = new JObject();
|
||||||
|
|
||||||
|
messageObject.Add("jsonrpc", JToken.FromObject("2.0"));
|
||||||
|
|
||||||
|
if (message.MessageType == MessageType.Request)
|
||||||
|
{
|
||||||
|
messageObject.Add("id", JToken.FromObject(message.Id));
|
||||||
|
messageObject.Add("method", message.Method);
|
||||||
|
messageObject.Add("params", message.Contents);
|
||||||
|
}
|
||||||
|
else if (message.MessageType == MessageType.Event)
|
||||||
|
{
|
||||||
|
messageObject.Add("method", message.Method);
|
||||||
|
messageObject.Add("params", message.Contents);
|
||||||
|
}
|
||||||
|
else if (message.MessageType == MessageType.Response)
|
||||||
|
{
|
||||||
|
messageObject.Add("id", JToken.FromObject(message.Id));
|
||||||
|
|
||||||
|
if (message.Error != null)
|
||||||
|
{
|
||||||
|
// Write error
|
||||||
|
messageObject.Add("error", message.Error);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Write result
|
||||||
|
messageObject.Add("result", message.Contents);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return messageObject;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Message DeserializeMessage(JObject messageJson)
|
||||||
|
{
|
||||||
|
// TODO: Check for jsonrpc version
|
||||||
|
|
||||||
|
JToken token = null;
|
||||||
|
if (messageJson.TryGetValue("id", out token))
|
||||||
|
{
|
||||||
|
// Message is a Request or Response
|
||||||
|
string messageId = token.ToString();
|
||||||
|
|
||||||
|
if (messageJson.TryGetValue("result", out token))
|
||||||
|
{
|
||||||
|
return Message.Response(messageId, null, token);
|
||||||
|
}
|
||||||
|
else if (messageJson.TryGetValue("error", out token))
|
||||||
|
{
|
||||||
|
return Message.ResponseError(messageId, null, token);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
JToken messageParams = null;
|
||||||
|
messageJson.TryGetValue("params", out messageParams);
|
||||||
|
|
||||||
|
if (!messageJson.TryGetValue("method", out token))
|
||||||
|
{
|
||||||
|
// TODO: Throw parse error
|
||||||
|
}
|
||||||
|
|
||||||
|
return Message.Request(messageId, token.ToString(), messageParams);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Messages without an id are events
|
||||||
|
JToken messageParams = token;
|
||||||
|
messageJson.TryGetValue("params", out messageParams);
|
||||||
|
|
||||||
|
if (!messageJson.TryGetValue("method", out token))
|
||||||
|
{
|
||||||
|
// TODO: Throw parse error
|
||||||
|
}
|
||||||
|
|
||||||
|
return Message.Event(token.ToString(), messageParams);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
115
ServiceHost/MessageProtocol/Serializers/V8MessageSerializer.cs
Normal file
115
ServiceHost/MessageProtocol/Serializers/V8MessageSerializer.cs
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
//
|
||||||
|
// 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.MessageProtocol;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol.Serializers
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Serializes messages in the V8 format. Used primarily for debug adapters.
|
||||||
|
/// </summary>
|
||||||
|
public class V8MessageSerializer : IMessageSerializer
|
||||||
|
{
|
||||||
|
public JObject SerializeMessage(Message message)
|
||||||
|
{
|
||||||
|
JObject messageObject = new JObject();
|
||||||
|
|
||||||
|
if (message.MessageType == MessageType.Request)
|
||||||
|
{
|
||||||
|
messageObject.Add("type", JToken.FromObject("request"));
|
||||||
|
messageObject.Add("seq", JToken.FromObject(message.Id));
|
||||||
|
messageObject.Add("command", message.Method);
|
||||||
|
messageObject.Add("arguments", message.Contents);
|
||||||
|
}
|
||||||
|
else if (message.MessageType == MessageType.Event)
|
||||||
|
{
|
||||||
|
messageObject.Add("type", JToken.FromObject("event"));
|
||||||
|
messageObject.Add("event", message.Method);
|
||||||
|
messageObject.Add("body", message.Contents);
|
||||||
|
}
|
||||||
|
else if (message.MessageType == MessageType.Response)
|
||||||
|
{
|
||||||
|
messageObject.Add("type", JToken.FromObject("response"));
|
||||||
|
messageObject.Add("request_seq", JToken.FromObject(message.Id));
|
||||||
|
messageObject.Add("command", message.Method);
|
||||||
|
|
||||||
|
if (message.Error != null)
|
||||||
|
{
|
||||||
|
// Write error
|
||||||
|
messageObject.Add("success", JToken.FromObject(false));
|
||||||
|
messageObject.Add("message", message.Error);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Write result
|
||||||
|
messageObject.Add("success", JToken.FromObject(true));
|
||||||
|
messageObject.Add("body", message.Contents);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return messageObject;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Message DeserializeMessage(JObject messageJson)
|
||||||
|
{
|
||||||
|
JToken token = null;
|
||||||
|
|
||||||
|
if (messageJson.TryGetValue("type", out token))
|
||||||
|
{
|
||||||
|
string messageType = token.ToString();
|
||||||
|
|
||||||
|
if (string.Equals("request", messageType, StringComparison.CurrentCultureIgnoreCase))
|
||||||
|
{
|
||||||
|
return Message.Request(
|
||||||
|
messageJson.GetValue("seq").ToString(),
|
||||||
|
messageJson.GetValue("command").ToString(),
|
||||||
|
messageJson.GetValue("arguments"));
|
||||||
|
}
|
||||||
|
else if (string.Equals("response", messageType, StringComparison.CurrentCultureIgnoreCase))
|
||||||
|
{
|
||||||
|
if (messageJson.TryGetValue("success", out token))
|
||||||
|
{
|
||||||
|
// Was the response for a successful request?
|
||||||
|
if (token.ToObject<bool>() == true)
|
||||||
|
{
|
||||||
|
return Message.Response(
|
||||||
|
messageJson.GetValue("request_seq").ToString(),
|
||||||
|
messageJson.GetValue("command").ToString(),
|
||||||
|
messageJson.GetValue("body"));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return Message.ResponseError(
|
||||||
|
messageJson.GetValue("request_seq").ToString(),
|
||||||
|
messageJson.GetValue("command").ToString(),
|
||||||
|
messageJson.GetValue("message"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// TODO: Parse error
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
else if (string.Equals("event", messageType, StringComparison.CurrentCultureIgnoreCase))
|
||||||
|
{
|
||||||
|
return Message.Event(
|
||||||
|
messageJson.GetValue("event").ToString(),
|
||||||
|
messageJson.GetValue("body"));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return Message.Unknown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Message.Unknown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
58
ServiceHost/Messages/PromptEvents.cs
Normal file
58
ServiceHost/Messages/PromptEvents.cs
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
//
|
||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
//
|
||||||
|
|
||||||
|
#if false
|
||||||
|
using Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol;
|
||||||
|
|
||||||
|
namespace Microsoft.PowerShell.EditorServices.Protocol.Messages
|
||||||
|
{
|
||||||
|
public class ShowChoicePromptRequest
|
||||||
|
{
|
||||||
|
public static readonly
|
||||||
|
RequestType<ShowChoicePromptRequest, ShowChoicePromptResponse> Type =
|
||||||
|
RequestType<ShowChoicePromptRequest, ShowChoicePromptResponse>.Create("powerShell/showChoicePrompt");
|
||||||
|
|
||||||
|
public string Caption { get; set; }
|
||||||
|
|
||||||
|
public string Message { get; set; }
|
||||||
|
|
||||||
|
public ChoiceDetails[] Choices { get; set; }
|
||||||
|
|
||||||
|
public int DefaultChoice { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ShowChoicePromptResponse
|
||||||
|
{
|
||||||
|
public bool PromptCancelled { get; set; }
|
||||||
|
|
||||||
|
public string ChosenItem { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ShowInputPromptRequest
|
||||||
|
{
|
||||||
|
public static readonly
|
||||||
|
RequestType<ShowInputPromptRequest, ShowInputPromptResponse> Type =
|
||||||
|
RequestType<ShowInputPromptRequest, ShowInputPromptResponse>.Create("powerShell/showInputPrompt");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the name of the field.
|
||||||
|
/// </summary>
|
||||||
|
public string Name { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the descriptive label for the field.
|
||||||
|
/// </summary>
|
||||||
|
public string Label { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ShowInputPromptResponse
|
||||||
|
{
|
||||||
|
public bool PromptCancelled { get; set; }
|
||||||
|
|
||||||
|
public string ResponseText { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
20
ServiceHost/Program.cs
Normal file
20
ServiceHost/Program.cs
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
using System;
|
||||||
|
using Microsoft.PowerShell.EditorServices.Protocol.Server;
|
||||||
|
using Microsoft.PowerShell.EditorServices.Session;
|
||||||
|
|
||||||
|
namespace Microsoft.SqlTools.ServiceHost
|
||||||
|
{
|
||||||
|
class Program
|
||||||
|
{
|
||||||
|
static void Main(string[] args)
|
||||||
|
{
|
||||||
|
var hostDetails = new HostDetails("name", "profileId", new Version(1,0));
|
||||||
|
var profilePaths = new ProfilePaths("hostProfileId", "baseAllUsersPath", "baseCurrentUserPath");
|
||||||
|
var languageServer = new LanguageServer(hostDetails, profilePaths);
|
||||||
|
|
||||||
|
languageServer.Start().Wait();
|
||||||
|
|
||||||
|
languageServer.WaitForExit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
44
ServiceHost/Properties/AssemblyInfo.cs
Normal file
44
ServiceHost/Properties/AssemblyInfo.cs
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
//
|
||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
//
|
||||||
|
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
// General Information about an assembly is controlled through the following
|
||||||
|
// set of attributes. Change these attribute values to modify the information
|
||||||
|
// associated with an assembly.
|
||||||
|
[assembly: AssemblyTitle("PowerShell Editor Services Host Protocol Library")]
|
||||||
|
[assembly: AssemblyDescription("Provides message types and client/server APIs for the PowerShell Editor Services JSON protocol.")]
|
||||||
|
[assembly: AssemblyConfiguration("")]
|
||||||
|
[assembly: AssemblyCompany("Microsoft")]
|
||||||
|
[assembly: AssemblyProduct("PowerShell Editor Services")]
|
||||||
|
[assembly: AssemblyCopyright("© Microsoft Corporation. All rights reserved.")]
|
||||||
|
[assembly: AssemblyTrademark("")]
|
||||||
|
[assembly: AssemblyCulture("")]
|
||||||
|
|
||||||
|
// Setting ComVisible to false makes the types in this assembly not visible
|
||||||
|
// to COM components. If you need to access a type in this assembly from
|
||||||
|
// COM, set the ComVisible attribute to true on that type.
|
||||||
|
[assembly: ComVisible(false)]
|
||||||
|
|
||||||
|
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||||
|
[assembly: Guid("78caf6c3-5955-4b15-a302-2bd6b7871d5b")]
|
||||||
|
|
||||||
|
// Version information for an assembly consists of the following four values:
|
||||||
|
//
|
||||||
|
// Major Version
|
||||||
|
// Minor Version
|
||||||
|
// Build Number
|
||||||
|
// Revision
|
||||||
|
//
|
||||||
|
// You can specify all the values or you can default the Build and Revision Numbers
|
||||||
|
// by using the '*' as shown below:
|
||||||
|
// [assembly: AssemblyVersion("1.0.*")]
|
||||||
|
[assembly: AssemblyVersion("0.0.0.0")]
|
||||||
|
[assembly: AssemblyFileVersion("0.0.0.0")]
|
||||||
|
[assembly: AssemblyInformationalVersion("0.0.0.0")]
|
||||||
|
|
||||||
|
[assembly: InternalsVisibleTo("Microsoft.PowerShell.EditorServices.Test.Protocol")]
|
||||||
584
ServiceHost/Server/DebugAdapter.cs
Normal file
584
ServiceHost/Server/DebugAdapter.cs
Normal file
@@ -0,0 +1,584 @@
|
|||||||
|
//
|
||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
//
|
||||||
|
|
||||||
|
#if false
|
||||||
|
|
||||||
|
using Microsoft.PowerShell.EditorServices.Protocol.DebugAdapter;
|
||||||
|
using Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol;
|
||||||
|
using Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol.Channel;
|
||||||
|
using Microsoft.PowerShell.EditorServices.Session;
|
||||||
|
using Microsoft.PowerShell.EditorServices.Utility;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Management.Automation;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Microsoft.PowerShell.EditorServices.Protocol.Server
|
||||||
|
{
|
||||||
|
public class DebugAdapter : DebugAdapterBase
|
||||||
|
{
|
||||||
|
private EditorSession editorSession;
|
||||||
|
private OutputDebouncer outputDebouncer;
|
||||||
|
private bool isConfigurationDoneRequestComplete;
|
||||||
|
private bool isLaunchRequestComplete;
|
||||||
|
private bool noDebug;
|
||||||
|
private string scriptPathToLaunch;
|
||||||
|
private string arguments;
|
||||||
|
|
||||||
|
public DebugAdapter(HostDetails hostDetails, ProfilePaths profilePaths)
|
||||||
|
: this(hostDetails, profilePaths, new StdioServerChannel())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public DebugAdapter(HostDetails hostDetails, ProfilePaths profilePaths, ChannelBase serverChannel)
|
||||||
|
: base(serverChannel)
|
||||||
|
{
|
||||||
|
this.editorSession = new EditorSession();
|
||||||
|
this.editorSession.StartSession(hostDetails, profilePaths);
|
||||||
|
this.editorSession.DebugService.DebuggerStopped += this.DebugService_DebuggerStopped;
|
||||||
|
this.editorSession.ConsoleService.OutputWritten += this.powerShellContext_OutputWritten;
|
||||||
|
|
||||||
|
// Set up the output debouncer to throttle output event writes
|
||||||
|
this.outputDebouncer = new OutputDebouncer(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Initialize()
|
||||||
|
{
|
||||||
|
// 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(SourceRequest.Type, this.HandleSourceRequest);
|
||||||
|
this.SetRequestHandler(EvaluateRequest.Type, this.HandleEvaluateRequest);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Task LaunchScript(RequestContext<object> requestContext)
|
||||||
|
{
|
||||||
|
return editorSession.PowerShellContext
|
||||||
|
.ExecuteScriptAtPath(this.scriptPathToLaunch, this.arguments)
|
||||||
|
.ContinueWith(
|
||||||
|
async (t) => {
|
||||||
|
Logger.Write(LogLevel.Verbose, "Execution completed, terminating...");
|
||||||
|
|
||||||
|
await requestContext.SendEvent(
|
||||||
|
TerminatedEvent.Type,
|
||||||
|
null);
|
||||||
|
|
||||||
|
// Stop the server
|
||||||
|
await this.Stop();
|
||||||
|
|
||||||
|
// Notify that the session has ended
|
||||||
|
this.OnSessionEnded();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Shutdown()
|
||||||
|
{
|
||||||
|
// Make sure remaining output is flushed before exiting
|
||||||
|
this.outputDebouncer.Flush().Wait();
|
||||||
|
|
||||||
|
Logger.Write(LogLevel.Normal, "Debug adapter is shutting down...");
|
||||||
|
|
||||||
|
if (this.editorSession != null)
|
||||||
|
{
|
||||||
|
this.editorSession.Dispose();
|
||||||
|
this.editorSession = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Built-in Message Handlers
|
||||||
|
|
||||||
|
protected async Task HandleConfigurationDoneRequest(
|
||||||
|
object args,
|
||||||
|
RequestContext<object> requestContext)
|
||||||
|
{
|
||||||
|
// The order of debug protocol messages apparently isn't as guaranteed as we might like.
|
||||||
|
// Need to be able to handle the case where we get the configurationDone request after the
|
||||||
|
// launch request.
|
||||||
|
if (this.isLaunchRequestComplete)
|
||||||
|
{
|
||||||
|
this.LaunchScript(requestContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.isConfigurationDoneRequestComplete = true;
|
||||||
|
|
||||||
|
await requestContext.SendResult(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async Task HandleLaunchRequest(
|
||||||
|
LaunchRequestArguments launchParams,
|
||||||
|
RequestContext<object> requestContext)
|
||||||
|
{
|
||||||
|
// Set the working directory for the PowerShell runspace to the cwd passed in via launch.json.
|
||||||
|
// In case that is null, use the the folder of the script to be executed. If the resulting
|
||||||
|
// working dir path is a file path then extract the directory and use that.
|
||||||
|
string workingDir = launchParams.Cwd ?? launchParams.Program;
|
||||||
|
workingDir = PowerShellContext.UnescapePath(workingDir);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if ((File.GetAttributes(workingDir) & FileAttributes.Directory) != FileAttributes.Directory)
|
||||||
|
{
|
||||||
|
workingDir = Path.GetDirectoryName(workingDir);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logger.Write(LogLevel.Error, "cwd path is invalid: " + ex.Message);
|
||||||
|
|
||||||
|
#if NanoServer
|
||||||
|
workingDir = AppContext.BaseDirectory;
|
||||||
|
#else
|
||||||
|
workingDir = Environment.CurrentDirectory;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
editorSession.PowerShellContext.SetWorkingDirectory(workingDir);
|
||||||
|
Logger.Write(LogLevel.Verbose, "Working dir set to: " + workingDir);
|
||||||
|
|
||||||
|
// Prepare arguments to the script - if specified
|
||||||
|
string arguments = null;
|
||||||
|
if ((launchParams.Args != null) && (launchParams.Args.Length > 0))
|
||||||
|
{
|
||||||
|
arguments = string.Join(" ", launchParams.Args);
|
||||||
|
Logger.Write(LogLevel.Verbose, "Script arguments are: " + arguments);
|
||||||
|
}
|
||||||
|
|
||||||
|
// We may not actually launch the script in response to this
|
||||||
|
// request unless it comes after the configurationDone request.
|
||||||
|
// If the launch request comes first, then stash the launch
|
||||||
|
// params so that the subsequent configurationDone request handler
|
||||||
|
// can launch the script.
|
||||||
|
this.noDebug = launchParams.NoDebug;
|
||||||
|
this.scriptPathToLaunch = launchParams.Program;
|
||||||
|
this.arguments = arguments;
|
||||||
|
|
||||||
|
// The order of debug protocol messages apparently isn't as guaranteed as we might like.
|
||||||
|
// Need to be able to handle the case where we get the launch request after the
|
||||||
|
// configurationDone request.
|
||||||
|
if (this.isConfigurationDoneRequestComplete)
|
||||||
|
{
|
||||||
|
this.LaunchScript(requestContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.isLaunchRequestComplete = true;
|
||||||
|
|
||||||
|
await requestContext.SendResult(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Task HandleAttachRequest(
|
||||||
|
AttachRequestArguments attachParams,
|
||||||
|
RequestContext<object> requestContext)
|
||||||
|
{
|
||||||
|
// TODO: Implement this once we support attaching to processes
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Task HandleDisconnectRequest(
|
||||||
|
object disconnectParams,
|
||||||
|
RequestContext<object> requestContext)
|
||||||
|
{
|
||||||
|
EventHandler<SessionStateChangedEventArgs> handler = null;
|
||||||
|
|
||||||
|
handler =
|
||||||
|
async (o, e) =>
|
||||||
|
{
|
||||||
|
if (e.NewSessionState == PowerShellContextState.Ready)
|
||||||
|
{
|
||||||
|
await requestContext.SendResult(null);
|
||||||
|
editorSession.PowerShellContext.SessionStateChanged -= handler;
|
||||||
|
|
||||||
|
// Stop the server
|
||||||
|
this.Stop();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
editorSession.PowerShellContext.SessionStateChanged += handler;
|
||||||
|
editorSession.PowerShellContext.AbortExecution();
|
||||||
|
|
||||||
|
return Task.FromResult(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async Task HandleSetBreakpointsRequest(
|
||||||
|
SetBreakpointsRequestArguments setBreakpointsParams,
|
||||||
|
RequestContext<SetBreakpointsResponseBody> requestContext)
|
||||||
|
{
|
||||||
|
ScriptFile scriptFile;
|
||||||
|
|
||||||
|
// Fix for issue #195 - user can change name of file outside of VSCode in which case
|
||||||
|
// VSCode sends breakpoint requests with the original filename that doesn't exist anymore.
|
||||||
|
try
|
||||||
|
{
|
||||||
|
scriptFile = editorSession.Workspace.GetFile(setBreakpointsParams.Source.Path);
|
||||||
|
}
|
||||||
|
catch (FileNotFoundException)
|
||||||
|
{
|
||||||
|
Logger.Write(
|
||||||
|
LogLevel.Warning,
|
||||||
|
$"Attempted to set breakpoints on a non-existing file: {setBreakpointsParams.Source.Path}");
|
||||||
|
|
||||||
|
string message = this.noDebug ? string.Empty : "Source does not exist, breakpoint not set.";
|
||||||
|
|
||||||
|
var srcBreakpoints = setBreakpointsParams.Breakpoints
|
||||||
|
.Select(srcBkpt => Protocol.DebugAdapter.Breakpoint.Create(
|
||||||
|
srcBkpt, setBreakpointsParams.Source.Path, message, verified: this.noDebug));
|
||||||
|
|
||||||
|
// Return non-verified breakpoint message.
|
||||||
|
await requestContext.SendResult(
|
||||||
|
new SetBreakpointsResponseBody {
|
||||||
|
Breakpoints = srcBreakpoints.ToArray()
|
||||||
|
});
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var breakpointDetails = new BreakpointDetails[setBreakpointsParams.Breakpoints.Length];
|
||||||
|
for (int i = 0; i < breakpointDetails.Length; i++)
|
||||||
|
{
|
||||||
|
SourceBreakpoint srcBreakpoint = setBreakpointsParams.Breakpoints[i];
|
||||||
|
breakpointDetails[i] = BreakpointDetails.Create(
|
||||||
|
scriptFile.FilePath,
|
||||||
|
srcBreakpoint.Line,
|
||||||
|
srcBreakpoint.Column,
|
||||||
|
srcBreakpoint.Condition);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If this is a "run without debugging (Ctrl+F5)" session ignore requests to set breakpoints.
|
||||||
|
BreakpointDetails[] updatedBreakpointDetails = breakpointDetails;
|
||||||
|
if (!this.noDebug)
|
||||||
|
{
|
||||||
|
updatedBreakpointDetails =
|
||||||
|
await editorSession.DebugService.SetLineBreakpoints(
|
||||||
|
scriptFile,
|
||||||
|
breakpointDetails);
|
||||||
|
}
|
||||||
|
|
||||||
|
await requestContext.SendResult(
|
||||||
|
new SetBreakpointsResponseBody {
|
||||||
|
Breakpoints =
|
||||||
|
updatedBreakpointDetails
|
||||||
|
.Select(Protocol.DebugAdapter.Breakpoint.Create)
|
||||||
|
.ToArray()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async Task HandleSetFunctionBreakpointsRequest(
|
||||||
|
SetFunctionBreakpointsRequestArguments setBreakpointsParams,
|
||||||
|
RequestContext<SetBreakpointsResponseBody> requestContext)
|
||||||
|
{
|
||||||
|
var breakpointDetails = new CommandBreakpointDetails[setBreakpointsParams.Breakpoints.Length];
|
||||||
|
for (int i = 0; i < breakpointDetails.Length; i++)
|
||||||
|
{
|
||||||
|
FunctionBreakpoint funcBreakpoint = setBreakpointsParams.Breakpoints[i];
|
||||||
|
breakpointDetails[i] = CommandBreakpointDetails.Create(
|
||||||
|
funcBreakpoint.Name,
|
||||||
|
funcBreakpoint.Condition);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If this is a "run without debugging (Ctrl+F5)" session ignore requests to set breakpoints.
|
||||||
|
CommandBreakpointDetails[] updatedBreakpointDetails = breakpointDetails;
|
||||||
|
if (!this.noDebug)
|
||||||
|
{
|
||||||
|
updatedBreakpointDetails =
|
||||||
|
await editorSession.DebugService.SetCommandBreakpoints(
|
||||||
|
breakpointDetails);
|
||||||
|
}
|
||||||
|
|
||||||
|
await requestContext.SendResult(
|
||||||
|
new SetBreakpointsResponseBody {
|
||||||
|
Breakpoints =
|
||||||
|
updatedBreakpointDetails
|
||||||
|
.Select(Protocol.DebugAdapter.Breakpoint.Create)
|
||||||
|
.ToArray()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async Task HandleSetExceptionBreakpointsRequest(
|
||||||
|
SetExceptionBreakpointsRequestArguments setExceptionBreakpointsParams,
|
||||||
|
RequestContext<object> requestContext)
|
||||||
|
{
|
||||||
|
// TODO: Handle this appropriately
|
||||||
|
|
||||||
|
await requestContext.SendResult(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async Task HandleContinueRequest(
|
||||||
|
object continueParams,
|
||||||
|
RequestContext<object> requestContext)
|
||||||
|
{
|
||||||
|
editorSession.DebugService.Continue();
|
||||||
|
|
||||||
|
await requestContext.SendResult(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async Task HandleNextRequest(
|
||||||
|
object nextParams,
|
||||||
|
RequestContext<object> requestContext)
|
||||||
|
{
|
||||||
|
editorSession.DebugService.StepOver();
|
||||||
|
|
||||||
|
await requestContext.SendResult(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Task HandlePauseRequest(
|
||||||
|
object pauseParams,
|
||||||
|
RequestContext<object> requestContext)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
editorSession.DebugService.Break();
|
||||||
|
}
|
||||||
|
catch (NotSupportedException e)
|
||||||
|
{
|
||||||
|
return requestContext.SendError(e.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
// This request is responded to by sending the "stopped" event
|
||||||
|
return Task.FromResult(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async Task HandleStepInRequest(
|
||||||
|
object stepInParams,
|
||||||
|
RequestContext<object> requestContext)
|
||||||
|
{
|
||||||
|
editorSession.DebugService.StepIn();
|
||||||
|
|
||||||
|
await requestContext.SendResult(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async Task HandleStepOutRequest(
|
||||||
|
object stepOutParams,
|
||||||
|
RequestContext<object> requestContext)
|
||||||
|
{
|
||||||
|
editorSession.DebugService.StepOut();
|
||||||
|
|
||||||
|
await requestContext.SendResult(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async Task HandleThreadsRequest(
|
||||||
|
object threadsParams,
|
||||||
|
RequestContext<ThreadsResponseBody> requestContext)
|
||||||
|
{
|
||||||
|
await requestContext.SendResult(
|
||||||
|
new ThreadsResponseBody
|
||||||
|
{
|
||||||
|
Threads = new Thread[]
|
||||||
|
{
|
||||||
|
// TODO: What do I do with these?
|
||||||
|
new Thread
|
||||||
|
{
|
||||||
|
Id = 1,
|
||||||
|
Name = "Main Thread"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async Task HandleStackTraceRequest(
|
||||||
|
StackTraceRequestArguments stackTraceParams,
|
||||||
|
RequestContext<StackTraceResponseBody> requestContext)
|
||||||
|
{
|
||||||
|
StackFrameDetails[] stackFrames =
|
||||||
|
editorSession.DebugService.GetStackFrames();
|
||||||
|
|
||||||
|
List<StackFrame> newStackFrames = new List<StackFrame>();
|
||||||
|
|
||||||
|
for (int i = 0; i < stackFrames.Length; i++)
|
||||||
|
{
|
||||||
|
// Create the new StackFrame object with an ID that can
|
||||||
|
// be referenced back to the current list of stack frames
|
||||||
|
newStackFrames.Add(
|
||||||
|
StackFrame.Create(
|
||||||
|
stackFrames[i],
|
||||||
|
i));
|
||||||
|
}
|
||||||
|
|
||||||
|
await requestContext.SendResult(
|
||||||
|
new StackTraceResponseBody
|
||||||
|
{
|
||||||
|
StackFrames = newStackFrames.ToArray()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async Task HandleScopesRequest(
|
||||||
|
ScopesRequestArguments scopesParams,
|
||||||
|
RequestContext<ScopesResponseBody> requestContext)
|
||||||
|
{
|
||||||
|
VariableScope[] variableScopes =
|
||||||
|
editorSession.DebugService.GetVariableScopes(
|
||||||
|
scopesParams.FrameId);
|
||||||
|
|
||||||
|
await requestContext.SendResult(
|
||||||
|
new ScopesResponseBody
|
||||||
|
{
|
||||||
|
Scopes =
|
||||||
|
variableScopes
|
||||||
|
.Select(Scope.Create)
|
||||||
|
.ToArray()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async Task HandleVariablesRequest(
|
||||||
|
VariablesRequestArguments variablesParams,
|
||||||
|
RequestContext<VariablesResponseBody> requestContext)
|
||||||
|
{
|
||||||
|
VariableDetailsBase[] variables =
|
||||||
|
editorSession.DebugService.GetVariables(
|
||||||
|
variablesParams.VariablesReference);
|
||||||
|
|
||||||
|
VariablesResponseBody variablesResponse = null;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
variablesResponse = new VariablesResponseBody
|
||||||
|
{
|
||||||
|
Variables =
|
||||||
|
variables
|
||||||
|
.Select(Variable.Create)
|
||||||
|
.ToArray()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
// TODO: This shouldn't be so broad
|
||||||
|
}
|
||||||
|
|
||||||
|
await requestContext.SendResult(variablesResponse);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Task HandleSourceRequest(
|
||||||
|
SourceRequestArguments sourceParams,
|
||||||
|
RequestContext<SourceResponseBody> requestContext)
|
||||||
|
{
|
||||||
|
// TODO: Implement this message. For now, doesn't seem to
|
||||||
|
// be a problem that it's missing.
|
||||||
|
|
||||||
|
return Task.FromResult(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async Task HandleEvaluateRequest(
|
||||||
|
EvaluateRequestArguments evaluateParams,
|
||||||
|
RequestContext<EvaluateResponseBody> requestContext)
|
||||||
|
{
|
||||||
|
string valueString = null;
|
||||||
|
int variableId = 0;
|
||||||
|
|
||||||
|
bool isFromRepl =
|
||||||
|
string.Equals(
|
||||||
|
evaluateParams.Context,
|
||||||
|
"repl",
|
||||||
|
StringComparison.CurrentCultureIgnoreCase);
|
||||||
|
|
||||||
|
if (isFromRepl)
|
||||||
|
{
|
||||||
|
// Send the input through the console service
|
||||||
|
editorSession.ConsoleService.ExecuteCommand(
|
||||||
|
evaluateParams.Expression,
|
||||||
|
false);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
VariableDetails result =
|
||||||
|
await editorSession.DebugService.EvaluateExpression(
|
||||||
|
evaluateParams.Expression,
|
||||||
|
evaluateParams.FrameId,
|
||||||
|
isFromRepl);
|
||||||
|
|
||||||
|
if (result != null)
|
||||||
|
{
|
||||||
|
valueString = result.ValueString;
|
||||||
|
variableId =
|
||||||
|
result.IsExpandable ?
|
||||||
|
result.Id : 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await requestContext.SendResult(
|
||||||
|
new EvaluateResponseBody
|
||||||
|
{
|
||||||
|
Result = valueString,
|
||||||
|
VariablesReference = variableId
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Events
|
||||||
|
|
||||||
|
public event EventHandler SessionEnded;
|
||||||
|
|
||||||
|
protected virtual void OnSessionEnded()
|
||||||
|
{
|
||||||
|
this.SessionEnded?.Invoke(this, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Event Handlers
|
||||||
|
|
||||||
|
async void DebugService_DebuggerStopped(object sender, DebuggerStopEventArgs e)
|
||||||
|
{
|
||||||
|
// Flush pending output before sending the event
|
||||||
|
await this.outputDebouncer.Flush();
|
||||||
|
|
||||||
|
// Provide the reason for why the debugger has stopped script execution.
|
||||||
|
// See https://github.com/Microsoft/vscode/issues/3648
|
||||||
|
// The reason is displayed in the breakpoints viewlet. Some recommended reasons are:
|
||||||
|
// "step", "breakpoint", "function breakpoint", "exception" and "pause".
|
||||||
|
// We don't support exception breakpoints and for "pause", we can't distinguish
|
||||||
|
// between stepping and the user pressing the pause/break button in the debug toolbar.
|
||||||
|
string debuggerStoppedReason = "step";
|
||||||
|
if (e.Breakpoints.Count > 0)
|
||||||
|
{
|
||||||
|
debuggerStoppedReason =
|
||||||
|
e.Breakpoints[0] is CommandBreakpoint
|
||||||
|
? "function breakpoint"
|
||||||
|
: "breakpoint";
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.SendEvent(
|
||||||
|
StoppedEvent.Type,
|
||||||
|
new StoppedEventBody
|
||||||
|
{
|
||||||
|
Source = new Source
|
||||||
|
{
|
||||||
|
Path = e.InvocationInfo.ScriptName,
|
||||||
|
},
|
||||||
|
Line = e.InvocationInfo.ScriptLineNumber,
|
||||||
|
Column = e.InvocationInfo.OffsetInLine,
|
||||||
|
ThreadId = 1, // TODO: Change this based on context
|
||||||
|
Reason = debuggerStoppedReason
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async void powerShellContext_OutputWritten(object sender, OutputWrittenEventArgs e)
|
||||||
|
{
|
||||||
|
// Queue the output for writing
|
||||||
|
await this.outputDebouncer.Invoke(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
73
ServiceHost/Server/DebugAdapterBase.cs
Normal file
73
ServiceHost/Server/DebugAdapterBase.cs
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
//
|
||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
//
|
||||||
|
#if false
|
||||||
|
using Microsoft.PowerShell.EditorServices.Protocol.DebugAdapter;
|
||||||
|
using Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol;
|
||||||
|
using Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol.Channel;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Microsoft.PowerShell.EditorServices.Protocol.Server
|
||||||
|
{
|
||||||
|
public abstract class DebugAdapterBase : ProtocolEndpoint
|
||||||
|
{
|
||||||
|
public DebugAdapterBase(ChannelBase serverChannel)
|
||||||
|
: base (serverChannel, MessageProtocolType.DebugAdapter)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Overridden by the subclass to provide initialization
|
||||||
|
/// logic after the server channel is started.
|
||||||
|
/// </summary>
|
||||||
|
protected abstract void Initialize();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Can be overridden by the subclass to provide shutdown
|
||||||
|
/// logic before the server exits.
|
||||||
|
/// </summary>
|
||||||
|
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<InitializeResponseBody> requestContext)
|
||||||
|
{
|
||||||
|
// Now send the Initialize response to continue setup
|
||||||
|
await requestContext.SendResult(
|
||||||
|
new InitializeResponseBody {
|
||||||
|
SupportsConfigurationDoneRequest = true,
|
||||||
|
SupportsConditionalBreakpoints = true,
|
||||||
|
SupportsFunctionBreakpoints = true
|
||||||
|
});
|
||||||
|
|
||||||
|
// Send the Initialized event so that we get breakpoints
|
||||||
|
await requestContext.SendEvent(
|
||||||
|
InitializedEvent.Type,
|
||||||
|
null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
1262
ServiceHost/Server/LanguageServer.cs
Normal file
1262
ServiceHost/Server/LanguageServer.cs
Normal file
File diff suppressed because it is too large
Load Diff
84
ServiceHost/Server/LanguageServerBase.cs
Normal file
84
ServiceHost/Server/LanguageServerBase.cs
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
//
|
||||||
|
// 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 System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Microsoft.PowerShell.EditorServices.Protocol.Server
|
||||||
|
{
|
||||||
|
public abstract class LanguageServerBase : ProtocolEndpoint
|
||||||
|
{
|
||||||
|
private bool isStarted;
|
||||||
|
private ChannelBase serverChannel;
|
||||||
|
private TaskCompletionSource<bool> serverExitedTask;
|
||||||
|
|
||||||
|
public LanguageServerBase(ChannelBase serverChannel) :
|
||||||
|
base(serverChannel, MessageProtocolType.LanguageServer)
|
||||||
|
{
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Overridden by the subclass to provide initialization
|
||||||
|
/// logic after the server channel is started.
|
||||||
|
/// </summary>
|
||||||
|
protected abstract void Initialize();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 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.
|
||||||
|
/// </summary>
|
||||||
|
protected virtual Task Shutdown()
|
||||||
|
{
|
||||||
|
// No default implementation yet.
|
||||||
|
return Task.FromResult(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task HandleShutdownRequest(
|
||||||
|
object shutdownParams,
|
||||||
|
RequestContext<object> 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();
|
||||||
|
|
||||||
|
// Notify any waiter that the server has exited
|
||||||
|
if (this.serverExitedTask != null)
|
||||||
|
{
|
||||||
|
this.serverExitedTask.SetResult(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
114
ServiceHost/Server/LanguageServerEditorOperations.cs
Normal file
114
ServiceHost/Server/LanguageServerEditorOperations.cs
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
//
|
||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
//
|
||||||
|
#if false
|
||||||
|
using Microsoft.PowerShell.EditorServices.Extensions;
|
||||||
|
using Microsoft.PowerShell.EditorServices.Protocol.LanguageServer;
|
||||||
|
using Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Microsoft.PowerShell.EditorServices.Protocol.Server
|
||||||
|
{
|
||||||
|
internal class LanguageServerEditorOperations : IEditorOperations
|
||||||
|
{
|
||||||
|
private EditorSession editorSession;
|
||||||
|
private IMessageSender messageSender;
|
||||||
|
|
||||||
|
public LanguageServerEditorOperations(
|
||||||
|
EditorSession editorSession,
|
||||||
|
IMessageSender messageSender)
|
||||||
|
{
|
||||||
|
this.editorSession = editorSession;
|
||||||
|
this.messageSender = messageSender;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<EditorContext> GetEditorContext()
|
||||||
|
{
|
||||||
|
ClientEditorContext clientContext =
|
||||||
|
await this.messageSender.SendRequest(
|
||||||
|
GetEditorContextRequest.Type,
|
||||||
|
new GetEditorContextRequest(),
|
||||||
|
true);
|
||||||
|
|
||||||
|
return this.ConvertClientEditorContext(clientContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task InsertText(string filePath, string text, BufferRange insertRange)
|
||||||
|
{
|
||||||
|
await this.messageSender.SendRequest(
|
||||||
|
InsertTextRequest.Type,
|
||||||
|
new InsertTextRequest
|
||||||
|
{
|
||||||
|
FilePath = filePath,
|
||||||
|
InsertText = text,
|
||||||
|
InsertRange =
|
||||||
|
new Range
|
||||||
|
{
|
||||||
|
Start = new Position
|
||||||
|
{
|
||||||
|
Line = insertRange.Start.Line - 1,
|
||||||
|
Character = insertRange.Start.Column - 1
|
||||||
|
},
|
||||||
|
End = new Position
|
||||||
|
{
|
||||||
|
Line = insertRange.End.Line - 1,
|
||||||
|
Character = insertRange.End.Column - 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, false);
|
||||||
|
|
||||||
|
// TODO: Set the last param back to true!
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task SetSelection(BufferRange selectionRange)
|
||||||
|
{
|
||||||
|
return this.messageSender.SendRequest(
|
||||||
|
SetSelectionRequest.Type,
|
||||||
|
new SetSelectionRequest
|
||||||
|
{
|
||||||
|
SelectionRange =
|
||||||
|
new Range
|
||||||
|
{
|
||||||
|
Start = new Position
|
||||||
|
{
|
||||||
|
Line = selectionRange.Start.Line - 1,
|
||||||
|
Character = selectionRange.Start.Column - 1
|
||||||
|
},
|
||||||
|
End = new Position
|
||||||
|
{
|
||||||
|
Line = selectionRange.End.Line - 1,
|
||||||
|
Character = selectionRange.End.Column - 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public EditorContext ConvertClientEditorContext(
|
||||||
|
ClientEditorContext clientContext)
|
||||||
|
{
|
||||||
|
return
|
||||||
|
new EditorContext(
|
||||||
|
this,
|
||||||
|
this.editorSession.Workspace.GetFile(clientContext.CurrentFilePath),
|
||||||
|
new BufferPosition(
|
||||||
|
clientContext.CursorPosition.Line + 1,
|
||||||
|
clientContext.CursorPosition.Character + 1),
|
||||||
|
new BufferRange(
|
||||||
|
clientContext.SelectionRange.Start.Line + 1,
|
||||||
|
clientContext.SelectionRange.Start.Character + 1,
|
||||||
|
clientContext.SelectionRange.End.Line + 1,
|
||||||
|
clientContext.SelectionRange.End.Character + 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task OpenFile(string filePath)
|
||||||
|
{
|
||||||
|
return
|
||||||
|
this.messageSender.SendRequest(
|
||||||
|
OpenFileRequest.Type,
|
||||||
|
filePath,
|
||||||
|
true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
88
ServiceHost/Server/LanguageServerSettings.cs
Normal file
88
ServiceHost/Server/LanguageServerSettings.cs
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
//
|
||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
//
|
||||||
|
|
||||||
|
using System.IO;
|
||||||
|
using Microsoft.PowerShell.EditorServices.Utility;
|
||||||
|
|
||||||
|
namespace Microsoft.PowerShell.EditorServices.Protocol.Server
|
||||||
|
{
|
||||||
|
public class LanguageServerSettings
|
||||||
|
{
|
||||||
|
public bool EnableProfileLoading { get; set; }
|
||||||
|
|
||||||
|
public ScriptAnalysisSettings ScriptAnalysis { get; set; }
|
||||||
|
|
||||||
|
public LanguageServerSettings()
|
||||||
|
{
|
||||||
|
this.ScriptAnalysis = new ScriptAnalysisSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Update(LanguageServerSettings settings, string workspaceRootPath)
|
||||||
|
{
|
||||||
|
if (settings != null)
|
||||||
|
{
|
||||||
|
this.EnableProfileLoading = settings.EnableProfileLoading;
|
||||||
|
this.ScriptAnalysis.Update(settings.ScriptAnalysis, workspaceRootPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ScriptAnalysisSettings
|
||||||
|
{
|
||||||
|
public bool? Enable { get; set; }
|
||||||
|
|
||||||
|
public string SettingsPath { get; set; }
|
||||||
|
|
||||||
|
public ScriptAnalysisSettings()
|
||||||
|
{
|
||||||
|
this.Enable = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Update(ScriptAnalysisSettings settings, string workspaceRootPath)
|
||||||
|
{
|
||||||
|
if (settings != null)
|
||||||
|
{
|
||||||
|
this.Enable = settings.Enable;
|
||||||
|
|
||||||
|
string settingsPath = settings.SettingsPath;
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(settingsPath))
|
||||||
|
{
|
||||||
|
settingsPath = null;
|
||||||
|
}
|
||||||
|
else if (!Path.IsPathRooted(settingsPath))
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(workspaceRootPath))
|
||||||
|
{
|
||||||
|
// The workspace root path could be an empty string
|
||||||
|
// when the user has opened a PowerShell script file
|
||||||
|
// without opening an entire folder (workspace) first.
|
||||||
|
// In this case we should just log an error and let
|
||||||
|
// the specified settings path go through even though
|
||||||
|
// it will fail to load.
|
||||||
|
Logger.Write(
|
||||||
|
LogLevel.Error,
|
||||||
|
"Could not resolve Script Analyzer settings path due to null or empty workspaceRootPath.");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
settingsPath = Path.GetFullPath(Path.Combine(workspaceRootPath, settingsPath));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.SettingsPath = settingsPath;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class LanguageServerSettingsWrapper
|
||||||
|
{
|
||||||
|
// NOTE: This property is capitalized as 'Powershell' because the
|
||||||
|
// mode name sent from the client is written as 'powershell' and
|
||||||
|
// JSON.net is using camelCasing.
|
||||||
|
|
||||||
|
public LanguageServerSettings Powershell { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
102
ServiceHost/Server/OutputDebouncer.cs
Normal file
102
ServiceHost/Server/OutputDebouncer.cs
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
//
|
||||||
|
// 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.Utility;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Microsoft.PowerShell.EditorServices.Protocol.Server
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Throttles output written via OutputEvents by batching all output
|
||||||
|
/// written within a short time window and writing it all out at once.
|
||||||
|
/// </summary>
|
||||||
|
internal class OutputDebouncer : AsyncDebouncer<OutputWrittenEventArgs>
|
||||||
|
{
|
||||||
|
#region Private Fields
|
||||||
|
|
||||||
|
private IMessageSender messageSender;
|
||||||
|
private bool currentOutputIsError = false;
|
||||||
|
private string currentOutputString = null;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Constants
|
||||||
|
|
||||||
|
// Set a really short window for output flushes. This
|
||||||
|
// gives the appearance of fast output without the crushing
|
||||||
|
// overhead of sending an OutputEvent for every single line
|
||||||
|
// written. At this point it seems that around 10-20 lines get
|
||||||
|
// batched for each flush when Get-Process is called.
|
||||||
|
public const int OutputFlushInterval = 200;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Constructors
|
||||||
|
|
||||||
|
public OutputDebouncer(IMessageSender messageSender)
|
||||||
|
: base(OutputFlushInterval, false)
|
||||||
|
{
|
||||||
|
this.messageSender = messageSender;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Private Methods
|
||||||
|
|
||||||
|
protected override async Task OnInvoke(OutputWrittenEventArgs output)
|
||||||
|
{
|
||||||
|
bool outputIsError = output.OutputType == OutputType.Error;
|
||||||
|
|
||||||
|
if (this.currentOutputIsError != outputIsError)
|
||||||
|
{
|
||||||
|
if (this.currentOutputString != null)
|
||||||
|
{
|
||||||
|
// Flush the output
|
||||||
|
await this.OnFlush();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.currentOutputString = string.Empty;
|
||||||
|
this.currentOutputIsError = outputIsError;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Output string could be null if the last output was already flushed
|
||||||
|
if (this.currentOutputString == null)
|
||||||
|
{
|
||||||
|
this.currentOutputString = string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add to string (and include newline)
|
||||||
|
this.currentOutputString +=
|
||||||
|
output.OutputText +
|
||||||
|
(output.IncludeNewLine ?
|
||||||
|
System.Environment.NewLine :
|
||||||
|
string.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override async Task OnFlush()
|
||||||
|
{
|
||||||
|
// Only flush output if there is some to flush
|
||||||
|
if (this.currentOutputString != null)
|
||||||
|
{
|
||||||
|
// Send an event for the current output
|
||||||
|
await this.messageSender.SendEvent(
|
||||||
|
OutputEvent.Type,
|
||||||
|
new OutputEventBody
|
||||||
|
{
|
||||||
|
Output = this.currentOutputString,
|
||||||
|
Category = (this.currentOutputIsError) ? "stderr" : "stdout"
|
||||||
|
});
|
||||||
|
|
||||||
|
// Clear the output string for the next batch
|
||||||
|
this.currentOutputString = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
190
ServiceHost/Server/PromptHandlers.cs
Normal file
190
ServiceHost/Server/PromptHandlers.cs
Normal file
@@ -0,0 +1,190 @@
|
|||||||
|
//
|
||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
//
|
||||||
|
|
||||||
|
#if false
|
||||||
|
using System;
|
||||||
|
using Microsoft.PowerShell.EditorServices.Console;
|
||||||
|
using Microsoft.PowerShell.EditorServices.Protocol.Messages;
|
||||||
|
using Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol;
|
||||||
|
using Microsoft.PowerShell.EditorServices.Utility;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Microsoft.PowerShell.EditorServices.Protocol.Server
|
||||||
|
{
|
||||||
|
internal class ProtocolPromptHandlerContext : IPromptHandlerContext
|
||||||
|
{
|
||||||
|
private IMessageSender messageSender;
|
||||||
|
private ConsoleService consoleService;
|
||||||
|
|
||||||
|
public ProtocolPromptHandlerContext(
|
||||||
|
IMessageSender messageSender,
|
||||||
|
ConsoleService consoleService)
|
||||||
|
{
|
||||||
|
this.messageSender = messageSender;
|
||||||
|
this.consoleService = consoleService;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ChoicePromptHandler GetChoicePromptHandler()
|
||||||
|
{
|
||||||
|
return new ProtocolChoicePromptHandler(
|
||||||
|
this.messageSender,
|
||||||
|
this.consoleService);
|
||||||
|
}
|
||||||
|
|
||||||
|
public InputPromptHandler GetInputPromptHandler()
|
||||||
|
{
|
||||||
|
return new ProtocolInputPromptHandler(
|
||||||
|
this.messageSender,
|
||||||
|
this.consoleService);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class ProtocolChoicePromptHandler : ChoicePromptHandler
|
||||||
|
{
|
||||||
|
private IMessageSender messageSender;
|
||||||
|
private ConsoleService consoleService;
|
||||||
|
|
||||||
|
public ProtocolChoicePromptHandler(
|
||||||
|
IMessageSender messageSender,
|
||||||
|
ConsoleService consoleService)
|
||||||
|
{
|
||||||
|
this.messageSender = messageSender;
|
||||||
|
this.consoleService = consoleService;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void ShowPrompt(PromptStyle promptStyle)
|
||||||
|
{
|
||||||
|
messageSender
|
||||||
|
.SendRequest(
|
||||||
|
ShowChoicePromptRequest.Type,
|
||||||
|
new ShowChoicePromptRequest
|
||||||
|
{
|
||||||
|
Caption = this.Caption,
|
||||||
|
Message = this.Message,
|
||||||
|
Choices = this.Choices,
|
||||||
|
DefaultChoice = this.DefaultChoice
|
||||||
|
}, true)
|
||||||
|
.ContinueWith(HandlePromptResponse)
|
||||||
|
.ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandlePromptResponse(
|
||||||
|
Task<ShowChoicePromptResponse> responseTask)
|
||||||
|
{
|
||||||
|
if (responseTask.IsCompleted)
|
||||||
|
{
|
||||||
|
ShowChoicePromptResponse response = responseTask.Result;
|
||||||
|
|
||||||
|
if (!response.PromptCancelled)
|
||||||
|
{
|
||||||
|
this.consoleService.ReceivePromptResponse(
|
||||||
|
response.ChosenItem,
|
||||||
|
false);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Cancel the current prompt
|
||||||
|
this.consoleService.SendControlC();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (responseTask.IsFaulted)
|
||||||
|
{
|
||||||
|
// Log the error
|
||||||
|
Logger.Write(
|
||||||
|
LogLevel.Error,
|
||||||
|
"ShowChoicePrompt request failed with error:\r\n{0}",
|
||||||
|
responseTask.Exception.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cancel the current prompt
|
||||||
|
this.consoleService.SendControlC();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class ProtocolInputPromptHandler : ConsoleInputPromptHandler
|
||||||
|
{
|
||||||
|
private IMessageSender messageSender;
|
||||||
|
private ConsoleService consoleService;
|
||||||
|
|
||||||
|
public ProtocolInputPromptHandler(
|
||||||
|
IMessageSender messageSender,
|
||||||
|
ConsoleService consoleService)
|
||||||
|
: base(consoleService)
|
||||||
|
{
|
||||||
|
this.messageSender = messageSender;
|
||||||
|
this.consoleService = consoleService;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void ShowErrorMessage(Exception e)
|
||||||
|
{
|
||||||
|
// Use default behavior for writing the error message
|
||||||
|
base.ShowErrorMessage(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void ShowPromptMessage(string caption, string message)
|
||||||
|
{
|
||||||
|
// Use default behavior for writing the prompt message
|
||||||
|
base.ShowPromptMessage(caption, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void ShowFieldPrompt(FieldDetails fieldDetails)
|
||||||
|
{
|
||||||
|
// Write the prompt to the console first so that there's a record
|
||||||
|
// of it occurring
|
||||||
|
base.ShowFieldPrompt(fieldDetails);
|
||||||
|
|
||||||
|
messageSender
|
||||||
|
.SendRequest(
|
||||||
|
ShowInputPromptRequest.Type,
|
||||||
|
new ShowInputPromptRequest
|
||||||
|
{
|
||||||
|
Name = fieldDetails.Name,
|
||||||
|
Label = fieldDetails.Label
|
||||||
|
}, true)
|
||||||
|
.ContinueWith(HandlePromptResponse)
|
||||||
|
.ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandlePromptResponse(
|
||||||
|
Task<ShowInputPromptResponse> responseTask)
|
||||||
|
{
|
||||||
|
if (responseTask.IsCompleted)
|
||||||
|
{
|
||||||
|
ShowInputPromptResponse response = responseTask.Result;
|
||||||
|
|
||||||
|
if (!response.PromptCancelled)
|
||||||
|
{
|
||||||
|
this.consoleService.ReceivePromptResponse(
|
||||||
|
response.ResponseText,
|
||||||
|
true);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Cancel the current prompt
|
||||||
|
this.consoleService.SendControlC();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (responseTask.IsFaulted)
|
||||||
|
{
|
||||||
|
// Log the error
|
||||||
|
Logger.Write(
|
||||||
|
LogLevel.Error,
|
||||||
|
"ShowInputPrompt request failed with error:\r\n{0}",
|
||||||
|
responseTask.Exception.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cancel the current prompt
|
||||||
|
this.consoleService.SendControlC();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
159
ServiceHost/Session/EditorSession.cs
Normal file
159
ServiceHost/Session/EditorSession.cs
Normal file
@@ -0,0 +1,159 @@
|
|||||||
|
//
|
||||||
|
// 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.Console;
|
||||||
|
// using Microsoft.PowerShell.EditorServices.Extensions;
|
||||||
|
using Microsoft.PowerShell.EditorServices.Session;
|
||||||
|
// using Microsoft.PowerShell.EditorServices.Utility;
|
||||||
|
// using System.IO;
|
||||||
|
|
||||||
|
namespace Microsoft.PowerShell.EditorServices
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Manages a single session for all editor services. This
|
||||||
|
/// includes managing all open script files for the session.
|
||||||
|
/// </summary>
|
||||||
|
public class EditorSession
|
||||||
|
{
|
||||||
|
public void StartSession(HostDetails hostDetails, ProfilePaths profilePaths)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
#if false
|
||||||
|
#region Properties
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the Workspace instance for this session.
|
||||||
|
/// </summary>
|
||||||
|
public Workspace Workspace { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the PowerShellContext instance for this session.
|
||||||
|
/// </summary>
|
||||||
|
public PowerShellContext PowerShellContext { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the LanguageService instance for this session.
|
||||||
|
/// </summary>
|
||||||
|
public LanguageService LanguageService { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the AnalysisService instance for this session.
|
||||||
|
/// </summary>
|
||||||
|
public AnalysisService AnalysisService { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the DebugService instance for this session.
|
||||||
|
/// </summary>
|
||||||
|
public DebugService DebugService { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the ConsoleService instance for this session.
|
||||||
|
/// </summary>
|
||||||
|
public ConsoleService ConsoleService { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the ExtensionService instance for this session.
|
||||||
|
/// </summary>
|
||||||
|
public ExtensionService ExtensionService { get; private set; }
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Public Methods
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Starts the session using the provided IConsoleHost implementation
|
||||||
|
/// for the ConsoleService.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="hostDetails">
|
||||||
|
/// Provides details about the host application.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="profilePaths">
|
||||||
|
/// An object containing the profile paths for the session.
|
||||||
|
/// </param>
|
||||||
|
public void StartSession(HostDetails hostDetails, ProfilePaths profilePaths)
|
||||||
|
{
|
||||||
|
// Initialize all services
|
||||||
|
this.PowerShellContext = new PowerShellContext(hostDetails, profilePaths);
|
||||||
|
this.LanguageService = new LanguageService(this.PowerShellContext);
|
||||||
|
this.DebugService = new DebugService(this.PowerShellContext);
|
||||||
|
this.ConsoleService = new ConsoleService(this.PowerShellContext);
|
||||||
|
this.ExtensionService = new ExtensionService(this.PowerShellContext);
|
||||||
|
|
||||||
|
this.InstantiateAnalysisService();
|
||||||
|
|
||||||
|
// Create a workspace to contain open files
|
||||||
|
this.Workspace = new Workspace(this.PowerShellContext.PowerShellVersion);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Restarts the AnalysisService so it can be configured with a new settings file.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="settingsPath">Path to the settings file.</param>
|
||||||
|
public void RestartAnalysisService(string settingsPath)
|
||||||
|
{
|
||||||
|
this.AnalysisService?.Dispose();
|
||||||
|
InstantiateAnalysisService(settingsPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void InstantiateAnalysisService(string settingsPath = null)
|
||||||
|
{
|
||||||
|
// Only enable the AnalysisService if the machine has PowerShell
|
||||||
|
// v5 installed. Script Analyzer works on earlier PowerShell
|
||||||
|
// versions but our hard dependency on their binaries complicates
|
||||||
|
// the deployment and assembly loading since we would have to
|
||||||
|
// conditionally load the binaries for v3/v4 support. This problem
|
||||||
|
// will be solved in the future by using Script Analyzer as a
|
||||||
|
// module rather than an assembly dependency.
|
||||||
|
if (this.PowerShellContext.PowerShellVersion.Major >= 5)
|
||||||
|
{
|
||||||
|
// AnalysisService will throw FileNotFoundException if
|
||||||
|
// Script Analyzer binaries are not included.
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.AnalysisService = new AnalysisService(this.PowerShellContext.ConsoleHost, settingsPath);
|
||||||
|
}
|
||||||
|
catch (FileNotFoundException)
|
||||||
|
{
|
||||||
|
Logger.Write(
|
||||||
|
LogLevel.Warning,
|
||||||
|
"Script Analyzer binaries not found, AnalysisService will be disabled.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Logger.Write(
|
||||||
|
LogLevel.Normal,
|
||||||
|
"Script Analyzer cannot be loaded due to unsupported PowerShell version " +
|
||||||
|
this.PowerShellContext.PowerShellVersion.ToString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region IDisposable Implementation
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Disposes of any Runspaces that were created for the
|
||||||
|
/// services used in this session.
|
||||||
|
/// </summary>
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
if (this.AnalysisService != null)
|
||||||
|
{
|
||||||
|
this.AnalysisService.Dispose();
|
||||||
|
this.AnalysisService = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.PowerShellContext != null)
|
||||||
|
{
|
||||||
|
this.PowerShellContext.Dispose();
|
||||||
|
this.PowerShellContext = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
92
ServiceHost/Session/HostDetails.cs
Normal file
92
ServiceHost/Session/HostDetails.cs
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
//
|
||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
//
|
||||||
|
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Microsoft.PowerShell.EditorServices.Session
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Contains details about the current host application (most
|
||||||
|
/// likely the editor which is using the host process).
|
||||||
|
/// </summary>
|
||||||
|
public class HostDetails
|
||||||
|
{
|
||||||
|
#region Constants
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The default host name for PowerShell Editor Services. Used
|
||||||
|
/// if no host name is specified by the host application.
|
||||||
|
/// </summary>
|
||||||
|
public const string DefaultHostName = "PowerShell Editor Services Host";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The default host ID for PowerShell Editor Services. Used
|
||||||
|
/// for the host-specific profile path if no host ID is specified.
|
||||||
|
/// </summary>
|
||||||
|
public const string DefaultHostProfileId = "Microsoft.PowerShellEditorServices";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The default host version for PowerShell Editor Services. If
|
||||||
|
/// no version is specified by the host application, we use 0.0.0
|
||||||
|
/// to indicate a lack of version.
|
||||||
|
/// </summary>
|
||||||
|
public static readonly Version DefaultHostVersion = new Version("0.0.0");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The default host details in a HostDetails object.
|
||||||
|
/// </summary>
|
||||||
|
public static readonly HostDetails Default = new HostDetails(null, null, null);
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Properties
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the name of the host.
|
||||||
|
/// </summary>
|
||||||
|
public string Name { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the profile ID of the host, used to determine the
|
||||||
|
/// host-specific profile path.
|
||||||
|
/// </summary>
|
||||||
|
public string ProfileId { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the version of the host.
|
||||||
|
/// </summary>
|
||||||
|
public Version Version { get; private set; }
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Constructors
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates an instance of the HostDetails class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name">
|
||||||
|
/// The display name for the host, typically in the form of
|
||||||
|
/// "[Application Name] Host".
|
||||||
|
/// </param>
|
||||||
|
/// <param name="profileId">
|
||||||
|
/// The identifier of the PowerShell host to use for its profile path.
|
||||||
|
/// loaded. Used to resolve a profile path of the form 'X_profile.ps1'
|
||||||
|
/// where 'X' represents the value of hostProfileId. If null, a default
|
||||||
|
/// will be used.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="version">The host application's version.</param>
|
||||||
|
public HostDetails(
|
||||||
|
string name,
|
||||||
|
string profileId,
|
||||||
|
Version version)
|
||||||
|
{
|
||||||
|
this.Name = name ?? DefaultHostName;
|
||||||
|
this.ProfileId = profileId ?? DefaultHostProfileId;
|
||||||
|
this.Version = version ?? DefaultHostVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
41
ServiceHost/Session/OutputType.cs
Normal file
41
ServiceHost/Session/OutputType.cs
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
//
|
||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
//
|
||||||
|
|
||||||
|
namespace Microsoft.PowerShell.EditorServices
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Enumerates the types of output lines that will be sent
|
||||||
|
/// to an IConsoleHost implementation.
|
||||||
|
/// </summary>
|
||||||
|
public enum OutputType
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A normal output line, usually written with the or Write-Host or
|
||||||
|
/// Write-Output cmdlets.
|
||||||
|
/// </summary>
|
||||||
|
Normal,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A debug output line, written with the Write-Debug cmdlet.
|
||||||
|
/// </summary>
|
||||||
|
Debug,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A verbose output line, written with the Write-Verbose cmdlet.
|
||||||
|
/// </summary>
|
||||||
|
Verbose,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A warning output line, written with the Write-Warning cmdlet.
|
||||||
|
/// </summary>
|
||||||
|
Warning,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An error output line, written with the Write-Error cmdlet or
|
||||||
|
/// as a result of some error during PowerShell pipeline execution.
|
||||||
|
/// </summary>
|
||||||
|
Error
|
||||||
|
}
|
||||||
|
}
|
||||||
65
ServiceHost/Session/OutputWrittenEventArgs.cs
Normal file
65
ServiceHost/Session/OutputWrittenEventArgs.cs
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
//
|
||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
//
|
||||||
|
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Microsoft.PowerShell.EditorServices
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Provides details about output that has been written to the
|
||||||
|
/// PowerShell host.
|
||||||
|
/// </summary>
|
||||||
|
public class OutputWrittenEventArgs
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the text of the output.
|
||||||
|
/// </summary>
|
||||||
|
public string OutputText { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the type of the output.
|
||||||
|
/// </summary>
|
||||||
|
public OutputType OutputType { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a boolean which indicates whether a newline
|
||||||
|
/// should be written after the output.
|
||||||
|
/// </summary>
|
||||||
|
public bool IncludeNewLine { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the foreground color of the output text.
|
||||||
|
/// </summary>
|
||||||
|
public ConsoleColor ForegroundColor { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the background color of the output text.
|
||||||
|
/// </summary>
|
||||||
|
public ConsoleColor BackgroundColor { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates an instance of the OutputWrittenEventArgs class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="outputText">The text of the output.</param>
|
||||||
|
/// <param name="includeNewLine">A boolean which indicates whether a newline should be written after the output.</param>
|
||||||
|
/// <param name="outputType">The type of the output.</param>
|
||||||
|
/// <param name="foregroundColor">The foreground color of the output text.</param>
|
||||||
|
/// <param name="backgroundColor">The background color of the output text.</param>
|
||||||
|
public OutputWrittenEventArgs(
|
||||||
|
string outputText,
|
||||||
|
bool includeNewLine,
|
||||||
|
OutputType outputType,
|
||||||
|
ConsoleColor foregroundColor,
|
||||||
|
ConsoleColor backgroundColor)
|
||||||
|
{
|
||||||
|
this.OutputText = outputText;
|
||||||
|
this.IncludeNewLine = includeNewLine;
|
||||||
|
this.OutputType = outputType;
|
||||||
|
this.ForegroundColor = foregroundColor;
|
||||||
|
this.BackgroundColor = backgroundColor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
109
ServiceHost/Session/ProfilePaths.cs
Normal file
109
ServiceHost/Session/ProfilePaths.cs
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
//
|
||||||
|
// 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.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace Microsoft.PowerShell.EditorServices.Session
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Provides profile path resolution behavior relative to the name
|
||||||
|
/// of a particular PowerShell host.
|
||||||
|
/// </summary>
|
||||||
|
public class ProfilePaths
|
||||||
|
{
|
||||||
|
#region Constants
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The file name for the "all hosts" profile. Also used as the
|
||||||
|
/// suffix for the host-specific profile filenames.
|
||||||
|
/// </summary>
|
||||||
|
public const string AllHostsProfileName = "profile.ps1";
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Properties
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the profile path for all users, all hosts.
|
||||||
|
/// </summary>
|
||||||
|
public string AllUsersAllHosts { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the profile path for all users, current host.
|
||||||
|
/// </summary>
|
||||||
|
public string AllUsersCurrentHost { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the profile path for the current user, all hosts.
|
||||||
|
/// </summary>
|
||||||
|
public string CurrentUserAllHosts { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the profile path for the current user and host.
|
||||||
|
/// </summary>
|
||||||
|
public string CurrentUserCurrentHost { get; private set; }
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Public Methods
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new instance of the ProfilePaths class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="hostProfileId">
|
||||||
|
/// The identifier of the host used in the host-specific X_profile.ps1 filename.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="baseAllUsersPath">The base path to use for constructing AllUsers profile paths.</param>
|
||||||
|
/// <param name="baseCurrentUserPath">The base path to use for constructing CurrentUser profile paths.</param>
|
||||||
|
public ProfilePaths(
|
||||||
|
string hostProfileId,
|
||||||
|
string baseAllUsersPath,
|
||||||
|
string baseCurrentUserPath)
|
||||||
|
{
|
||||||
|
this.Initialize(hostProfileId, baseAllUsersPath, baseCurrentUserPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Initialize(
|
||||||
|
string hostProfileId,
|
||||||
|
string baseAllUsersPath,
|
||||||
|
string baseCurrentUserPath)
|
||||||
|
{
|
||||||
|
string currentHostProfileName =
|
||||||
|
string.Format(
|
||||||
|
"{0}_{1}",
|
||||||
|
hostProfileId,
|
||||||
|
AllHostsProfileName);
|
||||||
|
|
||||||
|
this.AllUsersCurrentHost = Path.Combine(baseAllUsersPath, currentHostProfileName);
|
||||||
|
this.CurrentUserCurrentHost = Path.Combine(baseCurrentUserPath, currentHostProfileName);
|
||||||
|
this.AllUsersAllHosts = Path.Combine(baseAllUsersPath, AllHostsProfileName);
|
||||||
|
this.CurrentUserAllHosts = Path.Combine(baseCurrentUserPath, AllHostsProfileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the list of profile paths that exist on the filesystem.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>An IEnumerable of profile path strings to be loaded.</returns>
|
||||||
|
public IEnumerable<string> GetLoadableProfilePaths()
|
||||||
|
{
|
||||||
|
var profilePaths =
|
||||||
|
new string[]
|
||||||
|
{
|
||||||
|
this.AllUsersAllHosts,
|
||||||
|
this.AllUsersCurrentHost,
|
||||||
|
this.CurrentUserAllHosts,
|
||||||
|
this.CurrentUserCurrentHost
|
||||||
|
};
|
||||||
|
|
||||||
|
return profilePaths.Where(p => File.Exists(p));
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
52
ServiceHost/Utility/AsyncContext.cs
Normal file
52
ServiceHost/Utility/AsyncContext.cs
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
//
|
||||||
|
// 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;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Microsoft.PowerShell.EditorServices.Utility
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Simplifies the setup of a SynchronizationContext for the use
|
||||||
|
/// of async calls in the current thread.
|
||||||
|
/// </summary>
|
||||||
|
public static class AsyncContext
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Starts a new ThreadSynchronizationContext, attaches it to
|
||||||
|
/// the thread, and then runs the given async main function.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="asyncMainFunc">
|
||||||
|
/// The Task-returning Func which represents the "main" function
|
||||||
|
/// for the thread.
|
||||||
|
/// </param>
|
||||||
|
public static void Start(Func<Task> asyncMainFunc)
|
||||||
|
{
|
||||||
|
// Is there already a synchronization context?
|
||||||
|
if (SynchronizationContext.Current != null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
"A SynchronizationContext is already assigned on this thread.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create and register a synchronization context for this thread
|
||||||
|
var threadSyncContext = new ThreadSynchronizationContext();
|
||||||
|
SynchronizationContext.SetSynchronizationContext(threadSyncContext);
|
||||||
|
|
||||||
|
// Get the main task and act on its completion
|
||||||
|
Task asyncMainTask = asyncMainFunc();
|
||||||
|
asyncMainTask.ContinueWith(
|
||||||
|
t => threadSyncContext.EndLoop(),
|
||||||
|
TaskScheduler.Default);
|
||||||
|
|
||||||
|
// Start the synchronization context's request loop and
|
||||||
|
// wait for the main task to complete
|
||||||
|
threadSyncContext.RunLoopOnCurrentThread();
|
||||||
|
asyncMainTask.GetAwaiter().GetResult();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
85
ServiceHost/Utility/AsyncContextThread.cs
Normal file
85
ServiceHost/Utility/AsyncContextThread.cs
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
//
|
||||||
|
// 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;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Microsoft.PowerShell.EditorServices.Utility
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Provides a simplified interface for creating a new thread
|
||||||
|
/// and establishing an AsyncContext in it.
|
||||||
|
/// </summary>
|
||||||
|
public class AsyncContextThread
|
||||||
|
{
|
||||||
|
#region Private Fields
|
||||||
|
|
||||||
|
private Task threadTask;
|
||||||
|
private string threadName;
|
||||||
|
private CancellationTokenSource threadCancellationToken =
|
||||||
|
new CancellationTokenSource();
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Constructors
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the AsyncContextThread class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="threadName">
|
||||||
|
/// The name of the thread for debugging purposes.
|
||||||
|
/// </param>
|
||||||
|
public AsyncContextThread(string threadName)
|
||||||
|
{
|
||||||
|
this.threadName = threadName;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Public Methods
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Runs a task on the AsyncContextThread.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="taskReturningFunc">
|
||||||
|
/// A Func which returns the task to be run on the thread.
|
||||||
|
/// </param>
|
||||||
|
/// <returns>
|
||||||
|
/// A Task which can be used to monitor the thread for completion.
|
||||||
|
/// </returns>
|
||||||
|
public Task Run(Func<Task> taskReturningFunc)
|
||||||
|
{
|
||||||
|
// Start up a long-running task with the action as the
|
||||||
|
// main entry point for the thread
|
||||||
|
this.threadTask =
|
||||||
|
Task.Factory.StartNew(
|
||||||
|
() =>
|
||||||
|
{
|
||||||
|
// Set the thread's name to help with debugging
|
||||||
|
Thread.CurrentThread.Name = "AsyncContextThread: " + this.threadName;
|
||||||
|
|
||||||
|
// Set up an AsyncContext to run the task
|
||||||
|
AsyncContext.Start(taskReturningFunc);
|
||||||
|
},
|
||||||
|
this.threadCancellationToken.Token,
|
||||||
|
TaskCreationOptions.LongRunning,
|
||||||
|
TaskScheduler.Default);
|
||||||
|
|
||||||
|
return this.threadTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Stops the thread task.
|
||||||
|
/// </summary>
|
||||||
|
public void Stop()
|
||||||
|
{
|
||||||
|
this.threadCancellationToken.Cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
169
ServiceHost/Utility/AsyncDebouncer.cs
Normal file
169
ServiceHost/Utility/AsyncDebouncer.cs
Normal file
@@ -0,0 +1,169 @@
|
|||||||
|
//
|
||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
//
|
||||||
|
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Microsoft.PowerShell.EditorServices.Utility
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Restricts the invocation of an operation to a specified time
|
||||||
|
/// interval. Can also cause previous requests to be cancelled
|
||||||
|
/// by new requests within that time window. Typically used for
|
||||||
|
/// buffering information for an operation or ensuring that an
|
||||||
|
/// operation only runs after some interval.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TInvokeArgs">The argument type for the Invoke method.</typeparam>
|
||||||
|
public abstract class AsyncDebouncer<TInvokeArgs>
|
||||||
|
{
|
||||||
|
#region Private Fields
|
||||||
|
|
||||||
|
private int flushInterval;
|
||||||
|
private bool restartOnInvoke;
|
||||||
|
|
||||||
|
private Task currentTimerTask;
|
||||||
|
private CancellationTokenSource timerCancellationSource;
|
||||||
|
|
||||||
|
private AsyncLock asyncLock = new AsyncLock();
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Public Methods
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new instance of the AsyncDebouncer class with the
|
||||||
|
/// specified flush interval. If restartOnInvoke is true, any
|
||||||
|
/// calls to Invoke will cancel previous calls which have not yet
|
||||||
|
/// passed the flush interval.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="flushInterval">
|
||||||
|
/// A millisecond interval to use for flushing prior Invoke calls.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="restartOnInvoke">
|
||||||
|
/// If true, Invoke calls will reset prior calls which haven't passed the flush interval.
|
||||||
|
/// </param>
|
||||||
|
public AsyncDebouncer(int flushInterval, bool restartOnInvoke)
|
||||||
|
{
|
||||||
|
this.flushInterval = flushInterval;
|
||||||
|
this.restartOnInvoke = restartOnInvoke;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invokes the debouncer with the given input. The debouncer will
|
||||||
|
/// wait for the specified interval before calling the Flush method
|
||||||
|
/// to complete the operation.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="invokeArgument">
|
||||||
|
/// The argument for this implementation's Invoke method.
|
||||||
|
/// </param>
|
||||||
|
/// <returns>A Task to be awaited until the Invoke is queued.</returns>
|
||||||
|
public async Task Invoke(TInvokeArgs invokeArgument)
|
||||||
|
{
|
||||||
|
using (await this.asyncLock.LockAsync())
|
||||||
|
{
|
||||||
|
// Invoke the implementor
|
||||||
|
await this.OnInvoke(invokeArgument);
|
||||||
|
|
||||||
|
// If there's no timer, start one
|
||||||
|
if (this.currentTimerTask == null)
|
||||||
|
{
|
||||||
|
this.StartTimer();
|
||||||
|
}
|
||||||
|
else if (this.currentTimerTask != null && this.restartOnInvoke)
|
||||||
|
{
|
||||||
|
// Restart the existing timer
|
||||||
|
if (this.CancelTimer())
|
||||||
|
{
|
||||||
|
this.StartTimer();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Flushes the latest state regardless of the current interval.
|
||||||
|
/// An AsyncDebouncer MUST NOT invoke its own Flush method otherwise
|
||||||
|
/// deadlocks could occur.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>A Task to be awaited until Flush completes.</returns>
|
||||||
|
public async Task Flush()
|
||||||
|
{
|
||||||
|
using (await this.asyncLock.LockAsync())
|
||||||
|
{
|
||||||
|
// Cancel the current timer
|
||||||
|
this.CancelTimer();
|
||||||
|
|
||||||
|
// Flush the current output
|
||||||
|
await this.OnFlush();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Abstract Methods
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Implemented by the subclass to take the argument for the
|
||||||
|
/// future operation that will be performed by OnFlush.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="invokeArgument">
|
||||||
|
/// The argument for this implementation's OnInvoke method.
|
||||||
|
/// </param>
|
||||||
|
/// <returns>A Task to be awaited for the invoke to complete.</returns>
|
||||||
|
protected abstract Task OnInvoke(TInvokeArgs invokeArgument);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Implemented by the subclass to complete the current operation.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>A Task to be awaited for the operation to complete.</returns>
|
||||||
|
protected abstract Task OnFlush();
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Private Methods
|
||||||
|
|
||||||
|
private void StartTimer()
|
||||||
|
{
|
||||||
|
this.timerCancellationSource = new CancellationTokenSource();
|
||||||
|
|
||||||
|
this.currentTimerTask =
|
||||||
|
Task.Delay(this.flushInterval, this.timerCancellationSource.Token)
|
||||||
|
.ContinueWith(
|
||||||
|
t =>
|
||||||
|
{
|
||||||
|
if (!t.IsCanceled)
|
||||||
|
{
|
||||||
|
return this.Flush();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return Task.FromResult(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool CancelTimer()
|
||||||
|
{
|
||||||
|
if (this.timerCancellationSource != null)
|
||||||
|
{
|
||||||
|
// Attempt to cancel the timer task
|
||||||
|
this.timerCancellationSource.Cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Was the task cancelled?
|
||||||
|
bool wasCancelled =
|
||||||
|
this.currentTimerTask == null ||
|
||||||
|
this.currentTimerTask.IsCanceled;
|
||||||
|
|
||||||
|
// Clear the current task so that another may be created
|
||||||
|
this.currentTimerTask = null;
|
||||||
|
|
||||||
|
return wasCancelled;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
103
ServiceHost/Utility/AsyncLock.cs
Normal file
103
ServiceHost/Utility/AsyncLock.cs
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
//
|
||||||
|
// 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;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Microsoft.PowerShell.EditorServices.Utility
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Provides a simple wrapper over a SemaphoreSlim to allow
|
||||||
|
/// synchronization locking inside of async calls. Cannot be
|
||||||
|
/// used recursively.
|
||||||
|
/// </summary>
|
||||||
|
public class AsyncLock
|
||||||
|
{
|
||||||
|
#region Fields
|
||||||
|
|
||||||
|
private Task<IDisposable> lockReleaseTask;
|
||||||
|
private SemaphoreSlim lockSemaphore = new SemaphoreSlim(1, 1);
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Constructors
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the AsyncLock class.
|
||||||
|
/// </summary>
|
||||||
|
public AsyncLock()
|
||||||
|
{
|
||||||
|
this.lockReleaseTask =
|
||||||
|
Task.FromResult(
|
||||||
|
(IDisposable)new LockReleaser(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Public Methods
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Locks
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>A task which has an IDisposable</returns>
|
||||||
|
public Task<IDisposable> LockAsync()
|
||||||
|
{
|
||||||
|
return this.LockAsync(CancellationToken.None);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Obtains or waits for a lock which can be used to synchronize
|
||||||
|
/// access to a resource. The wait may be cancelled with the
|
||||||
|
/// given CancellationToken.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="cancellationToken">
|
||||||
|
/// A CancellationToken which can be used to cancel the lock.
|
||||||
|
/// </param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public Task<IDisposable> LockAsync(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
Task waitTask = lockSemaphore.WaitAsync(cancellationToken);
|
||||||
|
|
||||||
|
return waitTask.IsCompleted ?
|
||||||
|
this.lockReleaseTask :
|
||||||
|
waitTask.ContinueWith(
|
||||||
|
(t, releaser) =>
|
||||||
|
{
|
||||||
|
return (IDisposable)releaser;
|
||||||
|
},
|
||||||
|
this.lockReleaseTask.Result,
|
||||||
|
cancellationToken,
|
||||||
|
TaskContinuationOptions.ExecuteSynchronously,
|
||||||
|
TaskScheduler.Default);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Private Classes
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Provides an IDisposable wrapper around an AsyncLock so
|
||||||
|
/// that it can easily be used inside of a 'using' block.
|
||||||
|
/// </summary>
|
||||||
|
private class LockReleaser : IDisposable
|
||||||
|
{
|
||||||
|
private AsyncLock lockToRelease;
|
||||||
|
|
||||||
|
internal LockReleaser(AsyncLock lockToRelease)
|
||||||
|
{
|
||||||
|
this.lockToRelease = lockToRelease;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
this.lockToRelease.lockSemaphore.Release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
155
ServiceHost/Utility/AsyncQueue.cs
Normal file
155
ServiceHost/Utility/AsyncQueue.cs
Normal file
@@ -0,0 +1,155 @@
|
|||||||
|
//
|
||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
//
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Microsoft.PowerShell.EditorServices.Utility
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Provides a synchronized queue which can be used from within async
|
||||||
|
/// operations. This is primarily used for producer/consumer scenarios.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The type of item contained in the queue.</typeparam>
|
||||||
|
public class AsyncQueue<T>
|
||||||
|
{
|
||||||
|
#region Private Fields
|
||||||
|
|
||||||
|
private AsyncLock queueLock = new AsyncLock();
|
||||||
|
private Queue<T> itemQueue;
|
||||||
|
private Queue<TaskCompletionSource<T>> requestQueue;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Properties
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns true if the queue is currently empty.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsEmpty { get; private set; }
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Constructors
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes an empty instance of the AsyncQueue class.
|
||||||
|
/// </summary>
|
||||||
|
public AsyncQueue() : this(Enumerable.Empty<T>())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes an instance of the AsyncQueue class, pre-populated
|
||||||
|
/// with the given collection of items.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="initialItems">
|
||||||
|
/// An IEnumerable containing the initial items with which the queue will
|
||||||
|
/// be populated.
|
||||||
|
/// </param>
|
||||||
|
public AsyncQueue(IEnumerable<T> initialItems)
|
||||||
|
{
|
||||||
|
this.itemQueue = new Queue<T>(initialItems);
|
||||||
|
this.requestQueue = new Queue<TaskCompletionSource<T>>();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Public Methods
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Enqueues an item onto the end of the queue.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="item">The item to be added to the queue.</param>
|
||||||
|
/// <returns>
|
||||||
|
/// A Task which can be awaited until the synchronized enqueue
|
||||||
|
/// operation completes.
|
||||||
|
/// </returns>
|
||||||
|
public async Task EnqueueAsync(T item)
|
||||||
|
{
|
||||||
|
using (await queueLock.LockAsync())
|
||||||
|
{
|
||||||
|
TaskCompletionSource<T> requestTaskSource = null;
|
||||||
|
|
||||||
|
// Are any requests waiting?
|
||||||
|
while (this.requestQueue.Count > 0)
|
||||||
|
{
|
||||||
|
// Is the next request cancelled already?
|
||||||
|
requestTaskSource = this.requestQueue.Dequeue();
|
||||||
|
if (!requestTaskSource.Task.IsCanceled)
|
||||||
|
{
|
||||||
|
// Dispatch the item
|
||||||
|
requestTaskSource.SetResult(item);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// No more requests waiting, queue the item for a later request
|
||||||
|
this.itemQueue.Enqueue(item);
|
||||||
|
this.IsEmpty = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Dequeues an item from the queue or waits asynchronously
|
||||||
|
/// until an item is available.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>
|
||||||
|
/// A Task which can be awaited until a value can be dequeued.
|
||||||
|
/// </returns>
|
||||||
|
public Task<T> DequeueAsync()
|
||||||
|
{
|
||||||
|
return this.DequeueAsync(CancellationToken.None);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Dequeues an item from the queue or waits asynchronously
|
||||||
|
/// until an item is available. The wait can be cancelled
|
||||||
|
/// using the given CancellationToken.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="cancellationToken">
|
||||||
|
/// A CancellationToken with which a dequeue wait can be cancelled.
|
||||||
|
/// </param>
|
||||||
|
/// <returns>
|
||||||
|
/// A Task which can be awaited until a value can be dequeued.
|
||||||
|
/// </returns>
|
||||||
|
public async Task<T> DequeueAsync(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
Task<T> requestTask;
|
||||||
|
|
||||||
|
using (await queueLock.LockAsync(cancellationToken))
|
||||||
|
{
|
||||||
|
if (this.itemQueue.Count > 0)
|
||||||
|
{
|
||||||
|
// Items are waiting to be taken so take one immediately
|
||||||
|
T item = this.itemQueue.Dequeue();
|
||||||
|
this.IsEmpty = this.itemQueue.Count == 0;
|
||||||
|
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Queue the request for the next item
|
||||||
|
var requestTaskSource = new TaskCompletionSource<T>();
|
||||||
|
this.requestQueue.Enqueue(requestTaskSource);
|
||||||
|
|
||||||
|
// Register the wait task for cancel notifications
|
||||||
|
cancellationToken.Register(
|
||||||
|
() => requestTaskSource.TrySetCanceled());
|
||||||
|
|
||||||
|
requestTask = requestTaskSource.Task;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for the request task to complete outside of the lock
|
||||||
|
return await requestTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
34
ServiceHost/Utility/Extensions.cs
Normal file
34
ServiceHost/Utility/Extensions.cs
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
//
|
||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
//
|
||||||
|
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Microsoft.PowerShell.EditorServices.Utility
|
||||||
|
{
|
||||||
|
internal static class ObjectExtensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Extension to evaluate an object's ToString() method in an exception safe way. This will
|
||||||
|
/// extension method will not throw.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="obj">The object on which to call ToString()</param>
|
||||||
|
/// <returns>The ToString() return value or a suitable error message is that throws.</returns>
|
||||||
|
public static string SafeToString(this object obj)
|
||||||
|
{
|
||||||
|
string str;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
str = obj.ToString();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
str = $"<Error converting poperty value to string - {ex.Message}>";
|
||||||
|
}
|
||||||
|
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user