Skip to content

Commit

Permalink
re-enable terminal's beep and use... something else! (#23201)
Browse files Browse the repository at this point in the history
In the REPL, when an action cannot be done, the user can be
notified by animating the prompt - no need to use built-in
terminal's beep ('\a'), which is anyway often disabled.
  • Loading branch information
rfourquet authored Sep 10, 2017
1 parent 0a7fd04 commit 8c2fc37
Show file tree
Hide file tree
Showing 3 changed files with 89 additions and 33 deletions.
112 changes: 84 additions & 28 deletions base/repl/LineEdit.jl
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,9 @@ mutable struct PromptState <: ModeState
# indentation of lines which do not include the prompt
# if negative, the width of the prompt is used
indent::Int
refresh_lock::Threads.AbstractLock
# this would better be Threads.Atomic{Float64}, but not supported on some platforms
beeping::Float64
end

options(s::PromptState) = isdefined(s.p, :repl) ? s.p.repl.options : Base.REPL.Options()
Expand Down Expand Up @@ -116,9 +119,54 @@ complete_line(c::EmptyCompletionProvider, s) = [], true, true
terminal(s::IO) = s
terminal(s::PromptState) = s.terminal


# these may be better stored in Prompt or LineEditREPL
const BEEP_DURATION = Ref(0.2)
const BEEP_BLINK = Ref(0.2)
const BEEP_MAXDURATION = Ref(1.0)
const BEEP_COLORS = ["\e[90m"] # gray (text_colors not yet available)
const BEEP_USE_CURRENT = Ref(true)

function beep(s::PromptState, duration::Real=BEEP_DURATION[], blink::Real=BEEP_BLINK[],
maxduration::Real=BEEP_MAXDURATION[];
colors=BEEP_COLORS, use_current::Bool=BEEP_USE_CURRENT[])
isinteractive() || return # some tests fail on some platforms
s.beeping = min(s.beeping + duration, maxduration)
@async begin
trylock(s.refresh_lock) || return
orig_prefix = s.p.prompt_prefix
colors = Base.copymutable(colors)
use_current && push!(colors, orig_prefix)
i = 0
while s.beeping > 0.0
prefix = colors[mod1(i+=1, end)]
s.p.prompt_prefix = prefix
refresh_multi_line(s, beeping=true)
sleep(blink)
s.beeping -= blink
end
s.p.prompt_prefix = orig_prefix
refresh_multi_line(s, beeping=true)
s.beeping = 0.0
unlock(s.refresh_lock)
end
end

function cancel_beep(s::PromptState)
# wait till beeping finishes
while !trylock(s.refresh_lock)
s.beeping = 0.0
sleep(.05)
end
unlock(s.refresh_lock)
end

beep(::ModeState) = nothing
cancel_beep(::ModeState) = nothing

for f in [:terminal, :on_enter, :add_history, :buffer, :(Base.isempty),
:replace_line, :refresh_multi_line, :input_string, :update_display_buffer,
:empty_undo, :push_undo, :pop_undo, :options]
:empty_undo, :push_undo, :pop_undo, :options, :cancel_beep, :beep]
@eval ($f)(s::MIState, args...) = $(f)(state(s), args...)
end

Expand Down Expand Up @@ -175,16 +223,19 @@ end

# Prompt Completions
function complete_line(s::MIState)
complete_line(state(s), s.key_repeats)
refresh_line(s)
:complete_line
if complete_line(state(s), s.key_repeats)
refresh_line(s)
:complete_line
else
beep(s)
:ignore
end
end

function complete_line(s::PromptState, repeats)
completions, partial, should_complete = complete_line(s.p.complete, s)
if isempty(completions)
beep(terminal(s))
elseif !should_complete
isempty(completions) && return false
if !should_complete
# should_complete is false for cases where we only want to show
# a list of possible completions but not complete, e.g. foo(\t
show_completions(s, completions)
Expand All @@ -205,6 +256,7 @@ function complete_line(s::PromptState, repeats)
show_completions(s, completions)
end
end
true
end

clear_input_area(terminal, s) = (_clear_input_area(terminal, s.ias); s.ias = InputAreaState(0, 0))
Expand All @@ -230,9 +282,9 @@ prompt_string(p::Prompt) = prompt_string(p.prompt)
prompt_string(s::AbstractString) = s
prompt_string(f::Function) = Base.invokelatest(f)

