Full Code of timholy/Rebugger.jl for AI

master 3312b63210e8 cached
38 files
145.7 KB
40.6k tokens
1 requests
Download .txt
Repository: timholy/Rebugger.jl
Branch: master
Commit: 3312b63210e8
Files: 38
Total size: 145.7 KB

Directory structure:
gitextract_4j1i8l0j/

├── .github/
│   └── workflows/
│       ├── Documenter.yml
│       ├── TagBot.yml
│       └── ci.yml
├── .gitignore
├── LICENSE.md
├── Project.toml
├── README.md
├── docs/
│   ├── Project.toml
│   ├── make.jl
│   └── src/
│       ├── config.md
│       ├── index.md
│       ├── internals.md
│       ├── limitations.md
│       ├── reference.md
│       └── usage.md
├── src/
│   ├── Rebugger.jl
│   ├── debug.jl
│   ├── deepcopy.jl
│   ├── precompile.jl
│   ├── printing.jl
│   └── ui.jl
└── test/
    ├── edit.jl
    ├── interpret.jl
    ├── interpret_script.jl
    ├── interpret_ui.jl
    ├── my_gcd.jl
    ├── runtests.jl
    ├── testmodule.jl
    └── ui/
        ├── v1.0/
        │   ├── gcd.multiout
        │   └── gcdsc.multiout
        ├── v1.1/
        │   ├── gcd.multiout
        │   └── gcdsc.multiout
        ├── v1.2/
        │   ├── gcd.multiout
        │   └── gcdsc.multiout
        ├── v1.3/
        │   ├── gcd.multiout
        │   └── gcdsc.multiout
        └── v1.5/
            ├── gcd.multiout
            └── gcdsc.multiout

================================================
FILE CONTENTS
================================================

================================================
FILE: .github/workflows/Documenter.yml
================================================
name: Documenter
on:
  push:
    branches: [master]
    tags: [v*]
  pull_request:

jobs:
  Documenter:
    name: Documentation
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - uses: julia-actions/julia-buildpkg@latest
      - uses: julia-actions/julia-docdeploy@latest
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          DOCUMENTER_KEY: ${{ secrets.DOCUMENTER_KEY }}


================================================
FILE: .github/workflows/TagBot.yml
================================================
name: TagBot
on:
  issue_comment:
    types:
      - created
  workflow_dispatch:
jobs:
  TagBot:
    if: github.event_name == 'workflow_dispatch' || github.actor == 'JuliaTagBot'
    runs-on: ubuntu-latest
    steps:
      - uses: JuliaRegistries/TagBot@v1
        with:
          token: ${{ secrets.GITHUB_TOKEN }}
          ssh: ${{ secrets.DOCUMENTER_KEY }}


================================================
FILE: .github/workflows/ci.yml
================================================
name: CI
on:
  pull_request:
    branches:
      - master
  push:
    branches:
      - master
    tags: '*'
jobs:
  test:
    name: Julia ${{ matrix.version }} - ${{ matrix.os }} - ${{ matrix.arch }} - ${{ github.event_name }}
    runs-on: ${{ matrix.os }}
    strategy:
      fail-fast: false
      matrix:
        version:
          - '1.0'
          - '1'
          - 'nightly'
        os:
          - ubuntu-latest
        arch:
          - x64
    steps:
      - uses: actions/checkout@v2
      - uses: julia-actions/setup-julia@v1
        with:
          version: ${{ matrix.version }}
          arch: ${{ matrix.arch }}
      - uses: actions/cache@v1
        env:
          cache-name: cache-artifacts
        with:
          path: ~/.julia/artifacts
          key: ${{ runner.os }}-test-${{ env.cache-name }}-${{ hashFiles('**/Project.toml') }}
          restore-keys: |
            ${{ runner.os }}-test-${{ env.cache-name }}-
            ${{ runner.os }}-test-
            ${{ runner.os }}-
      - uses: julia-actions/julia-buildpkg@v1
      - uses: julia-actions/julia-runtest@v1
      - uses: julia-actions/julia-processcoverage@v1
      - uses: codecov/codecov-action@v1
        with:
          file: lcov.info


================================================
FILE: .gitignore
================================================
docs/build/
docs/site/
test/expected.out
test/failed.out


================================================
FILE: LICENSE.md
================================================
The Rebugger.jl package is licensed under the MIT "Expat" License:

> Copyright (c) 2018: Tim Holy.
>
> Permission is hereby granted, free of charge, to any person obtaining a copy
> of this software and associated documentation files (the "Software"), to deal
> in the Software without restriction, including without limitation the rights
> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
> copies of the Software, and to permit persons to whom the Software is
> furnished to do so, subject to the following conditions:
>
> The above copyright notice and this permission notice shall be included in all
> copies or substantial portions of the Software.
>
> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
> SOFTWARE.
>


================================================
FILE: Project.toml
================================================
name = "Rebugger"
uuid = "ee283ea6-eecd-56e3-beb3-83eb4d3c31e9"
version = "0.3.3"

[deps]
CodeTracking = "da1fd8a2-8d9e-5ec2-8556-3022fb5608a2"
HeaderREPLs = "54d51984-71c9-52bd-8df9-6718e63e4153"
InteractiveUtils = "b77e0a4c-d291-57a0-90e8-8db25a27a240"
JuliaInterpreter = "aa1ae85d-cabe-5617-a682-6adf51b2e16a"
REPL = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb"
Revise = "295af30f-e4ad-537b-8983-00126c2a3abe"
UUIDs = "cf7118a7-6976-5b1a-9a39-7adc72f591a4"

[compat]
CodeTracking = "0.5"
HeaderREPLs = "0.3"
JuliaInterpreter = "0.7"
Revise = "2.1.10"
julia = "1"

[extras]
Colors = "5ae59095-9a9b-59fe-a467-6f913c188581"
Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f"
TerminalRegressionTests = "98bfdc55-cc95-5876-a49a-74609291cbe0"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

[targets]
test = ["Test", "Colors", "Pkg", "TerminalRegressionTests"]


================================================
FILE: README.md
================================================
# Rebugger

