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

JuliaLang / julia / 1652

20 Apr 2026 08:38PM UTC coverage: 77.962% (+0.3%) from 77.623%
1652

push

buildkite

web-flow
codegen: Propagate `ipo_purity_bits` to LLVM function attributes (#61394)

Translate Julia's inferred effects (consistent, effect_free, nothrow,
terminates, notaskstate) into LLVM function attributes so that
middle-end passes like GVN, LICM, and DSE can exploit them.

The key design insight is that GC interactions don't need to be visible
before GC lowering. Call-site declarations get optimistic memory
attributes (e.g. memory(argmem: read)) that enable pre-GC optimizations,
then LateLowerGCFrame widens them to memory(readwrite) before safepoint
analysis so post-GC passes see correct semantics.

Attributes added:
- nounwind: for nothrow functions (with uwtable(async) on definitions
  to preserve .eh_frame for stack scanning)
- mustprogress: for terminating functions
- willreturn: for nothrow+terminating functions
- memory(argmem: read): for consistent+effect_free functions with no
  user-facing pointer arguments (call-site declarations only)
- readnone on gcstack param: for notaskstate functions, so LICM can
  hoist pure calls past heap stores
- "julia.safepoint" marker: on all call-site declarations, used by
  LateLowerGCFrame to identify and widen optimistic attrs

LateLowerGCFrame strips all optimistic attributes (memory effects,
readnone on gcstack) from both call instructions and function
declarations before safepoint analysis runs.

Previously explored in #47844

Developed with Claude

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Gabriel Baraldi <28694980+gbaraldi@users.noreply.github.com>

65490 of 84002 relevant lines covered (77.96%)

23535049.1 hits per line

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

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

3
"""
4
Run Evaluate Print Loop (REPL)
5

6
Example minimal code
7

8
```julia
9
import REPL
10
term = REPL.Terminals.TTYTerminal("dumb", stdin, stdout, stderr)
11
repl = REPL.LineEditREPL(term, true)
12
REPL.run_repl(repl)
13
```
14
"""
15
module REPL
16

17
Base.Experimental.@optlevel 1
18
Base.Experimental.@max_methods 1
19

20
function UndefVarError_REPL_hint(io::IO, ex::UndefVarError)
9✔
21
    var = ex.var
9✔
22
    if var === :or
9✔
23
        print(io, "\nSuggestion: Use `||` for short-circuiting boolean OR.")
×
24
    elseif var === :and
9✔
25
        print(io, "\nSuggestion: Use `&&` for short-circuiting boolean AND.")
×
26
    elseif var === :help
9✔
27
        println(io)
×
28
        # Show friendly help message when user types help or help() and help is undefined
29
        show(io, MIME("text/plain"), Base.Docs.parsedoc(Base.Docs.keywords[:help]))
×
30
    elseif var === :quit
9✔
31
        print(io, "\nSuggestion: To exit Julia, use Ctrl-D, or type exit() and press enter.")
×
32
    end
33
end
34

35
function __init__()
20✔
36
    Base.REPL_MODULE_REF[] = REPL
20✔
37
    Base.Experimental.register_error_hint(UndefVarError_REPL_hint, UndefVarError)
20✔
38
    return nothing
20✔
39
end
40

41
using Base.Meta, Sockets, StyledStrings
42
using JuliaSyntaxHighlighting
43
using Dates: now, UTC
44
import InteractiveUtils
45
import FileWatching
46
import Base.JuliaSyntax: kind, @K_str, @KSet_str, Tokenize.tokenize
47

48
export
49
    AbstractREPL,
50
    BasicREPL,
51
    LineEditREPL,
52
    StreamREPL
53

54
public TerminalMenus
55

56
import Base:
57
    AbstractDisplay,
58
    display,
59
    show,
60
    AnyDict,
61
    ==
62

63
_displaysize(io::IO) = displaysize(io)::Tuple{Int,Int}
189✔
64

65
using Base.Terminals
66

67
abstract type AbstractREPL end
68

69
include("options.jl")
70
include("StylingPasses.jl")
71
using .StylingPasses
72

73
function histsearch end # To work around circular dependency
74

75
include("LineEdit.jl")
76
using .LineEdit
77
import .LineEdit:
78
    CompletionProvider,
79
    HistoryProvider,
80
    add_history,
81
    complete_line,
82
    history_next,
83
    history_next_prefix,
84
    history_prev,
85
    history_prev_prefix,
86
    history_first,
87
    history_last,
88
    history_search,
89
    setmodifiers!,
90
    terminal,
91
    MIState,
92
    PromptState,
93
    mode_idx
94

95
include("SyntaxUtil.jl")
96
include("REPLCompletions.jl")
97
using .REPLCompletions
98

99
include("TerminalMenus/TerminalMenus.jl")
100
include("docview.jl")
101

102
include("History/History.jl")
103
using .History
104

105
histsearch(args...) = runsearch(args...)
×
106

107
include("Pkg_beforeload.jl")
108

109
@nospecialize # use only declared type signatures
110

111
answer_color(::AbstractREPL) = ""
×
112

113
const JULIA_PROMPT = "julia> "
114
const PKG_PROMPT = "pkg> "
115
const SHELL_PROMPT = "shell> "
116
const HELP_PROMPT = "help?> "
117

118
mutable struct REPLBackend
119
    "channel for AST"
120
    repl_channel::Channel{Any}
121
    "channel for results: (value, iserror)"
122
    response_channel::Channel{Any}
123
    "flag indicating the state of this backend"
124
    in_eval::Bool
125
    "transformation functions to apply before evaluating expressions"
126
    ast_transforms::Vector{Any}
127
    "current backend task"
128
    backend_task::Task
129

130
    REPLBackend(repl_channel, response_channel, in_eval, ast_transforms=copy(repl_ast_transforms)) =
249✔
131
        new(repl_channel, response_channel, in_eval, ast_transforms)
132
end
133
REPLBackend() = REPLBackend(Channel(1), Channel(1), false)
164✔
134

135
# A reference to a backend that is not mutable
136
struct REPLBackendRef
137
    repl_channel::Channel{Any}
79✔
138
    response_channel::Channel{Any}
139
end
140
REPLBackendRef(backend::REPLBackend) = REPLBackendRef(backend.repl_channel, backend.response_channel)
79✔
141

142
function destroy(ref::REPLBackendRef, state::Task)
143
    if istaskfailed(state)
76✔
144
        close(ref.repl_channel, TaskFailedException(state))
1✔
145
        close(ref.response_channel, TaskFailedException(state))
1✔
146
    end
147
    close(ref.repl_channel)
76✔
148
    close(ref.response_channel)
76✔
149
end
150

151
"""
152
    softscope(ex)
153

154
Return a modified version of the parsed expression `ex` that uses
155
the REPL's "soft" scoping rules for global syntax blocks.
156
"""
157
function softscope(@nospecialize ex)
1,135✔
158
    if ex isa Expr
1,135✔
159
        h = ex.head
733✔
160
        if h === :toplevel
733✔
161
            ex′ = Expr(h)
414✔
162
            map!(softscope, resize!(ex′.args, length(ex.args)), ex.args)
414✔
163
            return ex′
414✔
164
        elseif h in (:meta, :import, :using, :export, :module, :error, :incomplete, :thunk)
319✔
165
            return ex
6✔
166
        elseif h === :global && all(x->isa(x, Symbol), ex.args)
316✔
167
            return ex
3✔
168
        else
169
            return Expr(:block, Expr(:softscope, true), ex)
310✔
170
        end
171
    end
172
    return ex
402✔
173
end
174

175
# Temporary alias until Documenter updates
176
const softscope! = softscope
177

178
function print_qualified_access_warning(mod::Module, owner::Module, name::Symbol)
6✔
179
    @warn string(name, " is defined in ", owner, " and is not public in ", mod) maxlog = 1 _id = string("repl-warning-", mod, "-", owner, "-", name) _line = nothing _file = nothing _module = nothing
6✔
180
end
181

182
function has_ancestor(query::Module, target::Module)
6✔
183
    query == target && return true
113✔
184
    while true
90✔
185
        next = parentmodule(query)
90✔
186
        next == target && return true
90✔
187
        next == query && return false
84✔
188
        query = next
45✔
189
    end
45✔
190
end
191

192
retrieve_modules(::Module, ::Any) = (nothing,)
18✔
193
function retrieve_modules(current_module::Module, mod_name::Symbol)
287✔
194
    mod = try
287✔
195
        getproperty(current_module, mod_name)
287✔
196
    catch
197
        return (nothing,)
57✔
198
    end
199
    return (mod isa Module ? mod : nothing,)
230✔
200
end
201
retrieve_modules(current_module::Module, mod_name::QuoteNode) = retrieve_modules(current_module, mod_name.value)
123✔
202
function retrieve_modules(current_module::Module, mod_expr::Expr)
129✔
203
    if Meta.isexpr(mod_expr, :., 2)
129✔
204
        current_module = retrieve_modules(current_module, mod_expr.args[1])[1]
123✔
205
        current_module === nothing && return (nothing,)
123✔
206
        return (current_module, retrieve_modules(current_module, mod_expr.args[2])...)
123✔
207
    else
208
        return (nothing,)
6✔
209
    end
210
end
211

212
add_locals!(locals, ast::Any) = nothing
3✔
213
function add_locals!(locals, ast::Expr)
42✔
214
    for arg in ast.args
42✔
215
        add_locals!(locals, arg)
69✔
216
    end
69✔
217
    return nothing
42✔
218
end
219
function add_locals!(locals, ast::Symbol)
171✔
220
    push!(locals, ast)
171✔
221
    return nothing
171✔
222
end
223

224
function collect_names_to_warn!(warnings, locals, current_module::Module, ast)
3,549✔
225
    ast isa Expr || return
5,132✔
226

227
    # don't recurse through module definitions
228
    ast.head === :module && return
1,966✔
229

230
    if Meta.isexpr(ast, :., 2)
1,966✔
231
        mod_name, name_being_accessed = ast.args
188✔
232
        # retrieve the (possibly-nested) module being named here
233
        mods = retrieve_modules(current_module, mod_name)
188✔
234
        all(x -> x isa Module, mods) || return
523✔
235
        outer_mod = first(mods)
107✔
236
        mod = last(mods)
107✔
237
        if name_being_accessed isa QuoteNode
107✔
238
            name_being_accessed = name_being_accessed.value
107✔
239
        end
240
        name_being_accessed isa Symbol || return
107✔
241
        owner = try
107✔
242
            which(mod, name_being_accessed)
107✔
243
        catch
244
            return
×
245
        end
246
        # if `owner` is a submodule of `mod`, then don't warn. E.g. the name `parse` is present in the module `JSON`
247
        # but is owned by `JSON.Parser`; we don't warn if it is accessed as `JSON.parse`.
248
        has_ancestor(owner, mod) && return
182✔
249
        # Don't warn if the name is public in the module we are accessing it
250
        Base.ispublic(mod, name_being_accessed) && return
36✔
251
        # Don't warn if accessing names defined in Core from Base if they are present in Base (e.g. `Base.throw`).
252
        mod === Base && Base.ispublic(Core, name_being_accessed) && return
33✔
253
        push!(warnings, (; outer_mod, mod, owner, name_being_accessed))
33✔
254
        # no recursion
255
        return
33✔
256
    elseif Meta.isexpr(ast, :(=), 2)
1,778✔
257
        lhs, rhs = ast.args
141✔
258
        # any symbols we find on the LHS we will count as local. This can potentially be overzealous,
259
        # but we want to avoid false positives (unnecessary warnings) more than false negatives.
260
        add_locals!(locals, lhs)
