diff --git a/.travis.yml b/.travis.yml
index 8209d54c8..b8ee9869c 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -2,6 +2,10 @@ language: cpp
git:
depth: 1000
+
+env:
+ # Avoid expensive initialization of dotnet cli, see: https://donovanbrown.com/post/Stop-wasting-time-during-NET-Core-builds
+ DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1
os:
- linux
diff --git a/module/PowerShellEditorServices/Commands/Public/Out-CurrentFile.ps1 b/module/PowerShellEditorServices/Commands/Public/Out-CurrentFile.ps1
index 5d0b5cf5c..75c4a192d 100644
--- a/module/PowerShellEditorServices/Commands/Public/Out-CurrentFile.ps1
+++ b/module/PowerShellEditorServices/Commands/Public/Out-CurrentFile.ps1
@@ -9,14 +9,32 @@ function Out-CurrentFile {
#>
[CmdletBinding()]
param(
- [Parameter(ValueFromPipeline, Mandatory=$true)]
+ [Switch]$AsNewFile,
+
+ [Parameter(ValueFromPipeline, Mandatory = $true)]
$InputObject
)
Begin { $objectsToWrite = @() }
Process { $objectsToWrite += $InputObject }
End {
+
+ # If requested, create a new file
+ if ($AsNewFile) {
+ $psEditor.Workspace.NewFile()
+ }
+
$outputString = "@`"`r`n{0}`r`n`"@" -f ($objectsToWrite|out-string).Trim()
+
+ try {
+ # If there is no file open
+ $psEditor.GetEditorContext()
+ }
+ catch {
+ # create a new one
+ $psEditor.Workspace.NewFile()
+ }
+
$psEditor.GetEditorContext().CurrentFile.InsertText($outputString)
}
}
diff --git a/src/PowerShellEditorServices.Host/CodeLens/PesterCodeLensProvider.cs b/src/PowerShellEditorServices.Host/CodeLens/PesterCodeLensProvider.cs
index 619ca0a49..e814918ab 100644
--- a/src/PowerShellEditorServices.Host/CodeLens/PesterCodeLensProvider.cs
+++ b/src/PowerShellEditorServices.Host/CodeLens/PesterCodeLensProvider.cs
@@ -41,9 +41,7 @@ public PesterCodeLensProvider(EditorSession editorSession)
/// The Pester symbol to get CodeLenses for.
/// The script file the Pester symbol comes from.
/// All CodeLenses for the given Pester symbol.
- private CodeLens[] GetPesterLens(
- PesterSymbolReference pesterSymbol,
- ScriptFile scriptFile)
+ private CodeLens[] GetPesterLens(PesterSymbolReference pesterSymbol, ScriptFile scriptFile)
{
var codeLensResults = new CodeLens[]
{
@@ -54,7 +52,11 @@ private CodeLens[] GetPesterLens(
new ClientCommand(
"PowerShell.RunPesterTests",
"Run tests",
- new object[] { scriptFile.ClientFilePath, false /* No debug */, pesterSymbol.TestName })),
+ new object[] {
+ scriptFile.ClientFilePath,
+ false /* No debug */,
+ pesterSymbol.TestName,
+ pesterSymbol.ScriptRegion?.StartLineNumber })),
new CodeLens(
this,
@@ -63,7 +65,11 @@ private CodeLens[] GetPesterLens(
new ClientCommand(
"PowerShell.RunPesterTests",
"Debug tests",
- new object[] { scriptFile.ClientFilePath, true /* Run in debugger */, pesterSymbol.TestName })),
+ new object[] {
+ scriptFile.ClientFilePath,
+ true /* Run in the debugger */,
+ pesterSymbol.TestName,
+ pesterSymbol.ScriptRegion?.StartLineNumber })),
};
return codeLensResults;
@@ -99,9 +105,7 @@ public CodeLens[] ProvideCodeLenses(ScriptFile scriptFile)
/// The code lens to resolve.
///
/// The given CodeLens, wrapped in a task.
- public Task ResolveCodeLensAsync(
- CodeLens codeLens,
- CancellationToken cancellationToken)
+ public Task ResolveCodeLensAsync(CodeLens codeLens, CancellationToken cancellationToken)
{
// This provider has no specific behavior for
// resolving CodeLenses.
diff --git a/src/PowerShellEditorServices.Protocol/DebugAdapter/AttachRequest.cs b/src/PowerShellEditorServices.Protocol/DebugAdapter/AttachRequest.cs
index e9734e002..185a1aac0 100644
--- a/src/PowerShellEditorServices.Protocol/DebugAdapter/AttachRequest.cs
+++ b/src/PowerShellEditorServices.Protocol/DebugAdapter/AttachRequest.cs
@@ -20,6 +20,8 @@ public class AttachRequestArguments
public string ProcessId { get; set; }
- public int RunspaceId { get; set; }
+ public string RunspaceId { get; set; }
+
+ public string CustomPipeName { get; set; }
}
}
diff --git a/src/PowerShellEditorServices.Protocol/LanguageServer/GetRunspaceRequest.cs b/src/PowerShellEditorServices.Protocol/LanguageServer/GetRunspaceRequest.cs
new file mode 100644
index 000000000..e151aa7f0
--- /dev/null
+++ b/src/PowerShellEditorServices.Protocol/LanguageServer/GetRunspaceRequest.cs
@@ -0,0 +1,25 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+//
+
+using Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol;
+
+namespace Microsoft.PowerShell.EditorServices.Protocol.LanguageServer
+{
+ public class GetRunspaceRequest
+ {
+ public static readonly
+ RequestType Type =
+ RequestType.Create("powerShell/getRunspace");
+ }
+
+ public class GetRunspaceResponse
+ {
+ public int Id { get; set; }
+
+ public string Name { get; set; }
+
+ public string Availability { get; set; }
+ }
+}
diff --git a/src/PowerShellEditorServices.Protocol/Server/DebugAdapter.cs b/src/PowerShellEditorServices.Protocol/Server/DebugAdapter.cs
index a43f5a7d3..c3e203829 100644
--- a/src/PowerShellEditorServices.Protocol/Server/DebugAdapter.cs
+++ b/src/PowerShellEditorServices.Protocol/Server/DebugAdapter.cs
@@ -23,6 +23,8 @@ namespace Microsoft.PowerShell.EditorServices.Protocol.Server
{
public class DebugAdapter
{
+ private static readonly Version _minVersionForCustomPipeName = new Version(6, 2);
+
private EditorSession _editorSession;
private bool _noDebug;
@@ -344,11 +346,17 @@ protected async Task HandleAttachRequest(
RegisterEventHandlers();
+ bool processIdIsSet = !string.IsNullOrEmpty(attachParams.ProcessId) && attachParams.ProcessId != "undefined";
+ bool customPipeNameIsSet = !string.IsNullOrEmpty(attachParams.CustomPipeName) && attachParams.CustomPipeName != "undefined";
+
+ PowerShellVersionDetails runspaceVersion =
+ _editorSession.PowerShellContext.CurrentRunspace.PowerShellVersion;
+
// If there are no host processes to attach to or the user cancels selection, we get a null for the process id.
// This is not an error, just a request to stop the original "attach to" request.
// Testing against "undefined" is a HACK because I don't know how to make "Cancel" on quick pick loading
// to cancel on the VSCode side without sending an attachRequest with processId set to "undefined".
- if (string.IsNullOrEmpty(attachParams.ProcessId) || (attachParams.ProcessId == "undefined"))
+ if (!processIdIsSet && !customPipeNameIsSet)
{
Logger.Write(
LogLevel.Normal,
@@ -364,9 +372,6 @@ await requestContext.SendError(
if (attachParams.ComputerName != null)
{
- PowerShellVersionDetails runspaceVersion =
- _editorSession.PowerShellContext.CurrentRunspace.PowerShellVersion;
-
if (runspaceVersion.Version.Major < 4)
{
await requestContext.SendError(
@@ -397,16 +402,12 @@ await requestContext.SendError(
_isRemoteAttach = true;
}
- if (int.TryParse(attachParams.ProcessId, out int processId) && (processId > 0))
+ if (processIdIsSet && int.TryParse(attachParams.ProcessId, out int processId) && (processId > 0))
{
- PowerShellVersionDetails runspaceVersion =
- _editorSession.PowerShellContext.CurrentRunspace.PowerShellVersion;
-
if (runspaceVersion.Version.Major < 5)
{
await requestContext.SendError(
$"Attaching to a process is only available with PowerShell 5 and higher (current session is {runspaceVersion.Version}).");
-
return;
}
@@ -421,22 +422,29 @@ await requestContext.SendError(
return;
}
+ }
+ else if (customPipeNameIsSet)
+ {
+ if (runspaceVersion.Version < _minVersionForCustomPipeName)
+ {
+ await requestContext.SendError(
+ $"Attaching to a process with CustomPipeName is only available with PowerShell 6.2 and higher (current session is {runspaceVersion.Version}).");
+ return;
+ }
- // Clear any existing breakpoints before proceeding
- await ClearSessionBreakpoints();
-
- // Execute the Debug-Runspace command but don't await it because it
- // will block the debug adapter initialization process. The
- // InitializedEvent will be sent as soon as the RunspaceChanged
- // event gets fired with the attached runspace.
- int runspaceId = attachParams.RunspaceId > 0 ? attachParams.RunspaceId : 1;
- _waitingForAttach = true;
- Task nonAwaitedTask =
- _editorSession.PowerShellContext
- .ExecuteScriptString($"\nDebug-Runspace -Id {runspaceId}")
- .ContinueWith(OnExecutionCompleted);
+ await _editorSession.PowerShellContext.ExecuteScriptString(
+ $"Enter-PSHostProcess -CustomPipeName {attachParams.CustomPipeName}",
+ errorMessages);
+
+ if (errorMessages.Length > 0)
+ {
+ await requestContext.SendError(
+ $"Could not attach to process with CustomPipeName: '{attachParams.CustomPipeName}'");
+
+ return;
+ }
}
- else
+ else if (attachParams.ProcessId != "current")
{
Logger.Write(
LogLevel.Error,
@@ -448,6 +456,32 @@ await requestContext.SendError(
return;
}
+ // Clear any existing breakpoints before proceeding
+ await ClearSessionBreakpoints().ConfigureAwait(continueOnCapturedContext: false);
+
+ // Execute the Debug-Runspace command but don't await it because it
+ // will block the debug adapter initialization process. The
+ // InitializedEvent will be sent as soon as the RunspaceChanged
+ // event gets fired with the attached runspace.
+
+ var runspaceId = 1;
+ if (!int.TryParse(attachParams.RunspaceId, out runspaceId) || runspaceId <= 0)
+ {
+ Logger.Write(
+ LogLevel.Error,
+ $"Attach request failed, '{attachParams.RunspaceId}' is an invalid value for the processId.");
+
+ await requestContext.SendError(
+ "A positive integer must be specified for the RunspaceId field.");
+
+ return;
+ }
+
+ _waitingForAttach = true;
+ Task nonAwaitedTask = _editorSession.PowerShellContext
+ .ExecuteScriptString($"\nDebug-Runspace -Id {runspaceId}")
+ .ContinueWith(OnExecutionCompleted);
+
await requestContext.SendResult(null);
}
diff --git a/src/PowerShellEditorServices.Protocol/Server/LanguageServer.cs b/src/PowerShellEditorServices.Protocol/Server/LanguageServer.cs
index 0432fa2ee..de4fc99eb 100644
--- a/src/PowerShellEditorServices.Protocol/Server/LanguageServer.cs
+++ b/src/PowerShellEditorServices.Protocol/Server/LanguageServer.cs
@@ -18,6 +18,7 @@
using System.Linq;
using System.Management.Automation;
using System.Management.Automation.Language;
+using System.Management.Automation.Runspaces;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
@@ -162,6 +163,8 @@ public void Start()
this.messageHandlers.SetRequestHandler(GetPSHostProcessesRequest.Type, this.HandleGetPSHostProcessesRequest);
this.messageHandlers.SetRequestHandler(CommentHelpRequest.Type, this.HandleCommentHelpRequest);
+ this.messageHandlers.SetRequestHandler(GetRunspaceRequest.Type, this.HandleGetRunspaceRequestAsync);
+
// Initialize the extension service
// TODO: This should be made awaited once Initialize is async!
this.editorSession.ExtensionService.Initialize(
@@ -1231,6 +1234,54 @@ protected async Task HandleCommentHelpRequest(
await requestContext.SendResult(result);
}
+ protected async Task HandleGetRunspaceRequestAsync(
+ string processId,
+ RequestContext requestContext)
+ {
+ var runspaceResponses = new List();
+
+ if (this.editorSession.PowerShellContext.LocalPowerShellVersion.Version.Major >= 5)
+ {
+ if (processId == null) {
+ processId = "current";
+ }
+
+ var isNotCurrentProcess = processId != null && processId != "current";
+
+ var psCommand = new PSCommand();
+
+ if (isNotCurrentProcess) {
+ psCommand.AddCommand("Enter-PSHostProcess").AddParameter("Id", processId).AddStatement();
+ }
+
+ psCommand.AddCommand("Get-Runspace");
+
+ StringBuilder sb = new StringBuilder();
+ IEnumerable runspaces = await editorSession.PowerShellContext.ExecuteCommand(psCommand, sb);
+ if (runspaces != null)
+ {
+ foreach (var p in runspaces)
+ {
+ runspaceResponses.Add(
+ new GetRunspaceResponse
+ {
+ Id = p.Id,
+ Name = p.Name,
+ Availability = p.RunspaceAvailability.ToString()
+ });
+ }
+ }
+
+ if (isNotCurrentProcess) {
+ var exitCommand = new PSCommand();
+ exitCommand.AddCommand("Exit-PSHostProcess");
+ await editorSession.PowerShellContext.ExecuteCommand(exitCommand);
+ }
+ }
+
+ await requestContext.SendResult(runspaceResponses.ToArray());
+ }
+
private bool IsQueryMatch(string query, string symbolName)
{
return symbolName.IndexOf(query, StringComparison.OrdinalIgnoreCase) >= 0;
diff --git a/src/PowerShellEditorServices/Debugging/StackFrameDetails.cs b/src/PowerShellEditorServices/Debugging/StackFrameDetails.cs
index 4f68f02ad..e217457fc 100644
--- a/src/PowerShellEditorServices/Debugging/StackFrameDetails.cs
+++ b/src/PowerShellEditorServices/Debugging/StackFrameDetails.cs
@@ -107,12 +107,14 @@ static internal StackFrameDetails Create(
string scriptPath = (callStackFrameObject.Properties["ScriptName"].Value as string) ?? NoFileScriptPath;
int startLineNumber = (int)(callStackFrameObject.Properties["ScriptLineNumber"].Value ?? 0);
- if (workspaceRootPath != null &&
- invocationInfo != null &&
- !scriptPath.StartsWith(workspaceRootPath, StringComparison.OrdinalIgnoreCase))
- {
- isExternal = true;
- }
+ // TODO: RKH 2019-03-07 Temporarily disable "external" code until I have a chance to add
+ // settings to control this feature.
+ //if (workspaceRootPath != null &&
+ // invocationInfo != null &&
+ // !scriptPath.StartsWith(workspaceRootPath, StringComparison.OrdinalIgnoreCase))
+ //{
+ // isExternal = true;
+ //}
return new StackFrameDetails
{
diff --git a/src/PowerShellEditorServices/Workspace/Workspace.cs b/src/PowerShellEditorServices/Workspace/Workspace.cs
index 6f9cb8beb..d81f9f5e7 100644
--- a/src/PowerShellEditorServices/Workspace/Workspace.cs
+++ b/src/PowerShellEditorServices/Workspace/Workspace.cs
@@ -148,11 +148,12 @@ public bool TryGetFile(string filePath, out ScriptFile scriptFile)
return true;
}
catch (Exception e) when (
- e is IOException ||
- e is SecurityException ||
+ e is NotSupportedException ||
e is FileNotFoundException ||
e is DirectoryNotFoundException ||
e is PathTooLongException ||
+ e is IOException ||
+ e is SecurityException ||
e is UnauthorizedAccessException)
{
this.logger.WriteHandledException($"Failed to get file for {nameof(filePath)}: '{filePath}'", e);