Skip to content

Commit 99fcfdb

Browse files
committed
Move REPL support into a package extension
1 parent 8d4a07f commit 99fcfdb

File tree

4 files changed

+142
-104
lines changed

4 files changed

+142
-104
lines changed

Project.toml

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,15 @@ LibGit2 = "76f85450-5226-5b5a-8eaa-529ad045b433"
1313
LoweredCodeUtils = "6f1432cf-f94c-5a45-995e-cdbf5db27b0b"
1414
OrderedCollections = "bac558e1-5e72-5ebc-8fee-abe8a469f55d"
1515
Preferences = "21216c6a-2e73-6563-6e65-726566657250"
16-
REPL = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb"
1716
UUIDs = "cf7118a7-6976-5b1a-9a39-7adc72f591a4"
1817

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

2222
[extensions]
2323
ReviseDistributedExt = "Distributed"
24+
ReviseREPLExt = "REPL"
2425

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

5659
[targets]
57-
test = ["CatIndices", "Distributed", "EndpointRanges", "EponymTuples", "Example", "IndirectArrays", "InteractiveUtils", "MacroTools", "MappedArrays", "Pkg", "Random", "Requires", "RoundingIntegers", "Test", "UnsafeArrays"]
60+
test = ["CatIndices", "Distributed", "EndpointRanges", "EponymTuples", "Example", "IndirectArrays", "InteractiveUtils", "MacroTools", "MappedArrays", "Pkg", "Random", "REPL", "Requires", "RoundingIntegers", "Test", "UnsafeArrays"]

