diff --git a/src/SharpIDE.Application/Features/Debugging/Debugger.cs b/src/SharpIDE.Application/Features/Debugging/Debugger.cs new file mode 100644 index 0000000..f2abdd7 --- /dev/null +++ b/src/SharpIDE.Application/Features/Debugging/Debugger.cs @@ -0,0 +1,15 @@ +using SharpIDE.Application.Features.Debugging.Experimental; +using SharpIDE.Application.Features.SolutionDiscovery.VsPersistence; + +namespace SharpIDE.Application.Features.Debugging; + +public class Debugger +{ + public required SharpIdeProjectModel Project { get; init; } + public required int ProcessId { get; init; } + public async Task Attach(CancellationToken cancellationToken) + { + var debuggingService = new DebuggingService(); + await debuggingService.Attach(ProcessId, cancellationToken); + } +} diff --git a/src/SharpIDE.Application/Features/Debugging/DebuggingService.cs b/src/SharpIDE.Application/Features/Debugging/DebuggingService.cs index 4e50cc7..9d5acaf 100644 --- a/src/SharpIDE.Application/Features/Debugging/DebuggingService.cs +++ b/src/SharpIDE.Application/Features/Debugging/DebuggingService.cs @@ -1,119 +1,106 @@ using System.Diagnostics; -using System.IO.Pipelines; -using OmniSharp.Extensions.DebugAdapter.Client; -using OmniSharp.Extensions.DebugAdapter.Protocol.Events; -using OmniSharp.Extensions.DebugAdapter.Protocol.Models; -using OmniSharp.Extensions.DebugAdapter.Protocol.Requests; +using Ardalis.GuardClauses; +using Microsoft.Diagnostics.NETCore.Client; +using Microsoft.VisualStudio.Shared.VSCodeDebugProtocol; +using Microsoft.VisualStudio.Shared.VSCodeDebugProtocol.Messages; +using Newtonsoft.Json.Linq; +using SharpIDE.Application.Features.Debugging.Experimental; namespace SharpIDE.Application.Features.Debugging; public class DebuggingService { - - public async Task Test(CancellationToken cancellationToken = default) + public async Task Attach(int debuggeeProcessId, CancellationToken cancellationToken = default) { + Guard.Against.NegativeOrZero(debuggeeProcessId, nameof(debuggeeProcessId), "Process ID must be a positive integer."); await Task.CompletedTask.ConfigureAwait(ConfigureAwaitOptions.ForceYielding); + var process = new Process { StartInfo = new ProcessStartInfo { FileName = @"C:\Users\Matthew\Downloads\netcoredbg-win64\netcoredbg\netcoredbg.exe", - Arguments = """--interpreter=vscode""", + //FileName = @"C:\Users\Matthew\.vscode-insiders\extensions\ms-dotnettools.csharp-2.83.5-win32-x64\.debugger\x86_64\vsdbg.exe", + Arguments = "--interpreter=vscode", RedirectStandardInput = true, RedirectStandardOutput = true, - UseShellExecute = false + UseShellExecute = false, + CreateNoWindow = true } }; process.Start(); - var stoppedTcs = new TaskCompletionSource(); - - var client = DebugAdapterClient.Create(options => + var debugProtocolHost = new DebugProtocolHost(process.StandardInput.BaseStream, process.StandardOutput.BaseStream, false); + debugProtocolHost.LogMessage += (sender, args) => { - options.AdapterId = "coreclr"; - options.ClientId = "vscode"; - options.ClientName = "Visual Studio Code"; - options.LinesStartAt1 = true; - options.ColumnsStartAt1 = true; - options.SupportsVariableType = true; - options.SupportsVariablePaging = true; - options.SupportsRunInTerminalRequest = true; - options.Locale = "en-us"; - options.PathFormat = PathFormat.Path; - options - .WithInput(process.StandardOutput.BaseStream) - .WithOutput(process.StandardInput.BaseStream) - .OnStarted(async (adapterClient, token) => - { - Console.WriteLine("Started"); - }) - .OnCapabilities(async (capabilitiesEvent, token) => - { - Console.WriteLine("Capabilities"); - }) - .OnBreakpoint(async (breakpointEvent, token) => - { - Console.WriteLine($"Breakpoint hit: {breakpointEvent}"); - }) - .OnRunInTerminal(async (arguments, token) => - { - Console.WriteLine("Run In Terminal"); - return new RunInTerminalResponse(); - }) - .OnStopped(async (stoppedEvent, token) => - { - Console.WriteLine($"Notification received: {stoppedEvent}"); - stoppedTcs.SetResult(stoppedEvent); - }) - .OnTerminated(async (terminatedEvent, token) => - { - Console.WriteLine($"Terminated: {terminatedEvent}"); - }); - //.OnNotification(EventNames., ); + Console.WriteLine($"Log message: {args.Message}"); + }; + debugProtocolHost.EventReceived += (sender, args) => + { + Console.WriteLine($"Event received: {args.EventType}"); + }; + debugProtocolHost.DispatcherError += (sender, args) => + { + Console.WriteLine($"Dispatcher error: {args.Exception}"); + }; + debugProtocolHost.RequestReceived += (sender, args) => + { + Console.WriteLine($"Request received: {args.Command}"); + }; + debugProtocolHost.RegisterEventType(@event => + { + ; }); + debugProtocolHost.RegisterClientRequestType(responder => + { + var args = responder.Arguments; + var signatureResponse = VsSigner.Sign(responder.Arguments.Value); + responder.SetResponse(new HandshakeResponse(signatureResponse)); + }); + debugProtocolHost.RegisterEventType(@event => + { + ; + var threadId = @event.ThreadId; + }); + debugProtocolHost.VerifySynchronousOperationAllowed(); + var initializeRequest = new InitializeRequest + { + ClientID = "vscode", + ClientName = "Visual Studio Code", + AdapterID = "coreclr", + Locale = "en-us", + LinesStartAt1 = true, + ColumnsStartAt1 = true, + PathFormat = InitializeArguments.PathFormatValue.Path, + SupportsVariableType = true, + SupportsVariablePaging = true, + SupportsRunInTerminalRequest = true, + SupportsHandshakeRequest = true + }; + debugProtocolHost.Run(); + var response = debugProtocolHost.SendRequestSync(initializeRequest); - await client.Initialize(cancellationToken); - var breakpointsResponse = await client.SetBreakpoints(new SetBreakpointsArguments + var attachRequest = new AttachRequest { - Source = new Source { Path = @"C:\Users\Matthew\Documents\Git\BlazorCodeBreaker\src\WebApi\Program.cs" }, - Breakpoints = new Container(new SourceBreakpoint { Line = 7 }) - }, cancellationToken); - var launchResponse = await client.Launch(new LaunchRequestArguments() - { - NoDebug = false, - ExtensionData = new Dictionary + ConfigurationProperties = new Dictionary { - ["name"] = "LaunchRequestName", + ["name"] = "AttachRequestName", ["type"] = "coreclr", - ["program"] = @"C:\Users\Matthew\Documents\Git\BlazorCodeBreaker\artifacts\bin\WebApi\debug\WebApi.dll", - ["cwd"] = "" - //["cwd"] = @"C:\Users\Matthew\Documents\Git\BlazorCodeBreaker\artifacts\bin\WebApi\debug", // working directory - //["stopAtEntry"] = true, - //["env"] = new Dictionary { { "ASPNETCORE_ENVIRONMENT", "Development" } } + ["processId"] = debuggeeProcessId, + ["console"] = "internalConsole", // integratedTerminal, externalTerminal, internalConsole } - }, cancellationToken); + }; + debugProtocolHost.SendRequestSync(attachRequest); - var configurationDoneResponse = await client.RequestConfigurationDone(new ConfigurationDoneArguments(), cancellationToken); - - var stoppedEvent = await stoppedTcs.Task; - var threads = await client.RequestThreads(new ThreadsArguments(), cancellationToken); - - var currentThread = threads.Threads!.Single(s => s.Id == stoppedEvent.ThreadId); - var stackTrace = await client.RequestStackTrace(new StackTraceArguments { ThreadId = currentThread.Id }, cancellationToken); - var frame = stackTrace.StackFrames!.First(); - var scopes = await client.RequestScopes(new ScopesArguments { FrameId = frame.Id }, cancellationToken); - var scope = scopes.Scopes.First(); - var variablesResponse = await client.RequestVariables(new VariablesArguments() {VariablesReference = scope.VariablesReference}, cancellationToken); - var variable = variablesResponse.Variables!.Skip(1).First(); - var variables2Response = await client.RequestVariables(new VariablesArguments() {VariablesReference = variable.VariablesReference}, cancellationToken); - var variable2 = variables2Response.Variables!.First(); - var variables3Response = await client.RequestVariables(new VariablesArguments() {VariablesReference = variable2.VariablesReference}, cancellationToken); - //var continueResponse = await client.RequestContinue(new ContinueArguments(){ThreadId = 1}, cancellationToken); - //await Task.Delay(1000); - //var test = await client.RequestNext(new NextArguments(), cancellationToken: cancellationToken); - - //var result = await client.RequestStepIn(new StepInArguments(), cancellationToken: cancellationToken); - - await process.WaitForExitAsync(); + // var breakpointRequest = new SetBreakpointsRequest + // { + // Source = new Source { Path = @"C:\Users\Matthew\Documents\Git\BlazorCodeBreaker\src\WebApi\Program.cs" }, + // Breakpoints = [new SourceBreakpoint { Line = 7 }] + // }; + // var breakpointsResponse = debugProtocolHost.SendRequestSync(breakpointRequest); + new DiagnosticsClient(debuggeeProcessId).ResumeRuntime(); + var configurationDoneRequest = new ConfigurationDoneRequest(); + 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. } diff --git a/src/SharpIDE.Application/Features/Run/RunService.cs b/src/SharpIDE.Application/Features/Run/RunService.cs index f77d942..b69e8f4 100644 --- a/src/SharpIDE.Application/Features/Run/RunService.cs +++ b/src/SharpIDE.Application/Features/Run/RunService.cs @@ -2,6 +2,7 @@ using System.Threading.Channels; using Ardalis.GuardClauses; using AsyncReadProcess; +using SharpIDE.Application.Features.Debugging; using SharpIDE.Application.Features.Evaluation; using SharpIDE.Application.Features.Events; using SharpIDE.Application.Features.SolutionDiscovery.VsPersistence; @@ -13,6 +14,7 @@ public class RunService private readonly ConcurrentDictionary _projectLocks = []; public async Task RunProject(SharpIdeProjectModel project) { + var isDebug = true; Guard.Against.Null(project, nameof(project)); Guard.Against.NullOrWhiteSpace(project.FilePath, nameof(project.FilePath), "Project file path cannot be null or empty."); await Task.CompletedTask.ConfigureAwait(ConfigureAwaitOptions.ForceYielding); @@ -28,6 +30,7 @@ public class RunService var launchProfile = launchProfiles.FirstOrDefault(); try { + // TODO: handle running Blazor projects var processStartInfo = new ProcessStartInfo2 { FileName = "dotnet", @@ -46,6 +49,10 @@ public class RunService } if (launchProfile.ApplicationUrl != null) processStartInfo.EnvironmentVariables["ASPNETCORE_URLS"] = launchProfile.ApplicationUrl; } + if (isDebug) + { + processStartInfo.EnvironmentVariables["DOTNET_DefaultDiagnosticPortSuspend"] = "1"; + } var process = new Process2 { @@ -72,6 +79,13 @@ public class RunService logsDrained.TrySetResult(); }); + 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).ConfigureAwait(false); + } + project.Running = true; project.OpenInRunPanel = true; GlobalEvents.InvokeProjectsRunningChanged(); diff --git a/src/SharpIDE.Application/SharpIDE.Application.csproj b/src/SharpIDE.Application/SharpIDE.Application.csproj index 39baa2d..c5b83ad 100644 --- a/src/SharpIDE.Application/SharpIDE.Application.csproj +++ b/src/SharpIDE.Application/SharpIDE.Application.csproj @@ -13,6 +13,8 @@ .\Microsoft.CodeAnalysis.Workspaces.MSBuild.dll true + +