Skip to content

Commit

Permalink
Reimplement dummy pkg prompt as standard prompt (#54674)
Browse files Browse the repository at this point in the history
  • Loading branch information
IanButterworth authored Jun 9, 2024
1 parent 1c59231 commit 77c28ab
Show file tree
Hide file tree
Showing 3 changed files with 90 additions and 48 deletions.
11 changes: 8 additions & 3 deletions stdlib/REPL/src/LineEdit.jl
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,10 @@ mutable struct MIState
key_repeats::Int
last_action::Symbol
current_action::Symbol
async_channel::Channel
end

MIState(i, mod, c, a, m) = MIState(i, mod, c, a, m, String[], 0, Char[], 0, :none, :none)
MIState(i, mod, c, a, m) = MIState(i, mod, c, a, m, String[], 0, Char[], 0, :none, :none, Channel())

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

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

on_enter(s::PromptState) = s.p.on_enter(s)
on_enter(s::MIState) = state(s).p.on_enter(s)

move_input_start(s::BufferLike) = (seek(buffer(s), 0); nothing)
move_input_end(buf::IOBuffer) = (seekend(buf); nothing)
Expand Down Expand Up @@ -2829,7 +2830,11 @@ function prompt!(term::TextTerminal, prompt::ModalInterface, s::MIState = init_s
old_state = mode(s)
while true
kmap = keymap(s, prompt)
fcn = match_input(kmap, s)
waitany((
@async(eof(term) || peek(term, Char)),
@async(wait(s.async_channel)),
), throw=true)
fcn = isempty(s.async_channel) ? match_input(kmap, s) : take!(s.async_channel)
kdata = keymap_data(s, prompt)
s.current_action = :unknown # if the to-be-run action doesn't update this field,
# :unknown will be recorded in the last_action field
Expand Down
117 changes: 76 additions & 41 deletions stdlib/REPL/src/REPL.jl
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,7 @@ function check_for_missing_packages_and_run_hooks(ast)
mods = modules_to_be_loaded(ast)
filter!(mod -> isnothing(Base.identify_package(String(mod))), mods) # keep missing modules
if !isempty(mods)
isempty(install_packages_hooks) && Base.require_stdlib(Base.PkgId(Base.UUID("44cfe95a-1eb2-52ea-b672-e2afdf69b78f"), "Pkg"))
isempty(install_packages_hooks) && load_pkg()
for f in install_packages_hooks
Base.invokelatest(f, mods) && return
end
Expand Down Expand Up @@ -575,6 +575,7 @@ mutable struct LineEditREPL <: AbstractREPL
answer_color::String
shell_color::String
help_color::String
pkg_color::String
history_file::Bool
in_shell::Bool
in_help::Bool
Expand All @@ -587,13 +588,13 @@ mutable struct LineEditREPL <: AbstractREPL
interface::ModalInterface
backendref::REPLBackendRef
frontend_task::Task
function LineEditREPL(t,hascolor,prompt_color,input_color,answer_color,shell_color,help_color,history_file,in_shell,in_help,envcolors)
function LineEditREPL(t,hascolor,prompt_color,input_color,answer_color,shell_color,help_color,pkg_color,history_file,in_shell,in_help,envcolors)
opts = Options()
opts.hascolor = hascolor
if !hascolor
opts.beep_colors = [""]
end
new(t,hascolor,prompt_color,input_color,answer_color,shell_color,help_color,history_file,in_shell,
new(t,hascolor,prompt_color,input_color,answer_color,shell_color,help_color,pkg_color,history_file,in_shell,
in_help,envcolors,false,nothing, opts, nothing, Tuple{String,Int}[])
end
end
Expand All @@ -610,6 +611,7 @@ LineEditREPL(t::TextTerminal, hascolor::Bool, envcolors::Bool=false) =
hascolor ? Base.answer_color() : "",
hascolor ? Base.text_colors[:red] : "",
hascolor ? Base.text_colors[:yellow] : "",
hascolor ? Base.text_colors[:blue] : "",
false, false, false, envcolors
)

Expand Down Expand Up @@ -1080,6 +1082,20 @@ setup_interface(
extra_repl_keymap::Any = repl.options.extra_keymap
) = setup_interface(repl, hascolor, extra_repl_keymap)

const Pkg_pkgid = Base.PkgId(Base.UUID("44cfe95a-1eb2-52ea-b672-e2afdf69b78f"), "Pkg")
const Pkg_REPLExt_pkgid = Base.PkgId(Base.UUID("ceef7b17-42e7-5b1c-81d4-4cc4a2494ccf"), "REPLExt")

function load_pkg()
@lock Base.require_lock begin
REPLExt = Base.require_stdlib(Pkg_pkgid, "REPLExt")
# require_stdlib does not guarantee that the `__init__` of the package is done when loading is done async
# but we need to wait for the repl mode to be set up
lock = get(Base.package_locks, Pkg_REPLExt_pkgid.uuid, nothing)
lock !== nothing && wait(lock[2])
return REPLExt
end
end

# This non keyword method can be precompiled which is important
function setup_interface(
repl::LineEditREPL,
Expand Down Expand Up @@ -1155,14 +1171,43 @@ function setup_interface(
end,
sticky = true)

# Set up dummy Pkg mode that will be replaced once Pkg is loaded
# use 6 dots to occupy the same space as the most likely "@v1.xx" env name
dummy_pkg_mode = Prompt("(......) $PKG_PROMPT",
prompt_prefix = hascolor ? repl.pkg_color : "",
prompt_suffix = hascolor ?
(repl.envcolors ? Base.input_color : repl.input_color) : "",
repl = repl,
complete = LineEdit.EmptyCompletionProvider(),
on_done = respond(line->nothing, repl, julia_prompt),
on_enter = function (s::MIState)
# This is hit when the user tries to execute a command before the real Pkg mode has been
# switched to. Ok to do this even if Pkg is loading on the other task because of the loading lock.
REPLExt = load_pkg()
if REPLExt isa Module && isdefined(REPLExt, :PkgCompletionProvider)
for mode in repl.interface.modes
if mode isa LineEdit.Prompt && mode.complete isa REPLExt.PkgCompletionProvider
# pkg mode
buf = copy(LineEdit.buffer(s))
transition(s, mode) do
LineEdit.state(s, mode).input_buffer = buf
end
end
end
end
return true
end,
sticky = true)


################################# Stage II #############################

# Setup history
# We will have a unified history for all REPL modes
hp = REPLHistoryProvider(Dict{Symbol,Prompt}(:julia => julia_prompt,
:shell => shell_mode,
:help => help_mode))
:help => help_mode,
:pkg => dummy_pkg_mode))
if repl.history_file
try
hist_path = find_hist_file()
Expand All @@ -1185,6 +1230,7 @@ function setup_interface(
julia_prompt.hist = hp
shell_mode.hist = hp
help_mode.hist = hp
dummy_pkg_mode.hist = hp

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

Expand Down Expand Up @@ -1225,47 +1271,36 @@ function setup_interface(
end,
']' => function (s::MIState,o...)
if isempty(s) || position(LineEdit.buffer(s)) == 0
# print a dummy pkg prompt while Pkg loads
LineEdit.clear_line(LineEdit.terminal(s))
# use 6 .'s here because its the same width as the most likely `@v1.xx` env name
print(LineEdit.terminal(s), styled"{blue,bold:({gray:......}) pkg> }")
pkg_mode = nothing
transition_finished = false
iolock = Base.ReentrantLock() # to avoid race between tasks reading stdin & input buffer
# spawn Pkg load to avoid blocking typing during loading. Typing will block if only 1 thread
buf = copy(LineEdit.buffer(s))
transition(s, dummy_pkg_mode) do
LineEdit.state(s, dummy_pkg_mode).input_buffer = buf
end
# load Pkg on another thread if available so that typing in the dummy Pkg prompt
# isn't blocked, but instruct the main REPL task to do the transition via s.async_channel
t_replswitch = Threads.@spawn begin
pkgid = Base.PkgId(Base.UUID("44cfe95a-1eb2-52ea-b672-e2afdf69b78f"), "Pkg")
REPLExt = Base.require_stdlib(pkgid, "REPLExt")
REPLExt = load_pkg()
if REPLExt isa Module && isdefined(REPLExt, :PkgCompletionProvider)
for mode in repl.interface.modes
if mode isa LineEdit.Prompt && mode.complete isa REPLExt.PkgCompletionProvider
pkg_mode = mode
break
put!(s.async_channel,
function (s::MIState, _)
LineEdit.mode(s) === dummy_pkg_mode || return :ok
for mode in repl.interface.modes
if mode isa LineEdit.Prompt && mode.complete isa REPLExt.PkgCompletionProvider
buf = copy(LineEdit.buffer(s))
transition(s, mode) do
LineEdit.state(s, mode).input_buffer = buf
end
if !isempty(s) && @invokelatest(LineEdit.check_for_hint(s))
@invokelatest(LineEdit.refresh_line(s))
end
break
end
end
return :ok
end
end
end
if pkg_mode !== nothing
@lock iolock begin
buf = copy(LineEdit.buffer(s))
transition(s, pkg_mode) do
LineEdit.state(s, pkg_mode).input_buffer = buf
end
if !isempty(s)
@invokelatest(LineEdit.check_for_hint(s)) && @invokelatest(LineEdit.refresh_line(s))
end
transition_finished = true
end
)
end
end
Base.errormonitor(t_replswitch)
# while loading just accept all keys, no keymap functionality
while !istaskdone(t_replswitch)
# wait but only take if task is still running
peek(stdin, Char)
@lock iolock begin
transition_finished || edit_insert(s, read(stdin, Char))
end
end
else
edit_insert(s, ']')
end
Expand Down Expand Up @@ -1448,9 +1483,9 @@ function setup_interface(
b = Dict{Any,Any}[skeymap, mk, prefix_keymap, LineEdit.history_keymap, LineEdit.default_keymap, LineEdit.escape_defaults]
prepend!(b, extra_repl_keymap)

shell_mode.keymap_dict = help_mode.keymap_dict = LineEdit.keymap(b)
shell_mode.keymap_dict = help_mode.keymap_dict = dummy_pkg_mode.keymap_dict = LineEdit.keymap(b)

allprompts = LineEdit.TextInterface[julia_prompt, shell_mode, help_mode, search_prompt, prefix_prompt]
allprompts = LineEdit.TextInterface[julia_prompt, shell_mode, help_mode, dummy_pkg_mode, search_prompt, prefix_prompt]
return ModalInterface(allprompts)
end

Expand Down
10 changes: 6 additions & 4 deletions stdlib/REPL/test/repl.jl
Original file line number Diff line number Diff line change
Expand Up @@ -500,8 +500,9 @@ for prompt = ["TestΠ", () -> randstring(rand(1:10))]
repl_mode = repl.interface.modes[1]
shell_mode = repl.interface.modes[2]
help_mode = repl.interface.modes[3]
histp = repl.interface.modes[4]
prefix_mode = repl.interface.modes[5]
pkg_mode = repl.interface.modes[4]
histp = repl.interface.modes[5]
prefix_mode = repl.interface.modes[6]

hp = REPL.REPLHistoryProvider(Dict{Symbol,Any}(:julia => repl_mode,
:shell => shell_mode,
Expand Down Expand Up @@ -1588,8 +1589,9 @@ for prompt = ["TestΠ", () -> randstring(rand(1:10))]
repl_mode = repl.interface.modes[1]
shell_mode = repl.interface.modes[2]
help_mode = repl.interface.modes[3]
histp = repl.interface.modes[4]
prefix_mode = repl.interface.modes[5]
pkg_mode = repl.interface.modes[4]
histp = repl.interface.modes[5]
prefix_mode = repl.interface.modes[6]

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

4 comments on commit 77c28ab

@nanosoldier
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Executing the daily package evaluation, I will reply here when finished:

@nanosoldier runtests(isdaily = true)

@nanosoldier
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The package evaluation job you requested has completed - possible new issues were detected.
The full report is available.

@vtjnash
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@nanosoldier runbenchmarks(ALL, isdaily = true)

@nanosoldier
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your benchmark job has completed - possible performance regressions were detected. A full report can be found here.

Please sign in to comment.