diff --git a/module/PowerShellEditorServices/PowerShellEditorServices.psm1 b/module/PowerShellEditorServices/PowerShellEditorServices.psm1
index b19caa8bd..46b20884d 100644
--- a/module/PowerShellEditorServices/PowerShellEditorServices.psm1
+++ b/module/PowerShellEditorServices/PowerShellEditorServices.psm1
@@ -31,16 +31,21 @@ function Start-EditorServicesHost {
[string]
$HostVersion,
- [Parameter(Mandatory=$true)]
- [ValidateNotNullOrEmpty()]
[int]
$LanguageServicePort,
- [Parameter(Mandatory=$true)]
- [ValidateNotNullOrEmpty()]
[int]
$DebugServicePort,
+ [switch]
+ $Stdio,
+
+ [string]
+ $LanguageServiceNamedPipe,
+
+ [string]
+ $DebugServiceNamedPipe,
+
[ValidateNotNullOrEmpty()]
[string]
$BundledModulesPath,
@@ -89,12 +94,39 @@ function Start-EditorServicesHost {
$editorServicesHost.StartLogging($LogPath, $LogLevel);
- if ($DebugServiceOnly.IsPresent) {
- $editorServicesHost.StartDebugService($DebugServicePort, $profilePaths, $false);
+ $languageServiceConfig = New-Object Microsoft.PowerShell.EditorServices.Host.EditorServiceTransportConfig
+ $debugServiceConfig = New-Object Microsoft.PowerShell.EditorServices.Host.EditorServiceTransportConfig
+
+ if ($Stdio.IsPresent) {
+ $languageServiceConfig.TransportType = [Microsoft.PowerShell.EditorServices.Host.EditorServiceTransportType]::Stdio
+ $debugServiceConfig.TransportType = [Microsoft.PowerShell.EditorServices.Host.EditorServiceTransportType]::Stdio
}
- else {
- $editorServicesHost.StartLanguageService($LanguageServicePort, $profilePaths);
- $editorServicesHost.StartDebugService($DebugServicePort, $profilePaths, $true);
+
+ if ($LanguageServicePort) {
+ $languageServiceConfig.TransportType = [Microsoft.PowerShell.EditorServices.Host.EditorServiceTransportType]::Tcp
+ $languageServiceConfig.Endpoint = "$LanguageServicePort"
+ }
+
+ if ($DebugServicePort) {
+ $debugServiceConfig.TransportType = [Microsoft.PowerShell.EditorServices.Host.EditorServiceTransportType]::Tcp
+ $debugServiceConfig.Endpoint = "$DebugServicePort"
+ }
+
+ if ($LanguageServiceNamedPipe) {
+ $languageServiceConfig.TransportType = [Microsoft.PowerShell.EditorServices.Host.EditorServiceTransportType]::NamedPipe
+ $languageServiceConfig.Endpoint = "$LanguageServiceNamedPipe"
+ }
+
+ if ($DebugServiceNamedPipe) {
+ $debugServiceConfig.TransportType = [Microsoft.PowerShell.EditorServices.Host.EditorServiceTransportType]::NamedPipe
+ $debugServiceConfig.Endpoint = "$DebugServiceNamedPipe"
+ }
+
+ if ($DebugServiceOnly.IsPresent) {
+ $editorServicesHost.StartDebugService($debugServiceConfig, $profilePaths, $false);
+ } else {
+ $editorServicesHost.StartLanguageService($languageServiceConfig, $profilePaths);
+ $editorServicesHost.StartDebugService($debugServiceConfig, $profilePaths, $true);
}
return $editorServicesHost
diff --git a/module/Start-EditorServices.ps1 b/module/Start-EditorServices.ps1
index a1f9152ab..c81d92920 100644
--- a/module/Start-EditorServices.ps1
+++ b/module/Start-EditorServices.ps1
@@ -68,7 +68,16 @@ param(
$WaitForDebugger,
[switch]
- $ConfirmInstall
+ $ConfirmInstall,
+
+ [switch]
+ $Stdio,
+
+ [string]
+ $LanguageServicePipeName = $null,
+
+ [string]
+ $DebugServicePipeName = $null
)
$minPortNumber = 10000
@@ -271,16 +280,21 @@ try {
Import-Module PowerShellEditorServices -Version $parsedVersion -ErrorAction Stop
}
- # Locate available port numbers for services
- Log "Searching for available socket port for the language service"
- $languageServicePort = Get-AvailablePort
+ # Locate available port numbers for services
+ # There could be only one service on Stdio channel
- Log "Searching for available socket port for the debug service"
- $debugServicePort = Get-AvailablePort
+ $languageServiceTransport = $null
+ $debugServiceTransport = $null
- if (!$languageServicePort -or !$debugServicePort) {
- ExitWithError "Failed to find an open socket port for either the language or debug service."
- }
+ if ($Stdio.IsPresent -and -not $DebugServiceOnly.IsPresent) { $languageServiceTransport = "Stdio" }
+ elseif ($LanguageServicePipeName) { $languageServiceTransport = "NamedPipe"; $languageServicePipeName = "$LanguageServicePipeName" }
+ elseif ($languageServicePort = Get-AvailablePort) { $languageServiceTransport = "Tcp" }
+ else { ExitWithError "Failed to find an open socket port for language service." }
+
+ if ($Stdio.IsPresent -and $DebugServiceOnly.IsPresent) { $debugServiceTransport = "Stdio" }
+ elseif ($DebugServicePipeName) { $debugServiceTransport = "NamedPipe"; $debugServicePipeName = "$DebugServicePipeName" }
+ elseif ($debugServicePort = Get-AvailablePort) { $debugServiceTransport = "Tcp" }
+ else { ExitWithError "Failed to find an open socket port for debug service." }
if ($EnableConsoleRepl) {
Write-Host "PowerShell Integrated Console`n"
@@ -298,6 +312,9 @@ try {
-AdditionalModules $AdditionalModules `
-LanguageServicePort $languageServicePort `
-DebugServicePort $debugServicePort `
+ -LanguageServiceNamedPipe $LanguageServicePipeName `
+ -DebugServiceNamedPipe $DebugServicePipeName `
+ -Stdio:$Stdio.IsPresent`
-BundledModulesPath $BundledModulesPath `
-EnableConsoleRepl:$EnableConsoleRepl.IsPresent `
-DebugServiceOnly:$DebugServiceOnly.IsPresent `
@@ -308,10 +325,15 @@ try {
$resultDetails = @{
"status" = "started";
- "channel" = "tcp";
- "languageServicePort" = $languageServicePort;
- "debugServicePort" = $debugServicePort;
- }
+ "languageServiceTransport" = $languageServiceTransport;
+ "debugServiceTransport" = $debugServiceTransport;
+ };
+
+ if ($languageServicePipeName) { $resultDetails["languageServicePipeName"] = "$languageServicePipeName" }
+ if ($debugServicePipeName) { $resultDetails["debugServicePipeName"] = "$debugServicePipeName" }
+
+ if ($languageServicePort) { $resultDetails["languageServicePort"] = $languageServicePort }
+ if ($debugServicePort) { $resultDetails["debugServicePort"] = $debugServicePort }
# Notify the client that the services have started
WriteSessionFile $resultDetails
diff --git a/src/PowerShellEditorServices.Host/EditorServicesHost.cs b/src/PowerShellEditorServices.Host/EditorServicesHost.cs
index c0275efce..cb7a0162b 100644
--- a/src/PowerShellEditorServices.Host/EditorServicesHost.cs
+++ b/src/PowerShellEditorServices.Host/EditorServicesHost.cs
@@ -29,6 +29,25 @@ public enum EditorServicesHostStatus
Ended
}
+ public enum EditorServiceTransportType
+ {
+ Tcp,
+ NamedPipe,
+ Stdio
+ }
+
+ public class EditorServiceTransportConfig
+ {
+ public EditorServiceTransportType TransportType { get; set; }
+ ///
+ /// Configures the endpoint of the transport.
+ /// For Tcp it's an integer specifying the port.
+ /// For Stdio it's ignored.
+ /// For NamedPipe it's the pipe name.
+ ///
+ public string Endpoint { get; set; }
+ }
+
///
/// Provides a simplified interface for hosting the language and debug services
/// over the named pipe server protocol.
@@ -48,8 +67,8 @@ public class EditorServicesHost
private HashSet featureFlags;
private LanguageServer languageServer;
- private TcpSocketServerListener languageServiceListener;
- private TcpSocketServerListener debugServiceListener;
+ private IServerListener languageServiceListener;
+ private IServerListener debugServiceListener;
private TaskCompletionSource serverCompletedTask;
@@ -164,15 +183,11 @@ public void StartLogging(string logFilePath, LogLevel logLevel)
///
/// The port number for the language service.
/// The object containing the profile paths to load for this session.
- public void StartLanguageService(int languageServicePort, ProfilePaths profilePaths)
+ public void StartLanguageService(EditorServiceTransportConfig config, ProfilePaths profilePaths)
{
this.profilePaths = profilePaths;
- this.languageServiceListener =
- new TcpSocketServerListener(
- MessageProtocolType.LanguageServer,
- languageServicePort,
- this.logger);
+ this.languageServiceListener = CreateServiceListener(MessageProtocolType.LanguageServer, config);
this.languageServiceListener.ClientConnect += this.OnLanguageServiceClientConnect;
this.languageServiceListener.Start();
@@ -180,13 +195,13 @@ public void StartLanguageService(int languageServicePort, ProfilePaths profilePa
this.logger.Write(
LogLevel.Normal,
string.Format(
- "Language service started, listening on port {0}",
- languageServicePort));
+ "Language service started, type = {0}, endpoint = {1}",
+ config.TransportType, config.Endpoint));
}
private async void OnLanguageServiceClientConnect(
object sender,
- TcpSocketServerChannel serverChannel)
+ ChannelBase serverChannel)
{
MessageDispatcher messageDispatcher = new MessageDispatcher(this.logger);
@@ -238,27 +253,22 @@ await this.editorSession.PowerShellContext.ImportCommandsModule(
///
/// The port number for the debug service.
public void StartDebugService(
- int debugServicePort,
+ EditorServiceTransportConfig config,
ProfilePaths profilePaths,
bool useExistingSession)
{
- this.debugServiceListener =
- new TcpSocketServerListener(
- MessageProtocolType.DebugAdapter,
- debugServicePort,
- this.logger);
-
+ this.debugServiceListener = CreateServiceListener(MessageProtocolType.DebugAdapter, config);
this.debugServiceListener.ClientConnect += OnDebugServiceClientConnect;
this.debugServiceListener.Start();
this.logger.Write(
LogLevel.Normal,
string.Format(
- "Debug service started, listening on port {0}",
- debugServicePort));
+ "Debug service started, type = {0}, endpoint = {1}",
+ config.TransportType, config.Endpoint));
}
- private void OnDebugServiceClientConnect(object sender, TcpSocketServerChannel serverChannel)
+ private void OnDebugServiceClientConnect(object sender, ChannelBase serverChannel)
{
MessageDispatcher messageDispatcher = new MessageDispatcher(this.logger);
@@ -441,6 +451,31 @@ private void CurrentDomain_UnhandledException(
e.ExceptionObject.ToString()));
}
#endif
+ private IServerListener CreateServiceListener(MessageProtocolType protocol, EditorServiceTransportConfig config)
+ {
+ switch (config.TransportType)
+ {
+ case EditorServiceTransportType.Tcp:
+ {
+ return new TcpSocketServerListener(protocol, int.Parse(config.Endpoint), this.logger);
+ }
+
+ case EditorServiceTransportType.Stdio:
+ {
+ return new StdioServerListener(protocol, this.logger);
+ }
+
+ case EditorServiceTransportType.NamedPipe:
+ {
+ return new NamedPipeServerListener(protocol, config.Endpoint, this.logger);
+ }
+
+ default:
+ {
+ throw new NotSupportedException();
+ }
+ }
+ }
#endregion
}
diff --git a/src/PowerShellEditorServices.Protocol/MessageProtocol/Channel/NamedPipeServerListener.cs b/src/PowerShellEditorServices.Protocol/MessageProtocol/Channel/NamedPipeServerListener.cs
index cf1cb3e4c..1f17b749c 100644
--- a/src/PowerShellEditorServices.Protocol/MessageProtocol/Channel/NamedPipeServerListener.cs
+++ b/src/PowerShellEditorServices.Protocol/MessageProtocol/Channel/NamedPipeServerListener.cs
@@ -38,6 +38,7 @@ public override void Start()
1,
PipeTransmissionMode.Byte,
PipeOptions.Asynchronous);
+ ListenForConnection();
}
catch (IOException e)
{
diff --git a/src/PowerShellEditorServices.Protocol/MessageProtocol/Channel/ServerListenerBase.cs b/src/PowerShellEditorServices.Protocol/MessageProtocol/Channel/ServerListenerBase.cs
index de2b034f0..433f6aabb 100644
--- a/src/PowerShellEditorServices.Protocol/MessageProtocol/Channel/ServerListenerBase.cs
+++ b/src/PowerShellEditorServices.Protocol/MessageProtocol/Channel/ServerListenerBase.cs
@@ -8,7 +8,7 @@
namespace Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol.Channel
{
- public abstract class ServerListenerBase
+ public abstract class ServerListenerBase : IServerListener
where TChannel : ChannelBase
{
private MessageProtocolType messageProtocolType;
@@ -22,7 +22,7 @@ public ServerListenerBase(MessageProtocolType messageProtocolType)
public abstract void Stop();
- public event EventHandler ClientConnect;
+ public event EventHandler ClientConnect;
protected void OnClientConnect(TChannel channel)
{
@@ -30,4 +30,13 @@ protected void OnClientConnect(TChannel channel)
this.ClientConnect?.Invoke(this, channel);
}
}
+
+ public interface IServerListener
+ {
+ void Start();
+
+ void Stop();
+
+ event EventHandler ClientConnect;
+ }
}
\ No newline at end of file
diff --git a/src/PowerShellEditorServices.Protocol/Server/LanguageServer.cs b/src/PowerShellEditorServices.Protocol/Server/LanguageServer.cs
index f551d4884..a31173457 100644
--- a/src/PowerShellEditorServices.Protocol/Server/LanguageServer.cs
+++ b/src/PowerShellEditorServices.Protocol/Server/LanguageServer.cs
@@ -1362,13 +1362,16 @@ private static FileChange GetFileChangeDetails(Range changeRange, string insertS
{
// The protocol's positions are zero-based so add 1 to all offsets
+ if (changeRange == null) return new FileChange { InsertString = insertString, IsReload = true };
+
return new FileChange
{
InsertString = insertString,
Line = changeRange.Start.Line + 1,
Offset = changeRange.Start.Character + 1,
EndLine = changeRange.End.Line + 1,
- EndOffset = changeRange.End.Character + 1
+ EndOffset = changeRange.End.Character + 1,
+ IsReload = false
};
}
diff --git a/src/PowerShellEditorServices/Workspace/FileChange.cs b/src/PowerShellEditorServices/Workspace/FileChange.cs
index 3bec7982e..79f6925ea 100644
--- a/src/PowerShellEditorServices/Workspace/FileChange.cs
+++ b/src/PowerShellEditorServices/Workspace/FileChange.cs
@@ -34,5 +34,12 @@ public class FileChange
/// The 1-based column offset where the change ends.
///
public int EndOffset { get; set; }
+
+ ///
+ /// Indicates that the InsertString is an overwrite
+ /// of the content, and all stale content and metadata
+ /// should be discarded.
+ ///
+ public bool IsReload { get; set; }
}
}
diff --git a/src/PowerShellEditorServices/Workspace/ScriptFile.cs b/src/PowerShellEditorServices/Workspace/ScriptFile.cs
index 19060fb01..fc290cff0 100644
--- a/src/PowerShellEditorServices/Workspace/ScriptFile.cs
+++ b/src/PowerShellEditorServices/Workspace/ScriptFile.cs
@@ -63,7 +63,7 @@ public string Id
///
/// Gets a string containing the full contents of the file.
///
- public string Contents
+ public string Contents
{
get
{
@@ -175,7 +175,7 @@ public ScriptFile(
/// The path at which the script file resides.
/// The path which the client uses to identify the file.
/// The version of PowerShell for which the script is being parsed.
- public ScriptFile (
+ public ScriptFile(
string filePath,
string clientFilePath,
Version powerShellVersion)
@@ -184,9 +184,9 @@ public ScriptFile (
clientFilePath,
File.ReadAllText(filePath),
powerShellVersion)
- {
+ {
- }
+ }
#endregion
@@ -315,52 +315,64 @@ public void ValidatePosition(int line, int column)
/// The FileChange to apply to the file's contents.
public void ApplyChange(FileChange fileChange)
{
- this.ValidatePosition(fileChange.Line, fileChange.Offset);
- this.ValidatePosition(fileChange.EndLine, fileChange.EndOffset);
-
// Break up the change lines
string[] changeLines = fileChange.InsertString.Split('\n');
- // Get the first fragment of the first line
- string firstLineFragment =
+ if (fileChange.IsReload)
+ {
+ this.FileLines.Clear();
+ foreach (var changeLine in changeLines)
+ {
+ this.FileLines.Add(changeLine);
+ }
+ }
+ else
+ {
+ this.ValidatePosition(fileChange.Line, fileChange.Offset);
+ this.ValidatePosition(fileChange.EndLine, fileChange.EndOffset);
+
+ // Get the first fragment of the first line
+ string firstLineFragment =
this.FileLines[fileChange.Line - 1]
.Substring(0, fileChange.Offset - 1);
- // Get the last fragment of the last line
- string endLine = this.FileLines[fileChange.EndLine - 1];
- string lastLineFragment =
+ // Get the last fragment of the last line
+ string endLine = this.FileLines[fileChange.EndLine - 1];
+ string lastLineFragment =
endLine.Substring(
- fileChange.EndOffset - 1,
+ fileChange.EndOffset - 1,
(this.FileLines[fileChange.EndLine - 1].Length - fileChange.EndOffset) + 1);
- // Remove the old lines
- for (int i = 0; i <= fileChange.EndLine - fileChange.Line; i++)
- {
- this.FileLines.RemoveAt(fileChange.Line - 1);
- }
-
- // Build and insert the new lines
- int currentLineNumber = fileChange.Line;
- for (int changeIndex = 0; changeIndex < changeLines.Length; changeIndex++)
- {
- // Since we split the lines above using \n, make sure to
- // trim the ending \r's off as well.
- string finalLine = changeLines[changeIndex].TrimEnd('\r');
-
- // Should we add first or last line fragments?
- if (changeIndex == 0)
+ // Remove the old lines
+ for (int i = 0; i <= fileChange.EndLine - fileChange.Line; i++)
{
- // Append the first line fragment
- finalLine = firstLineFragment + finalLine;
+ this.FileLines.RemoveAt(fileChange.Line - 1);
}
- if (changeIndex == changeLines.Length - 1)
+
+ // Build and insert the new lines
+ int currentLineNumber = fileChange.Line;
+ for (int changeIndex = 0; changeIndex < changeLines.Length; changeIndex++)
{
- // Append the last line fragment
- finalLine = finalLine + lastLineFragment;
+ // Since we split the lines above using \n, make sure to
+ // trim the ending \r's off as well.
+ string finalLine = changeLines[changeIndex].TrimEnd('\r');
+
+ // Should we add first or last line fragments?
+ if (changeIndex == 0)
+ {
+ // Append the first line fragment
+ finalLine = firstLineFragment + finalLine;
+ }
+ if (changeIndex == changeLines.Length - 1)
+ {
+ // Append the last line fragment
+ finalLine = finalLine + lastLineFragment;
+ }
+
+ this.FileLines.Insert(currentLineNumber - 1, finalLine);
+ currentLineNumber++;
}
- this.FileLines.Insert(currentLineNumber - 1, finalLine);
- currentLineNumber++;
}
// Parse the script again to be up-to-date
@@ -381,12 +393,12 @@ public int GetOffsetAtPosition(int lineNumber, int columnNumber)
int offset = 0;
- for(int i = 0; i < lineNumber; i++)
+ for (int i = 0; i < lineNumber; i++)
{
if (i == lineNumber - 1)
{
// Subtract 1 to account for 1-based column numbering
- offset += columnNumber - 1;
+ offset += columnNumber - 1;
}
else
{
@@ -430,7 +442,7 @@ public FilePosition CalculatePosition(
/// A new BufferPosition containing the position of the offset.
public BufferPosition GetPositionAtOffset(int bufferOffset)
{
- BufferRange bufferRange =
+ BufferRange bufferRange =
GetRangeBetweenOffsets(
bufferOffset, bufferOffset);
@@ -572,7 +584,7 @@ private void ParseFileContents()
var parseError =
new ParseError(
null,
- ex.ErrorRecord.FullyQualifiedErrorId,
+ ex.ErrorRecord.FullyQualifiedErrorId,
ex.Message);
parseErrors = new[] { parseError };
@@ -585,12 +597,12 @@ private void ParseFileContents()
parseErrors
.Select(ScriptFileMarker.FromParseError)
.ToArray();
-
+
//Get all dot sourced referenced files and store them
this.ReferencedFiles =
AstOperations.FindDotSourcedIncludes(this.ScriptAst);
}
-#endregion
+ #endregion
}
}
diff --git a/test/PowerShellEditorServices.Test.Host/ServerTestsBase.cs b/test/PowerShellEditorServices.Test.Host/ServerTestsBase.cs
index 741b69abe..e02f6f8d2 100644
--- a/test/PowerShellEditorServices.Test.Host/ServerTestsBase.cs
+++ b/test/PowerShellEditorServices.Test.Host/ServerTestsBase.cs
@@ -14,6 +14,9 @@
using System.Text;
using System.Threading;
using System.Threading.Tasks;
+#if CoreCLR
+using System.Reflection;
+#endif
namespace Microsoft.PowerShell.EditorServices.Test.Host
{