I think it’s just something that nobody missed before It’s actually pretty easy to do, take this proof of concept: (and if somebody did already implement that before me: I’m sorry, my search-foo is pretty weak. It’s also still pretty brittle.)
copytorepl.jl
File content
struct CopyToREPL <: AbstractDisplay
io::IO
copyto::AbstractDisplay
insertnewlines::Bool
end
import Base: flush, close, display, displayable
flush(d::CopyToREPL) = flush(d.io)
close(d::CopyToREPL) = close(d.io) # not the copyto display, we don't want to tear it down too
display(d::CopyToREPL, @nospecialize x) = display(d, MIME"text/plain"(), x)
display(d::CopyToREPL, M::MIME"text/plain", @nospecialize x) = _display(d, M, x)
function _display(d::CopyToREPL, M, @nospecialize x)
show(d.io, M, x)
d.insertnewlines && println(d.io)
# you may insert a flush(d.io) here if you want to be able to kill the window without loosing content
show(d.copyto, M, x)
end
displayable(d::CopyToREPL, M::MIME) = istextmime(M)
# it's probably better not to catch every MIME, idk
#function display(d::CopyToREPL, M::MIME, @nospecialize x)
# displayable(d, M) || throw(MethodError(display, (d, M, x)))
# _display(d.io, M, x)
#end
function gethighesttextdisplay()
displays = Base.Multimedia.displays
message = "Probing displays!"
# because we need to find the last display that actually outputs text
# or we may end up backing up to a plotting pane or so. There may be a better way to do this.
for i = length(displays):-1:1
if Base.Multimedia.xdisplayable(displays[i], message)
try
display(displays[i], message)
return displays[i]
catch e
isa(e, MethodError) && (e.f === display || e.f === show) ||
rethrow()
end
end
end
error("No text display found, that's weird.")
end
function pophighestcopyto()
displays = Base.Multimedia.displays
for i = length(displays):-1:1
if displays[i] isa CopyToREPL
return splice!(displays, i)
end
end
throw(KeyError(CopyToREPL))
end
function startREPLcopy(noIO; append = false)
io = open(noIO, write = true, append = append)
startREPLcopy(io)
end
stdoutbackup = []
function startREPLcopy(io::IO; insertnewlines = true)
# insertnewlines because show(...) usually doesn't insert newlines after display,
# so the output is kinda mashed together. There may be cleaner way to do this.
backupdisplay = gethighesttextdisplay()
cpdisplay = CopyToREPL(io, backupdisplay, insertnewlines)
@info "Started copying REPL output to the specified location!"
pushdisplay(cpdisplay)
# TODO: It would be better to check here if the stdout hadn't been redirected before
# So technically, this may snatch a stdout and display more than the REPL
push!(stdoutbackup, Base.stdout)
Base.redirect_stdout(io)
# TODO I think this does only work for IOStreams, so maybe unwrap contexts and such
# TODO: may or may not be desirable to handle stderr
nothing
end
function endREPLcopy()
cpdisplay = try
pophighestcopyto()
catch e
isa(e, KeyError) || rethrow()
@warn "Couldn't stop copying the REPL output because it wasn't even started."
end
flush(cpdisplay)
oldstdout = pop!(stdoutbackup)
Base.redirect_stdout(oldstdout)
close(cpdisplay)
@info "Copying of REPL output stopped!"
nothing
end
and a demonstration:
julia> include("copytorepl.jl")
endREPLcopy (generic function with 1 method)
julia> startREPLcopy("testlog.log")
"Probing displays!"
[ Info: Started copying REPL output to the specified location!
julia> test = 2
2
julia> test
2
julia> println("Bla-Keks")
julia> show("αβ↑")
julia> notdefined
ERROR: UndefVarError: notdefined not defined
julia> # note that the error does not print
julia> rand(8)
8-element Vector{Float64}:
0.48184594197528385
0.4667812261893638
0.43096133172880347
0.791219818003811
0.12263780916474376
0.15859996424283396
0.28204060573062506
0.3664465404400239
julia> "Bye"
"Bye"
julia> endREPLcopy()
[ Info: Copying of REPL output stopped!
gives:
testlog.log
2
2
Bla-Keks
"αβ↑"8-element Vector{Float64}:
0.48184594197528385
0.4667812261893638
0.43096133172880347
0.791219818003811
0.12263780916474376
0.15859996424283396
0.28204060573062506
0.3664465404400239
"Bye"
May need some more formatting and cleanup but it is doable in principle.