Skip to content

Commit

Permalink
add an ipython mode on top of the output prefix functionality
Browse files Browse the repository at this point in the history
  • Loading branch information
KristofferC committed Sep 16, 2022
1 parent 3d3f9ae commit 6238a64
Show file tree
Hide file tree
Showing 3 changed files with 113 additions and 3 deletions.
14 changes: 14 additions & 0 deletions stdlib/REPL/docs/src/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -616,6 +616,20 @@ julia> REPL.activate(CustomMod)
var 8 bytes Int64
```

## IPython mode

It is possible to get an interface which is similar to the IPython REPL with numbered input prompts and output prefixes. This is done by calling `REPL.ipython_mode()`. If you want to have this enabled on startup, add
```julia
atreplinit() do repl
if !isdefined(repl, :interface)
repl.interface = REPL.setup_interface(repl)
end
REPL.ipython_mode(repl)
end
```

to your `startup.jl` file.

## TerminalMenus

TerminalMenus is a submodule of the Julia REPL and enables small, low-profile interactive menus in the terminal.
Expand Down
63 changes: 60 additions & 3 deletions stdlib/REPL/src/REPL.jl
Original file line number Diff line number Diff line change
Expand Up @@ -359,8 +359,7 @@ end
consumer is an optional function that takes a REPLBackend as an argument
"""
function run_repl(repl::AbstractREPL, @nospecialize(consumer = x -> nothing); backend_on_current_task::Bool = true)
backend = REPLBackend()
function run_repl(repl::AbstractREPL, @nospecialize(consumer = x -> nothing); backend_on_current_task::Bool = true, backend = REPLBackend())
backend_ref = REPLBackendRef(backend)
cleanup = @task try
destroy(backend_ref, t)
Expand Down Expand Up @@ -1067,7 +1066,7 @@ function setup_interface(

shell_prompt_len = length(SHELL_PROMPT)
help_prompt_len = length(HELP_PROMPT)
jl_prompt_regex = r"^(?:\(.+\) )?julia> "
jl_prompt_regex = r"^In \[[0-9]+\]: |^(?:\(.+\) )?julia> "
pkg_prompt_regex = r"^(?:\(.+\) )?pkg> "

# Canonicalize user keymap input
Expand Down Expand Up @@ -1393,4 +1392,62 @@ function run_frontend(repl::StreamREPL, backend::REPLBackendRef)
nothing
end

module IPython

using ..REPL

__current_ast_transforms() = isdefined(Base, :active_repl_backend) ? Base.active_repl_backend.ast_transforms : REPL.repl_ast_transforms

function repl_eval_counter(hp)
length(hp.history)-hp.start_idx
end

function out_transform(x, repl=Base.active_repl)
return quote
julia_prompt = $repl.interface.modes[1]
n = $repl_eval_counter(julia_prompt.hist)
mod = $REPL.active_module()
if !isdefined(mod, :Out)
setglobal!(mod, :Out, Dict{Int, Any}())
end
local __temp_val = $x # workaround https://github.com/JuliaLang/julia/issues/46451
if __temp_val !== getglobal(mod, :Out) && __temp_val !== nothing # remove this?
getglobal(mod, :Out)[n] = __temp_val
end
__temp_val
end
end

function set_prompt(repl=Base.active_repl)
julia_prompt = repl.interface.modes[1]
julia_prompt.prompt = () -> string("In [", repl_eval_counter(julia_prompt.hist)+1, "]: ")
end

function set_output_prefix(repl=Base.active_repl)
julia_prompt = repl.interface.modes[1]
if REPL.hascolor(repl)
julia_prompt.output_prefix_prefix = Base.text_colors[:red]
end
julia_prompt.output_prefix = () -> string("Out[", repl_eval_counter(julia_prompt.hist), "]: ")
end

function __current_ast_transforms(repltask)
if repltask === nothing
isdefined(Base, :active_repl_backend) ? Base.active_repl_backend.ast_transforms : REPL.repl_ast_transforms
else
repltask.ast_transforms
end
end


function ipython_mode!(repl=Base.active_repl, repltask=nothing)
set_prompt(repl)
set_output_prefix(repl)
push!(__current_ast_transforms(repltask), ast -> out_transform(ast, repl))
return
end
end

import .IPython.ipython_mode!

end # module
39 changes: 39 additions & 0 deletions stdlib/REPL/test/repl.jl
Original file line number Diff line number Diff line change
Expand Up @@ -707,6 +707,11 @@ fake_repl() do stdin_write, stdout_read, repl
wait(c)
@test Main.A == 2

# Test removal of prefix in single statement paste
sendrepl2("\e[200~In [12]: A = 2.2\e[201~\n")
wait(c)
@test Main.A == 2.2

# Test removal of prefix in multiple statement paste
sendrepl2("""\e[200~
julia> mutable struct T17599; a::Int; end
Expand Down Expand Up @@ -1548,3 +1553,37 @@ fake_repl() do stdin_write, stdout_read, repl
LineEdit.edit_input(s, input_f)
@test buffercontents(LineEdit.buffer(s)) == "1234αβ56γ"
end

# Non standard output_prefix, tested via `ipython_mode!`
fake_repl() do stdin_write, stdout_read, repl
repl.interface = REPL.setup_interface(repl)

backend = REPL.REPLBackend()
repltask = @async begin
REPL.run_repl(repl; backend)
end

REPL.ipython_mode!(repl, backend)

global c = Condition()
sendrepl2(cmd) = write(stdin_write, "$cmd\n notify($(curmod_prefix)c)\n")

sendrepl2("\"z\" * \"z\"\n")
wait(c)
s = String(readuntil(stdout_read, "\"zz\""; keep=true))
@test contains(s, "In [1]")
@test contains(s, "Out[1]: \"zz\"")

sendrepl2("\"y\" * \"y\"\n")
wait(c)
s = String(readuntil(stdout_read, "\"yy\""; keep=true))
@test contains(s, "Out[3]: \"yy\"")

sendrepl2("Out[1] * Out[3]\n")
wait(c)
s = String(readuntil(stdout_read, "\"zzyy\""; keep=true))
@test contains(s, "Out[5]: \"zzyy\"")

write(stdin_write, '\x04')
Base.wait(repltask)
end

0 comments on commit 6238a64

Please sign in to comment.