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

JuliaLang / julia / #37997

29 Jan 2025 02:08AM UTC coverage: 17.283% (-68.7%) from 85.981%
#37997

push

local

web-flow
bpart: Start enforcing min_world for global variable definitions (#57150)

This is the analog of #57102 for global variables. Unlike for consants,
there is no automatic global backdate mechanism. The reasoning for this
is that global variables can be declared at any time, unlike constants
which can only be decalared once their value is available. As a result
code patterns using `Core.eval` to declare globals are rarer and likely
incorrect.

1 of 22 new or added lines in 3 files covered. (4.55%)

31430 existing lines in 188 files now uncovered.

7903 of 45728 relevant lines covered (17.28%)

98663.7 hits per line

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

0.35
/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, named_completion
6

7
using Core: Const
8
# We want to insulate the REPLCompletion module from any changes the user may
9
# make to the compiler, since it runs by default and the system becomes unusable
10
# if it breaks.
11
const CC = Base.Compiler
12
using Base.Meta
13
using Base: propertynames, something, IdSet
14
using Base.Filesystem: _readdirx
15

16
using ..REPL.LineEdit: NamedCompletion
17

18
abstract type Completion end
19

20
struct TextCompletion <: Completion
21
    text::String
22
end
23

24
struct KeywordCompletion <: Completion
25
    keyword::String
26
end
27

28
struct KeyvalCompletion <: Completion
29
    keyval::String
30
end
31

32
struct PathCompletion <: Completion
33
    path::String
34
end
35

36
struct ModuleCompletion <: Completion
37
    parent::Module
38
    mod::String
39
end
40

41
struct PackageCompletion <: Completion
42
    package::String
43
end
44

45
struct PropertyCompletion <: Completion
46
    value
47
    property::Symbol
48
end
49

50
struct FieldCompletion <: Completion
51
    typ::DataType
52
    field::Symbol
53
end
54

55
struct MethodCompletion <: Completion
56
    tt # may be used by an external consumer to infer return type, etc.
57
    method::Method
UNCOV
58
    MethodCompletion(@nospecialize(tt), method::Method) = new(tt, method)
×
59
end
60

61
struct BslashCompletion <: Completion
62
    completion::String # what is actually completed, for example "\trianglecdot"
63
    name::String # what is displayed, for example "â—¬ \trianglecdot"
64
end
UNCOV
65
BslashCompletion(completion::String) = BslashCompletion(completion, completion)
×
66

67
struct ShellCompletion <: Completion
68
    text::String
69
end
70

71
struct DictCompletion <: Completion
72
    dict::AbstractDict
73
    key::String
74
end
75

76
struct KeywordArgumentCompletion <: Completion
77
    kwarg::String
78
end
79

80
# interface definition
UNCOV
81
function Base.getproperty(c::Completion, name::Symbol)
×
UNCOV
82
    if name === :text
×
UNCOV
83
        return getfield(c, :text)::String
×
UNCOV
84
    elseif name === :keyword
×
UNCOV
85
        return getfield(c, :keyword)::String
×
UNCOV
86
    elseif name === :path
×
UNCOV
87
        return getfield(c, :path)::String
×
UNCOV
88
    elseif name === :parent
×
89
        return getfield(c, :parent)::Module
×
UNCOV
90
    elseif name === :mod
×
UNCOV
91
        return getfield(c, :mod)::String
×
UNCOV
92
    elseif name === :package
×
UNCOV
93
        return getfield(c, :package)::String
×
UNCOV
94
    elseif name === :property
×
UNCOV
95
        return getfield(c, :property)::Symbol
×
UNCOV
96
    elseif name === :field
×
UNCOV
97
        return getfield(c, :field)::Symbol
×
UNCOV
98
    elseif name === :method
×
UNCOV
99
        return getfield(c, :method)::Method
×
UNCOV
100
    elseif name === :bslash
×
101
        return getfield(c, :bslash)::String
×
UNCOV
102
    elseif name === :text
×
103
        return getfield(c, :text)::String
×
UNCOV
104
    elseif name === :key
×
UNCOV
105
        return getfield(c, :key)::String
×
UNCOV
106
    elseif name === :kwarg
×
UNCOV
107
        return getfield(c, :kwarg)::String
×
108
    end
UNCOV
109
    return getfield(c, name)
×
110
end
111

UNCOV
112
_completion_text(c::TextCompletion) = c.text
×
UNCOV
113
_completion_text(c::KeywordCompletion) = c.keyword
×
UNCOV
114
_completion_text(c::KeyvalCompletion) = c.keyval
×
UNCOV
115
_completion_text(c::PathCompletion) = c.path
×
UNCOV
116
_completion_text(c::ModuleCompletion) = c.mod
×
UNCOV
117
_completion_text(c::PackageCompletion) = c.package
×
UNCOV
118
_completion_text(c::PropertyCompletion) = sprint(Base.show_sym, c.property)
×
UNCOV
119
_completion_text(c::FieldCompletion) = sprint(Base.show_sym, c.field)
×
UNCOV
120
_completion_text(c::MethodCompletion) = repr(c.method)
×
121
_completion_text(c::ShellCompletion) = c.text
×
UNCOV
122
_completion_text(c::DictCompletion) = c.key
×
UNCOV
123
_completion_text(c::KeywordArgumentCompletion) = c.kwarg*'='
×
124

UNCOV
125
completion_text(c) = _completion_text(c)::String
×
126

UNCOV
127
named_completion(c::BslashCompletion) = NamedCompletion(c.completion, c.name)
×
128

UNCOV
129
function named_completion(c)
×
UNCOV
130
    text = completion_text(c)::String
×
UNCOV
131
    return NamedCompletion(text, text)
×
132
end
133

UNCOV
134
named_completion_completion(c) = named_completion(c).completion::String
×
135

136
const Completions = Tuple{Vector{Completion}, UnitRange{Int}, Bool}
137

UNCOV
138
function completes_global(x, name)
×
UNCOV
139
    return startswith(x, name) && !('#' in x)
×
140
end
141

UNCOV
142
function appendmacro!(syms, macros, needle, endchar)
×
UNCOV
143
    for macsym in macros
×
UNCOV
144
        s = String(macsym)
×
UNCOV
145
        if endswith(s, needle)
×
UNCOV
146
            from = nextind(s, firstindex(s))
×
UNCOV
147
            to = prevind(s, sizeof(s)-sizeof(needle)+1)
×
UNCOV
148
            push!(syms, s[from:to]*endchar)
×
149
        end
UNCOV
150
    end
×
151
end
152

UNCOV
153
function append_filtered_mod_names!(ffunc::Function, suggestions::Vector{Completion},
×
154
                                    mod::Module, name::String, complete_internal_only::Bool)
UNCOV
155
    imported = usings = !complete_internal_only
×
UNCOV
156
    ssyms = names(mod; all=true, imported, usings)
×
UNCOV
157
    filter!(ffunc, ssyms)
×
UNCOV
158
    macros = filter(x -> startswith(String(x), "@" * name), ssyms)
×
159

160
    # don't complete string and command macros when the input matches the internal name like `r_` to `r"`
UNCOV
161
    if !startswith(name, "@")
×
UNCOV
162
        filter!(macros) do m
×
UNCOV
163
            s = String(m)
×
UNCOV
164
            if endswith(s, "_str") || endswith(s, "_cmd")
×
UNCOV
165
                occursin(name, first(s, length(s)-4))
×
166
            else
UNCOV
167
                true
×
168
            end
169
        end
170
    end
171

UNCOV
172
    syms = String[sprint((io,s)->Base.show_sym(io, s; allow_macroname=true), s) for s in ssyms if completes_global(String(s), name)]
×
UNCOV
173
    appendmacro!(syms, macros, "_str", "\"")
×
UNCOV
174
    appendmacro!(syms, macros, "_cmd", "`")
×
UNCOV
175
    for sym in syms
×
UNCOV
176
        push!(suggestions, ModuleCompletion(mod, sym))
×
UNCOV
177
    end
×
UNCOV
178
    return suggestions
×
179
end
180

181
# REPL Symbol Completions
UNCOV
182
function complete_symbol!(suggestions::Vector{Completion},
×
183
                          @nospecialize(prefix), name::String, context_module::Module;
184
                          complete_modules_only::Bool=false,
185
                          shift::Bool=false)
UNCOV
186
    local mod, t, val
×
UNCOV
187
    complete_internal_only = false
×
UNCOV
188
    if prefix !== nothing
×
UNCOV
189
        res = repl_eval_ex(prefix, context_module)
×
UNCOV
190
        res === nothing && return Completion[]
×
UNCOV
191
        if res isa Const
×
UNCOV
192
            val = res.val
×
UNCOV
193
            if isa(val, Module)
×
UNCOV
194
                mod = val
×
UNCOV
195
                if !shift
×
196
                    # when module is explicitly accessed, show internal bindings that are
197
                    # defined by the module, unless shift key is pressed
UNCOV
198
                    complete_internal_only = true
×
199
                end
200
            else
UNCOV
201
                t = typeof(val)
×
202
            end
203
        else
UNCOV
204
            t = CC.widenconst(res)
×
205
        end
206
    else
UNCOV
207
        mod = context_module
×
208
    end
209

UNCOV
210
    if @isdefined(mod) # lookup names available within the module
×
UNCOV
211
        let modname = nameof(mod),
×
212
            is_main = mod===Main
UNCOV
213
            append_filtered_mod_names!(suggestions, mod, name, complete_internal_only) do s::Symbol
×
UNCOV
214
                if Base.isdeprecated(mod, s)
×
215
                    return false
×
UNCOV
216
                elseif s === modname
×
UNCOV
217
                    return false # exclude `Main.Main.Main`, etc.
×
UNCOV
218
                elseif complete_modules_only && !completes_module(mod, s)
×
UNCOV
219
                    return false
×
UNCOV
220
                elseif is_main && s === :MainInclude
×
UNCOV
221
                    return false
×
222
                end
UNCOV
223
                return true
×
224
            end
225
        end
UNCOV
226
    elseif @isdefined(val) # looking for a property of an instance
×
UNCOV
227
        try
×
UNCOV
228
            for property in propertynames(val, false)
×
229
                # TODO: support integer arguments (#36872)
UNCOV
230
                if property isa Symbol && startswith(string(property), name)
×
UNCOV
231
                    push!(suggestions, PropertyCompletion(val, property))
×
232
                end
UNCOV
233
            end
×
UNCOV
234
        catch
×
235
        end
UNCOV
236
    elseif @isdefined(t) && field_completion_eligible(t)
×
237
        # Looking for a member of a type
UNCOV
238
        add_field_completions!(suggestions, name, t)
×
239
    end
UNCOV
240
    return suggestions
×
241
end
242

UNCOV
243
completes_module(mod::Module, x::Symbol) =
×
244
    Base.isbindingresolved(mod, x) && isdefined(mod, x) && isa(getglobal(mod, x), Module)
245

UNCOV
246
function add_field_completions!(suggestions::Vector{Completion}, name::String, @nospecialize(t))
×
UNCOV
247
    if isa(t, Union)
×
UNCOV
248
        add_field_completions!(suggestions, name, t.a)
×
UNCOV
249
        add_field_completions!(suggestions, name, t.b)
×
250
    else
UNCOV
251
        @assert isconcretetype(t)
×
UNCOV
252
        fields = fieldnames(t)
×
UNCOV
253
        for field in fields
×
UNCOV
254
            isa(field, Symbol) || continue # Tuple type has ::Int field name
×
UNCOV
255
            s = string(field)
×
UNCOV
256
            if startswith(s, name)
×
UNCOV
257
                push!(suggestions, FieldCompletion(t, field))
×
258
            end
UNCOV
259
        end
×
260
    end
261
end
262

263
const GENERIC_PROPERTYNAMES_METHOD = which(propertynames, (Any,))
264

UNCOV
265
function field_completion_eligible(@nospecialize t)
×
UNCOV
266
    if isa(t, Union)
×
UNCOV
267
        return field_completion_eligible(t.a) && field_completion_eligible(t.b)
×
268
    end
UNCOV
269
    isconcretetype(t) || return false
×
270
    # field completion is correct only when `getproperty` fallbacks to `getfield`
UNCOV
271
    match = Base._which(Tuple{typeof(propertynames),t}; raise=false)
×
UNCOV
272
    match === nothing && return false
×
UNCOV
273
    return match.method === GENERIC_PROPERTYNAMES_METHOD
×
274
end
275

UNCOV
276
function complete_from_list!(suggestions::Vector{Completion}, T::Type, list::Vector{String}, s::String)
×
UNCOV
277
    r = searchsorted(list, s)
×
UNCOV
278
    i = first(r)
×
UNCOV
279
    n = length(list)
×
UNCOV
280
    while i <= n && startswith(list[i],s)
×
UNCOV
281
        r = first(r):i
×
UNCOV
282
        i += 1
×
UNCOV
283
    end
×
UNCOV
284
    for kw in list[r]
×
UNCOV
285
        push!(suggestions, T(kw))
×
UNCOV
286
    end
×
UNCOV
287
    return suggestions
×
288
end
289

290
const sorted_keywords = [
291
    "abstract type", "baremodule", "begin", "break", "catch", "ccall",
292
    "const", "continue", "do", "else", "elseif", "end", "export",
293
    "finally", "for", "function", "global", "if", "import",
294
    "let", "local", "macro", "module", "mutable struct",
295
    "primitive type", "quote", "return", "struct",
296
    "try", "using", "while"]
297

UNCOV
298
complete_keyword!(suggestions::Vector{Completion}, s::String) =
×
299
    complete_from_list!(suggestions, KeywordCompletion, sorted_keywords, s)
300

301
const sorted_keyvals = ["false", "true"]
302

UNCOV
303
complete_keyval!(suggestions::Vector{Completion}, s::String) =
×
304
    complete_from_list!(suggestions, KeyvalCompletion, sorted_keyvals, s)
305

UNCOV
306
function do_raw_escape(s)
×
307
    # escape_raw_string with delim='`' and ignoring the rule for the ending \
308
    return replace(s, r"(\\+)`" => s"\1\\`")
×
309
end
UNCOV
310
function do_shell_escape(s)
×
UNCOV
311
    return Base.shell_escape_posixly(s)
×
312
end
UNCOV
313
function do_string_escape(s)
×
UNCOV
314
    return escape_string(s, ('\"','$'))
×
315
end
316

317
const PATH_cache_lock = Base.ReentrantLock()
318
const PATH_cache = Set{String}()
319
PATH_cache_task::Union{Task,Nothing} = nothing # used for sync in tests
320
next_cache_update::Float64 = 0.0
UNCOV
321
function maybe_spawn_cache_PATH()
×
UNCOV
322
    global PATH_cache_task, next_cache_update
×
UNCOV
323
    @lock PATH_cache_lock begin
×
UNCOV
324
        PATH_cache_task isa Task && !istaskdone(PATH_cache_task) && return
×
UNCOV
325
        time() < next_cache_update && return
×
UNCOV
326
        PATH_cache_task = Threads.@spawn begin
×
UNCOV
327
            REPLCompletions.cache_PATH()
×
UNCOV
328
            @lock PATH_cache_lock PATH_cache_task = nothing # release memory when done
×
329
        end
UNCOV
330
        Base.errormonitor(PATH_cache_task)
×
331
    end
332
end
333

334
# caches all reachable files in PATH dirs
UNCOV
335
function cache_PATH()
×
UNCOV
336
    path = get(ENV, "PATH", nothing)
×
UNCOV
337
    path isa String || return
×
338

UNCOV
339
    global next_cache_update
×
340

341
    # Calling empty! on PATH_cache would be annoying for async typing hints as completions would temporarily disappear.
342
    # 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.
UNCOV
343
    this_PATH_cache = Set{String}()
×
344

UNCOV
345
    @debug "caching PATH files" PATH=path
×
UNCOV
346
    pathdirs = split(path, @static Sys.iswindows() ? ";" : ":")
×
347

UNCOV
348
    next_yield_time = time() + 0.01
×
349

UNCOV
350
    t = @elapsed for pathdir in pathdirs
×
UNCOV
351
        actualpath = try
×
UNCOV
352
            realpath(pathdir)
×
353
        catch ex
UNCOV
354
            ex isa Base.IOError || rethrow()
×
355
            # Bash doesn't expect every folder in PATH to exist, so neither shall we
UNCOV
356
            continue
×
357
        end
358

UNCOV
359
        if actualpath != pathdir && in(actualpath, pathdirs)
×
360
            # Remove paths which (after resolving links) are in the env path twice.
361
            # Many distros eg. point /bin to /usr/bin but have both in the env path.
UNCOV
362
            continue
×
363
        end
364

UNCOV
365
        path_entries = try
×
UNCOV
366
            _readdirx(pathdir)
×
367
        catch e
368
            # Bash allows dirs in PATH that can't be read, so we should as well.
UNCOV
369
            if isa(e, Base.IOError) || isa(e, Base.ArgumentError)
×
UNCOV
370
                continue
×
371
            else
372
                # We only handle IOError and ArgumentError here
373
                rethrow()
×
374
            end
375
        end
UNCOV
376
        for entry in path_entries
×
377
            # In a perfect world, we would filter on whether the file is executable
378
            # here, or even on whether the current user can execute the file in question.
UNCOV
379
            try
×
UNCOV
380
                if isfile(entry)
×
UNCOV
381
                    @lock PATH_cache_lock push!(PATH_cache, entry.name)
×
UNCOV
382
                    push!(this_PATH_cache, entry.name)
×
383
                end
384
            catch e
385
                # `isfile()` can throw in rare cases such as when probing a
386
                # symlink that points to a file within a directory we do not
387
                # have read access to.
UNCOV
388
                if isa(e, Base.IOError)
×
UNCOV
389
                    continue
×
390
                else
391
                    rethrow()
×
392
                end
393
            end
UNCOV
394
            if time() >= next_yield_time
×
UNCOV
395
                yield() # to avoid blocking typing when -t1
×
UNCOV
396
                next_yield_time = time() + 0.01
×
397
            end
UNCOV
398
        end
×
UNCOV
399
    end
×
400

UNCOV
401
    @lock PATH_cache_lock begin
×
UNCOV
402
        intersect!(PATH_cache, this_PATH_cache) # remove entries from PATH_cache that weren't found this time
×
UNCOV
403
        next_cache_update = time() + 10 # earliest next update can run is 10s after
×
404
    end
405

UNCOV
406
    @debug "caching PATH files took $t seconds" length(pathdirs) length(PATH_cache)
×
UNCOV
407
    return PATH_cache
×
408
end
409

UNCOV
410
function complete_path(path::AbstractString;
×
411
                       use_envpath=false,
412
                       shell_escape=false,
413
                       raw_escape=false,
414
                       string_escape=false,
415
                       contract_user=false)
UNCOV
416
    @assert !(shell_escape && string_escape)
×
UNCOV
417
    if Base.Sys.isunix() && occursin(r"^~(?:/|$)", path)
×
418
        # if the path is just "~", don't consider the expanded username as a prefix
419
        if path == "~"
×
420
            dir, prefix = homedir(), ""
×
421
        else
422
            dir, prefix = splitdir(homedir() * path[2:end])
×
423
        end
424
    else
UNCOV
425
        dir, prefix = splitdir(path)
×
426
    end
UNCOV
427
    entries = try
×
UNCOV
428
        if isempty(dir)
×
UNCOV
429
            _readdirx()
×
UNCOV
430
        elseif isdir(dir)
×
UNCOV
431
            _readdirx(dir)
×
432
        else
UNCOV
433
            return Completion[], dir, false
×
434
        end
435
    catch ex
436
        ex isa Base.IOError || rethrow()
×
437
        return Completion[], dir, false
×
438
    end
439

UNCOV
440
    matches = Set{String}()
×
UNCOV
441
    for entry in entries
×
UNCOV
442
        if startswith(entry.name, prefix)
×
UNCOV
443
            is_dir = try isdir(entry) catch ex; ex isa Base.IOError ? false : rethrow() end
×
UNCOV
444
            push!(matches, is_dir ? entry.name * "/" : entry.name)
×
445
        end
UNCOV
446
    end
×
447

UNCOV
448
    if use_envpath && isempty(dir)
×
449
        # Look for files in PATH as well. These are cached in `cache_PATH` in an async task to not block typing.
450
        # If we cannot get lock because its still caching just pass over this so that typing isn't laggy.
UNCOV
451
        maybe_spawn_cache_PATH() # only spawns if enough time has passed and the previous caching task has completed
×
UNCOV
452
        @lock PATH_cache_lock begin
×
UNCOV
453
            for file in PATH_cache
×
UNCOV
454
                startswith(file, prefix) && push!(matches, file)
×
UNCOV
455
            end
×
456
        end
457
    end
458

UNCOV
459
    matches = ((shell_escape ? do_shell_escape(s) : string_escape ? do_string_escape(s) : s) for s in matches)
×
UNCOV
460
    matches = ((raw_escape ? do_raw_escape(s) : s) for s in matches)
×
UNCOV
461
    matches = Completion[PathCompletion(contract_user ? contractuser(s) : s) for s in matches]
×
UNCOV
462
    return matches, dir, !isempty(matches)
×
463
end
464

465
function complete_path(path::AbstractString,
×
466
                       pos::Int;
467
                       use_envpath=false,
468
                       shell_escape=false,
469
                       string_escape=false,
470
                       contract_user=false)
471
    ## TODO: enable this depwarn once Pkg is fixed
472
    #Base.depwarn("complete_path with pos argument is deprecated because the return value [2] is incorrect to use", :complete_path)
473
    paths, dir, success = complete_path(path; use_envpath, shell_escape, string_escape)
×
474
    if Base.Sys.isunix() && occursin(r"^~(?:/|$)", path)
×
475
        # if the path is just "~", don't consider the expanded username as a prefix
476
        if path == "~"
×
477
            dir, prefix = homedir(), ""
×
478
        else
479
            dir, prefix = splitdir(homedir() * path[2:end])
×
480
        end
481
    else
482
        dir, prefix = splitdir(path)
×
483
    end
484
    startpos = pos - lastindex(prefix) + 1
×
485
    Sys.iswindows() && map!(paths, paths) do c::PathCompletion
×
486
        # emulation for unnecessarily complicated return value, since / is a
487
        # perfectly acceptable path character which does not require quoting
488
        # but is required by Pkg's awkward parser handling
489
        return endswith(c.path, "/") ? PathCompletion(chop(c.path) * "\\\\") : c
×
490
    end
491
    return paths, startpos:pos, success
×
492
end
493

UNCOV
494
function complete_expanduser(path::AbstractString, r)
×
UNCOV
495
    expanded =
×
UNCOV
496
        try expanduser(path)
×
497
        catch e
UNCOV
498
            e isa ArgumentError || rethrow()
×
UNCOV
499
            path
×
500
        end
UNCOV
501
    return Completion[PathCompletion(expanded)], r, path != expanded
×
502
end
503

504
# Returns a range that includes the method name in front of the first non
505
# closed start brace from the end of the string.
UNCOV
506
function find_start_brace(s::AbstractString; c_start='(', c_end=')')
×
UNCOV
507
    r = reverse(s)
×
UNCOV
508
    i = firstindex(r)
×
UNCOV
509
    braces = in_comment = 0
×
UNCOV
510
    in_single_quotes = in_double_quotes = in_back_ticks = false
×
UNCOV
511
    num_single_quotes_in_string = count('\'', s)
×
UNCOV
512
    while i <= ncodeunits(r)
×
UNCOV
513
        c, i = iterate(r, i)
×
UNCOV
514
        if c == '#' && i <= ncodeunits(r) && iterate(r, i)[1] == '='
×
UNCOV
515
            c, i = iterate(r, i) # consume '='
×
UNCOV
516
            new_comments = 1
×
517
            # handle #=#=#=#, by counting =# pairs
UNCOV
518
            while i <= ncodeunits(r) && iterate(r, i)[1] == '#'
×
UNCOV
519
                c, i = iterate(r, i) # consume '#'
×
UNCOV
520
                iterate(r, i)[1] == '=' || break
×
UNCOV
521
                c, i = iterate(r, i) # consume '='
×
UNCOV
522
                new_comments += 1
×
UNCOV
523
            end
×
UNCOV
524
            if c == '='
×
UNCOV
525
                in_comment += new_comments
×
526
            else
UNCOV
527
                in_comment -= new_comments
×
528
            end
UNCOV
529
        elseif !in_single_quotes && !in_double_quotes && !in_back_ticks && in_comment == 0
×
UNCOV
530
            if c == c_start
×
UNCOV
531
                braces += 1
×
UNCOV
532
            elseif c == c_end
×
UNCOV
533
                braces -= 1
×
UNCOV
534
            elseif c == '\'' && num_single_quotes_in_string % 2 == 0
×
535
                # ' can be a transpose too, so check if there are even number of 's in the string
536
                # TODO: This probably needs to be more robust
UNCOV
537
                in_single_quotes = true
×
UNCOV
538
            elseif c == '"'
×
UNCOV
539
                in_double_quotes = true
×
UNCOV
540
            elseif c == '`'
×
UNCOV
541
                in_back_ticks = true
×
542
            end
543
        else
UNCOV
544
            if in_single_quotes &&
×
545
                c == '\'' && i <= ncodeunits(r) && iterate(r, i)[1] != '\\'
UNCOV
546
                in_single_quotes = false
×
UNCOV
547
            elseif in_double_quotes &&
×
548
                c == '"' && i <= ncodeunits(r) && iterate(r, i)[1] != '\\'
UNCOV
549
                in_double_quotes = false
×
UNCOV
550
            elseif in_back_ticks &&
×
551
                c == '`' && i <= ncodeunits(r) && iterate(r, i)[1] != '\\'
UNCOV
552
                in_back_ticks = false
×
UNCOV
553
            elseif in_comment > 0 &&
×
554
                c == '=' && i <= ncodeunits(r) && iterate(r, i)[1] == '#'
555
                # handle =#=#=#=, by counting #= pairs
UNCOV
556
                c, i = iterate(r, i) # consume '#'
×
UNCOV
557
                old_comments = 1
×
UNCOV
558
                while i <= ncodeunits(r) && iterate(r, i)[1] == '='
×
UNCOV
559
                    c, i = iterate(r, i) # consume '='
×
UNCOV
560
                    iterate(r, i)[1] == '#' || break
×
UNCOV
561
                    c, i = iterate(r, i) # consume '#'
×
UNCOV
562
                    old_comments += 1
×
UNCOV
563
                end
×
UNCOV
564
                if c == '#'
×
UNCOV
565
                    in_comment -= old_comments
×
566
                else
UNCOV
567
                    in_comment += old_comments
×
568
                end
569
            end
570
        end
UNCOV
571
        braces == 1 && break
×
UNCOV
572
    end
×
UNCOV
573
    braces != 1 && return 0:-1, -1
×
UNCOV
574
    method_name_end = reverseind(s, i)
×
UNCOV
575
    startind = nextind(s, something(findprev(in(non_identifier_chars), s, method_name_end), 0))::Int
×
UNCOV
576
    return (startind:lastindex(s), method_name_end)
×
577
end
578

579
struct REPLCacheToken end
580

581
struct REPLInterpreter <: CC.AbstractInterpreter
582
    limit_aggressive_inference::Bool
583
    world::UInt
584
    inf_params::CC.InferenceParams
585
    opt_params::CC.OptimizationParams
586
    inf_cache::Vector{CC.InferenceResult}
UNCOV
587
    function REPLInterpreter(limit_aggressive_inference::Bool=false;
×
588
                             world::UInt = Base.get_world_counter(),
589
                             inf_params::CC.InferenceParams = CC.InferenceParams(;
590
                                 aggressive_constant_propagation=true),
591
                             opt_params::CC.OptimizationParams = CC.OptimizationParams(),
592
                             inf_cache::Vector{CC.InferenceResult} = CC.InferenceResult[])
UNCOV
593
        return new(limit_aggressive_inference, world, inf_params, opt_params, inf_cache)
×
594
    end
595
end
UNCOV
596
CC.InferenceParams(interp::REPLInterpreter) = interp.inf_params
×
UNCOV
597
CC.OptimizationParams(interp::REPLInterpreter) = interp.opt_params
×
UNCOV
598
CC.get_inference_world(interp::REPLInterpreter) = interp.world
×
UNCOV
599
CC.get_inference_cache(interp::REPLInterpreter) = interp.inf_cache
×
600
CC.cache_owner(::REPLInterpreter) = REPLCacheToken()
×
601

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

605
# REPLInterpreter doesn't need any sources to be cached, so discard them aggressively
606
CC.transform_result_for_cache(::REPLInterpreter, ::CC.InferenceResult) = nothing
×
607

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

611
# `REPLInterpreter` aggressively resolves global bindings to enable reasonable completions
612
# for lines like `Mod.a.|` (where `|` is the cursor position).
613
# Aggressive binding resolution poses challenges for the inference cache validation
614
# (until https://github.com/JuliaLang/julia/issues/40399 is implemented).
615
# To avoid the cache validation issues, `REPLInterpreter` only allows aggressive binding
616
# resolution for top-level frame representing REPL input code and for child uncached frames
617
# that are constant propagated from the top-level frame ("repl-frame"s). This works, even if
618
# those global bindings are not constant and may be mutated in the future, since:
619
# a.) "repl-frame"s are never cached, and
620
# b.) mutable values are never observed by any cached frames.
621
#
622
# `REPLInterpreter` also aggressively concrete evaluate `:inconsistent` calls within
623
# "repl-frame" to provide reasonable completions for lines like `Ref(Some(42))[].|`.
624
# Aggressive concrete evaluation allows us to get accurate type information about complex
625
# expressions that otherwise can not be constant folded, in a safe way, i.e. it still
626
# doesn't evaluate effectful expressions like `pop!(xs)`.
627
# Similarly to the aggressive binding resolution, aggressive concrete evaluation doesn't
628
# present any cache validation issues because "repl-frame" is never cached.
629

630
# `REPLInterpreter` is specifically used by `repl_eval_ex`, where all top-level frames are
631
# `repl_frame` always. However, this assumption wouldn't stand if `REPLInterpreter` were to
632
# be employed, for instance, by `typeinf_ext_toplevel`.
UNCOV
633
is_repl_frame(sv::CC.InferenceState) = sv.linfo.def isa Module && sv.cache_mode === CC.CACHE_MODE_NULL
×
634

UNCOV
635
function is_call_graph_uncached(sv::CC.InferenceState)
×
UNCOV
636
    CC.is_cached(sv) && return false
×
UNCOV
637
    parent = CC.frame_parent(sv)
×
UNCOV
638
    parent === nothing && return true
×
UNCOV
639
    return is_call_graph_uncached(parent::CC.InferenceState)
×
640
end
641

642
# aggressive global binding resolution within `repl_frame`
UNCOV
643
function CC.abstract_eval_globalref(interp::REPLInterpreter, g::GlobalRef, bailed::Bool,
×
644
                                    sv::CC.InferenceState)
645
    # Ignore saw_latestworld
UNCOV
646
    partition = CC.abstract_eval_binding_partition!(interp, g, sv)
×
UNCOV
647
    if (interp.limit_aggressive_inference ? is_repl_frame(sv) : is_call_graph_uncached(sv))
×
UNCOV
648
        if CC.is_defined_const_binding(CC.binding_kind(partition))
×
UNCOV
649
            return Pair{CC.RTEffects, Union{Nothing, Core.BindingPartition}}(
×
650
                CC.RTEffects(Const(CC.partition_restriction(partition)), Union{}, CC.EFFECTS_TOTAL), partition)
651
        else
UNCOV
652
            b = convert(Core.Binding, g)
×
UNCOV
653
            if CC.binding_kind(partition) == CC.BINDING_KIND_GLOBAL && isdefined(b, :value)
×
UNCOV
654
                return Pair{CC.RTEffects, Union{Nothing, Core.BindingPartition}}(
×
655
                    CC.RTEffects(Const(b.value), Union{}, CC.EFFECTS_TOTAL), partition)
656
            end
657
        end
UNCOV
658
        return Pair{CC.RTEffects, Union{Nothing, Core.BindingPartition}}(
×
659
            CC.RTEffects(Union{}, UndefVarError, CC.EFFECTS_THROWS), partition)
660
    end
UNCOV
661
    return @invoke CC.abstract_eval_globalref(interp::CC.AbstractInterpreter, g::GlobalRef, bailed::Bool,
×
662
                                              sv::CC.InferenceState)
663
end
664

UNCOV
665
function is_repl_frame_getproperty(sv::CC.InferenceState)
×
666
    def = sv.linfo.def
×
667
    def isa Method || return false
×
668
    def.name === :getproperty || return false
×
669
    CC.is_cached(sv) && return false
×
670
    return is_repl_frame(CC.frame_parent(sv))
×
671
end
672

673
# aggressive global binding resolution for `getproperty(::Module, ::Symbol)` calls within `repl_frame`
UNCOV
674
function CC.builtin_tfunction(interp::REPLInterpreter, @nospecialize(f),
×
675
                              argtypes::Vector{Any}, sv::CC.InferenceState)
UNCOV
676
    if f === Core.getglobal && (interp.limit_aggressive_inference ? is_repl_frame_getproperty(sv) : is_call_graph_uncached(sv))
×
677
        if length(argtypes) == 2
×
678
            a1, a2 = argtypes
×
679
            if isa(a1, Const) && isa(a2, Const)
×
680
                a1val, a2val = a1.val, a2.val
×
681
                if isa(a1val, Module) && isa(a2val, Symbol)
×
682
                    g = GlobalRef(a1val, a2val)
×
683
                    if isdefined_globalref(g)
×
684
                        return Const(ccall(:jl_get_globalref_value, Any, (Any,), g))
×
685
                    end
686
                    return Union{}
×
687
                end
688
            end
689
        end
690
    end
UNCOV
691
    return @invoke CC.builtin_tfunction(interp::CC.AbstractInterpreter, f::Any,
×
692
                                        argtypes::Vector{Any}, sv::CC.InferenceState)
693
end
694

695
# aggressive concrete evaluation for `:inconsistent` frames within `repl_frame`
UNCOV
696
function CC.concrete_eval_eligible(interp::REPLInterpreter, @nospecialize(f),
×
697
                                   result::CC.MethodCallResult, arginfo::CC.ArgInfo,
698
                                   sv::CC.InferenceState)
UNCOV
699
    if (interp.limit_aggressive_inference ? is_repl_frame(sv) : is_call_graph_uncached(sv))
×
UNCOV
700
        neweffects = CC.Effects(result.effects; consistent=CC.ALWAYS_TRUE)
×
UNCOV
701
        result = CC.MethodCallResult(result.rt, result.exct, neweffects, result.edge,
×
702
                                     result.edgecycle, result.edgelimited, result.volatile_inf_result)
703
    end
UNCOV
704
    ret = @invoke CC.concrete_eval_eligible(interp::CC.AbstractInterpreter, f::Any,
×
705
                                            result::CC.MethodCallResult, arginfo::CC.ArgInfo,
706
                                            sv::CC.InferenceState)
UNCOV
707
    if ret === :semi_concrete_eval
×
708
        # while the base eligibility check probably won't permit semi-concrete evaluation
709
        # for `REPLInterpreter` (given it completely turns off optimization),
710
        # this ensures we don't inadvertently enter irinterp
711
        ret = :none
×
712
    end
UNCOV
713
    return ret
×
714
end
715

716
# allow constant propagation for mutable constants
UNCOV
717
function CC.const_prop_argument_heuristic(interp::REPLInterpreter, arginfo::CC.ArgInfo, sv::CC.InferenceState)
×
UNCOV
718
    if !interp.limit_aggressive_inference
×
UNCOV
719
        any(@nospecialize(a)->isa(a, Const), arginfo.argtypes) && return true # even if mutable
×
720
    end
UNCOV
721
    return @invoke CC.const_prop_argument_heuristic(interp::CC.AbstractInterpreter, arginfo::CC.ArgInfo, sv::CC.InferenceState)
×
722
end
723

UNCOV
724
function resolve_toplevel_symbols!(src::Core.CodeInfo, mod::Module)
×
UNCOV
725
    @ccall jl_resolve_definition_effects_in_ir(
×
726
        #=jl_array_t *stmts=# src.code::Any,
727
        #=jl_module_t *m=# mod::Any,
728
        #=jl_svec_t *sparam_vals=# Core.svec()::Any,
729
        #=int binding_effects=# 0::Int)::Cvoid
730
    return src
×
731
end
732

733
# lower `ex` and run type inference on the resulting top-level expression
UNCOV
734
function repl_eval_ex(@nospecialize(ex), context_module::Module; limit_aggressive_inference::Bool=false)
×
UNCOV
735
    if (isexpr(ex, :toplevel) || isexpr(ex, :tuple)) && !isempty(ex.args)
×
736
        # get the inference result for the last expression
UNCOV
737
        ex = ex.args[end]
×
738
    end
UNCOV
739
    lwr = try
×
UNCOV
740
        Meta.lower(context_module, ex)
×
741
    catch # macro expansion failed, etc.
UNCOV
742
        return nothing
×
743
    end
UNCOV
744
    if lwr isa Symbol
×
UNCOV
745
        return isdefined(context_module, lwr) ? Const(getfield(context_module, lwr)) : nothing
×
746
    end
UNCOV
747
    lwr isa Expr || return Const(lwr) # `ex` is literal
×
UNCOV
748
    isexpr(lwr, :thunk) || return nothing # lowered to `Expr(:error, ...)` or similar
×
UNCOV
749
    src = lwr.args[1]::Core.CodeInfo
×
750

UNCOV
751
    resolve_toplevel_symbols!(src, context_module)
×
752
    # construct top-level `MethodInstance`
UNCOV
753
    mi = ccall(:jl_method_instance_for_thunk, Ref{Core.MethodInstance}, (Any, Any), src, context_module)
×
754

UNCOV
755
    interp = REPLInterpreter(limit_aggressive_inference)
×
UNCOV
756
    result = CC.InferenceResult(mi)
×
UNCOV
757
    frame = CC.InferenceState(result, src, #=cache=#:no, interp)
×
758

759
    # NOTE Use the fixed world here to make `REPLInterpreter` robust against
760
    #      potential invalidations of `Core.Compiler` methods.
UNCOV
761
    Base.invoke_in_world(COMPLETION_WORLD[], CC.typeinf, interp, frame)
×
762

UNCOV
763
    result = frame.result.result
×
UNCOV
764
    result === Union{} && return nothing # for whatever reason, callers expect this as the Bottom and/or Top type instead
×
UNCOV
765
    return result
×
766
end
767

768
# `COMPLETION_WORLD[]` will be initialized within `__init__`
769
# (to allow us to potentially remove REPL from the sysimage in the future).
770
# Note that inference from the `code_typed` call below will use the current world age
771
# rather than `typemax(UInt)`, since `Base.invoke_in_world` uses the current world age
772
# when the given world age is higher than the current one.
773
const COMPLETION_WORLD = Ref{UInt}(typemax(UInt))
774

775
# Generate code cache for `REPLInterpreter` now:
776
# This code cache will be available at the world of `COMPLETION_WORLD`,
777
# assuming no invalidation will happen before initializing REPL.
778
# Once REPL is loaded, `REPLInterpreter` will be resilient against future invalidations.
779
code_typed(CC.typeinf, (REPLInterpreter, CC.InferenceState))
780

781
# Method completion on function call expression that look like :(max(1))
782
MAX_METHOD_COMPLETIONS::Int = 40
UNCOV
783
function _complete_methods(ex_org::Expr, context_module::Module, shift::Bool)
×
UNCOV
784
    funct = repl_eval_ex(ex_org.args[1], context_module)
×
UNCOV
785
    funct === nothing && return 2, nothing, [], Set{Symbol}()
×
UNCOV
786
    funct = CC.widenconst(funct)
×
UNCOV
787
    args_ex, kwargs_ex, kwargs_flag = complete_methods_args(ex_org, context_module, true, true)
×
UNCOV
788
    return kwargs_flag, funct, args_ex, kwargs_ex
×
789
end
790

UNCOV
791
function complete_methods(ex_org::Expr, context_module::Module=Main, shift::Bool=false)
×
UNCOV
792
    kwargs_flag, funct, args_ex, kwargs_ex = _complete_methods(ex_org, context_module, shift)::Tuple{Int, Any, Vector{Any}, Set{Symbol}}
×
UNCOV
793
    out = Completion[]
×
UNCOV
794
    kwargs_flag == 2 && return out # one of the kwargs is invalid
×
UNCOV
795
    kwargs_flag == 0 && push!(args_ex, Vararg{Any}) # allow more arguments if there is no semicolon
×
UNCOV
796
    complete_methods!(out, funct, args_ex, kwargs_ex, shift ? -2 : MAX_METHOD_COMPLETIONS, kwargs_flag == 1)
×
UNCOV
797
    return out
×
798
end
799

800
MAX_ANY_METHOD_COMPLETIONS::Int = 10
UNCOV
801
function recursive_explore_names!(seen::IdSet, callee_module::Module, initial_module::Module, exploredmodules::IdSet{Module}=IdSet{Module}())
×
UNCOV
802
    push!(exploredmodules, callee_module)
×
UNCOV
803
    for name in names(callee_module; all=true, imported=true)
×
UNCOV
804
        if !Base.isdeprecated(callee_module, name) && !startswith(string(name), '#') && isdefined(initial_module, name)
×
UNCOV
805
            func = getfield(callee_module, name)
×
UNCOV
806
            if !isa(func, Module)
×
UNCOV
807
                funct = Core.Typeof(func)
×
UNCOV
808
                push!(seen, funct)
×
UNCOV
809
            elseif isa(func, Module) && func ∉ exploredmodules
×
UNCOV
810
                recursive_explore_names!(seen, func, initial_module, exploredmodules)
×
811
            end
812
        end
UNCOV
813
    end
×
814
end
UNCOV
815
function recursive_explore_names(callee_module::Module, initial_module::Module)
×
UNCOV
816
    seen = IdSet{Any}()
×
UNCOV
817
    recursive_explore_names!(seen, callee_module, initial_module)
×
818
    seen
×
819
end
820

UNCOV
821
function complete_any_methods(ex_org::Expr, callee_module::Module, context_module::Module, moreargs::Bool, shift::Bool)
×
UNCOV
822
    out = Completion[]
×
UNCOV
823
    args_ex, kwargs_ex, kwargs_flag = try
×
824
        # this may throw, since we set default_any to false
UNCOV
825
        complete_methods_args(ex_org, context_module, false, false)
×
826
    catch ex
827
        ex isa ArgumentError || rethrow()
×
UNCOV
828
        return out
×
829
    end
UNCOV
830
    kwargs_flag == 2 && return out # one of the kwargs is invalid
×
831

832
    # moreargs determines whether to accept more args, independently of the presence of a
833
    # semicolon for the ".?(" syntax
UNCOV
834
    moreargs && push!(args_ex, Vararg{Any})
×
835

UNCOV
836
    for seen_name in recursive_explore_names(callee_module, callee_module)
×
UNCOV
837
        complete_methods!(out, seen_name, args_ex, kwargs_ex, MAX_ANY_METHOD_COMPLETIONS, false)
×
UNCOV
838
    end
×
839

UNCOV
840
    if !shift
×
841
        # Filter out methods where all arguments are `Any`
UNCOV
842
        filter!(out) do c
×
UNCOV
843
            isa(c, TextCompletion) && return false
×
UNCOV
844
            isa(c, MethodCompletion) || return true
×
UNCOV
845
            sig = Base.unwrap_unionall(c.method.sig)::DataType
×
UNCOV
846
            return !all(@nospecialize(T) -> T === Any || T === Vararg{Any}, sig.parameters[2:end])
×
847
        end
848
    end
849

UNCOV
850
    return out
×
851
end
852

UNCOV
853
function detect_invalid_kwarg!(kwargs_ex::Vector{Symbol}, @nospecialize(x), kwargs_flag::Int, possible_splat::Bool)
×
UNCOV
854
    n = isexpr(x, :kw) ? x.args[1] : x
×
UNCOV
855
    if n isa Symbol
×
UNCOV
856
        push!(kwargs_ex, n)
×
UNCOV
857
        return kwargs_flag
×
858
    end
UNCOV
859
    possible_splat && isexpr(x, :...) && return kwargs_flag
×
UNCOV
860
    return 2 # The kwarg is invalid
×
861
end
862

UNCOV
863
function detect_args_kwargs(funargs::Vector{Any}, context_module::Module, default_any::Bool, broadcasting::Bool)
×
UNCOV
864
    args_ex = Any[]
×
UNCOV
865
    kwargs_ex = Symbol[]
×
UNCOV
866
    kwargs_flag = 0
×
867
    # kwargs_flag is:
868
    # * 0 if there is no semicolon and no invalid kwarg
869
    # * 1 if there is a semicolon and no invalid kwarg
870
    # * 2 if there are two semicolons or more, or if some kwarg is invalid, which
871
    #        means that it is not of the form "bar=foo", "bar" or "bar..."
UNCOV
872
    for i in (1+!broadcasting):length(funargs)
×
UNCOV
873
        ex = funargs[i]
×
UNCOV
874
        if isexpr(ex, :parameters)
×
UNCOV
875
            kwargs_flag = ifelse(kwargs_flag == 0, 1, 2) # there should be at most one :parameters
×
UNCOV
876
            for x in ex.args
×
UNCOV
877
                kwargs_flag = detect_invalid_kwarg!(kwargs_ex, x, kwargs_flag, true)
×
UNCOV
878
            end
×
UNCOV
879
        elseif isexpr(ex, :kw)
×
UNCOV
880
            kwargs_flag = detect_invalid_kwarg!(kwargs_ex, ex, kwargs_flag, false)
×
881
        else
UNCOV
882
            if broadcasting
×
883
                # handle broadcasting, but only handle number of arguments instead of
884
                # argument types
UNCOV
885
                push!(args_ex, Any)
×
886
            else
UNCOV
887
                argt = repl_eval_ex(ex, context_module)
×
UNCOV
888
                if argt !== nothing
×
UNCOV
889
                    push!(args_ex, CC.widenconst(argt))
×
UNCOV
890
                elseif default_any
×
UNCOV
891
                    push!(args_ex, Any)
×
892
                else
893
                    throw(ArgumentError("argument not found"))
×
894
                end
895
            end
896
        end
UNCOV
897
    end
×
UNCOV
898
    return args_ex, Set{Symbol}(kwargs_ex), kwargs_flag
×
899
end
900

UNCOV
901
is_broadcasting_expr(ex::Expr) = ex.head === :. && isexpr(ex.args[2], :tuple)
×
902

UNCOV
903
function complete_methods_args(ex::Expr, context_module::Module, default_any::Bool, allow_broadcasting::Bool)
×
UNCOV
904
    if allow_broadcasting && is_broadcasting_expr(ex)
×
UNCOV
905
        return detect_args_kwargs((ex.args[2]::Expr).args, context_module, default_any, true)
×
906
    end
UNCOV
907
    return detect_args_kwargs(ex.args, context_module, default_any, false)
×
908
end
909

UNCOV
910
function complete_methods!(out::Vector{Completion}, @nospecialize(funct), args_ex::Vector{Any}, kwargs_ex::Set{Symbol}, max_method_completions::Int, exact_nargs::Bool)
×
911
    # Input types and number of arguments
UNCOV
912
    t_in = Tuple{funct, args_ex...}
×
UNCOV
913
    m = Base._methods_by_ftype(t_in, nothing, max_method_completions, Base.get_world_counter(),
×
914
        #=ambig=# true, Ref(typemin(UInt)), Ref(typemax(UInt)), Ptr{Int32}(C_NULL))
UNCOV
915
    if !isa(m, Vector)
×
UNCOV
916
        push!(out, TextCompletion(sprint(Base.show_signature_function, funct) * "( too many methods, use SHIFT-TAB to show )"))
×
UNCOV
917
        return
×
918
    end
UNCOV
919
    for match in m
×
920
        # TODO: if kwargs_ex, filter out methods without kwargs?
UNCOV
921
        push!(out, MethodCompletion(match.spec_types, match.method))
×
UNCOV
922
    end
×
923
    # TODO: filter out methods with wrong number of arguments if `exact_nargs` is set
924
end
925

926
include("latex_symbols.jl")
927
include("emoji_symbols.jl")
928

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

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

941
# Aux function to detect whether we're right after a using or import keyword
UNCOV
942
function get_import_mode(s::String)
×
943
    # allow all of these to start with leading whitespace and macros like @eval and @eval(
944
    # ^\s*(?:@\w+\s*(?:\(\s*)?)?
945

946
    # match simple cases like `using |` and `import  |`
UNCOV
947
    mod_import_match_simple = match(r"^\s*(?:@\w+\s*(?:\(\s*)?)?\b(using|import)\s*$", s)
×
UNCOV
948
    if mod_import_match_simple !== nothing
×
UNCOV
949
        if mod_import_match_simple[1] == "using"
×
UNCOV
950
            return :using_module
×
951
        else
UNCOV
952
            return :import_module
×
953
        end
954
    end
955
    # match module import statements like `using Foo|`, `import Foo, Bar|` and `using Foo.Bar, Baz, |`
UNCOV
956
    mod_import_match = match(r"^\s*(?:@\w+\s*(?:\(\s*)?)?\b(using|import)\s+([\w\.]+(?:\s*,\s*[\w\.]+)*),?\s*$", s)
×
UNCOV
957
    if mod_import_match !== nothing
×
UNCOV
958
        if mod_import_match.captures[1] == "using"
×
UNCOV
959
            return :using_module
×
960
        else
UNCOV
961
            return :import_module
×
962
        end
963
    end
964
    # now match explicit name import statements like `using Foo: |` and `import Foo: bar, baz|`
UNCOV
965
    name_import_match = match(r"^\s*(?:@\w+\s*(?:\(\s*)?)?\b(using|import)\s+([\w\.]+)\s*:\s*([\w@!\s,]+)$", s)
×
UNCOV
966
    if name_import_match !== nothing
×
UNCOV
967
        if name_import_match[1] == "using"
×
UNCOV
968
            return :using_name
×
969
        else
970
            return :import_name
×
971
        end
972
    end
UNCOV
973
    return nothing
×
974
end
975

UNCOV
976
function close_path_completion(dir, path, str, pos)
×
UNCOV
977
    path = unescape_string(replace(path, "\\\$"=>"\$"))
×
UNCOV
978
    path = joinpath(dir, path)
×
979
    # ...except if it's a directory...
UNCOV
980
    Base.isaccessibledir(path) && return false
×
981
    # ...and except if there's already a " at the cursor.
UNCOV
982
    return lastindex(str) <= pos || str[nextind(str, pos)] != '"'
×
983
end
984

UNCOV
985
function bslash_completions(string::String, pos::Int, hint::Bool=false)
×
UNCOV
986
    slashpos = something(findprev(isequal('\\'), string, pos), 0)
×
UNCOV
987
    if (something(findprev(in(bslash_separators), string, pos), 0) < slashpos &&
×
988
        !(1 < slashpos && (string[prevind(string, slashpos)]=='\\')))
989
        # latex / emoji symbol substitution
UNCOV
990
        s = string[slashpos:pos]
×
UNCOV
991
        latex = get(latex_symbols, s, "")
×
UNCOV
992
        if !isempty(latex) # complete an exact match
×
UNCOV
993
            return (true, (Completion[BslashCompletion(latex)], slashpos:pos, true))
×
UNCOV
994
        elseif occursin(subscript_regex, s)
×
UNCOV
995
            sub = map(c -> subscripts[c], s[3:end])
×
UNCOV
996
            return (true, (Completion[BslashCompletion(sub)], slashpos:pos, true))
×
UNCOV
997
        elseif occursin(superscript_regex, s)
×
UNCOV
998
            sup = map(c -> superscripts[c], s[3:end])
×
UNCOV
999
            return (true, (Completion[BslashCompletion(sup)], slashpos:pos, true))
×
1000
        end
UNCOV
1001
        emoji = get(emoji_symbols, s, "")
×
UNCOV
1002
        if !isempty(emoji)
×
UNCOV
1003
            return (true, (Completion[BslashCompletion(emoji)], slashpos:pos, true))
×
1004
        end
1005
        # return possible matches; these cannot be mixed with regular
1006
        # Julian completions as only latex / emoji symbols contain the leading \
UNCOV
1007
        symbol_dict = startswith(s, "\\:") ? emoji_symbols : latex_symbols
×
UNCOV
1008
        namelist = Iterators.filter(k -> startswith(k, s), keys(symbol_dict))
×
UNCOV
1009
        completions = Completion[BslashCompletion(name, "$(symbol_dict[name]) $name") for name in sort!(collect(namelist))]
×
UNCOV
1010
        return (true, (completions, slashpos:pos, true))
×
1011
    end
UNCOV
1012
    return (false, (Completion[], 0:-1, false))
×
1013
end
1014

UNCOV
1015
function dict_identifier_key(str::String, tag::Symbol, context_module::Module=Main)
×
UNCOV
1016
    if tag === :string
×
UNCOV
1017
        str_close = str*"\""
×
UNCOV
1018
    elseif tag === :cmd
×
UNCOV
1019
        str_close = str*"`"
×
1020
    else
UNCOV
1021
        str_close = str
×
1022
    end
UNCOV
1023
    frange, end_of_identifier = find_start_brace(str_close, c_start='[', c_end=']')
×
UNCOV
1024
    isempty(frange) && return (nothing, nothing, nothing)
×
UNCOV
1025
    objstr = str[1:end_of_identifier]
×
UNCOV
1026
    objex = Meta.parse(objstr, raise=false, depwarn=false)
×
UNCOV
1027
    objt = repl_eval_ex(objex, context_module)
×
UNCOV
1028
    isa(objt, Core.Const) || return (nothing, nothing, nothing)
×
UNCOV
1029
    obj = objt.val
×
UNCOV
1030
    isa(obj, AbstractDict) || return (nothing, nothing, nothing)
×
UNCOV
1031
    (Base.haslength(obj) && length(obj)::Int < 1_000_000) || return (nothing, nothing, nothing)
×
UNCOV
1032
    begin_of_key = something(findnext(!isspace, str, nextind(str, end_of_identifier) + 1), # +1 for [
×
1033
                             lastindex(str)+1)
UNCOV
1034
    return (obj, str[begin_of_key:end], begin_of_key)
×
1035
end
1036

1037
# This needs to be a separate non-inlined function, see #19441
UNCOV
1038
@noinline function find_dict_matches(identifier::AbstractDict, partial_key)
×
UNCOV
1039
    matches = String[]
×
UNCOV
1040
    for key in keys(identifier)
×
UNCOV
1041
        rkey = repr(key)
×
UNCOV
1042
        startswith(rkey,partial_key) && push!(matches,rkey)
×
UNCOV
1043
    end
×
UNCOV
1044
    return matches
×
1045
end
1046

1047
# Identify an argument being completed in a method call. If the argument is empty, method
1048
# suggestions will be provided instead of argument completions.
UNCOV
1049
function identify_possible_method_completion(partial, last_idx)
×
UNCOV
1050
    fail = 0:-1, Expr(:nothing), 0:-1, 0
×
1051

1052
    # First, check that the last punctuation is either ',', ';' or '('
UNCOV
1053
    idx_last_punct = something(findprev(x -> ispunct(x) && x != '_' && x != '!', partial, last_idx), 0)::Int
×
UNCOV
1054
    idx_last_punct == 0 && return fail
×
UNCOV
1055
    last_punct = partial[idx_last_punct]
×
UNCOV
1056
    last_punct == ',' || last_punct == ';' || last_punct == '(' || return fail
×
1057

1058
    # Then, check that `last_punct` is only followed by an identifier or nothing
UNCOV
1059
    before_last_word_start = something(findprev(in(non_identifier_chars), partial, last_idx), 0)
×
UNCOV
1060
    before_last_word_start == 0 && return fail
×
UNCOV
1061
    all(isspace, @view partial[nextind(partial, idx_last_punct):before_last_word_start]) || return fail
×
1062

1063
    # Check that `last_punct` is either the last '(' or placed after a previous '('
UNCOV
1064
    frange, method_name_end = find_start_brace(@view partial[1:idx_last_punct])
×
UNCOV
1065
    method_name_end ∈ frange || return fail
×
1066

1067
    # Strip the preceding ! operators, if any, and close the expression with a ')'
UNCOV
1068
    s = replace(partial[frange], r"\G\!+([^=\(]+)" => s"\1"; count=1) * ')'
×
UNCOV
1069
    ex = Meta.parse(s, raise=false, depwarn=false)
×
UNCOV
1070
    isa(ex, Expr) || return fail
×
1071

1072
    # `wordrange` is the position of the last argument to complete
UNCOV
1073
    wordrange = nextind(partial, before_last_word_start):last_idx
×
UNCOV
1074
    return frange, ex, wordrange, method_name_end
×
1075
end
1076

1077
# Provide completion for keyword arguments in function calls
UNCOV
1078
function complete_keyword_argument(partial::String, last_idx::Int, context_module::Module;
×
1079
                                   shift::Bool=false)
UNCOV
1080
    frange, ex, wordrange, = identify_possible_method_completion(partial, last_idx)
×
UNCOV
1081
    fail = Completion[], 0:-1, frange
×
UNCOV
1082
    ex.head === :call || is_broadcasting_expr(ex) || return fail
×
1083

UNCOV
1084
    kwargs_flag, funct, args_ex, kwargs_ex = _complete_methods(ex, context_module, true)::Tuple{Int, Any, Vector{Any}, Set{Symbol}}
×
UNCOV
1085
    kwargs_flag == 2 && return fail # one of the previous kwargs is invalid
×
1086

UNCOV
1087
    methods = Completion[]
×
UNCOV
1088
    complete_methods!(methods, funct, Any[Vararg{Any}], kwargs_ex, shift ? -1 : MAX_METHOD_COMPLETIONS, kwargs_flag == 1)
×
1089
    # TODO: use args_ex instead of Any[Vararg{Any}] and only provide kwarg completion for
1090
    # method calls compatible with the current arguments.
1091

1092
    # For each method corresponding to the function call, provide completion suggestions
1093
    # for each keyword that starts like the last word and that is not already used
1094
    # previously in the expression. The corresponding suggestion is "kwname=".
1095
    # If the keyword corresponds to an existing name, also include "kwname" as a suggestion
1096
    # since the syntax "foo(; kwname)" is equivalent to "foo(; kwname=kwname)".
UNCOV
1097
    last_word = partial[wordrange] # the word to complete
×
UNCOV
1098
    kwargs = Set{String}()
×
UNCOV
1099
    for m in methods
×
1100
        # if MAX_METHOD_COMPLETIONS is hit a single TextCompletion is return by complete_methods! with an explanation
1101
        # which can be ignored here
UNCOV
1102
        m isa TextCompletion && continue
×
UNCOV
1103
        m::MethodCompletion
×
UNCOV
1104
        possible_kwargs = Base.kwarg_decl(m.method)
×
UNCOV
1105
        current_kwarg_candidates = String[]
×
UNCOV
1106
        for _kw in possible_kwargs
×
UNCOV
1107
            kw = String(_kw)
×
UNCOV
1108
            if !endswith(kw, "...") && startswith(kw, last_word) && _kw ∉ kwargs_ex
×
UNCOV
1109
                push!(current_kwarg_candidates, kw)
×
1110
            end
UNCOV
1111
        end
×
UNCOV
1112
        union!(kwargs, current_kwarg_candidates)
×
UNCOV
1113
    end
×
1114

UNCOV
1115
    suggestions = Completion[KeywordArgumentCompletion(kwarg) for kwarg in kwargs]
×
1116

1117
    # Only add these if not in kwarg space. i.e. not in `foo(; `
UNCOV
1118
    if kwargs_flag == 0
×
UNCOV
1119
        complete_symbol!(suggestions, #=prefix=#nothing, last_word, context_module; shift)
×
UNCOV
1120
        complete_keyval!(suggestions, last_word)
×
1121
    end
1122

UNCOV
1123
    return sort!(suggestions, by=named_completion_completion), wordrange
×
1124
end
1125

UNCOV
1126
function get_loading_candidates(pkgstarts::String, project_file::String)
×
UNCOV
1127
    loading_candidates = String[]
×
UNCOV
1128
    d = Base.parsed_toml(project_file)
×
UNCOV
1129
    pkg = get(d, "name", nothing)::Union{String, Nothing}
×
UNCOV
1130
    if pkg !== nothing && startswith(pkg, pkgstarts)
×
UNCOV
1131
        push!(loading_candidates, pkg)
×
1132
    end
UNCOV
1133
    deps = get(d, "deps", nothing)::Union{Dict{String, Any}, Nothing}
×
UNCOV
1134
    if deps !== nothing
×
UNCOV
1135
        for (pkg, _) in deps
×
UNCOV
1136
            startswith(pkg, pkgstarts) && push!(loading_candidates, pkg)
×
UNCOV
1137
        end
×
1138
    end
UNCOV
1139
    return loading_candidates
×
1140
end
1141

UNCOV
1142
function complete_loading_candidates!(suggestions::Vector{Completion}, pkgstarts::String, project_file::String)
×
UNCOV
1143
    for name in get_loading_candidates(pkgstarts, project_file)
×
UNCOV
1144
        push!(suggestions, PackageCompletion(name))
×
UNCOV
1145
    end
×
UNCOV
1146
    return suggestions
×
1147
end
1148

UNCOV
1149
function complete_identifiers!(suggestions::Vector{Completion},
×
1150
                               context_module::Module, string::String, name::String,
1151
                               pos::Int, separatorpos::Int, startpos::Int;
1152
                               comp_keywords::Bool=false,
1153
                               complete_modules_only::Bool=false,
1154
                               shift::Bool=false)
UNCOV
1155
    if comp_keywords
×
UNCOV
1156
        complete_keyword!(suggestions, name)
×
UNCOV
1157
        complete_keyval!(suggestions, name)
×
1158
    end
UNCOV
1159
    if separatorpos > 1 && (string[separatorpos] == '.' || string[separatorpos] == ':')
×
UNCOV
1160
        s = string[1:prevind(string, separatorpos)]
×
1161
        # First see if the whole string up to `pos` is a valid expression. If so, use it.
UNCOV
1162
        prefix = Meta.parse(s, raise=false, depwarn=false)
×
UNCOV
1163
        if isexpr(prefix, :incomplete)
×
UNCOV
1164
            s = string[startpos:pos]
×
1165
            # Heuristic to find the start of the expression. TODO: This would be better
1166
            # done with a proper error-recovering parser.
UNCOV
1167
            if 0 < startpos <= lastindex(string) && string[startpos] == '.'
×
UNCOV
1168
                i = prevind(string, startpos)
×
UNCOV
1169
                while 0 < i
×
UNCOV
1170
                    c = string[i]
×
UNCOV
1171
                    if c in (')', ']')
×
1172
                        if c == ')'
×
1173
                            c_start = '('
×
1174
                            c_end = ')'
×
1175
                        elseif c == ']'
×
1176
                            c_start = '['
×
1177
                            c_end = ']'
×
1178
                        end
1179
                        frange, end_of_identifier = find_start_brace(string[1:prevind(string, i)], c_start=c_start, c_end=c_end)
×
1180
                        isempty(frange) && break # unbalanced parens
×
1181
                        startpos = first(frange)
×
1182
                        i = prevind(string, startpos)
×
UNCOV
1183
                    elseif c in ('\'', '\"', '\`')
×
1184
                        s = "$c$c"*string[startpos:pos]
×
1185
                        break
×
1186
                    else
UNCOV
1187
                        break
×
1188
                    end
1189
                    s = string[startpos:pos]
×
1190
                end
×
1191
            end
UNCOV
1192
            if something(findlast(in(non_identifier_chars), s), 0) < something(findlast(isequal('.'), s), 0)
×
UNCOV
1193
                lookup_name, name = rsplit(s, ".", limit=2)
×
UNCOV
1194
                name = String(name)
×
UNCOV
1195
                prefix = Meta.parse(lookup_name, raise=false, depwarn=false)
×
1196
            end
UNCOV
1197
            isexpr(prefix, :incomplete) && (prefix = nothing)
×
UNCOV
1198
        elseif isexpr(prefix, (:using, :import))
×
UNCOV
1199
            arglast = prefix.args[end] # focus on completion to the last argument
×
UNCOV
1200
            if isexpr(arglast, :.)
×
1201
                # We come here for cases like:
1202
                # - `string`: "using Mod1.Mod2.M"
1203
                # - `ex`: :(using Mod1.Mod2)
1204
                # - `name`: "M"
1205
                # Now we transform `ex` to `:(Mod1.Mod2)` to allow `complete_symbol!` to
1206
                # complete for inner modules whose name starts with `M`.
1207
                # Note that `complete_modules_only=true` is set within `completions`
UNCOV
1208
                prefix = nothing
×
UNCOV
1209
                firstdot = true
×
UNCOV
1210
                for arg = arglast.args
×
UNCOV
1211
                    if arg === :.
×
1212
                        # override `context_module` if multiple `.` accessors are used
UNCOV
1213
                        if firstdot
×
UNCOV
1214
                            firstdot = false
×
1215
                        else
UNCOV
1216
                            context_module = parentmodule(context_module)
×
1217
                        end
UNCOV
1218
                    elseif arg isa Symbol
×
UNCOV
1219
                        if prefix === nothing
×
UNCOV
1220
                            prefix = arg
×
1221
                        else
UNCOV
1222
                            prefix = Expr(:., prefix, QuoteNode(arg))
×
1223
                        end
1224
                    else # invalid expression
1225
                        prefix = nothing
×
1226
                        break
×
1227
                    end
UNCOV
1228
                end
×
1229
            end
UNCOV
1230
        elseif isexpr(prefix, :call) && length(prefix.args) > 1
×
UNCOV
1231
            isinfix = s[end] != ')'
×
1232
            # A complete call expression that does not finish with ')' is an infix call.
UNCOV
1233
            if !isinfix
×
1234
                # Handle infix call argument completion of the form bar + foo(qux).
UNCOV
1235
                frange, end_of_identifier = find_start_brace(@view s[1:prevind(s, end)])
×
UNCOV
1236
                if !isempty(frange) # if find_start_brace fails to find the brace just continue
×
UNCOV
1237
                    isinfix = Meta.parse(@view(s[frange[1]:end]), raise=false, depwarn=false) == prefix.args[end]
×
1238
                end
1239
            end
UNCOV
1240
            if isinfix
×
UNCOV
1241
                prefix = prefix.args[end]
×
1242
            end
UNCOV
1243
        elseif isexpr(prefix, :macrocall) && length(prefix.args) > 1
×
1244
            # allow symbol completions within potentially incomplete macrocalls
UNCOV
1245
            if s[end] ≠ '`' && s[end] ≠ ')'
×
UNCOV
1246
                prefix = prefix.args[end]
×
1247
            end
1248
        end
1249
    else
UNCOV
1250
        prefix = nothing
×
1251
    end
UNCOV
1252
    complete_symbol!(suggestions, prefix, name, context_module; complete_modules_only, shift)
×
UNCOV
1253
    return suggestions
×
1254
end
1255

UNCOV
1256
function completions(string::String, pos::Int, context_module::Module=Main, shift::Bool=true, hint::Bool=false)
×
1257
    # First parse everything up to the current position
UNCOV
1258
    partial = string[1:pos]
×
UNCOV
1259
    inc_tag = Base.incomplete_tag(Meta.parse(partial, raise=false, depwarn=false))
×
1260

UNCOV
1261
    if !hint # require a tab press for completion of these
×
1262
        # ?(x, y)TAB lists methods you can call with these objects
1263
        # ?(x, y TAB lists methods that take these objects as the first two arguments
1264
        # MyModule.?(x, y)TAB restricts the search to names in MyModule
UNCOV
1265
        rexm = match(r"(\w+\.|)\?\((.*)$", partial)
×
UNCOV
1266
        if rexm !== nothing
×
1267
            # Get the module scope
UNCOV
1268
            if isempty(rexm.captures[1])
×
UNCOV
1269
                callee_module = context_module
×
1270
            else
UNCOV
1271
                modname = Symbol(rexm.captures[1][1:end-1])
×
UNCOV
1272
                if isdefined(context_module, modname)
×
UNCOV
1273
                    callee_module = getfield(context_module, modname)
×
UNCOV
1274
                    if !isa(callee_module, Module)
×
1275
                        callee_module = context_module
×
1276
                    end
1277
                else
1278
                    callee_module = context_module
×
1279
                end
1280
            end
UNCOV
1281
            moreargs = !endswith(rexm.captures[2], ')')
×
UNCOV
1282
            callstr = "_(" * rexm.captures[2]
×
UNCOV
1283
            if moreargs
×
UNCOV
1284
                callstr *= ')'
×
1285
            end
UNCOV
1286
            ex_org = Meta.parse(callstr, raise=false, depwarn=false)
×
UNCOV
1287
            if isa(ex_org, Expr)
×
UNCOV
1288
                return complete_any_methods(ex_org, callee_module::Module, context_module, moreargs, shift), (0:length(rexm.captures[1])+1) .+ rexm.offset, false
×
1289
            end
1290
        end
1291
    end
1292

1293
    # if completing a key in a Dict
UNCOV
1294
    identifier, partial_key, loc = dict_identifier_key(partial, inc_tag, context_module)
×
UNCOV
1295
    if identifier !== nothing
×
UNCOV
1296
        matches = find_dict_matches(identifier, partial_key)
×
UNCOV
1297
        length(matches)==1 && (lastindex(string) <= pos || string[nextind(string,pos)] != ']') && (matches[1]*=']')
×
UNCOV
1298
        length(matches)>0 && return Completion[DictCompletion(identifier, match) for match in sort!(matches)], loc::Int:pos, true
×
1299
    end
1300

UNCOV
1301
    suggestions = Completion[]
×
1302

1303
    # Check if this is a var"" string macro that should be completed like
1304
    # an identifier rather than a string.
1305
    # TODO: It would be nice for the parser to give us more information here
1306
    # so that we can lookup the macro by identity rather than pattern matching
1307
    # its invocation.
UNCOV
1308
    varrange = findprev("var\"", string, pos)
×
1309

UNCOV
1310
    expanded = nothing
×
UNCOV
1311
    was_expanded = false
×
1312

UNCOV
1313
    if varrange !== nothing
×
UNCOV
1314
        ok, ret = bslash_completions(string, pos)
×
UNCOV
1315
        ok && return ret
×
UNCOV
1316
        startpos = first(varrange) + 4
×
UNCOV
1317
        separatorpos = something(findprev(isequal('.'), string, first(varrange)-1), 0)
×
UNCOV
1318
        name = string[startpos:pos]
×
UNCOV
1319
        complete_identifiers!(suggestions, context_module, string, name,
×
1320
                              pos, separatorpos, startpos;
1321
                              shift)
UNCOV
1322
        return sort!(unique!(named_completion, suggestions), by=named_completion_completion), (separatorpos+1):pos, true
×
UNCOV
1323
    elseif inc_tag === :cmd
×
1324
        # TODO: should this call shell_completions instead of partially reimplementing it?
UNCOV
1325
        let m = match(r"[\t\n\r\"`><=*?|]| (?!\\)", reverse(partial)) # fuzzy shell_parse in reverse
×
UNCOV
1326
            startpos = nextind(partial, reverseind(partial, m.offset))
×
UNCOV
1327
            r = startpos:pos
×
UNCOV
1328
            scs::String = string[r]
×
1329

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

UNCOV
1339
            path::String = replace(scs, r"(\\+)\g1(\\?)`" => "\1\2`") # fuzzy unescape_raw_string: match an even number of \ before ` and replace with half as many
×
1340
            # This expansion with "\\ "=>' ' replacement and shell_escape=true
1341
            # assumes the path isn't further quoted within the cmd backticks.
UNCOV
1342
            path = replace(path, r"\\ " => " ", r"\$" => "\$") # fuzzy shell_parse (reversed by shell_escape_posixly)
×
UNCOV
1343
            paths, dir, success = complete_path(path, shell_escape=true, raw_escape=true)
×
1344

UNCOV
1345
            if success && !isempty(dir)
×
1346
                let dir = do_raw_escape(do_shell_escape(dir))
×
1347
                    # if escaping of dir matches scs prefix, remove that from the completions
1348
                    # otherwise make it the whole completion
1349
                    if endswith(dir, "/") && startswith(scs, dir)
×
1350
                        r = (startpos + sizeof(dir)):pos
×
1351
                    elseif startswith(scs, dir * "/")
×
1352
                        r = nextind(string, startpos + sizeof(dir)):pos
×
1353
                    else
1354
                        map!(paths, paths) do c::PathCompletion
×
1355
                            p = dir * "/" * c.path
×
1356
                            was_expanded && (p = contractuser(p))
×
1357
                            return PathCompletion(p)
×
1358
                        end
1359
                    end
1360
                end
1361
            end
UNCOV
1362
            if isempty(paths) && !hint && was_expanded
×
1363
                # if not able to provide completions, not hinting, and ~ expansion was possible, return ~ expansion
1364
                return expanded::Completions
×
1365
            else
UNCOV
1366
                return sort!(paths, by=p->p.path), r::UnitRange{Int}, success
×
1367
            end
1368
        end
UNCOV
1369
    elseif inc_tag === :string
×
1370
        # Find first non-escaped quote
UNCOV
1371
        let m = match(r"\"(?!\\)", reverse(partial))
×
UNCOV
1372
            startpos = nextind(partial, reverseind(partial, m.offset))
×
UNCOV
1373
            r = startpos:pos
×
UNCOV
1374
            scs::String = string[r]
×
1375

UNCOV
1376
            expanded = complete_expanduser(scs, r)
×
UNCOV
1377
            was_expanded = expanded[3]
×
UNCOV
1378
            if was_expanded
×
UNCOV
1379
                scs = (only(expanded[1])::PathCompletion).path
×
1380
                # If tab press, ispath and user expansion available, return it now
1381
                # otherwise see if we can complete the path further before returning with expanded ~
UNCOV
1382
                !hint && ispath(scs) && return expanded::Completions
×
1383
            end
1384

UNCOV
1385
            path = try
×
UNCOV
1386
                unescape_string(replace(scs, "\\\$"=>"\$"))
×
1387
            catch ex
1388
                ex isa ArgumentError || rethrow()
×
UNCOV
1389
                nothing
×
1390
            end
UNCOV
1391
            if !isnothing(path)
×
UNCOV
1392
                paths, dir, success = complete_path(path::String, string_escape=true)
×
1393

UNCOV
1394
                if length(paths) == 1
×
UNCOV
1395
                    p = (paths[1]::PathCompletion).path
×
UNCOV
1396
                    hint && was_expanded && (p = contractuser(p))
×
UNCOV
1397
                    if close_path_completion(dir, p, path, pos)
×
UNCOV
1398
                        paths[1] = PathCompletion(p * "\"")
×
1399
                    end
1400
                end
1401

UNCOV
1402
                if success && !isempty(dir)
×
UNCOV
1403
                    let dir = do_string_escape(dir)
×
1404
                        # if escaping of dir matches scs prefix, remove that from the completions
1405
                        # otherwise make it the whole completion
UNCOV
1406
                        if endswith(dir, "/") && startswith(scs, dir)
×
UNCOV
1407
                            r = (startpos + sizeof(dir)):pos
×
UNCOV
1408
                        elseif startswith(scs, dir * "/") && dir != dirname(homedir())
×
UNCOV
1409
                            was_expanded && (dir = contractuser(dir))
×
UNCOV
1410
                            r = nextind(string, startpos + sizeof(dir)):pos
×
1411
                        else
1412
                            map!(paths, paths) do c::PathCompletion
×
1413
                                p = dir * "/" * c.path
×
1414
                                hint && was_expanded && (p = contractuser(p))
×
1415
                                return PathCompletion(p)
×
1416
                            end
1417
                        end
1418
                    end
1419
                end
1420

1421
                # Fallthrough allowed so that Latex symbols can be completed in strings
UNCOV
1422
                if success
×
UNCOV
1423
                    return sort!(paths, by=p->p.path), r::UnitRange{Int}, success
×
UNCOV
1424
                elseif !hint && was_expanded
×
1425
                    # if not able to provide completions, not hinting, and ~ expansion was possible, return ~ expansion
UNCOV
1426
                    return expanded::Completions
×
1427
                end
1428
            end
1429
        end
1430
    end
1431
    # if path has ~ and we didn't find any paths to complete just return the expanded path
UNCOV
1432
    was_expanded && return expanded::Completions
×
1433

UNCOV
1434
    ok, ret = bslash_completions(string, pos)
×
UNCOV
1435
    ok && return ret
×
1436

1437
    # Make sure that only bslash_completions is working on strings
UNCOV
1438
    inc_tag === :string && return Completion[], 0:-1, false
×
UNCOV
1439
    if inc_tag === :other
×
UNCOV
1440
        frange, ex, wordrange, method_name_end = identify_possible_method_completion(partial, pos)
×
UNCOV
1441
        if last(frange) != -1 && all(isspace, @view partial[wordrange]) # no last argument to complete
×
UNCOV
1442
            if ex.head === :call
×
UNCOV
1443
                return complete_methods(ex, context_module, shift), first(frange):method_name_end, false
×
UNCOV
1444
            elseif is_broadcasting_expr(ex)
×
UNCOV
1445
                return complete_methods(ex, context_module, shift), first(frange):(method_name_end - 1), false
×
1446
            end
1447
        end
UNCOV
1448
    elseif inc_tag === :comment
×
UNCOV
1449
        return Completion[], 0:-1, false
×
1450
    end
1451

1452
    # Check whether we can complete a keyword argument in a function call
UNCOV
1453
    kwarg_completion, wordrange = complete_keyword_argument(partial, pos, context_module; shift)
×
UNCOV
1454
    isempty(wordrange) || return kwarg_completion, wordrange, !isempty(kwarg_completion)
×
1455

UNCOV
1456
    startpos = nextind(string, something(findprev(in(non_identifier_chars), string, pos), 0))
×
1457
    # strip preceding ! operator
UNCOV
1458
    if (m = match(r"\G\!+", partial, startpos)) isa RegexMatch
×
UNCOV
1459
        startpos += length(m.match)
×
1460
    end
1461

UNCOV
1462
    separatorpos = something(findprev(isequal('.'), string, pos), 0)
×
UNCOV
1463
    namepos = max(startpos, separatorpos+1)
×
UNCOV
1464
    name = string[namepos:pos]
×
UNCOV
1465
    import_mode = get_import_mode(string)
×
UNCOV
1466
    if import_mode === :using_module || import_mode === :import_module
×
1467
        # Given input lines like `using Foo|`, `import Foo, Bar|` and `using Foo.Bar, Baz, |`:
1468
        # Let's look only for packages and modules we can reach from here
1469

1470
        # If there's no dot, we're in toplevel, so we should
1471
        # also search for packages
UNCOV
1472
        s = string[startpos:pos]
×
UNCOV
1473
        if separatorpos <= startpos
×
UNCOV
1474
            for dir in Base.load_path()
×
UNCOV
1475
                if basename(dir) in Base.project_names && isfile(dir)
×
UNCOV
1476
                    complete_loading_candidates!(suggestions, s, dir)
×
1477
                end
UNCOV
1478
                isdir(dir) || continue
×
UNCOV
1479
                for entry in _readdirx(dir)
×
UNCOV
1480
                    pname = entry.name
×
UNCOV
1481
                    if pname[1] != '.' && pname != "METADATA" &&
×
1482
                        pname != "REQUIRE" && startswith(pname, s)
1483
                        # Valid file paths are
1484
                        #   <Mod>.jl
1485
                        #   <Mod>/src/<Mod>.jl
1486
                        #   <Mod>.jl/src/<Mod>.jl
UNCOV
1487
                        if isfile(entry)
×
1488
                            endswith(pname, ".jl") && push!(suggestions,
×
1489
                                                            PackageCompletion(pname[1:prevind(pname, end-2)]))
1490
                        else
UNCOV
1491
                            mod_name = if endswith(pname, ".jl")
×
1492
                                pname[1:prevind(pname, end-2)]
×
1493
                            else
UNCOV
1494
                                pname
×
1495
                            end
UNCOV
1496
                            if isfile(joinpath(entry, "src",
×
1497
                                               "$mod_name.jl"))
UNCOV
1498
                                push!(suggestions, PackageCompletion(mod_name))
×
1499
                            end
1500
                        end
1501
                    end
UNCOV
1502
                end
×
UNCOV
1503
            end
×
1504
        end
UNCOV
1505
        comp_keywords = false
×
UNCOV
1506
        complete_modules_only = import_mode === :using_module # allow completion for `import Mod.name` (where `name` is not a module)
×
UNCOV
1507
    elseif import_mode === :using_name || import_mode === :import_name
×
1508
        # `using Foo: |` and `import Foo: bar, baz|`
UNCOV
1509
        separatorpos = findprev(isequal(':'), string, pos)::Int
×
UNCOV
1510
        comp_keywords = false
×
UNCOV
1511
        complete_modules_only = false
×
1512
    else
UNCOV
1513
        comp_keywords = !isempty(name) && startpos > separatorpos
×
UNCOV
1514
        complete_modules_only = false
×
1515
    end
1516

UNCOV
1517
    complete_identifiers!(suggestions, context_module, string, name,
×
1518
                          pos, separatorpos, startpos;
1519
                          comp_keywords, complete_modules_only, shift)
UNCOV
1520
    return sort!(unique!(named_completion, suggestions), by=named_completion_completion), namepos:pos, true
×
1521
end
1522

UNCOV
1523
function shell_completions(string, pos, hint::Bool=false)
×
1524
    # First parse everything up to the current position
UNCOV
1525
    scs = string[1:pos]
×
UNCOV
1526
    args, last_arg_start = try
×
UNCOV
1527
        Base.shell_parse(scs, true)::Tuple{Expr,Int}
×
1528
    catch ex
UNCOV
1529
        ex isa ArgumentError || ex isa ErrorException || rethrow()
×
UNCOV
1530
        return Completion[], 0:-1, false
×
1531
    end
UNCOV
1532
    ex = args.args[end]::Expr
×
1533
    # Now look at the last thing we parsed
UNCOV
1534
    isempty(ex.args) && return Completion[], 0:-1, false
×
UNCOV
1535
    lastarg = ex.args[end]
×
1536
    # As Base.shell_parse throws away trailing spaces (unless they are escaped),
1537
    # we need to special case here.
1538
    # If the last char was a space, but shell_parse ignored it search on "".
UNCOV
1539
    if isexpr(lastarg, :incomplete) || isexpr(lastarg, :error)
×
UNCOV
1540
        partial = string[last_arg_start:pos]
×
UNCOV
1541
        ret, range = completions(partial, lastindex(partial), Main, true, hint)
×
UNCOV
1542
        range = range .+ (last_arg_start - 1)
×
UNCOV
1543
        return ret, range, true
×
UNCOV
1544
    elseif endswith(scs, ' ') && !endswith(scs, "\\ ")
×
UNCOV
1545
        r = pos+1:pos
×
UNCOV
1546
        paths, dir, success = complete_path("", use_envpath=false, shell_escape=true)
×
UNCOV
1547
        return paths, r, success
×
UNCOV
1548
    elseif all(@nospecialize(arg) -> arg isa AbstractString, ex.args)
×
1549
        # Join these and treat this as a path
UNCOV
1550
        path::String = join(ex.args)
×
UNCOV
1551
        r = last_arg_start:pos
×
1552

1553
        # Also try looking into the env path if the user wants to complete the first argument
UNCOV
1554
        use_envpath = length(args.args) < 2
×
1555

UNCOV
1556
        expanded = complete_expanduser(path, r)
×
UNCOV
1557
        was_expanded = expanded[3]
×
UNCOV
1558
        if was_expanded
×
UNCOV
1559
            path = (only(expanded[1])::PathCompletion).path
×
1560
            # If tab press, ispath and user expansion available, return it now
1561
            # otherwise see if we can complete the path further before returning with expanded ~
UNCOV
1562
            !hint && ispath(path) && return expanded::Completions
×
1563
        end
1564

UNCOV
1565
        paths, dir, success = complete_path(path, use_envpath=use_envpath, shell_escape=true, contract_user=was_expanded)
×
1566

UNCOV
1567
        if success && !isempty(dir)
×
UNCOV
1568
            let dir = do_shell_escape(dir)
×
1569
                # if escaping of dir matches scs prefix, remove that from the completions
1570
                # otherwise make it the whole completion
UNCOV
1571
                partial = string[last_arg_start:pos]
×
UNCOV
1572
                if endswith(dir, "/") && startswith(partial, dir)
×
UNCOV
1573
                    r = (last_arg_start + sizeof(dir)):pos
×
UNCOV
1574
                elseif startswith(partial, dir * "/")
×
UNCOV
1575
                    r = nextind(string, last_arg_start + sizeof(dir)):pos
×
1576
                else
UNCOV
1577
                    map!(paths, paths) do c::PathCompletion
×
UNCOV
1578
                        return PathCompletion(dir * "/" * c.path)
×
1579
                    end
1580
                end
1581
            end
1582
        end
1583
        # if ~ was expanded earlier and the incomplete string isn't a path
1584
        # return the path with contracted user to match what the hint shows. Otherwise expand ~
1585
        # i.e. require two tab presses to expand user
UNCOV
1586
        if was_expanded && !ispath(path)
×
1587
            map!(paths, paths) do c::PathCompletion
×
1588
                PathCompletion(contractuser(c.path))
×
1589
            end
1590
        end
UNCOV
1591
        return paths, r, success
×
1592
    end
UNCOV
1593
    return Completion[], 0:-1, false
×
1594
end
1595

1596
function __init__()
1✔
1597
    COMPLETION_WORLD[] = Base.get_world_counter()
1✔
1598
    return nothing
1✔
1599
end
1600

1601
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