[![Build Status](https://travis-ci.org/timholy/Rebugger.jl.svg?branch=master)](https://travis-ci.org/timholy/Rebugger.jl)
[![Build status](https://ci.appveyor.com/api/projects/status/e9t1wlyy995whchc?svg=true)](https://ci.appveyor.com/project/timholy/Rebugger-jl/branch/master)
[![codecov.io](http://codecov.io/github/timholy/Rebugger.jl/coverage.svg?branch=master)](http://codecov.io/github/timholy/Rebugger.jl?branch=master)
[![PkgEval][pkgeval-img]][pkgeval-url]


Rebugger is an expression-level debugger for Julia.
It has no ability to interact with or manipulate call stacks (see [Debugger](https://github.com/JuliaDebug/Debugger.jl) or the debugger built into vscode),
but it can trace execution via the manipulation of Julia expressions.

The name "Rebugger" has 3 meanings:

- it is a REPL-based debugger (more on that in the documentation)
- it is the [Revise](https://github.com/timholy/Revise.jl)-based debugger
- it supports repeated-execution debugging

### Current status

Currently broken and unmaintained due to the author having too many other packages to maintain. However, the functionality and general concept is still quite attractive.
For anyone interested in taking over maintenance, see [issue #90](https://github.com/timholy/Rebugger.jl/issues/90) for more information.

### JuliaCon 2018 Talk

While it's somewhat dated, you can learn about the "edit" interface in the following video:
[![](https://img.youtube.com/vi/KuM0AGaN09s/0.jpg)](https://youtu.be/KuM0AGaN09s?t=515)

However, the "interpret" interface is recommended for most users.

## Installation and usage

**See the documentation**:

[![](https://img.shields.io/badge/docs-stable-blue.svg)](https://timholy.github.io/Rebugger.jl/stable)
[![](https://img.shields.io/badge/docs-latest-blue.svg)](https://timholy.github.io/Rebugger.jl/dev)

Note that Rebugger may benefit from custom configuration, as described in the documentation.

In terms of usage, very briefly

- for "interpret" mode, type your command and hit <kbd> Meta-i </kbd> (which stands for "interpret")
- for "edit" mode, "step in" is achieved by positioning your cursor in your input line to the beginning of
  the call expression you wish to descend into. Then hit <kbd> Meta-e </kbd> ("enter").
- also for "edit" mode, for an expression that generates an error, hit <kbd> Meta-s </kbd> ("stacktrace")
  to capture the stacktrace and populate your REPL history with a sequence of expressions
  that contain the method bodies of the calls in the stacktrace.

<kbd> Meta </kbd> means <kbd> Esc </kbd> or, if your system is configured appropriately,
<kbd> Alt </kbd> (Linux/Windows) or <kbd> Option </kbd> (Macs).
More information and complete examples are provided in the documentation.
If your operating system assigns these keybindings to something else, you can [configure them to keys of your own choosing](https://timholy.github.io/Rebugger.jl/stable/config/#Customize-keybindings-1).

## Status

Rebugger is in early stages of development, and users should currently expect bugs (please do [report](https://github.com/timholy/Rebugger.jl/issues) them).
Neverthess it may be of net benefit for some users.

[pkgeval-img]: https://juliaci.github.io/NanosoldierReports/pkgeval_badges/R/Rebugger.svg
[pkgeval-url]: https://juliaci.github.io/NanosoldierReports/pkgeval_badges/report.html


================================================
FILE: docs/Project.toml
================================================
[deps]
Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4"
Revise = "295af30f-e4ad-537b-8983-00126c2a3abe"

[compat]
Documenter = "~0.21"


================================================
FILE: docs/make.jl
================================================
using Documenter, Rebugger

makedocs(
    modules = [Rebugger],
    clean = false,
    format = Documenter.HTML(prettyurls = get(ENV, "CI", nothing) == "true"),
    sitename = "Rebugger.jl",
    authors = "Tim Holy",
    linkcheck = !("skiplinks" in ARGS),
    pages = [
        "Home" => "index.md",
        "usage.md",
        "config.md",
        "limitations.md",
        "internals.md",
        "reference.md",
    ],
)

deploydocs(
    repo = "github.com/timholy/Rebugger.jl.git",
)


================================================
FILE: docs/src/config.md
================================================
# Configuration

## Run on REPL startup

If you decide you like Rebugger, you can add lines such as the following to your
`~/.julia/config/startup.jl` file:

```julia
atreplinit() do repl
    try
        @eval using Revise
        @async Revise.wait_steal_repl_backend()
    catch
        @warn "Could not load Revise."
    end

    try
        @eval using Rebugger
    catch
        @warn "Could not load Rebugger."
    end
end
```

## Customize keybindings

As described in [Keyboard shortcuts](@ref), it's possible that Rebugger's default keybindings
don't work for you.
You can work around problems by changing them to keys of your own choosing.

To add your own keybindings, use `Rebugger.add_keybindings(action=keybinding, ...)`.
This can be done during a running Rebugger session. Here is an example that
maps the "step in" action to the key "F6" and "capture stacktrace" to "F7"

```julia
julia> Rebugger.add_keybindings(stepin="\e[17~", stacktrace="\e[18~")
```

To make your keybindings permanent, change the "Rebugger" section of your `startup.jl` file
to something like:
```julia
atreplinit() do repl
    ...

    try
        @eval using Rebugger
        # Activate Rebugger's key bindings
        Rebugger.keybindings[:stepin] = "\e[17~"      # Add the keybinding F6 to step into a function.
        Rebugger.keybindings[:stacktrace] = "\e[18~"  # Add the keybinding F7 to capture a stacktrace.
    catch
        @warn "Could not load Rebugger."
    end
end
```

!!! note

    Besides the obvious, one reason to insert the keybindings into the `startup.jl`,
    has to do with the order in which keybindings are added to the REPL and whether any
    "stale" bindings that might have side effects are still present.
    Doing it before `atreplinit` means that there won't be any stale bindings.

But how to find out the cryptic string that corresponds to the keybinding you
want? Use Julia's `read()` function:

```julia
julia> str = read(stdin, String)
^[[17~"\e[17~"  # Press F6, followed by Ctrl+D, Ctrl+D

julia> str
"\e[17~"
```

After calling `read()`, press the keybinding that you want. Then, press `Ctrl+D`
twice to terminate the input. The value of `str` is the cryptic string you are
looking for.

If you want to know whether your key binding is already taken, the
[REPL documentation](https://docs.julialang.org/en/latest/stdlib/REPL/#Key-bindings-1)
as well as any documentation on your operating system, desktop environment, and/or
terminal program can be useful references.


================================================
FILE: docs/src/index.md
================================================
# Introduction to Rebugger

Rebugger is an expression-level debugger for Julia.
It has two modes of action:

- an "interpret" mode that lets you step through code, set
  breakpoints, and other manipulations common to "typical" debuggers;
- an "edit" mode that presents method bodies as objects for manipulation,
  allowing you to interactively play with the code at different stages
  of execution.


The name "Rebugger" has 3 meanings:

- it is a [REPL](https://docs.julialang.org/en/latest/stdlib/REPL/)-based debugger (more on that below)
- it is the [Revise](https://github.com/timholy/Revise.jl)-based debugger
- it supports repeated-execution debugging

## Installation

Begin with

```julia
(v1.0) pkg> add Rebugger
```

You can experiment with Rebugger with just

```julia
julia> using Rebugger
```

If you eventually decide you like Rebugger, you can optionally configure it so that it
is always available (see [Configuration](@ref)).

## Keyboard shortcuts

Most of Rebugger's functionality gets triggered by special keyboard shortcuts added to Julia's REPL.
Unfortunately, different operating systems and desktop environments vary considerably in
their key bindings, and it is possible that the default choices in Rebugger are
already assigned other meanings on your platform.
There does not appear to be any one set of choices that works on all platforms.

The best strategy is to try the demos in [Usage](@ref); if the default shortcuts
are already taken on your platform, then you can easily configure Rebugger
to use different bindings (see [Configuration](@ref)).

Some platforms are known to require or benefit from special attention:

#### macOS

If you're on macOS, you may want to enable
"[Use `option` as the Meta key](https://github.com/timholy/Rebugger.jl/issues/28#issuecomment-414852133)"
in your Terminal settings to avoid the need to press Esc before each Rebugger command.

#### Ubuntu

The default meta key on some Ubuntu versions is left Alt, which is equivalent to Esc Alt on the default
Gnome terminal emulator.
However, even with this tip you may encounter problems because Rebugger's default key bindings
may be assigned to activate menu options within the terminal window, and
[this appears not to be configurable]( https://bugs.launchpad.net/ubuntu/+source/nautilus/+bug/1113420).
Affected users may wish to [Customize keybindings](@ref).


================================================
FILE: docs/src/internals.md
================================================
# How Rebugger works

Rebugger traces execution through use of expression-rewriting and Julia's ordinary
`try/catch` control-flow.
It maintains internal storage that allows other methods to "deposit" their arguments
(a *store*) or temporarily *stash* the function and arguments of a call.

## Implementation of "step in"

Rebugger makes use of the buffer containing user input: not just its contents, but also
the position of "point" (the seek position) to indicate the specific call expression
targeted for stepping in.

For example, if a buffer has contents

```julia
    # <some code>
    if x > 0.5
        ^fcomplex(x, 2; kw1=1.1)
        # <more code>
```

where in the above `^` indicates "point," Rebugger uses a multi-stage process
to enter `fcomplex` with appropriate arguments:

- First, it carries out *caller capture* to determine which function is being called
  at point, and with which arguments. The main goal here is to be able to then use
  `which` to determine the specific method.
- Once armed with the specific method, it then carries out *callee capture* to
  obtain all the inputs to the method. For simple methods this may be redundant
  with *caller capture*, but *callee capture* can also obtain the values of
  default arguments, keyword arguments, and type parameters.
- Finally, Rebugger rewrites the REPL command-line buffer with a suitably-modified
  version of the body of the called method, so that the user can inspect, run, and
  manipulate it.

### Caller capture

The original expression above is rewritten as

```julia
    # <some code>
    if x > 0.5
        Main.Rebugger.stashed[] = (fcomplex, (x, 2), (kw1=1.1,))
        throw(Rebugger.StopException())
        # <more code>
```

Note that the full context of the original expression is preserved, thereby ensuring
that we do not have to be concerned about not having the appropriate local scope for
the arguments to the call of `fcomplex`.
However, rather than actually calling `fcomplex`, this expression "stashes" the
arguments and function in a temporary store internal to Rebugger.
It then throws an exception type specifically crafted to signal that the expression
executed and exited as expected.

This expression is then evaluated inside a block

```julia
    try
        Core.eval(Main, caller_capture_expression)
        throw(StashingFailed())
    catch err
        err isa StashingFailed && rethrow(err)
        if !(err isa StopException)
            throw(EvalException(content(buffer), err))
        end
    end
```

Note that this looks for the `StopException`; this is considered the normal execution
path.
If the `StopException` is never hit, it means evaluation never reached the expression
marked by "point" and thus leads to a `StashingFailed` exception.
Any other error results in an `EvalException`, usually triggered by other errors
in the block of code.

Assuming the `StopException` is hit, we then proceed to callee capture.

### Callee capture

Rebugger removes the function and arguments from `Rebugger.stashed[]` and then uses
`which` to determine the specific method called.
It then asks [Revise](https://timholy.github.io/Revise.jl/stable/) for the expression
that defines the method.
It then analyzes the signature to determine the full complement of inputs and creates
a new method that stores them. For example, if the applicable method of `fcomplex` is
given by

```julia
    function fcomplex(x::A, y=1, z=""; kw1=3.2) where A<:AbstractArray{T} where T
        # <body>
    end
```

then Rebugger generates a new method

```julia
    function hidden_fcomplex(x::A, y=1, z=""; kw1=3.2) where A<:AbstractArray{T} where T
        Main.Rebugger.stored[uuid] = Main.Rebugger.Stored(fcomplex, (:x, :y, :z, :kw1, :A, :T), deepcopy((x, y, z, kw1, A, T)))
        throw(StopException())
    end
```

This method is then called from inside another `try/catch` block that again checks for a `StopException`.
This results in the complete set of inputs being *stored*, a more "permanent" form
of preservation than *stashing*, which only lasts for the gap between caller and callee capture.
If one has the appropriate `uuid`, one can then extract these values at will from storage
using [`Rebugger.getstored`](@ref).

### Generating the new buffer contents (the `let` expression)

Once callee capture is complete, the user can re-execute any components of the called method
as desired. To make this easier, Rebugger replaces the contents of the buffer with a line that
looks like this:

```julia
@eval <ModuleOf_fcomplex> let (x, y, z, kw1, A, T) = Main.Rebugger.getstored("0123abc...")
    # <body>
end
```

The `@eval` makes sure that the block will be executed within the module in which
`fcomplex` is defined; as a consequence it will have access to all the unexported methods,
etc., that `fcomplex` itself has.
The `let` block ensures that these variables do not conflict with other objects that
may be defined in `ModuleOf_fcomplex`.
The values are unloaded from the store (making copies, in case `fcomplex` modifies its
inputs) and then execution proceeds into `body`.

The user can then edit the buffer at will.

## Implementation of "catch stacktrace"

In contrast with "step in," when catching a stacktrace Rebugger does not know the specific
methods that will be used in advance of making the call.
Consequently, Rebugger has to execute the call twice:

- the first call is used to obtain a stacktrace
- The trace is analyzed to obtain the specific methods, which are then replaced with versions
  that place inputs in storage; see [Callee capture](@ref), with the differences
  + the original method is (temporarily) overwritten by one that executes the store
  + this "storing" method also includes the full method body
  These two changes ensure that the "call chain" is not broken.
- a second call (recreating the same error, for functions that have deterministic execution)
  is then made to store all the arguments at each captured stage of the stacktrace.
- finally, the original methods are restored.


================================================
FILE: docs/src/limitations.md
================================================
# Limitations

Rebugger is in the early stages of development, and users should currently expect bugs (please do [report](https://github.com/timholy/Rebugger.jl/issues) them).
Nevertheless it may be of net benefit for some users.

Here are some known shortcomings:

- Rebugger only has access to code tracked by Revise.
  To ensure that scripts are tracked, use `includet(filename)` to include-and-track.
  (See [Revise's documentation](https://timholy.github.io/Revise.jl/stable/user_reference.html).)
  For stepping into Julia's stdlibs, currently you need a source-build of Julia.
- You cannot step into methods defined at the REPL.
- For now you can't step into constructors (it tries to step into `(::Type{T})`)
- There are occasional glitches in the display.
  (For brave souls who want to help fix these,
  see [HeaderREPLs.jl](https://github.com/timholy/HeaderREPLs.jl))
- Rebugger runs best in Julia 1.0. While it should run on Julia 0.7,
  a local-scope deprecation can cause some
  problems. If you want 0.7 because of its deprecation warnings and are comfortable
  building Julia, consider building it at commit
  f08f3b668d222042425ce20a894801b385c2b1e2, which removed the local-scope deprecation
  but leaves most of the other deprecation warnings from 0.7 still in place.
- If you start `dev`ing a package that you had already loaded, you need to [restart
  your session](https://github.com/timholy/Revise.jl/issues/146)

Another important point (not particularly specific to Rebugger) is that
repeatedly executing code that modifies some global state
can lead to unexpected side effects.
Rebugger works best on methods whose behavior is determined solely by their input
arguments.


================================================
FILE: docs/src/reference.md
================================================
# Developer reference

## Capturing arguments

```@docs
Rebugger.stepin
Rebugger.prepare_caller_capture!
Rebugger.method_capture_from_callee
Rebugger.signature_names!
```

## Capturing stacktrace

```@docs
Rebugger.capture_stacktrace
Rebugger.pregenerated_stacktrace
Rebugger.linerange
```

## Utilities

```@docs
Rebugger.clear
Rebugger.getstored
```


================================================
FILE: docs/src/usage.md
================================================
# Usage

Rebugger works from Julia's native REPL prompt. Currently there are exactly three keybindings,
which here will be described as:

- Meta-i, which maps to "interpret"
- Meta-e, which maps to "enter" or "step in"
- Meta-s, which maps to "stacktrace" (for commands that throw an error)

Meta often maps to `Esc`, and if using `Esc` you should hit the two keys in
sequence rather than simultaneously.
For many users `Alt` (sometimes specifically `Left-Alt`, or `Option` on macs) may be more convenient, as it can be pressed
simultaneously with the key.

Of course, you may have configured Rebugger to use different key bindings (see [Customize keybindings](@ref)).

## Interpret mode

Interpret mode simulates an IDE debugger at the REPL: rather than entering your commands into
a special prompt, you use single keystrokes to quickly advance through the code.

Let's start with an example:

```julia
julia> using Rebugger

julia> a = [4, 1, 3, 2];
```

Now we're going to call `sort`, but don't hit enter:
```
julia> sort(a)
```

Instead, hit Meta-i (Esc-i, Alt-i, or option-i):

```
interpret> sort(a)[ Info: tracking Base

sort(v::AbstractArray{T,1} where T) in Base.Sort at sort.jl:742
  v = [4, 1, 3, 2]
 742  sort(v::AbstractVector; kws...) = begin
 742          sort!(copymutable(v); kws...)
          end

```

The message informs you that Revise (which is used by Rebugger) is now examining the code
in Base to extract the definition of `sort`.
There's a considerable pause the first time you do this, but later it should generally be faster.

After the "Info" line, you can see the method you called printed on top.
After that are the local variables of `sort`, which here is just the array you supplied.
(You can see some screenshots below in the "edit mode" section that show these in color.
The meaning is the same here.)
The "742" indicates the line number of "sort.jl", where the `sort` method you're calling
is defined.
Finally, you'll see a representation of the definition itself. Rebugger typically shows
you expressions rather than verbatim text; unlike the text in the original file,
 this works equally well for `@eval`ed functions and generated functions.

The current line number is printed in yellow; here, that's both lines, since the original
definition was written on a single line.

We can learn about the possibilities by typing `?`:

```
Commands:
  space: next line
  enter: continue to next breakpoint or completion
      →: step in to next call
      ←: finish frame and return to caller
      ↑: display the caller frame
      ↓: display the callee frame
      b: insert breakpoint at current line
      c: insert conditional breakpoint at current line
      r: remove breakpoint at current line
      d: disable breakpoint at current line
      e: enable breakpoint at current line
      q: abort (returns nothing)
sort(v::AbstractArray{T,1} where T) in Base.Sort at sort.jl:742
  v = [4, 1, 3, 2]
 742  sort(v::AbstractVector; kws...) = begin
 742          sort!(copymutable(v); kws...)
          end
```

Let's try stepping in to the call: hit the right arrow, at which point you should see

```
sort(v::AbstractArray{T,1} where T) in Base.Sort at sort.jl:742
 #sort#8(kws, ::Any, v::AbstractArray{T,1} where T) in Base.Sort at sort.jl:742
  #sort#8 = Base.Sort.#sort#8
  kws = Base.Iterators.Pairs{Union{},Union{},Tuple{},NamedTuple{(),Tuple{}}}()
  @_3 = sort
  v = [4, 1, 3, 2]
 742  sort(v::AbstractVector; kws...) = begin
 742          sort!(copymutable(v); kws...)
          end
```

We're now in a "hidden" method `#sort#8`, generated automatically by Julia to handle
keyword and/or optional arguments. This is what actually contains the main body of `sort`.
You'll note the source expression hasn't changed, because it's generated from the same
definition, but that some additional arguments (`kws` and the "nameless argument" `@_3`)
have appeared.

If we hit the right arrow again, we enter `copymutable`. Our interest is in stepping further into `sort`,
so we're not going to bother walking through `copymutable`; hit left arrow, which finishes
the current frame and returns to the caller. This should return
you to `#sort#8`. Then hit the right arrow again and you should be here:

```
sort(v::AbstractArray{T,1} where T) in Base.Sort at sort.jl:742
 #sort#8(kws, ::Any, v::AbstractArray{T,1} where T) in Base.Sort at sort.jl:742
  sort!(v::AbstractArray{T,1} where T) in Base.Sort at sort.jl:682
   #sort!#7(alg::Base.Sort.Algorithm, lt, by, rev::Union{Nothing, Bool}, order::Base.Order.Ordering, ::Any, v::AbstractArray{T,1} where T) in Base.Sort at sort.jl:682
  #sort!#7 = Base.Sort.#sort!#7
  alg = Base.Sort.QuickSortAlg()
  lt = isless
  by = identity
  rev = nothing
  order = Base.Order.ForwardOrdering()
  @_7 = sort!
  v = [4, 1, 3, 2]
 681  function sort!(v::AbstractVector; alg::Algorithm=defalg(v), lt=isless, by=identity, re…
 682      ordr = ord(lt, by, rev, order)
 683      if ordr === Forward && (v isa Vector && eltype(v) <: Integer)
 684          n = length(v)
```

Now you can see many more arguments. To understand everything you're seeing, sometimes
it may help to open the source file in an editor (hit 'o' for open) for comparison.

Note that long function bodies are
truncated; you only see a few lines around the current execution point.

Line 682 should be highlighted. Hit the space bar and you should advance to 683:

```
sort(v::AbstractArray{T,1} where T) in Base.Sort at sort.jl:742
 #sort#8(kws, ::Any, v::AbstractArray{T,1} where T) in Base.Sort at sort.jl:742
  sort!(v::AbstractArray{T,1} where T) in Base.Sort at sort.jl:682
   #sort!#7(alg::Base.Sort.Algorithm, lt, by, rev::Union{Nothing, Bool}, order::Base.Order.Ordering, ::Any, v::AbstractArray{T,1} where T) in Base.Sort at sort.jl:682
  #sort!#7 = Base.Sort.#sort!#7
  alg = Base.Sort.QuickSortAlg()
  lt = isless
  by = identity
  rev = nothing
  order = Base.Order.ForwardOrdering()
  @_7 = sort!
  v = [4, 1, 3, 2]
  ordr = Base.Order.ForwardOrdering()
 681  function sort!(v::AbstractVector; alg::Algorithm=defalg(v), lt=isless, by=identity, re…
 682      ordr = ord(lt, by, rev, order)
 683      if ordr === Forward && (v isa Vector && eltype(v) <: Integer)
 684          n = length(v)
 685          if n > 1
```

You can see that the code display also advanced by one line.

Let's go forward one more line (hit space) and then hit `b` to insert a breakpoint:

```
sort(v::AbstractArray{T,1} where T) in Base.Sort at sort.jl:742
 #sort#8(kws, ::Any, v::AbstractArray{T,1} where T) in Base.Sort at sort.jl:742
  sort!(v::AbstractArray{T,1} where T) in Base.Sort at sort.jl:682
   #sort!#7(alg::Base.Sort.Algorithm, lt, by, rev::Union{Nothing, Bool}, order::Base.Order.Ordering, ::Any, v::AbstractArray{T,1} where T) in Base.Sort at sort.jl:682
  #sort!#7 = Base.Sort.#sort!#7
  alg = Base.Sort.QuickSortAlg()
  lt = isless
  by = identity
  rev = nothing
  order = Base.Order.ForwardOrdering()
  @_7 = sort!
  v = [4, 1, 3, 2]
  ordr = Base.Order.ForwardOrdering()
  #temp# = true
 682      ordr = ord(lt, by, rev, order)
 683      if ordr === Forward && (v isa Vector && eltype(v) <: Integer)
b684          n = length(v)
 685          if n > 1
 686              (min, max) = extrema(v)
```

The `b` in the left column indicates an unconditional breakpoint; a `c` would indicate a
conditional breakpoint.

At this point, hit Enter to finish the entire command (you should see the result printed
at the REPL). Now let's run it again, by going back in the REPL history (hit the up arrow)
and then hitting Meta-i again:

```
interpret> sort(a)
sort(v::AbstractArray{T,1} where T) in Base.Sort at sort.jl:742
  v = [4, 1, 3, 2]
 742  sort(v::AbstractVector; kws...) = begin
 742          sort!(copymutable(v); kws...)
          end
```

We may be back at the beginning, but remember: we set a breakpoint. Hit Enter to
let execution move forward:

```
interpret> sort(a)
sort(v::AbstractArray{T,1} where T) in Base.Sort at sort.jl:742
 #sort#8(kws, ::Any, v::AbstractArray{T,1} where T) in Base.Sort at sort.jl:742
  sort!(v::AbstractArray{T,1} where T) in Base.Sort at sort.jl:682
   #sort!#7(alg::Base.Sort.Algorithm, lt, by, rev::Union{Nothing, Bool}, order::Base.Order.Ordering, ::Any, v::AbstractArray{T,1} where T) in Base.Sort at sort.jl:682
  #sort!#7 = Base.Sort.#sort!#7
  alg = Base.Sort.QuickSortAlg()
  lt = isless
  by = identity
  rev = nothing
  order = Base.Order.ForwardOrdering()
  @_7 = sort!
  v = [4, 1, 3, 2]
  ordr = Base.Order.ForwardOrdering()
  #temp# = true
 682      ordr = ord(lt, by, rev, order)
 683      if ordr === Forward && (v isa Vector && eltype(v) <: Integer)
b684          n = length(v)
 685          if n > 1
 686              (min, max) = extrema(v)
```

We're right back at that breakpoint again.

Let's illustrate another example, this time in the context of errors:

```
julia> convert(UInt, -8)
ERROR: InexactError: check_top_bit(Int64, -8)
Stacktrace:
 [1] throw_inexacterror(::Symbol, ::Any, ::Int64) at ./boot.jl:583
 [2] check_top_bit at ./boot.jl:597 [inlined]
 [3] toUInt64 at ./boot.jl:708 [inlined]
 [4] Type at ./boot.jl:738 [inlined]
 [5] convert(::Type{UInt64}, ::Int64) at ./number.jl:7
 [6] top-level scope at none:0
```

Rebugger re-exports [JuliaInterpreter's breakpoint manipulation utilities](https://juliadebug.github.io/JuliaInterpreter.jl/stable/).
Let's turn on breakpoints any time an (uncaught) exception is thrown:

```
julia> break_on(:error)
```

Now repeat that `convert` line but hit Meta-i instead of Enter:

```
interpret> convert(UInt, -8)
convert(::Type{T}, x::Number) where T<:Number in Base at number.jl:7
  #unused# = UInt64
  x = -8
  T = UInt64
 7  (convert(::Type{T}, x::Number) where T <: Number) = begin
 7          T(x)
        end
```

Now if you hit Enter, you'll be at the place where the error was thrown:

```

interpret> convert(UInt, -8)
convert(::Type{T}, x::Number) where T<:Number in Base at number.jl:7
 UInt64(x::Union{Bool, Int32, Int64, UInt32, UInt64, UInt8, Int128, Int16, Int8, UInt128, UInt16}) in Core at boot.jl:738
  toUInt64(x::Int64) in Core at boot.jl:708
   check_top_bit(x) in Core at boot.jl:596
    throw_inexacterror(f::Symbol, T, val) in Core at boot.jl:583
  f = check_top_bit
  T = Int64
  val = -8
 583  throw_inexacterror(f::Symbol, @nospecialize(T), val) = (@_noinline_meta; throw(Inexact…
```

Try using the up and down arrows to navigate up and down the call stack. This doesn't
change the notion of current execution point (that's still at that `throw_inexacterror` call
above), but it does let you see where you came from.

You can turn this off with `break_off` and clear all manually-set breakpoints with `remove()`.

### A few important details

There are some calls that you can't step into: most of these are the "builtins,"
"intrinsics," and "ccalls" that lie at Julia's lowest level.
Here's an example from hitting Meta-i on `show`:

```
interpret> show([1,2,4])
show(x) in Base at show.jl:313
  x = [1, 2, 4]
 313  show(x) = begin
 313          show(stdout::IO, x)
          end

```

That looks like a call you'd want to step into. But if you hit the right arrow, apparently
nothing happens. That's because the next statement is actually that type-assertion `stdout::IO`.
`typeassert` is a builtin, and consequently not a call you can step into.

When in doubt, just repeat the same keystroke; here, the second press of the right arrow
takes you to the two-argument `show` method that you probably thought you were descending
into.

## Edit mode

### Stepping in

Select the expression you want to step into by positioning "point" (your cursor)
at the desired location in the command line:

```@raw html
<img src="../images/stepin1.png" width="160px"/>
```

It's essential that point is at the very first character of the expression, in this case on
the `s` in `show`.

!!! note
    Don't confuse the REPL's cursor with your mouse pointer.
    Your mouse is essentially irrelevant on the REPL; use arrow keys or the other
    [navigation features of Julia's REPL](https://docs.julialang.org/en/latest/stdlib/REPL/).

Now if you hit Meta-e, you should see something like this:

```@raw html
<img src="../images/stepin2.png" width="660px"/>
```

(If not, check [Keyboard shortcuts](@ref) and [Customize keybindings](@ref).)
The cyan "Info" line is an indication that the method you're stepping into is
a function in Julia's Base module; this is shown by Revise (not Rebugger), and only happens
once per session.

The remaining lines correspond to the Rebugger header and user input.
The magenta line tells you which method you are stepping into.
Indented blue line(s) show the value(s) of any input arguments or type parameters.

If you're following along, move your cursor to the next `show` call as illustrated above.
Hit Meta-e again. You should see a new `show` method, this time with two input arguments.

Now let's demonstrate another important display item: position point at the
beginning of the `_show_empty` call and hit Meta-e.
The display should now look like this:

```@raw html
<img src="../images/stepin3.png" width="690px"/>
```

This time, note the yellow/orange line: this is a warning message, and you should pay attention to these.
(You might also see red lines, which are generally more serious "errors.")
In this case execution never reached `_show_empty`, because it enters `show_vector` instead;
if you moved your cursor there, you could trace execution more completely.

You can edit these expressions to insert code to display variables or test
changes to the code.
As an experiment, try stepping into the `show_vector` call from the example above
and adding `@show limited` to display a local variable's value:

```@raw html
<img src="../images/stepin4.png" width="800px"/>
```

!!! note
    When editing expressions, you can insert a blank line with Meta-Enter (i.e., Esc-Enter, Alt-Enter, or Option-Enter).
    See the many [advanced features of Julia's REPL](https://docs.julialang.org/en/latest/stdlib/REPL/#Key-bindings-1) that allow you to efficiently edit these `let`-blocks.

Having illustrated the importance of "point" and the various colors used for messages from Rebugger,
to ensure readability the remaining examples will be rendered as text.


### Capturing stacktraces in edit mode

For a quick demo, we'll use the `Colors` package (`add` it if you don't have it)
and deliberately choose a method that will end in an error: we'll try to parse
a string as a Hue, Saturation, Lightness (HSL) color, except we'll "forget" that hue
cannot be expressed as a percentage and deliberately trigger an error:

```julia
julia> using Colors

julia> colorant"hsl(80%, 20%, 15%)"
ERROR: LoadError: hue cannot end in %
Stacktrace:
 [1] error(::String) at ./error.jl:33
 [2] parse_hsl_hue(::SubString{String}) at /home/tim/.julia/dev/Colors/src/parse.jl:26
 [3] _parse_colorant(::String) at /home/tim/.julia/dev/Colors/src/parse.jl:75
 [4] _parse_colorant at /home/tim/.julia/dev/Colors/src/parse.jl:112 [inlined]
 [5] parse(::Type{Colorant}, ::String) at /home/tim/.julia/dev/Colors/src/parse.jl:140
 [6] @colorant_str(::LineNumberNode, ::Module, ::Any) at /home/tim/.julia/dev/Colors/src/parse.jl:147
in expression starting at REPL[3]:1
```

To capture the stacktrace, type the last line again or hit the up arrow, but instead of
pressing Enter, type Meta-s.
After a short delay, you should see something like this:

```julia
julia> colorant"hsl(80%, 20%, 15%)"
┌ Warning: Tuple{getfield(Colors, Symbol("#@colorant_str")),LineNumberNode,Module,Any} was not found, perhaps it was generated by code
└ @ Revise ~/.julia/dev/Revise/src/Revise.jl:659
Captured elements of stacktrace:
[1] parse_hsl_hue(num::AbstractString) in Colors at /home/tim/.julia/packages/Colors/4hvzi/src/parse.jl:25
[2] _parse_colorant(desc::AbstractString) in Colors at /home/tim/.julia/packages/Colors/4hvzi/src/parse.jl:51
[3] _parse_colorant(::Type{C}, ::Type{SUP}, desc::AbstractString) where {C<:Colorant, SUP} in Colors at /home/tim/.julia/packages/Colors/4hvzi/src/parse.jl:112
[4] parse(::Type{C}, desc::AbstractString) where C<:Colorant in Colors at /home/tim/.julia/packages/Colors/4hvzi/src/parse.jl:140
parse_hsl_hue(num::AbstractString) in Colors at /home/tim/.julia/packages/Colors/4hvzi/src/parse.jl:25
  num = 80%
rebug> @eval Colors let (num,) = Main.Rebugger.getstored("57dbc76a-0def-11e9-1dbf-ef97d29d2e25")
       begin
           if num[end] == '%'
               error("hue cannot end in %")
           else
               return parse(Int, num, base=10)
           end
       end
       end
```

(Again, if this doesn't happen check [Keyboard shortcuts](@ref) and [Customize keybindings](@ref).)
You are in the method corresponding to `[1]` in the stacktrace.
Now you can navigate with your up and down arrows to browse the captured stacktrace.
For example, if you hit the up arrow twice, you will be in the method corresponding to `[3]`:

```julia
julia> colorant"hsl(80%, 20%, 15%)"
┌ Warning: Tuple{getfield(Colors, Symbol("#@colorant_str")),LineNumberNode,Module,Any} was not found, perhaps it was generated by code
└ @ Revise ~/.julia/dev/Revise/src/Revise.jl:659
Captured elements of stacktrace:
[1] parse_hsl_hue(num::AbstractString) in Colors at /home/tim/.julia/packages/Colors/4hvzi/src/parse.jl:25
[2] _parse_colorant(desc::AbstractString) in Colors at /home/tim/.julia/packages/Colors/4hvzi/src/parse.jl:51
[3] _parse_colorant(::Type{C}, ::Type{SUP}, desc::AbstractString) where {C<:Colorant, SUP} in Colors at /home/tim/.julia/packages/Colors/4hvzi/src/parse.jl:112
[4] parse(::Type{C}, desc::AbstractString) where C<:Colorant in Colors at /home/tim/.julia/packages/Colors/4hvzi/src/parse.jl:140
[5] @colorant_str(__source__::LineNumberNode, __module__::Module, ex) in Colors at /home/tim/.julia/packages/Colors/4hvzi/src/parse.jl:146
_parse_colorant(::Type{C}, ::Type{SUP}, desc::AbstractString) where {C<:Colorant, SUP} in Colors at /home/tim/.julia/packages/Colors/4hvzi/src/parse.jl:112
  C = Colorant
  SUP = Any
  desc = hsl(80%, 20%, 15%)
rebug> @eval Colors let (C, SUP, desc) = Main.Rebugger.getstored("57d9ebc0-0def-11e9-2ab0-e5d1e4c6e82d")
       begin
           _parse_colorant(desc)
       end
       end
```

You can hit the down arrow and go back to earlier entries in the trace.
Alternatively, you can pick any of these expressions to execute (hit Enter) or edit before execution.
You can use the REPL history to test the results of many different changes to the same "method";
the "method" will be run with the same inputs each time.

!!! note
    When point is at the end of the input, the up and down arrows step through the history.
    But if you move point into the method body (e.g., by using left-arrow),
    the up and down arrows move within the method body.
    If you've entered edit mode, you can go back to history mode using PgUp and PgDn.

### Important notes

#### "Missing" methods from stacktraces

Sometimes, there's a large difference between the "real" stacktrace and the "captured" stacktrace:

```julia
julia> using Pkg

julia> Pkg.add("NoPkg")
  Updating registry at `~/.julia/registries/General`
  Updating git-repo `https://github.com/JuliaRegistries/General.git`
ERROR: The following package names could not be resolved:
 * NoPkg (not found in project, manifest or registry)
Please specify by known `name=uuid`.
Stacktrace:
 [1] pkgerror(::String) at /home/tim/src/julia-1.0/usr/share/julia/stdlib/v1.0/Pkg/src/Types.jl:120
 [2] #ensure_resolved#42(::Bool, ::Function, ::Pkg.Types.EnvCache, ::Array{Pkg.Types.PackageSpec,1}) at /home/tim/src/julia-1.0/usr/share/julia/stdlib/v1.0/Pkg/src/Types.jl:890
 [3] #ensure_resolved at ./none:0 [inlined]
 [4] #add_or_develop#13(::Symbol, ::Bool, ::Base.Iterators.Pairs{Union{},Union{},Tuple{},NamedTuple{(),Tuple{}}}, ::Function, ::Pkg.Types.Context, ::Array{Pkg.Types.PackageSpec,1}) at /home/tim/src/julia-1.0/usr/share/julia/stdlib/v1.0/Pkg/src/API.jl:59
 [5] #add_or_develop at ./none:0 [inlined]
 [6] #add_or_develop#12 at /home/tim/src/julia-1.0/usr/share/julia/stdlib/v1.0/Pkg/src/API.jl:29 [inlined]
 [7] #add_or_develop at ./none:0 [inlined]
 [8] #add_or_develop#11(::Base.Iterators.Pairs{Symbol,Symbol,Tuple{Symbol},NamedTuple{(:mode,),Tuple{Symbol}}}, ::Function, ::Array{String,1}) at /home/tim/src/julia-1.0/usr/share/julia/stdlib/v1.0/Pkg/src/API.jl:28
 [9] #add_or_develop at ./none:0 [inlined]
 [10] #add_or_develop#10 at /home/tim/src/julia-1.0/usr/share/julia/stdlib/v1.0/Pkg/src/API.jl:27 [inlined]
 [11] #add_or_develop at ./none:0 [inlined]
 [12] #add#18 at /home/tim/src/julia-1.0/usr/share/julia/stdlib/v1.0/Pkg/src/API.jl:69 [inlined]
 [13] add(::String) at /home/tim/src/julia-1.0/usr/share/julia/stdlib/v1.0/Pkg/src/API.jl:69
 [14] top-level scope at none:0

julia> Pkg.add("NoPkg")  # hit Meta-s here
[1] pkgerror(msg::String...) in Pkg.Types at /home/tim/src/julia-1/usr/share/julia/stdlib/v1.1/Pkg/src/Types.jl:120
[2] #ensure_resolved#72(registry::Bool, ::Any, env::Pkg.Types.EnvCache, pkgs::AbstractArray{Pkg.Types.PackageSpec,1}) in Pkg.Types at /home/tim/src/julia-1/usr/share/julia/stdlib/v1.1/Pkg/src/Types.jl:981
[3] #add_or_develop#15(mode::Symbol, shared::Bool, kwargs, ::Any, ctx::Pkg.Types.Context, pkgs::Array{Pkg.Types.PackageSpec,1}) in Pkg.API at /home/tim/src/julia-1/usr/share/julia/stdlib/v1.1/Pkg/src/API.jl:34
[4] #add_or_develop#12(kwargs, ::Any, pkg::Union{AbstractString, PackageSpec}) in Pkg.API at /home/tim/src/julia-1/usr/share/julia/stdlib/v1.1/Pkg/src/API.jl:28
[5] add(args...) in Pkg.API at /home/tim/src/julia-1/usr/share/julia/stdlib/v1.1/Pkg/src/API.jl:59
pkgerror(msg::String...) in Pkg.Types at /home/tim/src/julia-1/usr/share/julia/stdlib/v1.1/Pkg/src/Types.jl:120
  msg = ("The following package names could not be resolved:\n * NoPkg (not found in project, manifest or registry)\nPlease specify by known `name=uuid`.",)
rebug> @eval Pkg.Types let (msg,) = Main.Rebugger.getstored("1ac42628-4b15-11e9-28e7-33f71870bf31")
       begin
           throw(PkgError(join(msg)))
       end
       end
```

Note that only five methods got captured but the stacktrace is much longer.
Most of these methods, however, say "inlined" with line number 0.
Rebugger has no way of finding such methods.
However, you can enter (i.e., Meta-e) such methods from one that is higher in the stack trace.

#### Modified "signatures"

Some "methods" you see in the `let` block on the command line will have their
"signatures" slightly modified.
For example:

```julia
julia> dest = zeros(3);

julia> copyto!(dest, 1:4)
ERROR: BoundsError: attempt to access 3-element Array{Float64,1} at index [1, 2, 3, 4]
Stacktrace:
 [1] copyto!(::IndexLinear, ::Array{Float64,1}, ::IndexLinear, ::UnitRange{Int64}) at ./abstractarray.jl:728
 [2] copyto!(::Array{Float64,1}, ::UnitRange{Int64}) at ./abstractarray.jl:723
 [3] top-level scope at none:0

julia> copyto!(dest, 1:4)  # hit Meta-s here
Captured elements of stacktrace:
[1] copyto!(::IndexStyle, dest::AbstractArray, ::IndexStyle, src::AbstractArray) in Base at abstractarray.jl:727
[2] copyto!(dest::AbstractArray, src::AbstractArray) in Base at abstractarray.jl:723
copyto!(::IndexStyle, dest::AbstractArray, ::IndexStyle, src::AbstractArray) in Base at abstractarray.jl:727
  __IndexStyle_1 = IndexLinear()
  dest = [0.0, 0.0, 0.0]
  __IndexStyle_2 = IndexLinear()
  src = 1:4
rebug> @eval Base let (__IndexStyle_1, dest, __IndexStyle_2, src) = Main.Rebugger.getstored("21a8ab94-a228-11e8-0563-256e39b3996e")
       begin
           (destinds, srcinds) = (LinearIndices(dest), LinearIndices(src))
           isempty(srcinds) || (checkbounds(Bool, destinds, first(srcinds)) && checkbounds(Bool, destinds, last(srcinds)) || throw(BoundsError(dest, srcinds)))
           @inbounds for i = srcinds
                   dest[i] = src[i]
               end
           return dest
       end
       end
```

Note that this `copyto!` method contains two anonymous arguments annotated `::IndexStyle`.
Rebugger will make up names for these arguments (here `__IndexStyle_1` and `__IndexStyle_2`).
While these will be distinct from one another, Rebugger does not check whether they
conflict with any internal names.

!!! note
    This example illustrates a second important point: you may have noticed that this one was
    considerably slower to print.
    That's because capturing stacktraces overwrites the methods involved.
    Since `copyto!` is widely used, this forces recompilation of a lot
    of methods in Base.

    In contrast with capturing stacktraces, stepping in (Meta-e) does not overwrite methods,
    so is sometimes preferred. And of course, interpret mode (Meta-i) also doesn't overwrite methods.


================================================
FILE: src/Rebugger.jl
================================================
module Rebugger

using UUIDs, InteractiveUtils
using REPL
import REPL.LineEdit, REPL.Terminals
using REPL.LineEdit: buffer, bufend, content, edit_splice!
using REPL.LineEdit: transition, terminal, mode, state

using CodeTracking, Revise, JuliaInterpreter, HeaderREPLs
using Revise: RelocatableExpr, striplines!, printf_maxsize, whichtt, hasfile, unwrap
using JuliaInterpreter: FrameCode, scopeof
using Base.Meta: isexpr
using Core: CodeInfo

# Reexports
export @breakpoint, breakpoint, enable, disable, remove, break_on, break_off

const msgs = []  # for debugging. The REPL-magic can sometimes overprint error messages

include("debug.jl")
include("ui.jl")
include("printing.jl")
include("deepcopy.jl")

# Set up keys that enter rebug mode from the regular Julia REPL
# This should be called from your ~/.julia/config/startup.jl file
function repl_init(repl)
    repl.interface = REPL.setup_interface(repl; extra_repl_keymap = get_rebugger_modeswitch_dict())
end

function rebugrepl_init()
    # Set up the Rebugger REPL mode with all of its key bindings
    repl_inited = isdefined(Base, :active_repl)
    while !isdefined(Base, :active_repl)
        sleep(0.05)
    end
    sleep(0.1) # for extra safety
    # Set up the custom "rebug" REPL
    iprompt, eprompt = rebugrepl_init(Base.active_repl, repl_inited)
    interpret_prompt_ref[] = iprompt
    rebug_prompt_ref[] = eprompt
    return nothing
end

function rebugrepl_init(main_repl, repl_inited)
    irepl = HeaderREPL(main_repl, InterpretHeader())
    interface = REPL.setup_interface(irepl; extra_repl_keymap=Dict[])
    iprompt = interface.modes[end]
    erepl = HeaderREPL(main_repl, RebugHeader())
    interface = REPL.setup_interface(erepl; extra_repl_keymap=[get_rebugger_modeswitch_dict(), rebugger_keys])
    eprompt = interface.modes[end]
    add_keybindings(main_repl; override=repl_inited, keybindings...)
    return iprompt, eprompt
end

function __init__()
    # schedule(Task(rebugrepl_init))
    task = Task() do
        try
            rebugrepl_init()
        catch exception
            @error "Rebugger initialization failed" exception=(exception, catch_backtrace())
        end
    end
    schedule(task)
end

include("precompile.jl")
_precompile_()

end # module


================================================
FILE: src/debug.jl
================================================
# Core debugging logic.
# Hopefully someday much of this will be replaced by Gallium.

const VarnameType = Tuple{Vararg{Union{Symbol,Expr}}}  # Expr covers `foo(x, (a,b), y)` destructured-tuple signatures
struct Stored
    method::Method
    varnames::VarnameType
    varvals

    function Stored(m, names, vals)
        new(m, names, safe_deepcopy(vals...))
    end
end

const stashed     = Ref{Any}(nothing)
const stored      = Dict{UUID,Stored}()              # UUID => store data
const storefunc   = Dict{UUID,Function}()            # UUID => function that puts inputs into `stored`
const storemap    = Dict{Tuple{Method,Bool},UUID}()  # (method, overwrite) => UUID

struct StopException   <: Exception end
struct StashingFailed  <: Exception end   # stashing saves the function and arguments from the caller (transient)
struct StorageFailed   <: Exception end   # storage puts callee values into `stored` (lasts until `stored` is cleared)
struct DefMissing      <: Exception
    method::Method
    exception
end
struct SignatureError  <: Exception
    method::Method
end
struct StepException   <: Exception
    msg::String
end
struct EvalException   <: Exception
    exprstring
    exception
end

const base_prefix = '.' * Base.Filesystem.path_separator

"""
    Rebugger.clear()

Clear internal data. This deletes storage associated with stored variables, but also
forces regeneration of capture methods, which can be handy while debugging Rebugger itself.
"""
function clear()
    stashed[] = nothing
    empty!(stored)
    empty!(storefunc)
    empty!(storemap)
    nothing
end

### Stacktraces

"""
    r = linerange(expr, offset=0)

Compute the range of lines occupied by `expr`.
Returns `nothing` if no line statements can be found.
"""
function linerange(def::Expr)
    start = findline(def)
    stop  = findline(def, Iterators.reverse)
    start !== nothing && stop !== nothing && return start.line:stop.line
    return nothing
end

function findline(ex, order)
    isa(ex, Expr) || return nothing
    for a in order(ex.args)
        a isa LineNumberNode && return a
        if a isa Expr
            ln = findline(a, order)
            ln !== nothing && return ln
        end
    end
    return nothing
end
findline(ex) = findline(ex, identity)

"""
    usrtrace, defs = pregenerated_stacktrace(trace, topname=:capture_stacktrace)

Generate a list of methods `usrtrace` and their corresponding definition-expressions `defs`
from a stacktrace.
Not all methods can be looked up, but this attempts to resolve, e.g., keyword-handling methods
and so on.
"""
function pregenerated_stacktrace(trace; topname = :capture_stacktrace)
    usrtrace, defs = Method[], Expr[]
    methodsused = Set{Method}()

    # When the method can't be found directly in the tables,
    # look it up by fie and line number
    function add_by_file_line(defmap, line)
        for (rdef, sigts) in defmap
            def = rdef.ex
            (sigts === nothing || isempty(sigts)) && continue
            r = linerange(def)  # FIXME offset
            if line ∈ r
                m = whichtt(last(sigts))
                if m ∉ methodsused
                    push!(defs, def)
                    push!(usrtrace, m)
                    push!(methodsused, m)
                    return true
                end
            end
        end
        return false
    end
    function add_by_file_line(pkgdata, file, line)
        fi = Revise.maybe_parse_from_cache!(pkgdata, relpath(file, pkgdata))
        for (mod, exsigs) in fi.modexsigs
            add_by_file_line(exsigs, line) && return true
        end
        return false
    end

    for (i, sf) in enumerate(trace)
        (sf.func == topname || sf.func == Symbol("top-level scope")) && break  # truncate at the chosen spot
        sf.func ∈ notrace && continue
        mi = sf.linfo
        file = String(sf.file)
        if mi isa Core.MethodInstance
            method = mi.def
            def = definition(method)
            if def === nothing
                # This may be a generated method, perhaps it's a keyword function handler
                # Look for it by line number
                local id
                try
                    id = Revise.get_tracked_id(method.module)
                catch
                    # Methods from Core.Compiler cause errors on Julia binaries
                    continue
                end
                id === nothing && continue
                pkgdata = Revise.pkgdatas[id]
                cfile = get(Revise.src_file_key, file, file)
                rpath = relpath(cfile, pkgdata)
                hasfile(pkgdata, rpath) || continue
                fi = Revise.maybe_parse_from_cache!(pkgdata, rpath)
                # fi = get(pkgdata.fileinfos, rpath, nothing)
                # if fi !== nothing
                #     add_by_file_line(fi.fm[method.module].defmap, sf)
                # end
            else
                method ∈ methodsused && continue
                def isa Expr || continue
                push!(defs, def)
                push!(usrtrace, method)
            end
        else
            # This method was inlined and hence linfo was not available
            # Try to find it
            if startswith(file, base_prefix)
                # This is a file in Base or Core
                file = relpath(file, base_prefix)
                id = Revise.get_tracked_id(Base)
                pkgdata = Revise.pkgdatas[id]
                if hasfile(pkgdata, file)
                    add_by_file_line(pkgdata, file, sf.line) && continue
                elseif startswith(file, "compiler")
                    try
                        id = Revise.get_tracked_id(Core.Compiler)
                    catch
                        # On Julia binaries Core.Compiler is not available
                        continue
                    end
                    pkgdata = Revise.pkgdatas[id]
                    add_by_file_line(pkgdata, relpath(file, pkgdata), sf.line) && continue
                end
            end
            # Try all loaded packages
            for (id, pkgdata) in Revise.pkgdatas
                if hasfile(pkgdata, file)
                    add_by_file_line(pkgdata, relpath(file, pkgdata), sf.line) && break
                end
            end
        end
    end
    return usrtrace, defs
end

"""
    uuids = capture_stacktrace(mod, command)

Execute `command` in module `mod`. `command` must throw an error.
Then instrument the methods in the stacktrace so that their input
variables are stored in `Rebugger.stored`.
After storing the inputs, restore the original methods.

Since this requires two `eval`s of `command`, usage should be limited to
deterministic expressions that always result in the same call chain.
"""
function capture_stacktrace(mod::Module, command::Expr)
    errored = true
    trace = try
        Core.eval(mod, command)
        errored = false
    catch
        stacktrace(catch_backtrace())
    end
    errored || error("$command did not throw an error")
    usrtrace, defs = pregenerated_stacktrace(trace)
    # Eliminate duplicates. Keyword-funcs and default-arg funcs can return the same def
    i = 1
    while i < length(defs)
        if defs[i] == defs[i+1]
            deleteat!(defs, i)
            deleteat!(usrtrace, i)
        else
            i += 1
        end
    end
    isempty(usrtrace) && error("failed to capture any elements of the stacktrace")
    println(stderr, "Captured elements of stacktrace:")
    show(stderr, MIME("text/plain"), usrtrace)
    length(unique(usrtrace)) == length(usrtrace) || @error "the same method appeared twice, not supported. Try stepping into the command."
    uuids = UUID[]
    capture_stacktrace!(uuids, usrtrace, defs) do
        Core.eval(mod, command)
    end
    uuids
end
capture_stacktrace(command::Expr) = capture_stacktrace(Main, command)

function capture_stacktrace!(f::Function, uuids::Vector, usrtrace, defs)
    if isempty(usrtrace)
        # We've finished modifying all the methods, time to run the command
        try
            f()
            @warn "traced method did not throw an error"
        catch
        end
        return
    end
    method, def = usrtrace[end], defs[end]
    if def !== nothing
        uuid = method_capture_from_callee(method, def; overwrite=true)
        push!(uuids, uuid)
    end
    # Recurse up the stack until we get to the top...
    pop!(usrtrace)
    pop!(defs)
    capture_stacktrace!(f, uuids, usrtrace, defs)
    # ...after which it will call the erroring function and come back here.
    # Having come back, restore the original definition
    if def != nothing
        eval_noinfo(method.module, def)  # unfortunately this doesn't restore the original method as a viable key to storemap
    end
    return uuids
end

### Stepping

function stepin(io)
    @assert Rebugger.stashed[] === nothing
    # Step 1: rewrite the command to stash the call function and its arguments.
    prepare_caller_capture!(io)
    capexpr, stop = Meta.parse(content(io), 1)
    try
        Core.eval(Main, capexpr)
        throw(StashingFailed())
    catch err
        err isa StashingFailed && rethrow(err)
        if !(err isa StopException)
            throw(EvalException(content(io), err))
        end
    end
    f, args, kwargs = Rebugger.stashed[]
    Rebugger.stashed[] = nothing
    # Step 2: determine which method is called, and if need be create a function
    # that captures all of the callee's inputs. (This allows us to capture default arguments,
    # keyword arguments, and type parameters.)
    method = which(f, Base.typesof(args...))
    uuid = get(storemap, (method, false), nothing)
    if uuid === nothing
        uuid = method_capture_from_callee(method; overwrite=false)
    end
    # Step 3: execute the command to store the inputs.
    fcapture = storefunc[uuid]
    tv, decls = Base.arg_decl_parts(method)
    if !isempty(decls[1][1])
        # This is a call-overloaded method, prepend the calling object
        args = (f, args...)
    end
    try
        Base.invokelatest(fcapture, args...; kwargs...)
        throw(StorageFailed())   # this should never happen
    catch err
        err isa StopException || rethrow(err)
    end
    return uuid, generate_let_command(method, uuid)
end

"""
    callexpr = prepare_caller_capture!(io)

Given a buffer `io` representing a string and "point" (the seek position) set at a call expression,
replace the call with one that stashes the function and arguments of the call.

For example, if `io` has contents

    <some code>
    if x > 0.5
        ^fcomplex(x, 2; kw1=1.1)
        <more code>

where in the above `^` indicates `position(s)` ("point"), rewrite this as

    <some code>
    if x > 0.5
        Main.Rebugger.stashed[] = (fcomplex, (x, 2), (kw1=1.1,))
        throw(Rebugger.StopException())
        <more code>

(Keyword arguments do not affect dispatch and hence are not stashed.)
Consequently, if this is `eval`ed and execution reaches "^", it causes the arguments
of the call to be placed in `Rebugger.stashed`.

`callexpr` is the original (unmodified) expression specifying the call, i.e.,
`fcomplex(x, 2; kw1=1.1)` in this case.

This does the buffer-preparation for *caller* capture.
For *callee* capture, see [`method_capture_from_callee`](@ref),
and [`stepin`](@ref) which puts these two together.
"""
function prepare_caller_capture!(io)  # for testing, needs to work on a normal IO object
    start = position(io)
    callstring, _ = stripsc(content(io, start=>bufend(io)))
    callexpr, len = Meta.parse(callstring, 1; raise=false)
    callexpr == nothing && throw(StepException("Got empty expression from $callstring"))
    isa(callexpr, Expr) || throw(StepException("Rebugger can only step into expressions, got $callexpr"))
    if callexpr.head == :error
        iend = len
        for i = 1:2
            iend = prevind(callstring, iend)
        end
        callstring = callstring[1:iend]
        callexpr, len = Meta.parse(callstring, 1)
    end
    if callexpr.head == :tuple && !(startswith(callstring, "tuple") || startswith(callstring, "("))
        # An expression like foo(bar(x)..., 1) where point is positioned at bar
        callexpr = callexpr.args[1]
    end
    if callexpr.head == :ref
        callexpr = Expr(:call, :getindex, callexpr.args...)
    elseif callexpr.head == :(=) && isa(callexpr.args[1], Expr) && callexpr.args[1].head == :ref
        ref, val = callexpr.args
        callexpr = Expr(:call, :setindex!, ref.args[1], val, ref.args[2:end]...)
    elseif (callexpr.head == :&& || callexpr.head == :||) && isa(callexpr.args[1], Expr)
        callexpr = callexpr.args[1]
    elseif callexpr.head == :...
        callexpr = callexpr.args[1]
    elseif callexpr.head == :do
        callexpr = Expr(
            :call,
            callexpr.args[1].args[1],         # function name
            callexpr.args[2],                 # do block (anonymous function)
            callexpr.args[1].args[2:end]...)  # other arguments
    end
    # Must be a call or broadcast
    ((callexpr.head == :call) | (callexpr.head == :.)) || throw(Meta.ParseError("point must be at a call expression, got $callexpr"))
    if callexpr.head == :call
        fname, args = callexpr.args[1], callexpr.args[2:end]
    else
        fname, args = :broadcast, [callexpr.args[1], callexpr.args[2].args...]
    end
    # In the edited callstring separate any kwargs now. They don't affect dispatch.
    kwargs = []
    if length(args) >= 1 && isa(args[1], Expr) && args[1].head == :parameters
        # foo(x; kw1=1, ...) syntax    (with the semicolon)
        append!(kwargs, popfirst!(args).args)
    end
    while !isempty(args) && isa(args[end], Expr) && args[end].head == :kw
        # foo(x, kw1=1, ...) syntax    (with a comma, no semicolon)
        push!(kwargs, pop!(args))
    end
    captureexpr = quote
        Main.Rebugger.stashed[] = ($fname, (($(args...)),), Main.Rebugger.kwstasher(; $(kwargs...)))
        throw(StopException())
    end
    # Now insert this in place of the marked call
    # Unfortunately we have to convert to a string and there are scoping issues
    capturestr = string(captureexpr, '\n')
    regexunscoped = r"(?<!\.)StopException"
    capturestr = replace(capturestr, regexunscoped=>(s->"Rebugger."*s))
    regexscoped   = r"(?<!\.)Rebugger\.StopException"
    capturestr = replace(capturestr, regexscoped=>(s->"Main."*s))
    edit_splice!(io, start=>start+len-1, capturestr)
    return callexpr
end

"""
    uuid = method_capture_from_callee(method; overwrite::Bool=false)

Create a version of `method` that stores its inputs in `Main.Rebugger.stored`.
For a method

    function fcomplex(x::A, y=1, z=""; kw1=3.2) where A<:AbstractArray{T} where T
        <body>
    end

if `overwrite=false`, this generates a new method

    function hidden_fcomplex(x::A, y=1, z=""; kw1=3.2) where A<:AbstractArray{T} where T
        Main.Rebugger.stored[uuid] = Main.Rebugger.Stored(fcomplex, (:x, :y, :z, :kw1, :A, :T), deepcopy((x, y, z, kw1, A, T)))
        throw(StopException())
    end

(If a `uuid` already exists for `method` from a previous call to `method_capture_from_callee`,
it will simply be returned.)

With `overwrite=true`, there are two differences:

- it replaces `fcomplex` rather than defining `hidden_fcomplex`
- rather than throwing `StopException`, it re-inserts `<body>` after the line performing storage

The returned `uuid` can be used for accessing the stored data.
"""
function method_capture_from_callee(method, def; overwrite::Bool=false)
    uuid = get(storemap, (method, overwrite), nothing)
    uuid != nothing && return uuid
    def = pop_annotations(def)
    sigr, body = def.args[1], def.args[2]
    sigr == nothing && throw(SignatureError(method))
    sigex = convert(Expr, sigr)
    if sigex.head == :(::)
        sigex = sigex.args[1]  # return type declaration
    end
    methname, argnames, kwnames, paramnames = signature_names!(sigex)
    # Check for call-overloading method, e.g., (obj::ObjType)(x, y...) = <body>
    callerobj = nothing
    if methname isa Expr && methname.head == :(::)
        @assert length(methname.args) == 2
        callerobj = methname
        argnames = (methname.args[1], argnames...)
        methname = methname.args[2]
        if methname isa Expr
            if methname.head == :curly
                methname = methname.args[1]
            else
                dump(methname)
                error("unexpected call-overloading type")
            end
        end
    end
    allnames = (argnames..., kwnames..., paramnames...)
    qallnames = QuoteNode.(allnames)
    uuid = uuid1()
    uuidstr = string(uuid)
    makepair = Pair  # Needed for debugging into Core.Compiler, which has its own Pair
    storeexpr = :(Main.Rebugger.setstored!($makepair($uuidstr, Main.Rebugger.Stored($method, ($(qallnames...),), ($(allnames...),)) ) ))
    capture_body = overwrite ? quote
        $storeexpr
        $body
    end : quote
        $storeexpr
        throw(Main.Rebugger.StopException())
    end
    capture_name = try
        _gensym(methname)
    catch
        nothing
    end
    capture_name == nothing && (dump(methname); dump(sigex); error("couldn't gensym"))
    # capture_name = _gensym(methname)
    mod = method.module
    head = def.head == :(=) ? :function : def.head  # allows us to intercept macros
    capture_function = Expr(head, overwrite ? sigex : rename_method(sigex, capture_name, callerobj), capture_body)
    result = Core.eval(mod, capture_function)
    if !overwrite
        storefunc[uuid] = result
    end
    storemap[(method, overwrite)] = uuid
    return uuid
end
function method_capture_from_callee(method; kwargs...)
    Revise.get_def(method; modified_files=typeof(Revise.revision_queue)())  # update while ignoring mtime issues here
    def = definition(method)
    def == nothing && throw(DefMissing(method, nothing))
    method_capture_from_callee(method, def; kwargs...)
end

function generate_let_command(method::Method, uuid::UUID)
    s = stored[uuid]
    @assert method == s.method
    argstring = '(' * join(s.varnames, ", ") * (length(s.varnames)==1 ? ",)" : ')')
    Revise.get_def(method; modified_files=String[])  # to avoid mtime updates
    body = convert(Expr, striplines!(unwrap(definition(method)).args[end]))
    return """
        @eval $(method.module) let $argstring = Main.Rebugger.getstored(\"$uuid\")
        $body
        end"""
end
function generate_let_command(uuid::UUID)
    s = stored[uuid]
    generate_let_command(s.method, uuid)
end

"""
    args_and_types = Rebugger.getstored(uuid)

Retrieve the values of stored arguments and type-parameters from the store specified
`uuid`. This makes a copy of values, so as to be safe for repeated execution of methods
that modify their inputs.
"""
getstored(uuidstr::AbstractString) = safe_deepcopy(Main.Rebugger.stored[UUID(uuidstr)].varvals...)

function setstored!(p::Pair{S,Stored}) where S<:AbstractString
    uuidstr, val = p.first, p.second
    Main.Rebugger.stored[UUID(uuidstr)] = val
end

kwstasher(; kwargs...) = kwargs

### Utilities

"""
    fname, argnames, kwnames, parameternames = signature_names!(sigex::Expr)

Return the function name `fname` and names given to its arguments, keyword arguments,
and parameters, as specified by the method signature-expression `sigex`.

`sigex` will be modified if some of the arguments are unnamed.


# Examples

```jldoctest; setup=:(using Rebugger)
julia> Rebugger.signature_names!(:(complexargs(w::Ref{A}, @nospecialize(x::Integer), y, z::String=""; kwarg::Bool=false, kw2::String="", kwargs...) where A <: AbstractArray{T,N} where {T,N}))
(:complexargs, (:w, :x, :y, :z), (:kwarg, :kw2, :kwargs), (:A, :T, :N))

julia> ex = :(myzero(::Float64));     # unnamed argument

julia> Rebugger.signature_names!(ex)
(:myzero, (:__Float64_1,), (), ())

julia> ex
:(myzero(__Float64_1::Float64))
```
"""
function signature_names!(sigex::Expr)
    # TODO: add parameter names
    argname(s::Symbol) = s
    function argname(ex::Expr)
        if ex.head == :(...) && length(ex.args) == 1
            # varargs function
            ex = ex.args[1]
            ex isa Symbol && return ex
        end
        (ex.head == :macrocall || ex.head == :meta) && return argname(ex.args[end])  # @nospecialize
        ex.head == :kw && return argname(ex.args[1])  # default arguments
        ex.head == :tuple && return ex    # tuple-destructuring argument
        ex.head == :(::) || throw(ArgumentError(string("expected :(::) expression, got ", ex)))
        arg = ex.args[1]
        if length(ex.args) == 1 && (arg isa Symbol)
            # This argument has a type but no name
            return arg, true
        end
        if isa(arg, Expr) && arg.head == :curly
            if arg.args[1] == :Type
                # Argument of the form ::Type{T}
                return arg.args[2], false
            elseif arg.args[1] == :NamedTuple
                return :NamedTuple, true, arg
            end
        end
        return arg
    end
    paramname(s::Symbol) = s
    function paramname(ex::Expr)
        ex.head == :(<:) && return paramname(ex.args[1])
        throw(ArgumentError(string("expected parameter expression, got ", ex)))
    end

    kwnames, parameternames = (), []
    while sigex.head == :where
        parameternames = [paramname.(sigex.args[2:end])..., parameternames...]
        sigex = sigex.args[1]
    end
    name = sigex.args[1]
    offset = 1
    if length(sigex.args) > 1 && isa(sigex.args[2], Expr) && sigex.args[2].head == :parameters
        # keyword arguments
        kwnames = tuple(argname.(sigex.args[2].args)...)
        offset += 1
    end
    # Argnames. For any unnamed arguments we have to generate a name.
    empty!(usdict)
    argnames = Union{Symbol,Expr}[]
    for i = offset+1:length(sigex.args)
        arg = sigex.args[i]
        retname = argname(arg)
        if retname isa Tuple
            should_gen = retname[2]
            if should_gen
                # This argument is missing a real name
                argt = length(retname) == 3 ? retname[3] : retname[1]
                name = genunderscored(retname[1])
                sigex.args[i] = :($name::$argt)
            else
                # This is a ::Type{T} argument. We should remove this from the list of parameters
                name = retname[1]
                parameternames = filter(!isequal(name), parameternames)
            end
            retname = name
        end
        push!(argnames, retname)
    end

    return sigex.args[1], tuple(argnames...), kwnames, tuple(parameternames...)
end

function rename_method!(sig::Expr, name::Symbol, callerobj)
    ex = sig
    while isa(sig, Expr) && sig.head == :where
        sig = sig.args[1]
    end
    sig.head == :call || (dump(sig); throw(ArgumentError(string("expected call expression, got ", sig))))
    sig.args[1] = name
    if callerobj != nothing
        # Call overloading, add an argument
        sig.args = [sig.args[1]; callerobj; sig.args[2:end]]
    end
    return ex
end
rename_method(sig::Expr, name::Symbol, callerobj) = rename_method!(copy(sig), name, callerobj)

function stripsc(str)
    str = chomp(str)
    display_result = false
    if endswith(str, ';')
        str = str[1:end-1]
        display_result = true
    end
    return str, display_result
end

const poppable_macro = (Symbol("@inline"), Symbol("@noinline"), Symbol("@propagate_inbounds"), Symbol("@eval"), Symbol("@pure"))
is_poppable_macro(ex) = ex ∈ poppable_macro ||
    (ex isa Expr && ex.head == :. && ex.args[1] == :Base && ex.args[2].value ∈ poppable_macro)

function pop_annotations(def::Expr)
    def = unwrap(def)
    while def isa Expr && def.head == :macrocall && is_poppable_macro(def.args[1])
        def = def.args[end]
        def = unwrap(def)
    end
    def
end

# Use to re-evaluate an expression without leaving "breadcrumbs" about where
# the eval is coming from. This is used below to prevent the re-evaluaton of an
# original method from being attributed to Rebugger itself in future backtraces.
eval_noinfo(mod::Module, ex::Expr) = ccall(:jl_toplevel_eval, Any, (Any, Any), mod, ex)
eval_noinfo(mod::Module, rex::RelocatableExpr) = eval_noinfo(mod, convert(Expr, rex))

function unquote(ex::Expr)
    if ex.head == :quote
        return Expr(:block, ex.args...)
    end
    ex
end
unquote(rex::RelocatableExpr) = unquote(convert(Expr, rex))

_gensym(sym::Symbol) = gensym(sym)
_gensym(q::QuoteNode) = _gensym(q.value)
_gensym(ex::Expr) = (@assert ex.head == :. && length(ex.args) == 2; _gensym(ex.args[2]))

const usdict = Dict{Symbol,Int}()
function genunderscored(sym::Symbol)
    n = get(usdict, sym, 0) + 1
    usdict[sym] = n
    return Symbol("__"*String(sym)*'_'*string(n))
end

const notrace = (:error, :throw)


================================================
FILE: src/deepcopy.jl
================================================
# Because `deepcopy(mod::Module)` throws an error, we need a safe approach.
# Strategy: wrap the IdDict so that our methods get called rather than Base's.
# It's not guaranteed to work for user types that specialize `deepcopy_internal`,
# but hopefully that's rare.

struct WrappedIdDict
    dict::IdDict
end
Base.getindex(w::WrappedIdDict, key) = w.dict[key]
Base.setindex!(w::WrappedIdDict, val, key) = w.dict[key] = val
Base.haskey(w::WrappedIdDict, k) = haskey(w.dict, k)

function safe_deepcopy(a, args...)
    stackdict = WrappedIdDict(IdDict())
    _safe_deepcopy(stackdict, a, args...)
end
safe_deepcopy() = ()
_safe_deepcopy(stackdict, a, args...) =
    (Base.deepcopy_internal(a, stackdict), _safe_deepcopy(stackdict, args...)...)
_safe_deepcopy(stackdict) = ()

# This is the one method we want to override
Base.deepcopy_internal(x::Module, stackdict::WrappedIdDict) = x

# But the rest are necessary to make it work. This is just a direct copy from Base.
Base.deepcopy_internal(x::Union{Symbol,Core.MethodInstance,Method,GlobalRef,DataType,Union,Task},
                  stackdict::WrappedIdDict) = x
Base.deepcopy_internal(x::Tuple, stackdict::WrappedIdDict) =
    ntuple(i->Base.deepcopy_internal(x[i], stackdict), length(x))

function Base.deepcopy_internal(x::Base.SimpleVector, stackdict::WrappedIdDict)
    if haskey(stackdict, x)
        return stackdict[x]
    end
    y = Core.svec(Any[Base.deepcopy_internal(x[i], stackdict) for i = 1:length(x)]...)
    stackdict[x] = y
    return y
end

function Base.deepcopy_internal(x::String, stackdict::WrappedIdDict)
    if haskey(stackdict, x)
        return stackdict[x]
    end
    y = GC.@preserve x unsafe_string(pointer(x), sizeof(x))
    stackdict[x] = y
    return y
end

function Base.deepcopy_internal(@nospecialize(x), stackdict::WrappedIdDict)
    T = typeof(x)::DataType
    nf = nfields(x)
    (isbitstype(T) || nf == 0) && return x
    if haskey(stackdict, x)
        return stackdict[x]
    end
    if T.mutable
        y = ccall(:jl_new_struct_uninit, Any, (Any,), T)
        stackdict[x] = y
        for i in 1:nf
            if isdefined(x,i)
                ccall(:jl_set_nth_field, Cvoid, (Any, Csize_t, Any), y, i-1,
                      Base.deepcopy_internal(getfield(x,i), stackdict))
            end
        end
    else
        flds = Vector{Any}(undef, nf)
        for i in 1:nf
            if isdefined(x, i)
                xi = getfield(x, i)
                xi = Base.deepcopy_internal(xi, stackdict)::typeof(xi)
                flds[i] = xi
            else
                nf = i - 1 # rest of tail must be undefined values
                break
            end
        end
        y = ccall(:jl_new_structv, Any, (Any, Ptr{Any}, UInt32), T, flds, nf)
    end
    return y::T
end

function Base.deepcopy_internal(x::Array, stackdict::WrappedIdDict)
    if haskey(stackdict, x)
        return stackdict[x]
    end
    _deepcopy_array_t(x, eltype(x), stackdict)
end

function _deepcopy_array_t(@nospecialize(x), T, stackdict::WrappedIdDict)
    if isbitstype(T)
        return (stackdict[x]=copy(x))
    end
    dest = similar(x)
    stackdict[x] = dest
    for i = 1:(length(x)::Int)
        if ccall(:jl_array_isassigned, Cint, (Any, Csize_t), x, i-1) != 0
            xi = ccall(:jl_arrayref, Any, (Any, Csize_t), x, i-1)
            if !isbits(xi)
                xi = Base.deepcopy_internal(xi, stackdict)
            end
            ccall(:jl_arrayset, Cvoid, (Any, Any, Csize_t), dest, xi, i-1)
        end
    end
    return dest
end

function Base.deepcopy_internal(x::Union{Dict,IdDict}, stackdict::WrappedIdDict)
    if haskey(stackdict, x)
        return stackdict[x]::typeof(x)
    end

    if isbitstype(eltype(x))
        return (stackdict[x] = copy(x))
    end

    dest = empty(x)
    stackdict[x] = dest
    for (k, v) in x
        dest[Base.deepcopy_internal(k, stackdict)] = Base.deepcopy_internal(v, stackdict)
    end
    dest
end


================================================
FILE: src/precompile.jl
================================================
function _precompile_()
    ccall(:jl_generating_output, Cint, ()) == 1 || return nothing
    precompile(Tuple{typeof(get_rebugger_modeswitch_dict)})
    precompile(Tuple{typeof(rebugrepl_init)})
end


================================================
FILE: src/printing.jl
================================================
# A type for keeping track of the current line number when printing Exprs
struct LineNumberIO <: IO
    io::IO
    linenos::Vector{Union{Missing,Int}} # source line number for each printed line of the Expr
    file::Symbol   # used to avoid confusion when we are in expanded macros
end

LineNumberIO(io::IO, line::Integer, file::Symbol) = LineNumberIO(io, Union{Missing,Int}[line], file)
LineNumberIO(io::IO, line::Integer, file::AbstractString) = LineNumberIO(io, line, Symbol(file))

const LNIO = Union{LineNumberIO, IOContext{LineNumberIO}}

# Instead of printing the source line number to `io.io`, associate it with the
# corresponding line of the printout
function Base.show_linenumber(io::LineNumberIO, line, file)
    if file == io.file
        # Count how many newlines we've encountered
        data = io.io.data
        nlines = count(isequal(UInt8('\n')), data) + 1 # TODO: O(N^2), optimize? See below
        # If there have been more printed lines than assigned line numbers, fill
        # with `missing`
        while nlines > length(io.linenos)
            push!(io.linenos, missing)
        end
        # Record this line number
        io.linenos[nlines] = line
    end
    return nothing
end
Base.show_linenumber(io::LNIO, line, file) = Base.show_linenumber(io.io, line, file)
Base.show_linenumber(io::LNIO, line, ::Nothing) = nothing
Base.show_linenumber(io::LNIO, line) = nothing

# TODO? intercept `\n` here and break the result up into lines at writing time?
Base.write(io::LineNumberIO, x::UInt8) = write(io.io, x)

# See docstring below
function expression_lines(method::Method)
    def = definition(method)
    if def === nothing
        # If the expression is not available, use the source text. This happens for methods in e.g., boot.jl.
        src, line1 = definition(String, method)
        methstrings = split(chomp(src), '\n')
        return Vector(range(Int(line1), length=length(methstrings))), line1, methstrings
    end
    def = unwrap(def)
    # We'll use the file in LineNumberNodes to make sure line numbers refer to the "outer"
    # method (and does not get confused by macros etc). Because of symlinks and non-normalized paths,
    # it's more reliable to grab the first LNN for the template filename than to use method.file.
    lnn = findline(def)
    mfile = lnn === nothing ? method.file : lnn.file
    buf = IOBuffer()
    io = LineNumberIO(buf, method.line, mfile) # deliberately using the in-method numbering
    print(io, def)
    seek(buf, 0)
    methstrings = readlines(buf)
    linenos = io.linenos
    while length(linenos) < length(methstrings)
        push!(linenos, missing)
    end
    if startswith(methstrings[1], ":(")
        # Chop off the Expr-quotes from the printing
        methstrings[1] = methstrings[1][3:end]
        methstrings[end] = methstrings[end][1:end-1]
    end
    # If it prints with `function`, adjust numbering for the signature line
    # Note that this works independently of whether it's written as an `=` method
    # in the source code (the contents of that line may not be the same but that's
    # true quite generally)
    startswith(methstrings[1], "function") && (linenos[1] -= 1)
    @assert issorted(skipmissing(linenos))
    # Strip out blank lines from the printed expression
    # These arise from the fact that we intercepted the printing of LineNumberNodes
    keeplinenos, keepstrings = Union{Missing,Int}[], String[]
    for (i, line) in enumerate(methstrings)
        if !all(isspace, line)
            push!(keepstrings, line)
            ln = linenos[i]
            if ismissing(ln) && i > 1
                # Line numbers get associated with the missing LineNumberNodes rather than
                # the succeeding expression line. Thus we look to the previous entry.
                # Deliberately go back only one entry.
                ln = linenos[i-1]
            end
            push!(keeplinenos, ln)
        end
    end
    linenos, methstrings = keeplinenos, keepstrings
    # Fill in missing lines where possible. There is no line info for things like
    # `end`, `catch` and similar; these are subsumed by the block structure.
    # So we try to assign line numbers to these missing elements.
    # However, source text like
    #   for i = 1:5 s += i end
    # and
    #   for i = 1:5 s += i
    #   end
    # and
    #   for i = 1:5
    #       s +=i
    #   end
    # all get printed in the latter format---so the matching will work only under
    # certain conditions.
    lastknown = 1
    for i = 2:length(linenos)-1
        if ismissing(linenos[i])
            if !ismissing(linenos[i+1])
                # Process the previous block of missing statements
                # If the printout has advanced by the same number of lines as the source,
                # we know what the answer must be.
                Δidx = i+1 - lastknown
                Δsrc = linenos[i+1] - linenos[lastknown]
                if Δsrc == Δidx
                    for j = lastknown+1:i
                        linenos[j] = linenos[j-1] + 1
                    end
                end
            end
        else
            lastknown = i
        end
    end
    _, line1 = whereis(method)
    return linenos, line1, keepstrings
end

"""
    linenos, line1, methstrings = expression_lines(frame)

Compute the source lines associated with each printed line of the expression
associated with the method executed in `frame`.
`methstrings` is a vector of strings, one per line of the expression.
`linenos` is a vector in 1-to-1 correspondence with `methstrings`,
 containing either the *compiled* line number or `missing`
if the line number is not available. `line1` contains the *actual* (current) line
number of the first line of the method body.
"""
function expression_lines(frame::Frame)
    m = scopeof(frame)
    mlinenos, line1, msrc = expression_lines(m)
    isdefined(m, :generator) || return mlinenos, line1, msrc
    # The rest of this is specific to generated functions.
    # Call the generator to get the expression. First we have to build up the arguments.
    g = m.generator
    gg = g.gen
    vars = JuliaInterpreter.locals(frame)
    ggargs = []
    # Static parameters come first
    for v in vars
        v.isparam || continue
        push!(ggargs, v.value)
    end
    # Slots are next. Naturally, the generator takes only their types, not their values.
    for v in vars
        v.isparam && continue
        push!(ggargs, JuliaInterpreter._Typeof(v.value))
    end
    # Call the generator
    def = gg(ggargs...)
    # `def` contains just the body. Wrap in a `function` to ensure proper indentation,
    # and then print it.
    def = Expr(:function, :(generatedtmp()), def)
    buf = IOBuffer()
    print(buf, def)  # there are no linenos, so no need for LineNumberIO
    seek(buf, 0)
    glines = readlines(buf)
    # Extract the signature line from `msrc`, and paste the body in
    gsrc = [msrc[1]; glines[2:end]]
    # Assign line numbers. Other than the first line, there *aren't* any, so use `missing`
    linenos = [mlinenos[1]; fill(missing, length(gsrc)-1)]
    return linenos, line1, gsrc
end

function show_code(term, frame, deflines, nlines)
    width = displaysize(term)[2]
    method = scopeof(frame)
    linenos, line1, showlines = deflines   # linenos is in "compiled" numbering, line1 in "current" numbering
    offset = line1 - method.line           # compiled + offset -> current
    known_linenos = skipmissing(linenos)
    nd = isempty(known_linenos) ? 0 : ndigits(offset + maximum(known_linenos))
    line = linenumber_unexpanded(frame)    # this is in "compiled" numbering
    # lineidx = searchsortedfirst(linenos, line)
    # Can't use searchsortedfirst with missing
    lineidx = 0
    for (i, l) in enumerate(linenos)
        if !ismissing(l) && l >= line
            lineidx = i
            break
        end
    end
    idxrange = max(1, lineidx-2):min(length(linenos), lineidx+2)
    iochar = IOBuffer()
    for idx in idxrange
        thisline, codestr = linenos[idx], showlines[idx]
        print(term, breakpoint_style(frame.framecode, thisline))
        if ismissing(thisline)
            print(term, " "^nd)
        else
            linestr = lpad(thisline + offset, nd)
            printstyled(term, linestr; color = thisline==line ? Base.warn_color() : :normal)
        end
        linestr = linetrunc(iochar, codestr, width-nd-3)
        print(term, "  ", linestr, '\n')
    end
    return length(idxrange)
end

# Limit output to a single line
function linetrunc(iochar::IO, linestr, width)
    nchars = 0
    for c in linestr
        if nchars == width-2
            print(iochar, '…')
            break
        else
            print(iochar, c)
        end
        nchars += 1
    end
    return String(take!(iochar))
end
linetrunc(linestr, width) = linetrunc(IOBuffer(), linestr, width)

function breakpoint_style(framecode, thisline)
    rng = coderange(framecode, thisline)
    style = ' '
    breakpoints = framecode.breakpoints
    for i in rng
        if isassigned(breakpoints, i)
            bps = breakpoints[i]
            if !bps.isactive
                if bps.condition === JuliaInterpreter.falsecondition  # removed
                else
                    style = style == ' ' ? 'd' : 'm'   # disabled
                end
            else
                if bps.condition === JuliaInterpreter.truecondition
                    style = style == ' ' ? 'b' : 'm'   # unconditional
                else
                    style = style == ' ' ? 'c' : 'm'   # conditional
                end
            end
        end
    end
    return style
end
breakpoint_style(framecode, ::Missing) = ' '

### Header display

function HeaderREPLs.print_header(io::IO, header::RebugHeader)
    if header.nlines != 0
        HeaderREPLs.clear_header_area(io, header)
    end
    iocount = IOBuffer()  # for counting lines
    for s in (io, iocount)
        if !isempty(header.warnmsg)
            printstyled(s, header.warnmsg, '\n'; color=Base.warn_color())
        end
        if !isempty(header.errmsg)
            printstyled(s, header.errmsg, '\n'; color=Base.error_color())
        end
        if header.current_method != dummymethod
            printstyled(s, header.current_method, '\n'; color=:light_magenta)
        end
        if header.uuid != dummyuuid
            data = stored[header.uuid]
            ds = displaysize(io)
            printer(args...) = printstyled(args..., '\n'; color=:light_blue)
            for (name, val) in zip(data.varnames, data.varvals)
                # Make sure each only spans one line
                if val === nothing
                    val = "nothing"
                end
                try
                    printf_maxsize(printer, s, "  ", name, " = ", val; maxlines=1, maxchars=ds[2]-1)
                catch # don't error just because a print method is borked
                    printstyled(s, "  ", name, " errors in its show method"; color=:red)
                end
            end
        end
    end
    header.nlines = count_display_lines(iocount, displaysize(io))
    header.warnmsg = ""
    header.errmsg = ""
    return nothing
end

function HeaderREPLs.print_header(io::IO, header::InterpretHeader)
    if header.nlines != 0
        HeaderREPLs.clear_header_area(io, header)
    end
    header.frame == nothing && return nothing
    frame, Δ = frameoffset(header.frame, header.leveloffset)
    header.leveloffset -= Δ
    frame === nothing && return nothing
    iocount = IOBuffer()  # for counting lines
    for s in (io, iocount)
        ds = displaysize(io)
        printer(args...) = printstyled(args..., '\n'; color=:light_blue)
        if !isempty(header.warnmsg)
            printstyled(s, header.warnmsg, '\n'; color=Base.warn_color())
        end
        if !isempty(header.errmsg)
            printstyled(s, header.errmsg, '\n'; color=Base.error_color())
        end
        indent = ""
        f = root(frame)
        while f !== nothing
            scope = scopeof(f)
            if f === frame
                printstyled(s, indent, scope, '\n'; color=:light_magenta, bold=true)
            else
                printstyled(s, indent, scope, '\n'; color=:light_magenta)
            end
            indent *= ' '
            f = f.callee
        end
        for (i, var) in enumerate(JuliaInterpreter.locals(frame))
            name, val = var.name, var.value
            name == Symbol("#self#") && (isa(val, Type) || sizeof(val) == 0) && continue
            name == Symbol("") && (name = "@_" * string(i))
            if val === nothing
                val = "nothing"
            end
            try
                printf_maxsize(printer, s, "  ", name, " = ", val; maxlines=1, maxchars=ds[2]-1)
            catch # don't error just because a print method is borked
                printstyled(s, "  ", name, " errors in its show method"; color=:red)
            end
        end
    end
    header.nlines = count_display_lines(iocount, displaysize(io))
    header.warnmsg = ""
    header.errmsg = ""
    return nothing
end

function frameoffset(frame, offset)
    while offset > 0
        cframe = frame.caller
        cframe === nothing && break
        frame = cframe
        offset -= 1
    end
    return frame, offset
end

function linenumber_unexpanded(frame)
    framecode, pc = frame.framecode, frame.pc
    scope = framecode.scope::Method
    codeloc = JuliaInterpreter.codelocation(framecode.src, pc)
    codeloc == 0 && return nothing
    lineinfo = framecode.src.linetable[codeloc]
    while lineinfo.file != scope.file && codeloc > 0
        codeloc -= 1
        lineinfo = framecode.src.linetable[codeloc]
    end
    return JuliaInterpreter.getline(lineinfo)
end


================================================
FILE: src/ui.jl
================================================
const rebug_prompt_string = "rebug> "
const rebug_prompt_ref = Ref{Union{LineEdit.Prompt,Nothing}}(nothing)       # set by __init__
const interpret_prompt_ref = Ref{Union{LineEdit.Prompt,Nothing}}(nothing)   # set by __init__

# For debugging
function logaction(msg)
    open("/tmp/rebugger.log", "a") do io
        println(io, "* ", msg)
        flush(io)
    end
    nothing
end

dummy() = nothing
const dummymethod = first(methods(dummy))
const dummyuuid   = UUID(UInt128(0))

uuidextractor(str) = match(r"getstored\(\"([a-z0-9\-]+)\"\)", str)

mutable struct RebugHeader <: AbstractHeader
    warnmsg::String
    errmsg::String
    uuid::UUID
    current_method::Method
    nlines::Int   # size of the printed header
end
RebugHeader() = RebugHeader("", "", dummyuuid, dummymethod, 0)

mutable struct InterpretHeader <: AbstractHeader
    frame::Union{Nothing,Frame}
    leveloffset::Int
    val
    bt
    warnmsg::String
    errmsg::String
    nlines::Int   # size of the printed header
end
InterpretHeader() = InterpretHeader(nothing, 0, nothing, nothing, "", "", 0)

struct DummyAST end  # fictive input for the put!/take! evaluation by the InterpretREPL backend

header(s::LineEdit.MIState) = header(mode(s).repl)
header(repl::HeaderREPL) = repl.header
header(::LineEditREPL) = header(rebug_prompt_ref[].repl)  # default to the Rebug header

# Custom methods
set_method!(header::RebugHeader, method::Method) = header.current_method = method

function set_uuid!(header::RebugHeader, uuid::UUID)
    if haskey(stored, uuid)
        header.uuid = uuid
        header.current_method = stored[uuid].method
    else
        header.uuid = dummyuuid
        header.current_method = dummymethod
    end
    uuid
end

function set_uuid!(header::RebugHeader, str::AbstractString)
    m = uuidextractor(str)
    uuid = if m isa RegexMatch && length(m.captures) == 1
        UUID(m.captures[1])
    else
        dummyuuid
    end
    set_uuid!(header, uuid)
end

struct FakePrompt{Buf<:IO}
    input_buffer::Buf
end
LineEdit.mode(p::FakePrompt) = rebug_prompt_ref[]

"""
    stepin(s)

Given a buffer `s` representing a string and "point" (the seek position) set at a call expression,
replace the contents of the buffer with a `let` expression that wraps the *body* of the callee.

For example, if `s` has contents

    <some code>
    if x > 0.5
        ^fcomplex(x)
        <more code>

where in the above `^` indicates `position(s)` ("point"), and if the definition of `fcomplex` is

    function fcomplex(x::A, y=1, z=""; kw1=3.2) where A<:AbstractArray{T} where T
        <body>
    end

rewrite `s` so that its contents are

    @eval ModuleOf_fcomplex let (x, y, z, kw1, A, T) = Main.Rebugger.getstored(id)
        <body>
    end

where `Rebugger.getstored` returns has been pre-loaded with the values that would have been
set when you called `fcomplex(x)` in `s` above.
This line can be edited and `eval`ed at the REPL to analyze or improve `fcomplex`,
or can be used for further `stepin` calls.
"""
function stepin(s::LineEdit.MIState)
    # Add the command we're tracing to the history. That way we can go "up the call stack".
    pos = position(s)
    cmd = String(take!(copy(LineEdit.buffer(s))))
    add_history(s, cmd)
    # Analyze the command string and step in
    local uuid, letcmd
    try
        uuid, letcmd = stepin(LineEdit.buffer(s))
    catch err
        repl = rebug_prompt_ref[].repl
        handled = false
        if err isa StashingFailed
            repl.header.warnmsg = "Execution did not reach point"
            handled = true
        elseif err isa Meta.ParseError || err isa StepException
            repl.header.warnmsg = "Expression at point is not a call expression"
            handled = true
        elseif err isa EvalException
            io = IOBuffer()
            showerror(io, err.exception)
            errstr = String(take!(io))
            repl.header.errmsg = "$errstr while evaluating $(err.exprstring)"
            handled = true
        elseif err isa DefMissing
            repl.header.errmsg = "The expression for method $(err.method) was unavailable. Perhaps it was untracked or generated by code."
            handled = true
        end
        if handled
            buf = LineEdit.buffer(s)
            LineEdit.edit_clear(buf)
            write(buf, cmd)
            seek(buf, pos)
            return nothing
        end
        rethrow(err)
    end
    set_uuid!(header(s), uuid)
    LineEdit.edit_clear(s)
    LineEdit.edit_insert(s, letcmd)
    return nothing
end

function capture_stacktrace(s)
    if mode(s) isa LineEdit.PrefixHistoryPrompt
        # history search, re-enter with the corresponding mode
        LineEdit.accept_result(s, mode(s))
        return capture_stacktrace(s)
    end
    cmdstring = LineEdit.content(s)
    add_history(s, cmdstring)
    print(REPL.terminal(s), '\n')
    expr = Meta.parse(cmdstring)
    local uuids
    try
        uuids = capture_stacktrace(expr)
    catch err
        print(stderr, err)
        return nothing
    end
    io = IOBuffer()
    buf = FakePrompt(io)
    hp = mode(s).hist
    for uuid in uuids
        println(io, generate_let_command(uuid))
        REPL.add_history(hp, buf)
        take!(io)
    end
    hp.cur_idx = length(hp.history) + 1
    if !isempty(uuids)
        set_uuid!(header(s), uuids[end])
        print(REPL.terminal(s), '\n')
        LineEdit.edit_clear(s)
        LineEdit.enter_prefix_search(s, find_prompt(s, LineEdit.PrefixHistoryPrompt), true)
    end
    return nothing
end

function add_history(s, str::AbstractString)
    io = IOBuffer()
    buf = FakePrompt(io)
    hp = mode(s).hist
    println(io, str)
    REPL.add_history(hp, buf)
end

function interpret(s)
    hdr = header(s)
    hdr.bt = nothing
    term = REPL.terminal(s)
    cmdstring = LineEdit.content(s)
    isempty(cmdstring) && return :done
    add_history(s, cmdstring)
    assigns, expr, display_result = simplecall(cmdstring)
    tupleexpr = JuliaInterpreter.extract_args(Main, expr)
    callargs = Core.eval(Main, tupleexpr)   # get the elements of the call
    callexpr = Expr(:call, callargs...)
    frame = JuliaInterpreter.enter_call_expr(callexpr)
    if frame === nothing
        local val
        try
            hdr.val = Core.eval(Main, callexpr)
        catch err
            hdr.val = err
            hdr.bt = catch_backtrace()
        end
    else
        # frame = JuliaInterpreter.maybe_step_through_wrapper!(frame)
        hdr.frame = frame
        deflines = expression_lines(frame)

        print(term, '\n') # to advance beyond the user's input line
        nlines = 0
        try
            while true
                HeaderREPLs.clear_nlines(term, nlines)
                print_header(term, hdr)
                if hdr.leveloffset == 0
                    nlines = show_code(term, frame, deflines, nlines)
                else
                    f, Δ = frameoffset(frame, hdr.leveloffset)
                    hdr.leveloffset -= Δ
                    nlines = show_code(term, f, expression_lines(f), nlines)
                end
                cmd = read(term, Char)
                if cmd == '?'
                    hdr.warnmsg = """
                    Commands:
                      space: next line
                      enter: continue to next breakpoint or completion
                          →: step in to next call
                          ←: finish frame and return to caller
                          ↑: display the caller frame
                          ↓: display the callee frame
                          b: insert breakpoint at current line
                          c: insert conditional breakpoint at current line
                          r: remove breakpoint at current line
                          d: disable breakpoint at current line
                          e: enable breakpoint at current line
                          o: open current position in editor
                          q: abort (returns nothing)"""
                elseif cmd == ' '
                    hdr.leveloffset = 0
                    ret = debug_command(frame, :n)
                    if ret === nothing
                        hdr.val = JuliaInterpreter.get_return(root(frame))
                        break
                    end
                    frame, deflines = refresh(frame, ret, deflines)
                elseif cmd == '\n' || cmd == '\r'
                    hdr.leveloffset = 0
                    ret = debug_command(frame, :c)
                    if ret === nothing
                        hdr.val = JuliaInterpreter.get_return(root(frame))
                        break
                    end
                    frame, deflines = refresh(frame, ret, deflines)
                elseif cmd == 'q'
                    hdr.val = nothing
                    break
                elseif cmd == 'o'
                    f, Δ = frameoffset(frame, hdr.leveloffset)
                    file, line = whereis(f)
                    edit(file, line)
                elseif cmd == '\e'   # escape codes
                    nxtcmd = read(term, Char)
                    nxtcmd == "O" && (nxtcmd = '[')  # normalize escape code
                    cmd *= nxtcmd
                    while nxtcmd == '['
                        nxtcmd = read(term, Char)
                        cmd *= nxtcmd
                    end
                    if cmd == "\e[C"  # right arrow
                        hdr.leveloffset = 0
                        ret = debug_command(frame, :s)
                        if ret === nothing
                            # Trying to "step in" at program exit
                            hdr.val = JuliaInterpreter.get_return(frame)
                            break
                        end
                        frame, deflines = refresh(frame, ret, deflines)
                    elseif cmd == "\e[D"  # left arrow
                        hdr.leveloffset = 0
                        ret = debug_command(frame, :finish)
                        if ret === nothing
                            hdr.val = JuliaInterpreter.get_return(root(frame))
                            break
                        end
                        frame, deflines = refresh(frame, ret, deflines)
                    elseif cmd == "\e[A"  # up arrow
                        hdr.leveloffset += 1
                    elseif cmd == "\e[B"  # down arrow
                        hdr.leveloffset = max(0, hdr.leveloffset - 1)
                    end
                elseif cmd == 'b' || cmd == 'c'
                    if cmd == 'b'
                        cond = nothing
                    else
                        print(term, "enter condition: ")
                        condstr = ""
                        c = read(term, Char)
                        while c != '\n' && c != '\r'
                            print(term, c)
                            condstr *= c
                            c = read(term, Char)
                        end
                        condex = Base.parse_input_line(condstr; filename="condition")
                        cond = (JuliaInterpreter.moduleof(frame), condex)
                    end
                    ret = JuliaInterpreter.whereis(frame)
                    if ret === nothing
                        @warn "Failed to find location info at current statement"
                    else
                        file, line = ret
                        JuliaInterpreter.breakpoint(file, line, cond)
                    end
                elseif cmd == 'r'
                    breakpoint_action(remove, frame)
                elseif cmd == 'd'
                    breakpoint_action(disable, frame)
                elseif cmd == 'e'
                    breakpoint_action(enable, frame)
                else
                    push!(msgs, cmd)
                end
                hdr.frame = frame
            end
        catch err
            hdr.val = err
            hdr.bt = catch_backtrace()
        end
    end
    # Store the result
    repl = mode(s).repl
    if isdefined(repl, :backendref)
        response = (display_result ? hdr.val : nothing, VERSION >= v"1.2.0-DEV.249" ? hdr.bt !== nothing : hdr.bt)
        put!(repl.backendref.response_channel, response)
    end
    hdr.frame = nothing
    # Do this last in case of errors
    if assigns !== nothing && hdr.bt === nothing
        Core.eval(Main, Expr(:(=), assigns, hdr.val))
    end
    return :done
end

function refresh(frame, ret, deflines)
    @assert ret !== nothing
    cframe, _ = ret
    if cframe === frame
        return frame, deflines
    end
    return cframe, expression_lines(cframe)
end

# Find the range of statement indexes that preceed a line
# `thisline` should be in "compiled" numbering
function coderange(src::CodeInfo, thisline)
    codeloc = searchsortedfirst(src.linetable, LineNumberNode(thisline, ""); by=x->x.line)
    if codeloc == 1
        return 1:1
    end
    idxline = searchsortedfirst(src.codelocs, codeloc)
    idxprev = searchsortedfirst(src.codelocs, codeloc-1) + 1
    return idxprev:idxline
end
coderange(framecode::FrameCode, thisline) = coderange(framecode.src, thisline)

function breakpoint_action(f, frame)
    ret = CodeTracking.whereis(frame)
    if ret === nothing
        @warn "Failed to find location info at current statement"
    else
        file, line = ret
        for bp in JuliaInterpreter.breakpoints()
            if bp isa JuliaInterpreter.BreakpointFileLocation && bp.path == file && bp.line == line
                f(bp)
            end
        end
    end
    return nothing
end

function simplecall(cmdstring)
    cmdstring = chomp(cmdstring)
    display_result = !endswith(cmdstring, ';')
    if !display_result
        cmdstring = cmdstring[1:end-1]
    end
    expr = Meta.parse(cmdstring)
    if expr.head == :(=)
        assigns = expr.args[1]
        expr = expr.args[2]
    else
        assigns = nothing
    end
    return assigns, expr, display_result
end

### REPL modes

function HeaderREPLs.setup_prompt(repl::HeaderREPL{RebugHeader}, hascolor::Bool)
    julia_prompt = find_prompt(repl.interface, "julia")

    prompt = REPL.LineEdit.Prompt(
        rebug_prompt_string;
        prompt_prefix = hascolor ? repl.prompt_color : "",
        prompt_suffix = hascolor ?
            (repl.envcolors ? Base.input_color : repl.input_color) : "",
        complete = julia_prompt.complete,
        on_enter = REPL.return_callback)

    prompt.on_done = HeaderREPLs.respond(repl, julia_prompt) do str
        Base.parse_input_line(str; filename="REBUG")
    end
    # hist will be handled automatically if repl.history_file is true
    # keymap_dict is separate
    return prompt, :rebug
end

function HeaderREPLs.append_keymaps!(keymaps, repl::HeaderREPL{RebugHeader})
    julia_prompt = find_prompt(repl.interface, "julia")
    kms = [
        trigger_search_keymap(repl),
        mode_termination_keymap(repl, julia_prompt),
        trigger_prefix_keymap(repl),
        REPL.LineEdit.history_keymap,
        REPL.LineEdit.default_keymap,
        REPL.LineEdit.escape_defaults,
    ]
    append!(keymaps, kms)
end

# To get it to parse the UUID whenever we move through the history, we have to specialize
# this method
function HeaderREPLs.activate_header(header::RebugHeader, p, s, termbuf, term)
    str = String(take!(copy(LineEdit.buffer(s))))
    set_uuid!(header, str)
end

## Interpret also requires a separate repl

function HeaderREPLs.setup_prompt(repl::HeaderREPL{InterpretHeader}, hascolor::Bool)
    julia_prompt = find_prompt(repl.interface, "julia")

    prompt = REPL.LineEdit.Prompt(
        "interpret> ";   # should never be shown
        prompt_prefix = hascolor ? repl.prompt_color : "",
        prompt_suffix = hascolor ?
            (repl.envcolors ? Base.input_color : repl.input_color) : "",
        complete = julia_prompt.complete,
        on_enter = REPL.return_callback)

    prompt.on_done = HeaderREPLs.respond(repl, julia_prompt) do str
        return DummyAST()
    end
    return prompt, :interpret
end

function HeaderREPLs.append_keymaps!(keymaps, repl::HeaderREPL{InterpretHeader})
    # No keymaps
    return keymaps
end

function HeaderREPLs.activate_header(header::InterpretHeader, p, s, termbuf, term)
end

function Base.put!(c::Channel, input::Tuple{DummyAST,Int})
    return nothing
end

const keybindings = Dict{Symbol,String}(
    :stacktrace => "\es",                        # Alt-s ("[s]tacktrace")
    :stepin => "\ee",                            # Alt-e ("[e]nter")
    :interpret => "\ei",                         # Alt-i ("[i]nterpret")
)

const modeswitches = Dict{Any,Any}(
    :stacktrace => (s, o...) -> capture_stacktrace(s),
    :stepin => (s, o...) -> (stepin(s); enter_rebug(s)),
    :interpret => (s, o...) -> (enter_interpret(s); interpret(s)),
)

function get_rebugger_modeswitch_dict()
    rebugger_modeswitch = Dict()
    for (action, keybinding) in keybindings
        rebugger_modeswitch[keybinding] = modeswitches[action]
    end
    rebugger_modeswitch
end

function add_keybindings(main_repl; override::Bool=false, kwargs...)
    history_prompt = find_prompt(main_repl.interface, LineEdit.PrefixHistoryPrompt)
    julia_prompt = find_prompt(main_repl.interface, "julia")
    rebug_prompt = find_prompt(main_repl.interface, "rebug")
    for (action, keybinding) in kwargs
        if !(action in keys(keybindings))
            error("$action is not a supported action.")
        end
        if !(keybinding isa Union{String,Vector{String}})
            error("Expected the value for $action to be a String or Vector{String}, got $keybinding instead")
        end
        if haskey(keybindings, action)
            keybindings[action] = keybinding
        end
        # PackageCompiler can cause these keys to be added twice (once during compilation and once by __init__),
        # so put these in a try/catch (issue #62)
        if action == :interpret
            try
                LineEdit.add_nested_key!(julia_prompt.keymap_dict, keybinding, modeswitches[action], override=override)
            catch
            end
        else
            # We need Any here because "cannot convert REPL.LineEdit.PrefixHistoryPrompt to an object of type REPL.LineEdit.Prompt"
            prompts = Any[julia_prompt, rebug_prompt]
            if action == :stacktrace push!(prompts, history_prompt) end
            for prompt in prompts
                if keybinding isa Vector
                    for kb in keybinding
                        try
                            LineEdit.add_nested_key!(prompt.keymap_dict, kb, modeswitches[action], override=override)
                        catch
                        end
                    end
                else
                    try
                        LineEdit.add_nested_key!(prompt.keymap_dict, keybinding, modeswitches[action], override=override)
                    catch
                    end
                end
            end
        end
    end
end

# These work only at the `rebug>` prompt
const rebugger_keys = Dict{Any,Any}(
)

## REPL commands TODO?:
## "\em" (meta-m): create REPL line that populates Main with arguments to current method
## "\eS" (meta-S): save version at REPL to file? (a little dangerous, perhaps make it configurable as to whether this is on)
## F1 is "^[OP" (showvalues?), F4 is "^[OS" (showinputs?)


enter_rebug(s) = mode_switch(s, rebug_prompt_ref[])
enter_interpret(s) = mode_switch(s, interpret_prompt_ref[])

function mode_switch(s, other_prompt)
    buf = copy(LineEdit.buffer(s))
    LineEdit.edit_clear(s)
    LineEdit.transition(s, other_prompt) do
        LineEdit.state(s, other_prompt).input_buffer = buf
    end
end

# julia_prompt = find_prompt(main_repl.interface, "julia")
# julia_prompt.keymap_dict['|'] = (s, o...) -> enter_count(s)


================================================
FILE: test/edit.jl
================================================
using Rebugger
using Rebugger: StopException
using Test, UUIDs, InteractiveUtils, REPL, Pkg, HeaderREPLs
using REPL.LineEdit
using Revise, Colors

if !isdefined(Main, :RebuggerTesting)
    includet("testmodule.jl")   # so the source code here gets loaded
end

const empty_kwvarargs = Rebugger.kwstasher()
uuidextractor(str) = UUID(match(r"getstored\(\"([a-z0-9\-]+)\"\)", str).captures[1])

struct ErrorsOnShow end
Base.show(io::IO, ::ErrorsOnShow) = throw(ArgumentError("no show"))

const SPACE = VERSION < v"1.5.0-DEV.0" ? "" : " "  # maybe 1.4.0-DEV.537? most likely 1.4.0-DEV.604

@testset "Rebugger" begin
    id = uuid1()
    @test uuidextractor("vars = getstored(\"$id\") and more stuff") == id
    @testset "Debug core" begin
        @testset "Deepcopy" begin
            args = (3.2, rand(3,3), Rebugger, [Rebugger], "hello", sum, (2,3))
            argc = Rebugger.safe_deepcopy(args...)
            @test argc == args
        end
        @testset "Signatures" begin
            @test Rebugger.signature_names!(:(f(x::Int, @nospecialize(y::String)))) == (:f, (:x, :y), (), ())
            @test Rebugger.signature_names!(:(f(x::Int, $(Expr(:meta, :nospecialize, :(y::String)))))) ==
                (:f, (:x, :y), (), ())
            ex = :(f(::Type{T}, ::IndexStyle, x::Int, ::IndexStyle) where T)
            @test Rebugger.signature_names!(ex) == (:f, (:T, :__IndexStyle_1, :x, :__IndexStyle_2), (), ())
            @test ex == :(f(::Type{T}, __IndexStyle_1::IndexStyle, x::Int, __IndexStyle_2::IndexStyle) where T)
            ex = :(f(Tuseless::Type{T}, ::IndexStyle, x::Int) where T)
            @test Rebugger.signature_names!(ex) == (:f, (:Tuseless, :__IndexStyle_1, :x), (), (:T,))
            @test ex == :(f(Tuseless::Type{T}, __IndexStyle_1::IndexStyle, x::Int) where T)
            # issue #34
            ex = :(_mapreduce_dim(f, op, ::NamedTuple{()}, A::AbstractArray, ::Colon))
            @test Rebugger.signature_names!(ex) == (:_mapreduce_dim, (:f, :op, :__NamedTuple_1, :A, :__Colon_1), (), ())
            @test ex == :(_mapreduce_dim(f, op, __NamedTuple_1::NamedTuple{()}, A::AbstractArray, __Colon_1::Colon))
        end
        @testset "Caller buffer capture and insertion" begin
            function run_insertion(str, atstr)
                RebuggerTesting.cbdata1[] = RebuggerTesting.cbdata2[] = Rebugger.stashed[] = nothing
                io = IOBuffer()
                idx = findfirst(atstr, str)
                print(io, str)
                seek(io, first(idx)-1)
                callexpr = Rebugger.prepare_caller_capture!(io)
                capstring = String(take!(io))
                capexpr   = Meta.parse(capstring)
                try
                    Core.eval(RebuggerTesting, capexpr)
                catch err
                    isa(err, StopException) || rethrow(err)
                end
            end

            str = """
            for i = 1:5
                cbdata1[] = i
                foo(12, 13; akw="modified")
                cbdata2[] = i
            end
            """
            @test run_insertion(str, "foo")
            @test RebuggerTesting.cbdata1[] == 1
            @test RebuggerTesting.cbdata2[] == nothing
            @test Rebugger.stashed[] == (RebuggerTesting.foo, (12, 13), Rebugger.kwstasher(;akw="modified"))
            str = """
            for i = 1:5
                error("not caught")
                foo(12, 13; akw="modified")
            end
            """
            @test_throws ErrorException("not caught") run_insertion(str, "foo")
            @test_throws Rebugger.StepException("Rebugger can only step into expressions, got 77") run_insertion("x = 77", "77")

            # Module-scoped calls
            io = IOBuffer()
            cmdstr = "Scope.func(x, y, z)"
            print(io, cmdstr)
            seek(io, 0)
            callexpr = Rebugger.prepare_caller_capture!(io)
            @test callexpr == :(Scope.func(x, y, z))
            take!(io)

            # getindex and setindex! expressions
            cmdstr = "x = a[2,3]"
            print(io, cmdstr)
            seek(io, first(findfirst("a", cmdstr))-1)
            callexpr = Rebugger.prepare_caller_capture!(io)
            @test callexpr == :(getindex(a, 2, 3))
            take!(io)

            cmdstr = "a[2,3] = x"
            print(io, cmdstr)
            seek(io, 0)
            callexpr = Rebugger.prepare_caller_capture!(io)
            @test callexpr == :(setindex!(a, x, 2, 3))
            take!(io)

            # Expressions that go beyond "user intention".
            # More generally we should support marking, but in the case of && and || it's
            # handled by lowering, so there is nothing to step into anyway.
            for cmdstr in ("f1(x) && f2(z)", "f1(x) || f2(z)")
                print(io, cmdstr)
                seek(io, 0)
                callexpr = Rebugger.prepare_caller_capture!(io)
                @test callexpr == :(f1(x))
                take!(io)
            end

            # issue #5
            cmdstr = "abs(abs(x))"
            print(io, cmdstr)
            seek(io, 4)
            callexpr = Rebugger.prepare_caller_capture!(io)
            @test callexpr == :(abs(x))
            take!(io)

            # splat expressions
            cmdstr = "foo(bar(x)..., 1)"
            print(io, cmdstr)
            idx = findfirst("bar", cmdstr)
            seek(io, first(idx)-1)
            callexpr = Rebugger.prepare_caller_capture!(io)
            @test callexpr == :(bar(x))
        end

        @testset "Callee variable capture" begin
            def = quote
                function complexargs(x::A, y=1, str="1.0"; kw1=Float64, kw2=7, kwargs...) where A<:AbstractArray{T} where T
                    return (x .+ y, parse(kw1, str), kw2)
                end
            end
            f = Core.eval(RebuggerTesting, def)
            @test f([8,9]) == ([9,10], 1.0, 7)
            m = collect(methods(f))[end]
            uuid = Rebugger.method_capture_from_callee(m, def)
            @test  Rebugger.method_capture_from_callee(m, def) == uuid  # calling twice returns the previously-defined objects
            fc = Rebugger.storefunc[uuid]
            @test_throws StopException fc([8,9], 2, "13"; kw1=Int, kw2=0)
            @test Rebugger.stored[uuid].varnames == (:x, :y, :str, :kw1, :kw2, :kwargs, :A, :T)
            @test Rebugger.stored[uuid].varvals  == ([8,9], 2, "13", Int, 0, empty_kwvarargs, Vector{Int}, Int)
            @test_throws StopException fc([8,9]; otherkw=77)
            @test Rebugger.stored[uuid].varnames == (:x, :y, :str, :kw1, :kw2, :kwargs, :A, :T)
            @test Rebugger.stored[uuid].varvals  == ([8,9], 1, "1.0", Float64, 7, pairs((otherkw=77,)), Vector{Int}, Int)

            uuid2 = Rebugger.method_capture_from_callee(m, def; overwrite=true)
            @test uuid2 != uuid
            # note overwriting methods are not stored in storefunc, but our old `f` will call the new method
            @test f([8,9], 2, "13"; kw1=Int, kw2=0) == ([10,11], 13, 0)
            Core.eval(RebuggerTesting, def)
            @test Rebugger.stored[uuid2].varnames == (:x, :y, :str, :kw1, :kw2, :kwargs, :A, :T)
            @test Rebugger.stored[uuid2].varvals  == ([8,9], 2, "13", Int, 0, empty_kwvarargs, Vector{Int}, Int)

            def = quote
                @inline modifies!(x) = (x[1] += 1; x)
            end
            f = Core.eval(RebuggerTesting, def)
            @test f([8,9]) == [9,9]
            m = collect(methods(f))[end]
            uuid = Rebugger.method_capture_from_callee(m, def)
            fc = Rebugger.storefunc[uuid]
            @test_throws StopException fc([8,9])
            @test Rebugger.stored[uuid].varnames == (:x,)
            @test Rebugger.stored[uuid].varvals  == ([8,9],)

            # Extensions of functions from other modules
            m = @which RebuggerTesting.foo()
            uuid = Rebugger.method_capture_from_callee(m)
            fc = Rebugger.storefunc[uuid]
            @test_throws StopException fc()
            @test Rebugger.stored[uuid].varnames == Rebugger.stored[uuid].varvals == ()
        end

        @testset "Step in" begin
            function run_stepin(str, atstr)
                io = IOBuffer()
                idx = findfirst(atstr, str)
                @test !isempty(idx)
                print(io, str)
                seek(io, first(idx)-1)
                Rebugger.stepin(io)
            end

            str = "RebuggerTesting.snoop0()"
            uuidref, cmd = run_stepin(str, str)
            uuid1 = uuidextractor(cmd)
            @test uuid1 == uuidref
            @test cmd == """
            @eval Main.RebuggerTesting let () = Main.Rebugger.getstored("$uuid1")
            begin
                snoop1("Spy")
            end
            end"""
            _, cmd = run_stepin(cmd, "snoop1")
            uuid2 = uuidextractor(cmd)
            @test cmd == """
            @eval Main.RebuggerTesting let (word,) = Main.Rebugger.getstored("$uuid2")
            begin
                snoop2(word, "on")
            end
            end"""
            _, cmd = run_stepin(cmd, "snoop2")
            uuid3 = uuidextractor(cmd)
            @test cmd == """
            @eval Main.RebuggerTesting let (word1, word2) = Main.Rebugger.getstored("$uuid3")
            begin
                snoop3(word1, word2, "arguments")
            end
            end"""
            @test Rebugger.getstored(string(uuid1)) == ()
            @test Rebugger.getstored(string(uuid2)) == ("Spy",)
            @test Rebugger.getstored(string(uuid3)) == ("Spy", "on")

            str = "RebuggerTesting.kwvarargs(1)"
            _, cmd = run_stepin(str, str)
            uuid = uuidextractor(cmd)
            @test cmd == """
            @eval Main.RebuggerTesting let (x, kw1, kwargs) = Main.Rebugger.getstored("$uuid")
            begin
                kwvarargs2(x; kw1$(SPACE)=$(SPACE)kw1, kwargs...)
            end
            end"""
            @test Rebugger.getstored(string(uuid)) == (1, 1, empty_kwvarargs)
            cmd = run_stepin(cmd, "kwvarargs2")

            str = "RebuggerTesting.kwvarargs(1; passthrough=false)"
            _, cmd = run_stepin(str, str)
            uuid = uuidextractor(cmd)
            @test cmd == """
            @eval Main.RebuggerTesting let (x, kw1, kwargs) = Main.Rebugger.getstored("$uuid")
            begin
                kwvarargs2(x; kw1$(SPACE)=$(SPACE)kw1, kwargs...)
            end
            end"""
            @test Rebugger.getstored(string(uuid)) == (1, 1, pairs((passthrough=false,)))
            _, cmd = run_stepin(cmd, "kwvarargs2")

            # Step in to call-overloading methods
            str = "RebuggerTesting.hv_test(\"hi\")"
            _, cmd = run_stepin(str, str)
            uuid = uuidextractor(cmd)
            @test cmd == """
            @eval Main.RebuggerTesting let (hv, str) = Main.Rebugger.getstored("$uuid")
            begin
                hv.x
            end
            end"""
            @test Rebugger.getstored(string(uuid)) == (RebuggerTesting.hv_test, "hi")

            # Step in to methods that do tuple-destructuring of arguments
            str = "RebuggerTesting.destruct(1, (2,3), 4)"
            @test eval(Meta.parse(str)) == 2
            _, cmd = run_stepin(str, str)
            uuid = uuidextractor(cmd)
            @test cmd == """
            @eval Main.RebuggerTesting let (x, (a, b), y) = Main.Rebugger.getstored("$uuid")
            begin
                a
            end
            end"""
            @test Rebugger.getstored(string(uuid)) == (1, (2,3), 4)

            # Step in to a broadcast call
            str = "sum.([[1,2], (3,5)])"
            uuid, cmd = run_stepin(str, str)
            s = Rebugger.stored[uuid]
            @test s.method.name == :broadcast
            @test cmd == """
            @eval Base.Broadcast let (f, As, Tf) = Main.Rebugger.getstored("$uuid")
            begin
                materialize(broadcasted(f, As...))
            end
            end"""
            @test Rebugger.getstored(string(uuid)) == (sum, (Any[[1,2], (3,5)],), typeof(sum))
            Core.eval(Main, Meta.parse(cmd)) == [3,8]

            str = "max.([1,5], [2,-3])"
            uuid, cmd = run_stepin(str, str)
            s = Rebugger.stored[uuid]
            @test s.method.name == :broadcast
            @test cmd == """
            @eval Base.Broadcast let (f, As, Tf) = Main.Rebugger.getstored("$uuid")
            begin
                materialize(broadcasted(f, As...))
            end
            end"""
            @test Rebugger.getstored(string(uuid)) == (max, ([1,5], [2,-3]), typeof(max))
            Core.eval(Main, Meta.parse(cmd)) == [2,5]

            # Step in to a do block
            str = "RebuggerTesting.calldo()"
            uuidref, cmd = run_stepin(str, str)
            uuid1 = uuidextractor(cmd)
            @test uuid1 == uuidref
            @test cmd == """
            @eval Main.RebuggerTesting let () = Main.Rebugger.getstored("$uuid1")
            begin
                apply(2, 3, 4) do x, y, z
                    snoop3(x, y, z)
                end
            end
            end"""
            uuidref, cmd = run_stepin(cmd, "apply")
            uuid1 = uuidextractor(cmd)
            @test uuid1 == uuidref
            @test cmd == """
            @eval Main.RebuggerTesting let (f, args) = Main.Rebugger.getstored("$uuid1")
            begin
                kwvarargs(f)
                f(args...)
            end
            end"""
        end

        @testset "Capture stacktrace" begin
            uuids = nothing
            mktemp() do path, iostacktrace
                redirect_stderr(iostacktrace) do
                    uuids = Rebugger.capture_stacktrace(RebuggerTesting, :(snoop0()))
                end
                flush(iostacktrace)
                str = read(path, String)
                @test occursin("snoop3", str)
            end
            @test Rebugger.stored[uuids[1]].varvals == ()
            @test Rebugger.stored[uuids[2]].varvals == ("Spy",)
            @test Rebugger.stored[uuids[3]].varvals == ("Spy", "on")
            @test Rebugger.stored[uuids[4]].varvals == ("Spy", "on", "arguments", "simply", empty_kwvarargs, String)
            @test_throws ErrorException("oops") RebuggerTesting.snoop0()

            st = try RebuggerTesting.kwfunctop(3) catch; stacktrace(catch_backtrace()) end
            usrtrace, defs = Rebugger.pregenerated_stacktrace(st; topname=Symbol("macro expansion"))
            @test length(unique(usrtrace)) == length(usrtrace)
            m = @which RebuggerTesting.kwfuncmiddle(1,1)
            @test m ∈ usrtrace

            # A case that tests inlining and several other aspects of argument capture
            ex = :([1, 2, 3] .* [1, 2])
            # Capture the actual stack trace, trimming it to avoid
            # anything involving the `eval` itself
            trace = try
                Core.eval(Main, ex)
            catch
                stacktrace(catch_backtrace())
            end
            i = 1
            while i <= length(trace)
                t = trace[i]
                if t.func == Symbol("top-level scope")
                    deleteat!(trace, i:length(trace))
                end
                i += 1
            end
            # Get the capture from Rebugger
            uuids = mktemp() do path, iostacktrace
                redirect_stderr(iostacktrace) do
                    Rebugger.capture_stacktrace(Main, ex)
                end
            end
            @test length(uuids) == length(trace)
            for (uuid, t) in zip(reverse(uuids), trace)
                @test Rebugger.stored[uuid].method.name == t.func
            end

            # Try capturing a method from Core. On binaries this would throw
            # if we didn't catch it.
            # Because the first entry is "top-level scope", and that terminates
            # processing in Rebugger.pregenerated_stacktrace, we have to intervene a bit.
            mod, ex = Main, :(Core.throw(ArgumentError("oops")))
            trace = try Core.eval(mod, command) catch err stacktrace(catch_backtrace()) end
            usrtrace, defs = Rebugger.pregenerated_stacktrace(trace[2:3])
            @test usrtrace isa Vector
        end
    end

    @testset "User interface" begin
        @testset "Printing header" begin
            h = Rebugger.RebugHeader()
            h.uuid = uuid = uuid1()
            meth = @which RebuggerTesting.foo(1,2)
            h.current_method = meth
            Rebugger.stored[uuid] = Rebugger.Stored(meth, (:x, :y), (1, ErrorsOnShow()))
            h.warnmsg = "This is a warning"
            h.errmsg  = "You will not have a second chance"
            io = IOBuffer()
            Rebugger.print_header(io, h)
            str = String(take!(io))
            @test startswith(str, """
            This is a warning
            You will not have a second chance
            foo(x, y) in Main.RebuggerTesting at """) # skip the "upper" part of the file location
            @test endswith(str, "testmodule.jl:7\n  x = 1\n  y errors in its show method")
        end

        @testset "Demos" begin
            function prepare_step_command(cmd, atstr)
                LineEdit.edit_clear(mistate)
                idx = findfirst(atstr, cmd)
                @test !isempty(idx)
                LineEdit.replace_line(mistate, cmd)
                buf = LineEdit.buffer(mistate)
                seek(buf, first(idx)-1)
                return mistate
            end

            function do_capture_stacktrace(cmd)
                l = length(hist.history)
                LineEdit.replace_line(mistate, cmd)
                Rebugger.capture_stacktrace(mistate)
                LineEdit.transition(mistate, julia_prompt)
                return l+1:length(hist.history)
            end

            if isdefined(Base, :active_repl)
                repl = Base.active_repl
                mistate = repl.mistate
                julia_prompt = find_prompt(mistate, "julia")
                LineEdit.transition(mistate, julia_prompt)
                hist = julia_prompt.hist
                header = Rebugger.rebug_prompt_ref[].repl.header
                histdel = 0

                @testset "show demo" begin  # this is a demo that appears in the documentation
                    cmd1 = "show([1,2,4])"
                    s = prepare_step_command(cmd1, cmd1)
                    Rebugger.stepin(s)
                    histdel += 1
                    uuid = header.uuid
                    @test Rebugger.getstored(string(uuid)) == ([1,2,4],)
                    cmd2 = LineEdit.content(s)
                    s = prepare_step_command(cmd2, "show(stdout::IO, x)")
                    Rebugger.stepin(s)
                    histdel += 1
                    uuid = header.uuid
                    @test Rebugger.getstored(string(uuid))[2] == [1,2,4]
                    cmd3 = LineEdit.content(s)
                    s = prepare_step_command(cmd3, "_show_empty")
                    Rebugger.stepin(s)
                    histdel += 1
                    @test header.warnmsg == "Execution did not reach point"
                end

                @testset "Colors demo" begin  # another demo that appears in the documentation
                    desc = "hsl(80%, 20%, 15%)"
                    cmd = "colorant\"hsl(80%, 20%, 15%)\""
                    local idx
                    mktemp() do path, io
                        redirect_stderr(io) do
                            logs, _ = Test.collect_test_logs() do
                                idx = do_capture_stacktrace(cmd)
                            end
                        end
                        flush(io)
                        seek(io, 0)
                        @test countlines(io) >= 4
                    end
                    histdel += length(idx)
                    @test length(idx) >= 5
                    @test hist.history[idx[1]] == cmd
                    @test occursin("error", hist.history[idx[end]])
                end

                @testset "Pkg demo" begin
                    updated = Pkg.UPDATED_REGISTRY_THIS_SESSION[]
                    Pkg.UPDATED_REGISTRY_THIS_SESSION[] = true
                    uuids = Rebugger.capture_stacktrace(Pkg, :(add("NoPkg")))
                    @test length(uuids) >= 2
                    Pkg.UPDATED_REGISTRY_THIS_SESSION[] = updated
                end

                @testset "Empty stacktraces" begin
                    cmd = "ccall(:jl_throw, Nothing, (Any,), ArgumentError(\"oops\"))"
                    mktemp() do path, io
                        redirect_stderr(io) do
                            LineEdit.replace_line(mistate, cmd)
                            @test Rebugger.capture_stacktrace(mistate) === nothing
                            LineEdit.transition(mistate, julia_prompt)
                        end
                        flush(io)
                        str = read(path, String)
                        @test occursin("failed to capture", str)
                    end
                end

                LineEdit.edit_clear(mistate)
                l = length(hist.history)
                deleteat!(hist.history, l-histdel+1:l)
                deleteat!(hist.modes, l-histdel+1:l)
                hist.cur_idx = length(hist.history)+1
            end
        end
    end
end


================================================
FILE: test/interpret.jl
================================================
using Rebugger, JuliaInterpreter
using CodeTracking, Revise, Test, InteractiveUtils

if !isdefined(Main, :fixable1)
    includet("interpret_script.jl")
end

@testset "Expression-printing and line numbers" begin
    for m in (@which(Tuple((1,2))),
              which(Base.show_vector, Tuple{IO,Any}),
              which(Rebugger.interpret, Tuple{Any}),
             )
        def = definition(m)
        linenos, line1, methlines = Rebugger.expression_lines(m)
        @test length(linenos) == length(methlines)
        @test issorted(skipmissing(linenos))
        @test maximum(skipmissing(linenos)) >= maximum(CodeTracking.linerange(def))
    end

    # Line number fill-in
    for f in (fixable1, fixable2, fixable3)
        m = first(methods(f))
        linenos, line1, methlines = Rebugger.expression_lines(m)
        @test count(ismissing, linenos) == 1  # only the final end is ambiguous
    end
    linenos, line1, methlines = Rebugger.expression_lines(first(methods(unfixable1)))
    @test count(ismissing, linenos) == 3

    # Generated functions
    for ndims = 2:3
        frame = JuliaInterpreter.enter_call(call_generated1, ndims)
        pc, n = frame.pc, JuliaInterpreter.nstatements(frame.framecode)
        while pc < n-1
            frame, pc = debug_command(frame, :se)
        end
        frame, pc = debug_command(frame, :si)
        linenos, line1, methlines = Rebugger.expression_lines(frame)
        @test length(methlines) == 3 && strip(methlines[2]) == string(Expr(:tuple, ntuple(i->:val, ndims)...))
    end

    # Unparsed methods
    frame = JuliaInterpreter.enter_call(getline, LineNumberNode(0, Symbol("fake.jl")))
    frame, pc = debug_command(frame, :si)
    m = JuliaInterpreter.scopeof(frame)
    if m.file == Symbol("sysimg.jl")  # sysimg.jl is excluded from Revise tracking
        linenos, line1, methlines = Rebugger.expression_lines(frame)
        @test linenos == [m.line]
    end

    # Internal macros (issue #63)
    frame = JuliaInterpreter.enter_call(f63)
    deflines = Rebugger.expression_lines(frame)
    frame, pc = debug_command(frame, :n)
    io = IOBuffer()
    Rebugger.show_code(io, frame, deflines, 0)
    str = String(take!(io))
    @test occursin("y = 7", str)
end


================================================
FILE: test/interpret_script.jl
================================================
# Test functions for parsing
function fixable1(x)
    return x
end
fixable2(x) = x
function fixable3(A)
    s = 0
    fi = firstindex(A)
    for i in eachindex(A)
        for j in fi:i-1
            s += A[j]
        end
    end
    return s
end
function unfixable1(A)
    s = 0
    fi = firstindex(A)
    for i in eachindex(A)
        for j in fi:i-1
            s += A[j]
        end end
    return s
end

# Generated functions
@generated function generated1(A::AbstractArray{T,N}, val) where {T,N}
    ex = Expr(:tuple)
    for i = 1:N
        push!(ex.args, :val)
    end
    return ex
end
call_generated1(ndims) = generated1(fill(0, ntuple(d->1, ndims)...), 7)

# getproperty is defined in sysimg.jl
getline(lnn) = lnn.line

function f63()
    x = 1 + 1
    @info "hello"
    y = 7
end


================================================
FILE: test/interpret_ui.jl
================================================
# This was copied from Debugger.jl and then modified

using TerminalRegressionTests, Rebugger, Revise, CodeTracking
using HeaderREPLs, REPL
using Test

includet("my_gcd.jl")

function run_terminal_test(cmd, validation, commands)
    function compare_replace(em, target; replace=nothing)
        # Compare two buffer, skipping over the equivalent of key=>rep replacement
        # However, because of potential differences in wrapping we don't explicitly
        # perform the replacement; instead, we make the comparison tolerant of difference
        # `\n`.
        buf = IOBuffer()
        decoratorbuf = IOBuffer()
        TerminalRegressionTests.VT100.dump(buf, decoratorbuf, em)
        outbuf = take!(buf)
        success = true
        if replace !== nothing
            output = String(outbuf)
            key, rep = replace
            idxkey = findfirst(key, target)
            iout, itgt = firstindex(output), firstindex(target)
            outlast, tgtlast = lastindex(output), lastindex(target)
            lrep = length(rep)
            while success && iout <= outlast && itgt <= tgtlast
                if itgt == first(idxkey)
                    itgt += length(key)
                    for c in rep
                        cout = output[iout]
                        while c != cout && cout == '\n'
                            iout = nextind(output, iout)
                            cout = output[iout]
                        end
                        if c != cout
                            success = false
                            break
                        end
                        iout = nextind(output, iout)
                    end
                else
                    cout, ctgt = output[iout], target[itgt]
                    success = cout == ctgt
                    iout, itgt = nextind(output, iout), nextind(target, itgt)
                end
            end
            success && iout > outlast && itgt > tgtlast && return true
        end
        outbuf == codeunits(target) && return true
        open("failed.out","w") do f
            write(f, output)
        end
        open("expected.out","w") do f
            write(f, target)
        end
        error("Test failed. Expected result written to expected.out,
            actual result written to failed.out")
    end

    dirpath = joinpath(@__DIR__, "ui", "v$(VERSION.major).$(VERSION.minor)")
    isdir(dirpath) || mkpath(dirpath)
    filepath = joinpath(dirpath, validation)
    # Fix the path of gcd to match the current running version of Julia
    gcdfile, gcdline = whereis(@which my_gcd(10, 20))
    cmp(a, b, decorator) = compare_replace(a, b; replace="****" => gcdfile*':'*string(gcdline))
    TerminalRegressionTests.automated_test(cmp, filepath, commands) do emuterm
    # TerminalRegressionTests.create_automated_test(filepath, commands) do emuterm
        main_repl = REPL.LineEditREPL(emuterm, true)
        main_repl.interface = REPL.setup_interface(main_repl)
        main_repl.specialdisplay = REPL.REPLDisplay(main_repl)
        main_repl.mistate = REPL.LineEdit.init_state(REPL.terminal(main_repl), main_repl.interface)
        iprompt, eprompt = Rebugger.rebugrepl_init(main_repl, true)
        repl = iprompt.repl
        s = repl.mistate
        s.current_mode = iprompt
        repl.t = emuterm
        REPL.LineEdit.edit_clear(s)
        REPL.LineEdit.edit_insert(s, cmd)
        Rebugger.interpret(s)
    end
end

CTRL_C = "\x3"
EOT = "\x4"
UP_ARROW = "\e[A"

run_terminal_test("my_gcd(10, 20)",
                  "gcd.multiout",
                  ['\n'])
run_terminal_test("__gcdval__ = my_gcd(10, 20);",
                  "gcdsc.multiout",
                  ['\n'])
@test __gcdval__ == gcd(10, 20)


================================================
FILE: test/my_gcd.jl
================================================
# From base, but copied here to make sure we don't fail bacause base changed
function my_gcd(a::T, b::T) where T<:Union{Int8,UInt8,Int16,UInt16,Int32,UInt32,
Int64,UInt64,Int128,UInt128}
    a == 0 && return abs(b)
    b == 0 && return abs(a)
    za = trailing_zeros(a)
    zb = trailing_zeros(b)
    k = min(za, zb)
    u = unsigned(abs(a >> za))
    v = unsigned(abs(b >> zb))
    while u != v
        if u > v
            u, v = v, u
        end
        v -= u
        v >>= trailing_zeros(v)
    end
    r = u << k
    # T(r) would throw InexactError; we want OverflowError instead
    r > typemax(T) && throw(OverflowError("gcd($a, $b) overflows"))
    r % T
end


================================================
FILE: test/runtests.jl
================================================
using Rebugger, Test

@info "These tests manipulate the console. Wait until you see \"Done\""
include("edit.jl")
include("interpret.jl")
if Sys.isunix() && VERSION >= v"1.1.0"
    include("interpret_ui.jl")
else
    @warn "Skipping UI tests"
end
println("Done")


================================================
FILE: test/testmodule.jl
================================================
module RebuggerTesting

const cbdata1 = Ref{Any}(nothing)
const cbdata2 = Ref{Any}(nothing)

# Do not alter the line number at which `foo` occurs
foo(x, y) = nothing

snoop0()             = snoop1("Spy")
snoop1(word)         = snoop2(word, "on")
snoop2(word1, word2) = snoop3(word1, word2, "arguments")
snoop3(word1, word2, word3::T; adv="simply", morekws...) where T = error("oops")

kwvarargs(x; kw1=1, kwargs...)  = kwvarargs2(x; kw1=kw1, kwargs...)
kwvarargs2(x; kw1=0, passthrough=true) = (x, kw1, passthrough)

destruct(x, (a, b), y) = a

struct HasValue
    x::Float64
end
const hv_test = HasValue(11.1)

(hv::HasValue)(str::String) = hv.x

@inline kwfuncerr(y) = error("stop")
@noinline kwfuncmiddle(x::T, y::Integer=1; kw1="hello", kwargs...) where T = kwfuncerr(y)
@inline kwfunctop(x; kwargs...) = kwfuncmiddle(x, 2; kwargs...)

function apply(f, args...)
    kwvarargs(f)
    f(args...)
end

calldo() = apply(2, 3, 4) do x, y, z
    snoop3(x, y, z)
end

end

module RBT2

using ..RebuggerTesting

bar(::Int) = 5
RebuggerTesting.foo() = bar(1)

end


================================================
FILE: test/ui/v1.0/gcd.multiout
================================================
++++++++++++++++++++++++++++++++++++++++++++++++++
|gcd(10, 20)
|gcd(a::T, b::T) where T<:Union{Int128, Int16, Int32, Int64, Int8, UInt128, UInt1
|6, UInt32, UInt64, UInt8} in Base at intfuncs.jl:31
|  a = 10
|  b = 20
|  T = Int64
| 30  function gcd(a::T, b::T) where T <: Union{Int8, UInt8, Int16, UInt16, Int…
| 31      a == 0 && return abs(b)
| 32      b == 0 && return abs(a)
| 33      za = trailing_zeros(a)
|
--------------------------------------------------
|AAAAAAAAAAA
|AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|AAAAAAAA
|AAAAAAAA
|AAAAAAAAAAA
|AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|

================================================
FILE: test/ui/v1.0/gcdsc.multiout
================================================
++++++++++++++++++++++++++++++++++++++++++++++++++
|__gcdval__ = gcd(10, 20);
|gcd(a::T, b::T) where T<:Union{Int128, Int16, Int32, Int64, Int8, UInt128, UInt1
|6, UInt32, UInt64, UInt8} in Base at intfuncs.jl:31
|  a = 10
|  b = 20
|  T = Int64
| 30  function gcd(a::T, b::T) where T <: Union{Int8, UInt8, Int16, UInt16, Int…
| 31      a == 0 && return abs(b)
| 32      b == 0 && return abs(a)
| 33      za = trailing_zeros(a)
|
--------------------------------------------------
|AAAAAAAAAAAAAAAAAAAAAAAAA
|AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|AAAAAAAA
|AAAAAAAA
|AAAAAAAAAAA
|AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|

================================================
FILE: test/ui/v1.1/gcd.multiout
================================================
++++++++++++++++++++++++++++++++++++++++++++++++++
|gcd(10, 20)
|gcd(a::T, b::T) where T<:Union{Int128, Int16, Int32, Int64, Int8, UInt128, UInt1
|6, UInt32, UInt64, UInt8} in Base at intfuncs.jl:31
|  a = 10
|  b = 20
|  T = Int64
| 30  function gcd(a::T, b::T) where T <: Union{Int8, UInt8, Int16, UInt16, Int…
| 31      a == 0 && return abs(b)
| 32      b == 0 && return abs(a)
| 33      za = trailing_zeros(a)
|
--------------------------------------------------
|AAAAAAAAAAA
|AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|AAAAAAAA
|AAAAAAAA
|AAAAAAAAAAA
|AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|

================================================
FILE: test/ui/v1.1/gcdsc.multiout
================================================
++++++++++++++++++++++++++++++++++++++++++++++++++
|__gcdval__ = gcd(10, 20);
|gcd(a::T, b::T) where T<:Union{Int128, Int16, Int32, Int64, Int8, UInt128, UInt1
|6, UInt32, UInt64, UInt8} in Base at intfuncs.jl:31
|  a = 10
|  b = 20
|  T = Int64
| 30  function gcd(a::T, b::T) where T <: Union{Int8, UInt8, Int16, UInt16, Int…
| 31      a == 0 && return abs(b)
| 32      b == 0 && return abs(a)
| 33      za = trailing_zeros(a)
|
--------------------------------------------------
|AAAAAAAAAAAAAAAAAAAAAAAAA
|AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|AAAAAAAA
|AAAAAAAA
|AAAAAAAAAAA
|AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|

================================================
FILE: test/ui/v1.2/gcd.multiout
================================================
++++++++++++++++++++++++++++++++++++++++++++++++++
|gcd(10, 20)
|gcd(a::T, b::T) where T<:Union{Int128, Int16, Int32, Int64, Int8, UInt128, UInt1
|6, UInt32, UInt64, UInt8} in Base at ****
|  a = 10
|  b = 20
|  T = Int64
| 30  function gcd(a::T, b::T) where T <: Union{Int8, UInt8, Int16, UInt16, Int…
| 31      a == 0 && return abs(b)
| 32      b == 0 && return abs(a)
| 33      za = trailing_zeros(a)
|
--------------------------------------------------
|AAAAAAAAAAA
|AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|AAAAAAAA
|AAAAAAAA
|AAAAAAAAAAA
|AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|

================================================
FILE: test/ui/v1.2/gcdsc.multiout
================================================
++++++++++++++++++++++++++++++++++++++++++++++++++
|__gcdval__ = gcd(10, 20);
|gcd(a::T, b::T) where T<:Union{Int128, Int16, Int32, Int64, Int8, UInt128, UInt1
|6, UInt32, UInt64, UInt8} in Base at ****
|  a = 10
|  b = 20
|  T = Int64
| 30  function gcd(a::T, b::T) where T <: Union{Int8, UInt8, Int16, UInt16, Int…
| 31      a == 0 && return abs(b)
| 32      b == 0 && return abs(a)
| 33      za = trailing_zeros(a)
|
--------------------------------------------------
|AAAAAAAAAAAAAAAAAAAAAAAAA
|AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|AAAAAAAA
|AAAAAAAA
|AAAAAAAAAAA
|AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|

================================================
FILE: test/ui/v1.3/gcd.multiout
================================================
++++++++++++++++++++++++++++++++++++++++++++++++++
|my_gcd(10, 20)
|my_gcd(a::T, b::T) where T<:Union{Int128, Int16, Int32, Int64, Int8, UInt128, UI
|nt16, UInt32, UInt64, UInt8} in Main at ****
|  a = 10
|  b = 20
|  T = Int64
|  3  function my_gcd(a::T, b::T) where T <: Union{Int8, UInt8, Int16, UInt16, …
|  4      a == 0 && return abs(b)
|  5      b == 0 && return abs(a)
|  6      za = trailing_zeros(a)
|
--------------------------------------------------
|AAAAAAAAAAAAAA
|AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|AAAAAA
|AAAAAAAA
|AAAAAAAA
|AAAAAAAAAAA
|AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|


================================================
FILE: test/ui/v1.3/gcdsc.multiout
================================================
++++++++++++++++++++++++++++++++++++++++++++++++++
|__gcdval__ = my_gcd(10, 20);
|my_gcd(a::T, b::T) where T<:Union{Int128, Int16, Int32, Int64, Int8, UInt128, UI
|nt16, UInt32, UInt64, UInt8} in Main at ****
|  a = 10
|  b = 20
|  T = Int64
|  3  function my_gcd(a::T, b::T) where T <: Union{Int8, UInt8, Int16, UInt16, …
|  4      a == 0 && return abs(b)
|  5      b == 0 && return abs(a)
|  6      za = trailing_zeros(a)
|
--------------------------------------------------
|AAAAAAAAAAAAAAAAAAAAAAAAAAAA
|AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|AAAAAA
|AAAAAAAA
|AAAAAAAA
|AAAAAAAAAAA
|AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|


================================================
FILE: test/ui/v1.5/gcd.multiout
================================================
++++++++++++++++++++++++++++++++++++++++++++++++++
|my_gcd(10, 20)
|my_gcd(a::T, b::T) where T<:Union{Int128, Int16, Int32, Int64, Int8, UInt128, UI
|nt16, UInt32, UInt64, UInt8} in Main at ****
|  a = 10
|  b = 20
|  T = Int64
|  3  function my_gcd(a::T, b::T) where T <: Union{Int8, UInt8, Int16, UInt16, …
|  4      a == 0 && return abs(b)
|  5      b == 0 && return abs(a)
|  6      za = trailing_zeros(a)
|
--------------------------------------------------
|AAAAAAAAAAAAAA
|AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|AAAAAA
|AAAAAAAA
|AAAAAAAA
|AAAAAAAAAAA
|AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|


================================================
FILE: test/ui/v1.5/gcdsc.multiout
================================================
++++++++++++++++++++++++++++++++++++++++++++++++++
|__gcdval__ = my_gcd(10, 20);
|my_gcd(a::T, b::T) where T<:Union{Int128, Int16, Int32, Int64, Int8, UInt128, UI
|nt16, UInt32, UInt64, UInt8} in Main at ****
|  a = 10
|  b = 20
|  T = Int64
|  3  function my_gcd(a::T, b::T) where T <: Union{Int8, UInt8, Int16, UInt16, …
|  4      a == 0 && return abs(b)
|  5      b == 0 && return abs(a)
|  6      za = trailing_zeros(a)
|
--------------------------------------------------
|AAAAAAAAAAAAAAAAAAAAAAAAAAAA
|AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|AAAAAA
|AAAAAAAA
|AAAAAAAA
|AAAAAAAAAAA
|AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
Download .txt
gitextract_4j1i8l0j/

├── .github/
│   └── workflows/
│       ├── Documenter.yml
│       ├── TagBot.yml
│       └── ci.yml
├── .gitignore
├── LICENSE.md
├── Project.toml
├── README.md
├── docs/
│   ├── Project.toml
│   ├── make.jl
│   └── src/
│       ├── config.md
│       ├── index.md
│       ├── internals.md
│       ├── limitations.md
│       ├── reference.md
│       └── usage.md
├── src/
│   ├── Rebugger.jl
│   ├── debug.jl
│   ├── deepcopy.jl
│   ├── precompile.jl
│   ├── printing.jl
│   └── ui.jl
└── test/
    ├── edit.jl
    ├── interpret.jl
    ├── interpret_script.jl
    ├── interpret_ui.jl
    ├── my_gcd.jl
    ├── runtests.jl
    ├── testmodule.jl
    └── ui/
        ├── v1.0/
        │   ├── gcd.multiout
        │   └── gcdsc.multiout
        ├── v1.1/
        │   ├── gcd.multiout
        │   └── gcdsc.multiout
        ├── v1.2/
        │   ├── gcd.multiout
        │   └── gcdsc.multiout
        ├── v1.3/
        │   ├── gcd.multiout
        │   └── gcdsc.multiout
        └── v1.5/
            ├── gcd.multiout
            └── gcdsc.multiout
Condensed preview — 38 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (156K chars).
[
  {
    "path": ".github/workflows/Documenter.yml",
    "chars": 422,
    "preview": "name: Documenter\non:\n  push:\n    branches: [master]\n    tags: [v*]\n  pull_request:\n\njobs:\n  Documenter:\n    name: Docume"
  },
  {
    "path": ".github/workflows/TagBot.yml",
    "chars": 362,
    "preview": "name: TagBot\non:\n  issue_comment:\n    types:\n      - created\n  workflow_dispatch:\njobs:\n  TagBot:\n    if: github.event_n"
  },
  {
    "path": ".github/workflows/ci.yml",
    "chars": 1226,
    "preview": "name: CI\non:\n  pull_request:\n    branches:\n      - master\n  push:\n    branches:\n      - master\n    tags: '*'\njobs:\n  tes"
  },
  {
    "path": ".gitignore",
    "chars": 57,
    "preview": "docs/build/\ndocs/site/\ntest/expected.out\ntest/failed.out\n"
  },
  {
    "path": "LICENSE.md",
    "chars": 1159,
    "preview": "The Rebugger.jl package is licensed under the MIT \"Expat\" License:\n\n> Copyright (c) 2018: Tim Holy.\n>\n> Permission is he"
  },
  {
    "path": "Project.toml",
    "chars": 845,
    "preview": "name = \"Rebugger\"\nuuid = \"ee283ea6-eecd-56e3-beb3-83eb4d3c31e9\"\nversion = \"0.3.3\"\n\n[deps]\nCodeTracking = \"da1fd8a2-8d9e-"
  },
  {
    "path": "README.md",
    "chars": 3354,
    "preview": "# Rebugger\n\n[![Build Status](https://travis-ci.org/timholy/Rebugger.jl.svg?branch=master)](https://travis-ci.org/timholy"
  },
  {
    "path": "docs/Project.toml",
    "chars": 138,
    "preview": "[deps]\nDocumenter = \"e30172f5-a6a5-5a46-863b-614d45cd2de4\"\nRevise = \"295af30f-e4ad-537b-8983-00126c2a3abe\"\n\n[compat]\nDoc"
  },
  {
    "path": "docs/make.jl",
    "chars": 489,
    "preview": "using Documenter, Rebugger\n\nmakedocs(\n    modules = [Rebugger],\n    clean = false,\n    format = Documenter.HTML(prettyur"
  },
  {
    "path": "docs/src/config.md",
    "chars": 2501,
    "preview": "# Configuration\n\n## Run on REPL startup\n\nIf you decide you like Rebugger, you can add lines such as the following to you"
  },
  {
    "path": "docs/src/index.md",
    "chars": 2375,
    "preview": "# Introduction to Rebugger\n\nRebugger is an expression-level debugger for Julia.\nIt has two modes of action:\n\n- an \"inter"
  },
  {
    "path": "docs/src/internals.md",
    "chars": 6036,
    "preview": "# How Rebugger works\n\nRebugger traces execution through use of expression-rewriting and Julia's ordinary\n`try/catch` con"
  },
  {
    "path": "docs/src/limitations.md",
    "chars": 1697,
    "preview": "# Limitations\n\nRebugger is in the early stages of development, and users should currently expect bugs (please do [report"
  },
  {
    "path": "docs/src/reference.md",
    "chars": 352,
    "preview": "# Developer reference\n\n## Capturing arguments\n\n```@docs\nRebugger.stepin\nRebugger.prepare_caller_capture!\nRebugger.method"
  },
  {
    "path": "docs/src/usage.md",
    "chars": 24697,
    "preview": "# Usage\n\nRebugger works from Julia's native REPL prompt. Currently there are exactly three keybindings,\nwhich here will "
  },
  {
    "path": "src/Rebugger.jl",
    "chars": 2244,
    "preview": "module Rebugger\n\nusing UUIDs, InteractiveUtils\nusing REPL\nimport REPL.LineEdit, REPL.Terminals\nusing REPL.LineEdit: buff"
  },
  {
    "path": "src/debug.jl",
    "chars": 24815,
    "preview": "# Core debugging logic.\n# Hopefully someday much of this will be replaced by Gallium.\n\nconst VarnameType = Tuple{Vararg{"
  },
  {
    "path": "src/deepcopy.jl",
    "chars": 3949,
    "preview": "# Because `deepcopy(mod::Module)` throws an error, we need a safe approach.\n# Strategy: wrap the IdDict so that our meth"
  },
  {
    "path": "src/precompile.jl",
    "chars": 200,
    "preview": "function _precompile_()\n    ccall(:jl_generating_output, Cint, ()) == 1 || return nothing\n    precompile(Tuple{typeof(ge"
  },
  {
    "path": "src/printing.jl",
    "chars": 13732,
    "preview": "# A type for keeping track of the current line number when printing Exprs\nstruct LineNumberIO <: IO\n    io::IO\n    linen"
  },
  {
    "path": "src/ui.jl",
    "chars": 19835,
    "preview": "const rebug_prompt_string = \"rebug> \"\nconst rebug_prompt_ref = Ref{Union{LineEdit.Prompt,Nothing}}(nothing)       # set "
  },
  {
    "path": "test/edit.jl",
    "chars": 21458,
    "preview": "using Rebugger\nusing Rebugger: StopException\nusing Test, UUIDs, InteractiveUtils, REPL, Pkg, HeaderREPLs\nusing REPL.Line"
  },
  {
    "path": "test/interpret.jl",
    "chars": 2225,
    "preview": "using Rebugger, JuliaInterpreter\nusing CodeTracking, Revise, Test, InteractiveUtils\n\nif !isdefined(Main, :fixable1)\n    "
  },
  {
    "path": "test/interpret_script.jl",
    "chars": 791,
    "preview": "# Test functions for parsing\nfunction fixable1(x)\n    return x\nend\nfixable2(x) = x\nfunction fixable3(A)\n    s = 0\n    fi"
  },
  {
    "path": "test/interpret_ui.jl",
    "chars": 3736,
    "preview": "# This was copied from Debugger.jl and then modified\n\nusing TerminalRegressionTests, Rebugger, Revise, CodeTracking\nusin"
  },
  {
    "path": "test/my_gcd.jl",
    "chars": 668,
    "preview": "# From base, but copied here to make sure we don't fail bacause base changed\nfunction my_gcd(a::T, b::T) where T<:Union{"
  },
  {
    "path": "test/runtests.jl",
    "chars": 262,
    "preview": "using Rebugger, Test\n\n@info \"These tests manipulate the console. Wait until you see \\\"Done\\\"\"\ninclude(\"edit.jl\")\ninclude"
  },
  {
    "path": "test/testmodule.jl",
    "chars": 1060,
    "preview": "module RebuggerTesting\n\nconst cbdata1 = Ref{Any}(nothing)\nconst cbdata2 = Ref{Any}(nothing)\n\n# Do not alter the line num"
  },
  {
    "path": "test/ui/v1.0/gcd.multiout",
    "chars": 831,
    "preview": "++++++++++++++++++++++++++++++++++++++++++++++++++\n|gcd(10, 20)\n|gcd(a::T, b::T) where T<:Union{Int128, Int16, Int32, In"
  },
  {
    "path": "test/ui/v1.0/gcdsc.multiout",
    "chars": 859,
    "preview": "++++++++++++++++++++++++++++++++++++++++++++++++++\n|__gcdval__ = gcd(10, 20);\n|gcd(a::T, b::T) where T<:Union{Int128, In"
  },
  {
    "path": "test/ui/v1.1/gcd.multiout",
    "chars": 831,
    "preview": "++++++++++++++++++++++++++++++++++++++++++++++++++\n|gcd(10, 20)\n|gcd(a::T, b::T) where T<:Union{Int128, Int16, Int32, In"
  },
  {
    "path": "test/ui/v1.1/gcdsc.multiout",
    "chars": 859,
    "preview": "++++++++++++++++++++++++++++++++++++++++++++++++++\n|__gcdval__ = gcd(10, 20);\n|gcd(a::T, b::T) where T<:Union{Int128, In"
  },
  {
    "path": "test/ui/v1.2/gcd.multiout",
    "chars": 809,
    "preview": "++++++++++++++++++++++++++++++++++++++++++++++++++\n|gcd(10, 20)\n|gcd(a::T, b::T) where T<:Union{Int128, Int16, Int32, In"
  },
  {
    "path": "test/ui/v1.2/gcdsc.multiout",
    "chars": 837,
    "preview": "++++++++++++++++++++++++++++++++++++++++++++++++++\n|__gcdval__ = gcd(10, 20);\n|gcd(a::T, b::T) where T<:Union{Int128, In"
  },
  {
    "path": "test/ui/v1.3/gcd.multiout",
    "chars": 868,
    "preview": "++++++++++++++++++++++++++++++++++++++++++++++++++\n|my_gcd(10, 20)\n|my_gcd(a::T, b::T) where T<:Union{Int128, Int16, Int"
  },
  {
    "path": "test/ui/v1.3/gcdsc.multiout",
    "chars": 896,
    "preview": "++++++++++++++++++++++++++++++++++++++++++++++++++\n|__gcdval__ = my_gcd(10, 20);\n|my_gcd(a::T, b::T) where T<:Union{Int1"
  },
  {
    "path": "test/ui/v1.5/gcd.multiout",
    "chars": 868,
    "preview": "++++++++++++++++++++++++++++++++++++++++++++++++++\n|my_gcd(10, 20)\n|my_gcd(a::T, b::T) where T<:Union{Int128, Int16, Int"
  },
  {
    "path": "test/ui/v1.5/gcdsc.multiout",
    "chars": 896,
    "preview": "++++++++++++++++++++++++++++++++++++++++++++++++++\n|__gcdval__ = my_gcd(10, 20);\n|my_gcd(a::T, b::T) where T<:Union{Int1"
  }
]

About this extraction

This page contains the full source code of the timholy/Rebugger.jl GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 38 files (145.7 KB), approximately 40.6k tokens. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!