Skip to content

WIP Cherry pick for legacy #883

New issue

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

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

Already on GitHub? Sign in to your account

Merged
4 changes: 4 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,7 @@ public PesterCodeLensProvider(EditorSession editorSession)
/// <param name="pesterSymbol">The Pester symbol to get CodeLenses for.</param>
/// <param name="scriptFile">The script file the Pester symbol comes from.</param>
/// <returns>All CodeLenses for the given Pester symbol.</returns>
private CodeLens[] GetPesterLens(
PesterSymbolReference pesterSymbol,
ScriptFile scriptFile)
private CodeLens[] GetPesterLens(PesterSymbolReference pesterSymbol, ScriptFile scriptFile)
{
var codeLensResults = new CodeLens[]
{
Expand All @@ -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,
Expand All @@ -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;
Expand Down Expand Up @@ -99,9 +105,7 @@ public CodeLens[] ProvideCodeLenses(ScriptFile scriptFile)
/// <param name="codeLens">The code lens to resolve.</param>
/// <param name="cancellationToken"></param>
/// <returns>The given CodeLens, wrapped in a task.</returns>
public Task<CodeLens> ResolveCodeLensAsync(
CodeLens codeLens,
CancellationToken cancellationToken)
public Task<CodeLens> ResolveCodeLensAsync(CodeLens codeLens, CancellationToken cancellationToken)
{
// This provider has no specific behavior for
// resolving CodeLenses.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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; }
}
}
Original file line number Diff line number Diff line change
@@ -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<string, GetRunspaceResponse[], object, object> Type =
RequestType<string, GetRunspaceResponse[], object, object>.Create("powerShell/getRunspace");
}

public class GetRunspaceResponse
{
public int Id { get; set; }

public string Name { get; set; }

public string Availability { get; set; }
}
}
80 changes: 57 additions & 23 deletions src/PowerShellEditorServices.Protocol/Server/DebugAdapter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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,
Expand All @@ -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(
Expand Down Expand Up @@ -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;
}

Expand All @@ -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,
Expand All @@ -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);
}

Expand Down
51 changes: 51 additions & 0 deletions src/PowerShellEditorServices.Protocol/Server/LanguageServer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -1231,6 +1234,54 @@ protected async Task HandleCommentHelpRequest(
await requestContext.SendResult(result);
}

protected async Task HandleGetRunspaceRequestAsync(
string processId,
RequestContext<GetRunspaceResponse[]> requestContext)
{
var runspaceResponses = new List<GetRunspaceResponse>();

if (this.editorSession.PowerShellContext.LocalPowerShellVersion.Version.Major >= 5)
{
if (processId == null) {
processId = "current";
}

var isNotCurrentProcess = processId != null && processId != "current";
Copy link
Contributor

Choose a reason for hiding this comment

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

A few parens here would make this a bit easier to visually parse.

Copy link
Member Author

Choose a reason for hiding this comment

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

Also fixed in #881


var psCommand = new PSCommand();

if (isNotCurrentProcess) {
psCommand.AddCommand("Enter-PSHostProcess").AddParameter("Id", processId).AddStatement();
}

psCommand.AddCommand("Get-Runspace");

StringBuilder sb = new StringBuilder();
Copy link
Contributor

Choose a reason for hiding this comment

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

What happened to the var here?

Copy link
Member Author

Choose a reason for hiding this comment

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

That's in #881

IEnumerable<Runspace> runspaces = await editorSession.PowerShellContext.ExecuteCommand<Runspace>(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;
Expand Down
14 changes: 8 additions & 6 deletions src/PowerShellEditorServices/Debugging/StackFrameDetails.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down
5 changes: 3 additions & 2 deletions src/PowerShellEditorServices/Workspace/Workspace.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down