Skip to content

Commit 8c2fc37

Browse files
authored
re-enable terminal's beep and use... something else! (#23201)
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.
1 parent 0a7fd04 commit 8c2fc37

File tree

3 files changed

+89
-33
lines changed

3 files changed

+89
-33
lines changed

base/repl/LineEdit.jl

+84-28
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,9 @@ mutable struct PromptState <: ModeState
7373
# indentation of lines which do not include the prompt
7474
# if negative, the width of the prompt is used
7575
indent::Int
76+
refresh_lock::Threads.AbstractLock
77+
# this would better be Threads.Atomic{Float64}, but not supported on some platforms
78+
beeping::Float64
7679
end
7780

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

122+
123+
# these may be better stored in Prompt or LineEditREPL
124+
const BEEP_DURATION = Ref(0.2)
125+
const BEEP_BLINK = Ref(0.2)
126+
const BEEP_MAXDURATION = Ref(1.0)
127+
const BEEP_COLORS = ["\e[90m"] # gray (text_colors not yet available)
128+
const BEEP_USE_CURRENT = Ref(true)
129+
130+
function beep(s::PromptState, duration::Real=BEEP_DURATION[], blink::Real=BEEP_BLINK[],
131+
maxduration::Real=BEEP_MAXDURATION[];
132+
colors=BEEP_COLORS, use_current::Bool=BEEP_USE_CURRENT[])
133+
isinteractive() || return # some tests fail on some platforms
134+
s.beeping = min(s.beeping + duration, maxduration)
135+
@async begin
136+
trylock(s.refresh_lock) || return
137+
orig_prefix = s.p.prompt_prefix
138+
colors = Base.copymutable(colors)
139+
use_current && push!(colors, orig_prefix)
140+
i = 0
141+
while s.beeping > 0.0
142+
prefix = colors[mod1(i+=1, end)]
143+
s.p.prompt_prefix = prefix
144+
refresh_multi_line(s, beeping=true)
145+
sleep(blink)
146+
s.beeping -= blink
147+
end
148+
s.p.prompt_prefix = orig_prefix
149+
refresh_multi_line(s, beeping=true)
150+
s.beeping = 0.0
151+
unlock(s.refresh_lock)
152+
end
153+
end
154+
155+
function cancel_beep(s::PromptState)
156+
# wait till beeping finishes
157+
while !trylock(s.refresh_lock)
158+
s.beeping = 0.0
159+
sleep(.05)
160+
end
161+
unlock(s.refresh_lock)
162+
end
163+
164+
beep(::ModeState) = nothing
165+
cancel_beep(::ModeState) = nothing
166+
119167
for f in [:terminal, :on_enter, :add_history, :buffer, :(Base.isempty),
120168
:replace_line, :refresh_multi_line, :input_string, :update_display_buffer,
121-
:empty_undo, :push_undo, :pop_undo, :options]
169+
:empty_undo, :push_undo, :pop_undo, :options, :cancel_beep, :beep]
122170
@eval ($f)(s::MIState, args...) = $(f)(state(s), args...)
123171
end
124172

@@ -175,16 +223,19 @@ end
175223

176224
# Prompt Completions
177225
function complete_line(s::MIState)
178-
complete_line(state(s), s.key_repeats)
179-
refresh_line(s)
180-
:complete_line
226+
if complete_line(state(s), s.key_repeats)
227+
refresh_line(s)
228+
:complete_line
229+
else
230+
beep(s)
231+
:ignore
232+
end
181233
end
182234

183235
function complete_line(s::PromptState, repeats)
184236
completions, partial, should_complete = complete_line(s.p.complete, s)
185-
if isempty(completions)
186-
beep(terminal(s))
187-
elseif !should_complete
237+
isempty(completions) && return false
238+
if !should_complete
188239
# should_complete is false for cases where we only want to show
189240
# a list of possible completions but not complete, e.g. foo(\t
190241
show_completions(s, completions)
@@ -205,6 +256,7 @@ function complete_line(s::PromptState, repeats)
205256
show_completions(s, completions)
206257
end
207258
end
259+
true
208260
end
209261

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

