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

JuliaLang / julia / #37850

26 Jul 2024 05:48AM UTC coverage: 87.596% (+0.1%) from 87.45%
#37850

push

local

web-flow
Update ModernJuliaWorkflows URL (#55254)

Use our shiny new URL
[modernjuliaworkflows.org](https://modernjuliaworkflows.org/).
[Thanks a
lot](https://github.com/JuliaLang/julia/pull/55036#issuecomment-2211068320)
@ViralBShah!

77671 of 88670 relevant lines covered (87.6%)

16050203.59 hits per line

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

88.03
/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
181✔
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
8,439✔
29
end
30

31
struct ModuleCompletion <: Completion
32
    parent::Module
416,292✔
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)
4,925✔
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
8,583,265✔
76
        return getfield(c, :text)::String
181✔
77
    elseif name === :keyword
8,581,078✔
78
        return getfield(c, :keyword)::String
2,006✔
79
    elseif name === :path
8,573,165✔
80
        return getfield(c, :path)::String
34,626✔
81
    elseif name === :parent
8,573,165✔
82
        return getfield(c, :parent)::Module
×
83
    elseif name === :mod
15,974✔
84
        return getfield(c, :mod)::String
8,557,191✔
85
    elseif name === :package
12,007✔
86
        return getfield(c, :package)::String
3,967✔
87
    elseif name === :property
12,007✔
88
        return getfield(c, :property)::Symbol
271✔
89
    elseif name === :field
11,736✔
90
        return getfield(c, :field)::Symbol
35✔
91
    elseif name === :method
8,146✔
92
        return getfield(c, :method)::Method
4,927✔
93
    elseif name === :bslash
320✔
94
        return getfield(c, :bslash)::String
7,826✔
95
    elseif name === :text
320✔
96
        return getfield(c, :text)::String
×
97
    elseif name === :key
320✔
98
        return getfield(c, :key)::String
124✔
99
    elseif name === :kwarg
196✔
100
        return getfield(c, :kwarg)::String
97✔
101
    end
102
    return getfield(c, name)
99✔
103
end
104

105
_completion_text(c::TextCompletion) = c.text
181✔
106
_completion_text(c::KeywordCompletion) = c.keyword
1,879✔
107
_completion_text(c::KeyvalCompletion) = c.keyval
99✔
108
_completion_text(c::PathCompletion) = c.path
7,883✔
109
_completion_text(c::ModuleCompletion) = c.mod
4,683,349✔
110
_completion_text(c::PackageCompletion) = c.package
2,988✔
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,543✔
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*'='
97✔
118

119
completion_text(c) = _completion_text(c)::String
4,708,192✔
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,183,825✔
125
end
126

127
function appendmacro!(syms, macros, needle, endchar)
3,302✔
128
    for macsym in macros
3,302✔
129
        s = String(macsym)
54,628✔
130
        if endswith(s, needle)
54,628✔
131
            from = nextind(s, firstindex(s))
6,664✔
132
            to = prevind(s, sizeof(s)-sizeof(needle)+1)
3,332✔
133
            push!(syms, s[from:to]*endchar)
6,664✔
134
        end
135
    end
54,628✔
136
end
137

138
function append_filtered_mod_names!(ffunc::Function, suggestions::Vector{Completion},
1,651✔
139
                                    mod::Module, name::String, complete_internal_only::Bool)
140
    imported = usings = !complete_internal_only
1,651✔
141
    ssyms = names(mod; all=true, imported, usings)
1,651✔
142
    filter!(ffunc, ssyms)
1,651✔
143
    macros = filter(x -> startswith(String(x), "@" * name), ssyms)
1,850,002✔
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)]
414,611✔
145
    appendmacro!(syms, macros, "_str", "\"")
1,651✔
146
    appendmacro!(syms, macros, "_cmd", "`")
1,651✔
147
    for sym in syms
1,651✔
148
        push!(suggestions, ModuleCompletion(mod, sym))
416,292✔
149
    end
416,292✔
150
    return suggestions
1,651✔
151
end
152

