Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,15 @@ LibGit2 = "76f85450-5226-5b5a-8eaa-529ad045b433"
LoweredCodeUtils = "6f1432cf-f94c-5a45-995e-cdbf5db27b0b"
OrderedCollections = "bac558e1-5e72-5ebc-8fee-abe8a469f55d"
Preferences = "21216c6a-2e73-6563-6e65-726566657250"
REPL = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb"
UUIDs = "cf7118a7-6976-5b1a-9a39-7adc72f591a4"

[weakdeps]
Distributed = "8ba89e20-285c-5b6f-9357-94700520ee1b"
REPL = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb"

[extensions]
DistributedExt = "Distributed"
ReviseDistributedExt = "Distributed"
ReviseREPLExt = "REPL"

[compat]
CodeTracking = "3"
Expand All @@ -31,6 +32,7 @@ JuliaInterpreter = "0.10.8"
LoweredCodeUtils = "3.5"
OrderedCollections = "1"
Preferences = "1.5.0"
REPL = "1"
# Exclude Requires-1.1.0 - see https://github.com/JuliaPackaging/Requires.jl/issues/94
Requires = "~1.0, ^1.1.1"
julia = "1.10"
Expand All @@ -50,10 +52,11 @@ MacroTools = "1914dd2f-81c6-5fcd-8719-6d5c9610ff09"
MappedArrays = "dbb5928d-eab1-5f90-85c2-b9b0edb7c900"
Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f"
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
REPL = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb"
Requires = "ae029012-a4dd-5104-9daa-d747884805df"
RoundingIntegers = "d5f540fe-1c90-5db3-b776-2e2f362d9394"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
UnsafeArrays = "c4a57d5a-5b31-53a6-b365-19f8c011fbd6"

[targets]
test = ["CatIndices", "Distributed", "EndpointRanges", "EponymTuples", "Example", "IndirectArrays", "InteractiveUtils", "MacroTools", "MappedArrays", "Pkg", "Random", "Requires", "RoundingIntegers", "Test", "UnsafeArrays"]
test = ["CatIndices", "Distributed", "EndpointRanges", "EponymTuples", "Example", "IndirectArrays", "InteractiveUtils", "MacroTools", "MappedArrays", "Pkg", "Random", "REPL", "Requires", "RoundingIntegers", "Test", "UnsafeArrays"]
2 changes: 1 addition & 1 deletion ext/DistributedExt.jl → ext/ReviseDistributedExt.jl
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
module DistributedExt
module ReviseDistributedExt

import Distributed: myid, workers, remotecall

Expand Down
117 changes: 117 additions & 0 deletions ext/ReviseREPLExt.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
module ReviseREPLExt

using REPL: REPL
using Base: PkgId
using Base.Meta: isexpr
using Revise: @warnpcfail, FileInfo, LoweredCodeUtils, ModuleExprsInfos, Revise,
instantiate_sigs!, is_quotenode_egal, parse_source!, pkgdatas, revise, revise_first,
revision_queue, unwrap

const original_repl_prefix = Ref{Union{String, Function, Nothing}}(nothing)

Revise.revise(::REPL.REPLBackend) = revise()

# Check if the active REPL backend is available
active_repl_backend_available() = isdefined(Base, :active_repl_backend) && Base.active_repl_backend !== nothing

function maybe_set_prompt_color_impl(color::Symbol)
if isdefined(Base, :active_repl)
repl = Base.active_repl
if isa(repl, REPL.LineEditREPL)
if color === :warn
# First save the original setting
if original_repl_prefix[] === nothing
original_repl_prefix[] = repl.mistate.current_mode.prompt_prefix
end
repl.mistate.current_mode.prompt_prefix = "\e[33m" # yellow
else
color = original_repl_prefix[]
color === nothing && return nothing
repl.mistate.current_mode.prompt_prefix = color
original_repl_prefix[] = nothing
end
end
end
return nothing
end

function add_definitions_from_repl_impl(filename::String)
hist_idx = parse(Int, filename[6:end-1])
hp = (Base.active_repl::REPL.LineEditREPL).interface.modes[1].hist::REPL.REPLHistoryProvider
src = hp.history[hp.start_idx+hist_idx]
id = PkgId(nothing, "@REPL")
pkgdata = pkgdatas[id]
mod_exs_infos = ModuleExprsInfos(Main::Module)
parse_source!(mod_exs_infos, src, filename, Main::Module)
instantiate_sigs!(mod_exs_infos)
fi = FileInfo(mod_exs_infos)
push!(pkgdata, filename=>fi)
return fi
end
add_definitions_from_repl_impl(filename::AbstractString) = add_definitions_from_repl_impl(convert(String, filename)::String)

