Skip to content

Commit e87e30c

Browse files
timholyvtjnash
andauthored
?(x, y)TAB completes methods accepting x, y (#38791)
* ?(x, y)TAB completes methods accepting x, y Closes #30052 xref #38704 xref #37993 Co-authored-by: Jameson Nash <[email protected]>
1 parent 7005b7d commit e87e30c

File tree

6 files changed

+251
-31
lines changed

6 files changed

+251
-31
lines changed

NEWS.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,12 @@ Standard library changes
6363

6464
#### REPL
6565

66+
* ` ?(x, y` followed by TAB displays all methods that can be called
67+
with arguments `x, y, ...`. (The space at the beginning prevents entering help-mode.)
68+
`MyModule.?(x, y` limits the search to `MyModule`. TAB requires that at least one
69+
argument have a type more specific than `Any`; use SHIFT-TAB instead of TAB
70+
to allow any compatible methods.
71+
6672
#### SparseArrays
6773

6874
#### Dates

stdlib/REPL/docs/src/index.md

Lines changed: 98 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,27 @@ Users should refer to `LineEdit.jl` to discover the available actions on key inp
307307
In both the Julian and help modes of the REPL, one can enter the first few characters of a function
308308
or type and then press the tab key to get a list all matches:
309309

310+
```julia-repl
311+
julia> x[TAB]
312+
julia> xor
313+
```
314+
315+
In some cases it only completes part of the name, up to the next ambiguity:
316+
317+
```julia-repl
318+
julia> mapf[TAB]
319+
julia> mapfold
320+
```
321+
322+
If you hit tab again, then you get the list of things that might complete this:
323+
324+
```julia-repl
325+
julia> mapfold[TAB]
326+
mapfoldl mapfoldr
327+
```
328+
329+
Like other components of the REPL, the search is case-sensitive:
330+
310331
```julia-repl
311332
julia> stri[TAB]
312333
stride strides string strip
@@ -365,6 +386,46 @@ shell> /[TAB]
365386
.dockerinit bin/ dev/ home/ lib64/ mnt/ proc/ run/ srv/ tmp/ var/
366387
```
367388

389+
Dictionary keys can also be tab completed:
390+
391+
```julia-repl
392+
julia> foo = Dict("qwer1"=>1, "qwer2"=>2, "asdf"=>3)
393+
Dict{String,Int64} with 3 entries:
394+
"qwer2" => 2
395+
"asdf" => 3
396+
"qwer1" => 1
397+
398+
julia> foo["q[TAB]
399+
400+
"qwer1" "qwer2"
401+
julia> foo["qwer
402+
```
403+
404+
Tab completion can also help completing fields:
405+
406+
```julia-repl
407+
julia> x = 3 + 4im;
408+
409+
julia> julia> x.[TAB][TAB]
410+
im re
411+
412+
julia> import UUIDs
413+
414+
julia> UUIDs.uuid[TAB][TAB]
415+
uuid1 uuid4 uuid5 uuid_version
416+
```
417+
418+
Fields for output from functions can also be completed:
419+
420+
```julia-repl
421+
julia> split("","")[1].[TAB]
422+
lastindex offset string
423+
```
424+
425+
The completion of fields for output from functions uses type inference, and it can only suggest
426+
fields if the function is type stable.
427+
428+
368429
Tab completion can help with investigation of the available methods matching the input arguments:
369430

370431
```julia-repl
@@ -392,38 +453,54 @@ The completion of the methods uses type inference and can therefore see if the a
392453
even if the arguments are output from functions. The function needs to be type stable for the
393454
completion to be able to remove non-matching methods.
394455

395-
Tab completion can also help completing fields:
456+
If you wonder which methods can be used with particular argument types, use `?` as the function name.
457+
This shows an example of looking for functions in InteractiveUtils that accept a single string:
396458

397459
```julia-repl
398-
julia> import UUIDs
399-
400-
julia> UUIDs.uuid[TAB]
401-
uuid1 uuid4 uuid_version
460+
julia> InteractiveUtils.?("somefile")[TAB]
461+
edit(path::AbstractString) in InteractiveUtils at InteractiveUtils/src/editless.jl:197
462+
less(file::AbstractString) in InteractiveUtils at InteractiveUtils/src/editless.jl:266
402463
```
403464

404-
Fields for output from functions can also be completed:
465+
This listed methods in the `InteractiveUtils` module that can be called on a string.
466+
By default, this excludes methods where all arguments are typed as `Any`,
467+
but you can see those too by holding down SHIFT-TAB instead of TAB:
405468

406469
```julia-repl
407-
julia> split("","")[1].[TAB]
408-
lastindex offset string
470+
julia> InteractiveUtils.?("somefile")[SHIFT-TAB]
471+
apropos(string) in REPL at REPL/src/docview.jl:796
472+
clipboard(x) in InteractiveUtils at InteractiveUtils/src/clipboard.jl:64
473+
code_llvm(f) in InteractiveUtils at InteractiveUtils/src/codeview.jl:221
474+
code_native(f) in InteractiveUtils at InteractiveUtils/src/codeview.jl:243
475+
edit(path::AbstractString) in InteractiveUtils at InteractiveUtils/src/editless.jl:197
476+
edit(f) in InteractiveUtils at InteractiveUtils/src/editless.jl:225
477+
eval(x) in InteractiveUtils at InteractiveUtils/src/InteractiveUtils.jl:3
478+
include(x) in InteractiveUtils at InteractiveUtils/src/InteractiveUtils.jl:3
479+
less(file::AbstractString) in InteractiveUtils at InteractiveUtils/src/editless.jl:266
480+
less(f) in InteractiveUtils at InteractiveUtils/src/editless.jl:274
481+
report_bug(kind) in InteractiveUtils at InteractiveUtils/src/InteractiveUtils.jl:391
482+
separate_kwargs(args...; kwargs...) in InteractiveUtils at InteractiveUtils/src/macros.jl:7
409483
```
410484

411-
The completion of fields for output from functions uses type inference, and it can only suggest
412-
fields if the function is type stable.
485+
You can also use ` ?("somefile")[TAB]` and look across all modules, but the method lists can be long.
413486

414-
Dictionary keys can also be tab completed:
487+
By omitting the closing parenthesis, you can include functions that might require additional arguments:
415488

416489
```julia-repl
417-
julia> foo = Dict("qwer1"=>1, "qwer2"=>2, "asdf"=>3)
418-
Dict{String,Int64} with 3 entries:
419-
"qwer2" => 2
420-
"asdf" => 3
421-
"qwer1" => 1
422-
423-
julia> foo["q[TAB]
424-
425-
"qwer1" "qwer2"
426-
julia> foo["qwer
490+
julia> using Mmap
491+
492+
help?> Mmap.?("file",[TAB]
493+
Mmap.Anonymous(name::String, readonly::Bool, create::Bool) in Mmap at Mmap/src/Mmap.jl:16
494+
mmap(file::AbstractString) in Mmap at Mmap/src/Mmap.jl:245
495+
mmap(file::AbstractString, ::Type{T}) where T<:Array in Mmap at Mmap/src/Mmap.jl:245
496+
mmap(file::AbstractString, ::Type{T}, dims::Tuple{Vararg{Integer, N}}) where {T<:Array, N} in Mmap at Mmap/src/Mmap.jl:245
497+
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
498+
mmap(file::AbstractString, ::Type{T}, len::Integer) where T<:Array in Mmap at Mmap/src/Mmap.jl:251
499+
mmap(file::AbstractString, ::Type{T}, len::Integer, offset::Integer; grow, shared) where T<:Array in Mmap at Mmap/src/Mmap.jl:251
500+
mmap(file::AbstractString, ::Type{T}, dims::Tuple{Vararg{Integer, N}}) where {T<:BitArray, N} in Mmap at Mmap/src/Mmap.jl:316
501+
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
502+
mmap(file::AbstractString, ::Type{T}, len::Integer) where T<:BitArray in Mmap at Mmap/src/Mmap.jl:322
503+
mmap(file::AbstractString, ::Type{T}, len::Integer, offset::Integer; grow, shared) where T<:BitArray in Mmap at Mmap/src/Mmap.jl:322
427504
```
428505

429506
## Customizing Colors

stdlib/REPL/src/LineEdit.jl

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,11 @@ mutable struct PromptState <: ModeState
106106
refresh_wait::Union{Timer,Nothing}
107107
end
108108

109+
struct Modifiers
110+
shift::Bool
111+
end
112+
Modifiers() = Modifiers(false)
113+
109114
options(s::PromptState) =
110115
if isdefined(s.p, :repl) && isdefined(s.p.repl, :options)
111116
# we can't test isa(s.p.repl, LineEditREPL) as LineEditREPL is defined
@@ -1907,6 +1912,10 @@ mode(s::PromptState) = s.p # ::Prompt
19071912
mode(s::SearchState) = @assert false
19081913
mode(s::PrefixSearchState) = s.histprompt.parent_prompt # ::Prompt
19091914

1915+
setmodifiers!(s::MIState, m::Modifiers) = setmodifiers!(mode(s), m)
1916+
setmodifiers!(p::Prompt, m::Modifiers) = setmodifiers!(p.complete, m)
1917+
setmodifiers!(c) = nothing
1918+
19101919
# Search Mode completions
19111920
function complete_line(s::SearchState, repeats)
19121921
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
21742183
return refresh_line(s)
21752184
end
21762185

2186+
function shift_tab_completion(s::MIState)
2187+
setmodifiers!(s, Modifiers(true))
2188+
return complete_line(s)
2189+
end
2190+
21772191
# return true iff the content of the buffer is modified
21782192
# return false when only the position changed
21792193
function edit_insert_tab(buf::IOBuffer, jump_spaces::Bool=false, delete_trailing::Bool=jump_spaces)
@@ -2209,6 +2223,8 @@ const default_keymap =
22092223
AnyDict(
22102224
# Tab
22112225
'\t' => (s::MIState,o...)->edit_tab(s, true),
2226+
# Shift-tab
2227+
"\e[Z" => (s::MIState,o...)->shift_tab_completion(s),
22122228
# Enter
22132229
'\r' => (s::MIState,o...)->begin
22142230
if on_enter(s) || (eof(buffer(s)) && s.key_repeats > 1)

stdlib/REPL/src/REPL.jl

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ import ..LineEdit:
5555
history_last,
5656
history_search,
5757
accept_result,
58+
setmodifiers!,
5859
terminal,
5960
MIState,
6061
PromptState,
@@ -470,16 +471,30 @@ LineEditREPL(t::TextTerminal, hascolor::Bool, envcolors::Bool=false) =
470471
false, false, false, envcolors
471472
)
472473

473-
mutable struct REPLCompletionProvider <: CompletionProvider end
474+
mutable struct REPLCompletionProvider <: CompletionProvider
475+
modifiers::LineEdit.Modifiers
476+
end
477+
REPLCompletionProvider() = REPLCompletionProvider(LineEdit.Modifiers())
474478
mutable struct ShellCompletionProvider <: CompletionProvider end
475479
struct LatexCompletions <: CompletionProvider end
476480

481+
setmodifiers!(c::REPLCompletionProvider, m::LineEdit.Modifiers) = c.modifiers = m
482+
477483
beforecursor(buf::IOBuffer) = String(buf.data[1:buf.ptr-1])
478484

479485
function complete_line(c::REPLCompletionProvider, s::PromptState)
480486
partial = beforecursor(s.input_buffer)
481487
full = LineEdit.input_string(s)
482488
ret, range, should_complete = completions(full, lastindex(partial))
489+
if !c.modifiers.shift
490+
# Filter out methods where all arguments are `Any`
491+
filter!(ret) do c
492+
isa(c, REPLCompletions.MethodCompletion) || return true
493+
sig = Base.unwrap_unionall(c.method.sig)::DataType
494+
return !all(T -> T === Any || T === Vararg{Any}, sig.parameters[2:end])
495+
end
496+
end
497+
c.modifiers = LineEdit.Modifiers()
483498
return unique!(map(completion_text, ret)), partial[range], should_complete
484499
end
485500

stdlib/REPL/src/REPLCompletions.jl

Lines changed: 85 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -478,17 +478,59 @@ function get_type(sym, fn::Module)
478478
return found ? Core.Typeof(val) : Any, found
479479
end
480480

481+
function get_type(T, found::Bool, default_any::Bool)
482+
return found ? T :
483+
default_any ? Any : throw(ArgumentError("argument not found"))
484+
end
485+
481486
# Method completion on function call expression that look like :(max(1))
482487
function complete_methods(ex_org::Expr, context_module::Module=Main)
483488
func, found = get_value(ex_org.args[1], context_module)::Tuple{Any,Bool}
484489
!found && return Completion[]
485490

486-
funargs = ex_org.args[2:end]
487-
# handle broadcasting, but only handle number of arguments instead of
488-
# argument types
491+
args_ex, kwargs_ex = complete_methods_args(ex_org.args[2:end], ex_org, context_module, true, true)
492+
493+
out = Completion[]
494+
complete_methods!(out, func, args_ex, kwargs_ex)
495+
return out
496+
end
497+
498+
function complete_any_methods(ex_org::Expr, callee_module::Module, context_module::Module, moreargs::Bool)
499+
out = Completion[]
500+
args_ex, kwargs_ex = try
501+
complete_methods_args(ex_org.args[2:end], ex_org, context_module, false, false)
502+
catch
503+
return out
504+
end
505+
506+
for name in names(callee_module; all=true)
507+
if !Base.isdeprecated(callee_module, name) && isdefined(callee_module, name)
508+
func = getfield(callee_module, name)
509+
if !isa(func, Module)
510+
complete_methods!(out, func, args_ex, kwargs_ex, moreargs)
511+
elseif callee_module === Main::Module && isa(func, Module)
512+
callee_module2 = func
513+
for name in names(callee_module2)
514+
if isdefined(callee_module2, name)
515+
func = getfield(callee_module, name)
516+
if !isa(func, Module)
517+
complete_methods!(out, func, args_ex, kwargs_ex, moreargs)
518+
end
519+
end
520+
end
521+
end
522+
end
523+
end
524+
525+
return out
526+
end
527+
528+
function complete_methods_args(funargs::Vector{Any}, ex_org::Expr, context_module::Module, default_any::Bool, allow_broadcasting::Bool)
489529
args_ex = Any[]
490530
kwargs_ex = Pair{Symbol,Any}[]
491-
if ex_org.head === :. && ex_org.args[2] isa Expr
531+
if allow_broadcasting && ex_org.head === :. && ex_org.args[2] isa Expr
532+
# handle broadcasting, but only handle number of arguments instead of
533+
# argument types
492534
for _ in (ex_org.args[2]::Expr).args
493535
push!(args_ex, Any)
494536
end
@@ -497,18 +539,20 @@ function complete_methods(ex_org::Expr, context_module::Module=Main)
497539
if isexpr(ex, :parameters)
498540
for x in ex.args
499541
n, v = isexpr(x, :kw) ? (x.args...,) : (x, x)
500-
push!(kwargs_ex, n => first(get_type(v, context_module)))
542+
push!(kwargs_ex, n => get_type(get_type(v, context_module)..., default_any))
501543
end
502544
elseif isexpr(ex, :kw)
503545
n, v = (ex.args...,)
504-
push!(kwargs_ex, n => first(get_type(v, context_module)))
546+
push!(kwargs_ex, n => get_type(get_type(v, context_module)..., default_any))
505547
else
506-
push!(args_ex, first(get_type(ex, context_module)))
548+
push!(args_ex, get_type(get_type(ex, context_module)..., default_any))
507549
end
508550
end
509551
end
552+
return args_ex, kwargs_ex
553+
end
510554

511-
out = Completion[]
555+
function complete_methods!(out::Vector{Completion}, @nospecialize(func), args_ex::Vector{Any}, kwargs_ex::Vector{Pair{Symbol,Any}}, moreargs::Bool=true)
512556
ml = methods(func)
513557
# Input types and number of arguments
514558
if isempty(kwargs_ex)
@@ -525,6 +569,9 @@ function complete_methods(ex_org::Expr, context_module::Module=Main)
525569
ml = methods(kwfunc)
526570
func = kwfunc
527571
end
572+
if !moreargs
573+
na = typemax(Int)
574+
end
528575

529576
for (method::Method, orig_method) in zip(ml, orig_ml)
530577
ms = method.sig
@@ -534,7 +581,6 @@ function complete_methods(ex_org::Expr, context_module::Module=Main)
534581
push!(out, MethodCompletion(func, t_in, method, orig_method))
535582
end
536583
end
537-
return out
538584
end
539585

540586
include("latex_symbols.jl")
@@ -652,6 +698,36 @@ function completions(string::String, pos::Int, context_module::Module=Main)
652698
partial = string[1:pos]
653699
inc_tag = Base.incomplete_tag(Meta.parse(partial, raise=false, depwarn=false))
654700

701+
# ?(x, y)TAB lists methods you can call with these objects
702+
# ?(x, y TAB lists methods that take these objects as the first two arguments
703+
# MyModule.?(x, y)TAB restricts the search to names in MyModule
704+
rexm = match(r"(\w+\.|)\?\((.*)$", partial)
705+
if rexm !== nothing
706+
# Get the module scope
707+
if isempty(rexm.captures[1])
708+
callee_module = context_module
709+
else
710+
modname = Symbol(rexm.captures[1][1:end-1])
711+
if isdefined(context_module, modname)
712+
callee_module = getfield(context_module, modname)
713+
if !isa(callee_module, Module)
714+
callee_module = context_module
715+
end
716+
else
717+
callee_module = context_module
718+
end
719+
end
720+
moreargs = !endswith(rexm.captures[2], ')')
721+
callstr = "_(" * rexm.captures[2]
722+
if moreargs
723+
callstr *= ')'
724+
end
725+
ex_org = Meta.parse(callstr, raise=false, depwarn=false)
726+
if isa(ex_org, Expr)
727+
return complete_any_methods(ex_org, callee_module::Module, context_module, moreargs), (0:length(rexm.captures[1])+1) .+ rexm.offset, false
728+
end
729+
end
730+
655731
# if completing a key in a Dict
656732
identifier, partial_key, loc = dict_identifier_key(partial, inc_tag, context_module)
657733
if identifier !== nothing

0 commit comments

Comments
 (0)