Skip to content

Commit 0f52aa2

Browse files
committed
Stop debugger on task cancellation too
This synchronizes the `DebugContext` and LSP debug server state during both edge cases: 1. A REPL happened, the debugger was marked active, and the debugger is now no longer running (it was quit, detached, or continued to the end of the script or function). 2. A user manually cancelled a task, and it was running under the "debugger" (whether or not the PowerShell debugger was active).
1 parent d52caec commit 0f52aa2

File tree

2 files changed

+42
-16
lines changed

2 files changed

+42
-16
lines changed

src/PowerShellEditorServices/Services/PowerShell/Debugging/PowerShellDebugContext.cs

+13-6
Original file line numberDiff line numberDiff line change
@@ -47,19 +47,26 @@ public PowerShellDebugContext(
4747
_psesHost = psesHost;
4848
}
4949

50+
/// <summary>
51+
/// Tracks if the debugger is currently stopped at a breakpoint.
52+
/// </summary>
5053
public bool IsStopped { get; private set; }
5154

5255
/// <summary>
53-
/// Tracks the state of the LSP debug server (not the PowerShell debugger).
56+
/// Tracks the state of the PowerShell debugger. This is NOT the same as <see
57+
/// cref="Debugger.IsActive">, which is true whenever breakpoints are set. Instead, this is
58+
/// set to true when the first <see cref="PsesInternalHost.OnDebuggerStopped"> event is
59+
/// fired, and set to false in <see cref="PsesInternalHost.DoOneRepl"> when <see
60+
/// cref="Debugger.IsInBreakpoint"> is false. This is used to send the
61+
/// 'powershell/stopDebugger' notification to the LSP debug server in the cases where the
62+
/// server was started or ended by the PowerShell session instead of by Code's GUI.
5463
/// </summary>
55-
public bool IsDebugServerActive { get; set; }
64+
public bool IsActive { get; set; }
5665

5766
/// <summary>
58-
/// Tracks if the PowerShell session started the debug server itself (true), or if it was
59-
/// started by an LSP notification (false). Essentially, this marks if we're responsible for
60-
/// stopping the debug server (and thus need to send a notification to do so).
67+
/// Tracks the state of the LSP debug server (not the PowerShell debugger).
6168
/// </summary>
62-
public bool OwnsDebugServerState { get; set; }
69+
public bool IsDebugServerActive { get; set; }
6370

6471
public DebuggerStopEventArgs LastStopEventArgs { get; private set; }
6572

src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs

+29-10
Original file line numberDiff line numberDiff line change
@@ -601,12 +601,14 @@ private void DoOneRepl(CancellationToken cancellationToken)
601601
return;
602602
}
603603

604-
// If we started the debug server, then on each REPL we need to check if we're still
605-
// actively debugging, and if not, stop the server.
606-
if (DebugContext.OwnsDebugServerState && !CurrentRunspace.Runspace.Debugger.InBreakpoint)
604+
// We use the REPL as a poll to check if the debug context is active but PowerShell
605+
// indicates we're no longer debugging. This happens when PowerShell was used to start
606+
// the debugger (instead of using a Code launch configuration) via Wait-Debugger or
607+
// simply hitting a PSBreakpoint. We need to synchronize the state and stop the debug
608+
// context (and likely the debug server).
609+
if (DebugContext.IsActive && !CurrentRunspace.Runspace.Debugger.InBreakpoint)
607610
{
608-
DebugContext.OwnsDebugServerState = false;
609-
_languageServer?.SendNotification("powerShell/stopDebugger");
611+
StopDebugContext();
610612
}
611613

612614
// When a task must run in the foreground, we cancel out of the idle loop and return to the top level.
@@ -633,8 +635,7 @@ private void DoOneRepl(CancellationToken cancellationToken)
633635
// However, we must distinguish the last two scenarios, since PSRL will not print a new line in those cases.
634636
if (string.IsNullOrEmpty(userInput))
635637
{
636-
if (cancellationToken.IsCancellationRequested
637-
|| LastKeyWasCtrlC())
638+
if (cancellationToken.IsCancellationRequested || LastKeyWasCtrlC())
638639
{
639640
UI.WriteLine();
640641
}
@@ -834,7 +835,12 @@ private void OnPowerShellIdle(CancellationToken idleCancellationToken)
834835

835836
private void OnCancelKeyPress(object sender, ConsoleCancelEventArgs args)
836837
{
838+
// We need to cancel the current task.
837839
_cancellationContext.CancelCurrentTask();
840+
841+
// If the current task was running under the debugger, we need to synchronize the
842+
// cancelation with our debug context (and likely the debug server).
843+
StopDebugContext();
838844
}
839845

840846
private ConsoleKeyInfo ReadKey(bool intercept)
@@ -854,13 +860,26 @@ private bool LastKeyWasCtrlC()
854860
&& _lastKey.Value.IsCtrlC();
855861
}
856862

863+
private void StopDebugContext()
864+
{
865+
// We are officially stopping the debugger.
866+
DebugContext.IsActive = false;
867+
868+
// If the debug server is active, we need to synchronize state and stop it.
869+
if (DebugContext.IsDebugServerActive)
870+
{
871+
_languageServer?.SendNotification("powerShell/stopDebugger");
872+
}
873+
}
874+
857875
private void OnDebuggerStopped(object sender, DebuggerStopEventArgs debuggerStopEventArgs)
858876
{
877+
// The debugger has officially started. We use this to later check if we should stop it.
878+
DebugContext.IsActive = true;
879+
880+
// If the debug server is NOT active, we need to synchronize state and start it.
859881
if (!DebugContext.IsDebugServerActive)
860882
{
861-
// If the we've hit a breakpoint and the debug server is not active, then we need to
862-
// start it (and own stopping it later).
863-
DebugContext.OwnsDebugServerState = true;
864883
_languageServer?.SendNotification("powerShell/startDebugger");
865884
}
866885

0 commit comments

Comments
 (0)