153
# REPL Symbol Completions
154
function complete_symbol!(suggestions::Vector{Completion},
3,456✔
155
                          @nospecialize(prefix), name::String, context_module::Module;
156
                          complete_modules_only::Bool=false,
157
                          shift::Bool=false)
158
    local mod, t, val
1,728✔
159
    complete_internal_only = false
×
160
    if prefix !== nothing
1,728✔
161
        res = repl_eval_ex(prefix, context_module)
739✔
162
        res === nothing && return Completion[]
739✔
163
        if res isa Const
730✔
164
            val = res.val
708✔
165
            if isa(val, Module)
708✔
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)
46✔
174
            end
175
        else
176
            t = CC.widenconst(res)
22✔
177
        end
178
    else
179
        mod = context_module
989✔
180
    end
181

182
    if @isdefined(mod) # lookup names available within the module
1,719✔
183
        let modname = nameof(mod),
1,651✔
184
            is_main = mod===Main
185
            append_filtered_mod_names!(suggestions, mod, name, complete_internal_only) do s::Symbol
1,651✔
186
                if Base.isdeprecated(mod, s)
1,902,924✔
187
                    return false
×
188
                elseif s === modname
1,902,924✔
189
                    return false # exclude `Main.Main.Main`, etc.
2,605✔
190
                elseif complete_modules_only && !completes_module(mod, s)
1,900,319✔
191
                    return false
51,004✔
192
                elseif is_main && s === :MainInclude
1,849,315✔
193
                    return false
964✔
194
                end
195
                return true
1,848,351✔
196
            end
197
        end
198
    elseif @isdefined(val) # looking for a property of an instance
68✔
199
        try
46✔
200
            for property in propertynames(val, false)
46✔
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,719✔
213
end
214

215
completes_module(mod::Module, x::Symbol) =
51,528✔
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)
4✔
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)
17✔
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)
4,369✔
284
end
285
function do_string_escape(s)
286
    return escape_string(s, ('\"','$'))
7,258✔
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)
6✔
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,467✔
352
                if isfile(entry)
4,934✔
353
                    @lock PATH_cache_lock push!(PATH_cache, entry.name)
2,400✔
354
                    push!(this_PATH_cache, entry.name)
2,401✔
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,466✔
367
                yield() # to avoid blocking typing when -t1
1✔
368
                next_yield_time = time() + 0.01
1✔
369
            end
370
        end
2,467✔
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)
267✔
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,653✔
415
            is_dir = try isdir(entry) catch ex; ex isa Base.IOError ? false : rethrow() end
15,326✔
416
            push!(matches, is_dir ? entry.name * "/" : entry.name)
14,344✔
417
        end
418
    end
46,653✔
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,277✔
427
            end
22,554✔
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]
8,085✔
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,626✔
479
    r = reverse(s)
2,813✔
480
    i = firstindex(r)
×
481
    braces = in_comment = 0
×
482
    in_single_quotes = in_double_quotes = in_back_ticks = false
×
483
    while i <= ncodeunits(r)
64,305✔
484
        c, i = iterate(r, i)
124,360✔
485
        if c == '#' && i <= ncodeunits(r) && iterate(r, i)[1] == '='
62,180✔
486
            c, i = iterate(r, i) # consume '='
8✔
487
            new_comments = 1
×
488
            # handle #=#=#=#, by counting =# pairs
489
            while i <= ncodeunits(r) && iterate(r, i)[1] == '#'
6✔
490
                c, i = iterate(r, i) # consume '#'
6✔
491
                iterate(r, i)[1] == '=' || break
3✔
492
                c, i = iterate(r, i) # consume '='
4✔
493
                new_comments += 1
2✔
494
            end
2✔
495
            if c == '='
4✔
496
                in_comment += new_comments
3✔
497
            else
498
                in_comment -= new_comments
1✔
499
            end
500
        elseif !in_single_quotes && !in_double_quotes && !in_back_ticks && in_comment == 0
62,176✔
501
            if c == c_start
60,411✔
502
                braces += 1
958✔
503
            elseif c == c_end
59,453✔
504
                braces -= 1
270✔
505
            elseif c == '\''
59,183✔
506
                in_single_quotes = true
