Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 381e7fe

Browse files
Merge pull request #1778 from PowerShell/andschwa/fix-debugger
2 parents 08431bf + 3cba30c commit 381e7fe

File tree

4 files changed

+70
-59
lines changed

4 files changed

+70
-59
lines changed

‎src/PowerShellEditorServices/Services/DebugAdapter/Handlers/LaunchAndAttachHandler.cs‎

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,16 @@
1111
using Microsoft.Extensions.Logging;
1212
using Microsoft.PowerShell.EditorServices.Logging;
1313
using Microsoft.PowerShell.EditorServices.Services;
14-
using OmniSharp.Extensions.DebugAdapter.Protocol.Events;
15-
using OmniSharp.Extensions.JsonRpc;
16-
using Microsoft.PowerShell.EditorServices.Services.TextDocument;
17-
using OmniSharp.Extensions.DebugAdapter.Protocol.Requests;
18-
using OmniSharp.Extensions.DebugAdapter.Protocol.Server;
1914
using Microsoft.PowerShell.EditorServices.Services.PowerShell;
2015
using Microsoft.PowerShell.EditorServices.Services.PowerShell.Context;
21-
using Microsoft.PowerShell.EditorServices.Services.PowerShell.Runspace;
2216
using Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution;
17+
using Microsoft.PowerShell.EditorServices.Services.PowerShell.Host;
18+
using Microsoft.PowerShell.EditorServices.Services.PowerShell.Runspace;
19+
using Microsoft.PowerShell.EditorServices.Services.TextDocument;
20+
using OmniSharp.Extensions.DebugAdapter.Protocol.Events;
21+
using OmniSharp.Extensions.DebugAdapter.Protocol.Requests;
22+
using OmniSharp.Extensions.DebugAdapter.Protocol.Server;
23+
using OmniSharp.Extensions.JsonRpc;
2324