141✔
261
        # we'll recurse into the RHS only
262
        return collect_names_to_warn!(warnings, locals, current_module, rhs)
141✔
263
    elseif Meta.isexpr(ast, :function) && length(ast.args) >= 1
1,637✔
264

265
        if Meta.isexpr(ast.args[1], :call, 2)
9✔
266
            func_name, func_args = ast.args[1].args
6✔
267
            # here we have a function definition and are inspecting it's arguments for local variables.
268
            # we will error on the conservative side by adding all symbols we find (regardless if they are local variables or possibly-global default values)
269
            add_locals!(locals, func_args)
6✔
270
        end
271
        # fall through to general recursion
272
    end
273

274
    for arg in ast.args
1,637✔
275
        collect_names_to_warn!(warnings, locals, current_module, arg)
3,041✔
276
    end
3,041✔
277

278
    return nothing
1,637✔
279
end
280

281
function collect_qualified_access_warnings(current_mod, ast)
367✔
282
    warnings = Set()
367✔
283
    locals = Set{Symbol}()
367✔
284
    collect_names_to_warn!(warnings, locals, current_mod, ast)
367✔
285
    filter!(warnings) do (; outer_mod)
367✔
286
        nameof(outer_mod) ∉ locals
33✔
287
    end
288
    return warnings
367✔
289
end
290

291
function warn_on_non_owning_accesses(current_mod, ast)
331✔
292
    warnings = collect_qualified_access_warnings(current_mod, ast)
331✔
293
    for (; outer_mod, mod, owner, name_being_accessed) in warnings
662✔
294
        print_qualified_access_warning(mod, owner, name_being_accessed)
6✔
295
    end
12✔
296
    return ast
331✔
297
end
298
warn_on_non_owning_accesses(ast) = warn_on_non_owning_accesses(Base.active_module(), ast)
325✔
299

300
const repl_ast_transforms = Any[softscope, warn_on_non_owning_accesses] # defaults for new REPL backends
301

302
# Allows an external package to add hooks into the code loading.
303
# The hook should take a Vector{Symbol} of package names and
304
# return true if all packages could be installed, false if not
305
# to e.g. install packages on demand
306
const install_packages_hooks = Any[]
307

308
# N.B.: Any functions starting with __repl_entry cut off backtraces when printing in the REPL.
309
# We need to do this for both the actual eval and macroexpand, since the latter can cause custom macro
310
# code to run (and error).
311
__repl_entry_lower_with_loc(mod::Module, @nospecialize(ast), toplevel_file::Ref{Ptr{UInt8}}, toplevel_line::Ref{Csize_t}) =
649✔
312
    Core._lower(ast, mod, toplevel_file[], toplevel_line[])[1]
313
__repl_entry_eval_expanded_with_loc(mod::Module, @nospecialize(ast), toplevel_file::Ref{Ptr{UInt8}}, toplevel_line::Ref{Csize_t}) =
646✔
314
    ccall(:jl_toplevel_eval_flex, Any, (Any, Any, Cint, Cint, Ptr{Ptr{UInt8}}, Ptr{Csize_t}), mod, ast, 1, 1, toplevel_file, toplevel_line)
315

316
function toplevel_eval_with_hooks(mod::Module, @nospecialize(ast), toplevel_file=Ref{Ptr{UInt8}}(Base.unsafe_convert(Ptr{UInt8}, :REPL)), toplevel_line=Ref{Csize_t}(1))
988✔
317
    if !isexpr(ast, :toplevel)
1,313✔
318
        ast = invokelatest(__repl_entry_lower_with_loc, mod, ast, toplevel_file, toplevel_line)
649✔
319
        check_for_missing_packages_and_run_hooks(ast)
649✔
320
        return invokelatest(__repl_entry_eval_expanded_with_loc, mod, ast, toplevel_file, toplevel_line)
646✔
321
    end
322
    local value=nothing
339✔
323
    for i = 1:length(ast.args)
345✔
324
        value = toplevel_eval_with_hooks(mod, ast.args[i], toplevel_file, toplevel_line)
663✔
325
    end
969✔
326
    return value
321✔
327
end
328

329
function eval_user_input(@nospecialize(ast), backend::REPLBackend, mod::Module)
325✔
330
    lasterr = nothing
325✔
331
    Base.sigatomic_begin()
325✔
332
    while true
337✔
333
        try
337✔
334
            Base.sigatomic_end()
337✔
335
            if lasterr !== nothing
337✔
336
                put!(backend.response_channel, Pair{Any, Bool}(lasterr, true))
12✔
337
            else
338
                backend.in_eval = true
325✔
339
                for xf in backend.ast_transforms
325✔
340
                    ast = Base.invokelatest(xf, ast)
704✔
341
                end
704✔
342
                value = toplevel_eval_with_hooks(mod, ast)
325✔
343
                backend.in_eval = false
310✔
344
                setglobal!(Base.MainInclude, :ans, value)
310✔
345
                put!(backend.response_channel, Pair{Any, Bool}(value, false))
310✔
346
            end
347
            break
334✔
348
        catch err
349
            if lasterr !== nothing
12✔
350
                println("SYSTEM ERROR: Failed to report error to REPL frontend")
×
351
                println(err)
×
352
            end
353
            lasterr = current_exceptions()
12✔
354
        end
355
    end
12✔
356
    Base.sigatomic_end()
322✔
357
    nothing
322✔
358
end
359

360
function check_for_missing_packages_and_run_hooks(ast)
649✔
361
    isa(ast, Expr) || return
976✔
362
    mods = modules_to_be_loaded(ast)
322✔
363
    filter!(mod -> isnothing(Base.identify_package(String(mod))), mods) # keep missing modules
325✔
364
    if !isempty(mods)
322✔
365
        isempty(install_packages_hooks) && load_pkg()
3✔
366
        for f in install_packages_hooks
3✔
367
            Base.invokelatest(f, mods) && return
3✔
368
        end
×
369
    end
370
end
371

372
function _modules_to_be_loaded!(ast::Expr, mods::Vector{Symbol})
2,409✔
373
    function add!(ctx)
2,481✔
374
        if ctx.head == :as
72✔
375
            ctx = ctx.args[1]
3✔
376
        end
377
        if ctx.args[1] != :. # don't include local import `import .Foo`
144✔
378
            push!(mods, ctx.args[1])
63✔
379
        end
380
    end
381
    ast.head === :quote && return mods # don't search if it's not going to be run during this eval
2,409✔
382
    if ast.head == :call
2,409✔
383
        if length(ast.args) == 5 && ast.args[1] === GlobalRef(Base, :_eval_import)
1,295✔
384
            ctx = ast.args[4]
24✔
385
            if ctx isa QuoteNode # i.e. `Foo: bar`
24✔
386
                ctx = ctx.value
12✔
387
            else
388
                ctx = ast.args[5].value
12✔
389
            end
390
            add!(ctx)
24✔
391
        elseif length(ast.args) == 3 && ast.args[1] == GlobalRef(Base, :_eval_using)
1,271✔
392
            add!(ast.args[3].value)
48✔
393
        end
394
    end
395
    if ast.head !== :thunk
2,409✔
396
        for arg in ast.args
2,015✔
397
            if isexpr(arg, (:block, :if))
4,778✔
398
                _modules_to_be_loaded!(arg, mods)
12✔
399
            end
400
        end
4,778✔
401
    else
402
        code = ast.args[1]
394✔
403
        for arg in code.code
394✔
404
            isa(arg, Expr) || continue
3,507✔
405
            _modules_to_be_loaded!(arg, mods)
2,006✔
406
        end
3,507✔
407
    end
408
end
409

410
function modules_to_be_loaded(ast::Expr, mods::Vector{Symbol} = Symbol[])
411
    _modules_to_be_loaded!(ast, mods)
782✔
412
    filter!(mod::Symbol -> !in(mod, (:Base, :Main, :Core)), mods) # Exclude special non-package modules
454✔
413
    return unique(mods)
391✔
414
end
415

416
"""
417
    start_repl_backend(repl_channel::Channel, response_channel::Channel)
418

419
    Starts loop for REPL backend
420
    Returns a REPLBackend with backend_task assigned
421

422
    Deprecated since sync / async behavior cannot be selected
423
"""
424
function start_repl_backend(repl_channel::Channel{Any}, response_channel::Channel{Any}
×
425
                            ; get_module::Function = ()->Main)
426
    # Maintain legacy behavior of asynchronous backend
427
    backend = REPLBackend(repl_channel, response_channel, false)
×
428
    # Assignment will be made twice, but will be immediately available
429
    backend.backend_task = @async start_repl_backend(backend; get_module)
×
430
    return backend
×
431
end
432

433
"""
434
    start_repl_backend(backend::REPLBackend)
435

436
    Call directly to run backend loop on current Task.
437
    Use @async for run backend on new Task.
438

439
    Does not return backend until loop is finished.
440
"""
441
function start_repl_backend(backend::REPLBackend,  @nospecialize(consumer = x -> nothing); get_module::Function = ()->Main)
188✔
442
    backend.backend_task = Base.current_task()
85✔
443
    consumer(backend)
85✔
444
    repl_backend_loop(backend, get_module)
85✔
445
    return backend
81✔
446
end
447

448
function repl_backend_loop(backend::REPLBackend, get_module::Function)
85✔
449
    # include looks at this to determine the relative include path
450
    # nothing means cwd
451
    while true
590✔
452
        tls = task_local_storage()
590✔
453
        tls[:SOURCE_PATH] = nothing
590✔
454
        ast_or_func, show_value = take!(backend.repl_channel)
590✔
455
        if show_value == -1
589✔
456
            # exit flag
457
            break
81✔
458
        end
459
        if show_value == 2 # 2 indicates a function to be called
508✔
460
            f = ast_or_func
183✔
461
            try
183✔
462
                ret = f()
183✔
463
                put!(backend.response_channel, Pair{Any, Bool}(ret, false))
180✔
464
            catch
465
                put!(backend.response_channel, Pair{Any, Bool}(current_exceptions(), true))
3✔
466
            end
467
        else
468
            ast = ast_or_func
325✔
469
            eval_user_input(ast, backend, get_module())
325✔
470
        end
471
    end
505✔
472
    return nothing
81✔
473
end
474

475
SHOW_MAXIMUM_BYTES::Int = 1_048_576
476

477
# Limit printing during REPL display
478
mutable struct LimitIO{IO_t <: IO} <: IO
479
    io::IO_t
216✔
480
    maxbytes::Int
481
    n::Int # max bytes to write
482
end
483
LimitIO(io::IO, maxbytes) = LimitIO(io, maxbytes, 0)
216✔
484

485
struct LimitIOException <: Exception
486
    maxbytes::Int
18✔
487
end
488

489
function Base.showerror(io::IO, e::LimitIOException)
×
490
    print(io, "$LimitIOException: aborted printing after attempting to print more than $(Base.format_bytes(e.maxbytes)) within a `LimitIO`.")
×
491
end
492

493
Base.displaysize(io::LimitIO) = _displaysize(io.io)
84✔
494

495
function Base.write(io::LimitIO, v::UInt8)
465✔
496
    io.n > io.maxbytes && throw(LimitIOException(io.maxbytes))
5,022✔
497
    n_bytes = write(io.io, v)
9,567✔
498
    io.n += n_bytes
5,016✔
499
    return n_bytes
5,016✔
500
end
501

502
# Semantically, we only need to override `Base.write`, but we also
503
# override `unsafe_write` for performance.
504
function Base.unsafe_write(limiter::LimitIO, p::Ptr{UInt8}, nb::UInt)
505
    # already exceeded? throw