×
507
            elseif c == '"'
59,167✔
508
                in_double_quotes = true
×
509
            elseif c == '`'
58,889✔
510
                in_back_ticks = true
×
511
            end
512
        else
513
            if in_single_quotes &&
1,765✔
514
                c == '\'' && i <= ncodeunits(r) && iterate(r, i)[1] != '\\'
515
                in_single_quotes = false
×
516
            elseif in_double_quotes &&
1,750✔
517
                c == '"' && i <= ncodeunits(r) && iterate(r, i)[1] != '\\'
518
                in_double_quotes = false
×
519
            elseif in_back_ticks &&
1,522✔
520
                c == '`' && i <= ncodeunits(r) && iterate(r, i)[1] != '\\'
521
                in_back_ticks = false
×
522
            elseif in_comment > 0 &&
1,515✔
523
                c == '=' && i <= ncodeunits(r) && iterate(r, i)[1] == '#'
524
                # handle =#=#=#=, by counting #= pairs
525
                c, i = iterate(r, i) # consume '#'
6✔
526
                old_comments = 1
×
527
                while i <= ncodeunits(r) && iterate(r, i)[1] == '='
4✔
528
                    c, i = iterate(r, i) # consume '='
4✔
529
                    iterate(r, i)[1] == '#' || break
2✔
530
                    c, i = iterate(r, i) # consume '#'
2✔
531
                    old_comments += 1
1✔
532
                end
1✔
533
                if c == '#'
3✔
534
                    in_comment -= old_comments
2✔
535
                else
536
                    in_comment += old_comments
1✔
537
                end
538
            end
539
        end
540
        braces == 1 && break
62,180✔
541
    end
61,492✔
542
    braces != 1 && return 0:-1, -1
2,813✔
543
    method_name_end = reverseind(s, i)
1,368✔
544
    startind = nextind(s, something(findprev(in(non_identifier_chars), s, method_name_end), 0))::Int
1,066✔
545
    return (startind:lastindex(s), method_name_end)
688✔
546
end
547

548
struct REPLCacheToken end
549

550
struct REPLInterpreter <: CC.AbstractInterpreter
551
    limit_aggressive_inference::Bool
552
    world::UInt
553
    inf_params::CC.InferenceParams
554
    opt_params::CC.OptimizationParams
555
    inf_cache::Vector{CC.InferenceResult}
556
    function REPLInterpreter(limit_aggressive_inference::Bool=false;
523✔
557
                             world::UInt = Base.get_world_counter(),
558
                             inf_params::CC.InferenceParams = CC.InferenceParams(;
559
                                 aggressive_constant_propagation=true,
560
                                 unoptimize_throw_blocks=false),
561
                             opt_params::CC.OptimizationParams = CC.OptimizationParams(),
562
                             inf_cache::Vector{CC.InferenceResult} = CC.InferenceResult[])
563
        return new(limit_aggressive_inference, world, inf_params, opt_params, inf_cache)
517✔
564
    end
565
end
566
CC.InferenceParams(interp::REPLInterpreter) = interp.inf_params
436,601✔
567
CC.OptimizationParams(interp::REPLInterpreter) = interp.opt_params
×
568
CC.get_inference_world(interp::REPLInterpreter) = interp.world
196,630✔
569
CC.get_inference_cache(interp::REPLInterpreter) = interp.inf_cache
87,863✔
570
CC.cache_owner(::REPLInterpreter) = REPLCacheToken()
×
571

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

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

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

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

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

605
function is_call_graph_uncached(sv::CC.InferenceState)
540,229✔
606
    CC.is_cached(sv) && return false
742,856✔
607
    parent = sv.parent
548,509✔
608
    parent === nothing && return true
548,509✔
609
    return is_call_graph_uncached(parent::CC.InferenceState)
540,229✔
610
end
611

612
# aggressive global binding resolution within `repl_frame`
613
function CC.abstract_eval_globalref(interp::REPLInterpreter, g::GlobalRef,
614
                                    sv::CC.InferenceState)
615
    if (interp.limit_aggressive_inference ? is_repl_frame(sv) : is_call_graph_uncached(sv))
255,017✔
616
        if CC.isdefined_globalref(g)
5,589✔
617
            return CC.RTEffects(Const(ccall(:jl_get_globalref_value, Any, (Any,), g)), Union{}, CC.EFFECTS_TOTAL)
5,583✔
618
        end
619
        return CC.RTEffects(Union{}, UndefVarError, CC.EFFECTS_THROWS)
6✔
620
    end
621
    return @invoke CC.abstract_eval_globalref(interp::CC.AbstractInterpreter, g::GlobalRef,
121,986✔
622
                                              sv::CC.InferenceState)
623
end
624

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

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

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

676
# allow constant propagation for mutable constants
677
function CC.const_prop_argument_heuristic(interp::REPLInterpreter, arginfo::CC.ArgInfo, sv::CC.InferenceState)
678
    if !interp.limit_aggressive_inference
74,252✔
679
        any(@nospecialize(a)->isa(a, Const), arginfo.argtypes) && return true # even if mutable
148,704✔
680
    end
681
    return @invoke CC.const_prop_argument_heuristic(interp::CC.AbstractInterpreter, arginfo::CC.ArgInfo, sv::CC.InferenceState)
150✔
682
end
683

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

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

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

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

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

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

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

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

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

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

760
MAX_ANY_METHOD_COMPLETIONS::Int = 10
761
function recursive_explore_names!(seen::IdSet, callee_module::Module, initial_module::Module, exploredmodules::IdSet{Module}=IdSet{Module}())
62✔
762
    push!(exploredmodules, callee_module)
76✔
763
    for name in names(callee_module; all=true, imported=true)
62✔
764
        if !Base.isdeprecated(callee_module, name) && !startswith(string(name), '#') && isdefined(initial_module, name)
28,027✔
765
            func = getfield(callee_module, name)
2,200✔
766
            if !isa(func, Module)
2,200✔
767
                funct = Core.Typeof(func)
3,870✔
768
                push!(seen, funct)
2,087✔
769
            elseif isa(func, Module) && func ∉ exploredmodules
113✔
770
                recursive_explore_names!(seen, func, initial_module, exploredmodules)
48✔
771
            end
772
        end
773
    end
14,031✔
774
end
775
function recursive_explore_names(callee_module::Module, initial_module::Module)
776
    seen = IdSet{Any}()
14✔
777
    recursive_explore_names!(seen, callee_module, initial_module)
14✔
778
    seen
×
779
end
780

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

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

796
    for seen_name in recursive_explore_names(callee_module, callee_module)
28✔
797
        complete_methods!(out, seen_name, args_ex, kwargs_ex, MAX_ANY_METHOD_COMPLETIONS, false)
1,895✔
798
    end
3,776✔
799

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

810
    return out
14✔
811
end
812

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

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

861
is_broadcasting_expr(ex::Expr) = ex.head === :. && isexpr(ex.args[2], :tuple)
1,897✔
862

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

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

886
include("latex_symbols.jl")
887
include("emoji_symbols.jl")
888

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

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

901
# Aux function to detect whether we're right after a using or import keyword
902
function get_import_mode(s::String)
1,587✔
903
    # match simple cases like `using |` and `import  |`
904
    mod_import_match_simple = match(r"^\b(using|import)\s*$", s)
1,587✔
905
    if mod_import_match_simple !== nothing
1,587✔
906
        if mod_import_match_simple[1] == "using"
12✔
907
            return :using_module
4✔
908
        else
909
            return :import_module
2✔
910
        end
911
    end
912
    # match module import statements like `using Foo|`, `import Foo, Bar|` and `using Foo.Bar, Baz, |`
913
    mod_import_match = match(r"^\b(using|import)\s+([\w\.]+(?:\s*,\s*[\w\.]+)*),?\s*$", s)
1,581✔
914
    if mod_import_match !== nothing
1,581✔
915
        if mod_import_match.captures[1] == "using"
54✔
916
            return :using_module
20✔
917
        else
918
            return :import_module
7✔
919
        end
920
    end
921
    # now match explicit name import statements like `using Foo: |` and `import Foo: bar, baz|`
922
    name_import_match = match(r"^\b(using|import)\s+([\w\.]+)\s*:\s*([\w@!\s,]+)$", s)
1,554✔
923
    if name_import_match !== nothing
1,554✔
924
        if name_import_match[1] == "using"
10✔
925
            return :using_name
5✔
926
        else
927
            return :import_name
×
928
        end
929
    end
930
    return nothing
1,549✔
931
end
932

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

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

980
function dict_identifier_key(str::String, tag::Symbol, context_module::Module=Main)
2,151✔
981
    if tag === :string
2,151✔
982
        str_close = str*"\""
164✔
983
    elseif tag === :cmd
1,986✔
984
        str_close = str*"`"
5✔
985
    else
986
        str_close = str
×
987
    end
988
    frange, end_of_identifier = find_start_brace(str_close, c_start='[', c_end=']')
2,150✔
989
    isempty(frange) && return (nothing, nothing, nothing)
2,150✔
990
    objstr = str[1:end_of_identifier]
212✔
991
    objex = Meta.parse(objstr, raise=false, depwarn=false)
106✔
992
    objt = repl_eval_ex(objex, context_module)
106✔
993
    isa(objt, Core.Const) || return (nothing, nothing, nothing)
130✔
994
    obj = objt.val
82✔
995
    isa(obj, AbstractDict) || return (nothing, nothing, nothing)
83✔
996
    length(obj)::Int < 1_000_000 || return (nothing, nothing, nothing)
81✔
997
    begin_of_key = something(findnext(!isspace, str, nextind(str, end_of_identifier) + 1), # +1 for [
156✔
998
                             lastindex(str)+1)
999
    return (obj, str[begin_of_key:end], begin_of_key)
81✔
1000
end
1001

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

1012
# Identify an argument being completed in a method call. If the argument is empty, method
1013
# suggestions will be provided instead of argument completions.
1014
function identify_possible_method_completion(partial, last_idx)
2,749✔
1015
    fail = 0:-1, Expr(:nothing), 0:-1, 0
2,749✔
1016

1017
    # First, check that the last punctuation is either ',', ';' or '('
1018
    idx_last_punct = something(findprev(x -> ispunct(x) && x != '_' && x != '!', partial, last_idx), 0)::Int
19,399✔
1019
    idx_last_punct == 0 && return fail
2,749✔
1020
    last_punct = partial[idx_last_punct]
4,440✔
1021
    last_punct == ',' || last_punct == ';' || last_punct == '(' || return fail
4,229✔
1022

1023
    # Then, check that `last_punct` is only followed by an identifier or nothing
1024
    before_last_word_start = something(findprev(in(non_identifier_chars), partial, last_idx), 0)
1,506✔
1025
    before_last_word_start == 0 && return fail
753✔
1026
    all(isspace, @view partial[nextind(partial, idx_last_punct):before_last_word_start]) || return fail
857✔
1027

1028
    # Check that `last_punct` is either the last '(' or placed after a previous '('
1029
    frange, method_name_end = find_start_brace(@view partial[1:idx_last_punct])
649✔
1030
    method_name_end ∈ frange || return fail
815✔
1031

1032
    # Strip the preceding ! operators, if any, and close the expression with a ')'
1033
    s = replace(partial[frange], r"\G\!+([^=\(]+)" => s"\1"; count=1) * ')'
966✔
1034
    ex = Meta.parse(s, raise=false, depwarn=false)
483✔
1035
    isa(ex, Expr) || return fail
483✔
1036

1037
    # `wordrange` is the position of the last argument to complete
1038
    wordrange = nextind(partial, before_last_word_start):last_idx
622✔
1039
    return frange, ex, wordrange, method_name_end
483✔
1040
end
1041

1042
# Provide completion for keyword arguments in function calls
1043
function complete_keyword_argument(partial::String, last_idx::Int, context_module::Module;
3,512✔
1044
                                   shift::Bool=false)
1045
    frange, ex, wordrange, = identify_possible_method_completion(partial, last_idx)
1,756✔
1046
    fail = Completion[], 0:-1, frange
1,756✔
1047
    ex.head === :call || is_broadcasting_expr(ex) || return fail
3,344✔
1048

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

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

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

1077
    suggestions = Completion[KeywordArgumentCompletion(kwarg) for kwarg in kwargs]
216✔
1078

1079
    # Only add these if not in kwarg space. i.e. not in `foo(; `
1080
    if kwargs_flag == 0
169✔
1081
        complete_symbol!(suggestions, #=prefix=#nothing, last_word, context_module; shift)
138✔
1082
        complete_keyval!(suggestions, last_word)
138✔
1083
    end
1084

1085
    return sort!(suggestions, by=completion_text), wordrange
169✔
1086
end
1087

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

1104
function complete_loading_candidates!(suggestions::Vector{Completion}, pkgstarts::String, project_file::String)
1✔
1105
    for name in get_loading_candidates(pkgstarts, project_file)
1✔
1106
        push!(suggestions, PackageCompletion(name))
2✔
1107
    end
2✔
1108
    return suggestions
1✔
1109
end
1110

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

1216
function completions(string::String, pos::Int, context_module::Module=Main, shift::Bool=true, hint::Bool=false)
2,164✔
1217
    # First parse everything up to the current position
1218
    partial = string[1:pos]
4,722✔
1219
    inc_tag = Base.incomplete_tag(Meta.parse(partial, raise=false, depwarn=false))
2,163✔
1220

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

1251
    # if completing a key in a Dict
1252
    identifier, partial_key, loc = dict_identifier_key(partial, inc_tag, context_module)
2,229✔
1253
    if identifier !== nothing
2,149✔
1254
        matches = find_dict_matches(identifier, partial_key)
80✔
1255
        length(matches)==1 && (lastindex(string) <= pos || string[nextind(string,pos)] != ']') && (matches[1]*=']')
80✔
1256
        length(matches)>0 && return Completion[DictCompletion(identifier, match) for match in sort!(matches)], loc::Int:pos, true
80✔
1257
    end
1258

1259
    suggestions = Completion[]
2,085✔
1260

1261
    # Check if this is a var"" string macro that should be completed like
1262
    # an identifier rather than a string.
1263
    # TODO: It would be nice for the parser to give us more information here
1264
    # so that we can lookup the macro by identity rather than pattern matching
1265
    # its invocation.
1266
    varrange = findprev("var\"", string, pos)
2,085✔
1267

1268
    expanded = nothing
×
1269
    was_expanded = false
2,085✔
1270

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

1288
            expanded = complete_expanduser(scs, r)
1✔
1289
            was_expanded = expanded[3]
1✔
1290
            if was_expanded
1✔
1291
                scs = (only(expanded[1])::PathCompletion).path
×
1292
                # If tab press, ispath and user expansion available, return it now
1293
                # otherwise see if we can complete the path further before returning with expanded ~
1294
                !hint && ispath(scs) && return expanded::Completions
×
1295
            end
1296

1297
            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✔
1298
            # This expansion with "\\ "=>' ' replacement and shell_escape=true
1299
            # assumes the path isn't further quoted within the cmd backticks.
1300
            path = replace(path, r"\\ " => " ", r"\$" => "\$") # fuzzy shell_parse (reversed by shell_escape_posixly)
1✔
1301
            paths, dir, success = complete_path(path, shell_escape=true, raw_escape=true)
1✔
1302

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

1334
            expanded = complete_expanduser(scs, r)
143✔
1335
            was_expanded = expanded[3]
143✔
1336
            if was_expanded
143✔
1337
                scs = (only(expanded[1])::PathCompletion).path
2✔
1338
                # If tab press, ispath and user expansion available, return it now
1339
                # otherwise see if we can complete the path further before returning with expanded ~
1340
                !hint && ispath(scs) && return expanded::Completions
2✔
1341
            end
1342

1343
            path = try
142✔
1344
                unescape_string(replace(scs, "\\\$"=>"\$"))
261✔
1345
            catch ex
1346
                ex isa ArgumentError || rethrow()
×
1347
                nothing
142✔
1348
            end
1349
            if !isnothing(path)
284✔
1350
                paths, dir, success = complete_path(path::String, string_escape=true)
142✔
1351

1352
                if close_path_completion(dir, paths, path, pos)
142✔
1353
                    p = (paths[1]::PathCompletion).path * "\""
6✔
1354
                    hint && was_expanded && (p = contractuser(p))
6✔
1355
                    paths[1] = PathCompletion(p)
6✔
1356
                end
1357

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

1377
                # Fallthrough allowed so that Latex symbols can be completed in strings
1378
                if success
142✔
1379
                    return sort!(paths, by=p->p.path), r::UnitRange{Int}, success
26,784✔
1380
                elseif !hint && was_expanded
71✔
1381
                    # if not able to provide completions, not hinting, and ~ expansion was possible, return ~ expansion
1382
                    return expanded::Completions
1✔
1383
                end
1384
            end
1385
        end
1386
    end
1387
    # if path has ~ and we didn't find any paths to complete just return the expanded path
1388
    was_expanded && return expanded::Completions
2,008✔
1389

1390
    ok, ret = bslash_completions(string, pos)
2,008✔
1391
    ok && return ret
2,008✔
1392

1393
    # Make sure that only bslash_completions is working on strings
1394
    inc_tag === :string && return Completion[], 0:-1, false
1,957✔
1395
    if inc_tag === :other
1,896✔
1396
        frange, ex, wordrange, method_name_end = identify_possible_method_completion(partial, pos)
993✔
1397
        if last(frange) != -1 && all(isspace, @view partial[wordrange]) # no last argument to complete
993✔
1398
            if ex.head === :call
139✔
1399
                return complete_methods(ex, context_module, shift), first(frange):method_name_end, false
136✔
1400
            elseif is_broadcasting_expr(ex)
3✔
1401
                return complete_methods(ex, context_module, shift), first(frange):(method_name_end - 1), false
3✔
1402
            end
1403
        end
1404
    elseif inc_tag === :comment
903✔
1405
        return Completion[], 0:-1, false
1✔
1406
    end
1407

1408
    # Check whether we can complete a keyword argument in a function call
1409
    kwarg_completion, wordrange = complete_keyword_argument(partial, pos, context_module; shift)
3,343✔
1410
    isempty(wordrange) || return kwarg_completion, wordrange, !isempty(kwarg_completion)
1,925✔
1411

1412
    startpos = nextind(string, something(findprev(in(non_identifier_chars), string, pos), 0))
2,813✔
1413
    # strip preceding ! operator
1414
    if (m = match(r"\G\!+", partial, startpos)) isa RegexMatch
1,587✔
1415
        startpos += length(m.match)
2✔
1416
    end
1417

1418
    separatorpos = something(findprev(isequal('.'), string, pos), 0)
2,413✔
1419
    namepos = max(startpos, separatorpos+1)
1,587✔
1420
    name = string[namepos:pos]
2,776✔
1421
    import_mode = get_import_mode(string)
1,587✔
1422
    if import_mode === :using_module || import_mode === :import_module
3,150✔
1423
        # Given input lines like `using Foo|`, `import Foo, Bar|` and `using Foo.Bar, Baz, |`:
1424
        # Let's look only for packages and modules we can reach from here
1425

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

1473
    complete_identifiers!(suggestions, context_module, string, name,
1,587✔
1474
                          pos, separatorpos, startpos;
1475
                          comp_keywords, complete_modules_only, shift)
1476
    return sort!(unique!(completion_text, suggestions), by=completion_text), namepos:pos, true
1,587✔
1477
end
1478

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

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

1512
        expanded = complete_expanduser(path, r)
404✔
1513
        was_expanded = expanded[3]
404✔
1514
        if was_expanded
404✔
1515
            path = (only(expanded[1])::PathCompletion).path
2✔
1516
            # If tab press, ispath and user expansion available, return it now
1517
            # otherwise see if we can complete the path further before returning with expanded ~
1518
            !hint && ispath(path) && return expanded::Completions
2✔
1519
        end
1520

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

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

1552
function __init__()
5✔
1553
    COMPLETION_WORLD[] = Base.get_world_counter()
5✔
1554
    return nothing
5✔
1555
end
1556

1557
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