Skip to content

Commit 86dd212

Browse files
authored
Merge pull request #331 from PowerShell/daviwil/script-listings
Fetch script listings for debugger stop events with no ScriptName
2 parents ffa27f8 + 540c9ff commit 86dd212

File tree

4 files changed

+197
-55
lines changed

4 files changed

+197
-55
lines changed

src/PowerShellEditorServices/Debugging/DebugService.cs

Lines changed: 97 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
using System.Threading.Tasks;
1313
using Microsoft.PowerShell.EditorServices.Debugging;
1414
using Microsoft.PowerShell.EditorServices.Utility;
15-
using System.IO;
1615
using Microsoft.PowerShell.EditorServices.Session;
1716

1817
namespace Microsoft.PowerShell.EditorServices
@@ -26,6 +25,7 @@ public class DebugService
2625
#region Fields
2726

2827
private const string PsesGlobalVariableNamePrefix = "__psEditorServices_";
28+
private const string TemporaryScriptFileName = "TemporaryScript.ps1";
2929

3030
private PowerShellContext powerShellContext;
3131
private RemoteFileManager remoteFileManager;
@@ -35,6 +35,7 @@ public class DebugService
3535
new Dictionary<string, List<Breakpoint>>();
3636

3737
private int nextVariableId;
38+
private string temporaryScriptListingPath;
3839
private List<VariableDetailsBase> variables;
3940
private VariableContainerDetails globalScopeVariables;
4041
private VariableContainerDetails scriptScopeVariables;
@@ -93,8 +94,8 @@ public DebugService(
9394
/// <param name="clearExisting">If true, causes all existing breakpoints to be cleared before setting new ones.</param>
9495
/// <returns>An awaitable Task that will provide details about the breakpoints that were set.</returns>
9596
public async Task<BreakpointDetails[]> SetLineBreakpoints(
96-
ScriptFile scriptFile,
97-
BreakpointDetails[] breakpoints,
97+
ScriptFile scriptFile,
98+
BreakpointDetails[] breakpoints,
9899
bool clearExisting = true)
99100
{
100101
var resultBreakpointDetails = new List<BreakpointDetails>();
@@ -111,22 +112,32 @@ public async Task<BreakpointDetails[]> SetLineBreakpoints(
111112
if (this.powerShellContext.CurrentRunspace.Location == RunspaceLocation.Remote &&
112113
this.remoteFileManager != null)
113114
{
114-
string mappedPath =
115-
this.remoteFileManager.GetMappedPath(
116-
scriptPath,
117-
this.powerShellContext.CurrentRunspace);
118-
119-
if (mappedPath == null)
115+
if (!this.remoteFileManager.IsUnderRemoteTempPath(scriptPath))
120116
{
121117
Logger.Write(
122-
LogLevel.Error,
123-
$"Could not map local path '{scriptPath}' to a remote path.");
118+
LogLevel.Verbose,
119+
$"Could not set breakpoints for local path '{scriptPath}' in a remote session.");
124120

125121
return resultBreakpointDetails.ToArray();
126122
}
127123

124+
string mappedPath =
125+
this.remoteFileManager.GetMappedPath(
126+
scriptPath,
127+
this.powerShellContext.CurrentRunspace);
128+
128129
scriptPath = mappedPath;
129130
}
131+
else if (
132+
this.temporaryScriptListingPath != null &&
133+
this.temporaryScriptListingPath.Equals(scriptPath, StringComparison.CurrentCultureIgnoreCase))
134+
{
135+
Logger.Write(
136+
LogLevel.Verbose,
137+
$"Could not set breakpoint on temporary script listing path '{scriptPath}'.");
138+
139+
return resultBreakpointDetails.ToArray();
140+
}
130141

131142
// Fix for issue #123 - file paths that contain wildcard chars [ and ] need to
132143
// quoted and have those wildcard chars escaped.
@@ -622,7 +633,7 @@ private async Task ClearCommandBreakpoints()
622633
await this.powerShellContext.ExecuteCommand<object>(psCommand);
623634
}
624635

625-
private async Task FetchStackFramesAndVariables()
636+
private async Task FetchStackFramesAndVariables(string scriptNameOverride)
626637
{
627638
this.nextVariableId = VariableDetailsBase.FirstVariableId;
628639
this.variables = new List<VariableDetailsBase>();
@@ -633,7 +644,7 @@ private async Task FetchStackFramesAndVariables()
633644
// Must retrieve global/script variales before stack frame variables
634645
// as we check stack frame variables against globals.
635646
await FetchGlobalAndScriptVariables();
636-
await FetchStackFrames();
647+
await FetchStackFrames(scriptNameOverride);
637648
}
638649

639650
private async Task FetchGlobalAndScriptVariables()
@@ -750,7 +761,7 @@ private bool AddToAutoVariables(PSObject psvariable, string scope)
750761
return true;
751762
}
752763

