@@ -77,9 +77,12 @@ mutable struct MIState
7777 last_action:: Symbol
7878 current_action:: Symbol
7979 async_channel:: Channel{Function}
80+ line_modify_lock:: Base.ReentrantLock
81+ hint_generation_lock:: Base.ReentrantLock
82+ n_keys_pressed:: Int
8083end
8184
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 )
8386
8487const BufferLike = Union{MIState,ModeState,IOBuffer}
8588const State = Union{MIState,ModeState}
@@ -400,47 +403,82 @@ function complete_line_named(args...; kwargs...)::Tuple{Vector{NamedCompletion},
400403 end
401404end
402405
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 )
404411 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+
405421 if ! options (st). hint_tab_completes || ! eof (buffer (st))
406422 # only generate hints if enabled and at the end of the line
407423 # TODO : maybe show hints for insertions at other positions
408424 # Requires making space for them earlier in refresh_multi_line
409- return clear_hint (st)
425+ lock_clear_hint ()
426+ return
410427 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
435441 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
440476 end
441477 end
478+ lock_clear_hint ()
442479 end
443- return clear_hint (st)
480+ Base. errormonitor (t_completion)
481+ return
444482end
445483
446484function clear_hint (s:: ModeState )
@@ -2569,7 +2607,7 @@ AnyDict(
25692607 " ^_" => (s:: MIState ,o... )-> edit_undo! (s),
25702608 " \e _" => (s:: MIState ,o... )-> edit_redo! (s),
25712609 # 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)),
25732611 " ^U" => (s:: MIState ,o... )-> edit_kill_line_backwards (s),
25742612 " ^K" => (s:: MIState ,o... )-> edit_kill_line_forwards (s),
25752613 " ^Y" => (s:: MIState ,o... )-> edit_yank (s),
@@ -2875,10 +2913,9 @@ keymap_data(ms::MIState, m::ModalInterface) = keymap_data(state(ms), mode(ms))
28752913
28762914function prompt! (term:: TextTerminal , prompt:: ModalInterface , s:: MIState = init_state (term, prompt))
28772915 Base. reseteof (term)
2878- l = Base. ReentrantLock ()
28792916 t1 = Threads. @spawn :interactive while true
28802917 wait (s. async_channel)
2881- status = @lock l begin
2918+ status = @lock s . line_modify_lock begin
28822919 fcn = take! (s. async_channel)
28832920 fcn (s)
28842921 end
@@ -2893,7 +2930,8 @@ function prompt!(term::TextTerminal, prompt::ModalInterface, s::MIState = init_s
28932930 # and we want to not block typing when the repl task thread is busy
28942931 t2 = Threads. @spawn :interactive while true
28952932 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
28972935 kmap = keymap (s, prompt)
28982936 fcn = match_input (kmap, s)
28992937 kdata = keymap_data (s, prompt)
0 commit comments