ext/ReviseREPLExt.jl

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
module ReviseREPLExt
2+
3+
import REPL
4+
using Revise: Revise, revision_queue, revise, pkgdatas, pkgdatas_lock, PkgData, FileInfo,
5+
ModuleExprsSigs, parse_source!, instantiate_sigs!, unwrap, isexpr, revise_first,
6+
LoweredCodeUtils, is_quotenode_egal, @warnpcfail
7+
8+
using Base: PkgId
9+
10+
11+
const original_repl_prefix = Ref{Union{String, Function, Nothing}}(nothing)
12+
13+
Revise.revise(::REPL.REPLBackend) = revise()
14+
15+
# Check if the active REPL backend is available
16+
active_repl_backend_available() = isdefined(Base, :active_repl_backend) && Base.active_repl_backend !== nothing
17+
18+
function maybe_set_prompt_color_impl(color::Symbol)
19+
if isdefined(Base, :active_repl)
20+
repl = Base.active_repl
21+
if isa(repl, REPL.LineEditREPL)
22+
if color === :warn
23+
# First save the original setting
24+
if original_repl_prefix[] === nothing
25+
original_repl_prefix[] = repl.mistate.current_mode.prompt_prefix
26+
end
27+
repl.mistate.current_mode.prompt_prefix = "\e[33m" # yellow
28+
else
29+
color = original_repl_prefix[]
30+
color === nothing && return nothing
31+
repl.mistate.current_mode.prompt_prefix = color
32+
original_repl_prefix[] = nothing
33+
end
34+
end
35+
end
36+
return nothing
37+
end
38+
39+
function add_definitions_from_repl_impl(filename::String)
40+
hist_idx = parse(Int, filename[6:end-1])
41+
hp = (Base.active_repl::REPL.LineEditREPL).interface.modes[1].hist::REPL.REPLHistoryProvider
42+
src = hp.history[hp.start_idx+hist_idx]
43+
id = PkgId(nothing, "@REPL")
44+
pkgdata = pkgdatas[id]
45+
mod_exs_sigs = ModuleExprsSigs(Main::Module)
46+
parse_source!(mod_exs_sigs, src, filename, Main::Module)
47+
instantiate_sigs!(mod_exs_sigs)
48+
fi = FileInfo(mod_exs_sigs)
49+
push!(pkgdata, filename=>fi)
50+
return fi
51+
end
52+
add_definitions_from_repl_impl(filename::AbstractString) = add_definitions_from_repl_impl(convert(String, filename)::String)
53+
54+
# `revise_first` gets called by the REPL prior to executing the next command (by having been pushed
55+
# onto the `ast_transform` list).
56+
# This uses invokelatest not for reasons of world age but to ensure that the call is made at runtime.
57+
# This allows `revise_first` to be compiled without compiling `revise` itself, and greatly
58+
# reduces the overhead of using Revise.
59+
function Revise.revise_first(ex)
60+
# Special-case `exit()` (issue #562)
61+
if isa(ex, Expr)
62+
exu = unwrap(ex)
63+
if isexpr(exu, :block, 2)
64+
arg1 = exu.args[1]
65+
if isexpr(arg1, :softscope)
66+
exu = exu.args[2]
67+
end
68+
end
69+
if isa(exu, Expr)
70+
exu.head === :call && length(exu.args) == 1 && exu.args[1] === :exit && return ex
71+
lhsrhs = LoweredCodeUtils.get_lhs_rhs(exu)
72+
if lhsrhs !== nothing
73+
lhs, _ = lhsrhs
74+
if isexpr(lhs, :ref) && length(lhs.args) == 1
75+
arg1 = lhs.args[1]
76+
isexpr(arg1, :(.), 2) && arg1.args[1] === :Revise && is_quotenode_egal(arg1.args[2], :active) && return ex
77+
end
78+
end
79+
end
80+
end
81+
# Check for queued revisions, and if so call `revise` first before executing the expression
82+
return Expr(:toplevel, :($isempty($revision_queue) || $(Base.invokelatest)($revise)), ex)
83+
end
84+
85+
function __init__()
86+
# Set REPL functions in Revise
87+
Revise.maybe_set_prompt_color = maybe_set_prompt_color_impl
88+
Revise.add_definitions_from_repl = add_definitions_from_repl_impl
89+
90+
if Revise.should_enable_revise()
91+
pushfirst!(REPL.repl_ast_transforms, revise_first)
92+
# #664: once a REPL is started, it no longer interacts with REPL.repl_ast_transforms
93+
if active_repl_backend_available()
94+
push!(Base.active_repl_backend.ast_transforms, revise_first)
95+
else
96+
# wait for active_repl_backend to exist
97+
# #719: do this async in case Revise is being loaded from startup.jl
98+
t = @async begin
99+
iter = 0
100+
while !active_repl_backend_available() && iter < 20
101+
sleep(0.05)
102+
iter += 1
103+
end
104+
if active_repl_backend_available()
105+
push!(Base.active_repl_backend.ast_transforms, revise_first)
106+
end
107+
end
108+
errormonitor(t)
109+
end
110+
end
111+
end
112+
113+
@warnpcfail precompile(active_repl_backend_available, ())
114+
@warnpcfail precompile(maybe_set_prompt_color_impl, (Symbol,))
115+
@warnpcfail precompile(add_definitions_from_repl_impl, (String,))
116+
@warnpcfail precompile(revise_first, (Expr,))
117+
118+
end

src/packagedef.jl

Lines changed: 19 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
Base.Experimental.@optlevel 1
22

3-
using FileWatching, REPL, UUIDs
3+
using FileWatching, UUIDs
44
using LibGit2: LibGit2
55
using Base: PkgId
66
using Base.Meta: isexpr
@@ -42,6 +42,13 @@ function is_master_worker end
4242

4343
## END abstract Distributed API
4444

