184 lines
6.5 KiB
C#
184 lines
6.5 KiB
C#
using Ardalis.GuardClauses;
|
|
using Godot;
|
|
using Microsoft.VisualStudio.Shared.VSCodeDebugProtocol.Messages;
|
|
using SharpIDE.Application.Features.Debugging;
|
|
using SharpIDE.Application.Features.Events;
|
|
using SharpIDE.Application.Features.Run;
|
|
using SharpIDE.Application.Features.SolutionDiscovery.VsPersistence;
|
|
|
|
namespace SharpIDE.Godot.Features.Debug_.Tab.SubTabs;
|
|
|
|
public partial class ThreadsVariablesSubTab : Control
|
|
{
|
|
private PackedScene _threadListItemScene = GD.Load<PackedScene>("res://Features/Debug_/Tab/SubTabs/ThreadListItem.tscn");
|
|
|
|
private readonly Texture2D _fieldIcon = ResourceLoader.Load<Texture2D>("uid://c4y7d5m4upfju");
|
|
private readonly Texture2D _propertyIcon = ResourceLoader.Load<Texture2D>("uid://y5pwrwwrjqmc");
|
|
private readonly Texture2D _staticMembersIcon = ResourceLoader.Load<Texture2D>("uid://dudntp20myuxb");
|
|
|
|
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
|
|
|
|
[Inject] private readonly RunService _runService = null!;
|
|
|
|
private Callable? _debuggerVariableCustomDrawCallable;
|
|
private readonly Dictionary<TreeItem, Variable> _variableReferenceLookup = []; // primarily used for DebuggerVariableCustomDraw
|
|
|
|
public override void _Ready()
|
|
{
|
|
_threadsTree = GetNode<Tree>("%ThreadsTree");
|
|
_stackFramesTree = GetNode<Tree>("%StackFramesTree");
|
|
_variablesTree = GetNode<Tree>("%VariablesTree");
|
|
_debuggerVariableCustomDrawCallable = new Callable(this, MethodName.DebuggerVariableCustomDraw);
|
|
GlobalEvents.Instance.DebuggerExecutionStopped.Subscribe(OnDebuggerExecutionStopped);
|
|
GlobalEvents.Instance.DebuggerExecutionContinued.Subscribe(ClearAllTrees);
|
|
_threadsTree.ItemSelected += OnThreadSelected;
|
|
_stackFramesTree.ItemSelected += OnStackFrameSelected;
|
|
_variablesTree.ItemCollapsed += OnVariablesItemExpandedOrCollapsed;
|
|
Project.ProjectStoppedRunning.Subscribe(ClearAllTrees);
|
|
}
|
|
|
|
private void OnVariablesItemExpandedOrCollapsed(TreeItem item)
|
|
{
|
|
var wasExpanded = item.IsCollapsed() is false;
|
|
var metadata = item.GetMetadata(0).AsVector2I();
|
|
var alreadyRetrievedChildren = metadata.X == 1;
|
|
if (wasExpanded && alreadyRetrievedChildren is false)
|
|
{
|
|
// retrieve children
|
|
var variablesReferenceId = metadata.Y;
|
|
_ = Task.GodotRun(async () =>
|
|
{
|
|
var variables = await _runService.GetVariablesForVariablesReference(variablesReferenceId);
|
|
await this.InvokeAsync(() =>
|
|
{
|
|
var placeholderLoadingChild = item.GetFirstChild();
|
|
Guard.Against.Null(placeholderLoadingChild);
|
|
placeholderLoadingChild.Visible = false; // Set to visible false rather than RemoveChild, so we don't have to Free
|
|
foreach (var variable in variables)
|
|
{
|
|
AddVariableToTreeItem(item, variable);
|
|
}
|
|
// mark as retrieved
|
|
item.SetMetadata(0, new Vector2I(1, variablesReferenceId));
|
|
});
|
|
});
|
|
}
|
|
}
|
|
|
|
public override void _ExitTree()
|
|
{
|
|
GlobalEvents.Instance.DebuggerExecutionStopped.Unsubscribe(OnDebuggerExecutionStopped);
|
|
GlobalEvents.Instance.DebuggerExecutionContinued.Unsubscribe(ClearAllTrees);
|
|
Project.ProjectStoppedRunning.Unsubscribe(ClearAllTrees);
|
|
}
|
|
|
|
private async Task ClearAllTrees()
|
|
{
|
|
await this.InvokeAsync(() =>
|
|
{
|
|
_threadsTree.Clear();
|
|
_stackFramesTree.Clear();
|
|
_variablesTree.Clear();
|
|
_variableReferenceLookup.Clear();
|
|
});
|
|
}
|
|
|
|
private async void OnThreadSelected()
|
|
{
|
|
var selectedItem = _threadsTree.GetSelected();
|
|
Guard.Against.Null(selectedItem);
|
|
var threadId = selectedItem.GetMetadata(0).AsInt32();
|
|
await Task.CompletedTask.ConfigureAwait(ConfigureAwaitOptions.ForceYielding);
|
|
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();
|
|
await Task.CompletedTask.ConfigureAwait(ConfigureAwaitOptions.ForceYielding);
|
|
var variables = await _runService.GetVariablesForStackFrame(frameId);
|
|
await this.InvokeAsync(() =>
|
|
{
|
|
_variablesTree.Clear();
|
|
var root = _variablesTree.CreateItem();
|
|
foreach (var variable in variables)
|
|
{
|
|
AddVariableToTreeItem(root, variable);
|
|
}
|
|
});
|
|
}
|
|
|
|
private void AddVariableToTreeItem(TreeItem parentItem, Variable variable)
|
|
{
|
|
var variableItem = _variablesTree.CreateItem(parentItem);
|
|
_variableReferenceLookup[variableItem] = variable;
|
|
|
|
variableItem.SetMetadata(0, new Vector2I(0, variable.VariablesReference));
|
|
if (variable.Name == "Static members")
|
|
{
|
|
variableItem.SetTooltipText(0, null);
|
|
variableItem.SetIcon(0, _staticMembersIcon);
|
|
variableItem.SetText(0, "Static members");
|
|
}
|
|
else
|
|
{
|
|
variableItem.SetCellMode(0, TreeItem.TreeCellMode.Custom);
|
|
variableItem.SetCustomAsButton(0, true);
|
|
variableItem.SetCustomDrawCallback(0, _debuggerVariableCustomDrawCallable!.Value);
|
|
}
|
|
if (variable.VariablesReference is not 0)
|
|
{
|
|
var placeHolderItem = _variablesTree.CreateItem(variableItem);
|
|
placeHolderItem.SetText(0, "Loading...");
|
|
variableItem.Collapsed = true;
|
|
}
|
|
}
|
|
|
|
|
|
private async Task OnDebuggerExecutionStopped(ExecutionStopInfo stopInfo)
|
|
{
|
|
var threads = await _runService.GetThreadsAtStopPoint();
|
|
await this.InvokeAsync(() =>
|
|
{
|
|
_threadsTree.Clear();
|
|
var root = _threadsTree.CreateItem();
|
|
foreach (var thread in threads)
|
|
{
|
|
var threadItem = _threadsTree.CreateItem(root);
|
|
threadItem.SetText(0, $"@{thread.Id}: {thread.Name}");
|
|
threadItem.SetMetadata(0, thread.Id);
|
|
if (thread.Id == stopInfo.ThreadId) _threadsTree.SetSelected(threadItem, 0);
|
|
}
|
|
});
|
|
}
|
|
} |