506
    limiter.n > limiter.maxbytes && throw(LimitIOException(limiter.maxbytes))
828✔
507
    remaining = limiter.maxbytes - limiter.n # >= 0
828✔
508

509
    # Not enough bytes left; we will print up to the limit, then throw
510
    if remaining < nb
828✔
511
        if remaining > 0
6✔
512
            Base.unsafe_write(limiter.io, p, remaining)
3✔
513
        end
514
        throw(LimitIOException(limiter.maxbytes))
6✔
515
    end
516

517
    # We won't hit the limit so we'll write the full `nb` bytes
518
    bytes_written = Base.unsafe_write(limiter.io, p, nb)::Union{Int,UInt}
822✔
519
    limiter.n += bytes_written
822✔
520
    return bytes_written
822✔
521
end
522

523
struct REPLDisplay{Repl<:AbstractREPL} <: AbstractDisplay
524
    repl::Repl
116✔
525
end
526

527
function show_limited(io::IO, mime::MIME, x)
207✔
528
    try
207✔
529
        # We wrap in a LimitIO to limit the amount of printing.
530
        # We unpack `IOContext`s, since we will pass the properties on the outside.
531
        inner = io isa IOContext ? io.io : io
207✔
532
        wrapped_limiter = IOContext(LimitIO(inner, SHOW_MAXIMUM_BYTES), io)
207✔
533
        # `show_repl` to allow the hook with special syntax highlighting
534
        show_repl(wrapped_limiter, mime, x)
207✔
535
    catch e
536
        e isa LimitIOException || rethrow()
15✔
537
        printstyled(io, """…[printing stopped after displaying $(Base.format_bytes(e.maxbytes)); call `show(stdout, MIME"text/plain"(), ans)` to print without truncation]"""; color=:light_yellow, bold=true)
9✔
538
    end
539
end
540

541
function display(d::REPLDisplay, mime::MIME"text/plain", x)
192✔
542
    x = Ref{Any}(x)
192✔
543
    with_repl_linfo(d.repl) do io
192✔
544
        io = IOContext(io, :limit => true, :module => Base.active_module(d)::Module)
375✔
545
        if d.repl isa LineEditREPL
192✔
546
            mistate = d.repl.mistate
183✔
547
            mode = LineEdit.mode(mistate)
183✔
548
            if mode isa LineEdit.Prompt
183✔
549
                LineEdit.write_output_prefix(io, mode, get(io, :color, false)::Bool)
732✔
550
            end
551
        end
552
        get(io, :color, false)::Bool && write(io, answer_color(d.repl))
210✔
553
        if isdefined(d.repl, :options) && isdefined(d.repl.options, :iocontext)
192✔
554
            # this can override the :limit property set initially
555
            io = foldl(IOContext, d.repl.options.iocontext, init=io)
183✔
556
        end
557
        show_limited(io, mime, x[])
192✔
558
        println(io)
189✔
559
    end
560
    return nothing
189✔
561
end
562

563
display(d::REPLDisplay, x) = display(d, MIME("text/plain"), x)
192✔
564

565
show_repl(io::IO, mime::MIME"text/plain", x) = show(io, mime, x)
201✔
566

567
function show_repl(io::IO, mime::MIME"text/plain", c::AbstractChar)
12✔
568
    show(io, mime, c) # Call the original Base.show
12✔
569
    # Check for LaTeX/emoji alias and print if found and using symbol_latex which is used in help?> mode
570
    latex = symbol_latex(string(c))
12✔
571
    if !isempty(latex)
12✔
572
        print(io, ", input as ")
6✔
573
        printstyled(io, latex, "<tab>"; color=:cyan)
6✔
574
    end
575
end
576

577
show_repl(io::IO, ::MIME"text/plain", ex::Expr) =
6✔
578
    print(io, JuliaSyntaxHighlighting.highlight(
579
        sprint(show, ex, context=IOContext(io, :color => false))))
580

581
function print_response(repl::AbstractREPL, response, show_value::Bool, have_color::Bool)
313✔
582
    repl.waserror = response[2]
313✔
583
    with_repl_linfo(repl) do io
626✔
584
        io = IOContext(io, :module => Base.active_module(repl)::Module)
617✔
585
        print_response(io, response, backend(repl), show_value, have_color, specialdisplay(repl))
313✔
586
    end
587
    return nothing
313✔
588
end
589

590
# N.B.: Any functions starting with __repl_entry cut off backtraces when printing in the REPL.
591
__repl_entry_display(val) = Base.invokelatest(display, val)
144✔
592
__repl_entry_display(specialdisplay::Union{AbstractDisplay,Nothing}, val) = Base.invokelatest(display, specialdisplay, val)
48✔
593

594
function __repl_entry_display_error(errio::IO, @nospecialize errval)
24✔
595
    # this will be set to true if types in the stacktrace are truncated
596
    limitflag = Ref(false)
24✔
597
    errio = IOContext(errio, :stacktrace_types_limited => limitflag)
24✔
598
    Base.invokelatest(Base.display_error, errio, errval)
24✔
599
    if limitflag[]
18✔
600
        print(errio, "Some type information was truncated. Use `show(err)` to see complete types.")
3✔
601
        println(errio)
3✔
602
    end
603
    return nothing
18✔
604
end
605

606
function print_response(errio::IO, response, backend::Union{REPLBackendRef,Nothing}, show_value::Bool, have_color::Bool, specialdisplay::Union{AbstractDisplay,Nothing}=nothing)
316✔
607
    Base.sigatomic_begin()
316✔
608
    val, iserr = response
316✔
609
    if !iserr
316✔
610
        # display result
611
        try
298✔
612
            if val !== nothing && show_value
298✔
613
                Base.sigatomic_end() # allow display to be interrupted
192✔
614
                val_to_show = val
192✔
615
                val2, iserr = if specialdisplay === nothing
192✔
616
                    # display calls may require being run on the main thread
617
                    call_on_backend(backend) do
279✔
618
                        __repl_entry_display(val_to_show)
144✔
619
                    end
620
                else
621
                    call_on_backend(backend) do
240✔
622
                        __repl_entry_display(specialdisplay, val_to_show)
48✔
623
                    end
624
                end
625
                Base.sigatomic_begin()
192✔
626
                if iserr
192✔
627
                    println(errio)
3✔
628
                    println(errio, "Error showing value of type ", typeof(val), ":")
3✔
629
                    val = val2
3✔
630
                end
631
            end
632
        catch ex
633
            println(errio)
×
634
            println(errio, "SYSTEM (REPL): showing a value caused an error")
×
635
            val = current_exceptions()
×
636
            iserr = true
×
637
        end
638
    end
639
    if iserr
316✔
640
        # print error
641
        iserr = false
21✔
642
        while true
24✔
643
            try
24✔
644
                Base.sigatomic_end() # allow stacktrace printing to be interrupted
24✔
645
                val = Base.scrub_repl_backtrace(val)
24✔
646
                Base.istrivialerror(val) || setglobal!(Base.MainInclude, :err, val)
39✔
647
                __repl_entry_display_error(errio, val)
24✔
648
                break
24✔
649
            catch ex
650
                println(errio) # an error during printing is likely to leave us mid-line
6✔
651
                if !iserr
6✔
652
                    println(errio, "SYSTEM (REPL): showing an error caused an error")
3✔
653
                    val = current_exceptions()
3✔
654
                    iserr = true
3✔
655
                else
656
                    println(errio, "SYSTEM (REPL): caught exception of type ", typeof(ex).name.name,
3✔
657
                        " while trying to print an exception; giving up")
658
                    break
6✔
659
                end
660
            end
661
        end
3✔
662
    end
663
    Base.sigatomic_end()
316✔
664
    nothing
316✔
665
end
666

667

668

669
"""
670
    run_repl(repl::AbstractREPL)
671
    run_repl(repl, consumer = backend->nothing; backend_on_current_task = true)
672

673
    Main function to start the REPL
674

675
    consumer is an optional function that takes a REPLBackend as an argument
676
"""
677
function run_repl(repl::AbstractREPL, @nospecialize(consumer = x -> nothing); backend_on_current_task::Bool = true, backend = REPLBackend())
308✔
678
    backend_ref = REPLBackendRef(backend)
79✔
679
    get_module = () -> Base.active_module(repl)
392✔
680
    cleanup_task(backend_ref, t) = @task try
234✔
681
            destroy(backend_ref, t)
76✔
682
        catch e
683
            Core.print(Core.stderr, "\nINTERNAL ERROR: ")
×
684
            Core.println(Core.stderr, e)
×
685
            Core.println(Core.stderr, catch_backtrace())
×
686
        end
687
    if backend_on_current_task
79✔
688
        t = @async run_frontend(repl, backend_ref)
158✔
689
        cleanup = cleanup_task(backend_ref, t)
79✔
690
        errormonitor(t)
79✔
691
        Base._wait2(t, cleanup)
79✔
692
        start_repl_backend(backend, consumer; get_module)
79✔
693
    else
694
        t = @async start_repl_backend(backend, consumer; get_module)
×
695
        cleanup = cleanup_task(backend_ref, t)
×
696
        errormonitor(t)
×
697
        Base._wait2(t, cleanup)
×
698
        run_frontend(repl, backend_ref)
×
699
    end
700
    return backend
75✔
701
end
702

703
## BasicREPL ##
704

705
mutable struct BasicREPL <: AbstractREPL
706
    terminal::TextTerminal
707
    waserror::Bool
708
    frontend_task::Task
709
    BasicREPL(t) = new(t, false)
12✔
710
end
711

712
outstream(r::BasicREPL) = r.terminal
18✔
713
hascolor(r::BasicREPL) = Terminals.hascolor(r.terminal)
3✔
714

715
function run_frontend(repl::BasicREPL, backend::REPLBackendRef)
9✔
716
    repl.frontend_task = current_task()
9✔
717
    d = REPLDisplay(repl)
9✔
718
    dopushdisplay = !in(d,Base.Multimedia.displays)
18✔
719
    dopushdisplay && pushdisplay(d)
9✔
720
    hit_eof = false
9✔
721
    while true
18✔
722
        Base.reseteof(repl.terminal)
18✔
723
        write(repl.terminal, JULIA_PROMPT)
18✔
724
        line = ""
18✔
725
        ast = nothing
18✔
726
        interrupted = false
18✔
727
        while true
18✔
728
            try
18✔
729
                line *= readline(repl.terminal, keep=true)
18✔
730
            catch e
731
                if isa(e,InterruptException)
×
732
                    try # raise the debugger if present
×
733
                        ccall(:jl_raise_debugger, Int, ())
×
734
                    catch
×
735
                    end
736
                    line = ""
×
737
                    interrupted = true
×
738
                    break
×
739
                elseif isa(e,EOFError)
×
740
                    hit_eof = true
×
741
                    break
×
742
                else
743
                    rethrow()
×
744
                end
745
            end
746
            ast = parse_repl_input_line(line, repl)
18✔
747
            (isa(ast,Expr) && ast.head === :incomplete) || break
36✔
748
        end
×
749
        if !isempty(line)
18✔
750
            response = eval_on_backend(ast, backend)
12✔
751
            print_response(repl, response, !ends_with_semicolon(line), false)
9✔
752
        end
753
        write(repl.terminal, '\n')
15✔
754
        ((!interrupted && isempty(line)) || hit_eof) && break
24✔
755
    end
9✔
756
    # terminate backend
757
    put!(backend.repl_channel, (nothing, -1))
6✔
758
    dopushdisplay && popdisplay(d)
6✔
759
    nothing
6✔
760
end
761

762
## LineEditREPL ##
763

764
mutable struct LineEditREPL <: AbstractREPL
765
    t::TextTerminal