45+
# These functions will be set/defined by the REPL extension if it's loaded
46+
maybe_set_prompt_color::Function = Returns(nothing)
47+
add_definitions_from_repl::Function = Returns(nothing)
48+
# Unlike the above variables we declare this function in Revise for convenience
49+
# because it's used in the tests.
50+
function revise_first end
51+
4552
"""
4653
Revise.active[]
4754
@@ -873,7 +880,6 @@ function revise(; throw::Bool=false)
873880

874881
nothing
875882
end
876-
revise(::REPL.REPLBackend) = revise()
877883

878884
"""
879885
revise(mod::Module; force::Bool=true)
@@ -1150,7 +1156,11 @@ function get_def(method::Method; modified_files=revision_queue)
11501156
filename = fixpath(String(method.file))
11511157
if startswith(filename, "REPL[")
11521158
isdefined(Base, :active_repl) || return false
1153-
fi = add_definitions_from_repl(filename)
1159+
fi = add_definitions_from_repl(filename)::Union{Nothing, FileInfo}
1160+
if isnothing(fi)
1161+
return false
1162+
end
1163+
11541164
hassig = false
11551165
for (_, exs) in fi.modexsigs
11561166
for siginfos in values(exs)
@@ -1224,21 +1234,6 @@ function get_expressions(id::PkgId, filename)
12241234
return fi.modexsigs
12251235
end
12261236

1227-
function add_definitions_from_repl(filename::String)
1228-
hist_idx = parse(Int, filename[6:end-1])
1229-
hp = (Base.active_repl::REPL.LineEditREPL).interface.modes[1].hist::REPL.REPLHistoryProvider
1230-
src = hp.history[hp.start_idx+hist_idx]
1231-
id = PkgId(nothing, "@REPL")
1232-
pkgdata = pkgdatas[id]
1233-
mod_exs_sigs = ModuleExprsSigs(Main::Module)
1234-
parse_source!(mod_exs_sigs, src, filename, Main::Module)
1235-
instantiate_sigs!(mod_exs_sigs)
1236-
fi = FileInfo(mod_exs_sigs)
1237-
push!(pkgdata, filename=>fi)
1238-
return fi
1239-
end
1240-
add_definitions_from_repl(filename::AbstractString) = add_definitions_from_repl(convert(String, filename)::String)
1241-
12421237
function update_stacktrace_lineno!(trace)
12431238
local nrep
12441239
for i = 1:length(trace)
@@ -1289,60 +1284,6 @@ function method_location(method::Method)
12891284
return method.file, method.line
12901285
end
12911286

