rstudio/rstudio — pull request #88 by jmcphers (2014-01-27 20:48:43+00:00)
Title: Improve top-level debugging experience

This change alters the way top-level debugging works. The change has the following user-facing benefits:

- Stepping at the top level now works exactly like stepping through a function--loop bodies are stepped through one iteration at a time, etc. This was a common request on the support forum and the motivation for the work.

- The interface for top-level debugging now matches exactly with the interface for function debugging; the console experience is identical (it's a standard R environment browser at which one can use 'n', 'c', etc.)

- The file being sourced appears on the callstack, so it is possible to see e.g. the line from which an error arises.

- Breakpoints can now be set *within* top-level sub-expressions, such as loops or anonymous functions. It was previously only possible to set breakpoints on the outermost expressions in the file.

- Future improvements to the function debugging experience (added by R core or ourselves) will carry over to top-level debugging without additional engineering work.

Previously, top-level debugging worked by having the server and the client step through each of the top-level expressions in the file together (breaking up the `parse` and `eval` steps). After each expression was evaluated, the client and server stopped to see whether a breakpoint had been hit or breakpoints needed to be injected.

This approach works differently. The core of the work is in `makeSourceEquivalentFunction`, and works roughly as follows:

1. The file is parsed into an expression tree.
2. The expressions are wrapped into a function that evaluates them in the global environment.
3. Source references are created and copied into the function so that the parsed expressions map seamlessly back into the file being sourced.
3. Breakpoints (top-level and otherwise) are injected into the function. (This uses the server knowledge of breakpoints originally introduced to implement Shiny breakpoints.)
4. The function is executed.

This is not a simple operation, but once performed, it significantly reduces the amount of bookkeeping and state that needs to be maintained during top-level debugging; there is a little special treatment of the source-equivalent function, but the debugger is no longer required to have a special "top-level debugging" state.

Finally, this is all done synchronously on the server without client interaction; previously, `debugSource` needed to run at the console and be seen by the IDE to work effectively. This puts into place the architecture we will need to make `debugSource` a drop-in replacement for `source` if we decide to do that work.