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

JuliaLang / julia / #37798

05 Jun 2024 07:57AM UTC coverage: 83.152% (-3.8%) from 86.908%
#37798

push

local

web-flow
Use the public `wait()` in the `errormonitor()` docstring (#54650)

72847 of 87607 relevant lines covered (83.15%)

14515294.14 hits per line

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

87.45
/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
183✔
21
end
22

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

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

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

36
struct PackageCompletion <: Completion
37
    package::String
264✔
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,966✔
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,051,537✔
76
        return getfield(c, :text)::String
181✔
77
    elseif name === :keyword
9,049,581✔
78
        return getfield(c, :keyword)::String
1,775✔
79
    elseif name === :path
9,042,377✔
80
        return getfield(c, :path)::String
35,023✔
81
    elseif name === :parent
9,042,377✔
82
        return getfield(c, :parent)::Module
×
83
    elseif name === :mod
15,227✔
84
        return getfield(c, :mod)::String
9,027,150✔
85
    elseif name === :package
11,960✔
86
        return getfield(c, :package)::String
3,267✔
87
    elseif name === :property
11,960✔
88
        return getfield(c, :property)::Symbol
203✔
89
    elseif name === :field
11,757✔
90
        return getfield(c, :field)::Symbol
30✔
91
    elseif name === :method
8,139✔
92
        return getfield(c, :method)::Method
4,968✔
93
    elseif name === :bslash
313✔
94
        return getfield(c, :bslash)::String
7,826✔
95
    elseif name === :text
313✔
96
        return getfield(c, :text)::String
×
97
    elseif name === :key
313✔
98
        return getfield(c, :key)::String
124✔
99
    elseif name === :kwarg
189✔
100
        return getfield(c, :kwarg)::String
97✔
101
    end
102
    return getfield(c, name)
92✔
103
end
104

105
_completion_text(c::TextCompletion) = c.text
181✔
106
_completion_text(c::KeywordCompletion) = c.keyword
1,648✔
107
_completion_text(c::KeyvalCompletion) = c.keyval
92✔
108
_completion_text(c::PathCompletion) = c.path
7,174✔
109
_completion_text(c::ModuleCompletion) = c.mod
4,737,702✔
110
_completion_text(c::PackageCompletion) = c.package
2,201✔
111
_completion_text(c::PropertyCompletion) = sprint(Base.show_sym, c.property)
203✔
112
_completion_text(c::FieldCompletion) = sprint(Base.show_sym, c.field)
30✔
113
_completion_text(c::MethodCompletion) = repr(c.method)
3,576✔
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,760,770✔
120

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

123
function completes_global(x, name)
124
    return startswith(x, name) && !('#' in x)
4,267,908✔
125
end
126

127
function appendmacro!(syms, macros, needle, endchar)
3,272✔
128
    for macsym in macros
3,272✔
129
        s = String(macsym)
65,956✔
130
        if endswith(s, needle)
65,956✔
131
            from = nextind(s, firstindex(s))
8,240✔
132
            to = prevind(s, sizeof(s)-sizeof(needle)+1)
4,120✔
133
            push!(syms, s[from:to]*endchar)
8,240✔
134
        end
135
    end
65,956✔
136
end
137

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

152
# REPL Symbol Completions
153
function complete_symbol!(suggestions::Vector{Completion},
3,426✔
154
                          @nospecialize(ex), name::String, context_module::Module;
155
                          complete_modules_only::Bool=false)
156
    mod = context_module
1,713✔
157

158
    lookup_module = true
×
159
    t = Union{}
×
160
    val = nothing
×
161
    if ex !== nothing
1,713✔
162
        res = repl_eval_ex(ex, context_module)
691✔
163
        res === nothing && return Completion[]
691✔
164
        if res isa Const
682✔
165
            val = res.val
660✔
166
            if isa(val, Module)
660✔
167
                mod = val
614✔
168
                lookup_module = true
×
169
            else
170
                lookup_module = false
×
171
                t = typeof(val)
46✔
172
            end
173
        else
174
            lookup_module = false
×
175
            t = CC.widenconst(res)
22✔
176
        end
177
    end
178

179
    if lookup_module
1,704✔
180
        let modname = nameof(mod),
1,636✔
181
            is_main = mod===Main
182
            append_filtered_mod_names!(suggestions, mod, name) do s::Symbol
1,636✔
183
                if Base.isdeprecated(mod, s)
2,476,017✔
184
                    return false
×
185
                elseif s === modname
2,476,017✔
186
                    return false # exclude `Main.Main.Main`, etc.
2,929✔
187
                elseif complete_modules_only && !completes_module(mod, s)
2,473,088✔
188
                    return false
68,882✔
189
                elseif is_main && s === :MainInclude
2,404,206✔
190
                    return false
1,293✔
191
                end
192
                return true
2,402,913✔
193
            end
194
        end
195
    elseif val !== nothing # looking for a property of an instance
68✔
196
        try
46✔
197
            for property in propertynames(val, false)
46✔
198
                # TODO: support integer arguments (#36872)
199
                if property isa Symbol && startswith(string(property), name)
83✔
200
                    push!(suggestions, PropertyCompletion(val, property))
77✔
201
                end
202
            end
83✔
203
        catch
1✔
204
        end
205
    elseif field_completion_eligible(t)
22✔
206
        # Looking for a member of a type
207
        add_field_completions!(suggestions, name, t)
13✔
208
    end
209
    return suggestions
1,704✔
210
end
211

212
completes_module(mod::Module, x::Symbol) =
69,967✔
213
    Base.isbindingresolved(mod, x) && isdefined(mod, x) && isa(getglobal(mod, x), Module)
214

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

232
const GENERIC_PROPERTYNAMES_METHOD = which(propertynames, (Any,))
233

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

245
function complete_from_list!(suggestions::Vector{Completion}, T::Type, list::Vector{String}, s::Union{String,SubString{String}})
1,324✔
246
    r = searchsorted(list, s)
1,324✔
247
    i = first(r)
1,324✔
248
    n = length(list)
1,324✔
249
    while i <= n && startswith(list[i],s)
1,522✔
250
        r = first(r):i
198✔
251
        i += 1
198✔
252
    end
198✔
253
    for kw in list[r]
1,324✔
254
        push!(suggestions, T(kw))
198✔
255
    end
198✔
256
    return suggestions
1,324✔
257
end
258

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

267
complete_keyword!(suggestions::Vector{Completion}, s::Union{String,SubString{String}}) =
593✔
268
    complete_from_list!(suggestions, KeywordCompletion, sorted_keywords, s)
269

270
const sorted_keyvals = ["false", "true"]
271

272
complete_keyval!(suggestions::Vector{Completion}, s::Union{String,SubString{String}}) =
731✔
273
    complete_from_list!(suggestions, KeyvalCompletion, sorted_keyvals, s)
274

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

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

300
# caches all reachable files in PATH dirs
301
function cache_PATH()
3✔
302
    path = get(ENV, "PATH", nothing)
6✔
303
    path isa String || return
3✔
304

305
    global next_cache_update
×
306

307
    # Calling empty! on PATH_cache would be annoying for async typing hints as completions would temporarily disappear.
308
    # 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.
309
    this_PATH_cache = Set{String}()
3✔
310

311
    @debug "caching PATH files" PATH=path
3✔
312
    pathdirs = split(path, @static Sys.iswindows() ? ";" : ":")
3✔
313

314
    next_yield_time = time() + 0.01
3✔
315

316
    t = @elapsed for pathdir in pathdirs
3✔
317
        actualpath = try
18✔
318
            realpath(pathdir)
20✔
319
        catch ex
320
            ex isa Base.IOError || rethrow()
2✔
321
            # Bash doesn't expect every folder in PATH to exist, so neither shall we
322
            continue
2✔
323
        end
324

325
        if actualpath != pathdir && in(actualpath, pathdirs)
27✔
326
            # Remove paths which (after resolving links) are in the env path twice.
327
            # Many distros eg. point /bin to /usr/bin but have both in the env path.
328
            continue
3✔
329
        end
330

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

367
    @lock PATH_cache_lock begin
3✔
368
        intersect!(PATH_cache, this_PATH_cache) # remove entries from PATH_cache that weren't found this time
3✔
369
        next_cache_update = time() + 10 # earliest next update can run is 10s after
3✔
370
    end
371

372
    @debug "caching PATH files took $t seconds" length(pathdirs) length(PATH_cache)
3✔
373
    return PATH_cache
3✔
374
end
375

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

406
    matches = Set{String}()
404✔
407
    for entry in entries
404✔
408
        if startswith(entry.name, prefix)
45,327✔
409
            is_dir = try isdir(entry) catch ex; ex isa Base.IOError ? false : rethrow() end
14,004✔
410
            push!(matches, is_dir ? entry.name * "/" : entry.name)
13,047✔
411
        end
412
    end
45,327✔
413

414
    if use_envpath && isempty(dir)
404✔
415
        # Look for files in PATH as well. These are cached in `cache_PATH` in an async task to not block typing.
416
        # If we cannot get lock because its still caching just pass over this so that typing isn't laggy.
417
        maybe_spawn_cache_PATH() # only spawns if enough time has passed and the previous caching task has completed
21✔
418
        @lock PATH_cache_lock begin
21✔
419
            for file in PATH_cache
42✔
420
                startswith(file, prefix) && push!(matches, file)
9,707✔
421
            end
19,414✔
422
        end
423
    end
424

425
    matches = ((shell_escape ? do_shell_escape(s) : string_escape ? do_string_escape(s) : s) for s in matches)
404✔
426
    matches = ((raw_escape ? do_raw_escape(s) : s) for s in matches)
×
427
    matches = Completion[PathCompletion(contract_user ? contractuser(s) : s) for s in matches]
7,376✔
428
    return matches, dir, !isempty(matches)
404✔
429
end
430

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

460
function complete_expanduser(path::AbstractString, r)
545✔
461
    expanded =
545✔
462
        try expanduser(path)
546✔
463
        catch e
464
            e isa ArgumentError || rethrow()
1✔
465
            path
546✔
466
        end
467
    return Completion[PathCompletion(expanded)], r, path != expanded
545✔
468
end
469

470
# Returns a range that includes the method name in front of the first non
471
# closed start brace from the end of the string.
472
function find_start_brace(s::AbstractString; c_start='(', c_end=')')
5,594✔
473
    r = reverse(s)
2,797✔
474
    i = firstindex(r)
×
475
    braces = in_comment = 0
×
476
    in_single_quotes = in_double_quotes = in_back_ticks = false
×
477
    while i <= ncodeunits(r)
63,945✔
478
        c, i = iterate(r, i)
123,672✔
479
        if c == '#' && i <= ncodeunits(r) && iterate(r, i)[1] == '='
61,836✔
480
            c, i = iterate(r, i) # consume '='
8✔
481
            new_comments = 1
×
482
            # handle #=#=#=#, by counting =# pairs
483
            while i <= ncodeunits(r) && iterate(r, i)[1] == '#'
6✔
484
                c, i = iterate(r, i) # consume '#'
6✔
485
                iterate(r, i)[1] == '=' || break
3✔
486
                c, i = iterate(r, i) # consume '='
4✔
487
                new_comments += 1
2✔
488
            end
2✔
489
            if c == '='
4✔
490
                in_comment += new_comments
3✔
491
            else
492
                in_comment -= new_comments
1✔
493
            end
494
        elseif !in_single_quotes && !in_double_quotes && !in_back_ticks && in_comment == 0
61,832✔
495
            if c == c_start
60,067✔
496
                braces += 1
958✔
497
            elseif c == c_end
59,109✔
498
                braces -= 1
270✔
499
            elseif c == '\''
58,839✔
500
                in_single_quotes = true
×
501
            elseif c == '"'
58,823✔
502
                in_double_quotes = true
×
503
            elseif c == '`'
58,545✔
504
                in_back_ticks = true
×
505
            end
506
        else
507
            if in_single_quotes &&
1,765✔
508
                c == '\'' && i <= ncodeunits(r) && iterate(r, i)[1] != '\\'
509
                in_single_quotes = false
×
510
            elseif in_double_quotes &&
1,750✔
511
                c == '"' && i <= ncodeunits(r) && iterate(r, i)[1] != '\\'
512
                in_double_quotes = false
×
513
            elseif in_back_ticks &&
1,522✔
514
                c == '`' && i <= ncodeunits(r) && iterate(r, i)[1] != '\\'
515
                in_back_ticks = false
×
516
            elseif in_comment > 0 &&
1,515✔
517
                c == '=' && i <= ncodeunits(r) && iterate(r, i)[1] == '#'
518
                # handle =#=#=#=, by counting #= pairs
519
                c, i = iterate(r, i) # consume '#'
6✔
520
                old_comments = 1
×
521
                while i <= ncodeunits(r) && iterate(r, i)[1] == '='
4✔
522
                    c, i = iterate(r, i) # consume '='
4✔
523
                    iterate(r, i)[1] == '#' || break
2✔
524
                    c, i = iterate(r, i) # consume '#'
2✔
525
                    old_comments += 1
1✔
526
                end
1✔
527
                if c == '#'
3✔
528
                    in_comment -= old_comments
2✔
529
                else
530
                    in_comment += old_comments
1✔
531
                end
532
            end
533
        end
534
        braces == 1 && break
61,836✔
535
    end
61,148✔
536
    braces != 1 && return 0:-1, -1
2,797✔
537
    method_name_end = reverseind(s, i)
1,368✔
538
    startind = nextind(s, something(findprev(in(non_identifier_chars), s, method_name_end), 0))::Int
1,066✔
539
    return (startind:lastindex(s), method_name_end)
688✔
540
end
541

542
struct REPLCacheToken end
543

544
struct REPLInterpreter <: CC.AbstractInterpreter
545
    limit_aggressive_inference::Bool
546
    world::UInt
547
    inf_params::CC.InferenceParams
548
    opt_params::CC.OptimizationParams
549
    inf_cache::Vector{CC.InferenceResult}
550
    function REPLInterpreter(limit_aggressive_inference::Bool=false;
513✔
551
                             world::UInt = Base.get_world_counter(),
552
                             inf_params::CC.InferenceParams = CC.InferenceParams(;
553
                                 aggressive_constant_propagation=true,
554
                                 unoptimize_throw_blocks=false),
555
                             opt_params::CC.OptimizationParams = CC.OptimizationParams(),
556
                             inf_cache::Vector{CC.InferenceResult} = CC.InferenceResult[])
557
        return new(limit_aggressive_inference, world, inf_params, opt_params, inf_cache)
507✔
558
    end
559
end
560
CC.InferenceParams(interp::REPLInterpreter) = interp.inf_params
350,824✔
561
CC.OptimizationParams(interp::REPLInterpreter) = interp.opt_params
×
562
CC.get_inference_world(interp::REPLInterpreter) = interp.world
152,773✔
563
CC.get_inference_cache(interp::REPLInterpreter) = interp.inf_cache
70,740✔
564
CC.cache_owner(::REPLInterpreter) = REPLCacheToken()
×
565

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

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

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

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

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

599
function is_call_graph_uncached(sv::CC.InferenceState)
425,466✔
600
    CC.is_cached(sv) && return false
588,498✔
601
    parent = sv.parent
433,695✔
602
    parent === nothing && return true
433,695✔
603
    return is_call_graph_uncached(parent::CC.InferenceState)
425,466✔
604
end
605

606
# aggressive global binding resolution within `repl_frame`
607
function CC.abstract_eval_globalref(interp::REPLInterpreter, g::GlobalRef,
608
                                    sv::CC.InferenceState)
609
    if (interp.limit_aggressive_inference ? is_repl_frame(sv) : is_call_graph_uncached(sv))
205,119✔
610
        if CC.isdefined_globalref(g)
5,557✔
611
            return CC.RTEffects(Const(ccall(:jl_get_globalref_value, Any, (Any,), g)), Union{}, CC.EFFECTS_TOTAL)
5,551✔
612
        end
613
        return CC.RTEffects(Union{}, UndefVarError, CC.EFFECTS_THROWS)
6✔
614
    end
615
    return @invoke CC.abstract_eval_globalref(interp::CC.AbstractInterpreter, g::GlobalRef,
97,069✔
616
                                              sv::CC.InferenceState)
617
end
618

619
function is_repl_frame_getproperty(sv::CC.InferenceState)
1✔
620
    def = sv.linfo.def
1✔
621
    def isa Method || return false
1✔
622
    def.name === :getproperty || return false
1✔
623
    CC.is_cached(sv) && return false
1✔
624
    return is_repl_frame(sv.parent)
1✔
625
end
626

627
# aggressive global binding resolution for `getproperty(::Module, ::Symbol)` calls within `repl_frame`
628
function CC.builtin_tfunction(interp::REPLInterpreter, @nospecialize(f),
30,340✔
629
                              argtypes::Vector{Any}, sv::CC.InferenceState)
630
    if f === Core.getglobal && (interp.limit_aggressive_inference ? is_repl_frame_getproperty(sv) : is_call_graph_uncached(sv))
30,414✔
631
        if length(argtypes) == 2
49✔
632
            a1, a2 = argtypes
49✔
633
            if isa(a1, Const) && isa(a2, Const)
49✔
634
                a1val, a2val = a1.val, a2.val
49✔
635
                if isa(a1val, Module) && isa(a2val, Symbol)
49✔
636
                    g = GlobalRef(a1val, a2val)
49✔
637
                    if CC.isdefined_globalref(g)
49✔
638
                        return Const(ccall(:jl_get_globalref_value, Any, (Any,), g))
49✔
639
                    end
640
                    return Union{}
×
641
                end
642
            end
643
        end
644
    end
645
    return @invoke CC.builtin_tfunction(interp::CC.AbstractInterpreter, f::Any,
30,291✔
646
                                        argtypes::Vector{Any}, sv::CC.InferenceState)
647
end
648

649
# aggressive concrete evaluation for `:inconsistent` frames within `repl_frame`
650
function CC.concrete_eval_eligible(interp::REPLInterpreter, @nospecialize(f),
60,522✔
651
                                   result::CC.MethodCallResult, arginfo::CC.ArgInfo,
652
                                   sv::CC.InferenceState)
653
    if (interp.limit_aggressive_inference ? is_repl_frame(sv) : is_call_graph_uncached(sv))
120,987✔
654
        neweffects = CC.Effects(result.effects; consistent=CC.ALWAYS_TRUE)
2,633✔
655
        result = CC.MethodCallResult(result.rt, result.exct, result.edgecycle, result.edgelimited,
5,264✔
656
                                     result.edge, neweffects)
657
    end
658
    ret = @invoke CC.concrete_eval_eligible(interp::CC.AbstractInterpreter, f::Any,
121,044✔
659
                                            result::CC.MethodCallResult, arginfo::CC.ArgInfo,
660
                                            sv::CC.InferenceState)
661
    if ret === :semi_concrete_eval
60,522✔
662
        # while the base eligibility check probably won't permit semi-concrete evaluation
663
        # for `REPLInterpreter` (given it completely turns off optimization),
664
        # this ensures we don't inadvertently enter irinterp
665
        ret = :none
×
666
    end
667
    return ret
60,522✔
668
end
669

670
# allow constant propagation for mutable constants
671
function CC.const_prop_argument_heuristic(interp::REPLInterpreter, arginfo::CC.ArgInfo, sv::CC.InferenceState)
672
    if !interp.limit_aggressive_inference
59,794✔
673
        any(@nospecialize(a)->isa(a, Const), arginfo.argtypes) && return true # even if mutable
119,575✔
674
    end
675
    return @invoke CC.const_prop_argument_heuristic(interp::CC.AbstractInterpreter, arginfo::CC.ArgInfo, sv::CC.InferenceState)
99✔
676
end
677

678
function resolve_toplevel_symbols!(src::Core.CodeInfo, mod::Module)
679
    @ccall jl_resolve_globals_in_ir(
501✔
680
        #=jl_array_t *stmts=# src.code::Any,
681
        #=jl_module_t *m=# mod::Any,
682
        #=jl_svec_t *sparam_vals=# Core.svec()::Any,
683
        #=int binding_effects=# 0::Int)::Cvoid
684
    return src
×
685
end
686

687
# lower `ex` and run type inference on the resulting top-level expression
688
function repl_eval_ex(@nospecialize(ex), context_module::Module; limit_aggressive_inference::Bool=false)
2,648✔
689
    if (isexpr(ex, :toplevel) || isexpr(ex, :tuple)) && !isempty(ex.args)
2,647✔
690
        # get the inference result for the last expression
691
        ex = ex.args[end]
3✔
692
    end
693
    lwr = try
1,324✔
694
        Meta.lower(context_module, ex)
1,324✔
695
    catch # macro expansion failed, etc.
696
        return nothing
5✔
697
    end
698
    if lwr isa Symbol
1,319✔
699
        return isdefined(context_module, lwr) ? Const(getfield(context_module, lwr)) : nothing
686✔
700
    end
701
    lwr isa Expr || return Const(lwr) # `ex` is literal
735✔
702
    isexpr(lwr, :thunk) || return nothing # lowered to `Expr(:error, ...)` or similar
561✔
703
    src = lwr.args[1]::Core.CodeInfo
501✔
704

705
    resolve_toplevel_symbols!(src, context_module)
501✔
706
    # construct top-level `MethodInstance`
707
    mi = ccall(:jl_method_instance_for_thunk, Ref{Core.MethodInstance}, (Any, Any), src, context_module)
501✔
708

709
    interp = REPLInterpreter(limit_aggressive_inference)
501✔
710
    result = CC.InferenceResult(mi)
501✔
711
    frame = CC.InferenceState(result, src, #=cache=#:no, interp)
501✔
712

713
    # NOTE Use the fixed world here to make `REPLInterpreter` robust against
714
    #      potential invalidations of `Core.Compiler` methods.
715
    Base.invoke_in_world(COMPLETION_WORLD[], CC.typeinf, interp, frame)
501✔
716

717
    result = frame.result.result
501✔
718
    result === Union{} && return nothing # for whatever reason, callers expect this as the Bottom and/or Top type instead
501✔
719
    return result
492✔
720
end
721

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

729
# Generate code cache for `REPLInterpreter` now:
730
# This code cache will be available at the world of `COMPLETION_WORLD`,
731
# assuming no invalidation will happen before initializing REPL.
732
# Once REPL is loaded, `REPLInterpreter` will be resilient against future invalidations.
733
code_typed(CC.typeinf, (REPLInterpreter, CC.InferenceState))
734

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

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

754
MAX_ANY_METHOD_COMPLETIONS::Int = 10
755
function recursive_explore_names!(seen::IdSet, callee_module::Module, initial_module::Module, exploredmodules::IdSet{Module}=IdSet{Module}())
74✔
756
    push!(exploredmodules, callee_module)
88✔
757
    for name in names(callee_module; all=true, imported=true)
74✔
758
        if !Base.isdeprecated(callee_module, name) && !startswith(string(name), '#') && isdefined(initial_module, name)
28,157✔
759
            func = getfield(callee_module, name)
2,250✔
760
            if !isa(func, Module)
2,250✔
761
                funct = Core.Typeof(func)
3,924✔
762
                push!(seen, funct)
2,113✔
763
            elseif isa(func, Module) && func ∉ exploredmodules
137✔
764
                recursive_explore_names!(seen, func, initial_module, exploredmodules)
60✔
765
            end
766
        end
767
    end
14,096✔
768
end
769
function recursive_explore_names(callee_module::Module, initial_module::Module)
770
    seen = IdSet{Any}()
14✔
771
    recursive_explore_names!(seen, callee_module, initial_module)
14✔
772
    seen
×
773
end
774

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

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

790
    for seen_name in recursive_explore_names(callee_module, callee_module)
28✔
791
        complete_methods!(out, seen_name, args_ex, kwargs_ex, MAX_ANY_METHOD_COMPLETIONS, false)
1,917✔
792
    end
3,820✔
793

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

804
    return out
14✔
805
end
806

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

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

855
is_broadcasting_expr(ex::Expr) = ex.head === :. && isexpr(ex.args[2], :tuple)
1,882✔
856

857
function complete_methods_args(ex::Expr, context_module::Module, default_any::Bool, allow_broadcasting::Bool)
858
    if allow_broadcasting && is_broadcasting_expr(ex)
306✔
859
        return detect_args_kwargs((ex.args[2]::Expr).args, context_module, default_any, true)
6✔
860
    end
861
    return detect_args_kwargs(ex.args, context_module, default_any, false)
314✔
862
end
863

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

880
include("latex_symbols.jl")
881
include("emoji_symbols.jl")
882

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

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

895
# Aux function to detect whether we're right after a
896
# using or import keyword
897
function afterusing(string::String, startpos::Int)
1,572✔
898
    (isempty(string) || startpos == 0) && return false
1,572✔
899
    str = string[1:prevind(string,startpos)]
2,786✔
900
    isempty(str) && return false
1,569✔
901
    rstr = reverse(str)
1,217✔
902
    r = findfirst(r"\s(gnisu|tropmi)\b", rstr)
1,217✔
903
    r === nothing && return false
1,217✔
904
    fr = reverseind(str, last(r))
112✔
905
    return occursin(r"^\b(using|import)\s*((\w+[.])*\w+\s*,\s*)*$", str[fr:end])
56✔
906
end
907

908
function close_path_completion(dir, paths, str, pos)
142✔
909
    length(paths) == 1 || return false  # Only close if there's a single choice...
268✔
910
    path = (paths[1]::PathCompletion).path
16✔
911
    path = unescape_string(replace(path, "\\\$"=>"\$"))
32✔
912
    path = joinpath(dir, path)
16✔
913
    # ...except if it's a directory...
914
    try
16✔
915
        isdir(path)
17✔
916
    catch e
917
        e isa Base.IOError || rethrow() # `path` cannot be determined to be a file
17✔
918
    end && return false
919
    # ...and except if there's already a " at the cursor.
920
    return lastindex(str) <= pos || str[nextind(str, pos)] != '"'
6✔
921
end
922

923
function bslash_completions(string::String, pos::Int, hint::Bool=false)
2,001✔
924
    slashpos = something(findprev(isequal('\\'), string, pos), 0)
4,060✔
925
    if (something(findprev(in(bslash_separators), string, pos), 0) < slashpos &&
2,029✔
926
        !(1 < slashpos && (string[prevind(string, slashpos)]=='\\')))
927
        # latex / emoji symbol substitution
928
        s = string[slashpos:pos]
112✔
929
        latex = get(latex_symbols, s, "")
78✔
930
        if !isempty(latex) # complete an exact match
56✔
931
            return (true, (Completion[BslashCompletion(latex)], slashpos:pos, true))
22✔
932
        elseif occursin(subscript_regex, s)
34✔
933
            sub = map(c -> subscripts[c], s[3:end])
37✔
934
            return (true, (Completion[BslashCompletion(sub)], slashpos:pos, true))
6✔
935
        elseif occursin(superscript_regex, s)
28✔
936
            sup = map(c -> superscripts[c], s[3:end])
8✔
937
            return (true, (Completion[BslashCompletion(sup)], slashpos:pos, true))
1✔
938
        end
939
        emoji = get(emoji_symbols, s, "")
29✔
940
        if !isempty(emoji)
27✔
941
            return (true, (Completion[BslashCompletion(emoji)], slashpos:pos, true))
2✔
942
        end
943
        # return possible matches; these cannot be mixed with regular
944
        # Julian completions as only latex / emoji symbols contain the leading \
945
        if startswith(s, "\\:") # emoji
25✔
946
            namelist = Iterators.filter(k -> startswith(k, s), keys(emoji_symbols))
1,243✔
947
        else # latex
948
            namelist = Iterators.filter(k -> startswith(k, s), keys(latex_symbols))
61,176✔
949
        end
950
        return (true, (Completion[BslashCompletion(name) for name in sort!(collect(namelist))], slashpos:pos, true))
25✔
951
    end
952
    return (false, (Completion[], 0:-1, false))
1,945✔
953
end
954

955
function dict_identifier_key(str::String, tag::Symbol, context_module::Module=Main)
2,136✔
956
    if tag === :string
2,136✔
957
        str_close = str*"\""
164✔
958
    elseif tag === :cmd
1,971✔
959
        str_close = str*"`"
5✔
960
    else
961
        str_close = str
×
962
    end
963
    frange, end_of_identifier = find_start_brace(str_close, c_start='[', c_end=']')
2,135✔
964
    isempty(frange) && return (nothing, nothing, nothing)
2,135✔
965
    objstr = str[1:end_of_identifier]
212✔
966
    objex = Meta.parse(objstr, raise=false, depwarn=false)
106✔
967
    objt = repl_eval_ex(objex, context_module)
106✔
968
    isa(objt, Core.Const) || return (nothing, nothing, nothing)
130✔
969
    obj = objt.val
82✔
970
    isa(obj, AbstractDict) || return (nothing, nothing, nothing)
83✔
971
    length(obj)::Int < 1_000_000 || return (nothing, nothing, nothing)
81✔
972
    begin_of_key = something(findnext(!isspace, str, nextind(str, end_of_identifier) + 1), # +1 for [
156✔
973
                             lastindex(str)+1)
974
    return (obj, str[begin_of_key:end], begin_of_key)
81✔
975
end
976

977
# This needs to be a separate non-inlined function, see #19441
978
@noinline function find_dict_matches(identifier::AbstractDict, partial_key)
80✔
979
    matches = String[]
80✔
980
    for key in keys(identifier)
122✔
981
        rkey = repr(key)
933✔
982
        startswith(rkey,partial_key) && push!(matches,rkey)
933✔
983
    end
1,410✔
984
    return matches
80✔
985
end
986

987
# Identify an argument being completed in a method call. If the argument is empty, method
988
# suggestions will be provided instead of argument completions.
989
function identify_possible_method_completion(partial, last_idx)
2,733✔
990
    fail = 0:-1, Expr(:nothing), 0:-1, 0
2,733✔
991

992
    # First, check that the last punctuation is either ',', ';' or '('
993
    idx_last_punct = something(findprev(x -> ispunct(x) && x != '_' && x != '!', partial, last_idx), 0)::Int
19,311✔
994
    idx_last_punct == 0 && return fail
2,733✔
995
    last_punct = partial[idx_last_punct]
4,416✔
996
    last_punct == ',' || last_punct == ';' || last_punct == '(' || return fail
4,206✔
997

998
    # Then, check that `last_punct` is only followed by an identifier or nothing
999
    before_last_word_start = something(findprev(in(non_identifier_chars), partial, last_idx), 0)
1,504✔
1000
    before_last_word_start == 0 && return fail
752✔
1001
    all(isspace, @view partial[nextind(partial, idx_last_punct):before_last_word_start]) || return fail
856✔
1002

1003
    # Check that `last_punct` is either the last '(' or placed after a previous '('
1004
    frange, method_name_end = find_start_brace(@view partial[1:idx_last_punct])
648✔
1005
    method_name_end ∈ frange || return fail
813✔
1006

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

1012
    # `wordrange` is the position of the last argument to complete
1013
    wordrange = nextind(partial, before_last_word_start):last_idx
622✔
1014
    return frange, ex, wordrange, method_name_end
483✔
1015
end
1016

1017
# Provide completion for keyword arguments in function calls
1018
function complete_keyword_argument(partial, last_idx, context_module)
1,741✔
1019
    frange, ex, wordrange, = identify_possible_method_completion(partial, last_idx)
1,741✔
1020
    fail = Completion[], 0:-1, frange
1,741✔
1021
    ex.head === :call || is_broadcasting_expr(ex) || return fail
3,314✔
1022

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

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

1031
    # For each method corresponding to the function call, provide completion suggestions
1032
    # for each keyword that starts like the last word and that is not already used
1033
    # previously in the expression. The corresponding suggestion is "kwname=".
1034
    # If the keyword corresponds to an existing name, also include "kwname" as a suggestion
1035
    # since the syntax "foo(; kwname)" is equivalent to "foo(; kwname=kwname)".
1036
    last_word = partial[wordrange] # the word to complete
338✔
1037
    kwargs = Set{String}()
169✔
1038
    for m in methods
169✔
1039
        m::MethodCompletion
1,380✔
1040
        possible_kwargs = Base.kwarg_decl(m.method)
1,380✔
1041
        current_kwarg_candidates = String[]
1,380✔
1042
        for _kw in possible_kwargs
1,380✔
1043
            kw = String(_kw)
478✔
1044
            if !endswith(kw, "...") && startswith(kw, last_word) && _kw ∉ kwargs_ex
511✔
1045
                push!(current_kwarg_candidates, kw)
67✔
1046
            end
1047
        end
478✔
1048
        union!(kwargs, current_kwarg_candidates)
1,380✔
1049
    end
1,380✔
1050

1051
    suggestions = Completion[KeywordArgumentCompletion(kwarg) for kwarg in kwargs]
216✔
1052

1053
    # Only add these if not in kwarg space. i.e. not in `foo(; `
1054
    if kwargs_flag == 0
169✔
1055
        complete_symbol!(suggestions, nothing, last_word, context_module)
138✔
1056
        complete_keyval!(suggestions, last_word)
138✔
1057
    end
1058

1059
    return sort!(suggestions, by=completion_text), wordrange
169✔
1060
end
1061

1062
function get_loading_candidates(pkgstarts::String, project_file::String)
1✔
1063
    loading_candidates = String[]
1✔
1064
    d = Base.parsed_toml(project_file)
1✔
1065
    pkg = get(d, "name", nothing)::Union{String, Nothing}
2✔
1066
    if pkg !== nothing && startswith(pkg, pkgstarts)
1✔
1067
        push!(loading_candidates, pkg)
1✔
1068
    end
1069
    deps = get(d, "deps", nothing)::Union{Dict{String, Any}, Nothing}
2✔
1070
    if deps !== nothing
1✔
1071
        for (pkg, _) in deps
2✔
1072
            startswith(pkg, pkgstarts) && push!(loading_candidates, pkg)
1✔
1073
        end
1✔
1074
    end
1075
    return loading_candidates
1✔
1076
end
1077

1078
function complete_loading_candidates!(suggestions::Vector{Completion}, pkgstarts::String, project_file::String)
1✔
1079
    for name in get_loading_candidates(pkgstarts, project_file)
1✔
1080
        push!(suggestions, PackageCompletion(name))
2✔
1081
    end
2✔
1082
    return suggestions
1✔
1083
end
1084

1085
function complete_identifiers!(suggestions::Vector{Completion},
3,150✔
1086
                               context_module::Module, string::String, name::String,
1087
                               pos::Int, dotpos::Int, startpos::Int;
1088
                               comp_keywords::Bool=false,
1089
                               complete_modules_only::Bool=false)
1090
    ex = nothing
×
1091
    if comp_keywords
1,575✔
1092
        complete_keyword!(suggestions, name)
593✔
1093
        complete_keyval!(suggestions, name)
593✔
1094
    end
1095
    if dotpos > 1 && string[dotpos] == '.'
2,753✔
1096
        s = string[1:prevind(string, dotpos)]
1,384✔
1097
        # First see if the whole string up to `pos` is a valid expression. If so, use it.
1098
        ex = Meta.parse(s, raise=false, depwarn=false)
692✔
1099
        if isexpr(ex, :incomplete)
692✔
1100
            s = string[startpos:pos]
1,092✔
1101
            # Heuristic to find the start of the expression. TODO: This would be better
1102
            # done with a proper error-recovering parser.
1103
            if 0 < startpos <= lastindex(string) && string[startpos] == '.'
1,092✔
1104
                i = prevind(string, startpos)
2✔
1105
                while 0 < i
2✔
1106
                    c = string[i]
4✔
1107
                    if c in (')', ']')
4✔
1108
                        if c == ')'
×
1109
                            c_start = '('
×
1110
                            c_end = ')'
×
1111
                        elseif c == ']'
×
1112
                            c_start = '['
×
1113
                            c_end = ']'
×
1114
                        end
1115
                        frange, end_of_identifier = find_start_brace(string[1:prevind(string, i)], c_start=c_start, c_end=c_end)
×
1116
                        isempty(frange) && break # unbalanced parens
×
1117
                        startpos = first(frange)
×
1118
                        i = prevind(string, startpos)
×
1119
                    elseif c in ('\'', '\"', '\`')
4✔
1120
                        s = "$c$c"*string[startpos:pos]
×
1121
                        break
×
1122
                    else
1123
                        break
×
1124
                    end
1125
                    s = string[startpos:pos]
×
1126
                end
×
1127
            end
1128
            if something(findlast(in(non_identifier_chars), s), 0) < something(findlast(isequal('.'), s), 0)
1,092✔
1129
                lookup_name, name = rsplit(s, ".", limit=2)
546✔
1130
                name = String(name)
546✔
1131
                ex = Meta.parse(lookup_name, raise=false, depwarn=false)
546✔
1132
            end
1133
            isexpr(ex, :incomplete) && (ex = nothing)
546✔
1134
        elseif isexpr(ex, (:using, :import))
146✔
1135
            arglast = ex.args[end] # focus on completion to the last argument
8✔
1136
            if isexpr(arglast, :.)
8✔
1137
                # We come here for cases like:
1138
                # - `string`: "using Mod1.Mod2.M"
1139
                # - `ex`: :(using Mod1.Mod2)
1140
                # - `name`: "M"
1141
                # Now we transform `ex` to `:(Mod1.Mod2)` to allow `complete_symbol!` to
1142
                # complete for inner modules whose name starts with `M`.
1143
                # Note that `complete_modules_only=true` is set within `completions`
1144
                ex = nothing
8✔
1145
                firstdot = true
×
1146
                for arg = arglast.args
8✔
1147
                    if arg === :.
17✔
1148
                        # override `context_module` if multiple `.` accessors are used
1149
                        if firstdot
8✔
1150
                            firstdot = false
×
1151
                        else
1152
                            context_module = parentmodule(context_module)
3✔
1153
                        end
1154
                    elseif arg isa Symbol
9✔
1155
                        if ex === nothing
9✔
1156
                            ex = arg
8✔
1157
                        else
1158
                            ex = Expr(:., ex, QuoteNode(arg))
1✔
1159
                        end
1160
                    else # invalid expression
1161
                        ex = nothing
×
1162
                        break
×
1163
                    end
1164
                end
17✔
1165
            end
1166
        elseif isexpr(ex, :call) && length(ex.args) > 1
138✔
1167
            isinfix = s[end] != ')'
37✔
1168
            # A complete call expression that does not finish with ')' is an infix call.
1169
            if !isinfix
19✔
1170
                # Handle infix call argument completion of the form bar + foo(qux).
1171
                frange, end_of_identifier = find_start_brace(@view s[1:prevind(s, end)])
28✔
1172
                isinfix = Meta.parse(@view(s[frange[1]:end]), raise=false, depwarn=false) == ex.args[end]
14✔
1173
            end
1174
            if isinfix
19✔
1175
                ex = ex.args[end]
8✔
1176
            end
1177
        elseif isexpr(ex, :macrocall) && length(ex.args) > 1
119✔
1178
            # allow symbol completions within potentially incomplete macrocalls
1179
            if s[end] ≠ '`' && s[end] ≠ ')'
43✔
1180
                ex = ex.args[end]
12✔
1181
            end
1182
        end
1183
    end
1184
    complete_symbol!(suggestions, ex, name, context_module; complete_modules_only)
1,575✔
1185
    return sort!(unique(suggestions), by=completion_text), (dotpos+1):pos, true
1,575✔
1186
end
1187

1188
function completions(string::String, pos::Int, context_module::Module=Main, shift::Bool=true, hint::Bool=false)
2,149✔
1189
    # First parse everything up to the current position
1190
    partial = string[1:pos]
4,677✔
1191
    inc_tag = Base.incomplete_tag(Meta.parse(partial, raise=false, depwarn=false))
2,148✔
1192

1193
    # ?(x, y)TAB lists methods you can call with these objects
1194
    # ?(x, y TAB lists methods that take these objects as the first two arguments
1195
    # MyModule.?(x, y)TAB restricts the search to names in MyModule
1196
    rexm = match(r"(\w+\.|)\?\((.*)$", partial)
2,148✔
1197
    if rexm !== nothing
2,148✔
1198
        # Get the module scope
1199
        if isempty(rexm.captures[1])
28✔
1200
            callee_module = context_module
×
1201
        else
1202
            modname = Symbol(rexm.captures[1][1:end-1])
13✔
1203
            if isdefined(context_module, modname)
13✔
1204
                callee_module = getfield(context_module, modname)
13✔
1205
                if !isa(callee_module, Module)
13✔
1206
                    callee_module = context_module
×
1207
                end
1208
            else
1209
                callee_module = context_module
×
1210
            end
1211
        end
1212
        moreargs = !endswith(rexm.captures[2], ')')
14✔
1213
        callstr = "_(" * rexm.captures[2]
14✔
1214
        if moreargs
14✔
1215
            callstr *= ')'
8✔
1216
        end
1217
        ex_org = Meta.parse(callstr, raise=false, depwarn=false)
14✔
1218
        if isa(ex_org, Expr)
14✔
1219
            return complete_any_methods(ex_org, callee_module::Module, context_module, moreargs, shift), (0:length(rexm.captures[1])+1) .+ rexm.offset, false
14✔
1220
        end
1221
    end
1222

1223
    # if completing a key in a Dict
1224
    identifier, partial_key, loc = dict_identifier_key(partial, inc_tag, context_module)
2,214✔
1225
    if identifier !== nothing
2,134✔
1226
        matches = find_dict_matches(identifier, partial_key)
80✔
1227
        length(matches)==1 && (lastindex(string) <= pos || string[nextind(string,pos)] != ']') && (matches[1]*=']')
80✔
1228
        length(matches)>0 && return Completion[DictCompletion(identifier, match) for match in sort!(matches)], loc::Int:pos, true
80✔
1229
    end
1230

1231
    suggestions = Completion[]
2,070✔
1232

1233
    # Check if this is a var"" string macro that should be completed like
1234
    # an identifier rather than a string.
1235
    # TODO: It would be nice for the parser to give us more information here
1236
    # so that we can lookup the macro by identity rather than pattern matching
1237
    # its invocation.
1238
    varrange = findprev("var\"", string, pos)
2,070✔
1239

1240
    expanded = nothing
×
1241
    was_expanded = false
2,070✔
1242

1243
    if varrange !== nothing
2,070✔
1244
        ok, ret = bslash_completions(string, pos)
3✔
1245
        ok && return ret
3✔
1246
        startpos = first(varrange) + 4
3✔
1247
        dotpos = something(findprev(isequal('.'), string, first(varrange)-1), 0)
5✔
1248
        name = string[startpos:pos]
5✔
1249
        return complete_identifiers!(Completion[], context_module, string, name, pos,
3✔
1250
                                     dotpos, startpos)
1251
    elseif inc_tag === :cmd
2,067✔
1252
        # TODO: should this call shell_completions instead of partially reimplementing it?
1253
        let m = match(r"[\t\n\r\"`><=*?|]| (?!\\)", reverse(partial)) # fuzzy shell_parse in reverse
1✔
1254
            startpos = nextind(partial, reverseind(partial, m.offset))
2✔
1255
            r = startpos:pos
1✔
1256
            scs::String = string[r]
2✔
1257

1258
            expanded = complete_expanduser(scs, r)
1✔
1259
            was_expanded = expanded[3]
1✔
1260
            if was_expanded
1✔
1261
                scs = (only(expanded[1])::PathCompletion).path
×
1262
                # If tab press, ispath and user expansion available, return it now
1263
                # otherwise see if we can complete the path further before returning with expanded ~
1264
                !hint && ispath(scs) && return expanded::Completions
×
1265
            end
1266

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

1273
            if success && !isempty(dir)
1✔
1274
                let dir = do_raw_escape(do_shell_escape(dir))
×
1275
                    # if escaping of dir matches scs prefix, remove that from the completions
1276
                    # otherwise make it the whole completion
1277
                    if endswith(dir, "/") && startswith(scs, dir)
×
1278
                        r = (startpos + sizeof(dir)):pos
×
1279
                    elseif startswith(scs, dir * "/")
×
1280
                        r = nextind(string, startpos + sizeof(dir)):pos
×
1281
                    else
1282
                        map!(paths, paths) do c::PathCompletion
×
1283
                            p = dir * "/" * c.path
×
1284
                            was_expanded && (p = contractuser(p))
×
1285
                            return PathCompletion(p)
×
1286
                        end
1287
                    end
1288
                end
1289
            end
1290
            if isempty(paths) && !hint && was_expanded
1✔
1291
                # if not able to provide completions, not hinting, and ~ expansion was possible, return ~ expansion
1292
                return expanded::Completions
×
1293
            else
1294
                return sort!(paths, by=p->p.path), r::UnitRange{Int}, success
1✔
1295
            end
1296
        end
1297
    elseif inc_tag === :string
2,066✔
1298
        # Find first non-escaped quote
1299
        let m = match(r"\"(?!\\)", reverse(partial))
143✔
1300
            startpos = nextind(partial, reverseind(partial, m.offset))
286✔
1301
            r = startpos:pos
166✔
1302
            scs::String = string[r]
263✔
1303

1304
            expanded = complete_expanduser(scs, r)
143✔
1305
            was_expanded = expanded[3]
143✔
1306
            if was_expanded
143✔
1307
                scs = (only(expanded[1])::PathCompletion).path
2✔
1308
                # If tab press, ispath and user expansion available, return it now
1309
                # otherwise see if we can complete the path further before returning with expanded ~
1310
                !hint && ispath(scs) && return expanded::Completions
2✔
1311
            end
1312

1313
            path = try
142✔
1314
                unescape_string(replace(scs, "\\\$"=>"\$"))
261✔
1315
            catch ex
1316
                ex isa ArgumentError || rethrow()
×
1317
                nothing
142✔
1318
            end
1319
            if !isnothing(path)
284✔
1320
                paths, dir, success = complete_path(path::String, string_escape=true)
142✔
1321

1322
                if close_path_completion(dir, paths, path, pos)
142✔
1323
                    p = (paths[1]::PathCompletion).path * "\""
6✔
1324
                    hint && was_expanded && (p = contractuser(p))
6✔
1325
                    paths[1] = PathCompletion(p)
6✔
1326
                end
1327

1328
                if success && !isempty(dir)
142✔
1329
                    let dir = do_string_escape(dir)
18✔
1330
                        # if escaping of dir matches scs prefix, remove that from the completions
1331
                        # otherwise make it the whole completion
1332
                        if endswith(dir, "/") && startswith(scs, dir)
9✔
1333
                            r = (startpos + sizeof(dir)):pos
1✔
1334
                        elseif startswith(scs, dir * "/") && dir != dirname(homedir())
8✔
1335
                            was_expanded && (dir = contractuser(dir))
8✔
1336
                            r = nextind(string, startpos + sizeof(dir)):pos
8✔
1337
                        else
1338
                            map!(paths, paths) do c::PathCompletion
×
1339
                                p = dir * "/" * c.path
×
1340
                                hint && was_expanded && (p = contractuser(p))
×
1341
                                return PathCompletion(p)
×
1342
                            end
1343
                        end
1344
                    end
1345
                end
1346

1347
                # Fallthrough allowed so that Latex symbols can be completed in strings
1348
                if success
142✔
1349
                    return sort!(paths, by=p->p.path), r::UnitRange{Int}, success
27,890✔
1350
                elseif !hint && was_expanded
71✔
1351
                    # if not able to provide completions, not hinting, and ~ expansion was possible, return ~ expansion
1352
                    return expanded::Completions
1✔
1353
                end
1354
            end
1355
        end
1356
    end
1357
    # if path has ~ and we didn't find any paths to complete just return the expanded path
1358
    was_expanded && return expanded::Completions
1,993✔
1359

1360
    ok, ret = bslash_completions(string, pos)
1,993✔
1361
    ok && return ret
1,993✔
1362

1363
    # Make sure that only bslash_completions is working on strings
1364
    inc_tag === :string && return Completion[], 0:-1, false
1,942✔
1365
    if inc_tag === :other
1,881✔
1366
        frange, ex, wordrange, method_name_end = identify_possible_method_completion(partial, pos)
992✔
1367
        if last(frange) != -1 && all(isspace, @view partial[wordrange]) # no last argument to complete
992✔
1368
            if ex.head === :call
139✔
1369
                return complete_methods(ex, context_module, shift), first(frange):method_name_end, false
136✔
1370
            elseif is_broadcasting_expr(ex)
3✔
1371
                return complete_methods(ex, context_module, shift), first(frange):(method_name_end - 1), false
3✔
1372
            end
1373
        end
1374
    elseif inc_tag === :comment
889✔
1375
        return Completion[], 0:-1, false
1✔
1376
    end
1377

1378
    # Check whether we can complete a keyword argument in a function call
1379
    kwarg_completion, wordrange = complete_keyword_argument(partial, pos, context_module)
3,313✔
1380
    isempty(wordrange) || return kwarg_completion, wordrange, !isempty(kwarg_completion)
1,910✔
1381

1382
    dotpos = something(findprev(isequal('.'), string, pos), 0)
2,387✔
1383
    startpos = nextind(string, something(findprev(in(non_identifier_chars), string, pos), 0))
2,787✔
1384
    # strip preceding ! operator
1385
    if (m = match(r"\G\!+", partial, startpos)) isa RegexMatch
1,572✔
1386
        startpos += length(m.match)
2✔
1387
    end
1388

1389
    name = string[max(startpos, dotpos+1):pos]
2,748✔
1390
    if afterusing(string, startpos)
1,572✔
1391
        # We're right after using or import. Let's look only for packages
1392
        # and modules we can reach from here
1393

1394
        # If there's no dot, we're in toplevel, so we should
1395
        # also search for packages
1396
        s = string[startpos:pos]
74✔
1397
        if dotpos <= startpos
39✔
1398
            for dir in Base.load_path()
30✔
1399
                if basename(dir) in Base.project_names && isfile(dir)
66✔
1400
                    complete_loading_candidates!(suggestions, s, dir)
1✔
1401
                end
1402
                isdir(dir) || continue
33✔
1403
                for entry in _readdirx(dir)
31✔
1404
                    pname = entry.name
1,832✔
1405
                    if pname[1] != '.' && pname != "METADATA" &&
3,664✔
1406
                        pname != "REQUIRE" && startswith(pname, s)
1407
                        # Valid file paths are
1408
                        #   <Mod>.jl
1409
                        #   <Mod>/src/<Mod>.jl
1410
                        #   <Mod>.jl/src/<Mod>.jl
1411
                        if isfile(entry)
526✔
1412
                            endswith(pname, ".jl") && push!(suggestions,
×
1413
                                                            PackageCompletion(pname[1:prevind(pname, end-2)]))
1414
                        else
1415
                            mod_name = if endswith(pname, ".jl")
263✔
1416
                                pname[1:prevind(pname, end-2)]
×
1417
                            else
1418
                                pname
263✔
1419
                            end
1420
                            if isfile(joinpath(entry, "src",
263✔
1421
                                               "$mod_name.jl"))
1422
                                push!(suggestions, PackageCompletion(mod_name))
262✔
1423
                            end
1424
                        end
1425
                    end
1426
                end
1,832✔
1427
            end
33✔
1428
        end
1429
        comp_keywords = false
×
1430
        complete_modules_only = true
×
1431
    else
1432
        comp_keywords = !isempty(name) && startpos > dotpos
1,533✔
1433
        complete_modules_only = false
×
1434
    end
1435

1436
    startpos == 0 && (pos = -1)
1,572✔
1437
    dotpos < startpos && (dotpos = startpos - 1)
1,572✔
1438
    return complete_identifiers!(suggestions, context_module, string, name, pos,
1,572✔
1439
                                 dotpos, startpos;
1440
                                 comp_keywords, complete_modules_only)
1441
end
1442

1443
function shell_completions(string, pos, hint::Bool=false)
439✔
1444
    # First parse everything up to the current position
1445
    scs = string[1:pos]
898✔
1446
    args, last_arg_start = try
439✔
1447
        Base.shell_parse(scs, true)::Tuple{Expr,Int}
457✔
1448
    catch ex
1449
        ex isa ArgumentError || ex isa ErrorException || rethrow()
36✔
1450
        return Completion[], 0:-1, false
439✔
1451
    end
1452
    ex = args.args[end]::Expr
421✔
1453
    # Now look at the last thing we parsed
1454
    isempty(ex.args) && return Completion[], 0:-1, false
421✔
1455
    lastarg = ex.args[end]
421✔
1456
    # As Base.shell_parse throws away trailing spaces (unless they are escaped),
1457
    # we need to special case here.
1458
    # If the last char was a space, but shell_parse ignored it search on "".
1459
    if isexpr(lastarg, :incomplete) || isexpr(lastarg, :error)
840✔
1460
        partial = string[last_arg_start:pos]
4✔
1461
        ret, range = completions(partial, lastindex(partial), Main, true, hint)
4✔
1462
        range = range .+ (last_arg_start - 1)
2✔
1463
        return ret, range, true
2✔
1464
    elseif endswith(scs, ' ') && !endswith(scs, "\\ ")
419✔
1465
        r = pos+1:pos
34✔
1466
        paths, dir, success = complete_path("", use_envpath=false, shell_escape=true)
17✔
1467
        return paths, r, success
17✔
1468
    elseif all(@nospecialize(arg) -> arg isa AbstractString, ex.args)
809✔
1469
        # Join these and treat this as a path
1470
        path::String = join(ex.args)
401✔
1471
        r = last_arg_start:pos
401✔
1472

1473
        # Also try looking into the env path if the user wants to complete the first argument
1474
        use_envpath = length(args.args) < 2
401✔
1475

1476
        expanded = complete_expanduser(path, r)
401✔
1477
        was_expanded = expanded[3]
401✔
1478
        if was_expanded
401✔
1479
            path = (only(expanded[1])::PathCompletion).path
2✔
1480
            # If tab press, ispath and user expansion available, return it now
1481
            # otherwise see if we can complete the path further before returning with expanded ~
1482
            !hint && ispath(path) && return expanded::Completions
2✔
1483
        end
1484

1485
        paths, dir, success = complete_path(path, use_envpath=use_envpath, shell_escape=true, contract_user=was_expanded)
401✔
1486

1487
        if success && !isempty(dir)
401✔
1488
            let dir = do_shell_escape(dir)
97✔
1489
                # if escaping of dir matches scs prefix, remove that from the completions
1490
                # otherwise make it the whole completion
1491
                partial = string[last_arg_start:pos]
194✔
1492
                if endswith(dir, "/") && startswith(partial, dir)
97✔
1493
                    r = (last_arg_start + sizeof(dir)):pos
14✔
1494
                elseif startswith(partial, dir * "/")
85✔
1495
                    r = nextind(string, last_arg_start + sizeof(dir)):pos
81✔
1496
                else
1497
                    map!(paths, paths) do c::PathCompletion
4✔
1498
                        return PathCompletion(dir * "/" * c.path)
4✔
1499
                    end
1500
                end
1501
            end
1502
        end
1503
        # if ~ was expanded earlier and the incomplete string isn't a path
1504
        # return the path with contracted user to match what the hint shows. Otherwise expand ~
1505
        # i.e. require two tab presses to expand user
1506
        if was_expanded && !ispath(path)
401✔
1507
            map!(paths, paths) do c::PathCompletion
×
1508
                PathCompletion(contractuser(c.path))
×
1509
            end
1510
        end
1511
        return paths, r, success
401✔
1512
    end
1513
    return Completion[], 0:-1, false
1✔
1514
end
1515

1516
function __init__()
4✔
1517
    COMPLETION_WORLD[] = Base.get_world_counter()
4✔
1518
    return nothing
4✔
1519
end
1520

1521
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