766
    hascolor::Bool
767
    prompt_color::String
768
    input_color::String
769
    answer_color::String
770
    shell_color::String
771
    help_color::String
772
    pkg_color::String
773
    history_file::Bool
774
    in_shell::Bool
775
    in_help::Bool
776
    envcolors::Bool
777
    waserror::Bool
778
    specialdisplay::Union{Nothing,AbstractDisplay}
779
    options::Options
780
    mistate::Union{MIState,Nothing}
781
    last_shown_line_infos::Vector{Tuple{String,Int}}
782
    interface::ModalInterface
783
    backendref::REPLBackendRef
784
    frontend_task::Task
785
    # Optional event to notify when the prompt is ready (used by precompilation)
786
    prompt_ready_event::Union{Nothing, Base.Event}
787
    function LineEditREPL(t,hascolor,prompt_color,input_color,answer_color,shell_color,help_color,pkg_color,history_file,in_shell,in_help,envcolors)
91✔
788
        opts = Options()
91✔
789
        opts.hascolor = hascolor
91✔
790
        if !hascolor
91✔
791
            opts.beep_colors = [""]
×
792
        end
793
        r = new(t,hascolor,prompt_color,input_color,answer_color,shell_color,help_color,pkg_color,history_file,in_shell,
91✔
794
            in_help,envcolors,false,nothing, opts, nothing, Tuple{String,Int}[])
795
        r.prompt_ready_event = nothing
91✔
796
        r
91✔
797
    end
798
end
799
outstream(r::LineEditREPL) = (t = r.t; t isa TTYTerminal ? t.out_stream : t)
515✔
800
specialdisplay(r::LineEditREPL) = r.specialdisplay
304✔
801
specialdisplay(r::AbstractREPL) = nothing
9✔
802
terminal(r::LineEditREPL) = r.t
490✔
803
hascolor(r::LineEditREPL) = r.hascolor
611✔
804

805
LineEditREPL(t::TextTerminal, hascolor::Bool, envcolors::Bool=false) =
181✔
806
    LineEditREPL(t, hascolor,
807
        hascolor ? Base.text_colors[:green] : "",
808
        hascolor ? Base.input_color() : "",
809
        hascolor ? Base.answer_color() : "",
810
        hascolor ? Base.text_colors[:red] : "",
811
        hascolor ? Base.text_colors[:yellow] : "",
812
        hascolor ? Base.text_colors[:blue] : "",
813
        false, false, false, envcolors
814
    )
815

816
mutable struct REPLCompletionProvider <: CompletionProvider
817
    modifiers::LineEdit.Modifiers
82✔
818
end
819
REPLCompletionProvider() = REPLCompletionProvider(LineEdit.Modifiers())
82✔
820

821
mutable struct ShellCompletionProvider <: CompletionProvider end
82✔
822
struct LatexCompletions <: CompletionProvider end
823

824
Base.active_module(mistate::MIState) = mistate.active_module
9,760✔
825
Base.active_module((; mistate)::LineEditREPL) = mistate === nothing ? Main : Base.active_module(mistate)
18,589✔
826
Base.active_module(::AbstractREPL) = Main
54✔
827
Base.active_module(d::REPLDisplay) = Base.active_module(d.repl)
375✔
828

829
setmodifiers!(c::CompletionProvider, m::LineEdit.Modifiers) = nothing
×
830

831
setmodifiers!(c::REPLCompletionProvider, m::LineEdit.Modifiers) = c.modifiers = m
×
832

833
"""
834
    activate(mod::Module=Main)
835

836
Set `mod` as the default contextual module in the REPL,
837
both for evaluating expressions and printing them.
838
"""
839
function activate(mod::Module=Main; interactive_utils::Bool=true)
×
840
    mistate = (Base.active_repl::LineEditREPL).mistate
×
841
    mistate === nothing && return nothing
×
842
    mistate.active_module = mod
×
843
    interactive_utils && Base.load_InteractiveUtils(mod)
×
844
    return nothing
×
845
end
846

847
beforecursor(buf::IOBuffer) = String(buf.data[1:buf.ptr-1])
12✔
848

849
# Convert inclusive-inclusive 1-based char indexing to inclusive-exclusive byte Region.
850
to_region(s, r) = first(r)-1 => (length(r) > 0 ? nextind(s, last(r))-1 : first(r)-1)
92✔
851

852
function complete_line(c::REPLCompletionProvider, s::PromptState, mod::Module; hint::Bool=false)
96✔
853
    full = LineEdit.input_string(s)
48✔
854
    ret, range, should_complete = completions(full, thisind(full, position(s)), mod, c.modifiers.shift, hint)
96✔
855
    range = to_region(full, range)
81✔
856
    c.modifiers = LineEdit.Modifiers()
48✔
857
    return unique!(LineEdit.NamedCompletion[named_completion(x) for x in ret]), range, should_complete
48✔
858
end
859

860
function complete_line(c::ShellCompletionProvider, s::PromptState; hint::Bool=false)
14✔
861
    full = LineEdit.input_string(s)
7✔
862
    ret, range, should_complete = shell_completions(full, thisind(full, position(s)), hint)
14✔
863
    range = to_region(full, range)
11✔
864
    return unique!(LineEdit.NamedCompletion[named_completion(x) for x in ret]), range, should_complete
7✔
865
end
866

867
function complete_line(c::LatexCompletions, s; hint::Bool=false)
×
868
    full = LineEdit.input_string(s)::String
×
869
    ret, range, should_complete = bslash_completions(full, thisind(full, position(s)), hint)[2]
×
870
    range = to_region(full, range)
×
871
    return unique!(LineEdit.NamedCompletion[named_completion(x) for x in ret]), range, should_complete
×
872
end
873

874
with_repl_linfo(f, repl) = f(outstream(repl))
18✔
875
function with_repl_linfo(f, repl::LineEditREPL)
487✔
876
    linfos = Tuple{String,Int}[]
487✔
877
    io = IOContext(outstream(repl), :last_shown_line_infos => linfos)
487✔
878
    f(io)
487✔
879
    if !isempty(linfos)
484✔
880
        repl.last_shown_line_infos = linfos
18✔
881
    end
882
    nothing
484✔
883
end
884

885
mutable struct REPLHistoryProvider <: HistoryProvider
886
    history::HistoryFile
97✔
887
    start_idx::Int
888
    cur_idx::Int
889
    last_idx::Int
890
    last_buffer::IOBuffer
891
    last_mode::Union{Nothing,Prompt}
892
    mode_mapping::Dict{Symbol,Prompt}
893
end
894
REPLHistoryProvider(mode_mapping::Dict{Symbol}) =
97✔
895
    REPLHistoryProvider(HistoryFile(), 0, 0, -1, IOBuffer(),
896
                        nothing, mode_mapping)
897

898
function add_history(hist::REPLHistoryProvider, s::PromptState)
360✔
899
    str = rstrip(takestring!(copy(s.input_buffer)))
360✔
900
    isempty(strip(str)) && return
360✔
901
    mode = mode_idx(hist, LineEdit.mode(s))
304✔
902
    !isempty(hist.history) && isequal(mode, hist.history[end].mode) &&
304✔
903
        str == hist.history[end].content && return
904
    entry = HistEntry(mode, now(UTC), str, 0)
286✔
905
    push!(hist.history, entry)
286✔
906
    nothing
286✔
907
end
908

909
function history_move(s::Union{LineEdit.MIState,LineEdit.PrefixSearchState}, hist::REPLHistoryProvider, idx::Int, save_idx::Int = hist.cur_idx)
264✔
910
    max_idx = length(hist.history) + 1
378✔
911
    @assert 1 <= hist.cur_idx <= max_idx
264✔
912
    (1 <= idx <= max_idx) || return :none
270✔
913
    idx != hist.cur_idx || return :none
258✔
914

915
    # save the current line
916
    if save_idx == max_idx
258✔
917
        hist.last_mode = LineEdit.mode(s)
99✔
918
        hist.last_buffer = copy(LineEdit.buffer(s))
144✔
919
    else
920
        # NOTE: Modifying the history is a bit funky, so
921
        # we reach into the internals of `HistoryFile`
922
        # to do so rather than implementing `setindex!`.
923
        oldrec = hist.history.records[save_idx]
159✔
924
        hist.history.records[save_idx] = HistEntry(
159✔
925
            mode_idx(hist, LineEdit.mode(s)),
926
            oldrec.date,
927
            LineEdit.input_string(s),
928
            oldrec.index)
929
    end
930

931
    # load the saved line
932
    if idx == max_idx
258✔
933
        last_buffer = hist.last_buffer
30✔
934
        LineEdit.transition(s, hist.last_mode) do
42✔
935
            LineEdit.replace_line(s, last_buffer)
30✔
936
        end
937
        hist.last_mode = nothing
30✔
938
        hist.last_buffer = IOBuffer()
30✔
939
    else
940
        if haskey(hist.mode_mapping, hist.history[idx].mode)
456✔
941
            LineEdit.transition(s, hist.mode_mapping[hist.history[idx].mode]) do
180✔
942
                LineEdit.replace_line(s, hist.history[idx].content)
180✔
943
            end
944
        else
945
            return :skip
48✔
946
        end
947
    end
948
    hist.cur_idx = idx
210✔
949

950
    return :ok
210✔
951
end
952

953
# REPL History can also transitions modes
954
function LineEdit.accept_result_newmode(hist::REPLHistoryProvider)
33✔
955
    if 1 <= hist.cur_idx <= length(hist.history)
33✔
956
        return hist.mode_mapping[hist.history[hist.cur_idx].mode]
27✔
957
    end
958
    return nothing
6✔
959
end
960

961
function history_do_initialize(hist::REPLHistoryProvider)
962
    isempty(hist.history) || return false
367✔
963
    update!(hist.history)
31✔
964
    hist.start_idx = length(hist.history) + 1
31✔
965
    hist.cur_idx = hist.start_idx
31✔
966
    hist.last_idx = -1
31✔
967
    true
968
end
969

970
function history_prev(s::LineEdit.MIState, hist::REPLHistoryProvider,
90✔
971
                      num::Int=1, save_idx::Int = hist.cur_idx)
972
    num <= 0 && return history_next(s, hist, -num, save_idx)
150✔
973
    hist.last_idx = -1
84✔
974
    m = history_move(s, hist, hist.cur_idx-num, save_idx)
84✔
975
    if m === :ok
84✔
976
        LineEdit.move_input_start(s)
120✔
977
        LineEdit.reset_key_repeats(s) do
60✔
978
            LineEdit.move_line_end(s)
60✔
979
        end
980
        return LineEdit.refresh_line(s)
60✔
981
    elseif m === :skip
24✔
982
        return history_prev(s, hist, num+1, save_idx)
24✔
983
    else
984
        return Terminals.beep(s)
×
985
    end
986
end
987

988
function history_next(s::LineEdit.MIState, hist::REPLHistoryProvider,
72✔
989
                      num::Int=1, save_idx::Int = hist.cur_idx)
990
    if num == 0
120✔
991
        Terminals.beep(s)
×
992
        return
×
993
    end
994
    num < 0 && return history_prev(s, hist, -num, save_idx)
72✔
995
    history_do_initialize(hist)
132✔
996
    cur_idx = hist.cur_idx
66✔
997
    max_idx = length(hist.history) + 1
66✔
998
    if cur_idx == max_idx && 0 < hist.last_idx
66✔
999
        # issue #6312
1000
        cur_idx = hist.last_idx
×
1001
        hist.last_idx = -1
×
1002
    end
1003
    m = history_move(s, hist, cur_idx+num, save_idx)
66✔
1004
    if m === :ok