753-
private async Task FetchStackFrames()
764+
private async Task FetchStackFrames(string scriptNameOverride)
754765
{
755766
PSCommand psCommand = new PSCommand();
756767

@@ -782,7 +793,12 @@ private async Task FetchStackFrames()
782793
StackFrameDetails.Create(callStackFrames[i], autoVariables, localVariables);
783794

784795
string stackFrameScriptPath = this.stackFrameDetails[i].ScriptPath;
785-
if (this.powerShellContext.CurrentRunspace.Location == RunspaceLocation.Remote &&
796+
if (scriptNameOverride != null &&
797+
string.Equals(stackFrameScriptPath, StackFrameDetails.NoFileScriptPath))
798+
{
799+
this.stackFrameDetails[i].ScriptPath = scriptNameOverride;
800+
}
801+
else if (this.powerShellContext.CurrentRunspace.Location == RunspaceLocation.Remote &&
786802
this.remoteFileManager != null &&
787803
!string.Equals(stackFrameScriptPath, StackFrameDetails.NoFileScriptPath))
788804
{
@@ -979,6 +995,25 @@ private string FormatInvalidBreakpointConditionMessage(string condition, string
979995
return $"'{condition}' is not a valid PowerShell expression. {message}";
980996
}
981997

998+
private string TrimScriptListingLine(PSObject scriptLineObj, ref int prefixLength)
999+
{
1000+
string scriptLine = scriptLineObj.ToString();
1001+
1002+
if (!string.IsNullOrWhiteSpace(scriptLine))
1003+
{
1004+
if (prefixLength == 0)
1005+
{
1006+
// The prefix is a padded integer ending with ':', an asterisk '*'
1007+
// if this is the current line, and one character of padding
1008+
prefixLength = scriptLine.IndexOf(':') + 2;
1009+
}
1010+
1011+
return scriptLine.Substring(prefixLength);
1012+
}
1013+
1014+
return null;
1015+
}
1016+
9821017
#endregion
9831018

9841019
#region Events
@@ -990,11 +1025,56 @@ private string FormatInvalidBreakpointConditionMessage(string condition, string
9901025

9911026
private async void OnDebuggerStop(object sender, DebuggerStopEventArgs e)
9921027
{
1028+
bool noScriptName = false;
1029+
string localScriptPath = e.InvocationInfo.ScriptName;
1030+
1031+
// If there's no ScriptName, get the "list" of the current source
1032+
if (this.remoteFileManager != null && string.IsNullOrEmpty(localScriptPath))
1033+
{
1034+
// Get the current script listing and create the buffer
1035+
PSCommand command = new PSCommand();
1036+
command.AddScript($"list 1 {int.MaxValue}");
1037+
1038+
IEnumerable<PSObject> scriptListingLines =
1039+
await this.powerShellContext.ExecuteCommand<PSObject>(
1040+
command, false, false);
1041+
1042+
if (scriptListingLines != null)
1043+
{
1044+
int linePrefixLength = 0;
1045+
1046+
string scriptListing =
1047+
string.Join(
1048+
Environment.NewLine,
1049+
scriptListingLines
1050+
.Select(o => this.TrimScriptListingLine(o, ref linePrefixLength))
1051+
.Where(s => s != null));
1052+
1053+
this.temporaryScriptListingPath =
1054+
this.remoteFileManager.CreateTemporaryFile(
1055+
TemporaryScriptFileName,
1056+
scriptListing,
1057+
this.powerShellContext.CurrentRunspace);
1058+
1059+
localScriptPath =
1060+
this.temporaryScriptListingPath
1061+
?? StackFrameDetails.NoFileScriptPath;
1062+
1063+
noScriptName = localScriptPath != null;
1064+
}
1065+
else
1066+
{
1067+
Logger.Write(
1068+
LogLevel.Warning,
1069+
$"Could not load script context");
1070+
}
1071+
}
1072+
9931073
// Get call stack and variables.
994-
await this.FetchStackFramesAndVariables();
1074+
await this.FetchStackFramesAndVariables(
1075+
noScriptName ? localScriptPath : null);
9951076

9961077
// If this is a remote connection, get the file content
997-
string localScriptPath = e.InvocationInfo.ScriptName;
9981078
if (this.powerShellContext.CurrentRunspace.Location == RunspaceLocation.Remote &&
9991079
this.remoteFileManager != null)
10001080
{

src/PowerShellEditorServices/Session/PowerShellContext.cs

Lines changed: 16 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -776,31 +776,29 @@ public void Dispose()
776776
// Clean up the active runspace
777777
this.CleanupRunspace(this.CurrentRunspace);
778778

779-
// Drain the runspace stack if any have been pushed
780-
if (this.runspaceStack.Count > 0)
779+
// Push the active runspace so it will be included in the loop
780+
this.runspaceStack.Push(this.CurrentRunspace);
781+
782+
while (this.runspaceStack.Count > 0)
781783
{
782-
// Push the active runspace so it will be included in the loop
783-
this.runspaceStack.Push(this.CurrentRunspace);
784+
RunspaceDetails poppedRunspace = this.runspaceStack.Pop();
784785

785-
while (this.runspaceStack.Count > 1)
786+
// Close the popped runspace if it isn't the initial runspace
787+
// or if it is the initial runspace and we own that runspace
788+
if (this.initialRunspace != poppedRunspace || this.ownsInitialRunspace)
786789
{
787-
RunspaceDetails poppedRunspace = this.runspaceStack.Pop();
788790
this.CloseRunspace(poppedRunspace);
789-
790-
this.OnRunspaceChanged(
791-
this,
792-
new RunspaceChangedEventArgs(
793-
RunspaceChangeAction.Shutdown,
794-
poppedRunspace,
795-
null));
796791
}
797-
}
798792

799-
if (this.ownsInitialRunspace && this.initialRunspace != null)
800-
{
801-
this.CloseRunspace(this.initialRunspace);
802-
this.initialRunspace = null;
793+
this.OnRunspaceChanged(
794+
this,
795+
new RunspaceChangedEventArgs(
796+
RunspaceChangeAction.Shutdown,
797+
poppedRunspace,
798+
null));
803799
}
800+
801+
this.initialRunspace = null;
804802
}
805803

806804
private void CloseRunspace(RunspaceDetails runspaceDetails)

src/PowerShellEditorServices/Session/PowerShellVersionDetails.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ public static PowerShellVersionDetails GetVersionDetails(Runspace runspace)
117117
{
118118
powerShellVersion = (Version)version;
119119
}
120-
else if (string.Equals(powerShellEdition, "Core", StringComparison.CurrentCultureIgnoreCase))
120+
else if (version != null)
121121
{
122122
// Expected version string format is 6.0.0-alpha so build a simpler version from that
123123
powerShellVersion = new Version(version.ToString().Split('-')[0]);
@@ -148,7 +148,7 @@ public static PowerShellVersionDetails GetVersionDetails(Runspace runspace)
148148
{
149149
Logger.Write(
150150
LogLevel.Warning,
151-
"Failed to look up PowerShell version. Defaulting to version 5. " + ex.Message);
151+
"Failed to look up PowerShell version, defaulting to version 5.\r\n\r\n" + ex.ToString());
152152
}
153153

154154
return new PowerShellVersionDetails(

0 commit comments

Comments
 (0)