refresh_multi_line(s::ModeState) = refresh_multi_line(terminal(s), s)
refresh_multi_line(termbuf::TerminalBuffer, s::ModeState) = refresh_multi_line(termbuf, terminal(s), s)
refresh_multi_line(termbuf::TerminalBuffer, term, s::ModeState) = (@assert term == terminal(s); refresh_multi_line(termbuf,s))
refresh_multi_line(s::ModeState; kw...) = refresh_multi_line(terminal(s), s; kw...)
refresh_multi_line(termbuf::TerminalBuffer, s::ModeState; kw...) = refresh_multi_line(termbuf, terminal(s), s; kw...)
refresh_multi_line(termbuf::TerminalBuffer, term, s::ModeState; kw...) = (@assert term == terminal(s); refresh_multi_line(termbuf,s; kw...))
function refresh_multi_line(termbuf::TerminalBuffer, terminal::UnixTerminal, buf, state::InputAreaState, prompt = ""; indent = 0)
_clear_input_area(termbuf, state)

Expand Down Expand Up @@ -297,7 +349,6 @@ function refresh_multi_line(termbuf::TerminalBuffer, terminal::UnixTerminal, buf

#columns are 1 based
cmove_col(termbuf, curs_pos + 1)

# Updated cur_row,curs_row
return InputAreaState(cur_row, curs_row)
end
Expand Down Expand Up @@ -558,7 +609,7 @@ function edit_backspace(s::PromptState, align::Bool=options(s).backspace_align,
refresh_line(s)
else
pop_undo(s)
beep(terminal(s))
beep(s)
end
end

Expand Down Expand Up @@ -607,7 +658,7 @@ function edit_delete(s)
refresh_line(s)
else
pop_undo(s)
beep(terminal(s))
beep(s)
end
:edit_delete
end
Expand Down Expand Up @@ -676,7 +727,7 @@ end

function edit_yank(s::MIState)
if isempty(s.kill_ring)
beep(terminal(s))
beep(s)
return :ignore
end
setmark(s) # necessary for edit_yank_pop
Expand All @@ -689,7 +740,7 @@ end
function edit_yank_pop(s::MIState, require_previous_yank=true)
repeat = s.last_action (:edit_yank, :edit_yank_pop)
if require_previous_yank && !repeat || isempty(s.kill_ring)
beep(terminal(s))
beep(s)
:ignore
else
require_previous_yank || repeat || setmark(s)
Expand Down Expand Up @@ -857,7 +908,7 @@ function history_prev(s, hist)
move_input_start(s)
refresh_line(s)
else
beep(terminal(s))
beep(s)
end
end
function history_next(s, hist)
Expand All @@ -867,7 +918,7 @@ function history_next(s, hist)
move_input_end(s)
refresh_line(s)
else
beep(terminal(s))
beep(s)
end
end

Expand Down Expand Up @@ -1249,12 +1300,12 @@ end
terminal(s::SearchState) = s.terminal

function update_display_buffer(s::SearchState, data)
history_search(data.histprompt.hp, data.query_buffer, data.response_buffer, data.backward, false) || beep(terminal(s))
history_search(data.histprompt.hp, data.query_buffer, data.response_buffer, data.backward, false) || beep(s)
refresh_line(s)
end

function history_next_result(s::MIState, data::SearchState)
history_search(data.histprompt.hp, data.query_buffer, data.response_buffer, data.backward, true) || beep(terminal(s))
history_search(data.histprompt.hp, data.query_buffer, data.response_buffer, data.backward, true) || beep(s)
refresh_line(data)
end

Expand Down Expand Up @@ -1307,9 +1358,11 @@ function show(io::IO, s::PrefixSearchState)
isdefined(s,:mi) ? s.mi : "no MI")
end

refresh_multi_line(termbuf::TerminalBuffer, terminal::UnixTerminal,
s::Union{PromptState,PrefixSearchState}) = s.ias =
refresh_multi_line(termbuf, terminal, buffer(s), s.ias, s, indent = s.indent)
function refresh_multi_line(termbuf::TerminalBuffer, terminal::UnixTerminal,
s::Union{PromptState,PrefixSearchState}; beeping=false)
beeping || cancel_beep(s)
s.ias = refresh_multi_line(termbuf, terminal, buffer(s), s.ias, s, indent = s.indent)
end

input_string(s::PrefixSearchState) = String(take!(copy(s.response_buffer)))

Expand Down Expand Up @@ -1456,15 +1509,15 @@ function setup_search_keymap(hp)

# Backspace/^H
'\b' => (s,data,c)->(edit_backspace(data.query_buffer) ?
update_display_buffer(s, data) : beep(terminal(s))),
update_display_buffer(s, data) : beep(s)),
127 => KeyAlias('\b'),
# Meta Backspace
"\e\b" => (s,data,c)->(edit_delete_prev_word(data.query_buffer) ?
update_display_buffer(s, data) : beep(terminal(s))),
update_display_buffer(s, data) : beep(s)),
"\e\x7f" => "\e\b",
# Word erase to whitespace
"^W" => (s,data,c)->(edit_werase(data.query_buffer) ?
update_display_buffer(s, data) : beep(terminal(s))),
update_display_buffer(s, data) : beep(s)),
# ^C and ^D
"^C" => (s,data,c)->(edit_clear(data.query_buffer);
edit_clear(data.response_buffer);
Expand Down Expand Up @@ -1565,6 +1618,7 @@ function move_line_end(buf::IOBuffer)
end

function commit_line(s)
cancel_beep(s)
move_input_end(s)
refresh_line(s)
println(terminal(s))
Expand Down Expand Up @@ -1712,6 +1766,7 @@ AnyDict(
try # raise the debugger if present
ccall(:jl_raise_debugger, Int, ())
end
cancel_beep(s)
move_input_end(s)
refresh_line(s)
print(terminal(s), "^C\n\n")
Expand Down Expand Up @@ -1822,6 +1877,7 @@ activate(m::ModalInterface, s::MIState, termbuf, term::TextTerminal) =
commit_changes(t::UnixTerminal, termbuf) = write(t, take!(termbuf.out_stream))

function transition(f::Function, s::MIState, newmode)
cancel_beep(s)
if newmode === :abort
s.aborted = true
return
Expand Down Expand Up @@ -1879,7 +1935,7 @@ run_interface(::Prompt) = nothing

init_state(terminal, prompt::Prompt) =
PromptState(terminal, prompt, IOBuffer(), IOBuffer[], 1, InputAreaState(1, 1),
#=indent(spaces)=# -1)
#=indent(spaces)=# -1, Threads.SpinLock(), 0.0)

function init_state(terminal, m::ModalInterface)
s = MIState(m, m.modes[1], false, Dict{Any,Any}())
Expand Down Expand Up @@ -1941,7 +1997,7 @@ function edit_undo!(s::MIState)
if edit_undo!(state(s))
:edit_undo!
else
beep(terminal(s))
beep(s)
:ignore
end
end
Expand All @@ -1958,7 +2014,7 @@ function edit_redo!(s::MIState)
if s.last_action (:edit_redo!, :edit_undo!) && edit_redo!(state(s))
:edit_redo!
else
beep(terminal(s))
beep(s)
:ignore
end
end
Expand Down
8 changes: 4 additions & 4 deletions base/repl/REPL.jl
Original file line number Diff line number Diff line change
Expand Up @@ -493,14 +493,14 @@ function history_prev(s::LineEdit.MIState, hist::REPLHistoryProvider,
elseif m === :skip
history_prev(s, hist, num+1, save_idx)
else
Terminals.beep(LineEdit.terminal(s))
Terminals.beep(s)
end
end

function history_next(s::LineEdit.MIState, hist::REPLHistoryProvider,
num::Int=1, save_idx::Int = hist.cur_idx)
if num == 0
Terminals.beep(LineEdit.terminal(s))
Terminals.beep(s)
return
end
num < 0 && return history_prev(s, hist, -num, save_idx)
Expand All @@ -518,7 +518,7 @@ function history_next(s::LineEdit.MIState, hist::REPLHistoryProvider,
elseif m === :skip
history_next(s, hist, num+1, save_idx)
else
Terminals.beep(LineEdit.terminal(s))
Terminals.beep(s)
end
end

Expand Down Expand Up @@ -562,7 +562,7 @@ function history_move_prefix(s::LineEdit.PrefixSearchState,
end
end
end
Terminals.beep(LineEdit.terminal(s))
Terminals.beep(s)
end
history_next_prefix(s::LineEdit.PrefixSearchState, hist::REPLHistoryProvider, prefix::AbstractString) =
history_move_prefix(s, hist, prefix, false)
Expand Down
2 changes: 1 addition & 1 deletion base/repl/Terminals.jl
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ end

@eval clear(t::UnixTerminal) = write(t.out_stream, $"$(CSI)H$(CSI)2J")
@eval clear_line(t::UnixTerminal) = write(t.out_stream, $"\r$(CSI)0K")
#beep(t::UnixTerminal) = write(t.err_stream,"\x7")
beep(t::UnixTerminal) = write(t.err_stream,"\x7")

Base.displaysize(t::UnixTerminal) = displaysize(t.out_stream)

Expand Down

0 comments on commit 8c2fc37

Please sign in to comment.