66✔
1005
        LineEdit.move_input_end(s)
42✔
1006
        return LineEdit.refresh_line(s)
42✔
1007
    elseif m === :skip
24✔
1008
        return history_next(s, hist, num+1, save_idx)
18✔
1009
    else
1010
        return Terminals.beep(s)
6✔
1011
    end
1012
end
1013

1014
history_first(s::LineEdit.MIState, hist::REPLHistoryProvider) =
18✔
1015
    history_prev(s, hist, hist.cur_idx - 1 -
1016
                 (hist.cur_idx > hist.start_idx+1 ? hist.start_idx : 0))
1017

1018
history_last(s::LineEdit.MIState, hist::REPLHistoryProvider) =
12✔
1019
    history_next(s, hist, length(update!(hist.history)) - hist.cur_idx + 1)
1020

1021
function history_move_prefix(s::LineEdit.PrefixSearchState,
108✔
1022
                             hist::REPLHistoryProvider,
1023
                             prefix::AbstractString,
1024
                             backwards::Bool,
1025
                             cur_idx::Int = hist.cur_idx)
1026
    if history_do_initialize(hist)
312✔
1027
        cur_idx = hist.cur_idx
6✔
1028
    end
1029
    cur_response = takestring!(copy(LineEdit.buffer(s)))
108✔
1030
    # when searching forward, start at last_idx
1031
    if !backwards && hist.last_idx > 0
108✔
1032
        cur_idx = hist.last_idx
3✔
1033
    end
1034
    hist.last_idx = -1
108✔
1035
    max_idx = length(hist.history)+1
108✔
1036
    idxs = backwards ? ((cur_idx-1):-1:1) : ((cur_idx+1):1:max_idx)
183✔
1037
    for idx in idxs
108✔
1038
        if (idx == max_idx) || (startswith(hist.history[idx].content, prefix) && (hist.history[idx].content != cur_response || get(hist.mode_mapping, hist.history[idx].mode, nothing) !== LineEdit.mode(s)))
564✔
1039
            m = history_move(s, hist, idx)
102✔
1040
            if m === :ok
102✔
1041
                if idx == max_idx
96✔
1042
                    # on resuming the in-progress edit, leave the cursor where the user last had it
1043
                elseif isempty(prefix)
84✔
1044
                    # on empty prefix search, move cursor to the end
1045
                    LineEdit.move_input_end(s)
42✔
1046
                else
1047
                    # otherwise, keep cursor at the prefix position as a visual cue
1048
                    seek(LineEdit.buffer(s), sizeof(prefix))
42✔
1049
                end
1050
                LineEdit.refresh_line(s)
96✔
1051
                return :ok
96✔
1052
            elseif m === :skip
6✔
1053
                return history_move_prefix(s,hist,prefix,backwards,idx)
6✔
1054
            end
1055
        end
1056
    end
372✔
1057
    Terminals.beep(s)
6✔
1058
    nothing
6✔
1059
end
1060
history_next_prefix(s::LineEdit.PrefixSearchState, hist::REPLHistoryProvider, prefix::AbstractString) =
15✔
1061
    history_move_prefix(s, hist, prefix, false)
1062
history_prev_prefix(s::LineEdit.PrefixSearchState, hist::REPLHistoryProvider, prefix::AbstractString) =
87✔
1063
    history_move_prefix(s, hist, prefix, true)
1064

1065
function history_search(hist::REPLHistoryProvider, query_buffer::IOBuffer, response_buffer::IOBuffer,
×
1066
                        backwards::Bool=false, skip_current::Bool=false)
1067

1068
    qpos = position(query_buffer)
×
1069
    qpos > 0 || return true
×
1070
    searchdata = beforecursor(query_buffer)
×
1071
    response_str = takestring!(copy(response_buffer))
×
1072

1073
    # Alright, first try to see if the current match still works
1074
    a = position(response_buffer) + 1 # position is zero-indexed
×
1075
    # FIXME: I'm pretty sure this is broken since it uses an index
1076
    # into the search data to index into the response string
1077
    b = a + sizeof(searchdata)
×
1078
    b = b ≤ ncodeunits(response_str) ? prevind(response_str, b) : b-1
×
1079
    b = min(lastindex(response_str), b) # ensure that b is valid
×
1080

1081
    searchstart = backwards ? b : a
×
1082
    if searchdata == response_str[a:b]
×
1083
        if skip_current
×
1084
            searchstart = backwards ? prevind(response_str, b) : nextind(response_str, a)
×
1085
        else
1086
            return true
×
1087
        end
1088
    end
1089

1090
    # Start searching
1091
    # First the current response buffer
1092
    if 1 <= searchstart <= lastindex(response_str)
×
1093
        match = backwards ? findprev(searchdata, response_str, searchstart) :
×
1094
                            findnext(searchdata, response_str, searchstart)
1095
        if match !== nothing
×
1096
            seek(response_buffer, first(match) - 1)
×
1097
            return true
×
1098
        end
1099
    end
1100

1101
    # Now search all the other buffers
1102
    idxs = backwards ? ((hist.cur_idx-1):-1:1) : ((hist.cur_idx+1):1:length(hist.history))
×
1103
    for idx in idxs
×
1104
        h = hist.history[idx].content
×
1105
        match = backwards ? findlast(searchdata, h) : findfirst(searchdata, h)
×
1106
        if match !== nothing && h != response_str && haskey(hist.mode_mapping, hist.history[idx].mode)
×
1107
            truncate(response_buffer, 0)
×
1108
            write(response_buffer, h)
×
1109
            seek(response_buffer, first(match) - 1)
×
1110
            hist.cur_idx = idx
×
1111
            return true
×
1112
        end
1113
    end
×
1114

1115
    return false
×
1116
end
1117

1118
function history_reset_state(hist::REPLHistoryProvider)
1119
    if hist.cur_idx != length(hist.history) + 1
486✔
1120
        hist.last_idx = hist.cur_idx
389✔
1121
        hist.cur_idx = length(hist.history) + 1
389✔
1122
    end
1123
    nothing
486✔
1124
end
1125
LineEdit.reset_state(hist::REPLHistoryProvider) = history_reset_state(hist)
389✔
1126

1127
function parse_repl_input_line(line::String, repl; kwargs...)
1,192✔
1128
    # N.B.: This re-latches the syntax version for `Main`. If `Base.active_module` is not `Main`,
1129
    # then this does not affect the parser used for that module. We could probably skip this step
1130
    # in that case, but let's just be consistent on the off chance that the active module tries
1131
    # to `include(Main, ...)` or similar.
1132
    @Base.ScopedValues.with Base.MainInclude.main_parser=>Base.parser_for_active_project() Base.parse_input_line(line;
596✔
1133
        mod=Base.active_module(repl), kwargs...)
1134
end
1135

1136
function return_callback(s)
305✔
1137
    ast = parse_repl_input_line(takestring!(copy(LineEdit.buffer(s))), s; depwarn=false)
305✔
1138
    return !(isa(ast, Expr) && ast.head === :incomplete)
305✔
1139
end
1140

1141
find_hist_file() = get(ENV, "JULIA_HISTORY",
13✔
1142
                       !isempty(DEPOT_PATH) ? joinpath(DEPOT_PATH[1], "logs", "repl_history.jl") :
1143
                       error("DEPOT_PATH is empty and ENV[\"JULIA_HISTORY\"] not set."))
1144

1145
backend(r::AbstractREPL) = hasproperty(r, :backendref) && isdefined(r, :backendref) ? r.backendref : nothing
614✔
1146

1147

1148
function eval_on_backend(ast, backend::REPLBackendRef)
313✔
1149
    put!(backend.repl_channel, (ast, 1)) # (f, show_value)
313✔
1150
    return take!(backend.response_channel) # (val, iserr)
313✔
1151
end
1152
function call_on_backend(f, backend::REPLBackendRef)
183✔
1153
    applicable(f) || error("internal error: f is not callable")
183✔
1154
    put!(backend.repl_channel, (f, 2)) # (f, show_value) 2 indicates function (rather than ast)
183✔
1155
    return take!(backend.response_channel) # (val, iserr)
183✔
1156
end
1157
# if no backend just eval (used by tests)
1158
eval_on_backend(ast, backend::Nothing) = error("no backend for eval ast")
×
1159
function call_on_backend(f, backend::Nothing)
9✔
1160
    try
9✔
1161
        ret = f()
9✔
1162
        return (ret, false) # (val, iserr)
9✔
1163
    catch
1164
        return (current_exceptions(), true)
×
1165
    end
1166
end
1167

1168

1169
function respond(f, repl, main; pass_empty::Bool = false, suppress_on_semicolon::Bool = true)
328✔
1170
    return function do_respond(s::MIState, buf, ok::Bool)
747✔
1171
        if !ok
419✔
1172
            return transition(s, :abort)
69✔
1173
        end
1174
        line = String(take!(buf)::Vector{UInt8})
654✔
1175
        if !isempty(line) || pass_empty
396✔
1176
            reset(repl)
304✔
1177
            local response
1178
            try
304✔
1179
                ast = Base.invokelatest(f, line)
304✔
1180
                response = eval_on_backend(ast, backend(repl))
602✔
1181
            catch
1182
                response = Pair{Any, Bool}(current_exceptions(), true)
3✔
1183
            end
1184
            hide_output = suppress_on_semicolon && ends_with_semicolon(line)
304✔
1185
            print_response(repl, response, !hide_output, hascolor(repl))
304✔
1186
        end
1187
        prepare_next(repl)
350✔
1188
        reset_state(s)
350✔
1189
        return s.current_mode.sticky ? true : transition(s, main)
350✔
1190
    end
1191
end
1192

1193
function reset(repl::LineEditREPL)
304✔
1194
    raw!(repl.t, false)
304✔
1195
    hascolor(repl) && print(repl.t, Base.text_colors[:normal])
304✔
1196
    nothing
304✔
1197
end
1198

1199
function prepare_next(repl::LineEditREPL)
350✔
1200
    println(terminal(repl))
350✔
1201
end
1202

1203
function mode_keymap(julia_prompt::Prompt)
1204
    AnyDict(
82✔
1205
    '\b' => function (s::MIState,o...)
19✔
1206
        if isempty(s) || position(LineEdit.buffer(s)) == 0
19✔
1207
            let buf = copy(LineEdit.buffer(s))
19✔
1208
                transition(s, julia_prompt) do
19✔
1209
                    LineEdit.state(s, julia_prompt).input_buffer = buf
19✔
1210
                end
1211
            end
1212
        else
1213
            buf = LineEdit.buffer(s)
×
1214
            if LineEdit.try_remove_paired_delimiter(buf)
×
1215
                return LineEdit.refresh_line(s)
×
1216
            end
1217
            LineEdit.edit_backspace(s)
×
1218
        end
1219
    end,
1220
    "^C" => function (s::MIState,o...)
1221
        LineEdit.move_input_end(s)
1222
        LineEdit.refresh_line(s)
1223
        print(LineEdit.terminal(s), "^C\n\n")
1224
        transition(s, julia_prompt)
1225
        transition(s, :reset)
1226
        LineEdit.refresh_line(s)
1227
    end)
1228
end
1229

1230
repl_filename(repl, hp::REPLHistoryProvider) = "REPL[$(max(length(hp.history)-hp.start_idx, 1))]"
273✔
1231
repl_filename(repl, hp) = "REPL"
×
1232

1233
const JL_PROMPT_PASTE = Ref(true)
1234
enable_promptpaste(v::Bool) = JL_PROMPT_PASTE[] = v
×
1235

1236
function contextual_prompt(repl::LineEditREPL, prompt::Union{String,Function})
1237
    function ()