# `revise_first` gets called by the REPL prior to executing the next command (by having been pushed
# onto the `ast_transform` list).
# This uses invokelatest not for reasons of world age but to ensure that the call is made at runtime.
# This allows `revise_first` to be compiled without compiling `revise` itself, and greatly
# reduces the overhead of using Revise.
function Revise.revise_first(ex)
# Special-case `exit()` (issue #562)
if isa(ex, Expr)
exu = unwrap(ex)
if isexpr(exu, :block, 2)
arg1 = exu.args[1]
if isexpr(arg1, :softscope)
exu = exu.args[2]
end
end
if isa(exu, Expr)
exu.head === :call && length(exu.args) == 1 && exu.args[1] === :exit && return ex
lhsrhs = LoweredCodeUtils.get_lhs_rhs(exu)
if lhsrhs !== nothing
lhs, _ = lhsrhs
if isexpr(lhs, :ref) && length(lhs.args) == 1
arg1 = lhs.args[1]
isexpr(arg1, :(.), 2) && arg1.args[1] === :Revise && is_quotenode_egal(arg1.args[2], :active) && return ex
end
end
end
end
# Check for queued revisions, and if so call `revise` first before executing the expression
return Expr(:toplevel, :($isempty($revision_queue) || $(Base.invokelatest)($revise)), ex)
end

function __init__()
# Set REPL functions in Revise
Revise.maybe_set_prompt_color = maybe_set_prompt_color_impl
Revise.add_definitions_from_repl = add_definitions_from_repl_impl

if Revise.should_enable_revise()
pushfirst!(REPL.repl_ast_transforms, revise_first)
# #664: once a REPL is started, it no longer interacts with REPL.repl_ast_transforms
if active_repl_backend_available()
push!(Base.active_repl_backend.ast_transforms, revise_first)
else
# wait for active_repl_backend to exist
# #719: do this async in case Revise is being loaded from startup.jl
t = @async begin
iter = 0
while !active_repl_backend_available() && iter < 20
sleep(0.05)
iter += 1
end
if active_repl_backend_available()
push!(Base.active_repl_backend.ast_transforms, revise_first)
end
end
errormonitor(t)
end
end
end

@warnpcfail precompile(active_repl_backend_available, ())
@warnpcfail precompile(maybe_set_prompt_color_impl, (Symbol,))
@warnpcfail precompile(add_definitions_from_repl_impl, (String,))
@warnpcfail precompile(revise_first, (Expr,))

end
118 changes: 18 additions & 100 deletions src/packagedef.jl
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Base.Experimental.@optlevel 1

using FileWatching, REPL, UUIDs
using FileWatching, UUIDs
using LibGit2: LibGit2
using Base: PkgId, IdSet
using Base.Meta: isexpr
Expand Down Expand Up @@ -47,6 +47,13 @@ function is_master_worker end

## END abstract Distributed API

# These functions will be set/defined by the REPL extension if it's loaded
maybe_set_prompt_color::Function = Returns(nothing)
add_definitions_from_repl::Function = Returns(nothing)
# Unlike the above variables we declare this function in Revise for convenience
# because it's used in the tests.
function revise_first end

"""
Revise.active[]

Expand Down Expand Up @@ -1044,7 +1051,6 @@ function revise(; throw::Bool=false)

nothing
end
revise(::REPL.REPLBackend) = revise()

"""
revise(mod::Module; force::Bool=true)
Expand Down Expand Up @@ -1321,7 +1327,11 @@ function get_def(method::Method; modified_files=revision_queue)
filename = fixpath(String(method.file))
if startswith(filename, "REPL[")
isdefined(Base, :active_repl) || return false
fi = add_definitions_from_repl(filename)
fi = add_definitions_from_repl(filename)::Union{Nothing, FileInfo}
if isnothing(fi)
return false
end

hassig = false
for (_, exs_infos) in fi.mod_exs_infos
for exinfos in values(exs_infos)
Expand Down Expand Up @@ -1395,21 +1405,6 @@ function get_expressions(id::PkgId, filename)
return fi.mod_exs_infos
end

function add_definitions_from_repl(filename::String)
hist_idx = parse(Int, filename[6:end-1])
hp = (Base.active_repl::REPL.LineEditREPL).interface.modes[1].hist::REPL.REPLHistoryProvider
src = hp.history[hp.start_idx+hist_idx]
id = PkgId(nothing, "@REPL")
pkgdata = pkgdatas[id]
mod_exs_infos = ModuleExprsInfos(Main::Module)
parse_source!(mod_exs_infos, src, filename, Main::Module)
instantiate_sigs!(mod_exs_infos)
fi = FileInfo(mod_exs_infos)
push!(pkgdata, filename=>fi)
return fi
end
add_definitions_from_repl(filename::AbstractString) = add_definitions_from_repl(convert(String, filename)::String)

function update_stacktrace_lineno!(trace)
local nrep
for i = 1:length(trace)
Expand Down Expand Up @@ -1460,60 +1455,6 @@ function method_location(method::Method)
return method.file, method.line
end

# Set the prompt color to indicate the presence of unhandled revision errors
const original_repl_prefix = Ref{Union{String,Function,Nothing}}(nothing)
function maybe_set_prompt_color(color)
if isdefined(Base, :active_repl)
repl = Base.active_repl
if isa(repl, REPL.LineEditREPL)
if color === :warn
# First save the original setting
if original_repl_prefix[] === nothing
original_repl_prefix[] = repl.mistate.current_mode.prompt_prefix
end
repl.mistate.current_mode.prompt_prefix = "\e[33m" # yellow
else
color = original_repl_prefix[]
color === nothing && return nothing
repl.mistate.current_mode.prompt_prefix = color
original_repl_prefix[] = nothing
end
end
end
return nothing
end

