Skip to content

Commit 93f7423

Browse files
use standard repl tooling
1 parent 1b6ec0d commit 93f7423

File tree

3 files changed

+90
-48
lines changed

3 files changed

+90
-48
lines changed

stdlib/REPL/src/LineEdit.jl

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -75,9 +75,10 @@ mutable struct MIState
7575
key_repeats::Int
7676
last_action::Symbol
7777
current_action::Symbol
78+
async_channel::Channel
7879
end
7980

80-
MIState(i, mod, c, a, m) = MIState(i, mod, c, a, m, String[], 0, Char[], 0, :none, :none)
81+
MIState(i, mod, c, a, m) = MIState(i, mod, c, a, m, String[], 0, Char[], 0, :none, :none, Channel())
8182

8283
const BufferLike = Union{MIState,ModeState,IOBuffer}
8384
const State = Union{MIState,ModeState}
@@ -2309,7 +2310,7 @@ keymap_data(state, ::Union{HistoryPrompt, PrefixHistoryPrompt}) = state
23092310

23102311
Base.isempty(s::PromptState) = s.input_buffer.size == 0
23112312

2312-
on_enter(s::PromptState) = s.p.on_enter(s)
2313+
on_enter(s::MIState) = state(s).p.on_enter(s)
23132314

23142315
move_input_start(s::BufferLike) = (seek(buffer(s), 0); nothing)
23152316
move_input_end(buf::IOBuffer) = (seekend(buf); nothing)
@@ -2829,7 +2830,11 @@ function prompt!(term::TextTerminal, prompt::ModalInterface, s::MIState = init_s
28292830
old_state = mode(s)
28302831
while true
28312832
kmap = keymap(s, prompt)
2832-
fcn = match_input(kmap, s)
2833+
waitany((
2834+
@async(eof(term) || peek(term, Char)),
2835+
@async(wait(s.async_channel)),
2836+
), throw=true)
2837+
fcn = isempty(s.async_channel) ? match_input(kmap, s) : take!(s.async_channel)
28332838
kdata = keymap_data(s, prompt)
28342839
s.current_action = :unknown # if the to-be-run action doesn't update this field,
28352840
# :unknown will be recorded in the last_action field

stdlib/REPL/src/REPL.jl

Lines changed: 76 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -269,7 +269,7 @@ function check_for_missing_packages_and_run_hooks(ast)
269269
mods = modules_to_be_loaded(ast)
270270
filter!(mod -> isnothing(Base.identify_package(String(mod))), mods) # keep missing modules
271271
if !isempty(mods)
272-
isempty(install_packages_hooks) && Base.require_stdlib(Base.PkgId(Base.UUID("44cfe95a-1eb2-52ea-b672-e2afdf69b78f"), "Pkg"))
272+
isempty(install_packages_hooks) && load_pkg()
273273
for f in install_packages_hooks
274274
Base.invokelatest(f, mods) && return
275275
end
@@ -575,6 +575,7 @@ mutable struct LineEditREPL <: AbstractREPL
575575
answer_color::String
576576
shell_color::String
577577
help_color::String
578+
pkg_color::String
578579
history_file::Bool
579580
in_shell::Bool
580581
in_help::Bool
@@ -587,13 +588,13 @@ mutable struct LineEditREPL <: AbstractREPL
587588
interface::ModalInterface
588589
backendref::REPLBackendRef
589590
frontend_task::Task
590-
function LineEditREPL(t,hascolor,prompt_color,input_color,answer_color,shell_color,help_color,history_file,in_shell,in_help,envcolors)
591+
function LineEditREPL(t,hascolor,prompt_color,input_color,answer_color,shell_color,help_color,pkg_color,history_file,in_shell,in_help,envcolors)
591592
opts = Options()
592593
opts.hascolor = hascolor
593594
if !hascolor
594595
opts.beep_colors = [""]
595596
end
596-
new(t,hascolor,prompt_color,input_color,answer_color,shell_color,help_color,history_file,in_shell,
597+
new(t,hascolor,prompt_color,input_color,answer_color,shell_color,help_color,pkg_color,history_file,in_shell,
597598
in_help,envcolors,false,nothing, opts, nothing, Tuple{String,Int}[])
598599
end
599600
end
@@ -610,6 +611,7 @@ LineEditREPL(t::TextTerminal, hascolor::Bool, envcolors::Bool=false) =
610611
hascolor ? Base.answer_color() : "",
611612
hascolor ? Base.text_colors[:red] : "",
612613
hascolor ? Base.text_colors[:yellow] : "",
614+
hascolor ? Base.text_colors[:blue] : "",
613615
false, false, false, envcolors
614616
)
615617

@@ -1080,6 +1082,20 @@ setup_interface(
10801082
extra_repl_keymap::Any = repl.options.extra_keymap
10811083
) = setup_interface(repl, hascolor, extra_repl_keymap)
10821084

1085+
const Pkg_pkgid = Base.PkgId(Base.UUID("44cfe95a-1eb2-52ea-b672-e2afdf69b78f"), "Pkg")
1086+
const Pkg_REPLExt_pkgid = Base.PkgId(Base.UUID("ceef7b17-42e7-5b1c-81d4-4cc4a2494ccf"), "REPLExt")
1087+
1088+
function load_pkg()
1089+
@lock Base.require_lock begin
1090+
REPLExt = Base.require_stdlib(Pkg_pkgid, "REPLExt")
1091+
# require_stdlib does not guarantee that the `__init__` of the package is done when loading is done async
1092+
# but we need to wait for the repl mode to be set up
1093+
lock = get(Base.package_locks, Pkg_REPLExt_pkgid.uuid, nothing)
1094+
lock !== nothing && wait(lock[2])
1095+
return REPLExt
1096+
end
1097+
end
1098+
10831099
# This non keyword method can be precompiled which is important
10841100
function setup_interface(
10851101
repl::LineEditREPL,
@@ -1155,14 +1171,43 @@ function setup_interface(
11551171
end,
11561172
sticky = true)
11571173

1174+
# Set up dummy Pkg mode that will be replaced once Pkg is loaded
1175+
# use 6 dots to occupy the same space as the most likely "@v1.xx" env name
1176+
dummy_pkg_mode = Prompt("(......) $PKG_PROMPT",
1177+
prompt_prefix = hascolor ? repl.pkg_color : "",
1178+
prompt_suffix = hascolor ?
1179+
(repl.envcolors ? Base.input_color : repl.input_color) : "",
1180+
repl = repl,
1181+
complete = LineEdit.EmptyCompletionProvider(),
1182+
on_done = respond(line->nothing, repl, julia_prompt),
1183+
on_enter = function (s::MIState)
1184+
# This is hit when the user tries to execute a command before the real Pkg mode has been
1185+
# switched to. Ok to do this even if Pkg is loading on the other task because of the loading lock.
1186+
REPLExt = load_pkg()
1187+
if REPLExt isa Module && isdefined(REPLExt, :PkgCompletionProvider)
1188+
for mode in repl.interface.modes
1189+
if mode isa LineEdit.Prompt && mode.complete isa REPLExt.PkgCompletionProvider
1190+
# pkg mode
1191+
buf = copy(LineEdit.buffer(s))
1192+
transition(s, mode) do
1193+
LineEdit.state(s, mode).input_buffer = buf
1194+
end
1195+
end
1196+
end
1197+
end
1198+
return true
1199+
end,
1200+
sticky = true)
1201+
11581202

11591203
################################# Stage II #############################
11601204

11611205
# Setup history
11621206
# We will have a unified history for all REPL modes
11631207
hp = REPLHistoryProvider(Dict{Symbol,Prompt}(:julia => julia_prompt,
11641208
:shell => shell_mode,
1165-
:help => help_mode))
1209+
:help => help_mode,
1210+
:pkg => dummy_pkg_mode))
11661211
if repl.history_file
11671212
try
11681213
hist_path = find_hist_file()
@@ -1185,6 +1230,7 @@ function setup_interface(
11851230
julia_prompt.hist = hp
11861231
shell_mode.hist = hp
11871232
help_mode.hist = hp
1233+
dummy_pkg_mode.hist = hp
11881234

11891235
julia_prompt.on_done = respond(x->Base.parse_input_line(x,filename=repl_filename(repl,hp)), repl, julia_prompt)
11901236

@@ -1225,47 +1271,36 @@ function setup_interface(
12251271
end,
12261272
']' => function (s::MIState,o...)
12271273
if isempty(s) || position(LineEdit.buffer(s)) == 0
1228-
# print a dummy pkg prompt while Pkg loads
1229-
LineEdit.clear_line(LineEdit.terminal(s))
1230-
# use 6 .'s here because its the same width as the most likely `@v1.xx` env name
1231-
print(LineEdit.terminal(s), styled"{blue,bold:({gray:......}) pkg> }")
1232-
pkg_mode = nothing
1233-
transition_finished = false
1234-
iolock = Base.ReentrantLock() # to avoid race between tasks reading stdin & input buffer
1235-
# spawn Pkg load to avoid blocking typing during loading. Typing will block if only 1 thread
1274+
buf = copy(LineEdit.buffer(s))
1275+
transition(s, dummy_pkg_mode) do
1276+
LineEdit.state(s, dummy_pkg_mode).input_buffer = buf
1277+
end
1278+
# load Pkg on another thread if available so that typing in the dummy Pkg prompt
1279+
# isn't blocked, but instruct the main REPL task to do the transition via s.async_channel
12361280
t_replswitch = Threads.@spawn begin
1237-
pkgid = Base.PkgId(Base.UUID("44cfe95a-1eb2-52ea-b672-e2afdf69b78f"), "Pkg")
1238-
REPLExt = Base.require_stdlib(pkgid, "REPLExt")
1281+
REPLExt = load_pkg()
12391282
if REPLExt isa Module && isdefined(REPLExt, :PkgCompletionProvider)
1240-
for mode in repl.interface.modes
1241-
if mode isa LineEdit.Prompt && mode.complete isa REPLExt.PkgCompletionProvider
1242-
pkg_mode = mode
1243-
break
1283+
put!(s.async_channel,
1284+
function (s::MIState, _)
1285+
LineEdit.mode(s) === dummy_pkg_mode || return :ok
1286+
for mode in repl.interface.modes
1287+
if mode isa LineEdit.Prompt && mode.complete isa REPLExt.PkgCompletionProvider
1288+
buf = copy(LineEdit.buffer(s))
1289+
transition(s, mode) do
1290+
LineEdit.state(s, mode).input_buffer = buf
1291+
end
1292+
if !isempty(s) && @invokelatest(LineEdit.check_for_hint(s))
1293+
@invokelatest(LineEdit.refresh_line(s))
1294+
end
1295+
break
1296+
end
1297+
end
1298+
return :ok
12441299
end
1245-
end
1246-
end
1247-
if pkg_mode !== nothing
1248-
@lock iolock begin
1249-
buf = copy(LineEdit.buffer(s))
1250-
transition(s, pkg_mode) do
1251-
LineEdit.state(s, pkg_mode).input_buffer = buf
1252-
end
1253-
if !isempty(s)
1254-
@invokelatest(LineEdit.check_for_hint(s)) && @invokelatest(LineEdit.refresh_line(s))
1255-
end
1256-
transition_finished = true
1257-
end
1300+
)
12581301
end
12591302
end
12601303
Base.errormonitor(t_replswitch)
1261-
# while loading just accept all keys, no keymap functionality
1262-
while !istaskdone(t_replswitch)
1263-
# wait but only take if task is still running
1264-
peek(stdin, Char)
1265-
@lock iolock begin
1266-
transition_finished || edit_insert(s, read(stdin, Char))
1267-
end
1268-
end
12691304
else
12701305
edit_insert(s, ']')
12711306
end
@@ -1448,9 +1483,9 @@ function setup_interface(
14481483
b = Dict{Any,Any}[skeymap, mk, prefix_keymap, LineEdit.history_keymap, LineEdit.default_keymap, LineEdit.escape_defaults]
14491484
prepend!(b, extra_repl_keymap)
14501485

1451-
shell_mode.keymap_dict = help_mode.keymap_dict = LineEdit.keymap(b)
1486+
shell_mode.keymap_dict = help_mode.keymap_dict = dummy_pkg_mode.keymap_dict = LineEdit.keymap(b)
14521487

1453-
allprompts = LineEdit.TextInterface[julia_prompt, shell_mode, help_mode, search_prompt, prefix_prompt]
1488+
allprompts = LineEdit.TextInterface[julia_prompt, shell_mode, help_mode, dummy_pkg_mode, search_prompt, prefix_prompt]
14541489
return ModalInterface(allprompts)
14551490
end
14561491

stdlib/REPL/test/repl.jl

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -500,8 +500,9 @@ for prompt = ["TestΠ", () -> randstring(rand(1:10))]
500500
repl_mode = repl.interface.modes[1]
501501
shell_mode = repl.interface.modes[2]
502502
help_mode = repl.interface.modes[3]
503-
histp = repl.interface.modes[4]
504-
prefix_mode = repl.interface.modes[5]
503+
pkg_mode = repl.interface.modes[4]
504+
histp = repl.interface.modes[5]
505+
prefix_mode = repl.interface.modes[6]
505506

506507
hp = REPL.REPLHistoryProvider(Dict{Symbol,Any}(:julia => repl_mode,
507508
:shell => shell_mode,
@@ -1588,8 +1589,9 @@ for prompt = ["TestΠ", () -> randstring(rand(1:10))]
15881589
repl_mode = repl.interface.modes[1]
15891590
shell_mode = repl.interface.modes[2]
15901591
help_mode = repl.interface.modes[3]
1591-
histp = repl.interface.modes[4]
1592-
prefix_mode = repl.interface.modes[5]
1592+
pkg_mode = repl.interface.modes[4]
1593+
histp = repl.interface.modes[5]
1594+
prefix_mode = repl.interface.modes[6]
15931595

15941596
hp = REPL.REPLHistoryProvider(Dict{Symbol,Any}(:julia => repl_mode,
15951597
:shell => shell_mode,

0 commit comments

Comments
 (0)