1292-
# Set the prompt color to indicate the presence of unhandled revision errors
1293-
const original_repl_prefix = Ref{Union{String,Function,Nothing}}(nothing)
1294-
function maybe_set_prompt_color(color)
1295-
if isdefined(Base, :active_repl)
1296-
repl = Base.active_repl
1297-
if isa(repl, REPL.LineEditREPL)
1298-
if color === :warn
1299-
# First save the original setting
1300-
if original_repl_prefix[] === nothing
1301-
original_repl_prefix[] = repl.mistate.current_mode.prompt_prefix
1302-
end
1303-
repl.mistate.current_mode.prompt_prefix = "\e[33m" # yellow
1304-
else
1305-
color = original_repl_prefix[]
1306-
color === nothing && return nothing
1307-
repl.mistate.current_mode.prompt_prefix = color
1308-
original_repl_prefix[] = nothing
1309-
end
1310-
end
1311-
end
1312-
return nothing
1313-
end
1314-
1315-
# `revise_first` gets called by the REPL prior to executing the next command (by having been pushed
1316-
# onto the `ast_transform` list).
1317-
# This uses invokelatest not for reasons of world age but to ensure that the call is made at runtime.
1318-
# This allows `revise_first` to be compiled without compiling `revise` itself, and greatly
1319-
# reduces the overhead of using Revise.
1320-
function revise_first(ex)
1321-
# Special-case `exit()` (issue #562)
1322-
if isa(ex, Expr)
1323-
exu = unwrap(ex)
1324-
if isexpr(exu, :block, 2)
1325-
arg1 = exu.args[1]
1326-
if isexpr(arg1, :softscope)
1327-
exu = exu.args[2]
1328-
end
1329-
end
1330-
if isa(exu, Expr)
1331-
exu.head === :call && length(exu.args) == 1 && exu.args[1] === :exit && return ex
1332-
lhsrhs = LoweredCodeUtils.get_lhs_rhs(exu)
1333-
if lhsrhs !== nothing
1334-
lhs, _ = lhsrhs
1335-
if isexpr(lhs, :ref) && length(lhs.args) == 1
1336-
arg1 = lhs.args[1]
1337-
isexpr(arg1, :(.), 2) && arg1.args[1] === :Revise && is_quotenode_egal(arg1.args[2], :active) && return ex
1338-
end
1339-
end
1340-
end
1341-
end
1342-
# Check for queued revisions, and if so call `revise` first before executing the expression
1343-
return Expr(:toplevel, :($isempty($revision_queue) || $(Base.invokelatest)($revise)), ex)
1344-
end
1345-
13461287
steal_repl_backend(_...) = @warn """
13471288
`steal_repl_backend` has been removed from Revise, please update your `~/.julia/config/startup.jl`.
13481289
See https://timholy.github.io/Revise.jl/stable/config/
@@ -1375,7 +1316,7 @@ end
13751316

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

1378-
active_repl_backend_available() = isdefined(Base, :active_repl_backend) && Base.active_repl_backend !== nothing
1319+
should_enable_revise() = get(ENV, "JULIA_REVISE", "auto") == "auto"
13791320

13801321
function __init__()
13811322
ccall(:jl_generating_output, Cint, ()) == 1 && return nothing
@@ -1452,35 +1393,13 @@ function __init__()
14521393
push!(Base.include_callbacks, watch_includes)
14531394
push!(Base.package_callbacks, watch_package_callback)
14541395

1455-
mode = get(ENV, "JULIA_REVISE", "auto")
1456-
if mode == "auto"
1457-
pushfirst!(REPL.repl_ast_transforms, revise_first)
1458-
# #664: once a REPL is started, it no longer interacts with REPL.repl_ast_transforms
1459-
if active_repl_backend_available()
1460-
push!(Base.active_repl_backend.ast_transforms, revise_first)
1461-
else
1462-
# wait for active_repl_backend to exist
1463-
# #719: do this async in case Revise is being loaded from startup.jl
1464-
t = @async begin
1465-
iter = 0
1466-
while !active_repl_backend_available() && iter < 20
1467-
sleep(0.05)
1468-
iter += 1
1469-
end
1470-
if active_repl_backend_available()
1471-
push!(Base.active_repl_backend.ast_transforms, revise_first)
1472-
end
1473-
end
1474-
isdefined(Base, :errormonitor) && Base.errormonitor(t)
1475-
end
1476-
1477-
if isdefined(Main, :Atom)
1478-
Atom = getfield(Main, :Atom)
1479-
if Atom isa Module && isdefined(Atom, :handlers)
1480-
setup_atom(Atom)
1481-
end
1396+
if should_enable_revise() && isdefined(Main, :Atom)
1397+
Atom = getfield(Main, :Atom)
1398+
if Atom isa Module && isdefined(Atom, :handlers)
1399+
setup_atom(Atom)
14821400
end
14831401
end
1402+
14841403
return nothing
14851404
end
14861405

src/precompile.jl

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ function _precompile_()
2525
@warnpcfail precompile(Tuple{typeof(watch_package_callback), PkgId})
2626

2727
@warnpcfail precompile(Tuple{typeof(revise)})
28-
@warnpcfail precompile(Tuple{typeof(revise_first), Expr})
2928
@warnpcfail precompile(Tuple{typeof(includet), String})
3029
@warnpcfail precompile(Tuple{typeof(track), Module, String})
3130
# setindex! doesn't fully precompile, but it's still beneficial to do it
@@ -73,7 +72,6 @@ function _precompile_()
7372
@warnpcfail precompile(Tuple{typeof(filter_valid_cachefiles), String, Vector{String}})
7473
end
7574
@warnpcfail precompile(Tuple{typeof(Revise.iswritable), String})
76-
@warnpcfail precompile(Tuple{typeof(Revise.active_repl_backend_available)})
7775
@warnpcfail precompile(Tuple{typeof(pkg_fileinfo), PkgId})
7876
@warnpcfail precompile(Tuple{typeof(push!), WatchList, Pair{String,PkgId}})
7977
@warnpcfail precompile(Tuple{typeof(pushex!), ExprsSigs, Expr})

0 commit comments

Comments
 (0)