# `revise_first` gets called by the REPL prior to executing the next command (by having been pushed
# onto the `ast_transform` list).
# This uses invokelatest not for reasons of world age but to ensure that the call is made at runtime.
# This allows `revise_first` to be compiled without compiling `revise` itself, and greatly
# reduces the overhead of using Revise.
function revise_first(ex)
# Special-case `exit()` (issue #562)
if isa(ex, Expr)
exu = unwrap(ex)
if isexpr(exu, :block, 2)
arg1 = exu.args[1]
if isexpr(arg1, :softscope)
exu = exu.args[2]
end
end
if isa(exu, Expr)
exu.head === :call && length(exu.args) == 1 && exu.args[1] === :exit && return ex
lhsrhs = LoweredCodeUtils.get_lhs_rhs(exu)
if lhsrhs !== nothing
lhs, _ = lhsrhs
if isexpr(lhs, :ref) && length(lhs.args) == 1
arg1 = lhs.args[1]
isexpr(arg1, :(.), 2) && arg1.args[1] === :Revise && is_quotenode_egal(arg1.args[2], :active) && return ex
end
end
end
end
# Check for queued revisions, and if so call `revise` first before executing the expression
return Expr(:toplevel, :($isempty($revision_queue) || $(Base.invokelatest)($revise)), ex)
end

steal_repl_backend(_...) = @warn """
`steal_repl_backend` has been removed from Revise, please update your `~/.julia/config/startup.jl`.
See https://timholy.github.io/Revise.jl/stable/config/
Expand Down Expand Up @@ -1546,7 +1487,7 @@ end

init_worker(p::Int) = init_worker(DistributedWorker(p))

active_repl_backend_available() = isdefined(Base, :active_repl_backend) && Base.active_repl_backend !== nothing
should_enable_revise() = get(ENV, "JULIA_REVISE", "auto") == "auto"

function __init__()
ccall(:jl_generating_output, Cint, ()) == 1 && return nothing
Expand Down Expand Up @@ -1623,33 +1564,10 @@ function __init__()
push!(Base.include_callbacks, watch_includes)
push!(Base.package_callbacks, watch_package_callback)

mode = get(ENV, "JULIA_REVISE", "auto")
if mode == "auto"
pushfirst!(REPL.repl_ast_transforms, revise_first)
# #664: once a REPL is started, it no longer interacts with REPL.repl_ast_transforms
if active_repl_backend_available()
push!(Base.active_repl_backend.ast_transforms, revise_first)
else
# wait for active_repl_backend to exist
# #719: do this async in case Revise is being loaded from startup.jl
t = @async begin
iter = 0
while !active_repl_backend_available() && iter < 20
sleep(0.05)
iter += 1
end
if active_repl_backend_available()
push!(Base.active_repl_backend.ast_transforms, revise_first)
end
end
isdefined(Base, :errormonitor) && Base.errormonitor(t)
end

if isdefined(Main, :Atom)
Atom = getfield(Main, :Atom)
if Atom isa Module && isdefined(Atom, :handlers)
setup_atom(Atom)
end
if should_enable_revise() && isdefined(Main, :Atom)
Atom = getfield(Main, :Atom)
if Atom isa Module && isdefined(Atom, :handlers)
setup_atom(Atom)
end
end

Expand Down
2 changes: 0 additions & 2 deletions src/precompile.jl
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ function _precompile_()
@warnpcfail precompile(Tuple{typeof(watch_package_callback), PkgId})

@warnpcfail precompile(Tuple{typeof(revise)})
@warnpcfail precompile(Tuple{typeof(revise_first), Expr})
@warnpcfail precompile(Tuple{typeof(includet), String})
@warnpcfail precompile(Tuple{typeof(track), Module, String})
# setindex! doesn't fully precompile, but it's still beneficial to do it
Expand Down Expand Up @@ -73,7 +72,6 @@ function _precompile_()
@warnpcfail precompile(Tuple{typeof(filter_valid_cachefiles), String, Vector{String}})
end
@warnpcfail precompile(Tuple{typeof(Revise.iswritable), String})
@warnpcfail precompile(Tuple{typeof(Revise.active_repl_backend_available)})
@warnpcfail precompile(Tuple{typeof(pkg_fileinfo), PkgId})
@warnpcfail precompile(Tuple{typeof(push!), WatchList, Pair{String,PkgId}})
@warnpcfail precompile(Tuple{typeof(pushex!), ExprsInfos, Expr})
Expand Down
2 changes: 1 addition & 1 deletion test/start_late.jl
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ while !isdefined(Base, :active_repl_backend) || isnothing(Base.active_repl_backe
sleep(0.5)
end

using Revise
using Revise, REPL
@test Revise.revise_first ∈ Base.active_repl_backend.ast_transforms

exit()
Loading