8,543✔
1238
        mod = Base.active_module(repl)
16,728✔
1239
        prefix = mod == Main ? "" : string('(', mod, ") ")
8,458✔
1240
        pr = prompt isa String ? prompt : prompt()
8,376✔
1241
        prefix * pr
8,376✔
1242
    end
1243
end
1244

1245
setup_interface(
234✔
1246
    repl::LineEditREPL;
1247
    # those keyword arguments may be deprecated eventually in favor of the Options mechanism
1248
    hascolor::Bool = repl.options.hascolor,
1249
    extra_repl_keymap::Any = repl.options.extra_keymap
1250
) = setup_interface(repl, hascolor, extra_repl_keymap)
1251

1252

1253
# This non keyword method can be precompiled which is important
1254
function setup_interface(
82✔
1255
    repl::LineEditREPL,
1256
    hascolor::Bool,
1257
    extra_repl_keymap::Any, # Union{Dict,Vector{<:Dict}},
1258
)
1259
    # The precompile statement emitter has problem outputting valid syntax for the
1260
    # type of `Union{Dict,Vector{<:Dict}}` (see #28808).
1261
    # This function is however important to precompile for REPL startup time, therefore,
1262
    # make the type Any and just assert that we have the correct type below.
1263
    @assert extra_repl_keymap isa Union{Dict,Vector{<:Dict}}
82✔
1264

1265
    ###
1266
    #
1267
    # This function returns the main interface that describes the REPL
1268
    # functionality, it is called internally by functions that setup a
1269
    # Terminal-based REPL frontend.
1270
    #
1271
    # See run_frontend(repl::LineEditREPL, backend::REPLBackendRef)
1272
    # for usage
1273
    #
1274
    ###
1275

1276
    ###
1277
    # We setup the interface in two stages.
1278
    # First, we set up all components (prompt,rsearch,shell,help)
1279
    # Second, we create keymaps with appropriate transitions between them
1280
    #   and assign them to the components
1281
    #
1282
    ###
1283

1284
    ############################### Stage I ################################
1285

1286
    # This will provide completions for REPL and help mode
1287
    replc = REPLCompletionProvider()
82✔
1288

1289
    # Set up the main Julia prompt
1290
    julia_prompt = Prompt(contextual_prompt(repl, JULIA_PROMPT);
163✔
1291
        # Copy colors from the prompt object
1292
        prompt_prefix = hascolor ? repl.prompt_color : "",
1293
        prompt_suffix = hascolor ?
1294
            (repl.envcolors ? Base.input_color : repl.input_color) : "",
1295
        repl = repl,
1296
        complete = replc,
1297
        on_enter = return_callback,
1298
        styling_passes = StylingPasses.StylingPass[
1299
            StylingPasses.SyntaxHighlightPass(),
1300
            StylingPasses.EnclosingParenHighlightPass()
1301
        ])
1302

1303
    # Setup help mode
1304
    help_mode = Prompt(contextual_prompt(repl, HELP_PROMPT),
163✔
1305
        prompt_prefix = hascolor ? repl.help_color : "",
1306
        prompt_suffix = hascolor ?
1307
            (repl.envcolors ? Base.input_color : repl.input_color) : "",
1308
        repl = repl,
1309
        complete = replc,
1310
        # When we're done transform the entered line into a call to helpmode function
1311
        on_done = respond(line::String->helpmode(outstream(repl), line, Base.active_module(repl)),
6✔
1312
                          repl, julia_prompt, pass_empty=true, suppress_on_semicolon=false))
1313

1314

1315
    # Set up shell mode
1316
    shell_mode = Prompt(SHELL_PROMPT;
163✔
1317
        prompt_prefix = hascolor ? repl.shell_color : "",
1318
        prompt_suffix = hascolor ?
1319
            (repl.envcolors ? Base.input_color : repl.input_color) : "",
1320
        repl = repl,
1321
        complete = ShellCompletionProvider(),
1322
        # Transform "foo bar baz" into `foo bar baz` (shell quoting)
1323
        # and pass into Base.repl_cmd for processing (handles `ls` and `cd`
1324
        # special)
1325
        on_done = respond(repl, julia_prompt) do line
1326
            cmd_ex = Base.shell_parse(line::String)[1]
47✔
1327
            if Meta.isexpr(cmd_ex, :tuple)
44✔
1328
                cmd_ex = :(Base.cmd_gen($cmd_ex))
22✔
1329
            end
1330
            Expr(:call, :(Base.repl_cmd), cmd_ex, outstream(repl))
22✔
1331
        end,
1332
        sticky = true)
1333

1334
    # Set up dummy Pkg mode that will be replaced once Pkg is loaded
1335
    # use 6 dots to occupy the same space as the most likely "@v1.xx" env name
1336
    dummy_pkg_mode = Prompt(Pkg_promptf,
163✔
1337
        prompt_prefix = hascolor ? repl.pkg_color : "",
1338
        prompt_suffix = hascolor ?
1339
        (repl.envcolors ? Base.input_color : repl.input_color) : "",
1340
        repl = repl,
1341
        complete = LineEdit.EmptyCompletionProvider(),
1342
        on_done = respond(line->nothing, repl, julia_prompt),
1343
        on_enter = function (s::MIState)
1344
                # This is hit when the user tries to execute a command before the real Pkg mode has been
1345
                # switched to. Ok to do this even if Pkg is loading on the other task because of the loading lock.
1346
                REPLExt = load_pkg()
1347
                if REPLExt isa Module && isdefined(REPLExt, :PkgCompletionProvider)
1348
                    for mode in repl.interface.modes
1349
                        if mode isa LineEdit.Prompt && mode.complete isa REPLExt.PkgCompletionProvider
1350
                            # pkg mode
1351
                            let buf = copy(LineEdit.buffer(s))
1352
                                transition(s, mode) do
1353
                                    LineEdit.state(s, mode).input_buffer = buf
1354
                                end
1355
                            end
1356
                        end
1357
                    end
1358
                end
1359
                return true
1360
            end,
1361
        sticky = true)
1362

1363

1364
    ################################# Stage II #############################
1365

1366
    # Setup history
1367
    # We will have a unified history for all REPL modes
1368
    hp = REPLHistoryProvider(Dict{Symbol,Prompt}(:julia => julia_prompt,
82✔
1369
                                                 :shell => shell_mode,
1370
                                                 :help  => help_mode,
1371
                                                 :pkg  => dummy_pkg_mode))
1372
    if repl.history_file
82✔
1373
        try
13✔
1374
            path = find_hist_file()
13✔
1375
            mkpath(dirname(path))
13✔
1376
            hp.history = HistoryFile(path)
13✔
1377
            errormonitor(@async history_do_initialize(hp))
26✔
1378
            finalizer(replc) do replc
13✔
1379
                close(hp.history)
12✔
1380
            end
1381
        catch
1382
            # use REPL.hascolor to avoid using the local variable with the same name
1383
            print_response(repl, Pair{Any, Bool}(current_exceptions(), true), true, REPL.hascolor(repl))
×
1384
            println(outstream(repl))
×
1385
            @info "Disabling history file for this session"
×
1386
            repl.history_file = false
×
1387
        end
1388
    end
1389
    history_reset_state(hp)
82✔
1390
    julia_prompt.hist = hp
82✔
1391
    shell_mode.hist = hp
82✔
1392
    help_mode.hist = hp
82✔
1393
    dummy_pkg_mode.hist = hp
82✔
1394

1395
    julia_prompt.on_done = respond(x->parse_repl_input_line(x, repl; filename=repl_filename(repl,hp)), repl, julia_prompt)
355✔
1396

1397
    shell_prompt_len = length(SHELL_PROMPT)
82✔
1398
    help_prompt_len = length(HELP_PROMPT)
82✔
1399
    jl_prompt_regex = Regex("^In \\[[0-9]+\\]: |^(?:\\(.+\\) )?$JULIA_PROMPT")
82✔
1400
    pkg_prompt_regex = Regex("^(?:\\(.+\\) )?$PKG_PROMPT")
82✔
1401

1402
    # Canonicalize user keymap input
1403
    if isa(extra_repl_keymap, Dict)
82✔
1404
        extra_repl_keymap = AnyDict[extra_repl_keymap]
×
1405
    end
1406

