diff --git a/src/SharpIDE.Application/Features/Debugging/Debugger.cs b/src/SharpIDE.Application/Features/Debugging/Debugger.cs index 475eb40..0c63a48 100644 --- a/src/SharpIDE.Application/Features/Debugging/Debugger.cs +++ b/src/SharpIDE.Application/Features/Debugging/Debugger.cs @@ -13,4 +13,6 @@ public class Debugger { await _debuggingService.Attach(ProcessId, breakpointsByFile, cancellationToken); } + + public async Task StepOver(int threadId, CancellationToken cancellationToken = default) => await _debuggingService.StepOver(threadId, cancellationToken); } diff --git a/src/SharpIDE.Application/Features/Debugging/DebuggingService.cs b/src/SharpIDE.Application/Features/Debugging/DebuggingService.cs index 31e848a..139f1f5 100644 --- a/src/SharpIDE.Application/Features/Debugging/DebuggingService.cs +++ b/src/SharpIDE.Application/Features/Debugging/DebuggingService.cs @@ -72,7 +72,8 @@ public class DebuggingService var dict = prop?.GetValue(@event) as Dictionary; var filePath = dict?["source"]?["path"]!.Value()!; var line = (dict?["line"]?.Value()!).Value; - GlobalEvents.InvokeDebuggerExecutionStopped(filePath, line); + var executionStopInfo = new ExecutionStopInfo { FilePath = filePath, Line = line, ThreadId = @event.ThreadId!.Value }; + GlobalEvents.InvokeDebuggerExecutionStopped(executionStopInfo); if (@event.Reason is StoppedEvent.ReasonValue.Exception) { Console.WriteLine("Stopped due to exception, continuing"); @@ -125,4 +126,11 @@ public class DebuggingService debugProtocolHost.SendRequestSync(configurationDoneRequest); } // Typically you would do attachRequest, configurationDoneRequest, setBreakpointsRequest, then ResumeRuntime. But netcoredbg blows up on configurationDoneRequuest if ResumeRuntime hasn't been called yet. + + public async Task StepOver(int threadId, CancellationToken cancellationToken) + { + await Task.CompletedTask.ConfigureAwait(ConfigureAwaitOptions.ForceYielding); + var nextRequest = new NextRequest(threadId); + _debugProtocolHost.SendRequestSync(nextRequest); + } } diff --git a/src/SharpIDE.Application/Features/Debugging/ExecutionStopInfo.cs b/src/SharpIDE.Application/Features/Debugging/ExecutionStopInfo.cs new file mode 100644 index 0000000..30da7a6 --- /dev/null +++ b/src/SharpIDE.Application/Features/Debugging/ExecutionStopInfo.cs @@ -0,0 +1,8 @@ +namespace SharpIDE.Application.Features.Debugging; + +public class ExecutionStopInfo +{ + public required string FilePath { get; init; } + public required int Line { get; init; } + public required int ThreadId { get; init; } +} diff --git a/src/SharpIDE.Application/Features/Events/GlobalEvents.cs b/src/SharpIDE.Application/Features/Events/GlobalEvents.cs index 958d6d7..29e62a5 100644 --- a/src/SharpIDE.Application/Features/Events/GlobalEvents.cs +++ b/src/SharpIDE.Application/Features/Events/GlobalEvents.cs @@ -18,6 +18,6 @@ public static class GlobalEvents public static event Func ProjectStoppedRunning = _ => Task.CompletedTask; public static void InvokeProjectStoppedRunning(SharpIdeProjectModel project) => ProjectStoppedRunning?.Invoke(project); - public static event Func DebuggerExecutionStopped = (_, _) => Task.CompletedTask; - public static void InvokeDebuggerExecutionStopped(string filePath, int line) => DebuggerExecutionStopped?.Invoke(filePath, line); + public static event Func DebuggerExecutionStopped = _ => Task.CompletedTask; + public static void InvokeDebuggerExecutionStopped(ExecutionStopInfo executionStopInfo) => DebuggerExecutionStopped?.Invoke(executionStopInfo); } diff --git a/src/SharpIDE.Application/Features/Run/RunService.cs b/src/SharpIDE.Application/Features/Run/RunService.cs index 7a5e94e..cc75573 100644 --- a/src/SharpIDE.Application/Features/Run/RunService.cs +++ b/src/SharpIDE.Application/Features/Run/RunService.cs @@ -14,6 +14,7 @@ public class RunService { private readonly ConcurrentDictionary _projectLocks = []; public ConcurrentDictionary> Breakpoints { get; } = []; + private Debugger? _debugger; // TODO: Support multiple debuggers for multiple running projects public async Task RunProject(SharpIdeProjectModel project) { var isDebug = true; @@ -84,8 +85,9 @@ public class RunService if (isDebug) { // Attach debugger (which internally uses a DiagnosticClient to resume startup) - var debuggingService = new Debugger { Project = project, ProcessId = process.ProcessId }; - await debuggingService.Attach(project.RunningCancellationTokenSource.Token, Breakpoints.ToDictionary()).ConfigureAwait(false); + var debugger = new Debugger { Project = project, ProcessId = process.ProcessId }; + _debugger = debugger; + await debugger.Attach(project.RunningCancellationTokenSource.Token, Breakpoints.ToDictionary()).ConfigureAwait(false); } project.Running = true; @@ -126,6 +128,11 @@ public class RunService await project.RunningCancellationTokenSource.CancelAsync().ConfigureAwait(false); } + public async Task SendDebuggerStepOver(int threadId) + { + await _debugger!.StepOver(threadId); + } + private string GetRunArguments(SharpIdeProjectModel project) { var dllFullPath = ProjectEvaluation.GetOutputDllFullPath(project); diff --git a/src/SharpIDE.Godot/InputStringNames.cs b/src/SharpIDE.Godot/InputStringNames.cs index a9d5ea5..8ba1987 100644 --- a/src/SharpIDE.Godot/InputStringNames.cs +++ b/src/SharpIDE.Godot/InputStringNames.cs @@ -5,4 +5,5 @@ namespace SharpIDE.Godot; public static class InputStringNames { public static readonly StringName CodeFixes = "CodeFixes"; + public static readonly StringName StepOver = "StepOver"; } \ No newline at end of file diff --git a/src/SharpIDE.Godot/SharpIdeCodeEdit.cs b/src/SharpIDE.Godot/SharpIdeCodeEdit.cs index 9439c99..5a931a0 100644 --- a/src/SharpIDE.Godot/SharpIdeCodeEdit.cs +++ b/src/SharpIDE.Godot/SharpIdeCodeEdit.cs @@ -14,6 +14,7 @@ using SharpIDE.Application.Features.Analysis; using SharpIDE.Application.Features.Debugging; using SharpIDE.Application.Features.Events; using SharpIDE.Application.Features.SolutionDiscovery; +using SharpIDE.Godot.Features.Run; using Task = System.Threading.Tasks.Task; namespace SharpIDE.Godot; @@ -34,6 +35,7 @@ public partial class SharpIdeCodeEdit : CodeEdit private ImmutableArray<(FileLinePositionSpan fileSpan, Diagnostic diagnostic)> _diagnostics = []; private ImmutableArray _currentCodeActionsInPopup = []; + private ExecutionStopInfo? _executionStopInfo; public override void _Ready() { @@ -51,11 +53,12 @@ public partial class SharpIdeCodeEdit : CodeEdit GlobalEvents.DebuggerExecutionStopped += OnDebuggerExecutionStopped; } - private async Task OnDebuggerExecutionStopped(string filePath, int line) + private async Task OnDebuggerExecutionStopped(ExecutionStopInfo executionStopInfo) { - if (filePath != _currentFile.Path) return; // TODO: handle file switching - var lineInt = line - 1; // Debugging is 1-indexed, Godot is 0-indexed + if (executionStopInfo.FilePath != _currentFile.Path) return; // TODO: handle file switching + var lineInt = executionStopInfo.Line - 1; // Debugging is 1-indexed, Godot is 0-indexed Guard.Against.Negative(lineInt, nameof(lineInt)); + _executionStopInfo = executionStopInfo; await this.InvokeAsync(() => { @@ -237,6 +240,19 @@ public partial class SharpIdeCodeEdit : CodeEdit { EmitSignalCodeFixesRequested(); } + else if (@event.IsActionPressed(InputStringNames.StepOver)) + { + SendDebuggerStepOver(); + } + } + + private void SendDebuggerStepOver() + { + if (_executionStopInfo is null) return; + _ = GodotTask.Run(async () => + { + await Singletons.RunService.SendDebuggerStepOver(_executionStopInfo.ThreadId); + }); } private void SetDiagnosticsModel(ImmutableArray<(FileLinePositionSpan fileSpan, Diagnostic diagnostic)> diagnostics) diff --git a/src/SharpIDE.Godot/project.godot b/src/SharpIDE.Godot/project.godot index 493360e..d6310fb 100644 --- a/src/SharpIDE.Godot/project.godot +++ b/src/SharpIDE.Godot/project.godot @@ -32,3 +32,8 @@ CodeFixes={ "events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":true,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194309,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) ] } +StepOver={ +"deadzone": 0.2, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194341,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) +] +}