@@ -77,9 +77,12 @@ mutable struct MIState
77
77
last_action:: Symbol
78
78
current_action:: Symbol
79
79
async_channel:: Channel{Function}
80
+ line_modify_lock:: Base.ReentrantLock
81
+ hint_generation_lock:: Base.ReentrantLock
82
+ n_keys_pressed:: Int
80
83
end
81
84
82
- MIState (i, mod, c, a, m) = MIState (i, mod, mod, c, a, m, String[], 0 , Char[], 0 , :none , :none , Channel {Function} ())
85
+ MIState (i, mod, c, a, m) = MIState (i, mod, mod, c, a, m, String[], 0 , Char[], 0 , :none , :none , Channel {Function} (), Base . ReentrantLock (), Base . ReentrantLock (), 0 )
83
86
84
87
const BufferLike = Union{MIState,ModeState,IOBuffer}
85
88
const State = Union{MIState,ModeState}
@@ -400,47 +403,82 @@ function complete_line_named(args...; kwargs...)::Tuple{Vector{NamedCompletion},
400
403
end
401
404
end
402
405
403
- function check_for_hint (s:: MIState )
406
+ # checks for a hint and shows it if appropriate.
407
+ # to allow the user to type even if hint generation is slow, the
408
+ # hint is generated on a worker thread, and only shown if the user hasn't
409
+ # pressed a key since the hint generation was requested
410
+ function check_show_hint (s:: MIState )
404
411
st = state (s)
412
+
413
+ this_key_i = s. n_keys_pressed
414
+ next_key_pressed () = @lock s. line_modify_lock s. n_keys_pressed > this_key_i
415
+ function lock_clear_hint ()
416
+ @lock s. line_modify_lock begin
417
+ next_key_pressed () || s. aborted || clear_hint (st) && refresh_line (s)
418
+ end
419
+ end
420
+
405
421
if ! options (st). hint_tab_completes || ! eof (buffer (st))
406
422
# only generate hints if enabled and at the end of the line
407
423
# TODO : maybe show hints for insertions at other positions
408
424
# Requires making space for them earlier in refresh_multi_line
409
- return clear_hint (st)
425
+ lock_clear_hint ()
426
+ return
410
427
end
411
-
412
- named_completions, partial, should_complete = try
413
- complete_line_named (st. p. complete, st, s. active_module; hint = true )
414
- catch
415
- @debug " error completing line for hint" exception= current_exceptions ()
416
- return clear_hint (st)
417
- end
418
- completions = map (x -> x. completion, named_completions)
419
-
420
- isempty (completions) && return clear_hint (st)
421
- # Don't complete for single chars, given e.g. `x` completes to `xor`
422
- if length (partial) > 1 && should_complete
423
- singlecompletion = length (completions) == 1
424
- p = singlecompletion ? completions[1 ] : common_prefix (completions)
425
- if singlecompletion || p in completions # i.e. complete `@time` even though `@time_imports` etc. exists
426
- # The completion `p` and the input `partial` may not share the same initial
427
- # characters, for instance when completing to subscripts or superscripts.
428
- # So, in general, make sure that the hint starts at the correct position by
429
- # incrementing its starting position by as many characters as the input.
430
- startind = 1 # index of p from which to start providing the hint
431
- maxind = ncodeunits (p)
432
- for _ in partial
433
- startind = nextind (p, startind)
434
- startind > maxind && break
428
+ t_completion = Threads. @spawn :default begin
429
+ named_completions, partial, should_complete = nothing , nothing , nothing
430
+
431
+ # only allow one task to generate hints at a time and check around lock
432
+ # if the user has pressed a key since the hint was requested, to skip old completions
433
+ next_key_pressed () && return
434
+ @lock s. hint_generation_lock begin
435
+ next_key_pressed () && return
436
+ named_completions, partial, should_complete = try
437
+ complete_line_named (st. p. complete, st, s. active_module; hint = true )
438
+ catch
439
+ lock_clear_hint ()
440
+ return
435
441
end
436
- if startind ≤ maxind # completion on a complete name returns itself so check that there's something to hint
437
- hint = p[startind: end ]
438
- st. hint = hint
439
- return true
442
+ end
443
+ next_key_pressed () && return
444
+
445
+ completions = map (x -> x. completion, named_completions)
446
+ if isempty (completions)
447
+ lock_clear_hint ()
448
+ return
449
+ end
450
+ # Don't complete for single chars, given e.g. `x` completes to `xor`
451
+ if length (partial) > 1 && should_complete
452
+ singlecompletion = length (completions) == 1
453
+ p = singlecompletion ? completions[1 ] : common_prefix (completions)
454
+ if singlecompletion || p in completions # i.e. complete `@time` even though `@time_imports` etc. exists
455
+ # The completion `p` and the input `partial` may not share the same initial
456
+ # characters, for instance when completing to subscripts or superscripts.
457
+ # So, in general, make sure that the hint starts at the correct position by
458
+ # incrementing its starting position by as many characters as the input.
459
+ startind = 1 # index of p from which to start providing the hint
460
+ maxind = ncodeunits (p)
461
+ for _ in partial
462
+ startind = nextind (p, startind)
463
+ startind > maxind && break
464
+ end
465
+ if startind ≤ maxind # completion on a complete name returns itself so check that there's something to hint
466
+ hint = p[startind: end ]
467
+ next_key_pressed () && return
468
+ @lock s. line_modify_lock begin
469
+ if ! s. aborted
470
+ state (s). hint = hint
471
+ refresh_line (s)
472
+ end
473
+ end
474
+ return
475
+ end
440
476
end
441
477
end
478
+ lock_clear_hint ()
442
479
end
443
- return clear_hint (st)
480
+ Base. errormonitor (t_completion)
481
+ return
444
482
end
445
483
446
484
function clear_hint (s:: ModeState )
@@ -2569,7 +2607,7 @@ AnyDict(
2569
2607
" ^_" => (s:: MIState ,o... )-> edit_undo! (s),
2570
2608
" \e _" => (s:: MIState ,o... )-> edit_redo! (s),
2571
2609
# Show hints at what tab complete would do by default
2572
- " *" => (s:: MIState ,data,c:: StringLike )-> (edit_insert (s, c); check_for_hint (s) && refresh_line (s)),
2610
+ " *" => (s:: MIState ,data,c:: StringLike )-> (edit_insert (s, c); check_show_hint (s)),
2573
2611
" ^U" => (s:: MIState ,o... )-> edit_kill_line_backwards (s),
2574
2612
" ^K" => (s:: MIState ,o... )-> edit_kill_line_forwards (s),
2575
2613
" ^Y" => (s:: MIState ,o... )-> edit_yank (s),
@@ -2875,10 +2913,9 @@ keymap_data(ms::MIState, m::ModalInterface) = keymap_data(state(ms), mode(ms))
2875
2913
2876
2914
function prompt! (term:: TextTerminal , prompt:: ModalInterface , s:: MIState = init_state (term, prompt))
2877
2915
Base. reseteof (term)
2878
- l = Base. ReentrantLock ()
2879
2916
t1 = Threads. @spawn :interactive while true
2880
2917
wait (s. async_channel)
2881
- status = @lock l begin
2918
+ status = @lock s . line_modify_lock begin
2882
2919
fcn = take! (s. async_channel)
2883
2920
fcn (s)
2884
2921
end
@@ -2893,7 +2930,8 @@ function prompt!(term::TextTerminal, prompt::ModalInterface, s::MIState = init_s
2893
2930
# and we want to not block typing when the repl task thread is busy
2894
2931
t2 = Threads. @spawn :interactive while true
2895
2932
eof (term) || peek (term) # wait before locking but don't consume
2896
- @lock l begin
2933
+ @lock s. line_modify_lock begin
2934
+ s. n_keys_pressed += 1
2897
2935
kmap = keymap (s, prompt)
2898
2936
fcn = match_input (kmap, s)
2899
2937
kdata = keymap_data (s, prompt)
0 commit comments