1407
    repl_keymap = AnyDict(
82✔
1408
        ';' => function (s::MIState,o...)
163✔
1409
            if isempty(s) || position(LineEdit.buffer(s)) == 0
307✔
1410
                let buf = copy(LineEdit.buffer(s))
19✔
1411
                    transition(s, shell_mode) do
19✔
1412
                        LineEdit.state(s, shell_mode).input_buffer = buf
19✔
1413
                    end
1414
                end
1415
            else
1416
                edit_insert(s, ';')
144✔
1417
                LineEdit.check_show_hint(s)
144✔
1418
            end
1419
        end,
1420
        '?' => function (s::MIState,o...)
3✔
1421
            if isempty(s) || position(LineEdit.buffer(s)) == 0
3✔
1422
                let buf = copy(LineEdit.buffer(s))
3✔
1423
                    transition(s, help_mode) do
3✔
1424
                        LineEdit.state(s, help_mode).input_buffer = buf
3✔
1425
                    end
1426
                end
1427
            else
1428
                edit_insert(s, '?')
×
1429
                LineEdit.check_show_hint(s)
×
1430
            end
1431
        end,
1432
        ']' => function (s::MIState,o...)
9✔
1433
            if isempty(s) || position(LineEdit.buffer(s)) == 0
18✔
1434
                let buf = copy(LineEdit.buffer(s))
×
1435
                    transition(s, dummy_pkg_mode) do
×
1436
                        LineEdit.state(s, dummy_pkg_mode).input_buffer = buf
1437
                    end
1438
                end
1439
                # load Pkg on another thread if available so that typing in the dummy Pkg prompt
1440
                # isn't blocked, but instruct the main REPL task to do the transition via s.async_channel
1441
                t_replswitch = Threads.@spawn begin
×
1442
                    REPLExt = load_pkg()
1443
                    if REPLExt isa Module && isdefined(REPLExt, :PkgCompletionProvider)
1444
                        put!(s.async_channel,
1445
                            function (s::MIState)
1446
                                LineEdit.mode(s) === dummy_pkg_mode || return :ok
1447
                                for mode in repl.interface.modes
1448
                                    if mode isa LineEdit.Prompt && mode.complete isa REPLExt.PkgCompletionProvider
1449
                                        let buf = copy(LineEdit.buffer(s))
1450
                                            transition(s, mode) do
1451
                                                LineEdit.state(s, mode).input_buffer = buf
1452
                                            end
1453
                                        end
1454
                                        if !isempty(s)
1455
                                            @invokelatest(LineEdit.check_show_hint(s))
1456
                                        end
1457
                                        break
1458
                                    end
1459
                                end
1460
                                return :ok
1461
                            end
1462
                        )
1463
                    end
1464
                end
1465
                Base.errormonitor(t_replswitch)
×
1466
            else
1467
                # Use bracket insertion if enabled, otherwise just insert
1468
                if repl.options.auto_insert_closing_bracket
9✔
1469
                    buf = LineEdit.buffer(s)
×
1470
                    if !eof(buf) && LineEdit.peek(buf, Char) == ']'
×
1471
                        LineEdit.edit_move_right(buf)
×
1472
                    else
1473
                        edit_insert(buf, ']')
×
1474
                    end
1475
                    LineEdit.refresh_line(s)
×
1476
                else
1477
                    edit_insert(s, ']')
9✔
1478
                end
1479
                LineEdit.check_show_hint(s)
9✔
1480
            end
1481
        end,
1482

1483
        # Bracketed Paste Mode
1484
        "\e[200~" => (s::MIState,o...)->begin
24✔
1485
            input = LineEdit.bracketed_paste(s) # read directly from s until reaching the end-bracketed-paste marker
24✔
1486
            sbuffer = LineEdit.buffer(s)
24✔
1487
            curspos = position(sbuffer)
24✔
1488
            seek(sbuffer, 0)
48✔
1489
            shouldeval = (bytesavailable(sbuffer) == curspos && !occursin(UInt8('\n'), sbuffer))
24✔
1490
            seek(sbuffer, curspos)
48✔
1491
            if curspos == 0
24✔
1492
                # if pasting at the beginning, strip leading whitespace
1493
                input = lstrip(input)
21✔
1494
            end
1495
            if !shouldeval
24✔
1496
                # when pasting in the middle of input, just paste in place
1497
                # don't try to execute all the WIP, since that's rather confusing
1498
                # and is often ill-defined how it should behave
1499
                edit_insert(s, input)
×
1500
                return
×
1501
            end
1502
            LineEdit.push_undo(s)
24✔
1503
            edit_insert(sbuffer, input)
24✔
1504
            input = String(take!(sbuffer))
48✔
1505
            oldpos = firstindex(input)
24✔
1506
            firstline = true
24✔
1507
            isprompt_paste = false
24✔
1508
            curr_prompt_len = 0
24✔
1509
            pasting_help = false
24✔
1510

1511
            while oldpos <= lastindex(input) # loop until all lines have been executed
150✔
1512
                if JL_PROMPT_PASTE[]
72✔
1513
                    # Check if the next statement starts with a prompt i.e. "julia> ", in that case
1514
                    # skip it. But first skip whitespace unless pasting in a docstring which may have
1515
                    # indented prompt examples that we don't want to execute
1516
                    while input[oldpos] in (pasting_help ? ('\n') : ('\n', ' ', '\t'))
258✔
1517
                        oldpos = nextind(input, oldpos)
144✔
1518
                        oldpos >= sizeof(input) && return
72✔
1519
                    end
72✔
1520
                    substr = SubString(input, oldpos)
144✔
1521
                    # Check if input line starts with "julia> ", remove it if we are in prompt paste mode
1522
                    if (firstline || isprompt_paste) && startswith(substr, jl_prompt_regex)
72✔
1523
                        detected_jl_prompt = match(jl_prompt_regex, substr).match
33✔
1524
                        isprompt_paste = true
33✔
1525
                        curr_prompt_len = sizeof(detected_jl_prompt)
33✔
1526
                        oldpos += curr_prompt_len
33✔
1527
                        transition(s, julia_prompt)
33✔
1528
                        pasting_help = false
33✔
1529
                    # Check if input line starts with "pkg> " or "(...) pkg> ", remove it if we are in prompt paste mode and switch mode
1530
                    elseif (firstline || isprompt_paste) && startswith(substr, pkg_prompt_regex)
39✔
1531
                        detected_pkg_prompt = match(pkg_prompt_regex, substr).match
×
1532
                        isprompt_paste = true
×
1533
                        curr_prompt_len = sizeof(detected_pkg_prompt)
×
1534
                        oldpos += curr_prompt_len
×
1535
                        Base.active_repl.interface.modes[1].keymap_dict[']'](s, o...)
×
1536
                        pasting_help = false
×
1537
                    # Check if input line starts with "shell> ", remove it if we are in prompt paste mode and switch mode
1538
                    elseif (firstline || isprompt_paste) && startswith(substr, SHELL_PROMPT)
39✔
1539
                        isprompt_paste = true
6✔
1540
                        oldpos += shell_prompt_len
6✔
1541
                        curr_prompt_len = shell_prompt_len
6✔
1542
                        transition(s, shell_mode)
6✔
1543
                        pasting_help = false
6✔
1544
                    # Check if input line starts with "help?> ", remove it if we are in prompt paste mode and switch mode
1545
                    elseif (firstline || isprompt_paste) && startswith(substr, HELP_PROMPT)
33✔
1546
                        isprompt_paste = true
3✔
1547
                        oldpos += help_prompt_len
3✔
1548
                        curr_prompt_len = help_prompt_len
3✔
1549
                        transition(s, help_mode)
3✔
1550
                        pasting_help = true
3✔
1551
                    # If we are prompt pasting and current statement does not begin with a mode prefix, skip to next line
1552
                    elseif isprompt_paste
30✔
1553
                        while input[oldpos] != '\n'
948✔
1554
                            oldpos = nextind(input, oldpos)
906✔
1555
                            oldpos >= sizeof(input) && return
453✔
1556
                        end
447✔
1557
                        continue
21✔
1558
                    end
1559
                end
1560
                dump_tail = false
45✔
1561
                nl_pos = findfirst('\n', input[oldpos:end])
90✔
1562
                if s.current_mode == julia_prompt
45✔
1563
                    ast, pos = Meta.parse(input, oldpos, raise=false, depwarn=false, mod=Base.active_module(s))
36✔
1564
                    if (isa(ast, Expr) && (ast.head === :error || ast.head === :incomplete)) ||
66✔
1565
                            (pos > ncodeunits(input) && !endswith(input, '\n'))
1566
                        # remaining text is incomplete (an error, or parser ran to the end but didn't stop with a newline):
1567
                        # Insert all the remaining text as one line (might be empty)
1568
                        dump_tail = true
15✔
1569
                    end
1570
                elseif isnothing(nl_pos) # no newline at end, so just dump the tail into the prompt and don't execute
18✔
1571
                    dump_tail = true
×
1572
                elseif s.current_mode == shell_mode # handle multiline shell commands
9✔
1573
                    lines = split(input[oldpos:end], '\n')
12✔
1574
                    pos = oldpos + sizeof(lines[1]) + 1
6✔
1575
                    if length(lines) > 1
6✔
1576
                        for line in lines[2:end]
6✔
1577
                            # to be recognized as a multiline shell command, the lines must be indented to the
1578
                            # same prompt position
1579
                            if !startswith(line, ' '^curr_prompt_len)
9✔
1580
                                break
6✔
1581
                            end
1582
                            pos += sizeof(line) + 1
3✔
1583
                        end
3✔
1584
                    end
1585
                else
1586
                    pos = oldpos + nl_pos
3✔
1587
                end
1588
                if dump_tail
45✔
1589
                    tail = input[oldpos:end]
30✔
1590
                    if !firstline
15✔
1591
                        # strip leading whitespace, but only if it was the result of executing something
1592
                        # (avoids modifying the user's current leading wip line)
1593
                        tail = lstrip(tail)
3✔
1594
                    end
1595
                    if isprompt_paste # remove indentation spaces corresponding to the prompt
15✔
1596
                        tail = replace(tail, r"^"m * ' '^curr_prompt_len => "")
21✔
1597
                    end
1598
                    LineEdit.replace_line(s, tail, true)
15✔
1599
                    LineEdit.refresh_line(s)
15✔
1600
                    break
15✔
1601
                end
1602
                # get the line and strip leading and trailing whitespace
1603
                line = strip(input[oldpos:prevind(input, pos)])
60✔
1604
                if !isempty(line)
30✔
1605
                    if isprompt_paste # remove indentation spaces corresponding to the prompt
30✔
1606
                        line = replace(line, r"^"m * ' '^curr_prompt_len => "")
30✔
1607
                    end
1608
                    # put the line on the screen and history
1609
                    LineEdit.replace_line(s, line)
60✔
1610
                    LineEdit.commit_line(s)
30✔
1611
                    # execute the statement
1612
                    terminal = LineEdit.terminal(s) # This is slightly ugly but ok for now
30✔
1613
                    raw!(terminal, false) && disable_bracketed_paste(terminal)
30✔
1614
                    @invokelatest LineEdit.mode(s).on_done(s, LineEdit.buffer(s), true)
30✔
1615
                    raw!(terminal, true) && enable_bracketed_paste(terminal)
30✔
1616
                    LineEdit.push_undo(s) # when the last line is incomplete
30✔
1617
                end
1618
                oldpos = pos
30✔
1619
                firstline = false
30✔
1620
            end
69✔
1621
        end,
1622

1623
        # Open the editor at the location of a stackframe or method
1624
        # This is accessing a contextual variable that gets set in
1625
        # the show_backtrace and show_method_table functions.
1626
        "^Q" => (s::MIState, o...) -> begin
1627
            linfos = repl.last_shown_line_infos
1628
            str = String(take!(LineEdit.buffer(s)))
1629
            n = tryparse(Int, str)
1630
            @label writeback begin
1631
                n === nothing && break writeback
1632
                if n <= 0 || n > length(linfos) || startswith(linfos[n][1], "REPL[")
1633
                    break writeback
1634
                end
1635
                try
1636
                    InteractiveUtils.edit(Base.fixup_stdlib_path(linfos[n][1]), linfos[n][2])
1637
                catch ex
1638
                    ex isa ProcessFailedException || ex isa Base.IOError || ex isa SystemError || rethrow()
1639
                    @info "edit failed" _exception=ex
1640
                end
1641
                LineEdit.refresh_line(s)
1642
                return
1643
            end
1644
            write(LineEdit.buffer(s), str)
1645
            return
1646
        end,
1647
    )
1648

1649
    prefix_prompt, prefix_keymap = LineEdit.setup_prefix_keymap(hp, julia_prompt)
82✔
1650

1651
    # Build keymap list - add bracket insertion if enabled
1652
    base_keymaps = Dict{Any,Any}[repl_keymap, prefix_keymap, LineEdit.history_keymap]
246✔
1653
    if repl.options.auto_insert_closing_bracket
82✔
1654
        push!(base_keymaps, LineEdit.bracket_insert_keymap)
1✔
1655
    end
1656
    push!(base_keymaps, LineEdit.default_keymap, LineEdit.escape_defaults)
82✔
1657

1658
    a = base_keymaps
82✔
1659
    prepend!(a, extra_repl_keymap)
82✔
1660

1661
    julia_prompt.keymap_dict = LineEdit.keymap(a)
82✔
1662

1663
    mk = mode_keymap(julia_prompt)
82✔
1664

1665
    # Build keymap list for other modes
1666
    mode_base_keymaps = Dict{Any,Any}[mk, prefix_keymap, LineEdit.history_keymap]
246✔
1667
    if repl.options.auto_insert_closing_bracket
82✔
1668
        push!(mode_base_keymaps, LineEdit.bracket_insert_keymap)
1✔
1669
    end
1670
    push!(mode_base_keymaps, LineEdit.default_keymap, LineEdit.escape_defaults)
82✔
1671

1672
    b = mode_base_keymaps
82✔
1673
    prepend!(b, extra_repl_keymap)
82✔
1674

1675
    shell_mode.keymap_dict = help_mode.keymap_dict = dummy_pkg_mode.keymap_dict = LineEdit.keymap(b)
82✔
1676

1677
    allprompts = LineEdit.TextInterface[julia_prompt, shell_mode, help_mode, dummy_pkg_mode, prefix_prompt]
82✔
1678
    return ModalInterface(allprompts)
82✔
1679
end
1680

1681
function run_frontend(repl::LineEditREPL, backend::REPLBackendRef)
70✔
1682
    repl.frontend_task = current_task()
70✔
1683
    d = REPLDisplay(repl)
70✔
1684
    dopushdisplay = repl.specialdisplay === nothing && !in(d,Base.Multimedia.displays)
119✔
1685
    dopushdisplay && pushdisplay(d)
70✔
1686
    if !isdefined(repl,:interface)
70✔
1687
        interface = repl.interface = setup_interface(repl)
80✔
1688
    else
