Skip to content
Open
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 @@ -4161,13 +4161,18 @@ internal void Trace(string messageId, string resourceString, params object[] arg

internal void TraceLine(IScriptExtent extent)
{
string msg = PositionUtilities.BriefMessage(extent.StartScriptPosition);
string msg = PositionUtilities.BriefMessage(extent);
InternalHostUserInterface ui = (InternalHostUserInterface)_context.EngineHostInterface.UI;

ActionPreference pref = _context.PSDebugTraceStep ?
ActionPreference.Inquire : ActionPreference.Continue;

ui.WriteDebugLine(msg, ref pref);
// Write each line separately so each gets the DEBUG: prefix
string[] lines = msg.Split(new[] { "\r\n", "\n" }, StringSplitOptions.None);
foreach (string line in lines)
{
ui.WriteDebugLine(line, ref pref);
}

if (pref == ActionPreference.Continue)
_context.PSDebugTraceStep = false;
Expand Down
59 changes: 59 additions & 0 deletions src/System.Management.Automation/engine/parser/Position.cs
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,65 @@ internal static string BriefMessage(IScriptPosition position)
return StringUtil.Format(ParserStrings.TraceScriptLineMessage, position.LineNumber, message.ToString());
}

/// <summary>
/// Return a message for an extent that may span multiple lines.
/// For single-line extents, the format is:
/// 12+ >>>> $x + $b.
/// For multi-line extents (e.g., line continuation with backtick), the format is:
/// 12+ >>>> Write-Output "foo `
/// 13+ >>>> bar"
/// </summary>
internal static string BriefMessage(IScriptExtent extent)
{
// For single-line extents, delegate to the existing single-position method
if (extent.StartLineNumber == extent.EndLineNumber)
{
return BriefMessage(extent.StartScriptPosition);
}

// For multi-line extents, include all lines
string[] lines = extent.Text.Split(new[] { "\r\n", "\n", "\r" }, StringSplitOptions.None);
StringBuilder result = new StringBuilder();
int lineNumber = extent.StartLineNumber;

for (int i = 0; i < lines.Length; i++)
{
string line = lines[i];
StringBuilder message = new StringBuilder(line);

// Insert the marker at the appropriate position
if (i == 0)
{
// For the first line, insert at the start column
if (extent.StartColumnNumber > message.Length + 1)
{
message.Append(" <<<< ");
}
else
{
message.Insert(extent.StartColumnNumber - 1, " >>>> ");
}
}
else
{
// For continuation lines, insert the marker at the beginning
message.Insert(0, " >>>> ");
}

string formattedLine = StringUtil.Format(ParserStrings.TraceScriptLineMessage, lineNumber, message.ToString());

if (i > 0)
{
result.AppendLine();
}

result.Append(formattedLine);
lineNumber++;
}

return result.ToString();
}

internal static IScriptExtent NewScriptExtent(IScriptExtent start, IScriptExtent end)
{
if (start == end)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,51 @@ Describe "Set-PSDebug" -Tags "CI" {
[ClassWithDefaultCtor]::new()
} | Should -Not -Throw
}

It "Should trace all lines of a multiline command" {
$tempScript = Join-Path $TestDrive "multiline-trace.ps1"
$scriptContent = "Set-PSDebug -Trace 1`nWrite-Output `"foo ```nbar`""
Set-Content -Path $tempScript -Value $scriptContent -NoNewline

# Run in a separate process to capture trace output
$pinfo = [System.Diagnostics.ProcessStartInfo]::new()
$pinfo.FileName = (Get-Process -Id $PID).Path
$pinfo.Arguments = "-NoProfile -File `"$tempScript`""
$pinfo.RedirectStandardOutput = $true
$pinfo.UseShellExecute = $false

$process = [System.Diagnostics.Process]::new()
$process.StartInfo = $pinfo
$process.Start() | Should -BeTrue
$output = $process.StandardOutput.ReadToEnd()
$process.WaitForExit()

# The debug trace for multiline commands should include all lines with DEBUG: prefix
$output | Should -Match 'DEBUG:.*Write-Output' -Because "debug output should contain the command with DEBUG: prefix"
$output | Should -Match 'DEBUG:.*bar"' -Because "debug output should contain the continuation line with DEBUG: prefix"
}

It "Should trace all lines of a multiline command with -Trace 2" {
$tempScript = Join-Path $TestDrive "multiline-trace2.ps1"
$scriptContent = "Set-PSDebug -Trace 2`nWrite-Output `"foo ```nbar`""
Set-Content -Path $tempScript -Value $scriptContent -NoNewline

# Run in a separate process to capture trace output
$pinfo = [System.Diagnostics.ProcessStartInfo]::new()
$pinfo.FileName = (Get-Process -Id $PID).Path
$pinfo.Arguments = "-NoProfile -File `"$tempScript`""
$pinfo.RedirectStandardOutput = $true
$pinfo.UseShellExecute = $false

$process = [System.Diagnostics.Process]::new()
$process.StartInfo = $pinfo
$process.Start() | Should -BeTrue
$output = $process.StandardOutput.ReadToEnd()
$process.WaitForExit()

# The debug trace for multiline commands should include all lines with DEBUG: prefix
$output | Should -Match 'DEBUG:.*Write-Output' -Because "debug output should contain the command with DEBUG: prefix"
$output | Should -Match 'DEBUG:.*bar"' -Because "debug output should contain the continuation line with DEBUG: prefix"
}
}
}
Loading