Skip to content

Commit

Permalink
REPL: try to coalesce updates (#39538)
Browse files Browse the repository at this point in the history
When input is arriving too fast, delay computing and rendering the full
screen until a short delay after typing stops.
  • Loading branch information
vtjnash authored Mar 8, 2021
1 parent a01d7f3 commit 26d887e
Show file tree
Hide file tree
Showing 2 changed files with 60 additions and 12 deletions.
67 changes: 56 additions & 11 deletions stdlib/REPL/src/LineEdit.jl
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,8 @@ mutable struct PromptState <: ModeState
beeping::Float64
# this option is to detect when code is pasted in non-"bracketed paste mode" :
last_newline::Float64 # register when last newline was entered
# this option is to speed up output
refresh_wait::Union{Timer,Nothing}
end

options(s::PromptState) =
Expand Down Expand Up @@ -371,8 +373,17 @@ function complete_line(s::PromptState, repeats::Int)
return true
end

function clear_input_area(terminal::AbstractTerminal, s::PromptState)
if s.refresh_wait !== nothing
close(s.refresh_wait)
s.refresh_wait = nothing
end
_clear_input_area(terminal, s.ias)
s.ias = InputAreaState(0, 0)
end
clear_input_area(terminal::AbstractTerminal, s::ModeState) = (_clear_input_area(terminal, s.ias); s.ias = InputAreaState(0, 0))
clear_input_area(s::ModeState) = clear_input_area(s.terminal, s)

function _clear_input_area(terminal::AbstractTerminal, state::InputAreaState)
# Go to the last line
if state.curs_row < state.num_rows
Expand All @@ -395,6 +406,13 @@ prompt_string(p::Prompt) = prompt_string(p.prompt)
prompt_string(s::AbstractString) = s
prompt_string(f::Function) = Base.invokelatest(f)

function refresh_multi_line(s::PromptState; kw...)
if s.refresh_wait !== nothing
close(s.refresh_wait)
s.refresh_wait = nothing
end
refresh_multi_line(terminal(s), s; kw...)
end
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...))
Expand Down Expand Up @@ -738,7 +756,7 @@ function edit_insert(s::PromptState, c::StringLike)
buf = s.input_buffer

if ! options(s).auto_indent_bracketed_paste
pos=position(buf)
pos = position(buf)
if pos > 0
if buf.data[pos] != _space && string(c) != " "
options(s).auto_indent_tmp_off = false
Expand All @@ -757,20 +775,46 @@ function edit_insert(s::PromptState, c::StringLike)
end
end

old_wait = s.refresh_wait !== nothing
if old_wait
close(s.refresh_wait)
s.refresh_wait = nothing
end
str = string(c)
edit_insert(buf, str)
offset = s.ias.curs_row == 1 || s.indent < 0 ?
sizeof(prompt_string(s.p.prompt)::String) : s.indent
if !('\n' in str) && eof(buf) &&
((position(buf) - beginofline(buf) + # size of current line
offset + sizeof(str) - 1) < width(terminal(s)))
# Avoid full update when appending characters to the end
# and an update of curs_row isn't necessary (conservatively estimated)
write(terminal(s), str)
else
if '\n' in str
refresh_line(s)
else
after = options(s).auto_refresh_time_delay
termbuf = terminal(s)
w = width(termbuf)
delayup = !eof(buf) || old_wait
offset = s.ias.curs_row == 1 || s.indent < 0 ?
sizeof(prompt_string(s.p.prompt)::String) : s.indent
offset += position(buf) - beginofline(buf) # size of current line
if offset + textwidth(str) <= w
# Avoid full update when appending characters to the end
# and an update of curs_row isn't necessary (conservatively estimated)
write(termbuf, str)
elseif after == 0
refresh_line(s)
delayup = false
else
delayup = true
end
if delayup
write(termbuf, spin_seq[mod1(position(buf) - w, length(spin_seq))])
cmove_left(termbuf)
s.refresh_wait = Timer(after) do t
s.refresh_wait === t || return
s.refresh_wait = nothing
refresh_line(s)
end
end
end
nothing
end
const spin_seq = ("", "", "", "")

function edit_insert(buf::IOBuffer, c::StringLike)
if eof(buf)
Expand Down Expand Up @@ -804,6 +848,7 @@ function edit_insert_newline(s::PromptState, align::Int = 0 - options(s).auto_in
if ! options(s).auto_indent_bracketed_paste
s.last_newline = time()
end
nothing
end

# align: delete up to 4 spaces to align to a multiple of 4 chars
Expand Down Expand Up @@ -2420,7 +2465,7 @@ run_interface(::Prompt) = nothing

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

function init_state(terminal, m::ModalInterface)
s = MIState(m, m.modes[1], false, IdDict{Any,Any}())
Expand Down
5 changes: 4 additions & 1 deletion stdlib/REPL/src/options.jl
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ mutable struct Options
auto_indent_bracketed_paste::Bool # set to true if terminal knows paste mode
# cancel auto-indent when next character is entered within this time frame :
auto_indent_time_threshold::Float64
# refresh after time delay
auto_refresh_time_delay::Float64
# default IOContext settings at the REPL
iocontext::Dict{Symbol,Any}
end
Expand All @@ -44,14 +46,15 @@ Options(;
auto_indent_tmp_off = false,
auto_indent_bracketed_paste = false,
auto_indent_time_threshold = 0.005,
auto_refresh_time_delay = 0.05,
iocontext = Dict{Symbol,Any}()) =
Options(hascolor, extra_keymap, tabwidth,
kill_ring_max, region_animation_duration,
beep_duration, beep_blink, beep_maxduration,
beep_colors, beep_use_current,
backspace_align, backspace_adjust, confirm_exit,
auto_indent, auto_indent_tmp_off, auto_indent_bracketed_paste,
auto_indent_time_threshold,
auto_indent_time_threshold, auto_refresh_time_delay,
iocontext)

# for use by REPLs not having an options field
Expand Down

0 comments on commit 26d887e

Please sign in to comment.