HenrikBengtsson / Wishlist-for-R

Features and tweaks to R that I and others would love to see - feel free to add yours!
https://github.com/HenrikBengtsson/Wishlist-for-R/issues
GNU Lesser General Public License v3.0
133 stars 4 forks source link

Task & browser hooks to support implementation of a debug adapter protocol client #159

Open dgkf opened 6 months ago

dgkf commented 6 months ago

Background

The debug adapter protocol (DAP) is becoming standard for IDEs to implement graphical debugging interfaces that communicate with language-specific debuggers. The IDE acts as a debug server, and a language-specific tool acts as a client, receiving and responding to signals to synchronize a debugger state between an R session and the IDE.

Implementing a debug adapter client in R allows for an IDE to provide a graphical interface to the evaluation environment and the various debug actions.

A familiar example of a preferred workflow is RStudio's debug interface, allowing for stepping through code using either the graphical interface or using the R Console. A similar experience could be provided for any IDE that provides a debug server (VSCode, vim, among many others) if a running R session (similar to the RStudio R Console), could be attached as a debugging client and handle server requests at opportune times while providing an R REPL.

R Behavior

Assuming a running R session was listening for a debug server, the R session would:

Challenges

Listening for and inserting breakpoints

Assuming the R session is actively listening (via tcp or stdio) for breakpoint updates, there is a challenge of inserting those breakpoints before the next top-level expression is executed.

browser() commands

After entering the debugger, the other challenge is to synchronize state within the debugging session. This could be handled by adding hooks into the browser() interface to send debug state updates back to the IDE. For example, this may include sending information about the file and line number currently being evaluated in the debugger.

Existing Solutions & Their Limitations

Implementations often shim the R REPL, making the R REPL appear like a user session, but opaquely evaluate additional code to manage debug state in the background.

RStudio

From what I can tell, RStudio runs an R session in the background that both the console and debug manager communicate with, which allows additional R code to be executed to manage debug state that is hidden from the R Console.

VSCode "R Debugger" extension

Similarly, this extension intercepts and hides additional R code from being displayed in the REPL pane of a VSCode session ManuelHentschel/VSCode-R-Debugger. This is a viable solution for VSCode where you have tight coupling of the extension and display within the IDE, but means that any debugger solution is similarly tightly coupled to the IDE, which foregoes a lot of the value of implementing a generic protocol.

The developer of this extension shares similar difficulties preventing a more agnostic solution.

dgkf/debugadapter

This is my own attempt with the goal of avoiding a REPL shim. It is built on layer upon layer of horrible hack. Breakpoints always lag behind the IDE by one top-level expression evaluation and stdin/stdout are rerouted between forked processes to opaquely intercept the browser() REPL to synchronize debugger state. This relies on a fork of processx that allows rerouting stdin and use of forked processes that are unsupported on Windows.

Proposal

Add new hook events that allow injecting code before a top-level expression is evaluated, and at the start and end of each browser() step.

What does ideal look like?

If a user wants their running R session to be able to be attached as a debugger, they would just need to start listening for server connections in that REPL (or always listen for connections by default by adding it as part of a .Rprofile)

# .Rprofile
dap::listen()

After this, entering debug mode (in "attach" mode) in their IDE would connect to a background R process, which would flush any debug state updates to the parent R session before the next-evaluated user expression.

Should the user evaluate code that would hit the breakpoint, the user would be able to step through the code and observe their IDE's debugger reflecting their debug state, often by highlighting the active expression and updating the scoped environment variables.

sebffischer commented 3 months ago

Would this help? https://bugs.r-project.org/show_bug.cgi?id=18590