Skip to content

Add the ability to use either . or & when launching script #2177

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
merged 1 commit into from
Oct 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ internal class DebugStateService

internal bool IsUsingTempIntegratedConsole { get; set; }

internal string ExecuteMode { get; set; }

// This gets set at the end of the Launch/Attach handler which set debug state.
internal TaskCompletionSource<bool> ServerStarted { get; set; }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,9 +110,11 @@ internal async Task LaunchScriptAsync(string scriptToLaunch)
PSCommand command;
if (System.IO.File.Exists(scriptToLaunch))
{
// For a saved file we just execute its path (after escaping it).
// For a saved file we just execute its path (after escaping it), with the configured operator
// (which can't be called that because it's a reserved keyword in C#).
string executeMode = _debugStateService?.ExecuteMode == "Call" ? "&" : ".";
command = PSCommandHelpers.BuildDotSourceCommandWithArguments(
PSCommandHelpers.EscapeScriptFilePath(scriptToLaunch), _debugStateService?.Arguments);
PSCommandHelpers.EscapeScriptFilePath(scriptToLaunch), _debugStateService?.Arguments, executeMode);
}
else // It's a URI to an untitled script, or a raw script.
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,11 @@ internal record PsesLaunchRequestArguments : LaunchRequestArguments
/// </summary>
public string[] RuntimeArgs { get; set; }

/// <summary>
/// Gets or sets the script execution mode, either "DotSource" or "Call".
/// </summary>
public string ExecuteMode { 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.
Expand Down Expand Up @@ -186,6 +191,7 @@ public async Task<LaunchResponse> Handle(PsesLaunchRequestArguments request, Can
_debugStateService.ScriptToLaunch = request.Script;
_debugStateService.Arguments = request.Args;
_debugStateService.IsUsingTempIntegratedConsole = request.CreateTemporaryIntegratedConsole;
_debugStateService.ExecuteMode = request.ExecuteMode;

if (request.CreateTemporaryIntegratedConsole
&& !string.IsNullOrEmpty(request.Script)
Expand Down
6 changes: 4 additions & 2 deletions src/PowerShellEditorServices/Utility/PSCommandExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -94,10 +94,12 @@ private static StringBuilder AddCommandText(this StringBuilder sb, Command comma

public static string EscapeScriptFilePath(string f) => string.Concat("'", f.Replace("'", "''"), "'");

public static PSCommand BuildDotSourceCommandWithArguments(string command, IEnumerable<string> arguments)
// Operator defaults to dot-source but could also be call (ampersand).
// It can't be called that because it's a reserved keyword in C#.
public static PSCommand BuildDotSourceCommandWithArguments(string command, IEnumerable<string> arguments, string executeMode = ".")
{
string args = string.Join(" ", arguments ?? Array.Empty<string>());
string script = string.Concat(". ", command, string.IsNullOrEmpty(args) ? "" : " ", args);
string script = string.Concat(executeMode, " ", command, string.IsNullOrEmpty(args) ? "" : " ", args);
// HACK: We use AddScript instead of AddArgument/AddParameter to reuse Powershell parameter binding logic.
return new PSCommand().AddScript(script);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,21 +11,17 @@ namespace PowerShellEditorServices.Test.E2E
{
public static class DebugAdapterClientExtensions
{
public static async Task LaunchScript(this DebugAdapterClient debugAdapterClient, string script, TaskCompletionSource<object> started)
public static async Task LaunchScript(this DebugAdapterClient debugAdapterClient, string script, TaskCompletionSource<object> started, string executeMode = "DotSource")
{
LaunchResponse launchResponse = await debugAdapterClient.Launch(
_ = await debugAdapterClient.Launch(
new PsesLaunchRequestArguments
{
NoDebug = false,
Script = script,
Cwd = "",
CreateTemporaryIntegratedConsole = false
});

if (launchResponse is null)
{
throw new Exception("Launch response was null.");
}
CreateTemporaryIntegratedConsole = false,
ExecuteMode = executeMode,
}) ?? throw new Exception("Launch response was null.");

// This will check to see if we received the Initialized event from the server.
await started.Task;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,17 @@ public async Task UsesDotSourceOperatorAndQuotesAsync()
(i) => Assert.StartsWith(". '", i));
}

[Fact]
public async Task UsesCallOperatorWithSettingAsync()
{
string filePath = NewTestFile(GenerateScriptFromLoggingStatements("$($MyInvocation.Line)"));
await PsesDebugAdapterClient.LaunchScript(filePath, Started, executeMode: "Call");
ConfigurationDoneResponse configDoneResponse = await PsesDebugAdapterClient.RequestConfigurationDone(new ConfigurationDoneArguments());
Assert.NotNull(configDoneResponse);
Assert.Collection(await GetLog(),
(i) => Assert.StartsWith("& '", i));
}

[Fact]
public async Task CanLaunchScriptWithNoBreakpointsAsync()
{
Expand Down