• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

JuliaLang / julia / #37940

22 Oct 2024 05:36AM UTC coverage: 85.868% (-1.8%) from 87.654%
#37940

push

local

web-flow
Remove NewPM pass exports. (#56269)

All ecosystem consumers have switched to the string-based API.

77546 of 90308 relevant lines covered (85.87%)

16057626.0 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

88.15
/stdlib/REPL/src/REPLCompletions.jl
1
# This file is a part of Julia. License is MIT: https://julialang.org/license
2

3
module REPLCompletions
4

5
export completions, shell_completions, bslash_completions, completion_text
6

7
using Core: Const
8
const CC = Core.Compiler
9
using Base.Meta
10
using Base: propertynames, something, IdSet
11
using Base.Filesystem: _readdirx
12

13
abstract type Completion end
14

15
struct TextCompletion <: Completion
16
    text::String
193✔
17
end
18

19
struct KeywordCompletion <: Completion
20
    keyword::String
181✔
21
end
22

23
struct KeyvalCompletion <: Completion
24
    keyval::String
15✔
25
end
26

27
struct PathCompletion <: Completion
28
    path::String
7,895✔
29
end
30

31
struct ModuleCompletion <: Completion
32
    parent::Module
460,695✔
33
    mod::String
34
end
35

36
struct PackageCompletion <: Completion
37
    package::String
262✔
38
end
39

40
struct PropertyCompletion <: Completion
41
    value
77✔
42
    property::Symbol
43
end
44

45
struct FieldCompletion <: Completion
46
    typ::DataType
18✔
47
    field::Symbol
48
end
49

50
struct MethodCompletion <: Completion
51
    tt # may be used by an external consumer to infer return type, etc.
52
    method::Method
53
    MethodCompletion(@nospecialize(tt), method::Method) = new(tt, method)
5,078✔
54
end
55

56
struct BslashCompletion <: Completion
57
    bslash::String
7,826✔
58
end
59

60
struct ShellCompletion <: Completion
61
    text::String
62
end
63

64
struct DictCompletion <: Completion
65
    dict::AbstractDict
124✔
66
    key::String
67
end
68

69
struct KeywordArgumentCompletion <: Completion
70
    kwarg::String
47✔
71
end
72

73
# interface definition
74
function Base.getproperty(c::Completion, name::Symbol)
75
    if name === :text
9,825,070✔
76
        return getfield(c, :text)::String
193✔
77
    elseif name === :keyword
9,822,788✔
78
        return getfield(c, :keyword)::String
2,089✔
79
    elseif name === :path
9,815,419✔
80
        return getfield(c, :path)::String
39,045✔
81
    elseif name === :parent
9,815,419✔
82
        return getfield(c, :parent)::Module
×
83
    elseif name === :mod
16,422✔
84
        return getfield(c, :mod)::String
9,798,997✔
85
    elseif name === :package
12,084✔
86
        return getfield(c, :package)::String
4,338✔
87
    elseif name === :property
12,084✔
88
        return getfield(c, :property)::Symbol
271✔
89
    elseif name === :field
11,813✔
90
        return getfield(c, :field)::Symbol
35✔
91
    elseif name === :method
8,135✔
92
        return getfield(c, :method)::Method
5,080✔
93
    elseif name === :bslash
309✔
94
        return getfield(c, :bslash)::String
7,826✔
95
    elseif name === :text
309✔
96
        return getfield(c, :text)::String
×
97
    elseif name === :key
309✔
98
        return getfield(c, :key)::String
124✔
99
    elseif name === :kwarg
185✔
100
        return getfield(c, :kwarg)::String
89✔
101
    end
102
    return getfield(c, name)
96✔
103
end
104

105
_completion_text(c::TextCompletion) = c.text
193✔
106
_completion_text(c::KeywordCompletion) = c.keyword
1,962✔
107
_completion_text(c::KeyvalCompletion) = c.keyval
96✔
108
_completion_text(c::PathCompletion) = c.path
7,339✔
109
_completion_text(c::ModuleCompletion) = c.mod
5,358,420✔
110
_completion_text(c::PackageCompletion) = c.package
3,407✔
111
_completion_text(c::PropertyCompletion) = sprint(Base.show_sym, c.property)
271✔
112
_completion_text(c::FieldCompletion) = sprint(Base.show_sym, c.field)
35✔
113
_completion_text(c::MethodCompletion) = repr(c.method)
3,631✔
114
_completion_text(c::BslashCompletion) = c.bslash
7,826✔
115
_completion_text(c::ShellCompletion) = c.text
×
116
_completion_text(c::DictCompletion) = c.key
124✔
117
_completion_text(c::KeywordArgumentCompletion) = c.kwarg*'='
89✔
118

119
completion_text(c) = _completion_text(c)::String
5,383,310✔
120

121
const Completions = Tuple{Vector{Completion}, UnitRange{Int}, Bool}
122

123
function completes_global(x, name)
124
    return startswith(x, name) && !('#' in x)
3,133,421✔
125
end
126

127
function appendmacro!(syms, macros, needle, endchar)
3,402✔
128
    for macsym in macros
3,402✔
129
        s = String(macsym)
60,042✔
130
        if endswith(s, needle)
60,042✔
131
            from = nextind(s, firstindex(s))
7,760✔
132
            to = prevind(s, sizeof(s)-sizeof(needle)+1)
3,880✔
133
            push!(syms, s[from:to]*endchar)
7,760✔
134
        end
135
    end
60,042✔
136
end
137

138
function append_filtered_mod_names!(ffunc::Function, suggestions::Vector{Completion},
1,701✔
139
                                    mod::Module, name::String, complete_internal_only::Bool)
140
    imported = usings = !complete_internal_only
1,701✔
141
    ssyms = names(mod; all=true, imported, usings)
1,701✔
142
    filter!(ffunc, ssyms)
1,701✔
143
    macros = filter(x -> startswith(String(x), "@" * name), ssyms)
1,847,989✔
144
    syms = String[sprint((io,s)->Base.show_sym(io, s; allow_macroname=true), s) for s in ssyms if completes_global(String(s), name)]
458,516✔
145
    appendmacro!(syms, macros, "_str", "\"")
1,701✔
146
    appendmacro!(syms, macros, "_cmd", "`")
1,701✔
147
    for sym in syms
1,701✔
148
        push!(suggestions, ModuleCompletion(mod, sym))
460,695✔
149
    end
460,695✔
150
    return suggestions
1,701✔
151
end
152

153
# REPL Symbol Completions
154
function complete_symbol!(suggestions::Vector{Completion},
3,558✔
155
                          @nospecialize(prefix), name::String, context_module::Module;
156
                          complete_modules_only::Bool=false,
157
                          shift::Bool=false)
158
    local mod, t, val
1,779✔
159
    complete_internal_only = false
×
160
    if prefix !== nothing
1,779✔
161
        res = repl_eval_ex(prefix, context_module)
740✔
162
        res === nothing && return Completion[]
740✔
163
        if res isa Const
731✔
164
            val = res.val
709✔
165
            if isa(val, Module)
709✔
166
                mod = val
662✔
167
                if !shift
662✔
168
                    # when module is explicitly accessed, show internal bindings that are
169
                    # defined by the module, unless shift key is pressed
170
                    complete_internal_only = true
×
171
                end
172
            else
173
                t = typeof(val)
47✔
174
            end
175
        else
176
            t = CC.widenconst(res)
22✔
177
        end
178
    else
179
        mod = context_module
1,039✔
180
    end
181

182
    if @isdefined(mod) # lookup names available within the module
1,770✔
183
        let modname = nameof(mod),
1,701✔
184
            is_main = mod===Main
185
            append_filtered_mod_names!(suggestions, mod, name, complete_internal_only) do s::Symbol
1,701✔
186
                if Base.isdeprecated(mod, s)
1,901,737✔
187
                    return false
×
188
                elseif s === modname
1,901,737✔
189
                    return false # exclude `Main.Main.Main`, etc.
2,701✔
190
                elseif complete_modules_only && !completes_module(mod, s)
1,899,036✔
191
                    return false
51,755✔
192
                elseif is_main && s === :MainInclude
1,847,281✔
193
                    return false
993✔
194
                end
195
                return true
1,846,288✔
196
            end
197
        end
198
    elseif @isdefined(val) # looking for a property of an instance
69✔
199
        try
47✔
200
            for property in propertynames(val, false)
47✔
201
                # TODO: support integer arguments (#36872)
202
                if property isa Symbol && startswith(string(property), name)
83✔
203
                    push!(suggestions, PropertyCompletion(val, property))
77✔
204
                end
205
            end
83✔
206
        catch
1✔
207
        end
208
    elseif @isdefined(t) && field_completion_eligible(t)
22✔
209
        # Looking for a member of a type
210
        add_field_completions!(suggestions, name, t)
13✔
211
    end
212
    return suggestions
1,770✔
213
end
214

215
completes_module(mod::Module, x::Symbol) =
52,336✔
216
    Base.isbindingresolved(mod, x) && isdefined(mod, x) && isa(getglobal(mod, x), Module)
217

218
function add_field_completions!(suggestions::Vector{Completion}, name::String, @nospecialize(t))
15✔
219
    if isa(t, Union)
15✔
220
        add_field_completions!(suggestions, name, t.a)
2✔
221
        add_field_completions!(suggestions, name, t.b)
2✔
222
    else
223
        @assert isconcretetype(t)
15✔
224
        fields = fieldnames(t)
15✔
225
        for field in fields
15✔
226
            isa(field, Symbol) || continue # Tuple type has ::Int field name
18✔
227
            s = string(field)
18✔
228
            if startswith(s, name)
18✔
229
                push!(suggestions, FieldCompletion(t, field))
18✔
230
            end
231
        end
18✔
232
    end
233
end
234

235
const GENERIC_PROPERTYNAMES_METHOD = which(propertynames, (Any,))
236

237
function field_completion_eligible(@nospecialize t)
26✔
238
    if isa(t, Union)
26✔
239
        return field_completion_eligible(t.a) && field_completion_eligible(t.b)
2✔
240
    end
241
    isconcretetype(t) || return false
31✔
242
    # field completion is correct only when `getproperty` fallbacks to `getfield`
243
    match = Base._which(Tuple{typeof(propertynames),t}; raise=false)
34✔
244
    match === nothing && return false
17✔
245
    return match.method === GENERIC_PROPERTYNAMES_METHOD
17✔
246
end
247

248
function complete_from_list!(suggestions::Vector{Completion}, T::Type, list::Vector{String}, s::String)
1,346✔
249
    r = searchsorted(list, s)
1,346✔
250
    i = first(r)
1,346✔
251
    n = length(list)
1,346✔
252
    while i <= n && startswith(list[i],s)
1,542✔
253
        r = first(r):i
196✔
254
        i += 1
196✔
255
    end
196✔
256
    for kw in list[r]
1,346✔
257
        push!(suggestions, T(kw))
196✔
258
    end
196✔
259
    return suggestions
1,346✔
260
end
261

262
const sorted_keywords = [
263
    "abstract type", "baremodule", "begin", "break", "catch", "ccall",
264
    "const", "continue", "do", "else", "elseif", "end", "export",
265
    "finally", "for", "function", "global", "if", "import",
266
    "let", "local", "macro", "module", "mutable struct",
267
    "primitive type", "quote", "return", "struct",
268
    "try", "using", "while"]
269

270
complete_keyword!(suggestions::Vector{Completion}, s::String) =
604✔
271
    complete_from_list!(suggestions, KeywordCompletion, sorted_keywords, s)
272

273
const sorted_keyvals = ["false", "true"]
274

275
complete_keyval!(suggestions::Vector{Completion}, s::String) =
742✔
276
    complete_from_list!(suggestions, KeyvalCompletion, sorted_keyvals, s)
277

278
function do_raw_escape(s)
279
    # escape_raw_string with delim='`' and ignoring the rule for the ending \
280
    return replace(s, r"(\\+)`" => s"\1\\`")
×
281
end
282
function do_shell_escape(s)
283
    return Base.shell_escape_posixly(s)
3,740✔
284
end
285
function do_string_escape(s)
286
    return escape_string(s, ('\"','$'))
7,428✔
287
end
288

289
const PATH_cache_lock = Base.ReentrantLock()
290
const PATH_cache = Set{String}()
291
PATH_cache_task::Union{Task,Nothing} = nothing # used for sync in tests
292
next_cache_update::Float64 = 0.0
293
function maybe_spawn_cache_PATH()
21✔
294
    global PATH_cache_task, next_cache_update
×
295
    @lock PATH_cache_lock begin
21✔
296
        PATH_cache_task isa Task && !istaskdone(PATH_cache_task) && return
21✔
297
        time() < next_cache_update && return
20✔
298
        PATH_cache_task = Threads.@spawn begin
6✔
299
            REPLCompletions.cache_PATH()
3✔
300
            @lock PATH_cache_lock PATH_cache_task = nothing # release memory when done
3✔
301
        end
302
        Base.errormonitor(PATH_cache_task)
3✔
303
    end
304
end
305

306
# caches all reachable files in PATH dirs
307
function cache_PATH()
3✔
308
    path = get(ENV, "PATH", nothing)
3✔
309
    path isa String || return
3✔
310

311
    global next_cache_update
×
312

313
    # Calling empty! on PATH_cache would be annoying for async typing hints as completions would temporarily disappear.
314
    # So keep track of what's added this time and at the end remove any that didn't appear this time from the global cache.
315
    this_PATH_cache = Set{String}()
3✔
316

317
    @debug "caching PATH files" PATH=path
3✔
318
    pathdirs = split(path, @static Sys.iswindows() ? ";" : ":")
3✔
319

320
    next_yield_time = time() + 0.01
3✔
321

322
    t = @elapsed for pathdir in pathdirs
3✔
323
        actualpath = try
18✔
324
            realpath(pathdir)
20✔
325
        catch ex
326
            ex isa Base.IOError || rethrow()
2✔
327
            # Bash doesn't expect every folder in PATH to exist, so neither shall we
328
            continue
2✔
329
        end
330

331
        if actualpath != pathdir && in(actualpath, pathdirs)
27✔
332
            # Remove paths which (after resolving links) are in the env path twice.
333
            # Many distros eg. point /bin to /usr/bin but have both in the env path.
334
            continue
3✔
335
        end
336

337
        path_entries = try
13✔
338
            _readdirx(pathdir)
14✔
339
        catch e
340
            # Bash allows dirs in PATH that can't be read, so we should as well.
341
            if isa(e, Base.IOError) || isa(e, Base.ArgumentError)
1✔
342
                continue
1✔
343
            else
344
                # We only handle IOError and ArgumentError here
345
                rethrow()
×
346
            end
347
        end
348
        for entry in path_entries
12✔
349
            # In a perfect world, we would filter on whether the file is executable
350
            # here, or even on whether the current user can execute the file in question.
351
            try
2,463✔
352
                if isfile(entry)
2,463✔
353
                    @lock PATH_cache_lock push!(PATH_cache, entry.name)
2,393✔
354
                    push!(this_PATH_cache, entry.name)
2,394✔
355
                end
356
            catch e
357
                # `isfile()` can throw in rare cases such as when probing a
358
                # symlink that points to a file within a directory we do not
359
                # have read access to.
360
                if isa(e, Base.IOError)
1✔
361
                    continue
1✔
362
                else
363
                    rethrow()
×
364
                end
365
            end
366
            if time() >= next_yield_time
2,462✔
367
                yield() # to avoid blocking typing when -t1
2✔
368
                next_yield_time = time() + 0.01
2✔
369
            end
370
        end
2,463✔
371
    end
372

373
    @lock PATH_cache_lock begin
3✔
374
        intersect!(PATH_cache, this_PATH_cache) # remove entries from PATH_cache that weren't found this time
3✔
375
        next_cache_update = time() + 10 # earliest next update can run is 10s after
3✔
376
    end
377

378
    @debug "caching PATH files took $t seconds" length(pathdirs) length(PATH_cache)
3✔
379
    return PATH_cache
3✔
380
end
381

382
function complete_path(path::AbstractString;
1,128✔
383
                       use_envpath=false,
384
                       shell_escape=false,
385
                       raw_escape=false,
386
                       string_escape=false,
387
                       contract_user=false)
388
    @assert !(shell_escape && string_escape)
564✔
389
    if Base.Sys.isunix() && occursin(r"^~(?:/|$)", path)
564✔
390
        # if the path is just "~", don't consider the expanded username as a prefix
391
        if path == "~"
×
392
            dir, prefix = homedir(), ""
×
393
        else
394
            dir, prefix = splitdir(homedir() * path[2:end])
×
395
        end
396
    else
397
        dir, prefix = splitdir(path)
564✔
398
    end
399
    entries = try
564✔
400
        if isempty(dir)
564✔
401
            _readdirx()
297✔
402
        elseif isdir(dir)
534✔
403
            _readdirx(dir)
108✔
404
        else
405
            return Completion[], dir, false
564✔
406
        end
407
    catch ex
408
        ex isa Base.IOError || rethrow()
×
409
        return Completion[], dir, false
×
410
    end
411

412
    matches = Set{String}()
405✔
413
    for entry in entries
405✔
414
        if startswith(entry.name, prefix)
46,010✔
415
            is_dir = try isdir(entry) catch ex; ex isa Base.IOError ? false : rethrow() end
7,120✔
416
            push!(matches, is_dir ? entry.name * "/" : entry.name)
13,219✔
417
        end
418
    end
46,010✔
419

420
    if use_envpath && isempty(dir)
405✔
421
        # Look for files in PATH as well. These are cached in `cache_PATH` in an async task to not block typing.
422
        # If we cannot get lock because its still caching just pass over this so that typing isn't laggy.
423
        maybe_spawn_cache_PATH() # only spawns if enough time has passed and the previous caching task has completed
21✔
424
        @lock PATH_cache_lock begin
21✔
425
            for file in PATH_cache
42✔
426
                startswith(file, prefix) && push!(matches, file)
11,242✔
427
            end
22,484✔
428
        end
429
    end
430

431
    matches = ((shell_escape ? do_shell_escape(s) : string_escape ? do_string_escape(s) : s) for s in matches)
405✔
432
    matches = ((raw_escape ? do_raw_escape(s) : s) for s in matches)
×
433
    matches = Completion[PathCompletion(contract_user ? contractuser(s) : s) for s in matches]
7,541✔
434
    return matches, dir, !isempty(matches)
405✔
435
end
436

437
function complete_path(path::AbstractString,
×
438
                       pos::Int;
439
                       use_envpath=false,
440
                       shell_escape=false,
441
                       string_escape=false,
442
                       contract_user=false)
443
    ## TODO: enable this depwarn once Pkg is fixed
444
    #Base.depwarn("complete_path with pos argument is deprecated because the return value [2] is incorrect to use", :complete_path)
445
    paths, dir, success = complete_path(path; use_envpath, shell_escape, string_escape)
×
446
    if Base.Sys.isunix() && occursin(r"^~(?:/|$)", path)
×
447
        # if the path is just "~", don't consider the expanded username as a prefix
448
        if path == "~"
×
449
            dir, prefix = homedir(), ""
×
450
        else
451
            dir, prefix = splitdir(homedir() * path[2:end])
×
452
        end
453
    else
454
        dir, prefix = splitdir(path)
×
455
    end
456
    startpos = pos - lastindex(prefix) + 1
×
457
    Sys.iswindows() && map!(paths, paths) do c::PathCompletion
×
458
        # emulation for unnecessarily complicated return value, since / is a
459
        # perfectly acceptable path character which does not require quoting
460
        # but is required by Pkg's awkward parser handling
461
        return endswith(c.path, "/") ? PathCompletion(chop(c.path) * "\\\\") : c
×
462
    end
463
    return paths, startpos:pos, success
×
464
end
465

466
function complete_expanduser(path::AbstractString, r)
548✔
467
    expanded =
548✔
468
        try expanduser(path)
549✔
469
        catch e
470
            e isa ArgumentError || rethrow()
1✔
471
            path
549✔
472
        end
473
    return Completion[PathCompletion(expanded)], r, path != expanded
548✔
474
end
475

476
# Returns a range that includes the method name in front of the first non
477
# closed start brace from the end of the string.
478
function find_start_brace(s::AbstractString; c_start='(', c_end=')')
5,826✔
479
    r = reverse(s)
2,913✔
480
    i = firstindex(r)
×
481
    braces = in_comment = 0
×
482
    in_single_quotes = in_double_quotes = in_back_ticks = false
×
483
    num_single_quotes_in_string = count('\'', s)
2,913✔
484
    while i <= ncodeunits(r)
67,950✔
485
        c, i = iterate(r, i)
131,456✔
486
        if c == '#' && i <= ncodeunits(r) && iterate(r, i)[1] == '='
65,728✔
487
            c, i = iterate(r, i) # consume '='
8✔
488
            new_comments = 1
×
489
            # handle #=#=#=#, by counting =# pairs
490
            while i <= ncodeunits(r) && iterate(r, i)[1] == '#'
6✔
491
                c, i = iterate(r, i) # consume '#'
6✔
492
                iterate(r, i)[1] == '=' || break
3✔
493
                c, i = iterate(r, i) # consume '='
4✔
494
                new_comments += 1
2✔
495
            end
2✔
496
            if c == '='
4✔
497
                in_comment += new_comments
3✔
498
            else
499
                in_comment -= new_comments
1✔
500
            end
501
        elseif !in_single_quotes && !in_double_quotes && !in_back_ticks && in_comment == 0
65,724✔
502
            if c == c_start
63,918✔
503
                braces += 1
1,016✔
504
            elseif c == c_end
62,902✔
505
                braces -= 1
325✔
506
            elseif c == '\'' && num_single_quotes_in_string % 2 == 0
62,577✔
507
                # ' can be a transpose too, so check if there are even number of 's in the string
508
                # TODO: This probably needs to be more robust
509
                in_single_quotes = true
×
510
            elseif c == '"'
62,562✔
511
                in_double_quotes = true
×
512
            elseif c == '`'
62,280✔
513
                in_back_ticks = true
×
514
            end
515
        else
516
            if in_single_quotes &&
1,806✔
517
                c == '\'' && i <= ncodeunits(r) && iterate(r, i)[1] != '\\'
518
                in_single_quotes = false
×
519
            elseif in_double_quotes &&
1,791✔
520
                c == '"' && i <= ncodeunits(r) && iterate(r, i)[1] != '\\'
521
                in_double_quotes = false
×
522
            elseif in_back_ticks &&
1,559✔
523
                c == '`' && i <= ncodeunits(r) && iterate(r, i)[1] != '\\'
524
                in_back_ticks = false
×
525
            elseif in_comment > 0 &&
1,552✔
526
                c == '=' && i <= ncodeunits(r) && iterate(r, i)[1] == '#'
527
                # handle =#=#=#=, by counting #= pairs
528
                c, i = iterate(r, i) # consume '#'
6✔
529
                old_comments = 1
×
530
                while i <= ncodeunits(r) && iterate(r, i)[1] == '='
4✔
531
                    c, i = iterate(r, i) # consume '='
4✔
532
                    iterate(r, i)[1] == '#' || break
2✔
533
                    c, i = iterate(r, i) # consume '#'
2✔
534
                    old_comments += 1
1✔
535
                end
1✔
536
                if c == '#'
3✔
537
                    in_comment -= old_comments
2✔
538
                else
539
                    in_comment += old_comments
1✔
540
                end
541
            end
542
        end
543
        braces == 1 && break
65,728✔
544
    end
65,037✔
545
    braces != 1 && return 0:-1, -1
2,913✔
546
    method_name_end = reverseind(s, i)
1,374✔
547
    startind = nextind(s, something(findprev(in(non_identifier_chars), s, method_name_end), 0))::Int
1,070✔
548
    return (startind:lastindex(s), method_name_end)
691✔
549
end
550

551
struct REPLCacheToken end
552

553
struct REPLInterpreter <: CC.AbstractInterpreter
554
    limit_aggressive_inference::Bool
555
    world::UInt
556
    inf_params::CC.InferenceParams
557
    opt_params::CC.OptimizationParams
558
    inf_cache::Vector{CC.InferenceResult}
559
    function REPLInterpreter(limit_aggressive_inference::Bool=false;
523✔
560
                             world::UInt = Base.get_world_counter(),
561
                             inf_params::CC.InferenceParams = CC.InferenceParams(;
562
                                 aggressive_constant_propagation=true),
563
                             opt_params::CC.OptimizationParams = CC.OptimizationParams(),
564
                             inf_cache::Vector{CC.InferenceResult} = CC.InferenceResult[])
565
        return new(limit_aggressive_inference, world, inf_params, opt_params, inf_cache)
517✔
566
    end
567
end
568
CC.InferenceParams(interp::REPLInterpreter) = interp.inf_params
166,052✔
569
CC.OptimizationParams(interp::REPLInterpreter) = interp.opt_params
104✔
570
CC.get_inference_world(interp::REPLInterpreter) = interp.world
101,385✔
571
CC.get_inference_cache(interp::REPLInterpreter) = interp.inf_cache
44,063✔
572
CC.cache_owner(::REPLInterpreter) = REPLCacheToken()
×
573

574
# REPLInterpreter is only used for type analysis, so it should disable optimization entirely
575
CC.may_optimize(::REPLInterpreter) = false
×
576

577
# REPLInterpreter doesn't need any sources to be cached, so discard them aggressively
578
CC.transform_result_for_cache(::REPLInterpreter, ::Core.MethodInstance, ::CC.WorldRange, ::CC.InferenceResult) = nothing
×
579

580
# REPLInterpreter analyzes a top-level frame, so better to not bail out from it
581
CC.bail_out_toplevel_call(::REPLInterpreter, ::CC.InferenceLoopState, ::CC.InferenceState) = false
×
582

583
# `REPLInterpreter` aggressively resolves global bindings to enable reasonable completions
584
# for lines like `Mod.a.|` (where `|` is the cursor position).
585
# Aggressive binding resolution poses challenges for the inference cache validation
586
# (until https://github.com/JuliaLang/julia/issues/40399 is implemented).
587
# To avoid the cache validation issues, `REPLInterpreter` only allows aggressive binding
588
# resolution for top-level frame representing REPL input code and for child uncached frames
589
# that are constant propagated from the top-level frame ("repl-frame"s). This works, even if
590
# those global bindings are not constant and may be mutated in the future, since:
591
# a.) "repl-frame"s are never cached, and
592
# b.) mutable values are never observed by any cached frames.
593
#
594
# `REPLInterpreter` also aggressively concrete evaluate `:inconsistent` calls within
595
# "repl-frame" to provide reasonable completions for lines like `Ref(Some(42))[].|`.
596
# Aggressive concrete evaluation allows us to get accurate type information about complex
597
# expressions that otherwise can not be constant folded, in a safe way, i.e. it still
598
# doesn't evaluate effectful expressions like `pop!(xs)`.
599
# Similarly to the aggressive binding resolution, aggressive concrete evaluation doesn't
600
# present any cache validation issues because "repl-frame" is never cached.
601

602
# `REPLInterpreter` is specifically used by `repl_eval_ex`, where all top-level frames are
603
# `repl_frame` always. However, this assumption wouldn't stand if `REPLInterpreter` were to
604
# be employed, for instance, by `typeinf_ext_toplevel`.
605
is_repl_frame(sv::CC.InferenceState) = sv.linfo.def isa Module && sv.cache_mode === CC.CACHE_MODE_NULL
196✔
606

607
function is_call_graph_uncached(sv::CC.InferenceState)
244,501✔
608
    CC.is_cached(sv) && return false
343,244✔
609
    parent = CC.frame_parent(sv)
497,353✔
610
    parent === nothing && return true
252,852✔
611
    return is_call_graph_uncached(parent::CC.InferenceState)
244,501✔
612
end
613

614
# aggressive global binding resolution within `repl_frame`
615
function CC.abstract_eval_globalref(interp::REPLInterpreter, g::GlobalRef,
616
                                    sv::CC.InferenceState)
617
    if (interp.limit_aggressive_inference ? is_repl_frame(sv) : is_call_graph_uncached(sv))
124,416✔
618
        if CC.isdefined_globalref(g)
5,633✔
619
            return CC.RTEffects(Const(ccall(:jl_get_globalref_value, Any, (Any,), g)), Union{}, CC.EFFECTS_TOTAL)
5,627✔
620
        end
621
        return CC.RTEffects(Union{}, UndefVarError, CC.EFFECTS_THROWS)
6✔
622
    end
623
    return @invoke CC.abstract_eval_globalref(interp::CC.AbstractInterpreter, g::GlobalRef,
56,643✔
624
                                              sv::CC.InferenceState)
625
end
626

627
function is_repl_frame_getproperty(sv::CC.InferenceState)
628
    def = sv.linfo.def
1✔
629
    def isa Method || return false
1✔
630
    def.name === :getproperty || return false
1✔
631
    CC.is_cached(sv) && return false
1✔
632
    return is_repl_frame(CC.frame_parent(sv))
1✔
633
end
634

635
# aggressive global binding resolution for `getproperty(::Module, ::Symbol)` calls within `repl_frame`
636
function CC.builtin_tfunction(interp::REPLInterpreter, @nospecialize(f),
18,513✔
637
                              argtypes::Vector{Any}, sv::CC.InferenceState)
638
    if f === Core.getglobal && (interp.limit_aggressive_inference ? is_repl_frame_getproperty(sv) : is_call_graph_uncached(sv))
18,514✔
639
        if length(argtypes) == 2
57✔
640
            a1, a2 = argtypes
57✔
641
            if isa(a1, Const) && isa(a2, Const)
57✔
642
                a1val, a2val = a1.val, a2.val
57✔
643
                if isa(a1val, Module) && isa(a2val, Symbol)
57✔
644
                    g = GlobalRef(a1val, a2val)
57✔
645
                    if CC.isdefined_globalref(g)
57✔
646
                        return Const(ccall(:jl_get_globalref_value, Any, (Any,), g))
57✔
647
                    end
648
                    return Union{}
×
649
                end
650
            end
651
        end
652
    end
653
    return @invoke CC.builtin_tfunction(interp::CC.AbstractInterpreter, f::Any,
18,456✔
654
                                        argtypes::Vector{Any}, sv::CC.InferenceState)
655
end
656

657
# aggressive concrete evaluation for `:inconsistent` frames within `repl_frame`
658
function CC.concrete_eval_eligible(interp::REPLInterpreter, @nospecialize(f),
36,584✔
659
                                   result::CC.MethodCallResult, arginfo::CC.ArgInfo,
660
                                   sv::CC.InferenceState)
661
    if (interp.limit_aggressive_inference ? is_repl_frame(sv) : is_call_graph_uncached(sv))
73,109✔
662
        neweffects = CC.Effects(result.effects; consistent=CC.ALWAYS_TRUE)
2,671✔
663
        result = CC.MethodCallResult(result.rt, result.exct, result.edgecycle, result.edgelimited,
5,340✔
664
                                     result.edge, neweffects)
665
    end
666
    ret = @invoke CC.concrete_eval_eligible(interp::CC.AbstractInterpreter, f::Any,
73,168✔
667
                                            result::CC.MethodCallResult, arginfo::CC.ArgInfo,
668
                                            sv::CC.InferenceState)
669
    if ret === :semi_concrete_eval
36,584✔
670
        # while the base eligibility check probably won't permit semi-concrete evaluation
671
        # for `REPLInterpreter` (given it completely turns off optimization),
672
        # this ensures we don't inadvertently enter irinterp
673
        ret = :none
×
674
    end
675
    return ret
36,584✔
676
end
677

678
# allow constant propagation for mutable constants
679
function CC.const_prop_argument_heuristic(interp::REPLInterpreter, arginfo::CC.ArgInfo, sv::CC.InferenceState)
680
    if !interp.limit_aggressive_inference
36,050✔
681
        any(@nospecialize(a)->isa(a, Const), arginfo.argtypes) && return true # even if mutable
72,059✔
682
    end
683
    return @invoke CC.const_prop_argument_heuristic(interp::CC.AbstractInterpreter, arginfo::CC.ArgInfo, sv::CC.InferenceState)
95✔
684
end
685

686
function resolve_toplevel_symbols!(src::Core.CodeInfo, mod::Module)
687
    @ccall jl_resolve_globals_in_ir(
511✔
688
        #=jl_array_t *stmts=# src.code::Any,
689
        #=jl_module_t *m=# mod::Any,
690
        #=jl_svec_t *sparam_vals=# Core.svec()::Any,
691
        #=int binding_effects=# 0::Int)::Cvoid
692
    return src
×
693
end
694

695
# lower `ex` and run type inference on the resulting top-level expression
696
function repl_eval_ex(@nospecialize(ex), context_module::Module; limit_aggressive_inference::Bool=false)
2,750✔
697
    if (isexpr(ex, :toplevel) || isexpr(ex, :tuple)) && !isempty(ex.args)
2,749✔
698
        # get the inference result for the last expression
699
        ex = ex.args[end]
3✔
700
    end
701
    lwr = try
1,375✔
702
        Meta.lower(context_module, ex)
1,375✔
703
    catch # macro expansion failed, etc.
704
        return nothing
5✔
705
    end
706
    if lwr isa Symbol
1,370✔
707
        return isdefined(context_module, lwr) ? Const(getfield(context_module, lwr)) : nothing
726✔
708
    end
709
    lwr isa Expr || return Const(lwr) # `ex` is literal
747✔
710
    isexpr(lwr, :thunk) || return nothing # lowered to `Expr(:error, ...)` or similar
571✔
711
    src = lwr.args[1]::Core.CodeInfo
511✔
712

713
    resolve_toplevel_symbols!(src, context_module)
511✔
714
    # construct top-level `MethodInstance`
715
    mi = ccall(:jl_method_instance_for_thunk, Ref{Core.MethodInstance}, (Any, Any), src, context_module)
511✔
716

717
    interp = REPLInterpreter(limit_aggressive_inference)
511✔
718
    result = CC.InferenceResult(mi)
511✔
719
    frame = CC.InferenceState(result, src, #=cache=#:no, interp)
511✔
720

721
    # NOTE Use the fixed world here to make `REPLInterpreter` robust against
722
    #      potential invalidations of `Core.Compiler` methods.
723
    Base.invoke_in_world(COMPLETION_WORLD[], CC.typeinf, interp, frame)
511✔
724

725
    result = frame.result.result
511✔
726
    result === Union{} && return nothing # for whatever reason, callers expect this as the Bottom and/or Top type instead
511✔
727
    return result
502✔
728
end
729

730
# `COMPLETION_WORLD[]` will be initialized within `__init__`
731
# (to allow us to potentially remove REPL from the sysimage in the future).
732
# Note that inference from the `code_typed` call below will use the current world age
733
# rather than `typemax(UInt)`, since `Base.invoke_in_world` uses the current world age
734
# when the given world age is higher than the current one.
735
const COMPLETION_WORLD = Ref{UInt}(typemax(UInt))
736

737
# Generate code cache for `REPLInterpreter` now:
738
# This code cache will be available at the world of `COMPLETION_WORLD`,
739
# assuming no invalidation will happen before initializing REPL.
740
# Once REPL is loaded, `REPLInterpreter` will be resilient against future invalidations.
741
code_typed(CC.typeinf, (REPLInterpreter, CC.InferenceState))
742

743
# Method completion on function call expression that look like :(max(1))
744
MAX_METHOD_COMPLETIONS::Int = 40
745
function _complete_methods(ex_org::Expr, context_module::Module, shift::Bool)
311✔
746
    funct = repl_eval_ex(ex_org.args[1], context_module)
311✔
747
    funct === nothing && return 2, nothing, [], Set{Symbol}()
311✔
748
    funct = CC.widenconst(funct)
306✔
749
    args_ex, kwargs_ex, kwargs_flag = complete_methods_args(ex_org, context_module, true, true)
306✔
750
    return kwargs_flag, funct, args_ex, kwargs_ex
306✔
751
end
752

753
function complete_methods(ex_org::Expr, context_module::Module=Main, shift::Bool=false)
754
    kwargs_flag, funct, args_ex, kwargs_ex = _complete_methods(ex_org, context_module, shift)::Tuple{Int, Any, Vector{Any}, Set{Symbol}}
140✔
755
    out = Completion[]
140✔
756
    kwargs_flag == 2 && return out # one of the kwargs is invalid
140✔
757
    kwargs_flag == 0 && push!(args_ex, Vararg{Any}) # allow more arguments if there is no semicolon
127✔
758
    complete_methods!(out, funct, args_ex, kwargs_ex, shift ? -2 : MAX_METHOD_COMPLETIONS, kwargs_flag == 1)
185✔
759
    return out
127✔
760
end
761

762
MAX_ANY_METHOD_COMPLETIONS::Int = 10
763
function recursive_explore_names!(seen::IdSet, callee_module::Module, initial_module::Module, exploredmodules::IdSet{Module}=IdSet{Module}())
71✔
764
    push!(exploredmodules, callee_module)
85✔
765
    for name in names(callee_module; all=true, imported=true)
71✔
766
        if !Base.isdeprecated(callee_module, name) && !startswith(string(name), '#') && isdefined(initial_module, name)
28,595✔
767
            func = getfield(callee_module, name)
2,260✔
768
            if !isa(func, Module)
2,260✔
769
                funct = Core.Typeof(func)
3,938✔
770
                push!(seen, funct)
2,129✔
771
            elseif isa(func, Module) && func ∉ exploredmodules
131✔
772
                recursive_explore_names!(seen, func, initial_module, exploredmodules)
57✔
773
            end
774
        end
775
    end
14,315✔
776
end
777
function recursive_explore_names(callee_module::Module, initial_module::Module)
778
    seen = IdSet{Any}()
14✔
779
    recursive_explore_names!(seen, callee_module, initial_module)
14✔
780
    seen
×
781
end
782

783
function complete_any_methods(ex_org::Expr, callee_module::Module, context_module::Module, moreargs::Bool, shift::Bool)
14✔
784
    out = Completion[]
14✔
785
    args_ex, kwargs_ex, kwargs_flag = try
14✔
786
        # this may throw, since we set default_any to false
787
        complete_methods_args(ex_org, context_module, false, false)
14✔
788
    catch ex
789
        ex isa ArgumentError || rethrow()
×
790
        return out
14✔
791
    end
792
    kwargs_flag == 2 && return out # one of the kwargs is invalid
14✔
793

794
    # moreargs determines whether to accept more args, independently of the presence of a
795
    # semicolon for the ".?(" syntax
796
    moreargs && push!(args_ex, Vararg{Any})
14✔
797

798
    for seen_name in recursive_explore_names(callee_module, callee_module)
28✔
799
        complete_methods!(out, seen_name, args_ex, kwargs_ex, MAX_ANY_METHOD_COMPLETIONS, false)
1,935✔
800
    end
3,856✔
801

802
    if !shift
14✔
803
        # Filter out methods where all arguments are `Any`
804
        filter!(out) do c
2✔
805
            isa(c, TextCompletion) && return false
12✔
806
            isa(c, MethodCompletion) || return true
12✔
807
            sig = Base.unwrap_unionall(c.method.sig)::DataType
12✔
808
            return !all(@nospecialize(T) -> T === Any || T === Vararg{Any}, sig.parameters[2:end])
20✔
809
        end
810
    end
811

812
    return out
14✔
813
end
814

815
function detect_invalid_kwarg!(kwargs_ex::Vector{Symbol}, @nospecialize(x), kwargs_flag::Int, possible_splat::Bool)
816
    n = isexpr(x, :kw) ? x.args[1] : x
55✔
817
    if n isa Symbol
55✔
818
        push!(kwargs_ex, n)
41✔
819
        return kwargs_flag
41✔
820
    end
821
    possible_splat && isexpr(x, :...) && return kwargs_flag
13✔
822
    return 2 # The kwarg is invalid
10✔
823
end
824

825
function detect_args_kwargs(funargs::Vector{Any}, context_module::Module, default_any::Bool, broadcasting::Bool)
320✔
826
    args_ex = Any[]
320✔
827
    kwargs_ex = Symbol[]
320✔
828
    kwargs_flag = 0
×
829
    # kwargs_flag is:
830
    # * 0 if there is no semicolon and no invalid kwarg
831
    # * 1 if there is a semicolon and no invalid kwarg
832
    # * 2 if there are two semicolons or more, or if some kwarg is invalid, which
833
    #        means that it is not of the form "bar=foo", "bar" or "bar..."
834
    for i in (1+!broadcasting):length(funargs)
475✔
835
        ex = funargs[i]
297✔
836
        if isexpr(ex, :parameters)
297✔
837
            kwargs_flag = ifelse(kwargs_flag == 0, 1, 2) # there should be at most one :parameters
60✔
838
            for x in ex.args
60✔
839
                kwargs_flag = detect_invalid_kwarg!(kwargs_ex, x, kwargs_flag, true)
46✔
840
            end
33✔
841
        elseif isexpr(ex, :kw)
237✔
842
            kwargs_flag = detect_invalid_kwarg!(kwargs_ex, ex, kwargs_flag, false)
22✔
843
        else
844
            if broadcasting
215✔
845
                # handle broadcasting, but only handle number of arguments instead of
846
                # argument types
847
                push!(args_ex, Any)
5✔
848
            else
849
                argt = repl_eval_ex(ex, context_module)
210✔
850
                if argt !== nothing
210✔
851
                    push!(args_ex, CC.widenconst(argt))
184✔
852
                elseif default_any
26✔
853
                    push!(args_ex, Any)
26✔
854
                else
855
                    throw(ArgumentError("argument not found"))
×
856
                end
857
            end
858
        end
859
    end
429✔
860
    return args_ex, Set{Symbol}(kwargs_ex), kwargs_flag
320✔
861
end
862

863
is_broadcasting_expr(ex::Expr) = ex.head === :. && isexpr(ex.args[2], :tuple)
1,948✔
864

865
function complete_methods_args(ex::Expr, context_module::Module, default_any::Bool, allow_broadcasting::Bool)
866
    if allow_broadcasting && is_broadcasting_expr(ex)
306✔
867
        return detect_args_kwargs((ex.args[2]::Expr).args, context_module, default_any, true)
6✔
868
    end
869
    return detect_args_kwargs(ex.args, context_module, default_any, false)
314✔
870
end
871

872
function complete_methods!(out::Vector{Completion}, @nospecialize(funct), args_ex::Vector{Any}, kwargs_ex::Set{Symbol}, max_method_completions::Int, exact_nargs::Bool)
2,231✔
873
    # Input types and number of arguments
874
    t_in = Tuple{funct, args_ex...}
2,231✔
875
    m = Base._methods_by_ftype(t_in, nothing, max_method_completions, Base.get_world_counter(),
2,231✔
876
        #=ambig=# true, Ref(typemin(UInt)), Ref(typemax(UInt)), Ptr{Int32}(C_NULL))
877
    if !isa(m, Vector)
2,231✔
878
        push!(out, TextCompletion(sprint(Base.show_signature_function, funct) * "( too many methods, use SHIFT-TAB to show )"))
193✔
879
        return
193✔
880
    end
881
    for match in m
2,038✔
882
        # TODO: if kwargs_ex, filter out methods without kwargs?
883
        push!(out, MethodCompletion(match.spec_types, match.method))
5,078✔
884
    end
5,078✔
885
    # TODO: filter out methods with wrong number of arguments if `exact_nargs` is set
886
end
887

888
include("latex_symbols.jl")
889
include("emoji_symbols.jl")
890

891
const non_identifier_chars = [" \t\n\r\"\\'`\$><=:;|&{}()[],+-*/?%^~"...]
892
const whitespace_chars = [" \t\n\r"...]
893
# "\"'`"... is added to whitespace_chars as non of the bslash_completions
894
# characters contain any of these characters. It prohibits the
895
# bslash_completions function to try and complete on escaped characters in strings
896
const bslash_separators = [whitespace_chars..., "\"'`"...]
897

898
const subscripts = Dict(k[3]=>v[1] for (k,v) in latex_symbols if startswith(k, "\\_") && length(k)==3)
899
const subscript_regex = Regex("^\\\\_[" * join(isdigit(k) || isletter(k) ? "$k" : "\\$k" for k in keys(subscripts)) * "]+\\z")
900
const superscripts = Dict(k[3]=>v[1] for (k,v) in latex_symbols if startswith(k, "\\^") && length(k)==3)
901
const superscript_regex = Regex("^\\\\\\^[" * join(isdigit(k) || isletter(k) ? "$k" : "\\$k" for k in keys(superscripts)) * "]+\\z")
902

903
# Aux function to detect whether we're right after a using or import keyword
904
function get_import_mode(s::String)
1,638✔
905
    # allow all of these to start with leading whitespace and macros like @eval and @eval(
906
    # ^\s*(?:@\w+\s*(?:\(\s*)?)?
907

908
    # match simple cases like `using |` and `import  |`
909
    mod_import_match_simple = match(r"^\s*(?:@\w+\s*(?:\(\s*)?)?\b(using|import)\s*$", s)
1,638✔
910
    if mod_import_match_simple !== nothing
1,638✔
911
        if mod_import_match_simple[1] == "using"
12✔
912
            return :using_module
4✔
913
        else
914
            return :import_module
2✔
915
        end
916
    end
917
    # match module import statements like `using Foo|`, `import Foo, Bar|` and `using Foo.Bar, Baz, |`
918
    mod_import_match = match(r"^\s*(?:@\w+\s*(?:\(\s*)?)?\b(using|import)\s+([\w\.]+(?:\s*,\s*[\w\.]+)*),?\s*$", s)
1,632✔
919
    if mod_import_match !== nothing
1,632✔
920
        if mod_import_match.captures[1] == "using"
62✔
921
            return :using_module
24✔
922
        else
923
            return :import_module
7✔
924
        end
925
    end
926
    # now match explicit name import statements like `using Foo: |` and `import Foo: bar, baz|`
927
    name_import_match = match(r"^\s*(?:@\w+\s*(?:\(\s*)?)?\b(using|import)\s+([\w\.]+)\s*:\s*([\w@!\s,]+)$", s)
1,601✔
928
    if name_import_match !== nothing
1,601✔
929
        if name_import_match[1] == "using"
10✔
930
            return :using_name
5✔
931
        else
932
            return :import_name
×
933
        end
934
    end
935
    return nothing
1,596✔
936
end
937

938
function close_path_completion(dir, paths, str, pos)
142✔
939
    length(paths) == 1 || return false  # Only close if there's a single choice...
268✔
940
    path = (paths[1]::PathCompletion).path
16✔
941
    path = unescape_string(replace(path, "\\\$"=>"\$"))
32✔
942
    path = joinpath(dir, path)
16✔
943
    # ...except if it's a directory...
944
    try
16✔
945
        isdir(path)
31✔
946
    catch e
947
        e isa Base.IOError || rethrow() # `path` cannot be determined to be a file
17✔
948
    end && return false
949
    # ...and except if there's already a " at the cursor.
950
    return lastindex(str) <= pos || str[nextind(str, pos)] != '"'
6✔
951
end
952

953
function bslash_completions(string::String, pos::Int, hint::Bool=false)
2,068✔
954
    slashpos = something(findprev(isequal('\\'), string, pos), 0)
4,194✔
955
    if (something(findprev(in(bslash_separators), string, pos), 0) < slashpos &&
2,096✔
956
        !(1 < slashpos && (string[prevind(string, slashpos)]=='\\')))
957
        # latex / emoji symbol substitution
958
        s = string[slashpos:pos]
112✔
959
        latex = get(latex_symbols, s, "")
78✔
960
        if !isempty(latex) # complete an exact match
56✔
961
            return (true, (Completion[BslashCompletion(latex)], slashpos:pos, true))
22✔
962
        elseif occursin(subscript_regex, s)
34✔
963
            sub = map(c -> subscripts[c], s[3:end])
62✔
964
            return (true, (Completion[BslashCompletion(sub)], slashpos:pos, true))
6✔
965
        elseif occursin(superscript_regex, s)
28✔
966
            sup = map(c -> superscripts[c], s[3:end])
14✔
967
            return (true, (Completion[BslashCompletion(sup)], slashpos:pos, true))
1✔
968
        end
969
        emoji = get(emoji_symbols, s, "")
29✔
970
        if !isempty(emoji)
27✔
971
            return (true, (Completion[BslashCompletion(emoji)], slashpos:pos, true))
2✔
972
        end
973
        # return possible matches; these cannot be mixed with regular
974
        # Julian completions as only latex / emoji symbols contain the leading \
975
        if startswith(s, "\\:") # emoji
25✔
976
            namelist = Iterators.filter(k -> startswith(k, s), keys(emoji_symbols))
1,243✔
977
        else # latex
978
            namelist = Iterators.filter(k -> startswith(k, s), keys(latex_symbols))
61,176✔
979
        end
980
        return (true, (Completion[BslashCompletion(name) for name in sort!(collect(namelist))], slashpos:pos, true))
25✔
981
    end
982
    return (false, (Completion[], 0:-1, false))
2,012✔
983
end
984

985
function dict_identifier_key(str::String, tag::Symbol, context_module::Module=Main)
2,205✔
986
    if tag === :string
2,205✔
987
        str_close = str*"\""
164✔
988
    elseif tag === :cmd
2,039✔
989
        str_close = str*"`"
5✔
990
    else
991
        str_close = str
×
992
    end
993
    frange, end_of_identifier = find_start_brace(str_close, c_start='[', c_end=']')
2,203✔
994
    isempty(frange) && return (nothing, nothing, nothing)
2,203✔
995
    objstr = str[1:end_of_identifier]
214✔
996
    objex = Meta.parse(objstr, raise=false, depwarn=false)
107✔
997
    objt = repl_eval_ex(objex, context_module)
107✔
998
    isa(objt, Core.Const) || return (nothing, nothing, nothing)
131✔
999
    obj = objt.val
83✔
1000
    isa(obj, AbstractDict) || return (nothing, nothing, nothing)
84✔
1001
    (Base.haslength(obj) && length(obj)::Int < 1_000_000) || return (nothing, nothing, nothing)
83✔
1002
    begin_of_key = something(findnext(!isspace, str, nextind(str, end_of_identifier) + 1), # +1 for [
156✔
1003
                             lastindex(str)+1)
1004
    return (obj, str[begin_of_key:end], begin_of_key)
81✔
1005
end
1006

1007
# This needs to be a separate non-inlined function, see #19441
1008
@noinline function find_dict_matches(identifier::AbstractDict, partial_key)
80✔
1009
    matches = String[]
80✔
1010
    for key in keys(identifier)
122✔
1011
        rkey = repr(key)
933✔
1012
        startswith(rkey,partial_key) && push!(matches,rkey)
933✔
1013
    end
1,410✔
1014
    return matches
80✔
1015
end
1016

1017
# Identify an argument being completed in a method call. If the argument is empty, method
1018
# suggestions will be provided instead of argument completions.
1019
function identify_possible_method_completion(partial, last_idx)
2,804✔
1020
    fail = 0:-1, Expr(:nothing), 0:-1, 0
2,804✔
1021

1022
    # First, check that the last punctuation is either ',', ';' or '('
1023
    idx_last_punct = something(findprev(x -> ispunct(x) && x != '_' && x != '!', partial, last_idx), 0)::Int
19,579✔
1024
    idx_last_punct == 0 && return fail
2,804✔
1025
    last_punct = partial[idx_last_punct]
4,550✔
1026
    last_punct == ',' || last_punct == ';' || last_punct == '(' || return fail
4,339✔
1027

1028
    # Then, check that `last_punct` is only followed by an identifier or nothing
1029
    before_last_word_start = something(findprev(in(non_identifier_chars), partial, last_idx), 0)
1,598✔
1030
    before_last_word_start == 0 && return fail
799✔
1031
    all(isspace, @view partial[nextind(partial, idx_last_punct):before_last_word_start]) || return fail
903✔
1032

1033
    # Check that `last_punct` is either the last '(' or placed after a previous '('
1034
    frange, method_name_end = find_start_brace(@view partial[1:idx_last_punct])
695✔
1035
    method_name_end ∈ frange || return fail
906✔
1036

1037
    # Strip the preceding ! operators, if any, and close the expression with a ')'
1038
    s = replace(partial[frange], r"\G\!+([^=\(]+)" => s"\1"; count=1) * ')'
968✔
1039
    ex = Meta.parse(s, raise=false, depwarn=false)
484✔
1040
    isa(ex, Expr) || return fail
484✔
1041

1042
    # `wordrange` is the position of the last argument to complete
1043
    wordrange = nextind(partial, before_last_word_start):last_idx
624✔
1044
    return frange, ex, wordrange, method_name_end
484✔
1045
end
1046

1047
# Provide completion for keyword arguments in function calls
1048
function complete_keyword_argument(partial::String, last_idx::Int, context_module::Module;
3,614✔
1049
                                   shift::Bool=false)
1050
    frange, ex, wordrange, = identify_possible_method_completion(partial, last_idx)
1,807✔
1051
    fail = Completion[], 0:-1, frange
1,807✔
1052
    ex.head === :call || is_broadcasting_expr(ex) || return fail
3,446✔
1053

1054
    kwargs_flag, funct, args_ex, kwargs_ex = _complete_methods(ex, context_module, true)::Tuple{Int, Any, Vector{Any}, Set{Symbol}}
171✔
1055
    kwargs_flag == 2 && return fail # one of the previous kwargs is invalid
171✔
1056

1057
    methods = Completion[]
169✔
1058
    complete_methods!(methods, funct, Any[Vararg{Any}], kwargs_ex, -1, kwargs_flag == 1)
169✔
1059
    # TODO: use args_ex instead of Any[Vararg{Any}] and only provide kwarg completion for
1060
    # method calls compatible with the current arguments.
1061

1062
    # For each method corresponding to the function call, provide completion suggestions
1063
    # for each keyword that starts like the last word and that is not already used
1064
    # previously in the expression. The corresponding suggestion is "kwname=".
1065
    # If the keyword corresponds to an existing name, also include "kwname" as a suggestion
1066
    # since the syntax "foo(; kwname)" is equivalent to "foo(; kwname=kwname)".
1067
    last_word = partial[wordrange] # the word to complete
338✔
1068
    kwargs = Set{String}()
169✔
1069
    for m in methods
169✔
1070
        m::MethodCompletion
1,437✔
1071
        possible_kwargs = Base.kwarg_decl(m.method)
1,437✔
1072
        current_kwarg_candidates = String[]
1,437✔
1073
        for _kw in possible_kwargs
1,437✔
1074
            kw = String(_kw)
478✔
1075
            if !endswith(kw, "...") && startswith(kw, last_word) && _kw ∉ kwargs_ex
511✔
1076
                push!(current_kwarg_candidates, kw)
67✔
1077
            end
1078
        end
478✔
1079
        union!(kwargs, current_kwarg_candidates)
1,437✔
1080
    end
1,437✔
1081

1082
    suggestions = Completion[KeywordArgumentCompletion(kwarg) for kwarg in kwargs]
216✔
1083

1084
    # Only add these if not in kwarg space. i.e. not in `foo(; `
1085
    if kwargs_flag == 0
169✔
1086
        complete_symbol!(suggestions, #=prefix=#nothing, last_word, context_module; shift)
138✔
1087
        complete_keyval!(suggestions, last_word)
138✔
1088
    end
1089

1090
    return sort!(suggestions, by=completion_text), wordrange
169✔
1091
end
1092

1093
function get_loading_candidates(pkgstarts::String, project_file::String)
1✔
1094
    loading_candidates = String[]
1✔
1095
    d = Base.parsed_toml(project_file)
1✔
1096
    pkg = get(d, "name", nothing)::Union{String, Nothing}
2✔
1097
    if pkg !== nothing && startswith(pkg, pkgstarts)
1✔
1098
        push!(loading_candidates, pkg)
1✔
1099
    end
1100
    deps = get(d, "deps", nothing)::Union{Dict{String, Any}, Nothing}
2✔
1101
    if deps !== nothing
1✔
1102
        for (pkg, _) in deps
2✔
1103
            startswith(pkg, pkgstarts) && push!(loading_candidates, pkg)
1✔
1104
        end
1✔
1105
    end
1106
    return loading_candidates
1✔
1107
end
1108

1109
function complete_loading_candidates!(suggestions::Vector{Completion}, pkgstarts::String, project_file::String)
1✔
1110
    for name in get_loading_candidates(pkgstarts, project_file)
1✔
1111
        push!(suggestions, PackageCompletion(name))
2✔
1112
    end
2✔
1113
    return suggestions
1✔
1114
end
1115

1116
function complete_identifiers!(suggestions::Vector{Completion},
3,282✔
1117
                               context_module::Module, string::String, name::String,
1118
                               pos::Int, separatorpos::Int, startpos::Int;
1119
                               comp_keywords::Bool=false,
1120
                               complete_modules_only::Bool=false,
1121
                               shift::Bool=false)
1122
    if comp_keywords
1,641✔
1123
        complete_keyword!(suggestions, name)
604✔
1124
        complete_keyval!(suggestions, name)
604✔
1125
    end
1126
    if separatorpos > 1 && (string[separatorpos] == '.' || string[separatorpos] == ':')
1,646✔
1127
        s = string[1:prevind(string, separatorpos)]
1,706✔
1128
        # First see if the whole string up to `pos` is a valid expression. If so, use it.
1129
        prefix = Meta.parse(s, raise=false, depwarn=false)
853✔
1130
        if isexpr(prefix, :incomplete)
853✔
1131
            s = string[startpos:pos]
1,224✔
1132
            # Heuristic to find the start of the expression. TODO: This would be better
1133
            # done with a proper error-recovering parser.
1134
            if 0 < startpos <= lastindex(string) && string[startpos] == '.'
1,224✔
1135
                i = prevind(string, startpos)
6✔
1136
                while 0 < i
6✔
1137
                    c = string[i]
12✔
1138
                    if c in (')', ']')
12✔
1139
                        if c == ')'
×
1140
                            c_start = '('
×
1141
                            c_end = ')'
×
1142
                        elseif c == ']'
×
1143
                            c_start = '['
×
1144
                            c_end = ']'
×
1145
                        end
1146
                        frange, end_of_identifier = find_start_brace(string[1:prevind(string, i)], c_start=c_start, c_end=c_end)
×
1147
                        isempty(frange) && break # unbalanced parens
×
1148
                        startpos = first(frange)
×
1149
                        i = prevind(string, startpos)
×
1150
                    elseif c in ('\'', '\"', '\`')
12✔
1151
                        s = "$c$c"*string[startpos:pos]
×
1152
                        break
×
1153
                    else
1154
                        break
×
1155
                    end
1156
                    s = string[startpos:pos]
×
1157
                end
×
1158
            end
1159
            if something(findlast(in(non_identifier_chars), s), 0) < something(findlast(isequal('.'), s), 0)
1,208✔
1160
                lookup_name, name = rsplit(s, ".", limit=2)
550✔
1161
                name = String(name)
550✔
1162
                prefix = Meta.parse(lookup_name, raise=false, depwarn=false)
550✔
1163
            end
1164
            isexpr(prefix, :incomplete) && (prefix = nothing)
658✔
1165
        elseif isexpr(prefix, (:using, :import))
195✔
1166
            arglast = prefix.args[end] # focus on completion to the last argument
16✔
1167
            if isexpr(arglast, :.)
16✔
1168
                # We come here for cases like:
1169
                # - `string`: "using Mod1.Mod2.M"
1170
                # - `ex`: :(using Mod1.Mod2)
1171
                # - `name`: "M"
1172
                # Now we transform `ex` to `:(Mod1.Mod2)` to allow `complete_symbol!` to
1173
                # complete for inner modules whose name starts with `M`.
1174
                # Note that `complete_modules_only=true` is set within `completions`
1175
                prefix = nothing
16✔
1176
                firstdot = true
×
1177
                for arg = arglast.args
16✔
1178
                    if arg === :.
32✔
1179
                        # override `context_module` if multiple `.` accessors are used
1180
                        if firstdot
15✔
1181
                            firstdot = false
×
1182
                        else
1183
                            context_module = parentmodule(context_module)
3✔
1184
                        end
1185
                    elseif arg isa Symbol
17✔
1186
                        if prefix === nothing
17✔
1187
                            prefix = arg
16✔
1188
                        else
1189
                            prefix = Expr(:., prefix, QuoteNode(arg))
1✔
1190
                        end
1191
                    else # invalid expression
1192
                        prefix = nothing
×
1193
                        break
×
1194
                    end
1195
                end
32✔
1196
            end
1197
        elseif isexpr(prefix, :call) && length(prefix.args) > 1
179✔
1198
            isinfix = s[end] != ')'
39✔
1199
            # A complete call expression that does not finish with ')' is an infix call.
1200
            if !isinfix
20✔
1201
                # Handle infix call argument completion of the form bar + foo(qux).
1202
                frange, end_of_identifier = find_start_brace(@view s[1:prevind(s, end)])
30✔
1203
                if !isempty(frange) # if find_start_brace fails to find the brace just continue
15✔
1204
                    isinfix = Meta.parse(@view(s[frange[1]:end]), raise=false, depwarn=false) == prefix.args[end]
15✔
1205
                end
1206
            end
1207
            if isinfix
20✔
1208
                prefix = prefix.args[end]
8✔
1209
            end
1210
        elseif isexpr(prefix, :macrocall) && length(prefix.args) > 1
159✔
1211
            # allow symbol completions within potentially incomplete macrocalls
1212
            if s[end] ≠ '`' && s[end] ≠ ')'
43✔
1213
                prefix = prefix.args[end]
12✔
1214
            end
1215
        end
1216
    else
1217
        prefix = nothing
×
1218
    end
1219
    complete_symbol!(suggestions, prefix, name, context_module; complete_modules_only, shift)
1,641✔
1220
    return suggestions
1,641✔
1221
end
1222

1223
function completions(string::String, pos::Int, context_module::Module=Main, shift::Bool=true, hint::Bool=false)
2,216✔
1224
    # First parse everything up to the current position
1225
    partial = string[1:pos]
4,831✔
1226
    inc_tag = Base.incomplete_tag(Meta.parse(partial, raise=false, depwarn=false))
2,215✔
1227

1228
    # ?(x, y)TAB lists methods you can call with these objects
1229
    # ?(x, y TAB lists methods that take these objects as the first two arguments
1230
    # MyModule.?(x, y)TAB restricts the search to names in MyModule
1231
    rexm = match(r"(\w+\.|)\?\((.*)$", partial)
2,215✔
1232
    if rexm !== nothing
2,215✔
1233
        # Get the module scope
1234
        if isempty(rexm.captures[1])
28✔
1235
            callee_module = context_module
×
1236
        else
1237
            modname = Symbol(rexm.captures[1][1:end-1])
13✔
1238
            if isdefined(context_module, modname)
13✔
1239
                callee_module = getfield(context_module, modname)
13✔
1240
                if !isa(callee_module, Module)
13✔
1241
                    callee_module = context_module
×
1242
                end
1243
            else
1244
                callee_module = context_module
×
1245
            end
1246
        end
1247
        moreargs = !endswith(rexm.captures[2], ')')
14✔
1248
        callstr = "_(" * rexm.captures[2]
14✔
1249
        if moreargs
14✔
1250
            callstr *= ')'
8✔
1251
        end
1252
        ex_org = Meta.parse(callstr, raise=false, depwarn=false)
14✔
1253
        if isa(ex_org, Expr)
14✔
1254
            return complete_any_methods(ex_org, callee_module::Module, context_module, moreargs, shift), (0:length(rexm.captures[1])+1) .+ rexm.offset, false
14✔
1255
        end
1256
    end
1257

1258
    # if completing a key in a Dict
1259
    identifier, partial_key, loc = dict_identifier_key(partial, inc_tag, context_module)
2,281✔
1260
    if identifier !== nothing
2,201✔
1261
        matches = find_dict_matches(identifier, partial_key)
80✔
1262
        length(matches)==1 && (lastindex(string) <= pos || string[nextind(string,pos)] != ']') && (matches[1]*=']')
80✔
1263
        length(matches)>0 && return Completion[DictCompletion(identifier, match) for match in sort!(matches)], loc::Int:pos, true
80✔
1264
    end
1265

1266
    suggestions = Completion[]
2,137✔
1267

1268
    # Check if this is a var"" string macro that should be completed like
1269
    # an identifier rather than a string.
1270
    # TODO: It would be nice for the parser to give us more information here
1271
    # so that we can lookup the macro by identity rather than pattern matching
1272
    # its invocation.
1273
    varrange = findprev("var\"", string, pos)
2,137✔
1274

1275
    expanded = nothing
×
1276
    was_expanded = false
2,137✔
1277

1278
    if varrange !== nothing
2,137✔
1279
        ok, ret = bslash_completions(string, pos)
3✔
1280
        ok && return ret
3✔
1281
        startpos = first(varrange) + 4
3✔
1282
        separatorpos = something(findprev(isequal('.'), string, first(varrange)-1), 0)
5✔
1283
        name = string[startpos:pos]
5✔
1284
        complete_identifiers!(suggestions, context_module, string, name,
3✔
1285
                              pos, separatorpos, startpos;
1286
                              shift)
1287
        return sort!(unique!(completion_text, suggestions), by=completion_text), (separatorpos+1):pos, true
3✔
1288
    elseif inc_tag === :cmd
2,134✔
1289
        # TODO: should this call shell_completions instead of partially reimplementing it?
1290
        let m = match(r"[\t\n\r\"`><=*?|]| (?!\\)", reverse(partial)) # fuzzy shell_parse in reverse
1✔
1291
            startpos = nextind(partial, reverseind(partial, m.offset))
2✔
1292
            r = startpos:pos
1✔
1293
            scs::String = string[r]
2✔
1294

1295
            expanded = complete_expanduser(scs, r)
1✔
1296
            was_expanded = expanded[3]
1✔
1297
            if was_expanded
1✔
1298
                scs = (only(expanded[1])::PathCompletion).path
×
1299
                # If tab press, ispath and user expansion available, return it now
1300
                # otherwise see if we can complete the path further before returning with expanded ~
1301
                !hint && ispath(scs) && return expanded::Completions
×
1302
            end
1303

1304
            path::String = replace(scs, r"(\\+)\g1(\\?)`" => "\1\2`") # fuzzy unescape_raw_string: match an even number of \ before ` and replace with half as many
1✔
1305
            # This expansion with "\\ "=>' ' replacement and shell_escape=true
1306
            # assumes the path isn't further quoted within the cmd backticks.
1307
            path = replace(path, r"\\ " => " ", r"\$" => "\$") # fuzzy shell_parse (reversed by shell_escape_posixly)
1✔
1308
            paths, dir, success = complete_path(path, shell_escape=true, raw_escape=true)
1✔
1309

1310
            if success && !isempty(dir)
1✔
1311
                let dir = do_raw_escape(do_shell_escape(dir))
×
1312
                    # if escaping of dir matches scs prefix, remove that from the completions
1313
                    # otherwise make it the whole completion
1314
                    if endswith(dir, "/") && startswith(scs, dir)
×
1315
                        r = (startpos + sizeof(dir)):pos
×
1316
                    elseif startswith(scs, dir * "/")
×
1317
                        r = nextind(string, startpos + sizeof(dir)):pos
×
1318
                    else
1319
                        map!(paths, paths) do c::PathCompletion
×
1320
                            p = dir * "/" * c.path
×
1321
                            was_expanded && (p = contractuser(p))
×
1322
                            return PathCompletion(p)
×
1323
                        end
1324
                    end
1325
                end
1326
            end
1327
            if isempty(paths) && !hint && was_expanded
1✔
1328
                # if not able to provide completions, not hinting, and ~ expansion was possible, return ~ expansion
1329
                return expanded::Completions
×
1330
            else
1331
                return sort!(paths, by=p->p.path), r::UnitRange{Int}, success
1✔
1332
            end
1333
        end
1334
    elseif inc_tag === :string
2,133✔
1335
        # Find first non-escaped quote
1336
        let m = match(r"\"(?!\\)", reverse(partial))
143✔
1337
            startpos = nextind(partial, reverseind(partial, m.offset))
286✔
1338
            r = startpos:pos
166✔
1339
            scs::String = string[r]
263✔
1340

1341
            expanded = complete_expanduser(scs, r)
143✔
1342
            was_expanded = expanded[3]
143✔
1343
            if was_expanded
143✔
1344
                scs = (only(expanded[1])::PathCompletion).path
2✔
1345
                # If tab press, ispath and user expansion available, return it now
1346
                # otherwise see if we can complete the path further before returning with expanded ~
1347
                !hint && ispath(scs) && return expanded::Completions
2✔
1348
            end
1349

1350
            path = try
142✔
1351
                unescape_string(replace(scs, "\\\$"=>"\$"))
261✔
1352
            catch ex
1353
                ex isa ArgumentError || rethrow()
×
1354
                nothing
142✔
1355
            end
1356
            if !isnothing(path)
284✔
1357
                paths, dir, success = complete_path(path::String, string_escape=true)
142✔
1358

1359
                if close_path_completion(dir, paths, path, pos)
142✔
1360
                    p = (paths[1]::PathCompletion).path * "\""
6✔
1361
                    hint && was_expanded && (p = contractuser(p))
6✔
1362
                    paths[1] = PathCompletion(p)
6✔
1363
                end
1364

1365
                if success && !isempty(dir)
142✔
1366
                    let dir = do_string_escape(dir)
18✔
1367
                        # if escaping of dir matches scs prefix, remove that from the completions
1368
                        # otherwise make it the whole completion
1369
                        if endswith(dir, "/") && startswith(scs, dir)
9✔
1370
                            r = (startpos + sizeof(dir)):pos
1✔
1371
                        elseif startswith(scs, dir * "/") && dir != dirname(homedir())
8✔
1372
                            was_expanded && (dir = contractuser(dir))
8✔
1373
                            r = nextind(string, startpos + sizeof(dir)):pos
8✔
1374
                        else
1375
                            map!(paths, paths) do c::PathCompletion
×
1376
                                p = dir * "/" * c.path
×
1377
                                hint && was_expanded && (p = contractuser(p))
×
1378
                                return PathCompletion(p)
×
1379
                            end
1380
                        end
1381
                    end
1382
                end
1383

1384
                # Fallthrough allowed so that Latex symbols can be completed in strings
1385
                if success
142✔
1386
                    return sort!(paths, by=p->p.path), r::UnitRange{Int}, success
31,747✔
1387
                elseif !hint && was_expanded
71✔
1388
                    # if not able to provide completions, not hinting, and ~ expansion was possible, return ~ expansion
1389
                    return expanded::Completions
1✔
1390
                end
1391
            end
1392
        end
1393
    end
1394
    # if path has ~ and we didn't find any paths to complete just return the expanded path
1395
    was_expanded && return expanded::Completions
2,060✔
1396

1397
    ok, ret = bslash_completions(string, pos)
2,060✔
1398
    ok && return ret
2,060✔
1399

1400
    # Make sure that only bslash_completions is working on strings
1401
    inc_tag === :string && return Completion[], 0:-1, false
2,009✔
1402
    if inc_tag === :other
1,948✔
1403
        frange, ex, wordrange, method_name_end = identify_possible_method_completion(partial, pos)
997✔
1404
        if last(frange) != -1 && all(isspace, @view partial[wordrange]) # no last argument to complete
997✔
1405
            if ex.head === :call
140✔
1406
                return complete_methods(ex, context_module, shift), first(frange):method_name_end, false
137✔
1407
            elseif is_broadcasting_expr(ex)
3✔
1408
                return complete_methods(ex, context_module, shift), first(frange):(method_name_end - 1), false
3✔
1409
            end
1410
        end
1411
    elseif inc_tag === :comment
951✔
1412
        return Completion[], 0:-1, false
1✔
1413
    end
1414

1415
    # Check whether we can complete a keyword argument in a function call
1416
    kwarg_completion, wordrange = complete_keyword_argument(partial, pos, context_module; shift)
3,445✔
1417
    isempty(wordrange) || return kwarg_completion, wordrange, !isempty(kwarg_completion)
1,976✔
1418

1419
    startpos = nextind(string, something(findprev(in(non_identifier_chars), string, pos), 0))
2,915✔
1420
    # strip preceding ! operator
1421
    if (m = match(r"\G\!+", partial, startpos)) isa RegexMatch
1,638✔
1422
        startpos += length(m.match)
2✔
1423
    end
1424

1425
    separatorpos = something(findprev(isequal('.'), string, pos), 0)
2,488✔
1426
    namepos = max(startpos, separatorpos+1)
1,638✔
1427
    name = string[namepos:pos]
2,831✔
1428
    import_mode = get_import_mode(string)
1,638✔
1429
    if import_mode === :using_module || import_mode === :import_module
3,248✔
1430
        # Given input lines like `using Foo|`, `import Foo, Bar|` and `using Foo.Bar, Baz, |`:
1431
        # Let's look only for packages and modules we can reach from here
1432

1433
        # If there's no dot, we're in toplevel, so we should
1434
        # also search for packages
1435
        s = string[startpos:pos]
70✔
1436
        if separatorpos <= startpos
37✔
1437
            for dir in Base.load_path()
25✔
1438
                if basename(dir) in Base.project_names && isfile(dir)
29✔
1439
                    complete_loading_candidates!(suggestions, s, dir)
1✔
1440
                end
1441
                isdir(dir) || continue
56✔
1442
                for entry in _readdirx(dir)
26✔
1443
                    pname = entry.name
1,527✔
1444
                    if pname[1] != '.' && pname != "METADATA" &&
3,054✔
1445
                        pname != "REQUIRE" && startswith(pname, s)
1446
                        # Valid file paths are
1447
                        #   <Mod>.jl
1448
                        #   <Mod>/src/<Mod>.jl
1449
                        #   <Mod>.jl/src/<Mod>.jl
1450
                        if isfile(entry)
261✔
1451
                            endswith(pname, ".jl") && push!(suggestions,
×
1452
                                                            PackageCompletion(pname[1:prevind(pname, end-2)]))
1453
                        else
1454
                            mod_name = if endswith(pname, ".jl")
261✔
1455
                                pname[1:prevind(pname, end-2)]
×
1456
                            else
1457
                                pname
261✔
1458
                            end
1459
                            if isfile(joinpath(entry, "src",
522✔
1460
                                               "$mod_name.jl"))
1461
                                push!(suggestions, PackageCompletion(mod_name))
260✔
1462
                            end
1463
                        end
1464
                    end
1465
                end
1,527✔
1466
            end
28✔
1467
        end
1468
        comp_keywords = false
37✔
1469
        complete_modules_only = import_mode === :using_module # allow completion for `import Mod.name` (where `name` is not a module)
37✔
1470
    elseif import_mode === :using_name || import_mode === :import_name
3,197✔
1471
        # `using Foo: |` and `import Foo: bar, baz|`
1472
        separatorpos = findprev(isequal(':'), string, pos)::Int
5✔
1473
        comp_keywords = false
×
1474
        complete_modules_only = false
×
1475
    else
1476
        comp_keywords = !isempty(name) && startpos > separatorpos
1,596✔
1477
        complete_modules_only = false
×
1478
    end
1479

1480
    complete_identifiers!(suggestions, context_module, string, name,
1,638✔
1481
                          pos, separatorpos, startpos;
1482
                          comp_keywords, complete_modules_only, shift)
1483
    return sort!(unique!(completion_text, suggestions), by=completion_text), namepos:pos, true
1,638✔
1484
end
1485

1486
function shell_completions(string, pos, hint::Bool=false)
442✔
1487
    # First parse everything up to the current position
1488
    scs = string[1:pos]
904✔
1489
    args, last_arg_start = try
442✔
1490
        Base.shell_parse(scs, true)::Tuple{Expr,Int}
460✔
1491
    catch ex
1492
        ex isa ArgumentError || ex isa ErrorException || rethrow()
36✔
1493
        return Completion[], 0:-1, false
442✔
1494
    end
1495
    ex = args.args[end]::Expr
424✔
1496
    # Now look at the last thing we parsed
1497
    isempty(ex.args) && return Completion[], 0:-1, false
424✔
1498
    lastarg = ex.args[end]
424✔
1499
    # As Base.shell_parse throws away trailing spaces (unless they are escaped),
1500
    # we need to special case here.
1501
    # If the last char was a space, but shell_parse ignored it search on "".
1502
    if isexpr(lastarg, :incomplete) || isexpr(lastarg, :error)
846✔
1503
        partial = string[last_arg_start:pos]
4✔
1504
        ret, range = completions(partial, lastindex(partial), Main, true, hint)
4✔
1505
        range = range .+ (last_arg_start - 1)
2✔
1506
        return ret, range, true
2✔
1507
    elseif endswith(scs, ' ') && !endswith(scs, "\\ ")
422✔
1508
        r = pos+1:pos
34✔
1509
        paths, dir, success = complete_path("", use_envpath=false, shell_escape=true)
17✔
1510
        return paths, r, success
17✔
1511
    elseif all(@nospecialize(arg) -> arg isa AbstractString, ex.args)
815✔
1512
        # Join these and treat this as a path
1513
        path::String = join(ex.args)
404✔
1514
        r = last_arg_start:pos
404✔
1515

1516
        # Also try looking into the env path if the user wants to complete the first argument
1517
        use_envpath = length(args.args) < 2
404✔
1518

1519
        expanded = complete_expanduser(path, r)
404✔
1520
        was_expanded = expanded[3]
404✔
1521
        if was_expanded
404✔
1522
            path = (only(expanded[1])::PathCompletion).path
2✔
1523
            # If tab press, ispath and user expansion available, return it now
1524
            # otherwise see if we can complete the path further before returning with expanded ~
1525
            !hint && ispath(path) && return expanded::Completions
2✔
1526
        end
1527

1528
        paths, dir, success = complete_path(path, use_envpath=use_envpath, shell_escape=true, contract_user=was_expanded)
404✔
1529

1530
        if success && !isempty(dir)
404✔
1531
            let dir = do_shell_escape(dir)
98✔
1532
                # if escaping of dir matches scs prefix, remove that from the completions
1533
                # otherwise make it the whole completion
1534
                partial = string[last_arg_start:pos]
196✔
1535
                if endswith(dir, "/") && startswith(partial, dir)
98✔
1536
                    r = (last_arg_start + sizeof(dir)):pos
14✔
1537
                elseif startswith(partial, dir * "/")
86✔
1538
                    r = nextind(string, last_arg_start + sizeof(dir)):pos
82✔
1539
                else
1540
                    map!(paths, paths) do c::PathCompletion
4✔
1541
                        return PathCompletion(dir * "/" * c.path)
4✔
1542
                    end
1543
                end
1544
            end
1545
        end
1546
        # if ~ was expanded earlier and the incomplete string isn't a path
1547
        # return the path with contracted user to match what the hint shows. Otherwise expand ~
1548
        # i.e. require two tab presses to expand user
1549
        if was_expanded && !ispath(path)
404✔
1550
            map!(paths, paths) do c::PathCompletion
×
1551
                PathCompletion(contractuser(c.path))
×
1552
            end
1553
        end
1554
        return paths, r, success
404✔
1555
    end
1556
    return Completion[], 0:-1, false
1✔
1557
end
1558

1559
function __init__()
4✔
1560
    COMPLETION_WORLD[] = Base.get_world_counter()
4✔
1561
    return nothing
4✔
1562
end
1563

1564
end # module
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc