diff --git a/stdlib/REPL/src/LineEdit.jl b/stdlib/REPL/src/LineEdit.jl index 1b91afd56e221..718cd02e6eb98 100644 --- a/stdlib/REPL/src/LineEdit.jl +++ b/stdlib/REPL/src/LineEdit.jl @@ -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) = @@ -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 @@ -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...)) @@ -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 @@ -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) @@ -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 @@ -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}()) diff --git a/stdlib/REPL/src/options.jl b/stdlib/REPL/src/options.jl index 5f6e82a59cfbf..09e3f4265fabe 100644 --- a/stdlib/REPL/src/options.jl +++ b/stdlib/REPL/src/options.jl @@ -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 @@ -44,6 +46,7 @@ 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, @@ -51,7 +54,7 @@ Options(; 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