diff --git a/src/SharpIDE.Application/Features/Debugging/Debugger.cs b/src/SharpIDE.Application/Features/Debugging/Debugger.cs index 368a327..291c2a7 100644 --- a/src/SharpIDE.Application/Features/Debugging/Debugger.cs +++ b/src/SharpIDE.Application/Features/Debugging/Debugger.cs @@ -1,4 +1,5 @@ -using SharpIDE.Application.Features.SolutionDiscovery; +using Microsoft.VisualStudio.Shared.VSCodeDebugProtocol.Messages; +using SharpIDE.Application.Features.SolutionDiscovery; using SharpIDE.Application.Features.SolutionDiscovery.VsPersistence; namespace SharpIDE.Application.Features.Debugging; @@ -15,5 +16,7 @@ public class Debugger } public async Task StepOver(int threadId, CancellationToken cancellationToken = default) => await _debuggingService.StepOver(threadId, cancellationToken); - public async Task GetInfoAtStopPoint() => await _debuggingService.GetInfoAtStopPoint(); + public async Task> GetThreadsAtStopPoint() => await _debuggingService.GetThreadsAtStopPoint(); + public async Task> GetStackFramesForThread(int threadId) => await _debuggingService.GetStackFramesForThread(threadId); + public async Task> GetVariablesForStackFrame(int frameId) => await _debuggingService.GetVariablesForStackFrame(frameId); } diff --git a/src/SharpIDE.Application/Features/Debugging/DebuggingService.cs b/src/SharpIDE.Application/Features/Debugging/DebuggingService.cs index df3cc7f..1dea5b9 100644 --- a/src/SharpIDE.Application/Features/Debugging/DebuggingService.cs +++ b/src/SharpIDE.Application/Features/Debugging/DebuggingService.cs @@ -163,57 +163,72 @@ public class DebuggingService _debugProtocolHost.SendRequestSync(nextRequest); } - public async Task GetInfoAtStopPoint() + public async Task> GetThreadsAtStopPoint() { - var model = new ThreadsStackTraceModel(); - try + var threadsRequest = new ThreadsRequest(); + var threadsResponse = _debugProtocolHost.SendRequestSync(threadsRequest); + var mappedThreads = threadsResponse.Threads.Select(s => new ThreadModel { - var threads = _debugProtocolHost.SendRequestSync(new ThreadsRequest()); - foreach (var thread in threads.Threads) + Id = s.Id, + Name = s.Name + }).ToList(); + return mappedThreads; + } + + public async Task> GetStackFramesForThread(int threadId) + { + var stackTraceRequest = new StackTraceRequest { ThreadId = threadId }; + var stackTraceResponse = _debugProtocolHost.SendRequestSync(stackTraceRequest); + var stackFrames = stackTraceResponse.StackFrames; + + var mappedStackFrames = stackFrames!.Select(frame => + { + var isExternalCode = frame.Name == "[External Code]"; + ManagedStackFrameInfo? managedStackFrameInfo = isExternalCode ? null : ParseStackFrameName(frame.Name); + return new StackFrameModel { - var threadModel = new ThreadModel { Id = thread.Id, Name = thread.Name }; - model.Threads.Add(threadModel); - var stackTrace = _debugProtocolHost.SendRequestSync(new StackTraceRequest { ThreadId = thread.Id }); - var frame = stackTrace.StackFrames!.FirstOrDefault(); - if (frame == null) continue; - var name = frame.Name; - if (name == "[External Code]") continue; // TODO: handle this case + Id = frame.Id, + Name = frame.Name, + Line = frame.Line, + Column = frame.Column, + Source = frame.Source?.Path, + IsExternalCode = isExternalCode, + ManagedInfo = managedStackFrameInfo, + }; + }).ToList(); + return mappedStackFrames; + } - // Infrastructure.dll!Infrastructure.DependencyInjection.AddInfrastructure(Microsoft.Extensions.DependencyInjection.IServiceCollection services, Microsoft.Extensions.Configuration.IConfiguration configuration) Line 23 - // need to parse out the class name, method name, namespace, assembly name - var methodName = name.Split('!')[1].Split('(')[0]; - var className = methodName.Split('.').Reverse().Skip(1).First(); - var namespaceName = string.Join('.', methodName.Split('.').Reverse().Skip(2).Reverse()); - var assemblyName = name.Split('!')[0]; - methodName = methodName.Split('.').Reverse().First(); - var frameModel = new StackFrameModel - { - Id = frame.Id, - Name = frame.Name, - Line = frame.Line, - Column = frame.Column, - Source = frame.Source?.Path, - ClassName = className, - MethodName = methodName, - Namespace = namespaceName, - AssemblyName = assemblyName - }; - threadModel.StackFrames.Add(frameModel); - var scopes = _debugProtocolHost.SendRequestSync(new ScopesRequest { FrameId = frame.Id }); - foreach (var scope in scopes.Scopes) - { - var scopeModel = new ScopeModel { Name = scope.Name }; - frameModel.Scopes.Add(scopeModel); - var variablesResponse = _debugProtocolHost.SendRequestSync(new VariablesRequest { VariablesReference = scope.VariablesReference }); - scopeModel.Variables = variablesResponse.Variables; - } - } - } - catch (Exception) + public async Task> GetVariablesForStackFrame(int frameId) + { + var scopesRequest = new ScopesRequest { FrameId = frameId }; + var scopesResponse = _debugProtocolHost.SendRequestSync(scopesRequest); + var allVariables = new List(); + foreach (var scope in scopesResponse.Scopes) { - throw; + var variablesRequest = new VariablesRequest { VariablesReference = scope.VariablesReference }; + var variablesResponse = _debugProtocolHost.SendRequestSync(variablesRequest); + allVariables.AddRange(variablesResponse.Variables); } + return allVariables; + } - return model; + // netcoredbg does not provide the stack frame name in this format, so don't use this if using netcoredbg + private static ManagedStackFrameInfo? ParseStackFrameName(string name) + { + return null; + var methodName = name.Split('!')[1].Split('(')[0]; + var className = methodName.Split('.').Reverse().Skip(1).First(); + var namespaceName = string.Join('.', methodName.Split('.').Reverse().Skip(2).Reverse()); + var assemblyName = name.Split('!')[0]; + methodName = methodName.Split('.').Reverse().First(); + var managedStackFrameInfo = new ManagedStackFrameInfo + { + MethodName = methodName, + ClassName = className, + Namespace = namespaceName, + AssemblyName = assemblyName + }; + return managedStackFrameInfo; } } diff --git a/src/SharpIDE.Application/Features/Debugging/ThreadsStackTraceModel.cs b/src/SharpIDE.Application/Features/Debugging/ThreadsStackTraceModel.cs index 78a27dd..5be3778 100644 --- a/src/SharpIDE.Application/Features/Debugging/ThreadsStackTraceModel.cs +++ b/src/SharpIDE.Application/Features/Debugging/ThreadsStackTraceModel.cs @@ -1,36 +1,26 @@ -using Microsoft.VisualStudio.Shared.VSCodeDebugProtocol.Messages; +namespace SharpIDE.Application.Features.Debugging; -namespace SharpIDE.Application.Features.Debugging; - -public class ThreadsStackTraceModel +public class StackFrameModel { - public List Threads { get; set; } = []; + public required int Id { get; set; } + public required string Name { get; set; } + public required int? Line { get; set; } + public required int? Column { get; set; } + public required string? Source { get; set; } + public required bool IsExternalCode { get; set; } + public required ManagedStackFrameInfo? ManagedInfo { get; set; } +} + +public record struct ManagedStackFrameInfo +{ + public required string ClassName { get; set; } + public required string MethodName { get; set; } + public required string Namespace { get; set; } + public required string AssemblyName { get; set; } } public class ThreadModel { public required int Id { get; set; } public required string Name { get; set; } - public List StackFrames { get; set; } = []; -} - -public class StackFrameModel -{ - public required string ClassName { get; set; } - public required string MethodName { get; set; } - public required string Namespace { get; set; } - public required string AssemblyName { get; set; } - - public required int Id { get; set; } - public required string Name { get; set; } - public required int? Line { get; set; } - public required int? Column { get; set; } - public required string? Source { get; set; } - public List Scopes { get; set; } = []; -} - -public class ScopeModel -{ - public required string Name { get; set; } - public List Variables { get; set; } = []; } diff --git a/src/SharpIDE.Application/Features/Run/RunService.cs b/src/SharpIDE.Application/Features/Run/RunService.cs index 63e351e..eae125b 100644 --- a/src/SharpIDE.Application/Features/Run/RunService.cs +++ b/src/SharpIDE.Application/Features/Run/RunService.cs @@ -3,12 +3,14 @@ using System.Threading.Channels; using Ardalis.GuardClauses; using AsyncReadProcess; using Microsoft.Extensions.Logging; +using Microsoft.VisualStudio.Shared.VSCodeDebugProtocol.Messages; using SharpIDE.Application.Features.Analysis; using SharpIDE.Application.Features.Debugging; using SharpIDE.Application.Features.Evaluation; using SharpIDE.Application.Features.Events; using SharpIDE.Application.Features.SolutionDiscovery; using SharpIDE.Application.Features.SolutionDiscovery.VsPersistence; +using Breakpoint = SharpIDE.Application.Features.Debugging.Breakpoint; namespace SharpIDE.Application.Features.Run; @@ -153,9 +155,17 @@ public class RunService(ILogger logger, RoslynAnalysis roslynAnalysi await _debugger!.StepOver(threadId); } - public async Task GetInfoAtStopPoint() + public async Task> GetThreadsAtStopPoint() { - return await _debugger!.GetInfoAtStopPoint(); + return await _debugger!.GetThreadsAtStopPoint(); + } + public async Task> GetStackFrames(int threadId) + { + return await _debugger!.GetStackFramesForThread(threadId); + } + public async Task> GetVariablesForStackFrame(int frameId) + { + return await _debugger!.GetVariablesForStackFrame(frameId); } private async Task GetRunArguments(SharpIdeProjectModel project) diff --git a/src/SharpIDE.Godot/Features/Debug_/Tab/SubTabs/ThreadsVariablesSubTab.cs b/src/SharpIDE.Godot/Features/Debug_/Tab/SubTabs/ThreadsVariablesSubTab.cs index 266cae4..d789690 100644 --- a/src/SharpIDE.Godot/Features/Debug_/Tab/SubTabs/ThreadsVariablesSubTab.cs +++ b/src/SharpIDE.Godot/Features/Debug_/Tab/SubTabs/ThreadsVariablesSubTab.cs @@ -10,9 +10,11 @@ namespace SharpIDE.Godot.Features.Debug_.Tab.SubTabs; public partial class ThreadsVariablesSubTab : Control { private PackedScene _threadListItemScene = GD.Load("res://Features/Debug_/Tab/SubTabs/ThreadListItem.tscn"); - private VBoxContainer _threadsVboxContainer = null!; - private VBoxContainer _stackFramesVboxContainer = null!; - private VBoxContainer _variablesVboxContainer = null!; + + private Tree _threadsTree = null!; + private Tree _stackFramesTree = null!; + private Tree _variablesTree = null!; + public SharpIdeProjectModel Project { get; set; } = null!; // private ThreadModel? _selectedThread = null!; // null when not at a stop point @@ -20,63 +22,77 @@ public partial class ThreadsVariablesSubTab : Control public override void _Ready() { - _threadsVboxContainer = GetNode("%ThreadsVBoxContainer"); - _stackFramesVboxContainer = GetNode("%StackFramesVBoxContainer"); - _variablesVboxContainer = GetNode("%VariablesVBoxContainer"); - GlobalEvents.Instance.DebuggerExecutionStopped.Subscribe(OnDebuggerExecutionStopped); - + _threadsTree = GetNode("%ThreadsTree"); + _stackFramesTree = GetNode("%StackFramesTree"); + _variablesTree = GetNode("%VariablesTree"); + GlobalEvents.Instance.DebuggerExecutionStopped.Subscribe(OnDebuggerExecutionStopped2); + _threadsTree.ItemSelected += OnThreadSelected; + _stackFramesTree.ItemSelected += OnStackFrameSelected; + } + + private async void OnThreadSelected() + { + var selectedItem = _threadsTree.GetSelected(); + Guard.Against.Null(selectedItem); + var threadId = selectedItem.GetMetadata(0).AsInt32(); + var stackFrames = await _runService.GetStackFrames(threadId); + await this.InvokeAsync(() => + { + _variablesTree.Clear(); // If we select a thread that does not have stack frames, the variables would not be cleared otherwise + _stackFramesTree.Clear(); + var root = _stackFramesTree.CreateItem(); + foreach (var (index, s) in stackFrames.Index()) + { + var stackFrameItem = _stackFramesTree.CreateItem(root); + if (s.IsExternalCode) + { + stackFrameItem.SetText(0, "[External Code]"); + } + else + { + // for now, just use the raw name + stackFrameItem.SetText(0, s.Name); + //var managedFrameInfo = s.ManagedInfo!.Value; + //stackFrameItem.SetText(0, $"{managedFrameInfo.ClassName}.{managedFrameInfo.MethodName}() in {managedFrameInfo.Namespace}, {managedFrameInfo.AssemblyName}"); + } + stackFrameItem.SetMetadata(0, s.Id); + if (index is 0) _stackFramesTree.SetSelected(stackFrameItem, 0); + } + }); + } + + private async void OnStackFrameSelected() + { + var selectedItem = _stackFramesTree.GetSelected(); + Guard.Against.Null(selectedItem); + var frameId = selectedItem.GetMetadata(0).AsInt32(); + var variables = await _runService.GetVariablesForStackFrame(frameId); + await this.InvokeAsync(() => + { + _variablesTree.Clear(); + var root = _variablesTree.CreateItem(); + foreach (var variable in variables) + { + var variableItem = _variablesTree.CreateItem(root); + variableItem.SetText(0, $$"""{{variable.Name}} = {{{variable.Type}}} {{variable.Value}}"""); + } + }); } - private async Task OnDebuggerExecutionStopped(ExecutionStopInfo stopInfo) - { - var result = await _runService.GetInfoAtStopPoint(); - var threadScenes = result.Threads.Select(s => - { - var threadListItem = _threadListItemScene.Instantiate(); - threadListItem.GetNode