diff --git a/NEWS.md b/NEWS.md index 445f3b84ad178..f1f88a84d4cef 100644 --- a/NEWS.md +++ b/NEWS.md @@ -57,6 +57,12 @@ Standard library changes #### REPL +* ` ?(x, y` followed by TAB displays all methods that can be called + with arguments `x, y, ...`. (The space at the beginning prevents entering help-mode.) + `MyModule.?(x, y` limits the search to `MyModule`. TAB requires that at least one + argument have a type more specific than `Any`; use SHIFT-TAB instead of TAB + to allow any compatible methods. + #### SparseArrays #### Dates diff --git a/stdlib/REPL/docs/src/index.md b/stdlib/REPL/docs/src/index.md index 168d3e963b589..ab4c4df19df5b 100644 --- a/stdlib/REPL/docs/src/index.md +++ b/stdlib/REPL/docs/src/index.md @@ -307,6 +307,27 @@ Users should refer to `LineEdit.jl` to discover the available actions on key inp In both the Julian and help modes of the REPL, one can enter the first few characters of a function or type and then press the tab key to get a list all matches: +```julia-repl +julia> x[TAB] +julia> xor +``` + +In some cases it only completes part of the name, up to the next ambiguity: + +```julia-repl +julia> mapf[TAB] +julia> mapfold +``` + +If you hit tab again, then you get the list of things that might complete this: + +```julia-repl +julia> mapfold[TAB] +mapfoldl mapfoldr +``` + +Like other components of the REPL, the search is case-sensitive: + ```julia-repl julia> stri[TAB] stride strides string strip @@ -365,6 +386,46 @@ shell> /[TAB] .dockerinit bin/ dev/ home/ lib64/ mnt/ proc/ run/ srv/ tmp/ var/ ``` +Dictionary keys can also be tab completed: + +```julia-repl +julia> foo = Dict("qwer1"=>1, "qwer2"=>2, "asdf"=>3) +Dict{String,Int64} with 3 entries: + "qwer2" => 2 + "asdf" => 3 + "qwer1" => 1 + +julia> foo["q[TAB] + +"qwer1" "qwer2" +julia> foo["qwer +``` + +Tab completion can also help completing fields: + +```julia-repl +julia> x = 3 + 4im; + +julia> julia> x.[TAB][TAB] +im re + +julia> import UUIDs + +julia> UUIDs.uuid[TAB][TAB] +uuid1 uuid4 uuid5 uuid_version +``` + +Fields for output from functions can also be completed: + +```julia-repl +julia> split("","")[1].[TAB] +lastindex offset string +``` + +The completion of fields for output from functions uses type inference, and it can only suggest +fields if the function is type stable. + + Tab completion can help with investigation of the available methods matching the input arguments: ```julia-repl @@ -392,38 +453,54 @@ The completion of the methods uses type inference and can therefore see if the a even if the arguments are output from functions. The function needs to be type stable for the completion to be able to remove non-matching methods. -Tab completion can also help completing fields: +If you wonder which methods can be used with particular argument types, use `?` as the function name. +This shows an example of looking for functions in InteractiveUtils that accept a single string: ```julia-repl -julia> import UUIDs - -julia> UUIDs.uuid[TAB] -uuid1 uuid4 uuid_version +julia> InteractiveUtils.?("somefile")[TAB] +edit(path::AbstractString) in InteractiveUtils at InteractiveUtils/src/editless.jl:197 +less(file::AbstractString) in InteractiveUtils at InteractiveUtils/src/editless.jl:266 ``` -Fields for output from functions can also be completed: +This listed methods in the `InteractiveUtils` module that can be called on a string. +By default, this excludes methods where all arguments are typed as `Any`, +but you can see those too by holding down SHIFT-TAB instead of TAB: ```julia-repl -julia> split("","")[1].[TAB] -lastindex offset string +julia> InteractiveUtils.?("somefile")[SHIFT-TAB] +apropos(string) in REPL at REPL/src/docview.jl:796 +clipboard(x) in InteractiveUtils at InteractiveUtils/src/clipboard.jl:64 +code_llvm(f) in InteractiveUtils at InteractiveUtils/src/codeview.jl:221 +code_native(f) in InteractiveUtils at InteractiveUtils/src/codeview.jl:243 +edit(path::AbstractString) in InteractiveUtils at InteractiveUtils/src/editless.jl:197 +edit(f) in InteractiveUtils at InteractiveUtils/src/editless.jl:225 +eval(x) in InteractiveUtils at InteractiveUtils/src/InteractiveUtils.jl:3 +include(x) in InteractiveUtils at InteractiveUtils/src/InteractiveUtils.jl:3 +less(file::AbstractString) in InteractiveUtils at InteractiveUtils/src/editless.jl:266 +less(f) in InteractiveUtils at InteractiveUtils/src/editless.jl:274 +report_bug(kind) in InteractiveUtils at InteractiveUtils/src/InteractiveUtils.jl:391 +separate_kwargs(args...; kwargs...) in InteractiveUtils at InteractiveUtils/src/macros.jl:7 ``` -The completion of fields for output from functions uses type inference, and it can only suggest -fields if the function is type stable. +You can also use ` ?("somefile")[TAB]` and look across all modules, but the method lists can be long. -Dictionary keys can also be tab completed: +By omitting the closing parenthesis, you can include functions that might require additional arguments: ```julia-repl -julia> foo = Dict("qwer1"=>1, "qwer2"=>2, "asdf"=>3) -Dict{String,Int64} with 3 entries: - "qwer2" => 2 - "asdf" => 3 - "qwer1" => 1 - -julia> foo["q[TAB] - -"qwer1" "qwer2" -julia> foo["qwer +julia> using Mmap + +help?> Mmap.?("file",[TAB] +Mmap.Anonymous(name::String, readonly::Bool, create::Bool) in Mmap at Mmap/src/Mmap.jl:16 +mmap(file::AbstractString) in Mmap at Mmap/src/Mmap.jl:245 +mmap(file::AbstractString, ::Type{T}) where T<:Array in Mmap at Mmap/src/Mmap.jl:245 +mmap(file::AbstractString, ::Type{T}, dims::Tuple{Vararg{Integer, N}}) where {T<:Array, N} in Mmap at Mmap/src/Mmap.jl:245 +mmap(file::AbstractString, ::Type{T}, dims::Tuple{Vararg{Integer, N}}, offset::Integer; grow, shared) where {T<:Array, N} in Mmap at Mmap/src/Mmap.jl:245 +mmap(file::AbstractString, ::Type{T}, len::Integer) where T<:Array in Mmap at Mmap/src/Mmap.jl:251 +mmap(file::AbstractString, ::Type{T}, len::Integer, offset::Integer; grow, shared) where T<:Array in Mmap at Mmap/src/Mmap.jl:251 +mmap(file::AbstractString, ::Type{T}, dims::Tuple{Vararg{Integer, N}}) where {T<:BitArray, N} in Mmap at Mmap/src/Mmap.jl:316 +mmap(file::AbstractString, ::Type{T}, dims::Tuple{Vararg{Integer, N}}, offset::Integer; grow, shared) where {T<:BitArray, N} in Mmap at Mmap/src/Mmap.jl:316 +mmap(file::AbstractString, ::Type{T}, len::Integer) where T<:BitArray in Mmap at Mmap/src/Mmap.jl:322 +mmap(file::AbstractString, ::Type{T}, len::Integer, offset::Integer; grow, shared) where T<:BitArray in Mmap at Mmap/src/Mmap.jl:322 ``` ## Customizing Colors diff --git a/stdlib/REPL/src/LineEdit.jl b/stdlib/REPL/src/LineEdit.jl index 9a6160c960fe3..89f9a4cb99208 100644 --- a/stdlib/REPL/src/LineEdit.jl +++ b/stdlib/REPL/src/LineEdit.jl @@ -106,6 +106,11 @@ mutable struct PromptState <: ModeState refresh_wait::Union{Timer,Nothing} end +struct Modifiers + shift::Bool +end +Modifiers() = Modifiers(false) + options(s::PromptState) = if isdefined(s.p, :repl) && isdefined(s.p.repl, :options) # we can't test isa(s.p.repl, LineEditREPL) as LineEditREPL is defined @@ -1907,6 +1912,10 @@ mode(s::PromptState) = s.p # ::Prompt mode(s::SearchState) = @assert false mode(s::PrefixSearchState) = s.histprompt.parent_prompt # ::Prompt +setmodifiers!(s::MIState, m::Modifiers) = setmodifiers!(mode(s), m) +setmodifiers!(p::Prompt, m::Modifiers) = setmodifiers!(p.complete, m) +setmodifiers!(c) = nothing + # Search Mode completions function complete_line(s::SearchState, repeats) completions, partial, should_complete = complete_line(s.histprompt.complete, s) @@ -2174,6 +2183,11 @@ function edit_tab(s::MIState, jump_spaces::Bool=false, delete_trailing::Bool=jum return refresh_line(s) end +function shift_tab_completion(s::MIState) + setmodifiers!(s, Modifiers(true)) + return complete_line(s) +end + # return true iff the content of the buffer is modified # return false when only the position changed function edit_insert_tab(buf::IOBuffer, jump_spaces::Bool=false, delete_trailing::Bool=jump_spaces) @@ -2209,6 +2223,8 @@ const default_keymap = AnyDict( # Tab '\t' => (s::MIState,o...)->edit_tab(s, true), + # Shift-tab + "\e[Z" => (s::MIState,o...)->shift_tab_completion(s), # Enter '\r' => (s::MIState,o...)->begin if on_enter(s) || (eof(buffer(s)) && s.key_repeats > 1) diff --git a/stdlib/REPL/src/REPL.jl b/stdlib/REPL/src/REPL.jl index a661ffa218e97..a815678b7ba52 100644 --- a/stdlib/REPL/src/REPL.jl +++ b/stdlib/REPL/src/REPL.jl @@ -55,6 +55,7 @@ import ..LineEdit: history_last, history_search, accept_result, + setmodifiers!, terminal, MIState, PromptState, @@ -470,16 +471,30 @@ LineEditREPL(t::TextTerminal, hascolor::Bool, envcolors::Bool=false) = false, false, false, envcolors ) -mutable struct REPLCompletionProvider <: CompletionProvider end +mutable struct REPLCompletionProvider <: CompletionProvider + modifiers::LineEdit.Modifiers +end +REPLCompletionProvider() = REPLCompletionProvider(LineEdit.Modifiers()) mutable struct ShellCompletionProvider <: CompletionProvider end struct LatexCompletions <: CompletionProvider end +setmodifiers!(c::REPLCompletionProvider, m::LineEdit.Modifiers) = c.modifiers = m + beforecursor(buf::IOBuffer) = String(buf.data[1:buf.ptr-1]) function complete_line(c::REPLCompletionProvider, s::PromptState) partial = beforecursor(s.input_buffer) full = LineEdit.input_string(s) ret, range, should_complete = completions(full, lastindex(partial)) + if !c.modifiers.shift + # Filter out methods where all arguments are `Any` + filter!(ret) do c + isa(c, REPLCompletions.MethodCompletion) || return true + sig = Base.unwrap_unionall(c.method.sig)::DataType + return !all(T -> T === Any || T === Vararg{Any}, sig.parameters[2:end]) + end + end + c.modifiers = LineEdit.Modifiers() return unique!(map(completion_text, ret)), partial[range], should_complete end diff --git a/stdlib/REPL/src/REPLCompletions.jl b/stdlib/REPL/src/REPLCompletions.jl index 44b3e6a3a4158..152f67bdb34e5 100644 --- a/stdlib/REPL/src/REPLCompletions.jl +++ b/stdlib/REPL/src/REPLCompletions.jl @@ -478,17 +478,59 @@ function get_type(sym, fn::Module) return found ? Core.Typeof(val) : Any, found end +function get_type(T, found::Bool, default_any::Bool) + return found ? T : + default_any ? Any : throw(ArgumentError("argument not found")) +end + # Method completion on function call expression that look like :(max(1)) function complete_methods(ex_org::Expr, context_module::Module=Main) func, found = get_value(ex_org.args[1], context_module)::Tuple{Any,Bool} !found && return Completion[] - funargs = ex_org.args[2:end] - # handle broadcasting, but only handle number of arguments instead of - # argument types + args_ex, kwargs_ex = complete_methods_args(ex_org.args[2:end], ex_org, context_module, true, true) + + out = Completion[] + complete_methods!(out, func, args_ex, kwargs_ex) + return out +end + +function complete_any_methods(ex_org::Expr, callee_module::Module, context_module::Module, moreargs::Bool) + out = Completion[] + args_ex, kwargs_ex = try + complete_methods_args(ex_org.args[2:end], ex_org, context_module, false, false) + catch + return out + end + + for name in names(callee_module; all=true) + if !Base.isdeprecated(callee_module, name) && isdefined(callee_module, name) + func = getfield(callee_module, name) + if !isa(func, Module) + complete_methods!(out, func, args_ex, kwargs_ex, moreargs) + elseif callee_module === Main::Module && isa(func, Module) + callee_module2 = func + for name in names(callee_module2) + if isdefined(callee_module2, name) + func = getfield(callee_module, name) + if !isa(func, Module) + complete_methods!(out, func, args_ex, kwargs_ex, moreargs) + end + end + end + end + end + end + + return out +end + +function complete_methods_args(funargs::Vector{Any}, ex_org::Expr, context_module::Module, default_any::Bool, allow_broadcasting::Bool) args_ex = Any[] kwargs_ex = Pair{Symbol,Any}[] - if ex_org.head === :. && ex_org.args[2] isa Expr + if allow_broadcasting && ex_org.head === :. && ex_org.args[2] isa Expr + # handle broadcasting, but only handle number of arguments instead of + # argument types for _ in (ex_org.args[2]::Expr).args push!(args_ex, Any) end @@ -497,18 +539,20 @@ function complete_methods(ex_org::Expr, context_module::Module=Main) if isexpr(ex, :parameters) for x in ex.args n, v = isexpr(x, :kw) ? (x.args...,) : (x, x) - push!(kwargs_ex, n => first(get_type(v, context_module))) + push!(kwargs_ex, n => get_type(get_type(v, context_module)..., default_any)) end elseif isexpr(ex, :kw) n, v = (ex.args...,) - push!(kwargs_ex, n => first(get_type(v, context_module))) + push!(kwargs_ex, n => get_type(get_type(v, context_module)..., default_any)) else - push!(args_ex, first(get_type(ex, context_module))) + push!(args_ex, get_type(get_type(ex, context_module)..., default_any)) end end end + return args_ex, kwargs_ex +end - out = Completion[] +function complete_methods!(out::Vector{Completion}, @nospecialize(func), args_ex::Vector{Any}, kwargs_ex::Vector{Pair{Symbol,Any}}, moreargs::Bool=true) ml = methods(func) # Input types and number of arguments if isempty(kwargs_ex) @@ -525,6 +569,9 @@ function complete_methods(ex_org::Expr, context_module::Module=Main) ml = methods(kwfunc) func = kwfunc end + if !moreargs + na = typemax(Int) + end for (method::Method, orig_method) in zip(ml, orig_ml) ms = method.sig @@ -534,7 +581,6 @@ function complete_methods(ex_org::Expr, context_module::Module=Main) push!(out, MethodCompletion(func, t_in, method, orig_method)) end end - return out end include("latex_symbols.jl") @@ -652,6 +698,36 @@ function completions(string::String, pos::Int, context_module::Module=Main) partial = string[1:pos] inc_tag = Base.incomplete_tag(Meta.parse(partial, raise=false, depwarn=false)) + # ?(x, y)TAB lists methods you can call with these objects + # ?(x, y TAB lists methods that take these objects as the first two arguments + # MyModule.?(x, y)TAB restricts the search to names in MyModule + rexm = match(r"(\w+\.|)\?\((.*)$", partial) + if rexm !== nothing + # Get the module scope + if isempty(rexm.captures[1]) + callee_module = context_module + else + modname = Symbol(rexm.captures[1][1:end-1]) + if isdefined(context_module, modname) + callee_module = getfield(context_module, modname) + if !isa(callee_module, Module) + callee_module = context_module + end + else + callee_module = context_module + end + end + moreargs = !endswith(rexm.captures[2], ')') + callstr = "_(" * rexm.captures[2] + if moreargs + callstr *= ')' + end + ex_org = Meta.parse(callstr, raise=false, depwarn=false) + if isa(ex_org, Expr) + return complete_any_methods(ex_org, callee_module::Module, context_module, moreargs), (0:length(rexm.captures[1])+1) .+ rexm.offset, false + end + end + # if completing a key in a Dict identifier, partial_key, loc = dict_identifier_key(partial, inc_tag, context_module) if identifier !== nothing diff --git a/stdlib/REPL/test/replcompletions.jl b/stdlib/REPL/test/replcompletions.jl index 545e81a27968d..14f6de445c083 100644 --- a/stdlib/REPL/test/replcompletions.jl +++ b/stdlib/REPL/test/replcompletions.jl @@ -64,6 +64,8 @@ let ex = quote test6()=[a, a] test7() = rand(Bool) ? 1 : 1.0 test8() = Any[1][1] + test9(x::Char) = pass + test9(x::Char, i::Int) = pass kwtest(; x=1, y=2, w...) = pass kwtest2(a; x=1, y=2, w...) = pass @@ -516,6 +518,34 @@ for s in ("CompletionFoo.kwtest2(1; x=1,", @test occursin("a; x, y, w...", c[1]) end +################################################################# + +# method completion with `?` (arbitrary method with given argument types) +let s = "CompletionFoo.?([1,2,3], 2.0)" + c, r, res = test_complete(s) + @test !res + @test any(str->occursin("test(x::AbstractArray{T}, y) where T<:Real", str), c) + @test any(str->occursin("test(args...)", str), c) + @test !any(str->occursin("test3(x::AbstractArray{Int", str), c) + @test !any(str->occursin("test4", str), c) +end + +let s = "CompletionFoo.?('c')" + c, r, res = test_complete(s) + @test !res + @test any(str->occursin("test9(x::Char)", str), c) + @test !any(str->occursin("test9(x::Char, i::Int", str), c) +end + +let s = "CompletionFoo.?('c'" + c, r, res = test_complete(s) + @test !res + @test any(str->occursin("test9(x::Char)", str), c) + @test any(str->occursin("test9(x::Char, i::Int", str), c) +end + +################################################################# + # Test of inference based getfield completion let s = "(1+2im)." c,r = test_complete(s)