Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

REPL: improve prompt! async function handler #54760

Merged
merged 2 commits into from
Jun 11, 2024
Merged
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
71 changes: 38 additions & 33 deletions stdlib/REPL/src/LineEdit.jl
Original file line number Diff line number Diff line change
Expand Up @@ -75,10 +75,10 @@ mutable struct MIState
key_repeats::Int
last_action::Symbol
current_action::Symbol
async_channel::Channel
async_channel::Channel{Function}
end

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

const BufferLike = Union{MIState,ModeState,IOBuffer}
const State = Union{MIState,ModeState}
Expand Down Expand Up @@ -2828,40 +2828,45 @@ function prompt!(term::TextTerminal, prompt::ModalInterface, s::MIState = init_s
try
activate(prompt, s, term, term)
old_state = mode(s)
l = Base.ReentrantLock()
t = @async while true
fcn = take!(s.async_channel)
status = @lock l fcn(s)
Copy link
Member

Choose a reason for hiding this comment

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

I don't think this works. match_input needs to be serialized with any potential execution of this function, which may affect the keymap, make arbitrary modifications to the REPL, etc.

Copy link
Member Author

Choose a reason for hiding this comment

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

So split out the stdin reading part of match_input out before the lock?

Copy link
Member

Choose a reason for hiding this comment

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

no, that doesn't work either. The semantics need to be that waiting for the input (through either channel) happens outside the lock, but the modification of the channel take! or read happens only inside the lock. Otherwise, you're potentially dropping input.

status ∈ (:ok, :ignore) || break
end
Base.errormonitor(t)
while true
kmap = keymap(s, prompt)
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
local status
# errors in keymaps shouldn't cause the REPL to fail, so wrap in a
# try/catch block
try
status = fcn(s, kdata)
catch e
@error "Error in the keymap" exception=e,catch_backtrace()
# try to cleanup and get `s` back to its original state before returning
transition(s, :reset)
transition(s, old_state)
status = :done
end
status !== :ignore && (s.last_action = s.current_action)
if status === :abort
s.aborted = true
return buffer(s), false, false
elseif status === :done
return buffer(s), true, false
elseif status === :suspend
if Sys.isunix()
return buffer(s), true, true
fcn = match_input(kmap, s)
@lock l begin
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
local status
# errors in keymaps shouldn't cause the REPL to fail, so wrap in a
# try/catch block
try
status = fcn(s, kdata)
catch e
@error "Error in the keymap" exception=e,catch_backtrace()
# try to cleanup and get `s` back to its original state before returning
transition(s, :reset)
transition(s, old_state)
status = :done
end
status !== :ignore && (s.last_action = s.current_action)
if status === :abort
s.aborted = true
return buffer(s), false, false
elseif status === :done
return buffer(s), true, false
elseif status === :suspend
if Sys.isunix()
return buffer(s), true, true
end
else
@assert status ∈ (:ok, :ignore)
end
else
@assert status ∈ (:ok, :ignore)
end
end
finally
Expand Down
2 changes: 1 addition & 1 deletion stdlib/REPL/src/REPL.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1281,7 +1281,7 @@ function setup_interface(
REPLExt = load_pkg()
if REPLExt isa Module && isdefined(REPLExt, :PkgCompletionProvider)
put!(s.async_channel,
function (s::MIState, _)
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
Expand Down
12 changes: 3 additions & 9 deletions stdlib/REPL/src/precompile.jl
Original file line number Diff line number Diff line change
Expand Up @@ -220,14 +220,8 @@ end
generate_precompile_statements()

precompile(Tuple{typeof(getproperty), REPL.REPLBackend, Symbol})

# Helps Pkg repl mode switch
precompile(Tuple{typeof(REPL.Terminals.clear_line), REPL.Terminals.TTYTerminal})
precompile(Tuple{typeof(Base.print), REPL.Terminals.TTYTerminal, Base.AnnotatedString{String}})
precompile(Tuple{typeof(Base.peek), Base.TTY, Type{Char}})
precompile(Tuple{typeof(Base.similar), Array{String, 1}})
precompile(Tuple{typeof(Base.Iterators.enumerate), Array{String, 1}})
precompile(Tuple{typeof(Base.setindex!), Array{String, 1}, String, Int64})
precompile(Tuple{typeof(Base.convert), Type{Base.Dict{String, Union{Array{String, 1}, String}}}, Base.Dict{String, Any}})
precompile(Tuple{typeof(Base.take!), Base.Channel{Function}})
precompile(Tuple{typeof(Base.put!), Base.Channel{Function}, Function})
precompile(Tuple{typeof(Core.kwcall), NamedTuple{names, T} where T<:Tuple where names, typeof(REPL.LineEdit.complete_line), REPL.LineEdit.EmptyCompletionProvider, Any})

end # Precompile