233-
refresh_multi_line(s::ModeState) = refresh_multi_line(terminal(s), s)
234-
refresh_multi_line(termbuf::TerminalBuffer, s::ModeState) = refresh_multi_line(termbuf, terminal(s), s)
235-
refresh_multi_line(termbuf::TerminalBuffer, term, s::ModeState) = (@assert term == terminal(s); refresh_multi_line(termbuf,s))
285+
refresh_multi_line(s::ModeState; kw...) = refresh_multi_line(terminal(s), s; kw...)
286+
refresh_multi_line(termbuf::TerminalBuffer, s::ModeState; kw...) = refresh_multi_line(termbuf, terminal(s), s; kw...)
287+
refresh_multi_line(termbuf::TerminalBuffer, term, s::ModeState; kw...) = (@assert term == terminal(s); refresh_multi_line(termbuf,s; kw...))
236288
function refresh_multi_line(termbuf::TerminalBuffer, terminal::UnixTerminal, buf, state::InputAreaState, prompt = ""; indent = 0)
237289
_clear_input_area(termbuf, state)
238290

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

298350
#columns are 1 based
299351
cmove_col(termbuf, curs_pos + 1)
300-
301352
# Updated cur_row,curs_row
302353
return InputAreaState(cur_row, curs_row)
303354
end
@@ -558,7 +609,7 @@ function edit_backspace(s::PromptState, align::Bool=options(s).backspace_align,
558609
refresh_line(s)
559610
else
560611
pop_undo(s)
561-
beep(terminal(s))
612+
beep(s)
562613
end
563614
end
564615

@@ -607,7 +658,7 @@ function edit_delete(s)
607658
refresh_line(s)
608659
else
609660
pop_undo(s)
610-
beep(terminal(s))
661+
beep(s)
611662
end
612663
:edit_delete
613664
end
@@ -676,7 +727,7 @@ end
676727

677728
function edit_yank(s::MIState)
678729
if isempty(s.kill_ring)
679-
beep(terminal(s))
730+
beep(s)
680731
return :ignore
681732
end
682733
setmark(s) # necessary for edit_yank_pop
@@ -689,7 +740,7 @@ end
689740
function edit_yank_pop(s::MIState, require_previous_yank=true)
690741
repeat = s.last_action (:edit_yank, :edit_yank_pop)
691742
if require_previous_yank && !repeat || isempty(s.kill_ring)
692-
beep(terminal(s))
743+
beep(s)
693744
:ignore
694745
else
695746
require_previous_yank || repeat || setmark(s)
@@ -857,7 +908,7 @@ function history_prev(s, hist)
857908
move_input_start(s)
858909
refresh_line(s)
859910
else
860-
beep(terminal(s))
911+
beep(s)
861912
end
862913
end
863914
function history_next(s, hist)
@@ -867,7 +918,7 @@ function history_next(s, hist)
867918
move_input_end(s)
868919
refresh_line(s)
869920
else
870-
beep(terminal(s))
921+
beep(s)
871922
end
872923
end
873924

@@ -1249,12 +1300,12 @@ end
12491300
terminal(s::SearchState) = s.terminal
12501301

12511302
function update_display_buffer(s::SearchState, data)
1252-
history_search(data.histprompt.hp, data.query_buffer, data.response_buffer, data.backward, false) || beep(terminal(s))
1303+
history_search(data.histprompt.hp, data.query_buffer, data.response_buffer, data.backward, false) || beep(s)
12531304
refresh_line(s)
12541305
end
12551306

12561307
function history_next_result(s::MIState, data::SearchState)
1257-
history_search(data.histprompt.hp, data.query_buffer, data.response_buffer, data.backward, true) || beep(terminal(s))
1308+
history_search(data.histprompt.hp, data.query_buffer, data.response_buffer, data.backward, true) || beep(s)
12581309
refresh_line(data)
12591310
end
12601311

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

1310-
refresh_multi_line(termbuf::TerminalBuffer, terminal::UnixTerminal,
1311-
s::Union{PromptState,PrefixSearchState}) = s.ias =
1312-
refresh_multi_line(termbuf, terminal, buffer(s), s.ias, s, indent = s.indent)
1361+
function refresh_multi_line(termbuf::TerminalBuffer, terminal::UnixTerminal,
1362+
s::Union{PromptState,PrefixSearchState}; beeping=false)
1363+
beeping || cancel_beep(s)
1364+
s.ias = refresh_multi_line(termbuf, terminal, buffer(s), s.ias, s, indent = s.indent)
1365+
end
13131366

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

@@ -1456,15 +1509,15 @@ function setup_search_keymap(hp)
14561509

14571510
# Backspace/^H
14581511
'\b' => (s,data,c)->(edit_backspace(data.query_buffer) ?
1459-
update_display_buffer(s, data) : beep(terminal(s))),
1512+
update_display_buffer(s, data) : beep(s)),
14601513
127 => KeyAlias('\b'),
14611514
# Meta Backspace
14621515
"\e\b" => (s,data,c)->(edit_delete_prev_word(data.query_buffer) ?
1463-
update_display_buffer(s, data) : beep(terminal(s))),
1516+
update_display_buffer(s, data) : beep(s)),
14641517
"\e\x7f" => "\e\b",
14651518
# Word erase to whitespace
14661519
"^W" => (s,data,c)->(edit_werase(data.query_buffer) ?
1467-
update_display_buffer(s, data) : beep(terminal(s))),
1520+
update_display_buffer(s, data) : beep(s)),
14681521
# ^C and ^D
14691522
"^C" => (s,data,c)->(edit_clear(data.query_buffer);
14701523
edit_clear(data.response_buffer);
@@ -1565,6 +1618,7 @@ function move_line_end(buf::IOBuffer)
15651618
end
15661619

15671620
function commit_line(s)
1621+
cancel_beep(s)
15681622
move_input_end(s)
15691623
refresh_line(s)
15701624
println(terminal(s))
@@ -1712,6 +1766,7 @@ AnyDict(
17121766
try # raise the debugger if present
17131767
ccall(:jl_raise_debugger, Int, ())
17141768
end
1769+
cancel_beep(s)
17151770
move_input_end(s)
17161771
refresh_line(s)
17171772
print(terminal(s), "^C\n\n")
@@ -1822,6 +1877,7 @@ activate(m::ModalInterface, s::MIState, termbuf, term::TextTerminal) =
18221877
commit_changes(t::UnixTerminal, termbuf) = write(t, take!(termbuf.out_stream))
18231878

18241879
function transition(f::Function, s::MIState, newmode)
1880+
cancel_beep(s)
18251881
if newmode === :abort
18261882
s.aborted = true
18271883
return
@@ -1879,7 +1935,7 @@ run_interface(::Prompt) = nothing
18791935

18801936
init_state(terminal, prompt::Prompt) =
18811937
PromptState(terminal, prompt, IOBuffer(), IOBuffer[], 1, InputAreaState(1, 1),
1882-
#=indent(spaces)=# -1)
1938+
#=indent(spaces)=# -1, Threads.SpinLock(), 0.0)
18831939

18841940
function init_state(terminal, m::ModalInterface)
18851941
s = MIState(m, m.modes[1], false, Dict{Any,Any}())
@@ -1941,7 +1997,7 @@ function edit_undo!(s::MIState)
19411997
if edit_undo!(state(s))
19421998
:edit_undo!
19431999
else
1944-
beep(terminal(s))
2000+
beep(s)
19452001
:ignore
19462002
end
19472003
end
@@ -1958,7 +2014,7 @@ function edit_redo!(s::MIState)
19582014
if s.last_action (:edit_redo!, :edit_undo!) && edit_redo!(state(s))
19592015
:edit_redo!
19602016
else
1961-
beep(terminal(s))
2017+
beep(s)
19622018
:ignore
19632019
end
19642020
end

base/repl/REPL.jl

+4-4
Original file line numberDiff line numberDiff line change
@@ -493,14 +493,14 @@ function history_prev(s::LineEdit.MIState, hist::REPLHistoryProvider,
493493
elseif m === :skip
494494
history_prev(s, hist, num+1, save_idx)
495495
else
496-
Terminals.beep(LineEdit.terminal(s))
496+
Terminals.beep(s)
497497
end
498498
end
499499

500500
function history_next(s::LineEdit.MIState, hist::REPLHistoryProvider,
501501
num::Int=1, save_idx::Int = hist.cur_idx)
502502
if num == 0
503-
Terminals.beep(LineEdit.terminal(s))
503+
Terminals.beep(s)
504504
return
505505
end
506506
num < 0 && return history_prev(s, hist, -num, save_idx)
@@ -518,7 +518,7 @@ function history_next(s::LineEdit.MIState, hist::REPLHistoryProvider,
518518
elseif m === :skip
519519
history_next(s, hist, num+1, save_idx)
520520
else
521-
Terminals.beep(LineEdit.terminal(s))
521+
Terminals.beep(s)
522522
end
523523
end
524524

@@ -562,7 +562,7 @@ function history_move_prefix(s::LineEdit.PrefixSearchState,
562562
end
563563
end
564564
end
565-
Terminals.beep(LineEdit.terminal(s))
565+
Terminals.beep(s)
566566
end
567567
history_next_prefix(s::LineEdit.PrefixSearchState, hist::REPLHistoryProvider, prefix::AbstractString) =
568568
history_move_prefix(s, hist, prefix, false)

base/repl/Terminals.jl

+1-1
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ end
146146

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

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

0 commit comments

Comments
 (0)