From 72dda46fb949450ad9d2c9cb4c2b71e7194c3bf0 Mon Sep 17 00:00:00 2001 From: Ian Butterworth Date: Thu, 7 Sep 2023 13:29:19 +0100 Subject: [PATCH] add inline repl tab complete hints --- stdlib/REPL/src/LineEdit.jl | 47 +++++++++++++++++++++++++++++++++---- 1 file changed, 42 insertions(+), 5 deletions(-) diff --git a/stdlib/REPL/src/LineEdit.jl b/stdlib/REPL/src/LineEdit.jl index ff67e849fcc5a..55cfa87e4b3dc 100644 --- a/stdlib/REPL/src/LineEdit.jl +++ b/stdlib/REPL/src/LineEdit.jl @@ -97,6 +97,7 @@ mutable struct PromptState <: ModeState p::Prompt input_buffer::IOBuffer region_active::Symbol # :shift or :mark or :off + hint::Union{String,Nothing} undo_buffers::Vector{IOBuffer} undo_idx::Int ias::InputAreaState @@ -361,7 +362,7 @@ function show_completions(s::PromptState, completions::Vector{String}) end end -# Prompt Completions +# Prompt Completions & Hints function complete_line(s::MIState) set_action!(s, :complete_line) if complete_line(state(s), s.key_repeats, s.active_module) @@ -372,6 +373,27 @@ function complete_line(s::MIState) end end +function check_for_hint(s::MIState) + st = state(s) + completions, partial, _ = complete_line(st.p.complete, st, s.active_module)::Tuple{Vector{String},String,Bool} + if length(completions) == 1 + st.hint = completions[1][sizeof(partial)+1:end] + return refresh_line(s) + elseif length(completions) > 1 + p = common_prefix(completions) + if p in completions # i.e. complete `@time` even though `@time_imports` etc. exists + st.hint = p[sizeof(partial)+1:end] + return refresh_line(s) + end + end + if !isnothing(st.hint) + st.hint = "" # don't set to nothing here. That will be done in `maybe_show_hint` + return refresh_line(s) + else + return nothing + end +end + function complete_line(s::PromptState, repeats::Int, mod::Module) completions, partial, should_complete = complete_line(s.p.complete, s, mod)::Tuple{Vector{String},String,Bool} isempty(completions) && return false @@ -432,12 +454,27 @@ prompt_string(p::Prompt) = prompt_string(p.prompt) prompt_string(s::AbstractString) = s prompt_string(f::Function) = Base.invokelatest(f) +function maybe_show_hint(s::PromptState) + isa(s.hint, String) || return nothing + if isempty(s.hint) + print(terminal(s), "\e[0K") + s.hint = nothing + else + Base.printstyled(terminal(s), s.hint, color=:light_black) + cmove_left(terminal(s), textwidth(s.hint)) + s.hint = "" # clear line remainder of line but only once after a hint to allow column movement + end + return nothing +end + 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...) + r = refresh_multi_line(terminal(s), s; kw...) + maybe_show_hint(s) + return r 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...) @@ -2424,8 +2461,8 @@ AnyDict( "\e\n" => "\e\r", "^_" => (s::MIState,o...)->edit_undo!(s), "\e_" => (s::MIState,o...)->edit_redo!(s), - # Simply insert it into the buffer by default - "*" => (s::MIState,data,c::StringLike)->(edit_insert(s, c)), + # Show hints at what tab complete would do by default + "*" => (s::MIState,data,c::StringLike)->(edit_insert(s, c); check_for_hint(s)), "^U" => (s::MIState,o...)->edit_kill_line_backwards(s), "^K" => (s::MIState,o...)->edit_kill_line_forwards(s), "^Y" => (s::MIState,o...)->edit_yank(s), @@ -2634,7 +2671,7 @@ end run_interface(::Prompt) = nothing init_state(terminal, prompt::Prompt) = - PromptState(terminal, prompt, IOBuffer(), :off, IOBuffer[], 1, InputAreaState(1, 1), + PromptState(terminal, prompt, IOBuffer(), :off, nothing, IOBuffer[], 1, InputAreaState(1, 1), #=indent(spaces)=# -1, Threads.SpinLock(), 0.0, -Inf, nothing) function init_state(terminal, m::ModalInterface)