Skip to content

Commit ef38d58

Browse files
authored
REPLCompletions: allow completions for [import|using] AAA: xxx (JuliaLang#54719)
When an input line like `[import|using] AAA: xxx` is entered, if `AAA` is already loaded in the REPL session, it will now autocomplete to the names available in `AAA` that start with `xxx`. For example, it will autocomplete `using Base.Experimental: @ov|` to `using Base.Experimental: @overlay`. - fixes JuliaLang#23374
1 parent 2230f79 commit ef38d58

File tree

3 files changed

+90
-39
lines changed

3 files changed

+90
-39
lines changed

NEWS.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,8 @@ Standard library changes
113113

114114
- Using the new `usings=true` feature of the `names()` function, REPL completions can now
115115
complete names that have been explicitly `using`-ed. ([#54610])
116+
- REPL completions can now complete input lines like `[import|using] Mod: xxx|` e.g.
117+
complete `using Base.Experimental: @op` to `using Base.Experimental: @opaque`. ([#54719])
116118

117119
#### SuiteSparse
118120

stdlib/REPL/src/REPLCompletions.jl

Lines changed: 60 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,6 @@ function complete_symbol!(suggestions::Vector{Completion},
165165
val = res.val
166166
if isa(val, Module)
167167
mod = val
168-
lookup_module = true
169168
else
170169
lookup_module = false
171170
t = typeof(val)
@@ -242,7 +241,7 @@ function field_completion_eligible(@nospecialize t)
242241
return match.method === GENERIC_PROPERTYNAMES_METHOD
243242
end
244243

245-
function complete_from_list!(suggestions::Vector{Completion}, T::Type, list::Vector{String}, s::Union{String,SubString{String}})
244+
function complete_from_list!(suggestions::Vector{Completion}, T::Type, list::Vector{String}, s::String)
246245
r = searchsorted(list, s)
247246
i = first(r)
248247
n = length(list)
@@ -264,12 +263,12 @@ const sorted_keywords = [
264263
"primitive type", "quote", "return", "struct",
265264
"try", "using", "while"]
266265

267-
complete_keyword!(suggestions::Vector{Completion}, s::Union{String,SubString{String}}) =
266+
complete_keyword!(suggestions::Vector{Completion}, s::String) =
268267
complete_from_list!(suggestions, KeywordCompletion, sorted_keywords, s)
269268

270269
const sorted_keyvals = ["false", "true"]
271270

272-
complete_keyval!(suggestions::Vector{Completion}, s::Union{String,SubString{String}}) =
271+
complete_keyval!(suggestions::Vector{Completion}, s::String) =
273272
complete_from_list!(suggestions, KeyvalCompletion, sorted_keyvals, s)
274273

275274
function do_raw_escape(s)
@@ -892,17 +891,36 @@ const subscript_regex = Regex("^\\\\_[" * join(isdigit(k) || isletter(k) ? "$k"
892891
const superscripts = Dict(k[3]=>v[1] for (k,v) in latex_symbols if startswith(k, "\\^") && length(k)==3)
893892
const superscript_regex = Regex("^\\\\\\^[" * join(isdigit(k) || isletter(k) ? "$k" : "\\$k" for k in keys(superscripts)) * "]+\\z")
894893

895-
# Aux function to detect whether we're right after a
896-
# using or import keyword
897-
function afterusing(string::String, startpos::Int)
898-
(isempty(string) || startpos == 0) && return false
899-
str = string[1:prevind(string,startpos)]
900-
isempty(str) && return false
901-
rstr = reverse(str)
902-
r = findfirst(r"\s(gnisu|tropmi)\b", rstr)
903-
r === nothing && return false
904-
fr = reverseind(str, last(r))
905-
return occursin(r"^\b(using|import)\s*((\w+[.])*\w+\s*,\s*)*$", str[fr:end])
894+
# Aux function to detect whether we're right after a using or import keyword
895+
function get_import_mode(s::String)
896+
# match simple cases like `using |` and `import |`
897+
mod_import_match_simple = match(r"^\b(using|import)\s*$", s)
898+
if mod_import_match_simple !== nothing
899+
if mod_import_match_simple[1] == "using"
900+
return :using_module
901+
else
902+
return :import_module
903+
end
904+
end
905+
# match module import statements like `using Foo|`, `import Foo, Bar|` and `using Foo.Bar, Baz, |`
906+
mod_import_match = match(r"^\b(using|import)\s+([\w\.]+(?:\s*,\s*[\w\.]+)*),?\s*$", s)
907+
if mod_import_match !== nothing
908+
if mod_import_match.captures[1] == "using"
909+
return :using_module
910+
else
911+
return :import_module
912+
end
913+
end
914+
# now match explicit name import statements like `using Foo: |` and `import Foo: bar, baz|`
915+
name_import_match = match(r"^\b(using|import)\s+([\w\.]+)\s*:\s*([\w@!\s,]+)$", s)
916+
if name_import_match !== nothing
917+
if name_import_match[1] == "using"
918+
return :using_name
919+
else
920+
return :import_name
921+
end
922+
end
923+
return nothing
906924
end
907925

908926
function close_path_completion(dir, paths, str, pos)
@@ -1084,16 +1102,16 @@ end
10841102

10851103
function complete_identifiers!(suggestions::Vector{Completion},
10861104
context_module::Module, string::String, name::String,
1087-
pos::Int, dotpos::Int, startpos::Int;
1105+
pos::Int, separatorpos::Int, startpos::Int;
10881106
comp_keywords::Bool=false,
10891107
complete_modules_only::Bool=false)
10901108
ex = nothing
10911109
if comp_keywords
10921110
complete_keyword!(suggestions, name)
10931111
complete_keyval!(suggestions, name)
10941112
end
1095-
if dotpos > 1 && string[dotpos] == '.'
1096-
s = string[1:prevind(string, dotpos)]
1113+
if separatorpos > 1 && (string[separatorpos] == '.' || string[separatorpos] == ':')
1114+
s = string[1:prevind(string, separatorpos)]
10971115
# First see if the whole string up to `pos` is a valid expression. If so, use it.
10981116
ex = Meta.parse(s, raise=false, depwarn=false)
10991117
if isexpr(ex, :incomplete)
@@ -1132,10 +1150,6 @@ function complete_identifiers!(suggestions::Vector{Completion},
11321150
end
11331151
isexpr(ex, :incomplete) && (ex = nothing)
11341152
elseif isexpr(ex, (:using, :import))
1135-
if isexpr(ex, :import)
1136-
# allow completion for `import Mod.name` (where `name` is not a module)
1137-
complete_modules_only = false
1138-
end
11391153
arglast = ex.args[end] # focus on completion to the last argument
11401154
if isexpr(arglast, :.)
11411155
# We come here for cases like:
@@ -1186,7 +1200,7 @@ function complete_identifiers!(suggestions::Vector{Completion},
11861200
end
11871201
end
11881202
complete_symbol!(suggestions, ex, name, context_module; complete_modules_only)
1189-
return sort!(unique(suggestions), by=completion_text), (dotpos+1):pos, true
1203+
return suggestions
11901204
end
11911205

11921206
function completions(string::String, pos::Int, context_module::Module=Main, shift::Bool=true, hint::Bool=false)
@@ -1248,10 +1262,11 @@ function completions(string::String, pos::Int, context_module::Module=Main, shif
12481262
ok, ret = bslash_completions(string, pos)
12491263
ok && return ret
12501264
startpos = first(varrange) + 4
1251-
dotpos = something(findprev(isequal('.'), string, first(varrange)-1), 0)
1265+
separatorpos = something(findprev(isequal('.'), string, first(varrange)-1), 0)
12521266
name = string[startpos:pos]
1253-
return complete_identifiers!(Completion[], context_module, string, name, pos,
1254-
dotpos, startpos)
1267+
complete_identifiers!(suggestions, context_module, string, name,
1268+
pos, separatorpos, startpos)
1269+
return sort!(unique!(completion_text, suggestions), by=completion_text), (separatorpos+1):pos, true
12551270
elseif inc_tag === :cmd
12561271
# TODO: should this call shell_completions instead of partially reimplementing it?
12571272
let m = match(r"[\t\n\r\"`><=*?|]| (?!\\)", reverse(partial)) # fuzzy shell_parse in reverse
@@ -1383,22 +1398,24 @@ function completions(string::String, pos::Int, context_module::Module=Main, shif
13831398
kwarg_completion, wordrange = complete_keyword_argument(partial, pos, context_module)
13841399
isempty(wordrange) || return kwarg_completion, wordrange, !isempty(kwarg_completion)
13851400

1386-
dotpos = something(findprev(isequal('.'), string, pos), 0)
13871401
startpos = nextind(string, something(findprev(in(non_identifier_chars), string, pos), 0))
13881402
# strip preceding ! operator
13891403
if (m = match(r"\G\!+", partial, startpos)) isa RegexMatch
13901404
startpos += length(m.match)
13911405
end
13921406

1393-
name = string[max(startpos, dotpos+1):pos]
1394-
if afterusing(string, startpos)
1395-
# We're right after using or import. Let's look only for packages
1396-
# and modules we can reach from here
1407+
separatorpos = something(findprev(isequal('.'), string, pos), 0)
1408+
namepos = max(startpos, separatorpos+1)
1409+
name = string[namepos:pos]
1410+
import_mode = get_import_mode(string)
1411+
if import_mode === :using_module || import_mode === :import_module
1412+
# Given input lines like `using Foo|`, `import Foo, Bar|` and `using Foo.Bar, Baz, |`:
1413+
# Let's look only for packages and modules we can reach from here
13971414

13981415
# If there's no dot, we're in toplevel, so we should
13991416
# also search for packages
14001417
s = string[startpos:pos]
1401-
if dotpos <= startpos
1418+
if separatorpos <= startpos
14021419
for dir in Base.load_path()
14031420
if basename(dir) in Base.project_names && isfile(dir)
14041421
complete_loading_candidates!(suggestions, s, dir)
@@ -1431,17 +1448,21 @@ function completions(string::String, pos::Int, context_module::Module=Main, shif
14311448
end
14321449
end
14331450
comp_keywords = false
1434-
complete_modules_only = true
1451+
complete_modules_only = import_mode === :using_module # allow completion for `import Mod.name` (where `name` is not a module)
1452+
elseif import_mode === :using_name || import_mode === :import_name
1453+
# `using Foo: |` and `import Foo: bar, baz|`
1454+
separatorpos = findprev(isequal(':'), string, pos)::Int
1455+
comp_keywords = false
1456+
complete_modules_only = false
14351457
else
1436-
comp_keywords = !isempty(name) && startpos > dotpos
1458+
comp_keywords = !isempty(name) && startpos > separatorpos
14371459
complete_modules_only = false
14381460
end
14391461

1440-
startpos == 0 && (pos = -1)
1441-
dotpos < startpos && (dotpos = startpos - 1)
1442-
return complete_identifiers!(suggestions, context_module, string, name, pos,
1443-
dotpos, startpos;
1444-
comp_keywords, complete_modules_only)
1462+
complete_identifiers!(suggestions, context_module, string, name,
1463+
pos, separatorpos, startpos;
1464+
comp_keywords, complete_modules_only)
1465+
return sort!(unique!(completion_text, suggestions), by=completion_text), namepos:pos, true
14451466
end
14461467

14471468
function shell_completions(string, pos, hint::Bool=false)

stdlib/REPL/test/replcompletions.jl

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2312,14 +2312,42 @@ end
23122312
# JuliaLang/julia#23374: completion for `import Mod.name`
23132313
module Issue23374
23142314
global v23374 = nothing
2315+
global w23374 = missing
23152316
end
23162317
let s = "import .Issue23374.v"
23172318
c, r, res = test_complete_context(s)
23182319
@test res
23192320
@test "v23374" in c
23202321
end
2322+
let s = "import Base.sin, .Issue23374.v"
2323+
c, r, res = test_complete_context(s)
2324+
@test res
2325+
@test "v23374" in c
2326+
end
23212327
let s = "using .Issue23374.v"
23222328
c, r, res = test_complete_context(s)
23232329
@test res
23242330
@test isempty(c)
23252331
end
2332+
# JuliaLang/julia#23374: completion for `using Mod: name`
2333+
let s = "using Base: @ass"
2334+
c, r, res = test_complete_context(s)
2335+
@test res
2336+
@test "@assume_effects" in c
2337+
end
2338+
let s = "using .Issue23374: v"
2339+
c, r, res = test_complete_context(s)
2340+
@test res
2341+
@test "v23374" in c
2342+
end
2343+
let s = "using .Issue23374: v23374, w"
2344+
c, r, res = test_complete_context(s)
2345+
@test res
2346+
@test "w23374" in c
2347+
end
2348+
# completes `using ` to `using [list of available modules]`
2349+
let s = "using "
2350+
c, r, res = test_complete_context(s)
2351+
@test res
2352+
@test !isempty(c)
2353+
end

0 commit comments

Comments
 (0)