Skip to content

Commit f87bc8d

Browse files
oheilKristofferC
authored andcommitted
Rf/autoindentpaste improvements (#30755)
* REPL: disable auto-indent when code is likely being pasted (fix #25186) On some Windows terminals, pasting is not recognized as such. This happens also with tmux' builtin paste. In those cases, auto-indent is better disabled, as we want to preserve the original indentation of the code being pasted. This fix is quite a hack, but seems to work: using time(), if the next character after a newline is being inserted very fast, assume this was with paste, and cancel the insertion of the spaces done by auto-indent. I couldn't insert two characters in a raw within less than about 0.03 or 0.02 seconds, so the threshold for "very fast" is set to 0.0005, but this is an option which can changed. * fix tests * more reliability * don't include refresh_line in the timings * use a separate autoindent variable, as we want to undo indent in case of paste only for auto-indented code (although currently in the REPL code this function is never called with a positive argument) * Some improvements: autoident is now prevented and needs not to be redone. Paste of mixed space/tabs/empty lines is now more reliable. * fixed bug where indent is set externally
1 parent 95a52a5 commit f87bc8d

File tree

3 files changed

+48
-5
lines changed

3 files changed

+48
-5
lines changed

stdlib/REPL/src/LineEdit.jl

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,8 @@ mutable struct PromptState <: ModeState
8080
refresh_lock::Threads.AbstractLock
8181
# this would better be Threads.Atomic{Float64}, but not supported on some platforms
8282
beeping::Float64
83+
# this option is to detect when code is pasted in non-"bracketed paste mode" :
84+
last_newline::Float64 # register when last newline was entered
8385
end
8486

8587
options(s::PromptState) =
@@ -685,6 +687,27 @@ edit_splice!(s, ins::AbstractString) = edit_splice!(s, region(s), ins)
685687
function edit_insert(s::PromptState, c)
686688
push_undo(s)
687689
buf = s.input_buffer
690+
691+
if ! options(s).auto_indent_bracketed_paste
692+
pos=position(buf)
693+
if pos > 0
694+
if buf.data[pos] != _space && string(c) != " "
695+
options(s).auto_indent_tmp_off = false
696+
end
697+
if buf.data[pos] == _space
698+
#tabulators are already expanded to space
699+
#this expansion may take longer than auto_indent_time_threshold which breaks the timing
700+
s.last_newline = time()
701+
else
702+
#if characters after new line are coming in very fast
703+
#its probably copy&paste => switch auto-indent off for the next coming new line
704+
if ! options(s).auto_indent_tmp_off && time() - s.last_newline < options(s).auto_indent_time_threshold
705+
options(s).auto_indent_tmp_off = true
706+
end
707+
end
708+
end
709+
end
710+
688711
str = string(c)
689712
edit_insert(buf, str)
690713
offset = s.ias.curs_row == 1 || s.indent < 0 ?
@@ -715,14 +738,23 @@ end
715738
function edit_insert_newline(s::PromptState, align::Int = 0 - options(s).auto_indent)
716739
push_undo(s)
717740
buf = buffer(s)
718-
if align < 0
741+
autoindent = align < 0
742+
if autoindent && ! options(s).auto_indent_tmp_off
719743
beg = beginofline(buf)
720744
align = min(something(findnext(_notspace, buf.data[beg+1:buf.size], 1), 0) - 1,
721745
position(buf) - beg) # indentation must not increase
722746
align < 0 && (align = buf.size-beg)
747+
#else
748+
# align = 0
723749
end
750+
align < 0 && (align = 0)
724751
edit_insert(buf, '\n' * ' '^align)
725752
refresh_line(s)
753+
# updating s.last_newline should happen after refresh_line(s) which can take
754+
# an unpredictable amount of time and makes "paste detection" unreliable
755+
if ! options(s).auto_indent_bracketed_paste
756+
s.last_newline = time()
757+
end
726758
end
727759

728760
# align: delete up to 4 spaces to align to a multiple of 4 chars
@@ -1922,6 +1954,7 @@ function commit_line(s)
19221954
end
19231955

19241956
function bracketed_paste(s; tabwidth=options(s).tabwidth)
1957+
options(s).auto_indent_bracketed_paste = true
19251958
ps = state(s, mode(s))
19261959
input = readuntil(ps.terminal, "\e[201~")
19271960
input = replace(input, '\r' => '\n')
@@ -2252,7 +2285,7 @@ run_interface(::Prompt) = nothing
22522285

22532286
init_state(terminal, prompt::Prompt) =
22542287
PromptState(terminal, prompt, IOBuffer(), :off, IOBuffer[], 1, InputAreaState(1, 1),
2255-
#=indent(spaces)=# -1, Threads.SpinLock(), 0.0)
2288+
#=indent(spaces)=# -1, Threads.SpinLock(), 0.0, -Inf)
22562289

22572290
function init_state(terminal, m::ModalInterface)
22582291
s = MIState(m, m.modes[1], false, IdDict{Any,Any}())

stdlib/REPL/src/REPL.jl

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,10 @@ mutable struct Options
270270
backspace_adjust::Bool
271271
confirm_exit::Bool # ^D must be repeated to confirm exit
272272
auto_indent::Bool # indent a newline like line above
273+
auto_indent_tmp_off::Bool # switch auto_indent temporarily off if copy&paste
274+
auto_indent_bracketed_paste::Bool # set to true if terminal knows paste mode
275+
# cancel auto-indent when next character is entered within this time frame :
276+
auto_indent_time_threshold::Float64
273277
end
274278

275279
Options(;
@@ -283,12 +287,16 @@ Options(;
283287
beep_use_current = true,
284288
backspace_align = true, backspace_adjust = backspace_align,
285289
confirm_exit = false,
286-
auto_indent = true) =
290+
auto_indent = true,
291+
auto_indent_tmp_off = false,
292+
auto_indent_bracketed_paste = false,
293+
auto_indent_time_threshold = 0.005) =
287294
Options(hascolor, extra_keymap, tabwidth,
288295
kill_ring_max, region_animation_duration,
289296
beep_duration, beep_blink, beep_maxduration,
290297
beep_colors, beep_use_current,
291-
backspace_align, backspace_adjust, confirm_exit, auto_indent)
298+
backspace_align, backspace_adjust, confirm_exit,
299+
auto_indent, auto_indent_tmp_off, auto_indent_bracketed_paste, auto_indent_time_threshold)
292300

293301
# for use by REPLs not having an options field
294302
const GlobalOptions = Options()

stdlib/REPL/test/lineedit.jl

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@ include("FakeTerminals.jl")
99
import .FakeTerminals.FakeTerminal
1010

1111
# no need to have animation in tests
12-
REPL.GlobalOptions.region_animation_duration=0.001
12+
REPL.GlobalOptions.region_animation_duration=0.0001
13+
# tests are inserting code much faster than humans
14+
REPL.GlobalOptions.auto_indent_time_threshold = -0.0
1315

1416
## helper functions
1517

0 commit comments

Comments
 (0)