refactor debugging for multiple open files
This commit is contained in:
@@ -68,7 +68,7 @@ public class RunService
|
|||||||
SingleReader = true,
|
SingleReader = true,
|
||||||
SingleWriter = false,
|
SingleWriter = false,
|
||||||
});
|
});
|
||||||
var logsDrained = new TaskCompletionSource();
|
var logsDrained = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||||
_ = Task.Run(async () =>
|
_ = Task.Run(async () =>
|
||||||
{
|
{
|
||||||
await foreach(var log in process.CombinedOutputChannel.Reader.ReadAllAsync().ConfigureAwait(false))
|
await foreach(var log in process.CombinedOutputChannel.Reader.ReadAllAsync().ConfigureAwait(false))
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
|
using Ardalis.GuardClauses;
|
||||||
using Godot;
|
using Godot;
|
||||||
|
using SharpIDE.Application.Features.Debugging;
|
||||||
|
using SharpIDE.Application.Features.Events;
|
||||||
using SharpIDE.Application.Features.SolutionDiscovery;
|
using SharpIDE.Application.Features.SolutionDiscovery;
|
||||||
using SharpIDE.Application.Features.SolutionDiscovery.VsPersistence;
|
using SharpIDE.Application.Features.SolutionDiscovery.VsPersistence;
|
||||||
|
|
||||||
@@ -11,6 +14,7 @@ public partial class CodeEditorPanel : MarginContainer
|
|||||||
public SharpIdeSolutionModel Solution { get; set; } = null!;
|
public SharpIdeSolutionModel Solution { get; set; } = null!;
|
||||||
private PackedScene _sharpIdeCodeEditScene = GD.Load<PackedScene>("res://Features/CodeEditor/SharpIdeCodeEdit.tscn");
|
private PackedScene _sharpIdeCodeEditScene = GD.Load<PackedScene>("res://Features/CodeEditor/SharpIdeCodeEdit.tscn");
|
||||||
private TabContainer _tabContainer = null!;
|
private TabContainer _tabContainer = null!;
|
||||||
|
private ExecutionStopInfo? _debuggerExecutionStopInfo;
|
||||||
|
|
||||||
public override void _Ready()
|
public override void _Ready()
|
||||||
{
|
{
|
||||||
@@ -20,6 +24,15 @@ public partial class CodeEditorPanel : MarginContainer
|
|||||||
var tabBar = _tabContainer.GetTabBar();
|
var tabBar = _tabContainer.GetTabBar();
|
||||||
tabBar.TabCloseDisplayPolicy = TabBar.CloseButtonDisplayPolicy.ShowAlways;
|
tabBar.TabCloseDisplayPolicy = TabBar.CloseButtonDisplayPolicy.ShowAlways;
|
||||||
tabBar.TabClosePressed += OnTabClosePressed;
|
tabBar.TabClosePressed += OnTabClosePressed;
|
||||||
|
GlobalEvents.DebuggerExecutionStopped += OnDebuggerExecutionStopped;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void _UnhandledKeyInput(InputEvent @event)
|
||||||
|
{
|
||||||
|
if (@event.IsActionPressed(InputStringNames.StepOver))
|
||||||
|
{
|
||||||
|
SendDebuggerStepOver();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnTabClicked(long tab)
|
private void OnTabClicked(long tab)
|
||||||
@@ -37,7 +50,8 @@ public partial class CodeEditorPanel : MarginContainer
|
|||||||
|
|
||||||
public async Task SetSharpIdeFile(SharpIdeFile file)
|
public async Task SetSharpIdeFile(SharpIdeFile file)
|
||||||
{
|
{
|
||||||
var existingTab = _tabContainer.GetChildren().OfType<SharpIdeCodeEdit>().FirstOrDefault(t => t.SharpIdeFile == file);
|
await Task.CompletedTask.ConfigureAwait(ConfigureAwaitOptions.ForceYielding);
|
||||||
|
var existingTab = await this.InvokeAsync(() => _tabContainer.GetChildren().OfType<SharpIdeCodeEdit>().FirstOrDefault(t => t.SharpIdeFile == file));
|
||||||
if (existingTab is not null)
|
if (existingTab is not null)
|
||||||
{
|
{
|
||||||
var existingTabIndex = existingTab.GetIndex();
|
var existingTabIndex = existingTab.GetIndex();
|
||||||
@@ -58,4 +72,42 @@ public partial class CodeEditorPanel : MarginContainer
|
|||||||
});
|
});
|
||||||
await newTab.SetSharpIdeFile(file);
|
await newTab.SetSharpIdeFile(file);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task OnDebuggerExecutionStopped(ExecutionStopInfo executionStopInfo)
|
||||||
|
{
|
||||||
|
Guard.Against.Null(Solution, nameof(Solution));
|
||||||
|
|
||||||
|
var currentSharpIdeFile = await this.InvokeAsync<SharpIdeFile>(() => _tabContainer.GetChild<SharpIdeCodeEdit>(_tabContainer.CurrentTab).SharpIdeFile);
|
||||||
|
|
||||||
|
if (executionStopInfo.FilePath != currentSharpIdeFile?.Path)
|
||||||
|
{
|
||||||
|
var file = Solution.AllFiles.Single(s => s.Path == executionStopInfo.FilePath);
|
||||||
|
await GodotGlobalEvents.InvokeFileExternallySelectedAndWait(file).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
var lineInt = executionStopInfo.Line - 1; // Debugging is 1-indexed, Godot is 0-indexed
|
||||||
|
Guard.Against.Negative(lineInt, nameof(lineInt));
|
||||||
|
_debuggerExecutionStopInfo = executionStopInfo;
|
||||||
|
|
||||||
|
await this.InvokeAsync(() =>
|
||||||
|
{
|
||||||
|
var focusedTab = _tabContainer.GetChild<SharpIdeCodeEdit>(_tabContainer.CurrentTab);
|
||||||
|
focusedTab.SetLineBackgroundColor(lineInt, new Color("665001"));
|
||||||
|
focusedTab.SetLineAsExecuting(lineInt, true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SendDebuggerStepOver()
|
||||||
|
{
|
||||||
|
if (_debuggerExecutionStopInfo is null) return; // ie not currently stopped
|
||||||
|
var godotLine = _debuggerExecutionStopInfo.Line - 1;
|
||||||
|
var focusedTab = _tabContainer.GetChild<SharpIdeCodeEdit>(_tabContainer.CurrentTab);
|
||||||
|
focusedTab.SetLineAsExecuting(godotLine, false);
|
||||||
|
focusedTab.SetLineColour(godotLine);
|
||||||
|
var threadId = _debuggerExecutionStopInfo.ThreadId;
|
||||||
|
_debuggerExecutionStopInfo = null;
|
||||||
|
_ = Task.GodotRun(async () =>
|
||||||
|
{
|
||||||
|
await Singletons.RunService.SendDebuggerStepOver(threadId);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -7,13 +7,12 @@ using Microsoft.CodeAnalysis.CodeActions;
|
|||||||
using Microsoft.CodeAnalysis.Text;
|
using Microsoft.CodeAnalysis.Text;
|
||||||
using SharpIDE.Application.Features.Analysis;
|
using SharpIDE.Application.Features.Analysis;
|
||||||
using SharpIDE.Application.Features.Debugging;
|
using SharpIDE.Application.Features.Debugging;
|
||||||
using SharpIDE.Application.Features.Events;
|
|
||||||
using SharpIDE.Application.Features.SolutionDiscovery;
|
using SharpIDE.Application.Features.SolutionDiscovery;
|
||||||
using SharpIDE.Application.Features.SolutionDiscovery.VsPersistence;
|
using SharpIDE.Application.Features.SolutionDiscovery.VsPersistence;
|
||||||
using SharpIDE.RazorAccess;
|
using SharpIDE.RazorAccess;
|
||||||
using Task = System.Threading.Tasks.Task;
|
using Task = System.Threading.Tasks.Task;
|
||||||
|
|
||||||
namespace SharpIDE.Godot;
|
namespace SharpIDE.Godot.Features.CodeEditor;
|
||||||
|
|
||||||
public partial class SharpIdeCodeEdit : CodeEdit
|
public partial class SharpIdeCodeEdit : CodeEdit
|
||||||
{
|
{
|
||||||
@@ -33,7 +32,6 @@ public partial class SharpIdeCodeEdit : CodeEdit
|
|||||||
|
|
||||||
private ImmutableArray<(FileLinePositionSpan fileSpan, Diagnostic diagnostic)> _diagnostics = [];
|
private ImmutableArray<(FileLinePositionSpan fileSpan, Diagnostic diagnostic)> _diagnostics = [];
|
||||||
private ImmutableArray<CodeAction> _currentCodeActionsInPopup = [];
|
private ImmutableArray<CodeAction> _currentCodeActionsInPopup = [];
|
||||||
private ExecutionStopInfo? _executionStopInfo;
|
|
||||||
private bool _fileChangingSuppressBreakpointToggleEvent;
|
private bool _fileChangingSuppressBreakpointToggleEvent;
|
||||||
|
|
||||||
public override void _Ready()
|
public override void _Ready()
|
||||||
@@ -49,26 +47,6 @@ public partial class SharpIdeCodeEdit : CodeEdit
|
|||||||
SymbolHovered += OnSymbolHovered;
|
SymbolHovered += OnSymbolHovered;
|
||||||
SymbolValidate += OnSymbolValidate;
|
SymbolValidate += OnSymbolValidate;
|
||||||
SymbolLookup += OnSymbolLookup;
|
SymbolLookup += OnSymbolLookup;
|
||||||
GlobalEvents.DebuggerExecutionStopped += OnDebuggerExecutionStopped;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task OnDebuggerExecutionStopped(ExecutionStopInfo executionStopInfo)
|
|
||||||
{
|
|
||||||
Guard.Against.Null(Solution, nameof(Solution));
|
|
||||||
if (executionStopInfo.FilePath != _currentFile.Path)
|
|
||||||
{
|
|
||||||
var file = Solution.AllFiles.Single(s => s.Path == executionStopInfo.FilePath);
|
|
||||||
await GodotGlobalEvents.InvokeFileExternallySelectedAndWait(file);
|
|
||||||
}
|
|
||||||
var lineInt = executionStopInfo.Line - 1; // Debugging is 1-indexed, Godot is 0-indexed
|
|
||||||
Guard.Against.Negative(lineInt, nameof(lineInt));
|
|
||||||
_executionStopInfo = executionStopInfo;
|
|
||||||
|
|
||||||
await this.InvokeAsync(() =>
|
|
||||||
{
|
|
||||||
SetLineBackgroundColor(lineInt, new Color("665001"));
|
|
||||||
SetLineAsExecuting(lineInt, true);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnBreakpointToggled(long line)
|
private void OnBreakpointToggled(long line)
|
||||||
@@ -241,33 +219,18 @@ public partial class SharpIdeCodeEdit : CodeEdit
|
|||||||
|
|
||||||
public override void _UnhandledKeyInput(InputEvent @event)
|
public override void _UnhandledKeyInput(InputEvent @event)
|
||||||
{
|
{
|
||||||
|
if (HasFocus() is false) return; // every tab is currently listening for this input. Only respond if we have focus. Consider refactoring this _UnhandledKeyInput to CodeEditorPanel
|
||||||
if (@event.IsActionPressed(InputStringNames.CodeFixes))
|
if (@event.IsActionPressed(InputStringNames.CodeFixes))
|
||||||
{
|
{
|
||||||
EmitSignalCodeFixesRequested();
|
EmitSignalCodeFixesRequested();
|
||||||
}
|
}
|
||||||
else if (@event.IsActionPressed(InputStringNames.StepOver))
|
|
||||||
{
|
|
||||||
SendDebuggerStepOver();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SendDebuggerStepOver()
|
|
||||||
{
|
|
||||||
if (_executionStopInfo is null) return;
|
|
||||||
var godotLine = _executionStopInfo.Line - 1;
|
|
||||||
SetLineAsExecuting(godotLine, false);
|
|
||||||
SetLineColour(godotLine);
|
|
||||||
var threadId = _executionStopInfo.ThreadId;
|
|
||||||
_executionStopInfo = null;
|
|
||||||
_ = Task.GodotRun(async () =>
|
|
||||||
{
|
|
||||||
await Singletons.RunService.SendDebuggerStepOver(threadId);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private readonly Color _breakpointLineColor = new Color("3a2323");
|
private readonly Color _breakpointLineColor = new Color("3a2323");
|
||||||
private readonly Color _executingLineColor = new Color("665001");
|
private readonly Color _executingLineColor = new Color("665001");
|
||||||
private void SetLineColour(int line)
|
public void SetLineColour(int line)
|
||||||
{
|
{
|
||||||
var breakpointed = IsLineBreakpointed(line);
|
var breakpointed = IsLineBreakpointed(line);
|
||||||
var executing = IsLineExecuting(line);
|
var executing = IsLineExecuting(line);
|
||||||
|
|||||||
@@ -40,7 +40,8 @@ public partial class SolutionExplorerPanel : MarginContainer
|
|||||||
|
|
||||||
private async Task OnFileExternallySelected(SharpIdeFile file)
|
private async Task OnFileExternallySelected(SharpIdeFile file)
|
||||||
{
|
{
|
||||||
GodotGlobalEvents.InvokeFileSelected(file);
|
await Task.CompletedTask.ConfigureAwait(ConfigureAwaitOptions.ForceYielding);
|
||||||
|
var task = GodotGlobalEvents.InvokeFileSelectedAndWait(file);
|
||||||
var item = FindItemRecursive(_tree.GetRoot(), file);
|
var item = FindItemRecursive(_tree.GetRoot(), file);
|
||||||
if (item is not null)
|
if (item is not null)
|
||||||
{
|
{
|
||||||
@@ -52,6 +53,7 @@ public partial class SolutionExplorerPanel : MarginContainer
|
|||||||
_tree.QueueRedraw();
|
_tree.QueueRedraw();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
await task.ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static TreeItem? FindItemRecursive(TreeItem item, SharpIdeFile file)
|
private static TreeItem? FindItemRecursive(TreeItem item, SharpIdeFile file)
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ public static class GodotGlobalEvents
|
|||||||
|
|
||||||
public static event Func<SharpIdeFile, Task> FileSelected = _ => Task.CompletedTask;
|
public static event Func<SharpIdeFile, Task> FileSelected = _ => Task.CompletedTask;
|
||||||
public static void InvokeFileSelected(SharpIdeFile file) => FileSelected.InvokeParallelFireAndForget(file);
|
public static void InvokeFileSelected(SharpIdeFile file) => FileSelected.InvokeParallelFireAndForget(file);
|
||||||
|
public static async Task InvokeFileSelectedAndWait(SharpIdeFile file) => await FileSelected.InvokeParallelAsync(file);
|
||||||
public static event Func<SharpIdeFile, Task> FileExternallySelected = _ => Task.CompletedTask;
|
public static event Func<SharpIdeFile, Task> FileExternallySelected = _ => Task.CompletedTask;
|
||||||
public static void InvokeFileExternallySelected(SharpIdeFile file) => FileExternallySelected.InvokeParallelFireAndForget(file);
|
public static void InvokeFileExternallySelected(SharpIdeFile file) => FileExternallySelected.InvokeParallelFireAndForget(file);
|
||||||
public static async Task InvokeFileExternallySelectedAndWait(SharpIdeFile file) => await FileExternallySelected.InvokeParallelAsync(file);
|
public static async Task InvokeFileExternallySelectedAndWait(SharpIdeFile file) => await FileExternallySelected.InvokeParallelAsync(file);
|
||||||
|
|||||||
@@ -36,9 +36,26 @@ public static class NodeExtensions
|
|||||||
{
|
{
|
||||||
extension(Node node)
|
extension(Node node)
|
||||||
{
|
{
|
||||||
|
public Task<T> InvokeAsync<T>(Func<T> workItem)
|
||||||
|
{
|
||||||
|
var taskCompletionSource = new TaskCompletionSource<T>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||||
|
Dispatcher.SynchronizationContext.Post(_ =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var result = workItem();
|
||||||
|
taskCompletionSource.SetResult(result);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
taskCompletionSource.SetException(ex);
|
||||||
|
}
|
||||||
|
}, null);
|
||||||
|
return taskCompletionSource.Task;
|
||||||
|
}
|
||||||
public Task InvokeAsync(Action workItem)
|
public Task InvokeAsync(Action workItem)
|
||||||
{
|
{
|
||||||
var taskCompletionSource = new TaskCompletionSource();
|
var taskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||||
//WorkerThreadPool.AddTask();
|
//WorkerThreadPool.AddTask();
|
||||||
Dispatcher.SynchronizationContext.Post(_ =>
|
Dispatcher.SynchronizationContext.Post(_ =>
|
||||||
{
|
{
|
||||||
@@ -57,7 +74,7 @@ public static class NodeExtensions
|
|||||||
|
|
||||||
public Task InvokeAsync(Func<Task> workItem)
|
public Task InvokeAsync(Func<Task> workItem)
|
||||||
{
|
{
|
||||||
var taskCompletionSource = new TaskCompletionSource();
|
var taskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||||
Dispatcher.SynchronizationContext.Post(async void (_) =>
|
Dispatcher.SynchronizationContext.Post(async void (_) =>
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -75,7 +92,7 @@ public static class NodeExtensions
|
|||||||
|
|
||||||
public Task InvokeDeferredAsync(Action workItem)
|
public Task InvokeDeferredAsync(Action workItem)
|
||||||
{
|
{
|
||||||
var taskCompletionSource = new TaskCompletionSource();
|
var taskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||||
//WorkerThreadPool.AddTask();
|
//WorkerThreadPool.AddTask();
|
||||||
Callable.From(() =>
|
Callable.From(() =>
|
||||||
{
|
{
|
||||||
@@ -94,7 +111,7 @@ public static class NodeExtensions
|
|||||||
|
|
||||||
public Task InvokeDeferredAsync(Func<Task> workItem)
|
public Task InvokeDeferredAsync(Func<Task> workItem)
|
||||||
{
|
{
|
||||||
var taskCompletionSource = new TaskCompletionSource();
|
var taskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||||
//WorkerThreadPool.AddTask();
|
//WorkerThreadPool.AddTask();
|
||||||
Callable.From(async void () =>
|
Callable.From(async void () =>
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user