2425
namespace Microsoft.PowerShell.EditorServices.Handlers
2526
{
@@ -122,6 +123,9 @@ public LaunchAndAttachHandler(
122123

123124
public async Task<LaunchResponse> Handle(PsesLaunchRequestArguments request, CancellationToken cancellationToken)
124125
{
126+
// The debugger has officially started. We use this to later check if we should stop it.
127+
((PsesInternalHost)_executionService).DebugContext.IsActive = true;
128+
125129
_debugEventHandlerService.RegisterEventHandlers();
126130

127131
// Determine whether or not the working directory should be set in the PowerShellContext.
@@ -213,6 +217,9 @@ public async Task<LaunchResponse> Handle(PsesLaunchRequestArguments request, Can
213217

214218
public async Task<AttachResponse> Handle(PsesAttachRequestArguments request, CancellationToken cancellationToken)
215219
{
220+
// The debugger has officially started. We use this to later check if we should stop it.
221+
((PsesInternalHost)_executionService).DebugContext.IsActive = true;
222+
216223
_debugStateService.IsAttachSession = true;
217224

218225
_debugEventHandlerService.RegisterEventHandlers();

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

Lines changed: 28 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ public void EnableDebugMode()
9494
_psesHost.Runspace.Debugger.SetDebugMode(DebugModes.LocalScript | DebugModes.RemoteScript);
9595
}
9696

97-
public void Abort() => SetDebugResuming(DebuggerResumeAction.Stop,isDisconnect:true);
97+
public void Abort() => SetDebugResuming(DebuggerResumeAction.Stop);
9898

9999
public void BreakExecution() => _psesHost.Runspace.Debugger.SetDebuggerStepMode(enabled: true);
100100

@@ -106,10 +106,12 @@ public void EnableDebugMode()
106106

107107
public void StepOver() => SetDebugResuming(DebuggerResumeAction.StepOver);
108108

109-
public void SetDebugResuming(DebuggerResumeAction debuggerResumeAction,boolisDisconnect=false)
109+
public void SetDebugResuming(DebuggerResumeAction debuggerResumeAction)
110110
{
111-
// NOTE: We exit because the paused/stopped debugger is currently in a prompt REPL, and
112-
// to resume the debugger we must exit that REPL.
111+
// We exit because the paused/stopped debugger is currently in a prompt REPL, and to
112+
// resume the debugger we must exit that REPL. If we're continued from 'c' or 's', this
113+
// is already set and so is a no-op; but if the user clicks the continue or step button,
114+
// then this came over LSP and we need to set it.
113115
_psesHost.SetExit();
114116

115117
if (LastStopEventArgs is not null)
@@ -127,23 +129,31 @@ public void SetDebugResuming(DebuggerResumeAction debuggerResumeAction, bool isD
127129
return;
128130
}
129131

130-
if (debuggerResumeAction is DebuggerResumeAction.Stop)
132+
// If we're stopping (or disconnecting, which is the same thing in LSP-land), then we
133+
// want to cancel any debug prompts, remote prompts, debugged scripts, etc. However, if
134+
// the debugged script has exited normally (or was quit with 'q'), we still get an LSP
135+
// notification that eventually lands here with a stop event. In this case, the debug
136+
// context is NOT active and we do not want to cancel the regular REPL.
137+
if (!_psesHost.DebugContext.IsActive)
131138
{
132-
// If we're disconnecting we want to unwind all the way back to the default, local
133-
// state. So we use UnwindCallStack here to ensure every context frame is cancelled.
134-
if (isDisconnect)
135-
{
136-
_psesHost.UnwindCallStack();
137-
return;
138-
}
139+
return;
140+
}
139141

140-
_psesHost.CancelIdleParentTask();
142+
// If the debugger is active and we're stopping, we need to unwind everything.
143+
if (debuggerResumeAction is DebuggerResumeAction.Stop)
144+
{
145+
// TODO: We need to assign cancellation tokens to each frame, because the current
146+
// logic results in a deadlock here when we try to cancel the scopes...which
147+
// includes ourself (hence running it in a separate thread).
148+
Task.Run(() => _psesHost.UnwindCallStack());
141149
return;
142150
}
143151

152+
// Otherwise we're continuing or stepping (i.e. resuming) so we need to cancel the
153+
// debugger REPL.
144154
if (_psesHost.CurrentFrame.IsRepl)
145155
{
146-
_psesHost.CancelCurrentTask();
156+
_psesHost.CancelIdleParentTask();
147157
}
148158
}
149159

@@ -166,15 +176,14 @@ public void ProcessDebuggerResult(DebuggerCommandResults debuggerResult)
166176
{
167177
if (debuggerResult?.ResumeAction is not null)
168178
{
169-
SetDebugResuming(debuggerResult.ResumeAction.Value);
170-
171-
// If a debugging command like `c` is specified in a nested remote
172-
// debugging prompt we need to unwind the nested execution loop.
173-
if (_psesHost.CurrentFrame.IsRemote)
179+
// Since we're processing a command like 'c' or 's' remotely, we need to tell the
180+
// host to stop the remote REPL loop.
181+
if (debuggerResult.ResumeAction is not DebuggerResumeAction.Stop || _psesHost.CurrentFrame.IsRemote)
174182
{
175183
_psesHost.ForceSetExit();
176184
}
177185

186+
SetDebugResuming(debuggerResult.ResumeAction.Value);
178187
RaiseDebuggerResumingEvent(new DebuggerResumingEventArgs(debuggerResult.ResumeAction.Value));
179188

180189
// The Terminate exception is used by the engine for flow control

‎src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousPowerShellTask.cs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ public override IReadOnlyList<TResult> Run(CancellationToken cancellationToken)
6262

6363
if (PowerShellExecutionOptions.WriteInputToHost)
6464
{
65-
_psesHost.WriteWithPrompt(_psCommand,cancellationToken);
65+
_psesHost.UI.WriteLine(_psCommand.GetInvocationText());
6666
}
6767

6868
return _pwsh.Runspace.Debugger.InBreakpoint

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

Lines changed: 28 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -729,6 +729,19 @@ private void DoOneRepl(CancellationToken cancellationToken)
729729
return;
730730
}
731731

732+
// TODO: We must remove this awful logic, it causes so much pain. The StopDebugContext()
733+
// requires that we're not in a prompt that we're skipping, otherwise the debugger is
734+
// "active" but we haven't yet hit a breakpoint.
735+
//
736+
// When a task must run in the foreground, we cancel out of the idle loop and return to
737+
// the top level. At that point, we would normally run a REPL, but we need to
738+
// immediately execute the task. So we set _skipNextPrompt to do that.
739+
if (_skipNextPrompt)
740+
{
741+
_skipNextPrompt = false;
742+
return;
743+
}
744+
732745
// We use the REPL as a poll to check if the debug context is active but PowerShell
733746
// indicates we're no longer debugging. This happens when PowerShell was used to start
734747
// the debugger (instead of using a Code launch configuration) via Wait-Debugger or
@@ -741,15 +754,6 @@ private void DoOneRepl(CancellationToken cancellationToken)
741754
StopDebugContext();
742755
}
743756

744-
// When a task must run in the foreground, we cancel out of the idle loop and return to the top level.
745-
// At that point, we would normally run a REPL, but we need to immediately execute the task.
746-
// So we set _skipNextPrompt to do that.
747-
if (_skipNextPrompt)
748-
{
749-
_skipNextPrompt = false;
750-
return;
751-
}
752-
753757
try
754758
{
755759
string prompt = GetPrompt(cancellationToken);
@@ -758,18 +762,22 @@ private void DoOneRepl(CancellationToken cancellationToken)
758762

759763
// If the user input was empty it's because:
760764
// - the user provided no input
761-
// - the readline task was canceled
762-
// - CtrlC was sent to readline (which does not propagate a cancellation)
765+
// - the ReadLine task was canceled
766+
// - CtrlC was sent to ReadLine (which does not propagate a cancellation)
763767
//
764-
// In any event there's nothing to run in PowerShell, so we just loop back to the prompt again.
765-
// However, we must distinguish the last two scenarios, since PSRL will not print a new line in those cases.
768+
// In any event there's nothing to run in PowerShell, so we just loop back to the
769+
// prompt again. However, PSReadLine will not print a newline for CtrlC, so we print
770+
// one, but we do not want to print one if the ReadLine task was canceled.
766771
if (string.IsNullOrEmpty(userInput))
767772
{
768-
if (cancellationToken.IsCancellationRequested||LastKeyWasCtrlC())
773+
if (LastKeyWasCtrlC())
769774
{
770775
UI.WriteLine();
771776
}
772-
return;
777+
// Propogate cancellation if that's what happened, since ReadLine won't.
778+
// TODO: We may not need to do this at all.
779+
cancellationToken.ThrowIfCancellationRequested();
780+
return; // Task wasn't canceled but there was no input.
773781
}
774782

775783
InvokeInput(userInput, cancellationToken);
@@ -783,10 +791,8 @@ private void DoOneRepl(CancellationToken cancellationToken)
783791
{
784792
throw;
785793
}
786-
catch (FlowControlException)
787-
{
788-
// Do nothing, a break or continue statement was used outside of a loop.
789-
}
794+
// Do nothing, a break or continue statement was used outside of a loop.
795+
catch (FlowControlException) { }
790796
catch (Exception e)
791797
{
792798
UI.WriteErrorLine($"An error occurred while running the REPL loop:{Environment.NewLine}{e}");
@@ -830,25 +836,14 @@ private string GetPrompt(CancellationToken cancellationToken)
830836
return prompt;
831837
}
832838

833-
/// <summary>
834-
/// This is used to write the invocation text of a command with the user's prompt so that,
835-
/// for example, F8 (evaluate selection) appears as if the user typed it. Used when
836-
/// 'WriteInputToHost' is true.
837-
/// </summary>
838-
/// <param name="command">The PSCommand we'll print after the prompt.</param>
839-
/// <param name="cancellationToken"></param>
840-
public void WriteWithPrompt(PSCommand command, CancellationToken cancellationToken)
841-
{
842-
UI.Write(GetPrompt(cancellationToken));
843-
UI.WriteLine(command.GetInvocationText());
844-
}
845-
846839
private string InvokeReadLine(CancellationToken cancellationToken)
847840
{
848-
cancellationToken.ThrowIfCancellationRequested();
849841
try
850842
{
843+
// TODO: If we can pass the cancellation token to ReadKey directly in PSReadLine, we
844+
// can remove this logic.
851845
_readKeyCancellationToken = cancellationToken;
846+
cancellationToken.ThrowIfCancellationRequested();
852847
return _readLineProvider.ReadLine.ReadLine(cancellationToken);
853848
}
854849
finally

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /