Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
dgkf opened this issue Mar 19, 2024 · 1 comment

Comments

@dgkf
Copy link

dgkf commented Mar 19, 2024

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:

  • listen for various breakpoint updates and trace the appropriate expressions upon receiving a breakpoint message from the server
  • provide debugger state information to the IDE when entering a browser() session
  • relay state from the browser() session to the IDE to update the graphical representation of debugger state (highlighted expression, breakpoint state, scoped environment variables)
  • ideally perform all of these actions in the background so that the familiar R debugging experience is uninterrupted.

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
Copy link

sebffischer commented Jun 28, 2024

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants