[
  {
    "path": ".github/workflows/Documenter.yml",
    "content": "name: Documenter\non:\n  push:\n    branches: [master]\n    tags: [v*]\n  pull_request:\n\njobs:\n  Documenter:\n    name: Documentation\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v2\n      - uses: julia-actions/julia-buildpkg@latest\n      - uses: julia-actions/julia-docdeploy@latest\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n          DOCUMENTER_KEY: ${{ secrets.DOCUMENTER_KEY }}\n"
  },
  {
    "path": ".github/workflows/TagBot.yml",
    "content": "name: TagBot\non:\n  issue_comment:\n    types:\n      - created\n  workflow_dispatch:\njobs:\n  TagBot:\n    if: github.event_name == 'workflow_dispatch' || github.actor == 'JuliaTagBot'\n    runs-on: ubuntu-latest\n    steps:\n      - uses: JuliaRegistries/TagBot@v1\n        with:\n          token: ${{ secrets.GITHUB_TOKEN }}\n          ssh: ${{ secrets.DOCUMENTER_KEY }}\n"
  },
  {
    "path": ".github/workflows/ci.yml",
    "content": "name: CI\non:\n  pull_request:\n    branches:\n      - master\n  push:\n    branches:\n      - master\n    tags: '*'\njobs:\n  test:\n    name: Julia ${{ matrix.version }} - ${{ matrix.os }} - ${{ matrix.arch }} - ${{ github.event_name }}\n    runs-on: ${{ matrix.os }}\n    strategy:\n      fail-fast: false\n      matrix:\n        version:\n          - '1.0'\n          - '1'\n          - 'nightly'\n        os:\n          - ubuntu-latest\n        arch:\n          - x64\n    steps:\n      - uses: actions/checkout@v2\n      - uses: julia-actions/setup-julia@v1\n        with:\n          version: ${{ matrix.version }}\n          arch: ${{ matrix.arch }}\n      - uses: actions/cache@v1\n        env:\n          cache-name: cache-artifacts\n        with:\n          path: ~/.julia/artifacts\n          key: ${{ runner.os }}-test-${{ env.cache-name }}-${{ hashFiles('**/Project.toml') }}\n          restore-keys: |\n            ${{ runner.os }}-test-${{ env.cache-name }}-\n            ${{ runner.os }}-test-\n            ${{ runner.os }}-\n      - uses: julia-actions/julia-buildpkg@v1\n      - uses: julia-actions/julia-runtest@v1\n      - uses: julia-actions/julia-processcoverage@v1\n      - uses: codecov/codecov-action@v1\n        with:\n          file: lcov.info\n"
  },
  {
    "path": ".gitignore",
    "content": "docs/build/\ndocs/site/\ntest/expected.out\ntest/failed.out\n"
  },
  {
    "path": "LICENSE.md",
    "content": "The Rebugger.jl package is licensed under the MIT \"Expat\" License:\n\n> Copyright (c) 2018: Tim Holy.\n>\n> Permission is hereby granted, free of charge, to any person obtaining a copy\n> of this software and associated documentation files (the \"Software\"), to deal\n> in the Software without restriction, including without limitation the rights\n> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n> copies of the Software, and to permit persons to whom the Software is\n> furnished to do so, subject to the following conditions:\n>\n> The above copyright notice and this permission notice shall be included in all\n> copies or substantial portions of the Software.\n>\n> THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n> SOFTWARE.\n>\n"
  },
  {
    "path": "Project.toml",
    "content": "name = \"Rebugger\"\nuuid = \"ee283ea6-eecd-56e3-beb3-83eb4d3c31e9\"\nversion = \"0.3.3\"\n\n[deps]\nCodeTracking = \"da1fd8a2-8d9e-5ec2-8556-3022fb5608a2\"\nHeaderREPLs = \"54d51984-71c9-52bd-8df9-6718e63e4153\"\nInteractiveUtils = \"b77e0a4c-d291-57a0-90e8-8db25a27a240\"\nJuliaInterpreter = \"aa1ae85d-cabe-5617-a682-6adf51b2e16a\"\nREPL = \"3fa0cd96-eef1-5676-8a61-b3b8758bbffb\"\nRevise = \"295af30f-e4ad-537b-8983-00126c2a3abe\"\nUUIDs = \"cf7118a7-6976-5b1a-9a39-7adc72f591a4\"\n\n[compat]\nCodeTracking = \"0.5\"\nHeaderREPLs = \"0.3\"\nJuliaInterpreter = \"0.7\"\nRevise = \"2.1.10\"\njulia = \"1\"\n\n[extras]\nColors = \"5ae59095-9a9b-59fe-a467-6f913c188581\"\nPkg = \"44cfe95a-1eb2-52ea-b672-e2afdf69b78f\"\nTerminalRegressionTests = \"98bfdc55-cc95-5876-a49a-74609291cbe0\"\nTest = \"8dfed614-e22c-5e08-85e1-65c5234f0b40\"\n\n[targets]\ntest = [\"Test\", \"Colors\", \"Pkg\", \"TerminalRegressionTests\"]\n"
  },
  {
    "path": "README.md",
    "content": "# Rebugger\n\n[![Build Status](https://travis-ci.org/timholy/Rebugger.jl.svg?branch=master)](https://travis-ci.org/timholy/Rebugger.jl)\n[![Build status](https://ci.appveyor.com/api/projects/status/e9t1wlyy995whchc?svg=true)](https://ci.appveyor.com/project/timholy/Rebugger-jl/branch/master)\n[![codecov.io](http://codecov.io/github/timholy/Rebugger.jl/coverage.svg?branch=master)](http://codecov.io/github/timholy/Rebugger.jl?branch=master)\n[![PkgEval][pkgeval-img]][pkgeval-url]\n\n\nRebugger is an expression-level debugger for Julia.\nIt has no ability to interact with or manipulate call stacks (see [Debugger](https://github.com/JuliaDebug/Debugger.jl) or the debugger built into vscode),\nbut it can trace execution via the manipulation of Julia expressions.\n\nThe name \"Rebugger\" has 3 meanings:\n\n- it is a REPL-based debugger (more on that in the documentation)\n- it is the [Revise](https://github.com/timholy/Revise.jl)-based debugger\n- it supports repeated-execution debugging\n\n### Current status\n\nCurrently broken and unmaintained due to the author having too many other packages to maintain. However, the functionality and general concept is still quite attractive.\nFor anyone interested in taking over maintenance, see [issue #90](https://github.com/timholy/Rebugger.jl/issues/90) for more information.\n\n### JuliaCon 2018 Talk\n\nWhile it's somewhat dated, you can learn about the \"edit\" interface in the following video:\n[![](https://img.youtube.com/vi/KuM0AGaN09s/0.jpg)](https://youtu.be/KuM0AGaN09s?t=515)\n\nHowever, the \"interpret\" interface is recommended for most users.\n\n## Installation and usage\n\n**See the documentation**:\n\n[![](https://img.shields.io/badge/docs-stable-blue.svg)](https://timholy.github.io/Rebugger.jl/stable)\n[![](https://img.shields.io/badge/docs-latest-blue.svg)](https://timholy.github.io/Rebugger.jl/dev)\n\nNote that Rebugger may benefit from custom configuration, as described in the documentation.\n\nIn terms of usage, very briefly\n\n- for \"interpret\" mode, type your command and hit <kbd> Meta-i </kbd> (which stands for \"interpret\")\n- for \"edit\" mode, \"step in\" is achieved by positioning your cursor in your input line to the beginning of\n  the call expression you wish to descend into. Then hit <kbd> Meta-e </kbd> (\"enter\").\n- also for \"edit\" mode, for an expression that generates an error, hit <kbd> Meta-s </kbd> (\"stacktrace\")\n  to capture the stacktrace and populate your REPL history with a sequence of expressions\n  that contain the method bodies of the calls in the stacktrace.\n\n<kbd> Meta </kbd> means <kbd> Esc </kbd> or, if your system is configured appropriately,\n<kbd> Alt </kbd> (Linux/Windows) or <kbd> Option </kbd> (Macs).\nMore information and complete examples are provided in the documentation.\nIf 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).\n\n## Status\n\nRebugger is in early stages of development, and users should currently expect bugs (please do [report](https://github.com/timholy/Rebugger.jl/issues) them).\nNeverthess it may be of net benefit for some users.\n\n[pkgeval-img]: https://juliaci.github.io/NanosoldierReports/pkgeval_badges/R/Rebugger.svg\n[pkgeval-url]: https://juliaci.github.io/NanosoldierReports/pkgeval_badges/report.html\n"
  },
  {
    "path": "docs/Project.toml",
    "content": "[deps]\nDocumenter = \"e30172f5-a6a5-5a46-863b-614d45cd2de4\"\nRevise = \"295af30f-e4ad-537b-8983-00126c2a3abe\"\n\n[compat]\nDocumenter = \"~0.21\"\n"
  },
  {
    "path": "docs/make.jl",
    "content": "using Documenter, Rebugger\n\nmakedocs(\n    modules = [Rebugger],\n    clean = false,\n    format = Documenter.HTML(prettyurls = get(ENV, \"CI\", nothing) == \"true\"),\n    sitename = \"Rebugger.jl\",\n    authors = \"Tim Holy\",\n    linkcheck = !(\"skiplinks\" in ARGS),\n    pages = [\n        \"Home\" => \"index.md\",\n        \"usage.md\",\n        \"config.md\",\n        \"limitations.md\",\n        \"internals.md\",\n        \"reference.md\",\n    ],\n)\n\ndeploydocs(\n    repo = \"github.com/timholy/Rebugger.jl.git\",\n)\n"
  },
  {
    "path": "docs/src/config.md",
    "content": "# Configuration\n\n## Run on REPL startup\n\nIf you decide you like Rebugger, you can add lines such as the following to your\n`~/.julia/config/startup.jl` file:\n\n```julia\natreplinit() do repl\n    try\n        @eval using Revise\n        @async Revise.wait_steal_repl_backend()\n    catch\n        @warn \"Could not load Revise.\"\n    end\n\n    try\n        @eval using Rebugger\n    catch\n        @warn \"Could not load Rebugger.\"\n    end\nend\n```\n\n## Customize keybindings\n\nAs described in [Keyboard shortcuts](@ref), it's possible that Rebugger's default keybindings\ndon't work for you.\nYou can work around problems by changing them to keys of your own choosing.\n\nTo add your own keybindings, use `Rebugger.add_keybindings(action=keybinding, ...)`.\nThis can be done during a running Rebugger session. Here is an example that\nmaps the \"step in\" action to the key \"F6\" and \"capture stacktrace\" to \"F7\"\n\n```julia\njulia> Rebugger.add_keybindings(stepin=\"\\e[17~\", stacktrace=\"\\e[18~\")\n```\n\nTo make your keybindings permanent, change the \"Rebugger\" section of your `startup.jl` file\nto something like:\n```julia\natreplinit() do repl\n    ...\n\n    try\n        @eval using Rebugger\n        # Activate Rebugger's key bindings\n        Rebugger.keybindings[:stepin] = \"\\e[17~\"      # Add the keybinding F6 to step into a function.\n        Rebugger.keybindings[:stacktrace] = \"\\e[18~\"  # Add the keybinding F7 to capture a stacktrace.\n    catch\n        @warn \"Could not load Rebugger.\"\n    end\nend\n```\n\n!!! note\n\n    Besides the obvious, one reason to insert the keybindings into the `startup.jl`,\n    has to do with the order in which keybindings are added to the REPL and whether any\n    \"stale\" bindings that might have side effects are still present.\n    Doing it before `atreplinit` means that there won't be any stale bindings.\n\nBut how to find out the cryptic string that corresponds to the keybinding you\nwant? Use Julia's `read()` function:\n\n```julia\njulia> str = read(stdin, String)\n^[[17~\"\\e[17~\"  # Press F6, followed by Ctrl+D, Ctrl+D\n\njulia> str\n\"\\e[17~\"\n```\n\nAfter calling `read()`, press the keybinding that you want. Then, press `Ctrl+D`\ntwice to terminate the input. The value of `str` is the cryptic string you are\nlooking for.\n\nIf you want to know whether your key binding is already taken, the\n[REPL documentation](https://docs.julialang.org/en/latest/stdlib/REPL/#Key-bindings-1)\nas well as any documentation on your operating system, desktop environment, and/or\nterminal program can be useful references.\n"
  },
  {
    "path": "docs/src/index.md",
    "content": "# Introduction to Rebugger\n\nRebugger is an expression-level debugger for Julia.\nIt has two modes of action:\n\n- an \"interpret\" mode that lets you step through code, set\n  breakpoints, and other manipulations common to \"typical\" debuggers;\n- an \"edit\" mode that presents method bodies as objects for manipulation,\n  allowing you to interactively play with the code at different stages\n  of execution.\n\n\nThe name \"Rebugger\" has 3 meanings:\n\n- it is a [REPL](https://docs.julialang.org/en/latest/stdlib/REPL/)-based debugger (more on that below)\n- it is the [Revise](https://github.com/timholy/Revise.jl)-based debugger\n- it supports repeated-execution debugging\n\n## Installation\n\nBegin with\n\n```julia\n(v1.0) pkg> add Rebugger\n```\n\nYou can experiment with Rebugger with just\n\n```julia\njulia> using Rebugger\n```\n\nIf you eventually decide you like Rebugger, you can optionally configure it so that it\nis always available (see [Configuration](@ref)).\n\n## Keyboard shortcuts\n\nMost of Rebugger's functionality gets triggered by special keyboard shortcuts added to Julia's REPL.\nUnfortunately, different operating systems and desktop environments vary considerably in\ntheir key bindings, and it is possible that the default choices in Rebugger are\nalready assigned other meanings on your platform.\nThere does not appear to be any one set of choices that works on all platforms.\n\nThe best strategy is to try the demos in [Usage](@ref); if the default shortcuts\nare already taken on your platform, then you can easily configure Rebugger\nto use different bindings (see [Configuration](@ref)).\n\nSome platforms are known to require or benefit from special attention:\n\n#### macOS\n\nIf you're on macOS, you may want to enable\n\"[Use `option` as the Meta key](https://github.com/timholy/Rebugger.jl/issues/28#issuecomment-414852133)\"\nin your Terminal settings to avoid the need to press Esc before each Rebugger command.\n\n#### Ubuntu\n\nThe default meta key on some Ubuntu versions is left Alt, which is equivalent to Esc Alt on the default\nGnome terminal emulator.\nHowever, even with this tip you may encounter problems because Rebugger's default key bindings\nmay be assigned to activate menu options within the terminal window, and\n[this appears not to be configurable]( https://bugs.launchpad.net/ubuntu/+source/nautilus/+bug/1113420).\nAffected users may wish to [Customize keybindings](@ref).\n"
  },
  {
    "path": "docs/src/internals.md",
    "content": "# How Rebugger works\n\nRebugger traces execution through use of expression-rewriting and Julia's ordinary\n`try/catch` control-flow.\nIt maintains internal storage that allows other methods to \"deposit\" their arguments\n(a *store*) or temporarily *stash* the function and arguments of a call.\n\n## Implementation of \"step in\"\n\nRebugger makes use of the buffer containing user input: not just its contents, but also\nthe position of \"point\" (the seek position) to indicate the specific call expression\ntargeted for stepping in.\n\nFor example, if a buffer has contents\n\n```julia\n    # <some code>\n    if x > 0.5\n        ^fcomplex(x, 2; kw1=1.1)\n        # <more code>\n```\n\nwhere in the above `^` indicates \"point,\" Rebugger uses a multi-stage process\nto enter `fcomplex` with appropriate arguments:\n\n- First, it carries out *caller capture* to determine which function is being called\n  at point, and with which arguments. The main goal here is to be able to then use\n  `which` to determine the specific method.\n- Once armed with the specific method, it then carries out *callee capture* to\n  obtain all the inputs to the method. For simple methods this may be redundant\n  with *caller capture*, but *callee capture* can also obtain the values of\n  default arguments, keyword arguments, and type parameters.\n- Finally, Rebugger rewrites the REPL command-line buffer with a suitably-modified\n  version of the body of the called method, so that the user can inspect, run, and\n  manipulate it.\n\n### Caller capture\n\nThe original expression above is rewritten as\n\n```julia\n    # <some code>\n    if x > 0.5\n        Main.Rebugger.stashed[] = (fcomplex, (x, 2), (kw1=1.1,))\n        throw(Rebugger.StopException())\n        # <more code>\n```\n\nNote that the full context of the original expression is preserved, thereby ensuring\nthat we do not have to be concerned about not having the appropriate local scope for\nthe arguments to the call of `fcomplex`.\nHowever, rather than actually calling `fcomplex`, this expression \"stashes\" the\narguments and function in a temporary store internal to Rebugger.\nIt then throws an exception type specifically crafted to signal that the expression\nexecuted and exited as expected.\n\nThis expression is then evaluated inside a block\n\n```julia\n    try\n        Core.eval(Main, caller_capture_expression)\n        throw(StashingFailed())\n    catch err\n        err isa StashingFailed && rethrow(err)\n        if !(err isa StopException)\n            throw(EvalException(content(buffer), err))\n        end\n    end\n```\n\nNote that this looks for the `StopException`; this is considered the normal execution\npath.\nIf the `StopException` is never hit, it means evaluation never reached the expression\nmarked by \"point\" and thus leads to a `StashingFailed` exception.\nAny other error results in an `EvalException`, usually triggered by other errors\nin the block of code.\n\nAssuming the `StopException` is hit, we then proceed to callee capture.\n\n### Callee capture\n\nRebugger removes the function and arguments from `Rebugger.stashed[]` and then uses\n`which` to determine the specific method called.\nIt then asks [Revise](https://timholy.github.io/Revise.jl/stable/) for the expression\nthat defines the method.\nIt then analyzes the signature to determine the full complement of inputs and creates\na new method that stores them. For example, if the applicable method of `fcomplex` is\ngiven by\n\n```julia\n    function fcomplex(x::A, y=1, z=\"\"; kw1=3.2) where A<:AbstractArray{T} where T\n        # <body>\n    end\n```\n\nthen Rebugger generates a new method\n\n```julia\n    function hidden_fcomplex(x::A, y=1, z=\"\"; kw1=3.2) where A<:AbstractArray{T} where T\n        Main.Rebugger.stored[uuid] = Main.Rebugger.Stored(fcomplex, (:x, :y, :z, :kw1, :A, :T), deepcopy((x, y, z, kw1, A, T)))\n        throw(StopException())\n    end\n```\n\nThis method is then called from inside another `try/catch` block that again checks for a `StopException`.\nThis results in the complete set of inputs being *stored*, a more \"permanent\" form\nof preservation than *stashing*, which only lasts for the gap between caller and callee capture.\nIf one has the appropriate `uuid`, one can then extract these values at will from storage\nusing [`Rebugger.getstored`](@ref).\n\n### Generating the new buffer contents (the `let` expression)\n\nOnce callee capture is complete, the user can re-execute any components of the called method\nas desired. To make this easier, Rebugger replaces the contents of the buffer with a line that\nlooks like this:\n\n```julia\n@eval <ModuleOf_fcomplex> let (x, y, z, kw1, A, T) = Main.Rebugger.getstored(\"0123abc...\")\n    # <body>\nend\n```\n\nThe `@eval` makes sure that the block will be executed within the module in which\n`fcomplex` is defined; as a consequence it will have access to all the unexported methods,\netc., that `fcomplex` itself has.\nThe `let` block ensures that these variables do not conflict with other objects that\nmay be defined in `ModuleOf_fcomplex`.\nThe values are unloaded from the store (making copies, in case `fcomplex` modifies its\ninputs) and then execution proceeds into `body`.\n\nThe user can then edit the buffer at will.\n\n## Implementation of \"catch stacktrace\"\n\nIn contrast with \"step in,\" when catching a stacktrace Rebugger does not know the specific\nmethods that will be used in advance of making the call.\nConsequently, Rebugger has to execute the call twice:\n\n- the first call is used to obtain a stacktrace\n- The trace is analyzed to obtain the specific methods, which are then replaced with versions\n  that place inputs in storage; see [Callee capture](@ref), with the differences\n  + the original method is (temporarily) overwritten by one that executes the store\n  + this \"storing\" method also includes the full method body\n  These two changes ensure that the \"call chain\" is not broken.\n- a second call (recreating the same error, for functions that have deterministic execution)\n  is then made to store all the arguments at each captured stage of the stacktrace.\n- finally, the original methods are restored.\n"
  },
  {
    "path": "docs/src/limitations.md",
    "content": "# Limitations\n\nRebugger is in the early stages of development, and users should currently expect bugs (please do [report](https://github.com/timholy/Rebugger.jl/issues) them).\nNevertheless it may be of net benefit for some users.\n\nHere are some known shortcomings:\n\n- Rebugger only has access to code tracked by Revise.\n  To ensure that scripts are tracked, use `includet(filename)` to include-and-track.\n  (See [Revise's documentation](https://timholy.github.io/Revise.jl/stable/user_reference.html).)\n  For stepping into Julia's stdlibs, currently you need a source-build of Julia.\n- You cannot step into methods defined at the REPL.\n- For now you can't step into constructors (it tries to step into `(::Type{T})`)\n- There are occasional glitches in the display.\n  (For brave souls who want to help fix these,\n  see [HeaderREPLs.jl](https://github.com/timholy/HeaderREPLs.jl))\n- Rebugger runs best in Julia 1.0. While it should run on Julia 0.7,\n  a local-scope deprecation can cause some\n  problems. If you want 0.7 because of its deprecation warnings and are comfortable\n  building Julia, consider building it at commit\n  f08f3b668d222042425ce20a894801b385c2b1e2, which removed the local-scope deprecation\n  but leaves most of the other deprecation warnings from 0.7 still in place.\n- If you start `dev`ing a package that you had already loaded, you need to [restart\n  your session](https://github.com/timholy/Revise.jl/issues/146)\n\nAnother important point (not particularly specific to Rebugger) is that\nrepeatedly executing code that modifies some global state\ncan lead to unexpected side effects.\nRebugger works best on methods whose behavior is determined solely by their input\narguments.\n"
  },
  {
    "path": "docs/src/reference.md",
    "content": "# Developer reference\n\n## Capturing arguments\n\n```@docs\nRebugger.stepin\nRebugger.prepare_caller_capture!\nRebugger.method_capture_from_callee\nRebugger.signature_names!\n```\n\n## Capturing stacktrace\n\n```@docs\nRebugger.capture_stacktrace\nRebugger.pregenerated_stacktrace\nRebugger.linerange\n```\n\n## Utilities\n\n```@docs\nRebugger.clear\nRebugger.getstored\n```\n"
  },
  {
    "path": "docs/src/usage.md",
    "content": "# Usage\n\nRebugger works from Julia's native REPL prompt. Currently there are exactly three keybindings,\nwhich here will be described as:\n\n- Meta-i, which maps to \"interpret\"\n- Meta-e, which maps to \"enter\" or \"step in\"\n- Meta-s, which maps to \"stacktrace\" (for commands that throw an error)\n\nMeta often maps to `Esc`, and if using `Esc` you should hit the two keys in\nsequence rather than simultaneously.\nFor many users `Alt` (sometimes specifically `Left-Alt`, or `Option` on macs) may be more convenient, as it can be pressed\nsimultaneously with the key.\n\nOf course, you may have configured Rebugger to use different key bindings (see [Customize keybindings](@ref)).\n\n## Interpret mode\n\nInterpret mode simulates an IDE debugger at the REPL: rather than entering your commands into\na special prompt, you use single keystrokes to quickly advance through the code.\n\nLet's start with an example:\n\n```julia\njulia> using Rebugger\n\njulia> a = [4, 1, 3, 2];\n```\n\nNow we're going to call `sort`, but don't hit enter:\n```\njulia> sort(a)\n```\n\nInstead, hit Meta-i (Esc-i, Alt-i, or option-i):\n\n```\ninterpret> sort(a)[ Info: tracking Base\n\nsort(v::AbstractArray{T,1} where T) in Base.Sort at sort.jl:742\n  v = [4, 1, 3, 2]\n 742  sort(v::AbstractVector; kws...) = begin\n 742          sort!(copymutable(v); kws...)\n          end\n\n```\n\nThe message informs you that Revise (which is used by Rebugger) is now examining the code\nin Base to extract the definition of `sort`.\nThere's a considerable pause the first time you do this, but later it should generally be faster.\n\nAfter the \"Info\" line, you can see the method you called printed on top.\nAfter that are the local variables of `sort`, which here is just the array you supplied.\n(You can see some screenshots below in the \"edit mode\" section that show these in color.\nThe meaning is the same here.)\nThe \"742\" indicates the line number of \"sort.jl\", where the `sort` method you're calling\nis defined.\nFinally, you'll see a representation of the definition itself. Rebugger typically shows\nyou expressions rather than verbatim text; unlike the text in the original file,\n this works equally well for `@eval`ed functions and generated functions.\n\nThe current line number is printed in yellow; here, that's both lines, since the original\ndefinition was written on a single line.\n\nWe can learn about the possibilities by typing `?`:\n\n```\nCommands:\n  space: next line\n  enter: continue to next breakpoint or completion\n      →: step in to next call\n      ←: finish frame and return to caller\n      ↑: display the caller frame\n      ↓: display the callee frame\n      b: insert breakpoint at current line\n      c: insert conditional breakpoint at current line\n      r: remove breakpoint at current line\n      d: disable breakpoint at current line\n      e: enable breakpoint at current line\n      q: abort (returns nothing)\nsort(v::AbstractArray{T,1} where T) in Base.Sort at sort.jl:742\n  v = [4, 1, 3, 2]\n 742  sort(v::AbstractVector; kws...) = begin\n 742          sort!(copymutable(v); kws...)\n          end\n```\n\nLet's try stepping in to the call: hit the right arrow, at which point you should see\n\n```\nsort(v::AbstractArray{T,1} where T) in Base.Sort at sort.jl:742\n #sort#8(kws, ::Any, v::AbstractArray{T,1} where T) in Base.Sort at sort.jl:742\n  #sort#8 = Base.Sort.#sort#8\n  kws = Base.Iterators.Pairs{Union{},Union{},Tuple{},NamedTuple{(),Tuple{}}}()\n  @_3 = sort\n  v = [4, 1, 3, 2]\n 742  sort(v::AbstractVector; kws...) = begin\n 742          sort!(copymutable(v); kws...)\n          end\n```\n\nWe're now in a \"hidden\" method `#sort#8`, generated automatically by Julia to handle\nkeyword and/or optional arguments. This is what actually contains the main body of `sort`.\nYou'll note the source expression hasn't changed, because it's generated from the same\ndefinition, but that some additional arguments (`kws` and the \"nameless argument\" `@_3`)\nhave appeared.\n\nIf we hit the right arrow again, we enter `copymutable`. Our interest is in stepping further into `sort`,\nso we're not going to bother walking through `copymutable`; hit left arrow, which finishes\nthe current frame and returns to the caller. This should return\nyou to `#sort#8`. Then hit the right arrow again and you should be here:\n\n```\nsort(v::AbstractArray{T,1} where T) in Base.Sort at sort.jl:742\n #sort#8(kws, ::Any, v::AbstractArray{T,1} where T) in Base.Sort at sort.jl:742\n  sort!(v::AbstractArray{T,1} where T) in Base.Sort at sort.jl:682\n   #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\n  #sort!#7 = Base.Sort.#sort!#7\n  alg = Base.Sort.QuickSortAlg()\n  lt = isless\n  by = identity\n  rev = nothing\n  order = Base.Order.ForwardOrdering()\n  @_7 = sort!\n  v = [4, 1, 3, 2]\n 681  function sort!(v::AbstractVector; alg::Algorithm=defalg(v), lt=isless, by=identity, re…\n 682      ordr = ord(lt, by, rev, order)\n 683      if ordr === Forward && (v isa Vector && eltype(v) <: Integer)\n 684          n = length(v)\n```\n\nNow you can see many more arguments. To understand everything you're seeing, sometimes\nit may help to open the source file in an editor (hit 'o' for open) for comparison.\n\nNote that long function bodies are\ntruncated; you only see a few lines around the current execution point.\n\nLine 682 should be highlighted. Hit the space bar and you should advance to 683:\n\n```\nsort(v::AbstractArray{T,1} where T) in Base.Sort at sort.jl:742\n #sort#8(kws, ::Any, v::AbstractArray{T,1} where T) in Base.Sort at sort.jl:742\n  sort!(v::AbstractArray{T,1} where T) in Base.Sort at sort.jl:682\n   #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\n  #sort!#7 = Base.Sort.#sort!#7\n  alg = Base.Sort.QuickSortAlg()\n  lt = isless\n  by = identity\n  rev = nothing\n  order = Base.Order.ForwardOrdering()\n  @_7 = sort!\n  v = [4, 1, 3, 2]\n  ordr = Base.Order.ForwardOrdering()\n 681  function sort!(v::AbstractVector; alg::Algorithm=defalg(v), lt=isless, by=identity, re…\n 682      ordr = ord(lt, by, rev, order)\n 683      if ordr === Forward && (v isa Vector && eltype(v) <: Integer)\n 684          n = length(v)\n 685          if n > 1\n```\n\nYou can see that the code display also advanced by one line.\n\nLet's go forward one more line (hit space) and then hit `b` to insert a breakpoint:\n\n```\nsort(v::AbstractArray{T,1} where T) in Base.Sort at sort.jl:742\n #sort#8(kws, ::Any, v::AbstractArray{T,1} where T) in Base.Sort at sort.jl:742\n  sort!(v::AbstractArray{T,1} where T) in Base.Sort at sort.jl:682\n   #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\n  #sort!#7 = Base.Sort.#sort!#7\n  alg = Base.Sort.QuickSortAlg()\n  lt = isless\n  by = identity\n  rev = nothing\n  order = Base.Order.ForwardOrdering()\n  @_7 = sort!\n  v = [4, 1, 3, 2]\n  ordr = Base.Order.ForwardOrdering()\n  #temp# = true\n 682      ordr = ord(lt, by, rev, order)\n 683      if ordr === Forward && (v isa Vector && eltype(v) <: Integer)\nb684          n = length(v)\n 685          if n > 1\n 686              (min, max) = extrema(v)\n```\n\nThe `b` in the left column indicates an unconditional breakpoint; a `c` would indicate a\nconditional breakpoint.\n\nAt this point, hit Enter to finish the entire command (you should see the result printed\nat the REPL). Now let's run it again, by going back in the REPL history (hit the up arrow)\nand then hitting Meta-i again:\n\n```\ninterpret> sort(a)\nsort(v::AbstractArray{T,1} where T) in Base.Sort at sort.jl:742\n  v = [4, 1, 3, 2]\n 742  sort(v::AbstractVector; kws...) = begin\n 742          sort!(copymutable(v); kws...)\n          end\n```\n\nWe may be back at the beginning, but remember: we set a breakpoint. Hit Enter to\nlet execution move forward:\n\n```\ninterpret> sort(a)\nsort(v::AbstractArray{T,1} where T) in Base.Sort at sort.jl:742\n #sort#8(kws, ::Any, v::AbstractArray{T,1} where T) in Base.Sort at sort.jl:742\n  sort!(v::AbstractArray{T,1} where T) in Base.Sort at sort.jl:682\n   #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\n  #sort!#7 = Base.Sort.#sort!#7\n  alg = Base.Sort.QuickSortAlg()\n  lt = isless\n  by = identity\n  rev = nothing\n  order = Base.Order.ForwardOrdering()\n  @_7 = sort!\n  v = [4, 1, 3, 2]\n  ordr = Base.Order.ForwardOrdering()\n  #temp# = true\n 682      ordr = ord(lt, by, rev, order)\n 683      if ordr === Forward && (v isa Vector && eltype(v) <: Integer)\nb684          n = length(v)\n 685          if n > 1\n 686              (min, max) = extrema(v)\n```\n\nWe're right back at that breakpoint again.\n\nLet's illustrate another example, this time in the context of errors:\n\n```\njulia> convert(UInt, -8)\nERROR: InexactError: check_top_bit(Int64, -8)\nStacktrace:\n [1] throw_inexacterror(::Symbol, ::Any, ::Int64) at ./boot.jl:583\n [2] check_top_bit at ./boot.jl:597 [inlined]\n [3] toUInt64 at ./boot.jl:708 [inlined]\n [4] Type at ./boot.jl:738 [inlined]\n [5] convert(::Type{UInt64}, ::Int64) at ./number.jl:7\n [6] top-level scope at none:0\n```\n\nRebugger re-exports [JuliaInterpreter's breakpoint manipulation utilities](https://juliadebug.github.io/JuliaInterpreter.jl/stable/).\nLet's turn on breakpoints any time an (uncaught) exception is thrown:\n\n```\njulia> break_on(:error)\n```\n\nNow repeat that `convert` line but hit Meta-i instead of Enter:\n\n```\ninterpret> convert(UInt, -8)\nconvert(::Type{T}, x::Number) where T<:Number in Base at number.jl:7\n  #unused# = UInt64\n  x = -8\n  T = UInt64\n 7  (convert(::Type{T}, x::Number) where T <: Number) = begin\n 7          T(x)\n        end\n```\n\nNow if you hit Enter, you'll be at the place where the error was thrown:\n\n```\n\ninterpret> convert(UInt, -8)\nconvert(::Type{T}, x::Number) where T<:Number in Base at number.jl:7\n UInt64(x::Union{Bool, Int32, Int64, UInt32, UInt64, UInt8, Int128, Int16, Int8, UInt128, UInt16}) in Core at boot.jl:738\n  toUInt64(x::Int64) in Core at boot.jl:708\n   check_top_bit(x) in Core at boot.jl:596\n    throw_inexacterror(f::Symbol, T, val) in Core at boot.jl:583\n  f = check_top_bit\n  T = Int64\n  val = -8\n 583  throw_inexacterror(f::Symbol, @nospecialize(T), val) = (@_noinline_meta; throw(Inexact…\n```\n\nTry using the up and down arrows to navigate up and down the call stack. This doesn't\nchange the notion of current execution point (that's still at that `throw_inexacterror` call\nabove), but it does let you see where you came from.\n\nYou can turn this off with `break_off` and clear all manually-set breakpoints with `remove()`.\n\n### A few important details\n\nThere are some calls that you can't step into: most of these are the \"builtins,\"\n\"intrinsics,\" and \"ccalls\" that lie at Julia's lowest level.\nHere's an example from hitting Meta-i on `show`:\n\n```\ninterpret> show([1,2,4])\nshow(x) in Base at show.jl:313\n  x = [1, 2, 4]\n 313  show(x) = begin\n 313          show(stdout::IO, x)\n          end\n\n```\n\nThat looks like a call you'd want to step into. But if you hit the right arrow, apparently\nnothing happens. That's because the next statement is actually that type-assertion `stdout::IO`.\n`typeassert` is a builtin, and consequently not a call you can step into.\n\nWhen in doubt, just repeat the same keystroke; here, the second press of the right arrow\ntakes you to the two-argument `show` method that you probably thought you were descending\ninto.\n\n## Edit mode\n\n### Stepping in\n\nSelect the expression you want to step into by positioning \"point\" (your cursor)\nat the desired location in the command line:\n\n```@raw html\n<img src=\"../images/stepin1.png\" width=\"160px\"/>\n```\n\nIt's essential that point is at the very first character of the expression, in this case on\nthe `s` in `show`.\n\n!!! note\n    Don't confuse the REPL's cursor with your mouse pointer.\n    Your mouse is essentially irrelevant on the REPL; use arrow keys or the other\n    [navigation features of Julia's REPL](https://docs.julialang.org/en/latest/stdlib/REPL/).\n\nNow if you hit Meta-e, you should see something like this:\n\n```@raw html\n<img src=\"../images/stepin2.png\" width=\"660px\"/>\n```\n\n(If not, check [Keyboard shortcuts](@ref) and [Customize keybindings](@ref).)\nThe cyan \"Info\" line is an indication that the method you're stepping into is\na function in Julia's Base module; this is shown by Revise (not Rebugger), and only happens\nonce per session.\n\nThe remaining lines correspond to the Rebugger header and user input.\nThe magenta line tells you which method you are stepping into.\nIndented blue line(s) show the value(s) of any input arguments or type parameters.\n\nIf you're following along, move your cursor to the next `show` call as illustrated above.\nHit Meta-e again. You should see a new `show` method, this time with two input arguments.\n\nNow let's demonstrate another important display item: position point at the\nbeginning of the `_show_empty` call and hit Meta-e.\nThe display should now look like this:\n\n```@raw html\n<img src=\"../images/stepin3.png\" width=\"690px\"/>\n```\n\nThis time, note the yellow/orange line: this is a warning message, and you should pay attention to these.\n(You might also see red lines, which are generally more serious \"errors.\")\nIn this case execution never reached `_show_empty`, because it enters `show_vector` instead;\nif you moved your cursor there, you could trace execution more completely.\n\nYou can edit these expressions to insert code to display variables or test\nchanges to the code.\nAs an experiment, try stepping into the `show_vector` call from the example above\nand adding `@show limited` to display a local variable's value:\n\n```@raw html\n<img src=\"../images/stepin4.png\" width=\"800px\"/>\n```\n\n!!! note\n    When editing expressions, you can insert a blank line with Meta-Enter (i.e., Esc-Enter, Alt-Enter, or Option-Enter).\n    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.\n\nHaving illustrated the importance of \"point\" and the various colors used for messages from Rebugger,\nto ensure readability the remaining examples will be rendered as text.\n\n\n### Capturing stacktraces in edit mode\n\nFor a quick demo, we'll use the `Colors` package (`add` it if you don't have it)\nand deliberately choose a method that will end in an error: we'll try to parse\na string as a Hue, Saturation, Lightness (HSL) color, except we'll \"forget\" that hue\ncannot be expressed as a percentage and deliberately trigger an error:\n\n```julia\njulia> using Colors\n\njulia> colorant\"hsl(80%, 20%, 15%)\"\nERROR: LoadError: hue cannot end in %\nStacktrace:\n [1] error(::String) at ./error.jl:33\n [2] parse_hsl_hue(::SubString{String}) at /home/tim/.julia/dev/Colors/src/parse.jl:26\n [3] _parse_colorant(::String) at /home/tim/.julia/dev/Colors/src/parse.jl:75\n [4] _parse_colorant at /home/tim/.julia/dev/Colors/src/parse.jl:112 [inlined]\n [5] parse(::Type{Colorant}, ::String) at /home/tim/.julia/dev/Colors/src/parse.jl:140\n [6] @colorant_str(::LineNumberNode, ::Module, ::Any) at /home/tim/.julia/dev/Colors/src/parse.jl:147\nin expression starting at REPL[3]:1\n```\n\nTo capture the stacktrace, type the last line again or hit the up arrow, but instead of\npressing Enter, type Meta-s.\nAfter a short delay, you should see something like this:\n\n```julia\njulia> colorant\"hsl(80%, 20%, 15%)\"\n┌ Warning: Tuple{getfield(Colors, Symbol(\"#@colorant_str\")),LineNumberNode,Module,Any} was not found, perhaps it was generated by code\n└ @ Revise ~/.julia/dev/Revise/src/Revise.jl:659\nCaptured elements of stacktrace:\n[1] parse_hsl_hue(num::AbstractString) in Colors at /home/tim/.julia/packages/Colors/4hvzi/src/parse.jl:25\n[2] _parse_colorant(desc::AbstractString) in Colors at /home/tim/.julia/packages/Colors/4hvzi/src/parse.jl:51\n[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\n[4] parse(::Type{C}, desc::AbstractString) where C<:Colorant in Colors at /home/tim/.julia/packages/Colors/4hvzi/src/parse.jl:140\nparse_hsl_hue(num::AbstractString) in Colors at /home/tim/.julia/packages/Colors/4hvzi/src/parse.jl:25\n  num = 80%\nrebug> @eval Colors let (num,) = Main.Rebugger.getstored(\"57dbc76a-0def-11e9-1dbf-ef97d29d2e25\")\n       begin\n           if num[end] == '%'\n               error(\"hue cannot end in %\")\n           else\n               return parse(Int, num, base=10)\n           end\n       end\n       end\n```\n\n(Again, if this doesn't happen check [Keyboard shortcuts](@ref) and [Customize keybindings](@ref).)\nYou are in the method corresponding to `[1]` in the stacktrace.\nNow you can navigate with your up and down arrows to browse the captured stacktrace.\nFor example, if you hit the up arrow twice, you will be in the method corresponding to `[3]`:\n\n```julia\njulia> colorant\"hsl(80%, 20%, 15%)\"\n┌ Warning: Tuple{getfield(Colors, Symbol(\"#@colorant_str\")),LineNumberNode,Module,Any} was not found, perhaps it was generated by code\n└ @ Revise ~/.julia/dev/Revise/src/Revise.jl:659\nCaptured elements of stacktrace:\n[1] parse_hsl_hue(num::AbstractString) in Colors at /home/tim/.julia/packages/Colors/4hvzi/src/parse.jl:25\n[2] _parse_colorant(desc::AbstractString) in Colors at /home/tim/.julia/packages/Colors/4hvzi/src/parse.jl:51\n[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\n[4] parse(::Type{C}, desc::AbstractString) where C<:Colorant in Colors at /home/tim/.julia/packages/Colors/4hvzi/src/parse.jl:140\n[5] @colorant_str(__source__::LineNumberNode, __module__::Module, ex) in Colors at /home/tim/.julia/packages/Colors/4hvzi/src/parse.jl:146\n_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\n  C = Colorant\n  SUP = Any\n  desc = hsl(80%, 20%, 15%)\nrebug> @eval Colors let (C, SUP, desc) = Main.Rebugger.getstored(\"57d9ebc0-0def-11e9-2ab0-e5d1e4c6e82d\")\n       begin\n           _parse_colorant(desc)\n       end\n       end\n```\n\nYou can hit the down arrow and go back to earlier entries in the trace.\nAlternatively, you can pick any of these expressions to execute (hit Enter) or edit before execution.\nYou can use the REPL history to test the results of many different changes to the same \"method\";\nthe \"method\" will be run with the same inputs each time.\n\n!!! note\n    When point is at the end of the input, the up and down arrows step through the history.\n    But if you move point into the method body (e.g., by using left-arrow),\n    the up and down arrows move within the method body.\n    If you've entered edit mode, you can go back to history mode using PgUp and PgDn.\n\n### Important notes\n\n#### \"Missing\" methods from stacktraces\n\nSometimes, there's a large difference between the \"real\" stacktrace and the \"captured\" stacktrace:\n\n```julia\njulia> using Pkg\n\njulia> Pkg.add(\"NoPkg\")\n  Updating registry at `~/.julia/registries/General`\n  Updating git-repo `https://github.com/JuliaRegistries/General.git`\nERROR: The following package names could not be resolved:\n * NoPkg (not found in project, manifest or registry)\nPlease specify by known `name=uuid`.\nStacktrace:\n [1] pkgerror(::String) at /home/tim/src/julia-1.0/usr/share/julia/stdlib/v1.0/Pkg/src/Types.jl:120\n [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\n [3] #ensure_resolved at ./none:0 [inlined]\n [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\n [5] #add_or_develop at ./none:0 [inlined]\n [6] #add_or_develop#12 at /home/tim/src/julia-1.0/usr/share/julia/stdlib/v1.0/Pkg/src/API.jl:29 [inlined]\n [7] #add_or_develop at ./none:0 [inlined]\n [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\n [9] #add_or_develop at ./none:0 [inlined]\n [10] #add_or_develop#10 at /home/tim/src/julia-1.0/usr/share/julia/stdlib/v1.0/Pkg/src/API.jl:27 [inlined]\n [11] #add_or_develop at ./none:0 [inlined]\n [12] #add#18 at /home/tim/src/julia-1.0/usr/share/julia/stdlib/v1.0/Pkg/src/API.jl:69 [inlined]\n [13] add(::String) at /home/tim/src/julia-1.0/usr/share/julia/stdlib/v1.0/Pkg/src/API.jl:69\n [14] top-level scope at none:0\n\njulia> Pkg.add(\"NoPkg\")  # hit Meta-s here\n[1] pkgerror(msg::String...) in Pkg.Types at /home/tim/src/julia-1/usr/share/julia/stdlib/v1.1/Pkg/src/Types.jl:120\n[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\n[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\n[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\n[5] add(args...) in Pkg.API at /home/tim/src/julia-1/usr/share/julia/stdlib/v1.1/Pkg/src/API.jl:59\npkgerror(msg::String...) in Pkg.Types at /home/tim/src/julia-1/usr/share/julia/stdlib/v1.1/Pkg/src/Types.jl:120\n  msg = (\"The following package names could not be resolved:\\n * NoPkg (not found in project, manifest or registry)\\nPlease specify by known `name=uuid`.\",)\nrebug> @eval Pkg.Types let (msg,) = Main.Rebugger.getstored(\"1ac42628-4b15-11e9-28e7-33f71870bf31\")\n       begin\n           throw(PkgError(join(msg)))\n       end\n       end\n```\n\nNote that only five methods got captured but the stacktrace is much longer.\nMost of these methods, however, say \"inlined\" with line number 0.\nRebugger has no way of finding such methods.\nHowever, you can enter (i.e., Meta-e) such methods from one that is higher in the stack trace.\n\n#### Modified \"signatures\"\n\nSome \"methods\" you see in the `let` block on the command line will have their\n\"signatures\" slightly modified.\nFor example:\n\n```julia\njulia> dest = zeros(3);\n\njulia> copyto!(dest, 1:4)\nERROR: BoundsError: attempt to access 3-element Array{Float64,1} at index [1, 2, 3, 4]\nStacktrace:\n [1] copyto!(::IndexLinear, ::Array{Float64,1}, ::IndexLinear, ::UnitRange{Int64}) at ./abstractarray.jl:728\n [2] copyto!(::Array{Float64,1}, ::UnitRange{Int64}) at ./abstractarray.jl:723\n [3] top-level scope at none:0\n\njulia> copyto!(dest, 1:4)  # hit Meta-s here\nCaptured elements of stacktrace:\n[1] copyto!(::IndexStyle, dest::AbstractArray, ::IndexStyle, src::AbstractArray) in Base at abstractarray.jl:727\n[2] copyto!(dest::AbstractArray, src::AbstractArray) in Base at abstractarray.jl:723\ncopyto!(::IndexStyle, dest::AbstractArray, ::IndexStyle, src::AbstractArray) in Base at abstractarray.jl:727\n  __IndexStyle_1 = IndexLinear()\n  dest = [0.0, 0.0, 0.0]\n  __IndexStyle_2 = IndexLinear()\n  src = 1:4\nrebug> @eval Base let (__IndexStyle_1, dest, __IndexStyle_2, src) = Main.Rebugger.getstored(\"21a8ab94-a228-11e8-0563-256e39b3996e\")\n       begin\n           (destinds, srcinds) = (LinearIndices(dest), LinearIndices(src))\n           isempty(srcinds) || (checkbounds(Bool, destinds, first(srcinds)) && checkbounds(Bool, destinds, last(srcinds)) || throw(BoundsError(dest, srcinds)))\n           @inbounds for i = srcinds\n                   dest[i] = src[i]\n               end\n           return dest\n       end\n       end\n```\n\nNote that this `copyto!` method contains two anonymous arguments annotated `::IndexStyle`.\nRebugger will make up names for these arguments (here `__IndexStyle_1` and `__IndexStyle_2`).\nWhile these will be distinct from one another, Rebugger does not check whether they\nconflict with any internal names.\n\n!!! note\n    This example illustrates a second important point: you may have noticed that this one was\n    considerably slower to print.\n    That's because capturing stacktraces overwrites the methods involved.\n    Since `copyto!` is widely used, this forces recompilation of a lot\n    of methods in Base.\n\n    In contrast with capturing stacktraces, stepping in (Meta-e) does not overwrite methods,\n    so is sometimes preferred. And of course, interpret mode (Meta-i) also doesn't overwrite methods.\n"
  },
  {
    "path": "src/Rebugger.jl",
    "content": "module Rebugger\n\nusing UUIDs, InteractiveUtils\nusing REPL\nimport REPL.LineEdit, REPL.Terminals\nusing REPL.LineEdit: buffer, bufend, content, edit_splice!\nusing REPL.LineEdit: transition, terminal, mode, state\n\nusing CodeTracking, Revise, JuliaInterpreter, HeaderREPLs\nusing Revise: RelocatableExpr, striplines!, printf_maxsize, whichtt, hasfile, unwrap\nusing JuliaInterpreter: FrameCode, scopeof\nusing Base.Meta: isexpr\nusing Core: CodeInfo\n\n# Reexports\nexport @breakpoint, breakpoint, enable, disable, remove, break_on, break_off\n\nconst msgs = []  # for debugging. The REPL-magic can sometimes overprint error messages\n\ninclude(\"debug.jl\")\ninclude(\"ui.jl\")\ninclude(\"printing.jl\")\ninclude(\"deepcopy.jl\")\n\n# Set up keys that enter rebug mode from the regular Julia REPL\n# This should be called from your ~/.julia/config/startup.jl file\nfunction repl_init(repl)\n    repl.interface = REPL.setup_interface(repl; extra_repl_keymap = get_rebugger_modeswitch_dict())\nend\n\nfunction rebugrepl_init()\n    # Set up the Rebugger REPL mode with all of its key bindings\n    repl_inited = isdefined(Base, :active_repl)\n    while !isdefined(Base, :active_repl)\n        sleep(0.05)\n    end\n    sleep(0.1) # for extra safety\n    # Set up the custom \"rebug\" REPL\n    iprompt, eprompt = rebugrepl_init(Base.active_repl, repl_inited)\n    interpret_prompt_ref[] = iprompt\n    rebug_prompt_ref[] = eprompt\n    return nothing\nend\n\nfunction rebugrepl_init(main_repl, repl_inited)\n    irepl = HeaderREPL(main_repl, InterpretHeader())\n    interface = REPL.setup_interface(irepl; extra_repl_keymap=Dict[])\n    iprompt = interface.modes[end]\n    erepl = HeaderREPL(main_repl, RebugHeader())\n    interface = REPL.setup_interface(erepl; extra_repl_keymap=[get_rebugger_modeswitch_dict(), rebugger_keys])\n    eprompt = interface.modes[end]\n    add_keybindings(main_repl; override=repl_inited, keybindings...)\n    return iprompt, eprompt\nend\n\nfunction __init__()\n    # schedule(Task(rebugrepl_init))\n    task = Task() do\n        try\n            rebugrepl_init()\n        catch exception\n            @error \"Rebugger initialization failed\" exception=(exception, catch_backtrace())\n        end\n    end\n    schedule(task)\nend\n\ninclude(\"precompile.jl\")\n_precompile_()\n\nend # module\n"
  },
  {
    "path": "src/debug.jl",
    "content": "# Core debugging logic.\n# Hopefully someday much of this will be replaced by Gallium.\n\nconst VarnameType = Tuple{Vararg{Union{Symbol,Expr}}}  # Expr covers `foo(x, (a,b), y)` destructured-tuple signatures\nstruct Stored\n    method::Method\n    varnames::VarnameType\n    varvals\n\n    function Stored(m, names, vals)\n        new(m, names, safe_deepcopy(vals...))\n    end\nend\n\nconst stashed     = Ref{Any}(nothing)\nconst stored      = Dict{UUID,Stored}()              # UUID => store data\nconst storefunc   = Dict{UUID,Function}()            # UUID => function that puts inputs into `stored`\nconst storemap    = Dict{Tuple{Method,Bool},UUID}()  # (method, overwrite) => UUID\n\nstruct StopException   <: Exception end\nstruct StashingFailed  <: Exception end   # stashing saves the function and arguments from the caller (transient)\nstruct StorageFailed   <: Exception end   # storage puts callee values into `stored` (lasts until `stored` is cleared)\nstruct DefMissing      <: Exception\n    method::Method\n    exception\nend\nstruct SignatureError  <: Exception\n    method::Method\nend\nstruct StepException   <: Exception\n    msg::String\nend\nstruct EvalException   <: Exception\n    exprstring\n    exception\nend\n\nconst base_prefix = '.' * Base.Filesystem.path_separator\n\n\"\"\"\n    Rebugger.clear()\n\nClear internal data. This deletes storage associated with stored variables, but also\nforces regeneration of capture methods, which can be handy while debugging Rebugger itself.\n\"\"\"\nfunction clear()\n    stashed[] = nothing\n    empty!(stored)\n    empty!(storefunc)\n    empty!(storemap)\n    nothing\nend\n\n### Stacktraces\n\n\"\"\"\n    r = linerange(expr, offset=0)\n\nCompute the range of lines occupied by `expr`.\nReturns `nothing` if no line statements can be found.\n\"\"\"\nfunction linerange(def::Expr)\n    start = findline(def)\n    stop  = findline(def, Iterators.reverse)\n    start !== nothing && stop !== nothing && return start.line:stop.line\n    return nothing\nend\n\nfunction findline(ex, order)\n    isa(ex, Expr) || return nothing\n    for a in order(ex.args)\n        a isa LineNumberNode && return a\n        if a isa Expr\n            ln = findline(a, order)\n            ln !== nothing && return ln\n        end\n    end\n    return nothing\nend\nfindline(ex) = findline(ex, identity)\n\n\"\"\"\n    usrtrace, defs = pregenerated_stacktrace(trace, topname=:capture_stacktrace)\n\nGenerate a list of methods `usrtrace` and their corresponding definition-expressions `defs`\nfrom a stacktrace.\nNot all methods can be looked up, but this attempts to resolve, e.g., keyword-handling methods\nand so on.\n\"\"\"\nfunction pregenerated_stacktrace(trace; topname = :capture_stacktrace)\n    usrtrace, defs = Method[], Expr[]\n    methodsused = Set{Method}()\n\n    # When the method can't be found directly in the tables,\n    # look it up by fie and line number\n    function add_by_file_line(defmap, line)\n        for (rdef, sigts) in defmap\n            def = rdef.ex\n            (sigts === nothing || isempty(sigts)) && continue\n            r = linerange(def)  # FIXME offset\n            if line ∈ r\n                m = whichtt(last(sigts))\n                if m ∉ methodsused\n                    push!(defs, def)\n                    push!(usrtrace, m)\n                    push!(methodsused, m)\n                    return true\n                end\n            end\n        end\n        return false\n    end\n    function add_by_file_line(pkgdata, file, line)\n        fi = Revise.maybe_parse_from_cache!(pkgdata, relpath(file, pkgdata))\n        for (mod, exsigs) in fi.modexsigs\n            add_by_file_line(exsigs, line) && return true\n        end\n        return false\n    end\n\n    for (i, sf) in enumerate(trace)\n        (sf.func == topname || sf.func == Symbol(\"top-level scope\")) && break  # truncate at the chosen spot\n        sf.func ∈ notrace && continue\n        mi = sf.linfo\n        file = String(sf.file)\n        if mi isa Core.MethodInstance\n            method = mi.def\n            def = definition(method)\n            if def === nothing\n                # This may be a generated method, perhaps it's a keyword function handler\n                # Look for it by line number\n                local id\n                try\n                    id = Revise.get_tracked_id(method.module)\n                catch\n                    # Methods from Core.Compiler cause errors on Julia binaries\n                    continue\n                end\n                id === nothing && continue\n                pkgdata = Revise.pkgdatas[id]\n                cfile = get(Revise.src_file_key, file, file)\n                rpath = relpath(cfile, pkgdata)\n                hasfile(pkgdata, rpath) || continue\n                fi = Revise.maybe_parse_from_cache!(pkgdata, rpath)\n                # fi = get(pkgdata.fileinfos, rpath, nothing)\n                # if fi !== nothing\n                #     add_by_file_line(fi.fm[method.module].defmap, sf)\n                # end\n            else\n                method ∈ methodsused && continue\n                def isa Expr || continue\n                push!(defs, def)\n                push!(usrtrace, method)\n            end\n        else\n            # This method was inlined and hence linfo was not available\n            # Try to find it\n            if startswith(file, base_prefix)\n                # This is a file in Base or Core\n                file = relpath(file, base_prefix)\n                id = Revise.get_tracked_id(Base)\n                pkgdata = Revise.pkgdatas[id]\n                if hasfile(pkgdata, file)\n                    add_by_file_line(pkgdata, file, sf.line) && continue\n                elseif startswith(file, \"compiler\")\n                    try\n                        id = Revise.get_tracked_id(Core.Compiler)\n                    catch\n                        # On Julia binaries Core.Compiler is not available\n                        continue\n                    end\n                    pkgdata = Revise.pkgdatas[id]\n                    add_by_file_line(pkgdata, relpath(file, pkgdata), sf.line) && continue\n                end\n            end\n            # Try all loaded packages\n            for (id, pkgdata) in Revise.pkgdatas\n                if hasfile(pkgdata, file)\n                    add_by_file_line(pkgdata, relpath(file, pkgdata), sf.line) && break\n                end\n            end\n        end\n    end\n    return usrtrace, defs\nend\n\n\"\"\"\n    uuids = capture_stacktrace(mod, command)\n\nExecute `command` in module `mod`. `command` must throw an error.\nThen instrument the methods in the stacktrace so that their input\nvariables are stored in `Rebugger.stored`.\nAfter storing the inputs, restore the original methods.\n\nSince this requires two `eval`s of `command`, usage should be limited to\ndeterministic expressions that always result in the same call chain.\n\"\"\"\nfunction capture_stacktrace(mod::Module, command::Expr)\n    errored = true\n    trace = try\n        Core.eval(mod, command)\n        errored = false\n    catch\n        stacktrace(catch_backtrace())\n    end\n    errored || error(\"$command did not throw an error\")\n    usrtrace, defs = pregenerated_stacktrace(trace)\n    # Eliminate duplicates. Keyword-funcs and default-arg funcs can return the same def\n    i = 1\n    while i < length(defs)\n        if defs[i] == defs[i+1]\n            deleteat!(defs, i)\n            deleteat!(usrtrace, i)\n        else\n            i += 1\n        end\n    end\n    isempty(usrtrace) && error(\"failed to capture any elements of the stacktrace\")\n    println(stderr, \"Captured elements of stacktrace:\")\n    show(stderr, MIME(\"text/plain\"), usrtrace)\n    length(unique(usrtrace)) == length(usrtrace) || @error \"the same method appeared twice, not supported. Try stepping into the command.\"\n    uuids = UUID[]\n    capture_stacktrace!(uuids, usrtrace, defs) do\n        Core.eval(mod, command)\n    end\n    uuids\nend\ncapture_stacktrace(command::Expr) = capture_stacktrace(Main, command)\n\nfunction capture_stacktrace!(f::Function, uuids::Vector, usrtrace, defs)\n    if isempty(usrtrace)\n        # We've finished modifying all the methods, time to run the command\n        try\n            f()\n            @warn \"traced method did not throw an error\"\n        catch\n        end\n        return\n    end\n    method, def = usrtrace[end], defs[end]\n    if def !== nothing\n        uuid = method_capture_from_callee(method, def; overwrite=true)\n        push!(uuids, uuid)\n    end\n    # Recurse up the stack until we get to the top...\n    pop!(usrtrace)\n    pop!(defs)\n    capture_stacktrace!(f, uuids, usrtrace, defs)\n    # ...after which it will call the erroring function and come back here.\n    # Having come back, restore the original definition\n    if def != nothing\n        eval_noinfo(method.module, def)  # unfortunately this doesn't restore the original method as a viable key to storemap\n    end\n    return uuids\nend\n\n### Stepping\n\nfunction stepin(io)\n    @assert Rebugger.stashed[] === nothing\n    # Step 1: rewrite the command to stash the call function and its arguments.\n    prepare_caller_capture!(io)\n    capexpr, stop = Meta.parse(content(io), 1)\n    try\n        Core.eval(Main, capexpr)\n        throw(StashingFailed())\n    catch err\n        err isa StashingFailed && rethrow(err)\n        if !(err isa StopException)\n            throw(EvalException(content(io), err))\n        end\n    end\n    f, args, kwargs = Rebugger.stashed[]\n    Rebugger.stashed[] = nothing\n    # Step 2: determine which method is called, and if need be create a function\n    # that captures all of the callee's inputs. (This allows us to capture default arguments,\n    # keyword arguments, and type parameters.)\n    method = which(f, Base.typesof(args...))\n    uuid = get(storemap, (method, false), nothing)\n    if uuid === nothing\n        uuid = method_capture_from_callee(method; overwrite=false)\n    end\n    # Step 3: execute the command to store the inputs.\n    fcapture = storefunc[uuid]\n    tv, decls = Base.arg_decl_parts(method)\n    if !isempty(decls[1][1])\n        # This is a call-overloaded method, prepend the calling object\n        args = (f, args...)\n    end\n    try\n        Base.invokelatest(fcapture, args...; kwargs...)\n        throw(StorageFailed())   # this should never happen\n    catch err\n        err isa StopException || rethrow(err)\n    end\n    return uuid, generate_let_command(method, uuid)\nend\n\n\"\"\"\n    callexpr = prepare_caller_capture!(io)\n\nGiven a buffer `io` representing a string and \"point\" (the seek position) set at a call expression,\nreplace the call with one that stashes the function and arguments of the call.\n\nFor example, if `io` has contents\n\n    <some code>\n    if x > 0.5\n        ^fcomplex(x, 2; kw1=1.1)\n        <more code>\n\nwhere in the above `^` indicates `position(s)` (\"point\"), rewrite this as\n\n    <some code>\n    if x > 0.5\n        Main.Rebugger.stashed[] = (fcomplex, (x, 2), (kw1=1.1,))\n        throw(Rebugger.StopException())\n        <more code>\n\n(Keyword arguments do not affect dispatch and hence are not stashed.)\nConsequently, if this is `eval`ed and execution reaches \"^\", it causes the arguments\nof the call to be placed in `Rebugger.stashed`.\n\n`callexpr` is the original (unmodified) expression specifying the call, i.e.,\n`fcomplex(x, 2; kw1=1.1)` in this case.\n\nThis does the buffer-preparation for *caller* capture.\nFor *callee* capture, see [`method_capture_from_callee`](@ref),\nand [`stepin`](@ref) which puts these two together.\n\"\"\"\nfunction prepare_caller_capture!(io)  # for testing, needs to work on a normal IO object\n    start = position(io)\n    callstring, _ = stripsc(content(io, start=>bufend(io)))\n    callexpr, len = Meta.parse(callstring, 1; raise=false)\n    callexpr == nothing && throw(StepException(\"Got empty expression from $callstring\"))\n    isa(callexpr, Expr) || throw(StepException(\"Rebugger can only step into expressions, got $callexpr\"))\n    if callexpr.head == :error\n        iend = len\n        for i = 1:2\n            iend = prevind(callstring, iend)\n        end\n        callstring = callstring[1:iend]\n        callexpr, len = Meta.parse(callstring, 1)\n    end\n    if callexpr.head == :tuple && !(startswith(callstring, \"tuple\") || startswith(callstring, \"(\"))\n        # An expression like foo(bar(x)..., 1) where point is positioned at bar\n        callexpr = callexpr.args[1]\n    end\n    if callexpr.head == :ref\n        callexpr = Expr(:call, :getindex, callexpr.args...)\n    elseif callexpr.head == :(=) && isa(callexpr.args[1], Expr) && callexpr.args[1].head == :ref\n        ref, val = callexpr.args\n        callexpr = Expr(:call, :setindex!, ref.args[1], val, ref.args[2:end]...)\n    elseif (callexpr.head == :&& || callexpr.head == :||) && isa(callexpr.args[1], Expr)\n        callexpr = callexpr.args[1]\n    elseif callexpr.head == :...\n        callexpr = callexpr.args[1]\n    elseif callexpr.head == :do\n        callexpr = Expr(\n            :call,\n            callexpr.args[1].args[1],         # function name\n            callexpr.args[2],                 # do block (anonymous function)\n            callexpr.args[1].args[2:end]...)  # other arguments\n    end\n    # Must be a call or broadcast\n    ((callexpr.head == :call) | (callexpr.head == :.)) || throw(Meta.ParseError(\"point must be at a call expression, got $callexpr\"))\n    if callexpr.head == :call\n        fname, args = callexpr.args[1], callexpr.args[2:end]\n    else\n        fname, args = :broadcast, [callexpr.args[1], callexpr.args[2].args...]\n    end\n    # In the edited callstring separate any kwargs now. They don't affect dispatch.\n    kwargs = []\n    if length(args) >= 1 && isa(args[1], Expr) && args[1].head == :parameters\n        # foo(x; kw1=1, ...) syntax    (with the semicolon)\n        append!(kwargs, popfirst!(args).args)\n    end\n    while !isempty(args) && isa(args[end], Expr) && args[end].head == :kw\n        # foo(x, kw1=1, ...) syntax    (with a comma, no semicolon)\n        push!(kwargs, pop!(args))\n    end\n    captureexpr = quote\n        Main.Rebugger.stashed[] = ($fname, (($(args...)),), Main.Rebugger.kwstasher(; $(kwargs...)))\n        throw(StopException())\n    end\n    # Now insert this in place of the marked call\n    # Unfortunately we have to convert to a string and there are scoping issues\n    capturestr = string(captureexpr, '\\n')\n    regexunscoped = r\"(?<!\\.)StopException\"\n    capturestr = replace(capturestr, regexunscoped=>(s->\"Rebugger.\"*s))\n    regexscoped   = r\"(?<!\\.)Rebugger\\.StopException\"\n    capturestr = replace(capturestr, regexscoped=>(s->\"Main.\"*s))\n    edit_splice!(io, start=>start+len-1, capturestr)\n    return callexpr\nend\n\n\"\"\"\n    uuid = method_capture_from_callee(method; overwrite::Bool=false)\n\nCreate a version of `method` that stores its inputs in `Main.Rebugger.stored`.\nFor a method\n\n    function fcomplex(x::A, y=1, z=\"\"; kw1=3.2) where A<:AbstractArray{T} where T\n        <body>\n    end\n\nif `overwrite=false`, this generates a new method\n\n    function hidden_fcomplex(x::A, y=1, z=\"\"; kw1=3.2) where A<:AbstractArray{T} where T\n        Main.Rebugger.stored[uuid] = Main.Rebugger.Stored(fcomplex, (:x, :y, :z, :kw1, :A, :T), deepcopy((x, y, z, kw1, A, T)))\n        throw(StopException())\n    end\n\n(If a `uuid` already exists for `method` from a previous call to `method_capture_from_callee`,\nit will simply be returned.)\n\nWith `overwrite=true`, there are two differences:\n\n- it replaces `fcomplex` rather than defining `hidden_fcomplex`\n- rather than throwing `StopException`, it re-inserts `<body>` after the line performing storage\n\nThe returned `uuid` can be used for accessing the stored data.\n\"\"\"\nfunction method_capture_from_callee(method, def; overwrite::Bool=false)\n    uuid = get(storemap, (method, overwrite), nothing)\n    uuid != nothing && return uuid\n    def = pop_annotations(def)\n    sigr, body = def.args[1], def.args[2]\n    sigr == nothing && throw(SignatureError(method))\n    sigex = convert(Expr, sigr)\n    if sigex.head == :(::)\n        sigex = sigex.args[1]  # return type declaration\n    end\n    methname, argnames, kwnames, paramnames = signature_names!(sigex)\n    # Check for call-overloading method, e.g., (obj::ObjType)(x, y...) = <body>\n    callerobj = nothing\n    if methname isa Expr && methname.head == :(::)\n        @assert length(methname.args) == 2\n        callerobj = methname\n        argnames = (methname.args[1], argnames...)\n        methname = methname.args[2]\n        if methname isa Expr\n            if methname.head == :curly\n                methname = methname.args[1]\n            else\n                dump(methname)\n                error(\"unexpected call-overloading type\")\n            end\n        end\n    end\n    allnames = (argnames..., kwnames..., paramnames...)\n    qallnames = QuoteNode.(allnames)\n    uuid = uuid1()\n    uuidstr = string(uuid)\n    makepair = Pair  # Needed for debugging into Core.Compiler, which has its own Pair\n    storeexpr = :(Main.Rebugger.setstored!($makepair($uuidstr, Main.Rebugger.Stored($method, ($(qallnames...),), ($(allnames...),)) ) ))\n    capture_body = overwrite ? quote\n        $storeexpr\n        $body\n    end : quote\n        $storeexpr\n        throw(Main.Rebugger.StopException())\n    end\n    capture_name = try\n        _gensym(methname)\n    catch\n        nothing\n    end\n    capture_name == nothing && (dump(methname); dump(sigex); error(\"couldn't gensym\"))\n    # capture_name = _gensym(methname)\n    mod = method.module\n    head = def.head == :(=) ? :function : def.head  # allows us to intercept macros\n    capture_function = Expr(head, overwrite ? sigex : rename_method(sigex, capture_name, callerobj), capture_body)\n    result = Core.eval(mod, capture_function)\n    if !overwrite\n        storefunc[uuid] = result\n    end\n    storemap[(method, overwrite)] = uuid\n    return uuid\nend\nfunction method_capture_from_callee(method; kwargs...)\n    Revise.get_def(method; modified_files=typeof(Revise.revision_queue)())  # update while ignoring mtime issues here\n    def = definition(method)\n    def == nothing && throw(DefMissing(method, nothing))\n    method_capture_from_callee(method, def; kwargs...)\nend\n\nfunction generate_let_command(method::Method, uuid::UUID)\n    s = stored[uuid]\n    @assert method == s.method\n    argstring = '(' * join(s.varnames, \", \") * (length(s.varnames)==1 ? \",)\" : ')')\n    Revise.get_def(method; modified_files=String[])  # to avoid mtime updates\n    body = convert(Expr, striplines!(unwrap(definition(method)).args[end]))\n    return \"\"\"\n        @eval $(method.module) let $argstring = Main.Rebugger.getstored(\\\"$uuid\\\")\n        $body\n        end\"\"\"\nend\nfunction generate_let_command(uuid::UUID)\n    s = stored[uuid]\n    generate_let_command(s.method, uuid)\nend\n\n\"\"\"\n    args_and_types = Rebugger.getstored(uuid)\n\nRetrieve the values of stored arguments and type-parameters from the store specified\n`uuid`. This makes a copy of values, so as to be safe for repeated execution of methods\nthat modify their inputs.\n\"\"\"\ngetstored(uuidstr::AbstractString) = safe_deepcopy(Main.Rebugger.stored[UUID(uuidstr)].varvals...)\n\nfunction setstored!(p::Pair{S,Stored}) where S<:AbstractString\n    uuidstr, val = p.first, p.second\n    Main.Rebugger.stored[UUID(uuidstr)] = val\nend\n\nkwstasher(; kwargs...) = kwargs\n\n### Utilities\n\n\"\"\"\n    fname, argnames, kwnames, parameternames = signature_names!(sigex::Expr)\n\nReturn the function name `fname` and names given to its arguments, keyword arguments,\nand parameters, as specified by the method signature-expression `sigex`.\n\n`sigex` will be modified if some of the arguments are unnamed.\n\n\n# Examples\n\n```jldoctest; setup=:(using Rebugger)\njulia> 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}))\n(:complexargs, (:w, :x, :y, :z), (:kwarg, :kw2, :kwargs), (:A, :T, :N))\n\njulia> ex = :(myzero(::Float64));     # unnamed argument\n\njulia> Rebugger.signature_names!(ex)\n(:myzero, (:__Float64_1,), (), ())\n\njulia> ex\n:(myzero(__Float64_1::Float64))\n```\n\"\"\"\nfunction signature_names!(sigex::Expr)\n    # TODO: add parameter names\n    argname(s::Symbol) = s\n    function argname(ex::Expr)\n        if ex.head == :(...) && length(ex.args) == 1\n            # varargs function\n            ex = ex.args[1]\n            ex isa Symbol && return ex\n        end\n        (ex.head == :macrocall || ex.head == :meta) && return argname(ex.args[end])  # @nospecialize\n        ex.head == :kw && return argname(ex.args[1])  # default arguments\n        ex.head == :tuple && return ex    # tuple-destructuring argument\n        ex.head == :(::) || throw(ArgumentError(string(\"expected :(::) expression, got \", ex)))\n        arg = ex.args[1]\n        if length(ex.args) == 1 && (arg isa Symbol)\n            # This argument has a type but no name\n            return arg, true\n        end\n        if isa(arg, Expr) && arg.head == :curly\n            if arg.args[1] == :Type\n                # Argument of the form ::Type{T}\n                return arg.args[2], false\n            elseif arg.args[1] == :NamedTuple\n                return :NamedTuple, true, arg\n            end\n        end\n        return arg\n    end\n    paramname(s::Symbol) = s\n    function paramname(ex::Expr)\n        ex.head == :(<:) && return paramname(ex.args[1])\n        throw(ArgumentError(string(\"expected parameter expression, got \", ex)))\n    end\n\n    kwnames, parameternames = (), []\n    while sigex.head == :where\n        parameternames = [paramname.(sigex.args[2:end])..., parameternames...]\n        sigex = sigex.args[1]\n    end\n    name = sigex.args[1]\n    offset = 1\n    if length(sigex.args) > 1 && isa(sigex.args[2], Expr) && sigex.args[2].head == :parameters\n        # keyword arguments\n        kwnames = tuple(argname.(sigex.args[2].args)...)\n        offset += 1\n    end\n    # Argnames. For any unnamed arguments we have to generate a name.\n    empty!(usdict)\n    argnames = Union{Symbol,Expr}[]\n    for i = offset+1:length(sigex.args)\n        arg = sigex.args[i]\n        retname = argname(arg)\n        if retname isa Tuple\n            should_gen = retname[2]\n            if should_gen\n                # This argument is missing a real name\n                argt = length(retname) == 3 ? retname[3] : retname[1]\n                name = genunderscored(retname[1])\n                sigex.args[i] = :($name::$argt)\n            else\n                # This is a ::Type{T} argument. We should remove this from the list of parameters\n                name = retname[1]\n                parameternames = filter(!isequal(name), parameternames)\n            end\n            retname = name\n        end\n        push!(argnames, retname)\n    end\n\n    return sigex.args[1], tuple(argnames...), kwnames, tuple(parameternames...)\nend\n\nfunction rename_method!(sig::Expr, name::Symbol, callerobj)\n    ex = sig\n    while isa(sig, Expr) && sig.head == :where\n        sig = sig.args[1]\n    end\n    sig.head == :call || (dump(sig); throw(ArgumentError(string(\"expected call expression, got \", sig))))\n    sig.args[1] = name\n    if callerobj != nothing\n        # Call overloading, add an argument\n        sig.args = [sig.args[1]; callerobj; sig.args[2:end]]\n    end\n    return ex\nend\nrename_method(sig::Expr, name::Symbol, callerobj) = rename_method!(copy(sig), name, callerobj)\n\nfunction stripsc(str)\n    str = chomp(str)\n    display_result = false\n    if endswith(str, ';')\n        str = str[1:end-1]\n        display_result = true\n    end\n    return str, display_result\nend\n\nconst poppable_macro = (Symbol(\"@inline\"), Symbol(\"@noinline\"), Symbol(\"@propagate_inbounds\"), Symbol(\"@eval\"), Symbol(\"@pure\"))\nis_poppable_macro(ex) = ex ∈ poppable_macro ||\n    (ex isa Expr && ex.head == :. && ex.args[1] == :Base && ex.args[2].value ∈ poppable_macro)\n\nfunction pop_annotations(def::Expr)\n    def = unwrap(def)\n    while def isa Expr && def.head == :macrocall && is_poppable_macro(def.args[1])\n        def = def.args[end]\n        def = unwrap(def)\n    end\n    def\nend\n\n# Use to re-evaluate an expression without leaving \"breadcrumbs\" about where\n# the eval is coming from. This is used below to prevent the re-evaluaton of an\n# original method from being attributed to Rebugger itself in future backtraces.\neval_noinfo(mod::Module, ex::Expr) = ccall(:jl_toplevel_eval, Any, (Any, Any), mod, ex)\neval_noinfo(mod::Module, rex::RelocatableExpr) = eval_noinfo(mod, convert(Expr, rex))\n\nfunction unquote(ex::Expr)\n    if ex.head == :quote\n        return Expr(:block, ex.args...)\n    end\n    ex\nend\nunquote(rex::RelocatableExpr) = unquote(convert(Expr, rex))\n\n_gensym(sym::Symbol) = gensym(sym)\n_gensym(q::QuoteNode) = _gensym(q.value)\n_gensym(ex::Expr) = (@assert ex.head == :. && length(ex.args) == 2; _gensym(ex.args[2]))\n\nconst usdict = Dict{Symbol,Int}()\nfunction genunderscored(sym::Symbol)\n    n = get(usdict, sym, 0) + 1\n    usdict[sym] = n\n    return Symbol(\"__\"*String(sym)*'_'*string(n))\nend\n\nconst notrace = (:error, :throw)\n"
  },
  {
    "path": "src/deepcopy.jl",
    "content": "# Because `deepcopy(mod::Module)` throws an error, we need a safe approach.\n# Strategy: wrap the IdDict so that our methods get called rather than Base's.\n# It's not guaranteed to work for user types that specialize `deepcopy_internal`,\n# but hopefully that's rare.\n\nstruct WrappedIdDict\n    dict::IdDict\nend\nBase.getindex(w::WrappedIdDict, key) = w.dict[key]\nBase.setindex!(w::WrappedIdDict, val, key) = w.dict[key] = val\nBase.haskey(w::WrappedIdDict, k) = haskey(w.dict, k)\n\nfunction safe_deepcopy(a, args...)\n    stackdict = WrappedIdDict(IdDict())\n    _safe_deepcopy(stackdict, a, args...)\nend\nsafe_deepcopy() = ()\n_safe_deepcopy(stackdict, a, args...) =\n    (Base.deepcopy_internal(a, stackdict), _safe_deepcopy(stackdict, args...)...)\n_safe_deepcopy(stackdict) = ()\n\n# This is the one method we want to override\nBase.deepcopy_internal(x::Module, stackdict::WrappedIdDict) = x\n\n# But the rest are necessary to make it work. This is just a direct copy from Base.\nBase.deepcopy_internal(x::Union{Symbol,Core.MethodInstance,Method,GlobalRef,DataType,Union,Task},\n                  stackdict::WrappedIdDict) = x\nBase.deepcopy_internal(x::Tuple, stackdict::WrappedIdDict) =\n    ntuple(i->Base.deepcopy_internal(x[i], stackdict), length(x))\n\nfunction Base.deepcopy_internal(x::Base.SimpleVector, stackdict::WrappedIdDict)\n    if haskey(stackdict, x)\n        return stackdict[x]\n    end\n    y = Core.svec(Any[Base.deepcopy_internal(x[i], stackdict) for i = 1:length(x)]...)\n    stackdict[x] = y\n    return y\nend\n\nfunction Base.deepcopy_internal(x::String, stackdict::WrappedIdDict)\n    if haskey(stackdict, x)\n        return stackdict[x]\n    end\n    y = GC.@preserve x unsafe_string(pointer(x), sizeof(x))\n    stackdict[x] = y\n    return y\nend\n\nfunction Base.deepcopy_internal(@nospecialize(x), stackdict::WrappedIdDict)\n    T = typeof(x)::DataType\n    nf = nfields(x)\n    (isbitstype(T) || nf == 0) && return x\n    if haskey(stackdict, x)\n        return stackdict[x]\n    end\n    if T.mutable\n        y = ccall(:jl_new_struct_uninit, Any, (Any,), T)\n        stackdict[x] = y\n        for i in 1:nf\n            if isdefined(x,i)\n                ccall(:jl_set_nth_field, Cvoid, (Any, Csize_t, Any), y, i-1,\n                      Base.deepcopy_internal(getfield(x,i), stackdict))\n            end\n        end\n    else\n        flds = Vector{Any}(undef, nf)\n        for i in 1:nf\n            if isdefined(x, i)\n                xi = getfield(x, i)\n                xi = Base.deepcopy_internal(xi, stackdict)::typeof(xi)\n                flds[i] = xi\n            else\n                nf = i - 1 # rest of tail must be undefined values\n                break\n            end\n        end\n        y = ccall(:jl_new_structv, Any, (Any, Ptr{Any}, UInt32), T, flds, nf)\n    end\n    return y::T\nend\n\nfunction Base.deepcopy_internal(x::Array, stackdict::WrappedIdDict)\n    if haskey(stackdict, x)\n        return stackdict[x]\n    end\n    _deepcopy_array_t(x, eltype(x), stackdict)\nend\n\nfunction _deepcopy_array_t(@nospecialize(x), T, stackdict::WrappedIdDict)\n    if isbitstype(T)\n        return (stackdict[x]=copy(x))\n    end\n    dest = similar(x)\n    stackdict[x] = dest\n    for i = 1:(length(x)::Int)\n        if ccall(:jl_array_isassigned, Cint, (Any, Csize_t), x, i-1) != 0\n            xi = ccall(:jl_arrayref, Any, (Any, Csize_t), x, i-1)\n            if !isbits(xi)\n                xi = Base.deepcopy_internal(xi, stackdict)\n            end\n            ccall(:jl_arrayset, Cvoid, (Any, Any, Csize_t), dest, xi, i-1)\n        end\n    end\n    return dest\nend\n\nfunction Base.deepcopy_internal(x::Union{Dict,IdDict}, stackdict::WrappedIdDict)\n    if haskey(stackdict, x)\n        return stackdict[x]::typeof(x)\n    end\n\n    if isbitstype(eltype(x))\n        return (stackdict[x] = copy(x))\n    end\n\n    dest = empty(x)\n    stackdict[x] = dest\n    for (k, v) in x\n        dest[Base.deepcopy_internal(k, stackdict)] = Base.deepcopy_internal(v, stackdict)\n    end\n    dest\nend\n"
  },
  {
    "path": "src/precompile.jl",
    "content": "function _precompile_()\n    ccall(:jl_generating_output, Cint, ()) == 1 || return nothing\n    precompile(Tuple{typeof(get_rebugger_modeswitch_dict)})\n    precompile(Tuple{typeof(rebugrepl_init)})\nend\n"
  },
  {
    "path": "src/printing.jl",
    "content": "# A type for keeping track of the current line number when printing Exprs\nstruct LineNumberIO <: IO\n    io::IO\n    linenos::Vector{Union{Missing,Int}} # source line number for each printed line of the Expr\n    file::Symbol   # used to avoid confusion when we are in expanded macros\nend\n\nLineNumberIO(io::IO, line::Integer, file::Symbol) = LineNumberIO(io, Union{Missing,Int}[line], file)\nLineNumberIO(io::IO, line::Integer, file::AbstractString) = LineNumberIO(io, line, Symbol(file))\n\nconst LNIO = Union{LineNumberIO, IOContext{LineNumberIO}}\n\n# Instead of printing the source line number to `io.io`, associate it with the\n# corresponding line of the printout\nfunction Base.show_linenumber(io::LineNumberIO, line, file)\n    if file == io.file\n        # Count how many newlines we've encountered\n        data = io.io.data\n        nlines = count(isequal(UInt8('\\n')), data) + 1 # TODO: O(N^2), optimize? See below\n        # If there have been more printed lines than assigned line numbers, fill\n        # with `missing`\n        while nlines > length(io.linenos)\n            push!(io.linenos, missing)\n        end\n        # Record this line number\n        io.linenos[nlines] = line\n    end\n    return nothing\nend\nBase.show_linenumber(io::LNIO, line, file) = Base.show_linenumber(io.io, line, file)\nBase.show_linenumber(io::LNIO, line, ::Nothing) = nothing\nBase.show_linenumber(io::LNIO, line) = nothing\n\n# TODO? intercept `\\n` here and break the result up into lines at writing time?\nBase.write(io::LineNumberIO, x::UInt8) = write(io.io, x)\n\n# See docstring below\nfunction expression_lines(method::Method)\n    def = definition(method)\n    if def === nothing\n        # If the expression is not available, use the source text. This happens for methods in e.g., boot.jl.\n        src, line1 = definition(String, method)\n        methstrings = split(chomp(src), '\\n')\n        return Vector(range(Int(line1), length=length(methstrings))), line1, methstrings\n    end\n    def = unwrap(def)\n    # We'll use the file in LineNumberNodes to make sure line numbers refer to the \"outer\"\n    # method (and does not get confused by macros etc). Because of symlinks and non-normalized paths,\n    # it's more reliable to grab the first LNN for the template filename than to use method.file.\n    lnn = findline(def)\n    mfile = lnn === nothing ? method.file : lnn.file\n    buf = IOBuffer()\n    io = LineNumberIO(buf, method.line, mfile) # deliberately using the in-method numbering\n    print(io, def)\n    seek(buf, 0)\n    methstrings = readlines(buf)\n    linenos = io.linenos\n    while length(linenos) < length(methstrings)\n        push!(linenos, missing)\n    end\n    if startswith(methstrings[1], \":(\")\n        # Chop off the Expr-quotes from the printing\n        methstrings[1] = methstrings[1][3:end]\n        methstrings[end] = methstrings[end][1:end-1]\n    end\n    # If it prints with `function`, adjust numbering for the signature line\n    # Note that this works independently of whether it's written as an `=` method\n    # in the source code (the contents of that line may not be the same but that's\n    # true quite generally)\n    startswith(methstrings[1], \"function\") && (linenos[1] -= 1)\n    @assert issorted(skipmissing(linenos))\n    # Strip out blank lines from the printed expression\n    # These arise from the fact that we intercepted the printing of LineNumberNodes\n    keeplinenos, keepstrings = Union{Missing,Int}[], String[]\n    for (i, line) in enumerate(methstrings)\n        if !all(isspace, line)\n            push!(keepstrings, line)\n            ln = linenos[i]\n            if ismissing(ln) && i > 1\n                # Line numbers get associated with the missing LineNumberNodes rather than\n                # the succeeding expression line. Thus we look to the previous entry.\n                # Deliberately go back only one entry.\n                ln = linenos[i-1]\n            end\n            push!(keeplinenos, ln)\n        end\n    end\n    linenos, methstrings = keeplinenos, keepstrings\n    # Fill in missing lines where possible. There is no line info for things like\n    # `end`, `catch` and similar; these are subsumed by the block structure.\n    # So we try to assign line numbers to these missing elements.\n    # However, source text like\n    #   for i = 1:5 s += i end\n    # and\n    #   for i = 1:5 s += i\n    #   end\n    # and\n    #   for i = 1:5\n    #       s +=i\n    #   end\n    # all get printed in the latter format---so the matching will work only under\n    # certain conditions.\n    lastknown = 1\n    for i = 2:length(linenos)-1\n        if ismissing(linenos[i])\n            if !ismissing(linenos[i+1])\n                # Process the previous block of missing statements\n                # If the printout has advanced by the same number of lines as the source,\n                # we know what the answer must be.\n                Δidx = i+1 - lastknown\n                Δsrc = linenos[i+1] - linenos[lastknown]\n                if Δsrc == Δidx\n                    for j = lastknown+1:i\n                        linenos[j] = linenos[j-1] + 1\n                    end\n                end\n            end\n        else\n            lastknown = i\n        end\n    end\n    _, line1 = whereis(method)\n    return linenos, line1, keepstrings\nend\n\n\"\"\"\n    linenos, line1, methstrings = expression_lines(frame)\n\nCompute the source lines associated with each printed line of the expression\nassociated with the method executed in `frame`.\n`methstrings` is a vector of strings, one per line of the expression.\n`linenos` is a vector in 1-to-1 correspondence with `methstrings`,\n containing either the *compiled* line number or `missing`\nif the line number is not available. `line1` contains the *actual* (current) line\nnumber of the first line of the method body.\n\"\"\"\nfunction expression_lines(frame::Frame)\n    m = scopeof(frame)\n    mlinenos, line1, msrc = expression_lines(m)\n    isdefined(m, :generator) || return mlinenos, line1, msrc\n    # The rest of this is specific to generated functions.\n    # Call the generator to get the expression. First we have to build up the arguments.\n    g = m.generator\n    gg = g.gen\n    vars = JuliaInterpreter.locals(frame)\n    ggargs = []\n    # Static parameters come first\n    for v in vars\n        v.isparam || continue\n        push!(ggargs, v.value)\n    end\n    # Slots are next. Naturally, the generator takes only their types, not their values.\n    for v in vars\n        v.isparam && continue\n        push!(ggargs, JuliaInterpreter._Typeof(v.value))\n    end\n    # Call the generator\n    def = gg(ggargs...)\n    # `def` contains just the body. Wrap in a `function` to ensure proper indentation,\n    # and then print it.\n    def = Expr(:function, :(generatedtmp()), def)\n    buf = IOBuffer()\n    print(buf, def)  # there are no linenos, so no need for LineNumberIO\n    seek(buf, 0)\n    glines = readlines(buf)\n    # Extract the signature line from `msrc`, and paste the body in\n    gsrc = [msrc[1]; glines[2:end]]\n    # Assign line numbers. Other than the first line, there *aren't* any, so use `missing`\n    linenos = [mlinenos[1]; fill(missing, length(gsrc)-1)]\n    return linenos, line1, gsrc\nend\n\nfunction show_code(term, frame, deflines, nlines)\n    width = displaysize(term)[2]\n    method = scopeof(frame)\n    linenos, line1, showlines = deflines   # linenos is in \"compiled\" numbering, line1 in \"current\" numbering\n    offset = line1 - method.line           # compiled + offset -> current\n    known_linenos = skipmissing(linenos)\n    nd = isempty(known_linenos) ? 0 : ndigits(offset + maximum(known_linenos))\n    line = linenumber_unexpanded(frame)    # this is in \"compiled\" numbering\n    # lineidx = searchsortedfirst(linenos, line)\n    # Can't use searchsortedfirst with missing\n    lineidx = 0\n    for (i, l) in enumerate(linenos)\n        if !ismissing(l) && l >= line\n            lineidx = i\n            break\n        end\n    end\n    idxrange = max(1, lineidx-2):min(length(linenos), lineidx+2)\n    iochar = IOBuffer()\n    for idx in idxrange\n        thisline, codestr = linenos[idx], showlines[idx]\n        print(term, breakpoint_style(frame.framecode, thisline))\n        if ismissing(thisline)\n            print(term, \" \"^nd)\n        else\n            linestr = lpad(thisline + offset, nd)\n            printstyled(term, linestr; color = thisline==line ? Base.warn_color() : :normal)\n        end\n        linestr = linetrunc(iochar, codestr, width-nd-3)\n        print(term, \"  \", linestr, '\\n')\n    end\n    return length(idxrange)\nend\n\n# Limit output to a single line\nfunction linetrunc(iochar::IO, linestr, width)\n    nchars = 0\n    for c in linestr\n        if nchars == width-2\n            print(iochar, '…')\n            break\n        else\n            print(iochar, c)\n        end\n        nchars += 1\n    end\n    return String(take!(iochar))\nend\nlinetrunc(linestr, width) = linetrunc(IOBuffer(), linestr, width)\n\nfunction breakpoint_style(framecode, thisline)\n    rng = coderange(framecode, thisline)\n    style = ' '\n    breakpoints = framecode.breakpoints\n    for i in rng\n        if isassigned(breakpoints, i)\n            bps = breakpoints[i]\n            if !bps.isactive\n                if bps.condition === JuliaInterpreter.falsecondition  # removed\n                else\n                    style = style == ' ' ? 'd' : 'm'   # disabled\n                end\n            else\n                if bps.condition === JuliaInterpreter.truecondition\n                    style = style == ' ' ? 'b' : 'm'   # unconditional\n                else\n                    style = style == ' ' ? 'c' : 'm'   # conditional\n                end\n            end\n        end\n    end\n    return style\nend\nbreakpoint_style(framecode, ::Missing) = ' '\n\n### Header display\n\nfunction HeaderREPLs.print_header(io::IO, header::RebugHeader)\n    if header.nlines != 0\n        HeaderREPLs.clear_header_area(io, header)\n    end\n    iocount = IOBuffer()  # for counting lines\n    for s in (io, iocount)\n        if !isempty(header.warnmsg)\n            printstyled(s, header.warnmsg, '\\n'; color=Base.warn_color())\n        end\n        if !isempty(header.errmsg)\n            printstyled(s, header.errmsg, '\\n'; color=Base.error_color())\n        end\n        if header.current_method != dummymethod\n            printstyled(s, header.current_method, '\\n'; color=:light_magenta)\n        end\n        if header.uuid != dummyuuid\n            data = stored[header.uuid]\n            ds = displaysize(io)\n            printer(args...) = printstyled(args..., '\\n'; color=:light_blue)\n            for (name, val) in zip(data.varnames, data.varvals)\n                # Make sure each only spans one line\n                if val === nothing\n                    val = \"nothing\"\n                end\n                try\n                    printf_maxsize(printer, s, \"  \", name, \" = \", val; maxlines=1, maxchars=ds[2]-1)\n                catch # don't error just because a print method is borked\n                    printstyled(s, \"  \", name, \" errors in its show method\"; color=:red)\n                end\n            end\n        end\n    end\n    header.nlines = count_display_lines(iocount, displaysize(io))\n    header.warnmsg = \"\"\n    header.errmsg = \"\"\n    return nothing\nend\n\nfunction HeaderREPLs.print_header(io::IO, header::InterpretHeader)\n    if header.nlines != 0\n        HeaderREPLs.clear_header_area(io, header)\n    end\n    header.frame == nothing && return nothing\n    frame, Δ = frameoffset(header.frame, header.leveloffset)\n    header.leveloffset -= Δ\n    frame === nothing && return nothing\n    iocount = IOBuffer()  # for counting lines\n    for s in (io, iocount)\n        ds = displaysize(io)\n        printer(args...) = printstyled(args..., '\\n'; color=:light_blue)\n        if !isempty(header.warnmsg)\n            printstyled(s, header.warnmsg, '\\n'; color=Base.warn_color())\n        end\n        if !isempty(header.errmsg)\n            printstyled(s, header.errmsg, '\\n'; color=Base.error_color())\n        end\n        indent = \"\"\n        f = root(frame)\n        while f !== nothing\n            scope = scopeof(f)\n            if f === frame\n                printstyled(s, indent, scope, '\\n'; color=:light_magenta, bold=true)\n            else\n                printstyled(s, indent, scope, '\\n'; color=:light_magenta)\n            end\n            indent *= ' '\n            f = f.callee\n        end\n        for (i, var) in enumerate(JuliaInterpreter.locals(frame))\n            name, val = var.name, var.value\n            name == Symbol(\"#self#\") && (isa(val, Type) || sizeof(val) == 0) && continue\n            name == Symbol(\"\") && (name = \"@_\" * string(i))\n            if val === nothing\n                val = \"nothing\"\n            end\n            try\n                printf_maxsize(printer, s, \"  \", name, \" = \", val; maxlines=1, maxchars=ds[2]-1)\n            catch # don't error just because a print method is borked\n                printstyled(s, \"  \", name, \" errors in its show method\"; color=:red)\n            end\n        end\n    end\n    header.nlines = count_display_lines(iocount, displaysize(io))\n    header.warnmsg = \"\"\n    header.errmsg = \"\"\n    return nothing\nend\n\nfunction frameoffset(frame, offset)\n    while offset > 0\n        cframe = frame.caller\n        cframe === nothing && break\n        frame = cframe\n        offset -= 1\n    end\n    return frame, offset\nend\n\nfunction linenumber_unexpanded(frame)\n    framecode, pc = frame.framecode, frame.pc\n    scope = framecode.scope::Method\n    codeloc = JuliaInterpreter.codelocation(framecode.src, pc)\n    codeloc == 0 && return nothing\n    lineinfo = framecode.src.linetable[codeloc]\n    while lineinfo.file != scope.file && codeloc > 0\n        codeloc -= 1\n        lineinfo = framecode.src.linetable[codeloc]\n    end\n    return JuliaInterpreter.getline(lineinfo)\nend\n"
  },
  {
    "path": "src/ui.jl",
    "content": "const rebug_prompt_string = \"rebug> \"\nconst rebug_prompt_ref = Ref{Union{LineEdit.Prompt,Nothing}}(nothing)       # set by __init__\nconst interpret_prompt_ref = Ref{Union{LineEdit.Prompt,Nothing}}(nothing)   # set by __init__\n\n# For debugging\nfunction logaction(msg)\n    open(\"/tmp/rebugger.log\", \"a\") do io\n        println(io, \"* \", msg)\n        flush(io)\n    end\n    nothing\nend\n\ndummy() = nothing\nconst dummymethod = first(methods(dummy))\nconst dummyuuid   = UUID(UInt128(0))\n\nuuidextractor(str) = match(r\"getstored\\(\\\"([a-z0-9\\-]+)\\\"\\)\", str)\n\nmutable struct RebugHeader <: AbstractHeader\n    warnmsg::String\n    errmsg::String\n    uuid::UUID\n    current_method::Method\n    nlines::Int   # size of the printed header\nend\nRebugHeader() = RebugHeader(\"\", \"\", dummyuuid, dummymethod, 0)\n\nmutable struct InterpretHeader <: AbstractHeader\n    frame::Union{Nothing,Frame}\n    leveloffset::Int\n    val\n    bt\n    warnmsg::String\n    errmsg::String\n    nlines::Int   # size of the printed header\nend\nInterpretHeader() = InterpretHeader(nothing, 0, nothing, nothing, \"\", \"\", 0)\n\nstruct DummyAST end  # fictive input for the put!/take! evaluation by the InterpretREPL backend\n\nheader(s::LineEdit.MIState) = header(mode(s).repl)\nheader(repl::HeaderREPL) = repl.header\nheader(::LineEditREPL) = header(rebug_prompt_ref[].repl)  # default to the Rebug header\n\n# Custom methods\nset_method!(header::RebugHeader, method::Method) = header.current_method = method\n\nfunction set_uuid!(header::RebugHeader, uuid::UUID)\n    if haskey(stored, uuid)\n        header.uuid = uuid\n        header.current_method = stored[uuid].method\n    else\n        header.uuid = dummyuuid\n        header.current_method = dummymethod\n    end\n    uuid\nend\n\nfunction set_uuid!(header::RebugHeader, str::AbstractString)\n    m = uuidextractor(str)\n    uuid = if m isa RegexMatch && length(m.captures) == 1\n        UUID(m.captures[1])\n    else\n        dummyuuid\n    end\n    set_uuid!(header, uuid)\nend\n\nstruct FakePrompt{Buf<:IO}\n    input_buffer::Buf\nend\nLineEdit.mode(p::FakePrompt) = rebug_prompt_ref[]\n\n\"\"\"\n    stepin(s)\n\nGiven a buffer `s` representing a string and \"point\" (the seek position) set at a call expression,\nreplace the contents of the buffer with a `let` expression that wraps the *body* of the callee.\n\nFor example, if `s` has contents\n\n    <some code>\n    if x > 0.5\n        ^fcomplex(x)\n        <more code>\n\nwhere in the above `^` indicates `position(s)` (\"point\"), and if the definition of `fcomplex` is\n\n    function fcomplex(x::A, y=1, z=\"\"; kw1=3.2) where A<:AbstractArray{T} where T\n        <body>\n    end\n\nrewrite `s` so that its contents are\n\n    @eval ModuleOf_fcomplex let (x, y, z, kw1, A, T) = Main.Rebugger.getstored(id)\n        <body>\n    end\n\nwhere `Rebugger.getstored` returns has been pre-loaded with the values that would have been\nset when you called `fcomplex(x)` in `s` above.\nThis line can be edited and `eval`ed at the REPL to analyze or improve `fcomplex`,\nor can be used for further `stepin` calls.\n\"\"\"\nfunction stepin(s::LineEdit.MIState)\n    # Add the command we're tracing to the history. That way we can go \"up the call stack\".\n    pos = position(s)\n    cmd = String(take!(copy(LineEdit.buffer(s))))\n    add_history(s, cmd)\n    # Analyze the command string and step in\n    local uuid, letcmd\n    try\n        uuid, letcmd = stepin(LineEdit.buffer(s))\n    catch err\n        repl = rebug_prompt_ref[].repl\n        handled = false\n        if err isa StashingFailed\n            repl.header.warnmsg = \"Execution did not reach point\"\n            handled = true\n        elseif err isa Meta.ParseError || err isa StepException\n            repl.header.warnmsg = \"Expression at point is not a call expression\"\n            handled = true\n        elseif err isa EvalException\n            io = IOBuffer()\n            showerror(io, err.exception)\n            errstr = String(take!(io))\n            repl.header.errmsg = \"$errstr while evaluating $(err.exprstring)\"\n            handled = true\n        elseif err isa DefMissing\n            repl.header.errmsg = \"The expression for method $(err.method) was unavailable. Perhaps it was untracked or generated by code.\"\n            handled = true\n        end\n        if handled\n            buf = LineEdit.buffer(s)\n            LineEdit.edit_clear(buf)\n            write(buf, cmd)\n            seek(buf, pos)\n            return nothing\n        end\n        rethrow(err)\n    end\n    set_uuid!(header(s), uuid)\n    LineEdit.edit_clear(s)\n    LineEdit.edit_insert(s, letcmd)\n    return nothing\nend\n\nfunction capture_stacktrace(s)\n    if mode(s) isa LineEdit.PrefixHistoryPrompt\n        # history search, re-enter with the corresponding mode\n        LineEdit.accept_result(s, mode(s))\n        return capture_stacktrace(s)\n    end\n    cmdstring = LineEdit.content(s)\n    add_history(s, cmdstring)\n    print(REPL.terminal(s), '\\n')\n    expr = Meta.parse(cmdstring)\n    local uuids\n    try\n        uuids = capture_stacktrace(expr)\n    catch err\n        print(stderr, err)\n        return nothing\n    end\n    io = IOBuffer()\n    buf = FakePrompt(io)\n    hp = mode(s).hist\n    for uuid in uuids\n        println(io, generate_let_command(uuid))\n        REPL.add_history(hp, buf)\n        take!(io)\n    end\n    hp.cur_idx = length(hp.history) + 1\n    if !isempty(uuids)\n        set_uuid!(header(s), uuids[end])\n        print(REPL.terminal(s), '\\n')\n        LineEdit.edit_clear(s)\n        LineEdit.enter_prefix_search(s, find_prompt(s, LineEdit.PrefixHistoryPrompt), true)\n    end\n    return nothing\nend\n\nfunction add_history(s, str::AbstractString)\n    io = IOBuffer()\n    buf = FakePrompt(io)\n    hp = mode(s).hist\n    println(io, str)\n    REPL.add_history(hp, buf)\nend\n\nfunction interpret(s)\n    hdr = header(s)\n    hdr.bt = nothing\n    term = REPL.terminal(s)\n    cmdstring = LineEdit.content(s)\n    isempty(cmdstring) && return :done\n    add_history(s, cmdstring)\n    assigns, expr, display_result = simplecall(cmdstring)\n    tupleexpr = JuliaInterpreter.extract_args(Main, expr)\n    callargs = Core.eval(Main, tupleexpr)   # get the elements of the call\n    callexpr = Expr(:call, callargs...)\n    frame = JuliaInterpreter.enter_call_expr(callexpr)\n    if frame === nothing\n        local val\n        try\n            hdr.val = Core.eval(Main, callexpr)\n        catch err\n            hdr.val = err\n            hdr.bt = catch_backtrace()\n        end\n    else\n        # frame = JuliaInterpreter.maybe_step_through_wrapper!(frame)\n        hdr.frame = frame\n        deflines = expression_lines(frame)\n\n        print(term, '\\n') # to advance beyond the user's input line\n        nlines = 0\n        try\n            while true\n                HeaderREPLs.clear_nlines(term, nlines)\n                print_header(term, hdr)\n                if hdr.leveloffset == 0\n                    nlines = show_code(term, frame, deflines, nlines)\n                else\n                    f, Δ = frameoffset(frame, hdr.leveloffset)\n                    hdr.leveloffset -= Δ\n                    nlines = show_code(term, f, expression_lines(f), nlines)\n                end\n                cmd = read(term, Char)\n                if cmd == '?'\n                    hdr.warnmsg = \"\"\"\n                    Commands:\n                      space: next line\n                      enter: continue to next breakpoint or completion\n                          →: step in to next call\n                          ←: finish frame and return to caller\n                          ↑: display the caller frame\n                          ↓: display the callee frame\n                          b: insert breakpoint at current line\n                          c: insert conditional breakpoint at current line\n                          r: remove breakpoint at current line\n                          d: disable breakpoint at current line\n                          e: enable breakpoint at current line\n                          o: open current position in editor\n                          q: abort (returns nothing)\"\"\"\n                elseif cmd == ' '\n                    hdr.leveloffset = 0\n                    ret = debug_command(frame, :n)\n                    if ret === nothing\n                        hdr.val = JuliaInterpreter.get_return(root(frame))\n                        break\n                    end\n                    frame, deflines = refresh(frame, ret, deflines)\n                elseif cmd == '\\n' || cmd == '\\r'\n                    hdr.leveloffset = 0\n                    ret = debug_command(frame, :c)\n                    if ret === nothing\n                        hdr.val = JuliaInterpreter.get_return(root(frame))\n                        break\n                    end\n                    frame, deflines = refresh(frame, ret, deflines)\n                elseif cmd == 'q'\n                    hdr.val = nothing\n                    break\n                elseif cmd == 'o'\n                    f, Δ = frameoffset(frame, hdr.leveloffset)\n                    file, line = whereis(f)\n                    edit(file, line)\n                elseif cmd == '\\e'   # escape codes\n                    nxtcmd = read(term, Char)\n                    nxtcmd == \"O\" && (nxtcmd = '[')  # normalize escape code\n                    cmd *= nxtcmd\n                    while nxtcmd == '['\n                        nxtcmd = read(term, Char)\n                        cmd *= nxtcmd\n                    end\n                    if cmd == \"\\e[C\"  # right arrow\n                        hdr.leveloffset = 0\n                        ret = debug_command(frame, :s)\n                        if ret === nothing\n                            # Trying to \"step in\" at program exit\n                            hdr.val = JuliaInterpreter.get_return(frame)\n                            break\n                        end\n                        frame, deflines = refresh(frame, ret, deflines)\n                    elseif cmd == \"\\e[D\"  # left arrow\n                        hdr.leveloffset = 0\n                        ret = debug_command(frame, :finish)\n                        if ret === nothing\n                            hdr.val = JuliaInterpreter.get_return(root(frame))\n                            break\n                        end\n                        frame, deflines = refresh(frame, ret, deflines)\n                    elseif cmd == \"\\e[A\"  # up arrow\n                        hdr.leveloffset += 1\n                    elseif cmd == \"\\e[B\"  # down arrow\n                        hdr.leveloffset = max(0, hdr.leveloffset - 1)\n                    end\n                elseif cmd == 'b' || cmd == 'c'\n                    if cmd == 'b'\n                        cond = nothing\n                    else\n                        print(term, \"enter condition: \")\n                        condstr = \"\"\n                        c = read(term, Char)\n                        while c != '\\n' && c != '\\r'\n                            print(term, c)\n                            condstr *= c\n                            c = read(term, Char)\n                        end\n                        condex = Base.parse_input_line(condstr; filename=\"condition\")\n                        cond = (JuliaInterpreter.moduleof(frame), condex)\n                    end\n                    ret = JuliaInterpreter.whereis(frame)\n                    if ret === nothing\n                        @warn \"Failed to find location info at current statement\"\n                    else\n                        file, line = ret\n                        JuliaInterpreter.breakpoint(file, line, cond)\n                    end\n                elseif cmd == 'r'\n                    breakpoint_action(remove, frame)\n                elseif cmd == 'd'\n                    breakpoint_action(disable, frame)\n                elseif cmd == 'e'\n                    breakpoint_action(enable, frame)\n                else\n                    push!(msgs, cmd)\n                end\n                hdr.frame = frame\n            end\n        catch err\n            hdr.val = err\n            hdr.bt = catch_backtrace()\n        end\n    end\n    # Store the result\n    repl = mode(s).repl\n    if isdefined(repl, :backendref)\n        response = (display_result ? hdr.val : nothing, VERSION >= v\"1.2.0-DEV.249\" ? hdr.bt !== nothing : hdr.bt)\n        put!(repl.backendref.response_channel, response)\n    end\n    hdr.frame = nothing\n    # Do this last in case of errors\n    if assigns !== nothing && hdr.bt === nothing\n        Core.eval(Main, Expr(:(=), assigns, hdr.val))\n    end\n    return :done\nend\n\nfunction refresh(frame, ret, deflines)\n    @assert ret !== nothing\n    cframe, _ = ret\n    if cframe === frame\n        return frame, deflines\n    end\n    return cframe, expression_lines(cframe)\nend\n\n# Find the range of statement indexes that preceed a line\n# `thisline` should be in \"compiled\" numbering\nfunction coderange(src::CodeInfo, thisline)\n    codeloc = searchsortedfirst(src.linetable, LineNumberNode(thisline, \"\"); by=x->x.line)\n    if codeloc == 1\n        return 1:1\n    end\n    idxline = searchsortedfirst(src.codelocs, codeloc)\n    idxprev = searchsortedfirst(src.codelocs, codeloc-1) + 1\n    return idxprev:idxline\nend\ncoderange(framecode::FrameCode, thisline) = coderange(framecode.src, thisline)\n\nfunction breakpoint_action(f, frame)\n    ret = CodeTracking.whereis(frame)\n    if ret === nothing\n        @warn \"Failed to find location info at current statement\"\n    else\n        file, line = ret\n        for bp in JuliaInterpreter.breakpoints()\n            if bp isa JuliaInterpreter.BreakpointFileLocation && bp.path == file && bp.line == line\n                f(bp)\n            end\n        end\n    end\n    return nothing\nend\n\nfunction simplecall(cmdstring)\n    cmdstring = chomp(cmdstring)\n    display_result = !endswith(cmdstring, ';')\n    if !display_result\n        cmdstring = cmdstring[1:end-1]\n    end\n    expr = Meta.parse(cmdstring)\n    if expr.head == :(=)\n        assigns = expr.args[1]\n        expr = expr.args[2]\n    else\n        assigns = nothing\n    end\n    return assigns, expr, display_result\nend\n\n### REPL modes\n\nfunction HeaderREPLs.setup_prompt(repl::HeaderREPL{RebugHeader}, hascolor::Bool)\n    julia_prompt = find_prompt(repl.interface, \"julia\")\n\n    prompt = REPL.LineEdit.Prompt(\n        rebug_prompt_string;\n        prompt_prefix = hascolor ? repl.prompt_color : \"\",\n        prompt_suffix = hascolor ?\n            (repl.envcolors ? Base.input_color : repl.input_color) : \"\",\n        complete = julia_prompt.complete,\n        on_enter = REPL.return_callback)\n\n    prompt.on_done = HeaderREPLs.respond(repl, julia_prompt) do str\n        Base.parse_input_line(str; filename=\"REBUG\")\n    end\n    # hist will be handled automatically if repl.history_file is true\n    # keymap_dict is separate\n    return prompt, :rebug\nend\n\nfunction HeaderREPLs.append_keymaps!(keymaps, repl::HeaderREPL{RebugHeader})\n    julia_prompt = find_prompt(repl.interface, \"julia\")\n    kms = [\n        trigger_search_keymap(repl),\n        mode_termination_keymap(repl, julia_prompt),\n        trigger_prefix_keymap(repl),\n        REPL.LineEdit.history_keymap,\n        REPL.LineEdit.default_keymap,\n        REPL.LineEdit.escape_defaults,\n    ]\n    append!(keymaps, kms)\nend\n\n# To get it to parse the UUID whenever we move through the history, we have to specialize\n# this method\nfunction HeaderREPLs.activate_header(header::RebugHeader, p, s, termbuf, term)\n    str = String(take!(copy(LineEdit.buffer(s))))\n    set_uuid!(header, str)\nend\n\n## Interpret also requires a separate repl\n\nfunction HeaderREPLs.setup_prompt(repl::HeaderREPL{InterpretHeader}, hascolor::Bool)\n    julia_prompt = find_prompt(repl.interface, \"julia\")\n\n    prompt = REPL.LineEdit.Prompt(\n        \"interpret> \";   # should never be shown\n        prompt_prefix = hascolor ? repl.prompt_color : \"\",\n        prompt_suffix = hascolor ?\n            (repl.envcolors ? Base.input_color : repl.input_color) : \"\",\n        complete = julia_prompt.complete,\n        on_enter = REPL.return_callback)\n\n    prompt.on_done = HeaderREPLs.respond(repl, julia_prompt) do str\n        return DummyAST()\n    end\n    return prompt, :interpret\nend\n\nfunction HeaderREPLs.append_keymaps!(keymaps, repl::HeaderREPL{InterpretHeader})\n    # No keymaps\n    return keymaps\nend\n\nfunction HeaderREPLs.activate_header(header::InterpretHeader, p, s, termbuf, term)\nend\n\nfunction Base.put!(c::Channel, input::Tuple{DummyAST,Int})\n    return nothing\nend\n\nconst keybindings = Dict{Symbol,String}(\n    :stacktrace => \"\\es\",                        # Alt-s (\"[s]tacktrace\")\n    :stepin => \"\\ee\",                            # Alt-e (\"[e]nter\")\n    :interpret => \"\\ei\",                         # Alt-i (\"[i]nterpret\")\n)\n\nconst modeswitches = Dict{Any,Any}(\n    :stacktrace => (s, o...) -> capture_stacktrace(s),\n    :stepin => (s, o...) -> (stepin(s); enter_rebug(s)),\n    :interpret => (s, o...) -> (enter_interpret(s); interpret(s)),\n)\n\nfunction get_rebugger_modeswitch_dict()\n    rebugger_modeswitch = Dict()\n    for (action, keybinding) in keybindings\n        rebugger_modeswitch[keybinding] = modeswitches[action]\n    end\n    rebugger_modeswitch\nend\n\nfunction add_keybindings(main_repl; override::Bool=false, kwargs...)\n    history_prompt = find_prompt(main_repl.interface, LineEdit.PrefixHistoryPrompt)\n    julia_prompt = find_prompt(main_repl.interface, \"julia\")\n    rebug_prompt = find_prompt(main_repl.interface, \"rebug\")\n    for (action, keybinding) in kwargs\n        if !(action in keys(keybindings))\n            error(\"$action is not a supported action.\")\n        end\n        if !(keybinding isa Union{String,Vector{String}})\n            error(\"Expected the value for $action to be a String or Vector{String}, got $keybinding instead\")\n        end\n        if haskey(keybindings, action)\n            keybindings[action] = keybinding\n        end\n        # PackageCompiler can cause these keys to be added twice (once during compilation and once by __init__),\n        # so put these in a try/catch (issue #62)\n        if action == :interpret\n            try\n                LineEdit.add_nested_key!(julia_prompt.keymap_dict, keybinding, modeswitches[action], override=override)\n            catch\n            end\n        else\n            # We need Any here because \"cannot convert REPL.LineEdit.PrefixHistoryPrompt to an object of type REPL.LineEdit.Prompt\"\n            prompts = Any[julia_prompt, rebug_prompt]\n            if action == :stacktrace push!(prompts, history_prompt) end\n            for prompt in prompts\n                if keybinding isa Vector\n                    for kb in keybinding\n                        try\n                            LineEdit.add_nested_key!(prompt.keymap_dict, kb, modeswitches[action], override=override)\n                        catch\n                        end\n                    end\n                else\n                    try\n                        LineEdit.add_nested_key!(prompt.keymap_dict, keybinding, modeswitches[action], override=override)\n                    catch\n                    end\n                end\n            end\n        end\n    end\nend\n\n# These work only at the `rebug>` prompt\nconst rebugger_keys = Dict{Any,Any}(\n)\n\n## REPL commands TODO?:\n## \"\\em\" (meta-m): create REPL line that populates Main with arguments to current method\n## \"\\eS\" (meta-S): save version at REPL to file? (a little dangerous, perhaps make it configurable as to whether this is on)\n## F1 is \"^[OP\" (showvalues?), F4 is \"^[OS\" (showinputs?)\n\n\nenter_rebug(s) = mode_switch(s, rebug_prompt_ref[])\nenter_interpret(s) = mode_switch(s, interpret_prompt_ref[])\n\nfunction mode_switch(s, other_prompt)\n    buf = copy(LineEdit.buffer(s))\n    LineEdit.edit_clear(s)\n    LineEdit.transition(s, other_prompt) do\n        LineEdit.state(s, other_prompt).input_buffer = buf\n    end\nend\n\n# julia_prompt = find_prompt(main_repl.interface, \"julia\")\n# julia_prompt.keymap_dict['|'] = (s, o...) -> enter_count(s)\n"
  },
  {
    "path": "test/edit.jl",
    "content": "using Rebugger\nusing Rebugger: StopException\nusing Test, UUIDs, InteractiveUtils, REPL, Pkg, HeaderREPLs\nusing REPL.LineEdit\nusing Revise, Colors\n\nif !isdefined(Main, :RebuggerTesting)\n    includet(\"testmodule.jl\")   # so the source code here gets loaded\nend\n\nconst empty_kwvarargs = Rebugger.kwstasher()\nuuidextractor(str) = UUID(match(r\"getstored\\(\\\"([a-z0-9\\-]+)\\\"\\)\", str).captures[1])\n\nstruct ErrorsOnShow end\nBase.show(io::IO, ::ErrorsOnShow) = throw(ArgumentError(\"no show\"))\n\nconst SPACE = VERSION < v\"1.5.0-DEV.0\" ? \"\" : \" \"  # maybe 1.4.0-DEV.537? most likely 1.4.0-DEV.604\n\n@testset \"Rebugger\" begin\n    id = uuid1()\n    @test uuidextractor(\"vars = getstored(\\\"$id\\\") and more stuff\") == id\n    @testset \"Debug core\" begin\n        @testset \"Deepcopy\" begin\n            args = (3.2, rand(3,3), Rebugger, [Rebugger], \"hello\", sum, (2,3))\n            argc = Rebugger.safe_deepcopy(args...)\n            @test argc == args\n        end\n        @testset \"Signatures\" begin\n            @test Rebugger.signature_names!(:(f(x::Int, @nospecialize(y::String)))) == (:f, (:x, :y), (), ())\n            @test Rebugger.signature_names!(:(f(x::Int, $(Expr(:meta, :nospecialize, :(y::String)))))) ==\n                (:f, (:x, :y), (), ())\n            ex = :(f(::Type{T}, ::IndexStyle, x::Int, ::IndexStyle) where T)\n            @test Rebugger.signature_names!(ex) == (:f, (:T, :__IndexStyle_1, :x, :__IndexStyle_2), (), ())\n            @test ex == :(f(::Type{T}, __IndexStyle_1::IndexStyle, x::Int, __IndexStyle_2::IndexStyle) where T)\n            ex = :(f(Tuseless::Type{T}, ::IndexStyle, x::Int) where T)\n            @test Rebugger.signature_names!(ex) == (:f, (:Tuseless, :__IndexStyle_1, :x), (), (:T,))\n            @test ex == :(f(Tuseless::Type{T}, __IndexStyle_1::IndexStyle, x::Int) where T)\n            # issue #34\n            ex = :(_mapreduce_dim(f, op, ::NamedTuple{()}, A::AbstractArray, ::Colon))\n            @test Rebugger.signature_names!(ex) == (:_mapreduce_dim, (:f, :op, :__NamedTuple_1, :A, :__Colon_1), (), ())\n            @test ex == :(_mapreduce_dim(f, op, __NamedTuple_1::NamedTuple{()}, A::AbstractArray, __Colon_1::Colon))\n        end\n        @testset \"Caller buffer capture and insertion\" begin\n            function run_insertion(str, atstr)\n                RebuggerTesting.cbdata1[] = RebuggerTesting.cbdata2[] = Rebugger.stashed[] = nothing\n                io = IOBuffer()\n                idx = findfirst(atstr, str)\n                print(io, str)\n                seek(io, first(idx)-1)\n                callexpr = Rebugger.prepare_caller_capture!(io)\n                capstring = String(take!(io))\n                capexpr   = Meta.parse(capstring)\n                try\n                    Core.eval(RebuggerTesting, capexpr)\n                catch err\n                    isa(err, StopException) || rethrow(err)\n                end\n            end\n\n            str = \"\"\"\n            for i = 1:5\n                cbdata1[] = i\n                foo(12, 13; akw=\"modified\")\n                cbdata2[] = i\n            end\n            \"\"\"\n            @test run_insertion(str, \"foo\")\n            @test RebuggerTesting.cbdata1[] == 1\n            @test RebuggerTesting.cbdata2[] == nothing\n            @test Rebugger.stashed[] == (RebuggerTesting.foo, (12, 13), Rebugger.kwstasher(;akw=\"modified\"))\n            str = \"\"\"\n            for i = 1:5\n                error(\"not caught\")\n                foo(12, 13; akw=\"modified\")\n            end\n            \"\"\"\n            @test_throws ErrorException(\"not caught\") run_insertion(str, \"foo\")\n            @test_throws Rebugger.StepException(\"Rebugger can only step into expressions, got 77\") run_insertion(\"x = 77\", \"77\")\n\n            # Module-scoped calls\n            io = IOBuffer()\n            cmdstr = \"Scope.func(x, y, z)\"\n            print(io, cmdstr)\n            seek(io, 0)\n            callexpr = Rebugger.prepare_caller_capture!(io)\n            @test callexpr == :(Scope.func(x, y, z))\n            take!(io)\n\n            # getindex and setindex! expressions\n            cmdstr = \"x = a[2,3]\"\n            print(io, cmdstr)\n            seek(io, first(findfirst(\"a\", cmdstr))-1)\n            callexpr = Rebugger.prepare_caller_capture!(io)\n            @test callexpr == :(getindex(a, 2, 3))\n            take!(io)\n\n            cmdstr = \"a[2,3] = x\"\n            print(io, cmdstr)\n            seek(io, 0)\n            callexpr = Rebugger.prepare_caller_capture!(io)\n            @test callexpr == :(setindex!(a, x, 2, 3))\n            take!(io)\n\n            # Expressions that go beyond \"user intention\".\n            # More generally we should support marking, but in the case of && and || it's\n            # handled by lowering, so there is nothing to step into anyway.\n            for cmdstr in (\"f1(x) && f2(z)\", \"f1(x) || f2(z)\")\n                print(io, cmdstr)\n                seek(io, 0)\n                callexpr = Rebugger.prepare_caller_capture!(io)\n                @test callexpr == :(f1(x))\n                take!(io)\n            end\n\n            # issue #5\n            cmdstr = \"abs(abs(x))\"\n            print(io, cmdstr)\n            seek(io, 4)\n            callexpr = Rebugger.prepare_caller_capture!(io)\n            @test callexpr == :(abs(x))\n            take!(io)\n\n            # splat expressions\n            cmdstr = \"foo(bar(x)..., 1)\"\n            print(io, cmdstr)\n            idx = findfirst(\"bar\", cmdstr)\n            seek(io, first(idx)-1)\n            callexpr = Rebugger.prepare_caller_capture!(io)\n            @test callexpr == :(bar(x))\n        end\n\n        @testset \"Callee variable capture\" begin\n            def = quote\n                function complexargs(x::A, y=1, str=\"1.0\"; kw1=Float64, kw2=7, kwargs...) where A<:AbstractArray{T} where T\n                    return (x .+ y, parse(kw1, str), kw2)\n                end\n            end\n            f = Core.eval(RebuggerTesting, def)\n            @test f([8,9]) == ([9,10], 1.0, 7)\n            m = collect(methods(f))[end]\n            uuid = Rebugger.method_capture_from_callee(m, def)\n            @test  Rebugger.method_capture_from_callee(m, def) == uuid  # calling twice returns the previously-defined objects\n            fc = Rebugger.storefunc[uuid]\n            @test_throws StopException fc([8,9], 2, \"13\"; kw1=Int, kw2=0)\n            @test Rebugger.stored[uuid].varnames == (:x, :y, :str, :kw1, :kw2, :kwargs, :A, :T)\n            @test Rebugger.stored[uuid].varvals  == ([8,9], 2, \"13\", Int, 0, empty_kwvarargs, Vector{Int}, Int)\n            @test_throws StopException fc([8,9]; otherkw=77)\n            @test Rebugger.stored[uuid].varnames == (:x, :y, :str, :kw1, :kw2, :kwargs, :A, :T)\n            @test Rebugger.stored[uuid].varvals  == ([8,9], 1, \"1.0\", Float64, 7, pairs((otherkw=77,)), Vector{Int}, Int)\n\n            uuid2 = Rebugger.method_capture_from_callee(m, def; overwrite=true)\n            @test uuid2 != uuid\n            # note overwriting methods are not stored in storefunc, but our old `f` will call the new method\n            @test f([8,9], 2, \"13\"; kw1=Int, kw2=0) == ([10,11], 13, 0)\n            Core.eval(RebuggerTesting, def)\n            @test Rebugger.stored[uuid2].varnames == (:x, :y, :str, :kw1, :kw2, :kwargs, :A, :T)\n            @test Rebugger.stored[uuid2].varvals  == ([8,9], 2, \"13\", Int, 0, empty_kwvarargs, Vector{Int}, Int)\n\n            def = quote\n                @inline modifies!(x) = (x[1] += 1; x)\n            end\n            f = Core.eval(RebuggerTesting, def)\n            @test f([8,9]) == [9,9]\n            m = collect(methods(f))[end]\n            uuid = Rebugger.method_capture_from_callee(m, def)\n            fc = Rebugger.storefunc[uuid]\n            @test_throws StopException fc([8,9])\n            @test Rebugger.stored[uuid].varnames == (:x,)\n            @test Rebugger.stored[uuid].varvals  == ([8,9],)\n\n            # Extensions of functions from other modules\n            m = @which RebuggerTesting.foo()\n            uuid = Rebugger.method_capture_from_callee(m)\n            fc = Rebugger.storefunc[uuid]\n            @test_throws StopException fc()\n            @test Rebugger.stored[uuid].varnames == Rebugger.stored[uuid].varvals == ()\n        end\n\n        @testset \"Step in\" begin\n            function run_stepin(str, atstr)\n                io = IOBuffer()\n                idx = findfirst(atstr, str)\n                @test !isempty(idx)\n                print(io, str)\n                seek(io, first(idx)-1)\n                Rebugger.stepin(io)\n            end\n\n            str = \"RebuggerTesting.snoop0()\"\n            uuidref, cmd = run_stepin(str, str)\n            uuid1 = uuidextractor(cmd)\n            @test uuid1 == uuidref\n            @test cmd == \"\"\"\n            @eval Main.RebuggerTesting let () = Main.Rebugger.getstored(\"$uuid1\")\n            begin\n                snoop1(\"Spy\")\n            end\n            end\"\"\"\n            _, cmd = run_stepin(cmd, \"snoop1\")\n            uuid2 = uuidextractor(cmd)\n            @test cmd == \"\"\"\n            @eval Main.RebuggerTesting let (word,) = Main.Rebugger.getstored(\"$uuid2\")\n            begin\n                snoop2(word, \"on\")\n            end\n            end\"\"\"\n            _, cmd = run_stepin(cmd, \"snoop2\")\n            uuid3 = uuidextractor(cmd)\n            @test cmd == \"\"\"\n            @eval Main.RebuggerTesting let (word1, word2) = Main.Rebugger.getstored(\"$uuid3\")\n            begin\n                snoop3(word1, word2, \"arguments\")\n            end\n            end\"\"\"\n            @test Rebugger.getstored(string(uuid1)) == ()\n            @test Rebugger.getstored(string(uuid2)) == (\"Spy\",)\n            @test Rebugger.getstored(string(uuid3)) == (\"Spy\", \"on\")\n\n            str = \"RebuggerTesting.kwvarargs(1)\"\n            _, cmd = run_stepin(str, str)\n            uuid = uuidextractor(cmd)\n            @test cmd == \"\"\"\n            @eval Main.RebuggerTesting let (x, kw1, kwargs) = Main.Rebugger.getstored(\"$uuid\")\n            begin\n                kwvarargs2(x; kw1$(SPACE)=$(SPACE)kw1, kwargs...)\n            end\n            end\"\"\"\n            @test Rebugger.getstored(string(uuid)) == (1, 1, empty_kwvarargs)\n            cmd = run_stepin(cmd, \"kwvarargs2\")\n\n            str = \"RebuggerTesting.kwvarargs(1; passthrough=false)\"\n            _, cmd = run_stepin(str, str)\n            uuid = uuidextractor(cmd)\n            @test cmd == \"\"\"\n            @eval Main.RebuggerTesting let (x, kw1, kwargs) = Main.Rebugger.getstored(\"$uuid\")\n            begin\n                kwvarargs2(x; kw1$(SPACE)=$(SPACE)kw1, kwargs...)\n            end\n            end\"\"\"\n            @test Rebugger.getstored(string(uuid)) == (1, 1, pairs((passthrough=false,)))\n            _, cmd = run_stepin(cmd, \"kwvarargs2\")\n\n            # Step in to call-overloading methods\n            str = \"RebuggerTesting.hv_test(\\\"hi\\\")\"\n            _, cmd = run_stepin(str, str)\n            uuid = uuidextractor(cmd)\n            @test cmd == \"\"\"\n            @eval Main.RebuggerTesting let (hv, str) = Main.Rebugger.getstored(\"$uuid\")\n            begin\n                hv.x\n            end\n            end\"\"\"\n            @test Rebugger.getstored(string(uuid)) == (RebuggerTesting.hv_test, \"hi\")\n\n            # Step in to methods that do tuple-destructuring of arguments\n            str = \"RebuggerTesting.destruct(1, (2,3), 4)\"\n            @test eval(Meta.parse(str)) == 2\n            _, cmd = run_stepin(str, str)\n            uuid = uuidextractor(cmd)\n            @test cmd == \"\"\"\n            @eval Main.RebuggerTesting let (x, (a, b), y) = Main.Rebugger.getstored(\"$uuid\")\n            begin\n                a\n            end\n            end\"\"\"\n            @test Rebugger.getstored(string(uuid)) == (1, (2,3), 4)\n\n            # Step in to a broadcast call\n            str = \"sum.([[1,2], (3,5)])\"\n            uuid, cmd = run_stepin(str, str)\n            s = Rebugger.stored[uuid]\n            @test s.method.name == :broadcast\n            @test cmd == \"\"\"\n            @eval Base.Broadcast let (f, As, Tf) = Main.Rebugger.getstored(\"$uuid\")\n            begin\n                materialize(broadcasted(f, As...))\n            end\n            end\"\"\"\n            @test Rebugger.getstored(string(uuid)) == (sum, (Any[[1,2], (3,5)],), typeof(sum))\n            Core.eval(Main, Meta.parse(cmd)) == [3,8]\n\n            str = \"max.([1,5], [2,-3])\"\n            uuid, cmd = run_stepin(str, str)\n            s = Rebugger.stored[uuid]\n            @test s.method.name == :broadcast\n            @test cmd == \"\"\"\n            @eval Base.Broadcast let (f, As, Tf) = Main.Rebugger.getstored(\"$uuid\")\n            begin\n                materialize(broadcasted(f, As...))\n            end\n            end\"\"\"\n            @test Rebugger.getstored(string(uuid)) == (max, ([1,5], [2,-3]), typeof(max))\n            Core.eval(Main, Meta.parse(cmd)) == [2,5]\n\n            # Step in to a do block\n            str = \"RebuggerTesting.calldo()\"\n            uuidref, cmd = run_stepin(str, str)\n            uuid1 = uuidextractor(cmd)\n            @test uuid1 == uuidref\n            @test cmd == \"\"\"\n            @eval Main.RebuggerTesting let () = Main.Rebugger.getstored(\"$uuid1\")\n            begin\n                apply(2, 3, 4) do x, y, z\n                    snoop3(x, y, z)\n                end\n            end\n            end\"\"\"\n            uuidref, cmd = run_stepin(cmd, \"apply\")\n            uuid1 = uuidextractor(cmd)\n            @test uuid1 == uuidref\n            @test cmd == \"\"\"\n            @eval Main.RebuggerTesting let (f, args) = Main.Rebugger.getstored(\"$uuid1\")\n            begin\n                kwvarargs(f)\n                f(args...)\n            end\n            end\"\"\"\n        end\n\n        @testset \"Capture stacktrace\" begin\n            uuids = nothing\n            mktemp() do path, iostacktrace\n                redirect_stderr(iostacktrace) do\n                    uuids = Rebugger.capture_stacktrace(RebuggerTesting, :(snoop0()))\n                end\n                flush(iostacktrace)\n                str = read(path, String)\n                @test occursin(\"snoop3\", str)\n            end\n            @test Rebugger.stored[uuids[1]].varvals == ()\n            @test Rebugger.stored[uuids[2]].varvals == (\"Spy\",)\n            @test Rebugger.stored[uuids[3]].varvals == (\"Spy\", \"on\")\n            @test Rebugger.stored[uuids[4]].varvals == (\"Spy\", \"on\", \"arguments\", \"simply\", empty_kwvarargs, String)\n            @test_throws ErrorException(\"oops\") RebuggerTesting.snoop0()\n\n            st = try RebuggerTesting.kwfunctop(3) catch; stacktrace(catch_backtrace()) end\n            usrtrace, defs = Rebugger.pregenerated_stacktrace(st; topname=Symbol(\"macro expansion\"))\n            @test length(unique(usrtrace)) == length(usrtrace)\n            m = @which RebuggerTesting.kwfuncmiddle(1,1)\n            @test m ∈ usrtrace\n\n            # A case that tests inlining and several other aspects of argument capture\n            ex = :([1, 2, 3] .* [1, 2])\n            # Capture the actual stack trace, trimming it to avoid\n            # anything involving the `eval` itself\n            trace = try\n                Core.eval(Main, ex)\n            catch\n                stacktrace(catch_backtrace())\n            end\n            i = 1\n            while i <= length(trace)\n                t = trace[i]\n                if t.func == Symbol(\"top-level scope\")\n                    deleteat!(trace, i:length(trace))\n                end\n                i += 1\n            end\n            # Get the capture from Rebugger\n            uuids = mktemp() do path, iostacktrace\n                redirect_stderr(iostacktrace) do\n                    Rebugger.capture_stacktrace(Main, ex)\n                end\n            end\n            @test length(uuids) == length(trace)\n            for (uuid, t) in zip(reverse(uuids), trace)\n                @test Rebugger.stored[uuid].method.name == t.func\n            end\n\n            # Try capturing a method from Core. On binaries this would throw\n            # if we didn't catch it.\n            # Because the first entry is \"top-level scope\", and that terminates\n            # processing in Rebugger.pregenerated_stacktrace, we have to intervene a bit.\n            mod, ex = Main, :(Core.throw(ArgumentError(\"oops\")))\n            trace = try Core.eval(mod, command) catch err stacktrace(catch_backtrace()) end\n            usrtrace, defs = Rebugger.pregenerated_stacktrace(trace[2:3])\n            @test usrtrace isa Vector\n        end\n    end\n\n    @testset \"User interface\" begin\n        @testset \"Printing header\" begin\n            h = Rebugger.RebugHeader()\n            h.uuid = uuid = uuid1()\n            meth = @which RebuggerTesting.foo(1,2)\n            h.current_method = meth\n            Rebugger.stored[uuid] = Rebugger.Stored(meth, (:x, :y), (1, ErrorsOnShow()))\n            h.warnmsg = \"This is a warning\"\n            h.errmsg  = \"You will not have a second chance\"\n            io = IOBuffer()\n            Rebugger.print_header(io, h)\n            str = String(take!(io))\n            @test startswith(str, \"\"\"\n            This is a warning\n            You will not have a second chance\n            foo(x, y) in Main.RebuggerTesting at \"\"\") # skip the \"upper\" part of the file location\n            @test endswith(str, \"testmodule.jl:7\\n  x = 1\\n  y errors in its show method\")\n        end\n\n        @testset \"Demos\" begin\n            function prepare_step_command(cmd, atstr)\n                LineEdit.edit_clear(mistate)\n                idx = findfirst(atstr, cmd)\n                @test !isempty(idx)\n                LineEdit.replace_line(mistate, cmd)\n                buf = LineEdit.buffer(mistate)\n                seek(buf, first(idx)-1)\n                return mistate\n            end\n\n            function do_capture_stacktrace(cmd)\n                l = length(hist.history)\n                LineEdit.replace_line(mistate, cmd)\n                Rebugger.capture_stacktrace(mistate)\n                LineEdit.transition(mistate, julia_prompt)\n                return l+1:length(hist.history)\n            end\n\n            if isdefined(Base, :active_repl)\n                repl = Base.active_repl\n                mistate = repl.mistate\n                julia_prompt = find_prompt(mistate, \"julia\")\n                LineEdit.transition(mistate, julia_prompt)\n                hist = julia_prompt.hist\n                header = Rebugger.rebug_prompt_ref[].repl.header\n                histdel = 0\n\n                @testset \"show demo\" begin  # this is a demo that appears in the documentation\n                    cmd1 = \"show([1,2,4])\"\n                    s = prepare_step_command(cmd1, cmd1)\n                    Rebugger.stepin(s)\n                    histdel += 1\n                    uuid = header.uuid\n                    @test Rebugger.getstored(string(uuid)) == ([1,2,4],)\n                    cmd2 = LineEdit.content(s)\n                    s = prepare_step_command(cmd2, \"show(stdout::IO, x)\")\n                    Rebugger.stepin(s)\n                    histdel += 1\n                    uuid = header.uuid\n                    @test Rebugger.getstored(string(uuid))[2] == [1,2,4]\n                    cmd3 = LineEdit.content(s)\n                    s = prepare_step_command(cmd3, \"_show_empty\")\n                    Rebugger.stepin(s)\n                    histdel += 1\n                    @test header.warnmsg == \"Execution did not reach point\"\n                end\n\n                @testset \"Colors demo\" begin  # another demo that appears in the documentation\n                    desc = \"hsl(80%, 20%, 15%)\"\n                    cmd = \"colorant\\\"hsl(80%, 20%, 15%)\\\"\"\n                    local idx\n                    mktemp() do path, io\n                        redirect_stderr(io) do\n                            logs, _ = Test.collect_test_logs() do\n                                idx = do_capture_stacktrace(cmd)\n                            end\n                        end\n                        flush(io)\n                        seek(io, 0)\n                        @test countlines(io) >= 4\n                    end\n                    histdel += length(idx)\n                    @test length(idx) >= 5\n                    @test hist.history[idx[1]] == cmd\n                    @test occursin(\"error\", hist.history[idx[end]])\n                end\n\n                @testset \"Pkg demo\" begin\n                    updated = Pkg.UPDATED_REGISTRY_THIS_SESSION[]\n                    Pkg.UPDATED_REGISTRY_THIS_SESSION[] = true\n                    uuids = Rebugger.capture_stacktrace(Pkg, :(add(\"NoPkg\")))\n                    @test length(uuids) >= 2\n                    Pkg.UPDATED_REGISTRY_THIS_SESSION[] = updated\n                end\n\n                @testset \"Empty stacktraces\" begin\n                    cmd = \"ccall(:jl_throw, Nothing, (Any,), ArgumentError(\\\"oops\\\"))\"\n                    mktemp() do path, io\n                        redirect_stderr(io) do\n                            LineEdit.replace_line(mistate, cmd)\n                            @test Rebugger.capture_stacktrace(mistate) === nothing\n                            LineEdit.transition(mistate, julia_prompt)\n                        end\n                        flush(io)\n                        str = read(path, String)\n                        @test occursin(\"failed to capture\", str)\n                    end\n                end\n\n                LineEdit.edit_clear(mistate)\n                l = length(hist.history)\n                deleteat!(hist.history, l-histdel+1:l)\n                deleteat!(hist.modes, l-histdel+1:l)\n                hist.cur_idx = length(hist.history)+1\n            end\n        end\n    end\nend\n"
  },
  {
    "path": "test/interpret.jl",
    "content": "using Rebugger, JuliaInterpreter\nusing CodeTracking, Revise, Test, InteractiveUtils\n\nif !isdefined(Main, :fixable1)\n    includet(\"interpret_script.jl\")\nend\n\n@testset \"Expression-printing and line numbers\" begin\n    for m in (@which(Tuple((1,2))),\n              which(Base.show_vector, Tuple{IO,Any}),\n              which(Rebugger.interpret, Tuple{Any}),\n             )\n        def = definition(m)\n        linenos, line1, methlines = Rebugger.expression_lines(m)\n        @test length(linenos) == length(methlines)\n        @test issorted(skipmissing(linenos))\n        @test maximum(skipmissing(linenos)) >= maximum(CodeTracking.linerange(def))\n    end\n\n    # Line number fill-in\n    for f in (fixable1, fixable2, fixable3)\n        m = first(methods(f))\n        linenos, line1, methlines = Rebugger.expression_lines(m)\n        @test count(ismissing, linenos) == 1  # only the final end is ambiguous\n    end\n    linenos, line1, methlines = Rebugger.expression_lines(first(methods(unfixable1)))\n    @test count(ismissing, linenos) == 3\n\n    # Generated functions\n    for ndims = 2:3\n        frame = JuliaInterpreter.enter_call(call_generated1, ndims)\n        pc, n = frame.pc, JuliaInterpreter.nstatements(frame.framecode)\n        while pc < n-1\n            frame, pc = debug_command(frame, :se)\n        end\n        frame, pc = debug_command(frame, :si)\n        linenos, line1, methlines = Rebugger.expression_lines(frame)\n        @test length(methlines) == 3 && strip(methlines[2]) == string(Expr(:tuple, ntuple(i->:val, ndims)...))\n    end\n\n    # Unparsed methods\n    frame = JuliaInterpreter.enter_call(getline, LineNumberNode(0, Symbol(\"fake.jl\")))\n    frame, pc = debug_command(frame, :si)\n    m = JuliaInterpreter.scopeof(frame)\n    if m.file == Symbol(\"sysimg.jl\")  # sysimg.jl is excluded from Revise tracking\n        linenos, line1, methlines = Rebugger.expression_lines(frame)\n        @test linenos == [m.line]\n    end\n\n    # Internal macros (issue #63)\n    frame = JuliaInterpreter.enter_call(f63)\n    deflines = Rebugger.expression_lines(frame)\n    frame, pc = debug_command(frame, :n)\n    io = IOBuffer()\n    Rebugger.show_code(io, frame, deflines, 0)\n    str = String(take!(io))\n    @test occursin(\"y = 7\", str)\nend\n"
  },
  {
    "path": "test/interpret_script.jl",
    "content": "# Test functions for parsing\nfunction fixable1(x)\n    return x\nend\nfixable2(x) = x\nfunction fixable3(A)\n    s = 0\n    fi = firstindex(A)\n    for i in eachindex(A)\n        for j in fi:i-1\n            s += A[j]\n        end\n    end\n    return s\nend\nfunction unfixable1(A)\n    s = 0\n    fi = firstindex(A)\n    for i in eachindex(A)\n        for j in fi:i-1\n            s += A[j]\n        end end\n    return s\nend\n\n# Generated functions\n@generated function generated1(A::AbstractArray{T,N}, val) where {T,N}\n    ex = Expr(:tuple)\n    for i = 1:N\n        push!(ex.args, :val)\n    end\n    return ex\nend\ncall_generated1(ndims) = generated1(fill(0, ntuple(d->1, ndims)...), 7)\n\n# getproperty is defined in sysimg.jl\ngetline(lnn) = lnn.line\n\nfunction f63()\n    x = 1 + 1\n    @info \"hello\"\n    y = 7\nend\n"
  },
  {
    "path": "test/interpret_ui.jl",
    "content": "# This was copied from Debugger.jl and then modified\n\nusing TerminalRegressionTests, Rebugger, Revise, CodeTracking\nusing HeaderREPLs, REPL\nusing Test\n\nincludet(\"my_gcd.jl\")\n\nfunction run_terminal_test(cmd, validation, commands)\n    function compare_replace(em, target; replace=nothing)\n        # Compare two buffer, skipping over the equivalent of key=>rep replacement\n        # However, because of potential differences in wrapping we don't explicitly\n        # perform the replacement; instead, we make the comparison tolerant of difference\n        # `\\n`.\n        buf = IOBuffer()\n        decoratorbuf = IOBuffer()\n        TerminalRegressionTests.VT100.dump(buf, decoratorbuf, em)\n        outbuf = take!(buf)\n        success = true\n        if replace !== nothing\n            output = String(outbuf)\n            key, rep = replace\n            idxkey = findfirst(key, target)\n            iout, itgt = firstindex(output), firstindex(target)\n            outlast, tgtlast = lastindex(output), lastindex(target)\n            lrep = length(rep)\n            while success && iout <= outlast && itgt <= tgtlast\n                if itgt == first(idxkey)\n                    itgt += length(key)\n                    for c in rep\n                        cout = output[iout]\n                        while c != cout && cout == '\\n'\n                            iout = nextind(output, iout)\n                            cout = output[iout]\n                        end\n                        if c != cout\n                            success = false\n                            break\n                        end\n                        iout = nextind(output, iout)\n                    end\n                else\n                    cout, ctgt = output[iout], target[itgt]\n                    success = cout == ctgt\n                    iout, itgt = nextind(output, iout), nextind(target, itgt)\n                end\n            end\n            success && iout > outlast && itgt > tgtlast && return true\n        end\n        outbuf == codeunits(target) && return true\n        open(\"failed.out\",\"w\") do f\n            write(f, output)\n        end\n        open(\"expected.out\",\"w\") do f\n            write(f, target)\n        end\n        error(\"Test failed. Expected result written to expected.out,\n            actual result written to failed.out\")\n    end\n\n    dirpath = joinpath(@__DIR__, \"ui\", \"v$(VERSION.major).$(VERSION.minor)\")\n    isdir(dirpath) || mkpath(dirpath)\n    filepath = joinpath(dirpath, validation)\n    # Fix the path of gcd to match the current running version of Julia\n    gcdfile, gcdline = whereis(@which my_gcd(10, 20))\n    cmp(a, b, decorator) = compare_replace(a, b; replace=\"****\" => gcdfile*':'*string(gcdline))\n    TerminalRegressionTests.automated_test(cmp, filepath, commands) do emuterm\n    # TerminalRegressionTests.create_automated_test(filepath, commands) do emuterm\n        main_repl = REPL.LineEditREPL(emuterm, true)\n        main_repl.interface = REPL.setup_interface(main_repl)\n        main_repl.specialdisplay = REPL.REPLDisplay(main_repl)\n        main_repl.mistate = REPL.LineEdit.init_state(REPL.terminal(main_repl), main_repl.interface)\n        iprompt, eprompt = Rebugger.rebugrepl_init(main_repl, true)\n        repl = iprompt.repl\n        s = repl.mistate\n        s.current_mode = iprompt\n        repl.t = emuterm\n        REPL.LineEdit.edit_clear(s)\n        REPL.LineEdit.edit_insert(s, cmd)\n        Rebugger.interpret(s)\n    end\nend\n\nCTRL_C = \"\\x3\"\nEOT = \"\\x4\"\nUP_ARROW = \"\\e[A\"\n\nrun_terminal_test(\"my_gcd(10, 20)\",\n                  \"gcd.multiout\",\n                  ['\\n'])\nrun_terminal_test(\"__gcdval__ = my_gcd(10, 20);\",\n                  \"gcdsc.multiout\",\n                  ['\\n'])\n@test __gcdval__ == gcd(10, 20)\n"
  },
  {
    "path": "test/my_gcd.jl",
    "content": "# 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{Int8,UInt8,Int16,UInt16,Int32,UInt32,\nInt64,UInt64,Int128,UInt128}\n    a == 0 && return abs(b)\n    b == 0 && return abs(a)\n    za = trailing_zeros(a)\n    zb = trailing_zeros(b)\n    k = min(za, zb)\n    u = unsigned(abs(a >> za))\n    v = unsigned(abs(b >> zb))\n    while u != v\n        if u > v\n            u, v = v, u\n        end\n        v -= u\n        v >>= trailing_zeros(v)\n    end\n    r = u << k\n    # T(r) would throw InexactError; we want OverflowError instead\n    r > typemax(T) && throw(OverflowError(\"gcd($a, $b) overflows\"))\n    r % T\nend\n"
  },
  {
    "path": "test/runtests.jl",
    "content": "using Rebugger, Test\n\n@info \"These tests manipulate the console. Wait until you see \\\"Done\\\"\"\ninclude(\"edit.jl\")\ninclude(\"interpret.jl\")\nif Sys.isunix() && VERSION >= v\"1.1.0\"\n    include(\"interpret_ui.jl\")\nelse\n    @warn \"Skipping UI tests\"\nend\nprintln(\"Done\")\n"
  },
  {
    "path": "test/testmodule.jl",
    "content": "module RebuggerTesting\n\nconst cbdata1 = Ref{Any}(nothing)\nconst cbdata2 = Ref{Any}(nothing)\n\n# Do not alter the line number at which `foo` occurs\nfoo(x, y) = nothing\n\nsnoop0()             = snoop1(\"Spy\")\nsnoop1(word)         = snoop2(word, \"on\")\nsnoop2(word1, word2) = snoop3(word1, word2, \"arguments\")\nsnoop3(word1, word2, word3::T; adv=\"simply\", morekws...) where T = error(\"oops\")\n\nkwvarargs(x; kw1=1, kwargs...)  = kwvarargs2(x; kw1=kw1, kwargs...)\nkwvarargs2(x; kw1=0, passthrough=true) = (x, kw1, passthrough)\n\ndestruct(x, (a, b), y) = a\n\nstruct HasValue\n    x::Float64\nend\nconst hv_test = HasValue(11.1)\n\n(hv::HasValue)(str::String) = hv.x\n\n@inline kwfuncerr(y) = error(\"stop\")\n@noinline kwfuncmiddle(x::T, y::Integer=1; kw1=\"hello\", kwargs...) where T = kwfuncerr(y)\n@inline kwfunctop(x; kwargs...) = kwfuncmiddle(x, 2; kwargs...)\n\nfunction apply(f, args...)\n    kwvarargs(f)\n    f(args...)\nend\n\ncalldo() = apply(2, 3, 4) do x, y, z\n    snoop3(x, y, z)\nend\n\nend\n\nmodule RBT2\n\nusing ..RebuggerTesting\n\nbar(::Int) = 5\nRebuggerTesting.foo() = bar(1)\n\nend\n"
  },
  {
    "path": "test/ui/v1.0/gcd.multiout",
    "content": "++++++++++++++++++++++++++++++++++++++++++++++++++\n|gcd(10, 20)\n|gcd(a::T, b::T) where T<:Union{Int128, Int16, Int32, Int64, Int8, UInt128, UInt1\n|6, UInt32, UInt64, UInt8} in Base at intfuncs.jl:31\n|  a = 10\n|  b = 20\n|  T = Int64\n| 30  function gcd(a::T, b::T) where T <: Union{Int8, UInt8, Int16, UInt16, Int…\n| 31      a == 0 && return abs(b)\n| 32      b == 0 && return abs(a)\n| 33      za = trailing_zeros(a)\n|\n--------------------------------------------------\n|AAAAAAAAAAA\n|AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n|AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n|AAAAAAAA\n|AAAAAAAA\n|AAAAAAAAAAA\n|AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n|AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n|AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n|AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n|"
  },
  {
    "path": "test/ui/v1.0/gcdsc.multiout",
    "content": "++++++++++++++++++++++++++++++++++++++++++++++++++\n|__gcdval__ = gcd(10, 20);\n|gcd(a::T, b::T) where T<:Union{Int128, Int16, Int32, Int64, Int8, UInt128, UInt1\n|6, UInt32, UInt64, UInt8} in Base at intfuncs.jl:31\n|  a = 10\n|  b = 20\n|  T = Int64\n| 30  function gcd(a::T, b::T) where T <: Union{Int8, UInt8, Int16, UInt16, Int…\n| 31      a == 0 && return abs(b)\n| 32      b == 0 && return abs(a)\n| 33      za = trailing_zeros(a)\n|\n--------------------------------------------------\n|AAAAAAAAAAAAAAAAAAAAAAAAA\n|AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n|AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n|AAAAAAAA\n|AAAAAAAA\n|AAAAAAAAAAA\n|AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n|AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n|AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n|AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n|"
  },
  {
    "path": "test/ui/v1.1/gcd.multiout",
    "content": "++++++++++++++++++++++++++++++++++++++++++++++++++\n|gcd(10, 20)\n|gcd(a::T, b::T) where T<:Union{Int128, Int16, Int32, Int64, Int8, UInt128, UInt1\n|6, UInt32, UInt64, UInt8} in Base at intfuncs.jl:31\n|  a = 10\n|  b = 20\n|  T = Int64\n| 30  function gcd(a::T, b::T) where T <: Union{Int8, UInt8, Int16, UInt16, Int…\n| 31      a == 0 && return abs(b)\n| 32      b == 0 && return abs(a)\n| 33      za = trailing_zeros(a)\n|\n--------------------------------------------------\n|AAAAAAAAAAA\n|AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n|AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n|AAAAAAAA\n|AAAAAAAA\n|AAAAAAAAAAA\n|AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n|AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n|AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n|AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n|"
  },
  {
    "path": "test/ui/v1.1/gcdsc.multiout",
    "content": "++++++++++++++++++++++++++++++++++++++++++++++++++\n|__gcdval__ = gcd(10, 20);\n|gcd(a::T, b::T) where T<:Union{Int128, Int16, Int32, Int64, Int8, UInt128, UInt1\n|6, UInt32, UInt64, UInt8} in Base at intfuncs.jl:31\n|  a = 10\n|  b = 20\n|  T = Int64\n| 30  function gcd(a::T, b::T) where T <: Union{Int8, UInt8, Int16, UInt16, Int…\n| 31      a == 0 && return abs(b)\n| 32      b == 0 && return abs(a)\n| 33      za = trailing_zeros(a)\n|\n--------------------------------------------------\n|AAAAAAAAAAAAAAAAAAAAAAAAA\n|AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n|AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n|AAAAAAAA\n|AAAAAAAA\n|AAAAAAAAAAA\n|AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n|AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n|AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n|AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n|"
  },
  {
    "path": "test/ui/v1.2/gcd.multiout",
    "content": "++++++++++++++++++++++++++++++++++++++++++++++++++\n|gcd(10, 20)\n|gcd(a::T, b::T) where T<:Union{Int128, Int16, Int32, Int64, Int8, UInt128, UInt1\n|6, UInt32, UInt64, UInt8} in Base at ****\n|  a = 10\n|  b = 20\n|  T = Int64\n| 30  function gcd(a::T, b::T) where T <: Union{Int8, UInt8, Int16, UInt16, Int…\n| 31      a == 0 && return abs(b)\n| 32      b == 0 && return abs(a)\n| 33      za = trailing_zeros(a)\n|\n--------------------------------------------------\n|AAAAAAAAAAA\n|AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n|AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n|AAAAAAAA\n|AAAAAAAA\n|AAAAAAAAAAA\n|AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n|AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n|AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n|AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n|"
  },
  {
    "path": "test/ui/v1.2/gcdsc.multiout",
    "content": "++++++++++++++++++++++++++++++++++++++++++++++++++\n|__gcdval__ = gcd(10, 20);\n|gcd(a::T, b::T) where T<:Union{Int128, Int16, Int32, Int64, Int8, UInt128, UInt1\n|6, UInt32, UInt64, UInt8} in Base at ****\n|  a = 10\n|  b = 20\n|  T = Int64\n| 30  function gcd(a::T, b::T) where T <: Union{Int8, UInt8, Int16, UInt16, Int…\n| 31      a == 0 && return abs(b)\n| 32      b == 0 && return abs(a)\n| 33      za = trailing_zeros(a)\n|\n--------------------------------------------------\n|AAAAAAAAAAAAAAAAAAAAAAAAA\n|AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n|AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n|AAAAAAAA\n|AAAAAAAA\n|AAAAAAAAAAA\n|AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n|AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n|AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n|AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n|"
  },
  {
    "path": "test/ui/v1.3/gcd.multiout",
    "content": "++++++++++++++++++++++++++++++++++++++++++++++++++\n|my_gcd(10, 20)\n|my_gcd(a::T, b::T) where T<:Union{Int128, Int16, Int32, Int64, Int8, UInt128, UI\n|nt16, UInt32, UInt64, UInt8} in Main at ****\n|  a = 10\n|  b = 20\n|  T = Int64\n|  3  function my_gcd(a::T, b::T) where T <: Union{Int8, UInt8, Int16, UInt16, …\n|  4      a == 0 && return abs(b)\n|  5      b == 0 && return abs(a)\n|  6      za = trailing_zeros(a)\n|\n--------------------------------------------------\n|AAAAAAAAAAAAAA\n|AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n|AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n|AAAAAA\n|AAAAAAAA\n|AAAAAAAA\n|AAAAAAAAAAA\n|AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n|AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n|AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n|AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n|\n"
  },
  {
    "path": "test/ui/v1.3/gcdsc.multiout",
    "content": "++++++++++++++++++++++++++++++++++++++++++++++++++\n|__gcdval__ = my_gcd(10, 20);\n|my_gcd(a::T, b::T) where T<:Union{Int128, Int16, Int32, Int64, Int8, UInt128, UI\n|nt16, UInt32, UInt64, UInt8} in Main at ****\n|  a = 10\n|  b = 20\n|  T = Int64\n|  3  function my_gcd(a::T, b::T) where T <: Union{Int8, UInt8, Int16, UInt16, …\n|  4      a == 0 && return abs(b)\n|  5      b == 0 && return abs(a)\n|  6      za = trailing_zeros(a)\n|\n--------------------------------------------------\n|AAAAAAAAAAAAAAAAAAAAAAAAAAAA\n|AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n|AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n|AAAAAA\n|AAAAAAAA\n|AAAAAAAA\n|AAAAAAAAAAA\n|AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n|AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n|AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n|AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n|\n"
  },
  {
    "path": "test/ui/v1.5/gcd.multiout",
    "content": "++++++++++++++++++++++++++++++++++++++++++++++++++\n|my_gcd(10, 20)\n|my_gcd(a::T, b::T) where T<:Union{Int128, Int16, Int32, Int64, Int8, UInt128, UI\n|nt16, UInt32, UInt64, UInt8} in Main at ****\n|  a = 10\n|  b = 20\n|  T = Int64\n|  3  function my_gcd(a::T, b::T) where T <: Union{Int8, UInt8, Int16, UInt16, …\n|  4      a == 0 && return abs(b)\n|  5      b == 0 && return abs(a)\n|  6      za = trailing_zeros(a)\n|\n--------------------------------------------------\n|AAAAAAAAAAAAAA\n|AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n|AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n|AAAAAA\n|AAAAAAAA\n|AAAAAAAA\n|AAAAAAAAAAA\n|AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n|AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n|AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n|AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n|\n"
  },
  {
    "path": "test/ui/v1.5/gcdsc.multiout",
    "content": "++++++++++++++++++++++++++++++++++++++++++++++++++\n|__gcdval__ = my_gcd(10, 20);\n|my_gcd(a::T, b::T) where T<:Union{Int128, Int16, Int32, Int64, Int8, UInt128, UI\n|nt16, UInt32, UInt64, UInt8} in Main at ****\n|  a = 10\n|  b = 20\n|  T = Int64\n|  3  function my_gcd(a::T, b::T) where T <: Union{Int8, UInt8, Int16, UInt16, …\n|  4      a == 0 && return abs(b)\n|  5      b == 0 && return abs(a)\n|  6      za = trailing_zeros(a)\n|\n--------------------------------------------------\n|AAAAAAAAAAAAAAAAAAAAAAAAAAAA\n|AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n|AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n|AAAAAA\n|AAAAAAAA\n|AAAAAAAA\n|AAAAAAAAAAA\n|AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n|AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n|AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n|AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n|\n"
  }
]