1689
        interface = repl.interface
30✔
1690
    end
1691
    repl.backendref = backend
70✔
1692
    repl.mistate = LineEdit.init_state(terminal(repl), interface)
70✔
1693
    # Copy prompt_ready_event from repl to mistate (used by precompilation)
1694
    if isdefined(repl, :prompt_ready_event) && repl.prompt_ready_event !== nothing
70✔
1695
        repl.mistate.prompt_ready_event = repl.prompt_ready_event
×
1696
    end
1697
    run_interface(terminal(repl), interface, repl.mistate)
70✔
1698
    # Terminate Backend
1699
    put!(backend.repl_channel, (nothing, -1))
69✔
1700
    dopushdisplay && popdisplay(d)
69✔
1701
    nothing
69✔
1702
end
1703

1704
## StreamREPL ##
1705

1706
mutable struct StreamREPL <: AbstractREPL
1707
    stream::IO
1708
    prompt_color::String
1709
    input_color::String
1710
    answer_color::String
1711
    waserror::Bool
1712
    frontend_task::Task
1713
    StreamREPL(stream,pc,ic,ac) = new(stream,pc,ic,ac,false)
×
1714
end
1715
StreamREPL(stream::IO) = StreamREPL(stream, Base.text_colors[:green], Base.input_color(), Base.answer_color())
×
1716
run_repl(stream::IO) = run_repl(StreamREPL(stream))
×
1717

1718
outstream(s::StreamREPL) = s.stream
×
1719
hascolor(s::StreamREPL) = get(s.stream, :color, false)::Bool
×
1720

1721
answer_color(r::LineEditREPL) = r.envcolors ? Base.answer_color() : r.answer_color
×
1722
answer_color(r::StreamREPL) = r.answer_color
×
1723
input_color(r::LineEditREPL) = r.envcolors ? Base.input_color() : r.input_color
×
1724
input_color(r::StreamREPL) = r.input_color
×
1725

1726
# heuristic function to decide if the presence of a semicolon
1727
# at the end of the expression was intended for suppressing output
1728
function ends_with_semicolon(code)
394✔
1729
    semi = false
394✔
1730
    for tok in tokenize(code)
394✔
1731
        kind(tok) in KSet"Whitespace NewlineWs Comment EndMarker" && continue
7,387✔
1732
        semi = kind(tok) == K";"
2,858✔
1733
    end
3,985✔
1734
    return semi
394✔
1735
end
1736

1737
function banner(io::IO = stdout; short = false)
12✔
1738
    if Base.GIT_VERSION_INFO.tagged_commit
6✔
1739
        commit_string = Base.TAGGED_RELEASE_BANNER
×
1740
    elseif isempty(Base.GIT_VERSION_INFO.commit)
6✔
1741
        commit_string = ""
×
1742
    else
1743
        days = Int(floor((ccall(:jl_clock_now, Float64, ()) - Base.GIT_VERSION_INFO.fork_master_timestamp) / (60 * 60 * 24)))
6✔
1744
        days = max(0, days)
6✔
1745
        unit = days == 1 ? "day" : "days"
6✔
1746
        distance = Base.GIT_VERSION_INFO.fork_master_distance
6✔
1747
        commit = Base.GIT_VERSION_INFO.commit_short
6✔
1748

1749
        if distance == 0
6✔
1750
            commit_string = "Commit $(commit) ($(days) $(unit) old master)"
6✔
1751
        else
1752
            branch = Base.GIT_VERSION_INFO.branch
×
1753
            commit_string = "$(branch)/$(commit) (fork: $(distance) commits, $(days) $(unit))"
×
1754
        end
1755
    end
1756

1757
    commit_date = isempty(Base.GIT_VERSION_INFO.date_string) ? "" : " ($(split(Base.GIT_VERSION_INFO.date_string)[1]))"
6✔
1758

1759
    if get(io, :color, false)::Bool
6✔
1760
        c = Base.text_colors
×
1761
        tx = c[:normal] # text
×
1762
        jl = c[:normal] # julia
×
1763
        d1 = c[:bold] * c[:blue]    # first dot
×
1764
        d2 = c[:bold] * c[:red]     # second dot
×
1765
        d3 = c[:bold] * c[:green]   # third dot
×
1766
        d4 = c[:bold] * c[:magenta] # fourth dot
×
1767

1768
        if short
×
1769
            print(io,"""
×
1770
              $(d3)o$(tx)  | Version $(VERSION)$(commit_date)
1771
             $(d2)o$(tx) $(d4)o$(tx) | $(commit_string)
1772
            """)
1773
        else
1774
            print(io,"""               $(d3)_$(tx)
×
1775
               $(d1)_$(tx)       $(jl)_$(tx) $(d2)_$(d3)(_)$(d4)_$(tx)     |  Documentation: https://docs.julialang.org
1776
              $(d1)(_)$(jl)     | $(d2)(_)$(tx) $(d4)(_)$(tx)    |
1777
               $(jl)_ _   _| |_  __ _$(tx)   |  Type \"?\" for help, \"]?\" for Pkg help.
1778
              $(jl)| | | | | | |/ _` |$(tx)  |
1779
              $(jl)| | |_| | | | (_| |$(tx)  |  Version $(VERSION)$(commit_date)
1780
             $(jl)_/ |\\__'_|_|_|\\__'_|$(tx)  |  $(commit_string)
1781
            $(jl)|__/$(tx)                   |
1782

1783
            """)
1784
        end
1785
    else
1786
        if short
6✔
1787
            print(io,"""
3✔
1788
              o  |  Version $(VERSION)$(commit_date)
1789
             o o |  $(commit_string)
1790
            """)
1791
        else
1792
            print(io,"""
3✔
1793
                           _
1794
               _       _ _(_)_     |  Documentation: https://docs.julialang.org
1795
              (_)     | (_) (_)    |
1796
               _ _   _| |_  __ _   |  Type \"?\" for help, \"]?\" for Pkg help.
1797
              | | | | | | |/ _` |  |
1798
              | | |_| | | | (_| |  |  Version $(VERSION)$(commit_date)
1799
             _/ |\\__'_|_|_|\\__'_|  |  $(commit_string)
1800
            |__/                   |
1801

1802
            """)
1803
        end
1804
    end
1805
end
1806

1807
function run_frontend(repl::StreamREPL, backend::REPLBackendRef)
×
1808
    repl.frontend_task = current_task()
×
1809
    have_color = hascolor(repl)
×
1810
    banner(repl.stream)
×
1811
    d = REPLDisplay(repl)
×
1812
    dopushdisplay = !in(d,Base.Multimedia.displays)
×
1813
    dopushdisplay && pushdisplay(d)
×
1814
    while !eof(repl.stream)::Bool
×
1815
        if have_color
×
1816
            print(repl.stream,repl.prompt_color)
×
1817
        end
1818
        print(repl.stream, JULIA_PROMPT)
×
1819
        if have_color
×
1820
            print(repl.stream, input_color(repl))
×
1821
        end
1822
        line = readline(repl.stream, keep=true)
×
1823
        if !isempty(line)
×
1824
            ast = parse_repl_input_line(line, repl)
×
1825
            if have_color
×
1826
                print(repl.stream, Base.color_normal)
×
1827
            end
1828
            response = eval_on_backend(ast, backend)
×
1829
            print_response(repl, response, !ends_with_semicolon(line), have_color)
×
1830
        end
1831
    end
×
1832
    # Terminate Backend
1833
    put!(backend.repl_channel, (nothing, -1))
×
1834
    dopushdisplay && popdisplay(d)
×
1835
    nothing
×
1836
end
1837

1838
module Numbered
1839

1840
using ..REPL
1841

1842
__current_ast_transforms() = Base.active_repl_backend !== nothing ? Base.active_repl_backend.ast_transforms : REPL.repl_ast_transforms
×
1843

1844
function repl_eval_counter(hp)
1,701✔
1845
    return length(hp.history) - hp.start_idx
1,701✔
1846
end
1847

1848
function out_transform(@nospecialize(x), n::Ref{Int})
48✔
1849
    return Expr(:block, # avoid line numbers or scope that would leak into the output and change the meaning of x
48✔
1850
        :(local __temp_val_a72df459 = $x),
1851
        Expr(:call, capture_result, n, :__temp_val_a72df459),
1852
        :__temp_val_a72df459)
1853
end
1854

1855
function create_global_out!(mod)
3✔
1856
    if !isdefinedglobal(mod, :Out)
3✔
1857
        out = Dict{Int, Any}()
3✔
1858
        @eval mod begin
3✔
1859
            const Out = $(out)
1860
            export Out
1861
        end
1862
        return out
3✔
1863
    end
1864
    return getglobal(mod, :Out)
×
1865
end
1866

1867
function capture_result(n::Ref{Int}, @nospecialize(x))
48✔
1868
    n = n[]
48✔
1869
    mod = Base.MainInclude
48✔
1870
    # TODO: This invokelatest is only required due to backdated constants
1871
    # and should be removed after
1872
    out = isdefinedglobal(mod, :Out) ? invokelatest(getglobal, mod, :Out) : invokelatest(create_global_out!, mod)
51✔
1873
    if x !== out && x !== nothing # remove this?
48✔
1874
        out[n] = x
42✔
1875
    end
1876
    nothing
48✔
1877
end
1878

1879
function set_prompt(repl::LineEditREPL, n::Ref{Int})
3✔
1880
    julia_prompt = repl.interface.modes[1]
3✔
1881
    julia_prompt.prompt = REPL.contextual_prompt(repl, function()
1,704✔
1882
        n[] = repl_eval_counter(julia_prompt.hist)+1
1,701✔
1883
        string("In [", n[], "]: ")
1,701✔
1884
    end)
1885
    nothing
3✔
1886
end
1887

1888
function set_output_prefix(repl::LineEditREPL, n::Ref{Int})
3✔
1889
    julia_prompt = repl.interface.modes[1]
3✔
1890
    if REPL.hascolor(repl)
3✔
1891
        julia_prompt.output_prefix_prefix = Base.text_colors[:red]
3✔
1892
    end
1893
    julia_prompt.output_prefix = () -> string("Out[", n[], "]: ")
45✔
1894
    nothing
3✔
1895
end
1896

1897
function __current_ast_transforms(backend)
1898
    if backend === nothing
3✔
1899
        Base.active_repl_backend !== nothing ? Base.active_repl_backend.ast_transforms : REPL.repl_ast_transforms
×
1900
    else
1901
        backend.ast_transforms
3✔
1902
    end
1903
end
1904

1905
function numbered_prompt!(repl::LineEditREPL=Base.active_repl::LineEditREPL, backend=nothing)
1906
    n = Ref{Int}(0)
3✔
1907
    set_prompt(repl, n)
3✔
1908
    set_output_prefix(repl, n)
3✔
1909
    push!(__current_ast_transforms(backend), @nospecialize(ast) -> out_transform(ast, n))
51✔
1910
    return
3✔
1911
end
1912

1913
"""
1914
    Out[n]
1915

1916
A variable referring to all previously computed values, automatically imported to the interactive prompt.
1917
Only defined and exists while using [Numbered prompt](@ref Numbered-prompt).
1918

1919
See also [`ans`](@ref).
1920
"""
1921
Base.MainInclude.Out
1922

1923
end
1924

1925
import .Numbered.numbered_prompt!
1926

1927
# this assignment won't survive precompilation,
1928
# but will stick if REPL is baked into a sysimg.
1929
# Needs to occur after this module is finished.
1930
Base.REPL_MODULE_REF[] = REPL
1931

1932
if Base.generating_output()
1933
   include("precompile.jl")
1934
end
1935

1936
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