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

JuliaLang / julia / #37711

09 Mar 2024 10:22PM UTC coverage: 86.665% (+1.5%) from 85.122%
#37711

push

local

web-flow
Bump CSL to 1.1.1 to fix libgomp bug (#53643)

Resolves https://github.com/JuliaLang/julia/issues/53363

76369 of 88120 relevant lines covered (86.66%)

15458526.27 hits per line

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

77.33
/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_hint(io::IO, ex::UndefVarError)
2✔
21
    var = ex.var
2✔
22
    if var === :or
2✔
23
        print(io, "\nSuggestion: Use `||` for short-circuiting boolean OR.")
×
24
    elseif var === :and
2✔
25
        print(io, "\nSuggestion: Use `&&` for short-circuiting boolean AND.")
×
26
    elseif var === :help
2✔
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
2✔
31
        print(io, "\nSuggestion: To exit Julia, use Ctrl-D, or type exit() and press enter.")
×
32
    end
33
    if isdefined(ex, :scope)
2✔
34
        scope = ex.scope
2✔
35
        if scope isa Module
2✔
36
            bnd = ccall(:jl_get_module_binding, Any, (Any, Any, Cint), scope, var, true)::Core.Binding
2✔
37
            if isdefined(bnd, :owner)
2✔
38
                owner = bnd.owner
×
39
                if owner === bnd
×
40
                    print(io, "\nSuggestion: add an appropriate import or assignment. This global was declared but not assigned.")
×
41
                end
42
            else
43
                owner = ccall(:jl_binding_owner, Ptr{Cvoid}, (Any, Any), scope, var)
2✔
44
                if C_NULL == owner
2✔
45
                    # No global of this name exists in this module.
46
                    # This is the common case, so do not print that information.
47
                    # It could be the binding was exported by two modules, which we can detect
48
                    # by the `usingfailed` flag in the binding:
49
                    if isdefined(bnd, :flags) && Bool(bnd.flags >> 4 & 1) # magic location of the `usingfailed` flag
3✔
50
                        print(io, "\nHint: It looks like two or more modules export different ",
1✔
51
                              "bindings with this name, resulting in ambiguity. Try explicitly ",
52
                              "importing it from a particular module, or qualifying the name ",
53
                              "with the module it should come from.")
54
                    else
55
                        print(io, "\nSuggestion: check for spelling errors or missing imports.")
1✔
56
                    end
57
                    owner = bnd
2✔
58
                else
59
                    owner = unsafe_pointer_to_objref(owner)::Core.Binding
×
60
                end
61
            end
62
            if owner !== bnd
2✔
63
                # this could use jl_binding_dbgmodule for the exported location in the message too
64
                print(io, "\nSuggestion: this global was defined as `$(owner.globalref)` but not assigned a value.")
×
65
            end
66
        elseif scope === :static_parameter
×
67
            print(io, "\nSuggestion: run Test.detect_unbound_args to detect method arguments that do not fully constrain a type parameter.")
×
68
        elseif scope === :local
×
69
            print(io, "\nSuggestion: check for an assignment to a local variable that shadows a global of the same name.")
×
70
        end
71
    else
72
        scope = undef
×
73
    end
74
    if scope !== Base && !_UndefVarError_warnfor(io, Base, var)
2✔
75
        warned = false
1✔
76
        for m in Base.loaded_modules_order
1✔
77
            m === Core && continue
24✔
78
            m === Base && continue
23✔
79
            m === Main && continue
22✔
80
            m === scope && continue
21✔
81
            warned |= _UndefVarError_warnfor(io, m, var)
21✔
82
        end
24✔
83
        warned ||
2✔
84
            _UndefVarError_warnfor(io, Core, var) ||
85
            _UndefVarError_warnfor(io, Main, var)
86
    end
87
    return nothing
2✔
88
end
89

90
function _UndefVarError_warnfor(io::IO, m::Module, var::Symbol)
25✔
91
    Base.isbindingresolved(m, var) || return false
48✔
92
    (Base.isexported(m, var) || Base.ispublic(m, var)) || return false
4✔
93
    print(io, "\nHint: a global variable of this name also exists in $m.")
1✔
94
    return true
1✔
95
end
96

97
function __init__()
5✔
98
    Base.REPL_MODULE_REF[] = REPL
5✔
99
    Base.Experimental.register_error_hint(UndefVarError_hint, UndefVarError)
5✔
100
    return nothing
5✔
101
end
102

103
using Base.Meta, Sockets, StyledStrings
104
import InteractiveUtils
105

106
export
107
    AbstractREPL,
108
    BasicREPL,
109
    LineEditREPL,
110
    StreamREPL
111

112
import Base:
113
    AbstractDisplay,
114
    display,
115
    show,
116
    AnyDict,
117
    ==
118

119
_displaysize(io::IO) = displaysize(io)::Tuple{Int,Int}
32✔
120

121
include("Terminals.jl")
122
using .Terminals
123

124
abstract type AbstractREPL end
125

126
include("options.jl")
127

128
include("LineEdit.jl")
129
using .LineEdit
130
import ..LineEdit:
131
    CompletionProvider,
132
    HistoryProvider,
133
    add_history,
134
    complete_line,
135
    history_next,
136
    history_next_prefix,
137
    history_prev,
138
    history_prev_prefix,
139
    history_first,
140
    history_last,
141
    history_search,
142
    setmodifiers!,
143
    terminal,
144
    MIState,
145
    PromptState,
146
    mode_idx
147

148
include("REPLCompletions.jl")
149
using .REPLCompletions
150

151
include("TerminalMenus/TerminalMenus.jl")
152
include("docview.jl")
153

154
@nospecialize # use only declared type signatures
155

156
answer_color(::AbstractREPL) = ""
×
157

158
const JULIA_PROMPT = "julia> "
159
const PKG_PROMPT = "pkg> "
160
const SHELL_PROMPT = "shell> "
161
const HELP_PROMPT = "help?> "
162

163
mutable struct REPLBackend
164
    "channel for AST"
165
    repl_channel::Channel{Any}
166
    "channel for results: (value, iserror)"
167
    response_channel::Channel{Any}
168
    "flag indicating the state of this backend"
169
    in_eval::Bool
170
    "transformation functions to apply before evaluating expressions"
171
    ast_transforms::Vector{Any}
172
    "current backend task"
173
    backend_task::Task
174

175
    REPLBackend(repl_channel, response_channel, in_eval, ast_transforms=copy(repl_ast_transforms)) =
50✔
176
        new(repl_channel, response_channel, in_eval, ast_transforms)
177
end
178
REPLBackend() = REPLBackend(Channel(1), Channel(1), false)
25✔
179

180
"""
181
    softscope(ex)
182

183
Return a modified version of the parsed expression `ex` that uses
184
the REPL's "soft" scoping rules for global syntax blocks.
185
"""
186
function softscope(@nospecialize ex)
370✔
187
    if ex isa Expr
370✔
188
        h = ex.head
237✔
189
        if h === :toplevel
237✔
190
            ex′ = Expr(h)
135✔
191
            map!(softscope, resize!(ex′.args, length(ex.args)), ex.args)
135✔
192
            return ex′
135✔
193
        elseif h in (:meta, :import, :using, :export, :module, :error, :incomplete, :thunk)
102✔
194
            return ex
2✔
195
        elseif h === :global && all(x->isa(x, Symbol), ex.args)
101✔
196
            return ex
1✔
197
        else
198
            return Expr(:block, Expr(:softscope, true), ex)
99✔
199
        end
200
    end
201
    return ex
133✔
202
end
203

204
# Temporary alias until Documenter updates
205
const softscope! = softscope
206

207
const repl_ast_transforms = Any[softscope] # defaults for new REPL backends
208

209
# Allows an external package to add hooks into the code loading.
210
# The hook should take a Vector{Symbol} of package names and
211
# return true if all packages could be installed, false if not
212
# to e.g. install packages on demand
213
const install_packages_hooks = Any[]
214

215
function eval_user_input(@nospecialize(ast), backend::REPLBackend, mod::Module)
106✔
216
    lasterr = nothing
×
217
    Base.sigatomic_begin()
106✔
218
    while true
110✔
219
        try
110✔
220
            Base.sigatomic_end()
110✔
221
            if lasterr !== nothing
110✔
222
                put!(backend.response_channel, Pair{Any, Bool}(lasterr, true))
4✔
223
            else
224
                backend.in_eval = true
106✔
225
                check_for_missing_packages_and_run_hooks(ast)
106✔
226
                for xf in backend.ast_transforms
106✔
227
                    ast = Base.invokelatest(xf, ast)
124✔
228
                end
124✔
229
                value = Core.eval(mod, ast)
106✔
230
                backend.in_eval = false
101✔
231
                setglobal!(Base.MainInclude, :ans, value)
101✔
232
                put!(backend.response_channel, Pair{Any, Bool}(value, false))
101✔
233
            end
234
            break
109✔
235
        catch err
236
            if lasterr !== nothing
4✔
237
                println("SYSTEM ERROR: Failed to report error to REPL frontend")
×
238
                println(err)
×
239
            end
240
            lasterr = current_exceptions()
4✔
241
        end
242
    end
4✔
243
    Base.sigatomic_end()
105✔
244
    nothing
×
245
end
246

247
function check_for_missing_packages_and_run_hooks(ast)
106✔
248
    isa(ast, Expr) || return
106✔
249
    mods = modules_to_be_loaded(ast)
106✔
250
    filter!(mod -> isnothing(Base.identify_package(String(mod))), mods) # keep missing modules
108✔
251
    if !isempty(mods)
106✔
252
        isempty(install_packages_hooks) && Base.require_stdlib(Base.PkgId(Base.UUID("44cfe95a-1eb2-52ea-b672-e2afdf69b78f"), "Pkg"))
×
253
        for f in install_packages_hooks
×
254
            Base.invokelatest(f, mods) && return
×
255
        end
×
256
    end
257
end
258

259
function modules_to_be_loaded(ast::Expr, mods::Vector{Symbol} = Symbol[])
182✔
260
    ast.head === :quote && return mods # don't search if it's not going to be run during this eval
288✔
261
    if ast.head === :using || ast.head === :import
306✔
262
        for arg in ast.args
18✔
263
            arg = arg::Expr
22✔
264
            arg1 = first(arg.args)
22✔
265
            if arg1 isa Symbol # i.e. `Foo`
22✔
266
                if arg1 != :. # don't include local imports
19✔
267
                    push!(mods, arg1)
18✔
268
                end
269
            else # i.e. `Foo: bar`
270
                push!(mods, first((arg1::Expr).args))
3✔
271
            end
272
        end
22✔
273
    end
274
    for arg in ast.args
160✔
275
        if isexpr(arg, (:block, :if, :using, :import))
342✔
276
            modules_to_be_loaded(arg, mods)
32✔
277
        end
278
    end
342✔
279
    filter!(mod -> !in(String(mod), ["Base", "Main", "Core"]), mods) # Exclude special non-package modules
212✔
280
    return unique(mods)
160✔
281
end
282

283
"""
284
    start_repl_backend(repl_channel::Channel, response_channel::Channel)
285

286
    Starts loop for REPL backend
287
    Returns a REPLBackend with backend_task assigned
288

289
    Deprecated since sync / async behavior cannot be selected
290
"""
291
function start_repl_backend(repl_channel::Channel{Any}, response_channel::Channel{Any}
×
292
                            ; get_module::Function = ()->Main)
293
    # Maintain legacy behavior of asynchronous backend
294
    backend = REPLBackend(repl_channel, response_channel, false)
×
295
    # Assignment will be made twice, but will be immediately available
296
    backend.backend_task = @async start_repl_backend(backend; get_module)
×
297
    return backend
×
298
end
299

300
"""
301
    start_repl_backend(backend::REPLBackend)
302

303
    Call directly to run backend loop on current Task.
304
    Use @async for run backend on new Task.
305

306
    Does not return backend until loop is finished.
307
"""
308
function start_repl_backend(backend::REPLBackend,  @nospecialize(consumer = x -> nothing); get_module::Function = ()->Main)
56✔
309
    backend.backend_task = Base.current_task()
25✔
310
    consumer(backend)
25✔
311
    repl_backend_loop(backend, get_module)
25✔
312
    return backend
24✔
313
end
314

315
function repl_backend_loop(backend::REPLBackend, get_module::Function)
25✔
316
    # include looks at this to determine the relative include path
317
    # nothing means cwd
318
    while true
×
319
        tls = task_local_storage()
130✔
320
        tls[:SOURCE_PATH] = nothing
130✔
321
        ast, show_value = take!(backend.repl_channel)
130✔
322
        if show_value == -1
130✔
323
            # exit flag
324
            break
24✔
325
        end
326
        eval_user_input(ast, backend, get_module())
106✔
327
    end
105✔
328
    return nothing
24✔
329
end
330

331
struct REPLDisplay{Repl<:AbstractREPL} <: AbstractDisplay
332
    repl::Repl
34✔
333
end
334

335
function display(d::REPLDisplay, mime::MIME"text/plain", x)
62✔
336
    x = Ref{Any}(x)
62✔
337
    with_repl_linfo(d.repl) do io
62✔
338
        io = IOContext(io, :limit => true, :module => active_module(d)::Module)
121✔
339
        if d.repl isa LineEditREPL
3✔
340
            mistate = d.repl.mistate
59✔
341
            mode = LineEdit.mode(mistate)
59✔
342
            if mode isa LineEdit.Prompt
59✔
343
                LineEdit.write_output_prefix(io, mode, get(io, :color, false)::Bool)
236✔
344
            end
345
        end
346
        get(io, :color, false)::Bool && write(io, answer_color(d.repl))
68✔
347
        if isdefined(d.repl, :options) && isdefined(d.repl.options, :iocontext)
3✔
348
            # this can override the :limit property set initially
349
            io = foldl(IOContext, d.repl.options.iocontext, init=io)
59✔
350
        end
351
        show(io, mime, x[])
62✔
352
        println(io)
61✔
353
    end
354
    return nothing
61✔
355
end
356
display(d::REPLDisplay, x) = display(d, MIME("text/plain"), x)
62✔
357

358
function print_response(repl::AbstractREPL, response, show_value::Bool, have_color::Bool)
102✔
359
    repl.waserror = response[2]
102✔
360
    with_repl_linfo(repl) do io
102✔
361
        io = IOContext(io, :module => active_module(repl)::Module)
201✔
362
        print_response(io, response, show_value, have_color, specialdisplay(repl))
102✔
363
    end
364
    return nothing
102✔
365
end
366

367
function repl_display_error(errio::IO, @nospecialize errval)
8✔
368
    # this will be set to true if types in the stacktrace are truncated
369
    limitflag = Ref(false)
8✔
370
    errio = IOContext(errio, :stacktrace_types_limited => limitflag)
8✔
371
    Base.invokelatest(Base.display_error, errio, errval)
8✔
372
    if limitflag[]
6✔
373
        print(errio, "Some type information was truncated. Use `show(err)` to see complete types.")
2✔
374
        println(errio)
2✔
375
    end
376
    return nothing
6✔
377
end
378

379
function print_response(errio::IO, response, show_value::Bool, have_color::Bool, specialdisplay::Union{AbstractDisplay,Nothing}=nothing)
103✔
380
    Base.sigatomic_begin()
103✔
381
    val, iserr = response
103✔
382
    while true
104✔
383
        try
104✔
384
            Base.sigatomic_end()
104✔
385
            if iserr
104✔
386
                val = Base.scrub_repl_backtrace(val)
7✔
387
                Base.istrivialerror(val) || setglobal!(Base.MainInclude, :err, val)
11✔
388
                repl_display_error(errio, val)
7✔
389
            else
390
                if val !== nothing && show_value
97✔
391
                    try
62✔
392
                        if specialdisplay === nothing
62✔
393
                            Base.invokelatest(display, val)
48✔
394
                        else
395
                            Base.invokelatest(display, specialdisplay, val)
61✔
396
                        end
397
                    catch
398
                        println(errio, "Error showing value of type ", typeof(val), ":")
1✔
399
                        rethrow()
1✔
400
                    end
401
                end
402
            end
403
            break
104✔
404
        catch ex
405
            if iserr
2✔
406
                println(errio) # an error during printing is likely to leave us mid-line
1✔
407
                println(errio, "SYSTEM (REPL): showing an error caused an error")
1✔
408
                try
1✔
409
                    excs = Base.scrub_repl_backtrace(current_exceptions())
1✔
410
                    setglobal!(Base.MainInclude, :err, excs)
1✔
411
                    repl_display_error(errio, excs)
2✔
412
                catch e
413
                    # at this point, only print the name of the type as a Symbol to
414
                    # minimize the possibility of further errors.
415
                    println(errio)
1✔
416
                    println(errio, "SYSTEM (REPL): caught exception of type ", typeof(e).name.name,
1✔
417
                            " while trying to handle a nested exception; giving up")
418
                end
419
                break
1✔
420
            end
421
            val = current_exceptions()
1✔
422
            iserr = true
1✔
423
        end
424
    end
1✔
425
    Base.sigatomic_end()
103✔
426
    nothing
×
427
end
428

429
# A reference to a backend that is not mutable
430
struct REPLBackendRef
431
    repl_channel::Channel{Any}
23✔
432
    response_channel::Channel{Any}
433
end
434
REPLBackendRef(backend::REPLBackend) = REPLBackendRef(backend.repl_channel, backend.response_channel)
23✔
435

436
function destroy(ref::REPLBackendRef, state::Task)
22✔
437
    if istaskfailed(state)
22✔
438
        close(ref.repl_channel, TaskFailedException(state))
×
439
        close(ref.response_channel, TaskFailedException(state))
×
440
    end
441
    close(ref.repl_channel)
22✔
442
    close(ref.response_channel)
22✔
443
end
444

445
"""
446
    run_repl(repl::AbstractREPL)
447
    run_repl(repl, consumer = backend->nothing; backend_on_current_task = true)
448

449
    Main function to start the REPL
450

451
    consumer is an optional function that takes a REPLBackend as an argument
452
"""
453
function run_repl(repl::AbstractREPL, @nospecialize(consumer = x -> nothing); backend_on_current_task::Bool = true, backend = REPLBackend())
68✔
454
    backend_ref = REPLBackendRef(backend)
23✔
455
    cleanup = @task try
45✔
456
            destroy(backend_ref, t)
22✔
457
        catch e
458
            Core.print(Core.stderr, "\nINTERNAL ERROR: ")
×
459
            Core.println(Core.stderr, e)
×
460
            Core.println(Core.stderr, catch_backtrace())
×
461
        end
462
    get_module = () -> active_module(repl)
125✔
463
    if backend_on_current_task
23✔
464
        t = @async run_frontend(repl, backend_ref)
46✔
465
        errormonitor(t)
23✔
466
        Base._wait2(t, cleanup)
23✔
467
        start_repl_backend(backend, consumer; get_module)
23✔
468
    else
469
        t = @async start_repl_backend(backend, consumer; get_module)
×
470
        errormonitor(t)
×
471
        Base._wait2(t, cleanup)
×
472
        run_frontend(repl, backend_ref)
×
473
    end
474
    return backend
22✔
475
end
476

477
## BasicREPL ##
478

479
mutable struct BasicREPL <: AbstractREPL
480
    terminal::TextTerminal
481
    waserror::Bool
482
    frontend_task::Task
483
    BasicREPL(t) = new(t, false)
3✔
484
end
485

486
outstream(r::BasicREPL) = r.terminal
6✔
487
hascolor(r::BasicREPL) = hascolor(r.terminal)
×
488

489
function run_frontend(repl::BasicREPL, backend::REPLBackendRef)
3✔
490
    repl.frontend_task = current_task()
3✔
491
    d = REPLDisplay(repl)
3✔
492
    dopushdisplay = !in(d,Base.Multimedia.displays)
6✔
493
    dopushdisplay && pushdisplay(d)
3✔
494
    hit_eof = false
3✔
495
    while true
6✔
496
        Base.reseteof(repl.terminal)
6✔
497
        write(repl.terminal, JULIA_PROMPT)
6✔
498
        line = ""
6✔
499
        ast = nothing
6✔
500
        interrupted = false
6✔
501
        while true
6✔
502
            try
6✔
503
                line *= readline(repl.terminal, keep=true)
6✔
504
            catch e
505
                if isa(e,InterruptException)
×
506
                    try # raise the debugger if present
×
507
                        ccall(:jl_raise_debugger, Int, ())
×
508
                    catch
×
509
                    end
510
                    line = ""
×
511
                    interrupted = true
×
512
                    break
×
513
                elseif isa(e,EOFError)
×
514
                    hit_eof = true
×
515
                    break
×
516
                else
517
                    rethrow()
×
518
                end
519
            end
520
            ast = Base.parse_input_line(line)
12✔
521
            (isa(ast,Expr) && ast.head === :incomplete) || break
12✔
522
        end
×
523
        if !isempty(line)
6✔
524
            response = eval_with_backend(ast, backend)
4✔
525
            print_response(repl, response, !ends_with_semicolon(line), false)
3✔
526
        end
527
        write(repl.terminal, '\n')
5✔
528
        ((!interrupted && isempty(line)) || hit_eof) && break
8✔
529
    end
3✔
530
    # terminate backend
531
    put!(backend.repl_channel, (nothing, -1))
2✔
532
    dopushdisplay && popdisplay(d)
2✔
533
    nothing
×
534
end
535

536
## LineEditREPL ##
537

538
mutable struct LineEditREPL <: AbstractREPL
539
    t::TextTerminal
540
    hascolor::Bool
541
    prompt_color::String
542
    input_color::String
543
    answer_color::String
544
    shell_color::String
545
    help_color::String
546
    history_file::Bool
547
    in_shell::Bool
548
    in_help::Bool
549
    envcolors::Bool
550
    waserror::Bool
551
    specialdisplay::Union{Nothing,AbstractDisplay}
552
    options::Options
553
    mistate::Union{MIState,Nothing}
554
    last_shown_line_infos::Vector{Tuple{String,Int}}
555
    interface::ModalInterface
556
    backendref::REPLBackendRef
557
    frontend_task::Task
558
    function LineEditREPL(t,hascolor,prompt_color,input_color,answer_color,shell_color,help_color,history_file,in_shell,in_help,envcolors)
×
559
        opts = Options()
27✔
560
        opts.hascolor = hascolor
27✔
561
        if !hascolor
27✔
562
            opts.beep_colors = [""]
×
563
        end
564
        new(t,hascolor,prompt_color,input_color,answer_color,shell_color,help_color,history_file,in_shell,
27✔
565
            in_help,envcolors,false,nothing, opts, nothing, Tuple{String,Int}[])
566
    end
567
end
568
outstream(r::LineEditREPL) = (t = r.t; t isa TTYTerminal ? t.out_stream : t)
336✔
569
specialdisplay(r::LineEditREPL) = r.specialdisplay
99✔
570
specialdisplay(r::AbstractREPL) = nothing
×
571
terminal(r::LineEditREPL) = r.t
154✔
572
hascolor(r::LineEditREPL) = r.hascolor
199✔
573

574
LineEditREPL(t::TextTerminal, hascolor::Bool, envcolors::Bool=false) =
54✔
575
    LineEditREPL(t, hascolor,
576
        hascolor ? Base.text_colors[:green] : "",
577
        hascolor ? Base.input_color() : "",
578
        hascolor ? Base.answer_color() : "",
579
        hascolor ? Base.text_colors[:red] : "",
580
        hascolor ? Base.text_colors[:yellow] : "",
581
        false, false, false, envcolors
582
    )
583

584
mutable struct REPLCompletionProvider <: CompletionProvider
585
    modifiers::LineEdit.Modifiers
24✔
586
end
587
REPLCompletionProvider() = REPLCompletionProvider(LineEdit.Modifiers())
24✔
588

589
mutable struct ShellCompletionProvider <: CompletionProvider end
24✔
590
struct LatexCompletions <: CompletionProvider end
591

592
function active_module() # this method is also called from Base
27,340✔
593
    isdefined(Base, :active_repl) || return Main
54,680✔
594
    return active_module(Base.active_repl::AbstractREPL)
×
595
end
596
active_module((; mistate)::LineEditREPL) = mistate === nothing ? Main : mistate.active_module
4,655✔
597
active_module(::AbstractREPL) = Main
10✔
598
active_module(d::REPLDisplay) = active_module(d.repl)
121✔
599

600
setmodifiers!(c::CompletionProvider, m::LineEdit.Modifiers) = nothing
×
601

602
setmodifiers!(c::REPLCompletionProvider, m::LineEdit.Modifiers) = c.modifiers = m
×
603

604
"""
605
    activate(mod::Module=Main)
606

607
Set `mod` as the default contextual module in the REPL,
608
both for evaluating expressions and printing them.
609
"""
610
function activate(mod::Module=Main)
×
611
    mistate = (Base.active_repl::LineEditREPL).mistate
×
612
    mistate === nothing && return nothing
×
613
    mistate.active_module = mod
×
614
    Base.load_InteractiveUtils(mod)
×
615
    return nothing
×
616
end
617

618
beforecursor(buf::IOBuffer) = String(buf.data[1:buf.ptr-1])
4,332✔
619

620
function complete_line(c::REPLCompletionProvider, s::PromptState, mod::Module)
1,712✔
621
    partial = beforecursor(s.input_buffer)
3,424✔
622
    full = LineEdit.input_string(s)
3,424✔
623
    ret, range, should_complete = completions(full, lastindex(partial), mod, c.modifiers.shift)
3,424✔
624
    c.modifiers = LineEdit.Modifiers()
1,712✔
625
    return unique!(map(completion_text, ret)), partial[range], should_complete
1,712✔
626
end
627

628
function complete_line(c::ShellCompletionProvider, s::PromptState)
422✔
629
    # First parse everything up to the current position
630
    partial = beforecursor(s.input_buffer)
844✔
631
    full = LineEdit.input_string(s)
844✔
632
    ret, range, should_complete = shell_completions(full, lastindex(partial))
844✔
633
    return unique!(map(completion_text, ret)), partial[range], should_complete
422✔
634
end
635

636
function complete_line(c::LatexCompletions, s)
×
637
    partial = beforecursor(LineEdit.buffer(s))
×
638
    full = LineEdit.input_string(s)::String
×
639
    ret, range, should_complete = bslash_completions(full, lastindex(partial))[2]
×
640
    return unique!(map(completion_text, ret)), partial[range], should_complete
×
641
end
642

643
with_repl_linfo(f, repl) = f(outstream(repl))
6✔
644
function with_repl_linfo(f, repl::LineEditREPL)
158✔
645
    linfos = Tuple{String,Int}[]
158✔
646
    io = IOContext(outstream(repl), :last_shown_line_infos => linfos)
158✔
647
    f(io)
158✔
648
    if !isempty(linfos)
157✔
649
        repl.last_shown_line_infos = linfos
5✔
650
    end
651
    nothing
×
652
end
653

654
mutable struct REPLHistoryProvider <: HistoryProvider
655
    history::Vector{String}
29✔
656
    file_path::String
657
    history_file::Union{Nothing,IO}
658
    start_idx::Int
659
    cur_idx::Int
660
    last_idx::Int
661
    last_buffer::IOBuffer
662
    last_mode::Union{Nothing,Prompt}
663
    mode_mapping::Dict{Symbol,Prompt}
664
    modes::Vector{Symbol}
665
end
666
REPLHistoryProvider(mode_mapping::Dict{Symbol}) =
29✔
667
    REPLHistoryProvider(String[], "", nothing, 0, 0, -1, IOBuffer(),
668
                        nothing, mode_mapping, UInt8[])
669

670
invalid_history_message(path::String) = """
×
671
Invalid history file ($path) format:
672
If you have a history file left over from an older version of Julia,
673
try renaming or deleting it.
674
Invalid character: """
675

676
munged_history_message(path::String) = """
×
677
Invalid history file ($path) format:
678
An editor may have converted tabs to spaces at line """
679

680
function hist_open_file(hp::REPLHistoryProvider)
×
681
    f = open(hp.file_path, read=true, write=true, create=true)
4✔
682
    hp.history_file = f
4✔
683
    seekend(f)
4✔
684
end
685

686
function hist_from_file(hp::REPLHistoryProvider, path::String)
8✔
687
    getline(lines, i) = i > length(lines) ? "" : lines[i]
276✔
688
    file_lines = readlines(path)
8✔
689
    countlines = 0
×
690
    while true
42✔
691
        # First parse the metadata that starts with '#' in particular the REPL mode
692
        countlines += 1
42✔
693
        line = getline(file_lines, countlines)
76✔
694
        mode = :julia
×
695
        isempty(line) && break
42✔
696
        line[1] != '#' &&
68✔
697
            error(invalid_history_message(path), repr(line[1]), " at line ", countlines)
698
        while !isempty(line)
102✔
699
            startswith(line, '#') || break
204✔
700
            if startswith(line, "# mode: ")
68✔
701
                mode = Symbol(SubString(line, 9))
68✔
702
            end
703
            countlines += 1
68✔
704
            line = getline(file_lines, countlines)
136✔
705
        end
68✔
706
        isempty(line) && break
34✔
707

708
        # Now parse the code for the current REPL mode
709
        line[1] == ' '  &&
68✔
710
            error(munged_history_message(path), countlines)
711
        line[1] != '\t' &&
68✔
712
            error(invalid_history_message(path), repr(line[1]), " at line ", countlines)
713
        lines = String[]
34✔
714
        while !isempty(line)
34✔
715
            push!(lines, chomp(SubString(line, 2)))
68✔
716
            next_line = getline(file_lines, countlines+1)
64✔
717
            isempty(next_line) && break
34✔
718
            first(next_line) == ' '  && error(munged_history_message(path), countlines)
60✔
719
            # A line not starting with a tab means we are done with code for this entry
720
            first(next_line) != '\t' && break
60✔
721
            countlines += 1
×
722
            line = getline(file_lines, countlines)
×
723
        end
×
724
        push!(hp.modes, mode)
34✔
725
        push!(hp.history, join(lines, '\n'))
34✔
726
    end
34✔
727
    hp.start_idx = length(hp.history)
8✔
728
    return hp
8✔
729
end
730

731
function add_history(hist::REPLHistoryProvider, s::PromptState)
117✔
732
    str = rstrip(String(take!(copy(s.input_buffer))))
219✔
733
    isempty(strip(str)) && return
117✔
734
    mode = mode_idx(hist, LineEdit.mode(s))
99✔
735
    !isempty(hist.history) &&
99✔
736
        isequal(mode, hist.modes[end]) && str == hist.history[end] && return
737
    push!(hist.modes, mode)
93✔
738
    push!(hist.history, str)
93✔
739
    hist.history_file === nothing && return
93✔
740
    entry = """
16✔
741
    # time: $(Libc.strftime("%Y-%m-%d %H:%M:%S %Z", time()))
742
    # mode: $mode
743
    $(replace(str, r"^"ms => "\t"))
744
    """
745
    # TODO: write-lock history file
746
    try
16✔
747
        seekend(hist.history_file)
16✔
748
    catch err
749
        (err isa SystemError) || rethrow()
×
750
        # File handle might get stale after a while, especially under network file systems
751
        # If this doesn't fix it (e.g. when file is deleted), we'll end up rethrowing anyway
752
        hist_open_file(hist)
×
753
    end
754
    print(hist.history_file, entry)
32✔
755
    flush(hist.history_file)
16✔
756
    nothing
×
757
end
758

759
function history_move(s::Union{LineEdit.MIState,LineEdit.PrefixSearchState}, hist::REPLHistoryProvider, idx::Int, save_idx::Int = hist.cur_idx)
100✔
760
    max_idx = length(hist.history) + 1
136✔
761
    @assert 1 <= hist.cur_idx <= max_idx
96✔
762
    (1 <= idx <= max_idx) || return :none
98✔
763
    idx != hist.cur_idx || return :none
94✔
764

765
    # save the current line
766
    if save_idx == max_idx
94✔
767
        hist.last_mode = LineEdit.mode(s)
46✔
768
        hist.last_buffer = copy(LineEdit.buffer(s))
46✔
769
    else
770
        hist.history[save_idx] = LineEdit.input_string(s)
84✔
771
        hist.modes[save_idx] = mode_idx(hist, LineEdit.mode(s))
63✔
772
    end
773

774
    # load the saved line
775
    if idx == max_idx
94✔
776
        last_buffer = hist.last_buffer
10✔
777
        LineEdit.transition(s, hist.last_mode) do
10✔
778
            LineEdit.replace_line(s, last_buffer)
10✔
779
        end
780
        hist.last_mode = nothing
10✔
781
        hist.last_buffer = IOBuffer()
10✔
782
    else
783
        if haskey(hist.mode_mapping, hist.modes[idx])
168✔
784
            LineEdit.transition(s, hist.mode_mapping[hist.modes[idx]]) do
68✔
785
                LineEdit.replace_line(s, hist.history[idx])
68✔
786
            end
787
        else
788
            return :skip
16✔
789
        end
790
    end
791
    hist.cur_idx = idx
78✔
792

793
    return :ok
78✔
794
end
795

796
# REPL History can also transitions modes
797
function LineEdit.accept_result_newmode(hist::REPLHistoryProvider)
27✔
798
    if 1 <= hist.cur_idx <= length(hist.modes)
27✔
799
        return hist.mode_mapping[hist.modes[hist.cur_idx]]
23✔
800
    end
801
    return nothing
4✔
802
end
803

804
function history_prev(s::LineEdit.MIState, hist::REPLHistoryProvider,
58✔
805
                      num::Int=1, save_idx::Int = hist.cur_idx)
806
    num <= 0 && return history_next(s, hist, -num, save_idx)
58✔
807
    hist.last_idx = -1
32✔
808
    m = history_move(s, hist, hist.cur_idx-num, save_idx)
32✔
809
    if m === :ok
32✔
810
        LineEdit.move_input_start(s)
24✔
811
        LineEdit.reset_key_repeats(s) do
24✔
812
            LineEdit.move_line_end(s)
24✔
813
        end
814
        return LineEdit.refresh_line(s)
24✔
815
    elseif m === :skip
8✔
816
        return history_prev(s, hist, num+1, save_idx)
8✔
817
    else
818
        return Terminals.beep(s)
×
819
    end
820
end
821

822
function history_next(s::LineEdit.MIState, hist::REPLHistoryProvider,
44✔
823
                      num::Int=1, save_idx::Int = hist.cur_idx)
824
    if num == 0
44✔
825
        Terminals.beep(s)
×
826
        return
×
827
    end
828
    num < 0 && return history_prev(s, hist, -num, save_idx)
26✔
829
    cur_idx = hist.cur_idx
24✔
830
    max_idx = length(hist.history) + 1
24✔
831
    if cur_idx == max_idx && 0 < hist.last_idx
24✔
832
        # issue #6312
833
        cur_idx = hist.last_idx
×
834
        hist.last_idx = -1
×
835
    end
836
    m = history_move(s, hist, cur_idx+num, save_idx)
24✔
837
    if m === :ok
24✔
838
        LineEdit.move_input_end(s)
16✔
839
        return LineEdit.refresh_line(s)
16✔
840
    elseif m === :skip
8✔
841
        return history_next(s, hist, num+1, save_idx)
6✔
842
    else
843
        return Terminals.beep(s)
2✔
844
    end
845
end
846

847
history_first(s::LineEdit.MIState, hist::REPLHistoryProvider) =
6✔
848
    history_prev(s, hist, hist.cur_idx - 1 -
849
                 (hist.cur_idx > hist.start_idx+1 ? hist.start_idx : 0))
850

851
history_last(s::LineEdit.MIState, hist::REPLHistoryProvider) =
4✔
852
    history_next(s, hist, length(hist.history) - hist.cur_idx + 1)
853

854
function history_move_prefix(s::LineEdit.PrefixSearchState,
62✔
855
                             hist::REPLHistoryProvider,
856
                             prefix::AbstractString,
857
                             backwards::Bool,
858
                             cur_idx::Int = hist.cur_idx)
859
    cur_response = String(take!(copy(LineEdit.buffer(s))))
101✔
860
    # when searching forward, start at last_idx
861
    if !backwards && hist.last_idx > 0
38✔
862
        cur_idx = hist.last_idx
1✔
863
    end
864
    hist.last_idx = -1
38✔
865
    max_idx = length(hist.history)+1
38✔
866
    idxs = backwards ? ((cur_idx-1):-1:1) : ((cur_idx+1):1:max_idx)
65✔
867
    for idx in idxs
38✔
868
        if (idx == max_idx) || (startswith(hist.history[idx], prefix) && (hist.history[idx] != cur_response || get(hist.mode_mapping, hist.modes[idx], nothing) !== LineEdit.mode(s)))
192✔
869
            m = history_move(s, hist, idx)
36✔
870
            if m === :ok
36✔
871
                if idx == max_idx
34✔
872
                    # on resuming the in-progress edit, leave the cursor where the user last had it
873
                elseif isempty(prefix)
30✔
874
                    # on empty prefix search, move cursor to the end
875
                    LineEdit.move_input_end(s)
14✔
876
                else
877
                    # otherwise, keep cursor at the prefix position as a visual cue
878
                    seek(LineEdit.buffer(s), sizeof(prefix))
16✔
879
                end
880
                LineEdit.refresh_line(s)
34✔
881
                return :ok
34✔
882
            elseif m === :skip
2✔
883
                return history_move_prefix(s,hist,prefix,backwards,idx)
2✔
884
            end
885
        end
886
    end
62✔
887
    Terminals.beep(s)
×
888
    nothing
×
889
end
890
history_next_prefix(s::LineEdit.PrefixSearchState, hist::REPLHistoryProvider, prefix::AbstractString) =
5✔
891
    history_move_prefix(s, hist, prefix, false)
892
history_prev_prefix(s::LineEdit.PrefixSearchState, hist::REPLHistoryProvider, prefix::AbstractString) =
31✔
893
    history_move_prefix(s, hist, prefix, true)
894

895
function history_search(hist::REPLHistoryProvider, query_buffer::IOBuffer, response_buffer::IOBuffer,
28✔
896
                        backwards::Bool=false, skip_current::Bool=false)
897

898
    qpos = position(query_buffer)
28✔
899
    qpos > 0 || return true
28✔
900
    searchdata = beforecursor(query_buffer)
56✔
901
    response_str = String(take!(copy(response_buffer)))
50✔
902

903
    # Alright, first try to see if the current match still works
904
    a = position(response_buffer) + 1 # position is zero-indexed
28✔
905
    # FIXME: I'm pretty sure this is broken since it uses an index
906
    # into the search data to index into the response string
907
    b = a + sizeof(searchdata)
28✔
908
    b = b ≤ ncodeunits(response_str) ? prevind(response_str, b) : b-1
48✔
909
    b = min(lastindex(response_str), b) # ensure that b is valid
50✔
910

911
    searchstart = backwards ? b : a
28✔
912
    if searchdata == response_str[a:b]
44✔
913
        if skip_current
10✔
914
            searchstart = backwards ? prevind(response_str, b) : nextind(response_str, a)
4✔
915
        else
916
            return true
6✔
917
        end
918
    end
919

920
    # Start searching
921
    # First the current response buffer
922
    if 1 <= searchstart <= lastindex(response_str)
36✔
923
        match = backwards ? findprev(searchdata, response_str, searchstart) :
14✔
924
                            findnext(searchdata, response_str, searchstart)
925
        if match !== nothing
14✔
926
            seek(response_buffer, first(match) - 1)
6✔
927
            return true
6✔
928
        end
929
    end
930

931
    # Now search all the other buffers
932
    idxs = backwards ? ((hist.cur_idx-1):-1:1) : ((hist.cur_idx+1):1:length(hist.history))
32✔
933
    for idx in idxs
16✔
934
        h = hist.history[idx]
40✔
935
        match = backwards ? findlast(searchdata, h) : findfirst(searchdata, h)
80✔
936
        if match !== nothing && h != response_str && haskey(hist.mode_mapping, hist.modes[idx])
54✔
937
            truncate(response_buffer, 0)
12✔
938
            write(response_buffer, h)
12✔
939
            seek(response_buffer, first(match) - 1)
12✔
940
            hist.cur_idx = idx
12✔
941
            return true
12✔
942
        end
943
    end
52✔
944

945
    return false
4✔
946
end
947

948
function history_reset_state(hist::REPLHistoryProvider)
7✔
949
    if hist.cur_idx != length(hist.history) + 1
271✔
950
        hist.last_idx = hist.cur_idx
130✔
951
        hist.cur_idx = length(hist.history) + 1
130✔
952
    end
953
    nothing
×
954
end
955
LineEdit.reset_state(hist::REPLHistoryProvider) = history_reset_state(hist)
240✔
956

957
function return_callback(s)
96✔
958
    ast = Base.parse_input_line(String(take!(copy(LineEdit.buffer(s)))), depwarn=false)
177✔
959
    return !(isa(ast, Expr) && ast.head === :incomplete)
96✔
960
end
961

962
find_hist_file() = get(ENV, "JULIA_HISTORY",
8✔
963
                       !isempty(DEPOT_PATH) ? joinpath(DEPOT_PATH[1], "logs", "repl_history.jl") :
964
                       error("DEPOT_PATH is empty and ENV[\"JULIA_HISTORY\"] not set."))
965

966
backend(r::AbstractREPL) = r.backendref
98✔
967

968
function eval_with_backend(ast, backend::REPLBackendRef)
102✔
969
    put!(backend.repl_channel, (ast, 1))
102✔
970
    return take!(backend.response_channel) # (val, iserr)
102✔
971
end
972

973
function respond(f, repl, main; pass_empty::Bool = false, suppress_on_semicolon::Bool = true)
72✔
974
    return function do_respond(s::MIState, buf, ok::Bool)
206✔
975
        if !ok
134✔
976
            return transition(s, :abort)
20✔
977
        end
978
        line = String(take!(buf)::Vector{UInt8})
213✔
979
        if !isempty(line) || pass_empty
129✔
980
            reset(repl)
99✔
981
            local response
×
982
            try
99✔
983
                ast = Base.invokelatest(f, line)
99✔
984
                response = eval_with_backend(ast, backend(repl))
98✔
985
            catch
986
                response = Pair{Any, Bool}(current_exceptions(), true)
1✔
987
            end
988
            hide_output = suppress_on_semicolon && ends_with_semicolon(line)
99✔
989
            print_response(repl, response, !hide_output, hascolor(repl))
99✔
990
        end
991
        prepare_next(repl)
114✔
992
        reset_state(s)
114✔
993
        return s.current_mode.sticky ? true : transition(s, main)
114✔
994
    end
995
end
996

997
function reset(repl::LineEditREPL)
99✔
998
    raw!(repl.t, false)
99✔
999
    hascolor(repl) && print(repl.t, Base.text_colors[:normal])
99✔
1000
    nothing
×
1001
end
1002

1003
function prepare_next(repl::LineEditREPL)
114✔
1004
    println(terminal(repl))
114✔
1005
end
1006

1007
function mode_keymap(julia_prompt::Prompt)
2✔
1008
    AnyDict(
26✔
1009
    '\b' => function (s::MIState,o...)
7✔
1010
        if isempty(s) || position(LineEdit.buffer(s)) == 0
7✔
1011
            buf = copy(LineEdit.buffer(s))
7✔
1012
            transition(s, julia_prompt) do
7✔
1013
                LineEdit.state(s, julia_prompt).input_buffer = buf
7✔
1014
            end
1015
        else
1016
            LineEdit.edit_backspace(s)
×
1017
        end
1018
    end,
1019
    "^C" => function (s::MIState,o...)
1020
        LineEdit.move_input_end(s)
1021
        LineEdit.refresh_line(s)
1022
        print(LineEdit.terminal(s), "^C\n\n")
1023
        transition(s, julia_prompt)
1024
        transition(s, :reset)
1025
        LineEdit.refresh_line(s)
1026
    end)
1027
end
1028

1029
repl_filename(repl, hp::REPLHistoryProvider) = "REPL[$(max(length(hp.history)-hp.start_idx, 1))]"
88✔
1030
repl_filename(repl, hp) = "REPL"
×
1031

1032
const JL_PROMPT_PASTE = Ref(true)
1033
enable_promptpaste(v::Bool) = JL_PROMPT_PASTE[] = v
×
1034

1035
function contextual_prompt(repl::LineEditREPL, prompt::Union{String,Function})
×
1036
    function ()
2,123✔
1037
        mod = active_module(repl)
4,143✔
1038
        prefix = mod == Main ? "" : string('(', mod, ") ")
2,107✔
1039
        pr = prompt isa String ? prompt : prompt()
2,075✔
1040
        prefix * pr
2,075✔
1041
    end
1042
end
1043

1044
setup_interface(
68✔
1045
    repl::LineEditREPL;
1046
    # those keyword arguments may be deprecated eventually in favor of the Options mechanism
1047
    hascolor::Bool = repl.options.hascolor,
1048
    extra_repl_keymap::Any = repl.options.extra_keymap
1049
) = setup_interface(repl, hascolor, extra_repl_keymap)
1050

1051
# This non keyword method can be precompiled which is important
1052
function setup_interface(
24✔
1053
    repl::LineEditREPL,
1054
    hascolor::Bool,
1055
    extra_repl_keymap::Any, # Union{Dict,Vector{<:Dict}},
1056
)
1057
    # The precompile statement emitter has problem outputting valid syntax for the
1058
    # type of `Union{Dict,Vector{<:Dict}}` (see #28808).
1059
    # This function is however important to precompile for REPL startup time, therefore,
1060
    # make the type Any and just assert that we have the correct type below.
1061
    @assert extra_repl_keymap isa Union{Dict,Vector{<:Dict}}
24✔
1062

1063
    ###
1064
    #
1065
    # This function returns the main interface that describes the REPL
1066
    # functionality, it is called internally by functions that setup a
1067
    # Terminal-based REPL frontend.
1068
    #
1069
    # See run_frontend(repl::LineEditREPL, backend::REPLBackendRef)
1070
    # for usage
1071
    #
1072
    ###
1073

1074
    ###
1075
    # We setup the interface in two stages.
1076
    # First, we set up all components (prompt,rsearch,shell,help)
1077
    # Second, we create keymaps with appropriate transitions between them
1078
    #   and assign them to the components
1079
    #
1080
    ###
1081

1082
    ############################### Stage I ################################
1083

1084
    # This will provide completions for REPL and help mode
1085
    replc = REPLCompletionProvider()
24✔
1086

1087
    # Set up the main Julia prompt
1088
    julia_prompt = Prompt(contextual_prompt(repl, JULIA_PROMPT);
48✔
1089
        # Copy colors from the prompt object
1090
        prompt_prefix = hascolor ? repl.prompt_color : "",
1091
        prompt_suffix = hascolor ?
1092
            (repl.envcolors ? Base.input_color : repl.input_color) : "",
1093
        repl = repl,
1094
        complete = replc,
1095
        on_enter = return_callback)
1096

1097
    # Setup help mode
1098
    help_mode = Prompt(contextual_prompt(repl, "help?> "),
48✔
1099
        prompt_prefix = hascolor ? repl.help_color : "",
1100
        prompt_suffix = hascolor ?
1101
            (repl.envcolors ? Base.input_color : repl.input_color) : "",
1102
        repl = repl,
1103
        complete = replc,
1104
        # When we're done transform the entered line into a call to helpmode function
1105
        on_done = respond(line::String->helpmode(outstream(repl), line, repl.mistate.active_module),
2✔
1106
                          repl, julia_prompt, pass_empty=true, suppress_on_semicolon=false))
1107

1108

1109
    # Set up shell mode
1110
    shell_mode = Prompt(SHELL_PROMPT;
48✔
1111
        prompt_prefix = hascolor ? repl.shell_color : "",
1112
        prompt_suffix = hascolor ?
1113
            (repl.envcolors ? Base.input_color : repl.input_color) : "",
1114
        repl = repl,
1115
        complete = ShellCompletionProvider(),
1116
        # Transform "foo bar baz" into `foo bar baz` (shell quoting)
1117
        # and pass into Base.repl_cmd for processing (handles `ls` and `cd`
1118
        # special)
1119
        on_done = respond(repl, julia_prompt) do line
1120
            Expr(:call, :(Base.repl_cmd),
9✔
1121
                :(Base.cmd_gen($(Base.shell_parse(line::String)[1]))),
1122
                outstream(repl))
1123
        end,
1124
        sticky = true)
1125

1126

1127
    ################################# Stage II #############################
1128

1129
    # Setup history
1130
    # We will have a unified history for all REPL modes
1131
    hp = REPLHistoryProvider(Dict{Symbol,Prompt}(:julia => julia_prompt,
24✔
1132
                                                 :shell => shell_mode,
1133
                                                 :help  => help_mode))
1134
    if repl.history_file
24✔
1135
        try
4✔
1136
            hist_path = find_hist_file()
8✔
1137
            mkpath(dirname(hist_path))
4✔
1138
            hp.file_path = hist_path
4✔
1139
            hist_open_file(hp)
4✔
1140
            finalizer(replc) do replc
4✔
1141
                close(hp.history_file)
4✔
1142
            end
1143
            hist_from_file(hp, hist_path)
4✔
1144
        catch
1145
            # use REPL.hascolor to avoid using the local variable with the same name
1146
            print_response(repl, Pair{Any, Bool}(current_exceptions(), true), true, REPL.hascolor(repl))
×
1147
            println(outstream(repl))
×
1148
            @info "Disabling history file for this session"
×
1149
            repl.history_file = false
×
1150
        end
1151
    end
1152
    history_reset_state(hp)
24✔
1153
    julia_prompt.hist = hp
24✔
1154
    shell_mode.hist = hp
24✔
1155
    help_mode.hist = hp
24✔
1156

1157
    julia_prompt.on_done = respond(x->Base.parse_input_line(x,filename=repl_filename(repl,hp)), repl, julia_prompt)
112✔
1158

1159

1160
    search_prompt, skeymap = LineEdit.setup_search_keymap(hp)
24✔
1161
    search_prompt.complete = LatexCompletions()
24✔
1162

1163
    shell_prompt_len = length(SHELL_PROMPT)
×
1164
    help_prompt_len = length(HELP_PROMPT)
×
1165
    jl_prompt_regex = r"^In \[[0-9]+\]: |^(?:\(.+\) )?julia> "
×
1166
    pkg_prompt_regex = r"^(?:\(.+\) )?pkg> "
×
1167

1168
    # Canonicalize user keymap input
1169
    if isa(extra_repl_keymap, Dict)
24✔
1170
        extra_repl_keymap = AnyDict[extra_repl_keymap]
×
1171
    end
1172

1173
    repl_keymap = AnyDict(
24✔
1174
        ';' => function (s::MIState,o...)
53✔
1175
            if isempty(s) || position(LineEdit.buffer(s)) == 0
99✔
1176
                buf = copy(LineEdit.buffer(s))
7✔
1177
                transition(s, shell_mode) do
7✔
1178
                    LineEdit.state(s, shell_mode).input_buffer = buf
7✔
1179
                end
1180
            else
1181
                edit_insert(s, ';')
46✔
1182
            end
1183
        end,
1184
        '?' => function (s::MIState,o...)
1✔
1185
            if isempty(s) || position(LineEdit.buffer(s)) == 0
1✔
1186
                buf = copy(LineEdit.buffer(s))
1✔
1187
                transition(s, help_mode) do
1✔
1188
                    LineEdit.state(s, help_mode).input_buffer = buf
1✔
1189
                end
1190
            else
1191
                edit_insert(s, '?')
×
1192
            end
1193
        end,
1194
        ']' => function (s::MIState,o...)
3✔
1195
            if isempty(s) || position(LineEdit.buffer(s)) == 0
6✔
1196
                pkgid = Base.PkgId(Base.UUID("44cfe95a-1eb2-52ea-b672-e2afdf69b78f"), "Pkg")
×
1197
                REPLExt = Base.require_stdlib(pkgid, "REPLExt")
×
1198
                pkg_mode = nothing
×
1199
                if REPLExt isa Module && isdefined(REPLExt, :PkgCompletionProvider)
×
1200
                    for mode in repl.interface.modes
×
1201
                        if mode isa LineEdit.Prompt && mode.complete isa REPLExt.PkgCompletionProvider
×
1202
                            pkg_mode = mode
×
1203
                            break
×
1204
                        end
1205
                    end
×
1206
                end
1207
                # TODO: Cache the `pkg_mode`?
1208
                if pkg_mode !== nothing
×
1209
                    buf = copy(LineEdit.buffer(s))
×
1210
                    transition(s, pkg_mode) do
×
1211
                        LineEdit.state(s, pkg_mode).input_buffer = buf
1212
                    end
1213
                    return
×
1214
                end
1215
            end
1216
            edit_insert(s, ']')
3✔
1217
        end,
1218

1219
        # Bracketed Paste Mode
1220
        "\e[200~" => (s::MIState,o...)->begin
8✔
1221
            input = LineEdit.bracketed_paste(s) # read directly from s until reaching the end-bracketed-paste marker
8✔
1222
            sbuffer = LineEdit.buffer(s)
8✔
1223
            curspos = position(sbuffer)
8✔
1224
            seek(sbuffer, 0)
8✔
1225
            shouldeval = (bytesavailable(sbuffer) == curspos && !occursin(UInt8('\n'), sbuffer))
8✔
1226
            seek(sbuffer, curspos)
8✔
1227
            if curspos == 0
8✔
1228
                # if pasting at the beginning, strip leading whitespace
1229
                input = lstrip(input)
7✔
1230
            end
1231
            if !shouldeval
8✔
1232
                # when pasting in the middle of input, just paste in place
1233
                # don't try to execute all the WIP, since that's rather confusing
1234
                # and is often ill-defined how it should behave
1235
                edit_insert(s, input)
×
1236
                return
×
1237
            end
1238
            LineEdit.push_undo(s)
8✔
1239
            edit_insert(sbuffer, input)
8✔
1240
            input = String(take!(sbuffer))
16✔
1241
            oldpos = firstindex(input)
×
1242
            firstline = true
×
1243
            isprompt_paste = false
×
1244
            curr_prompt_len = 0
×
1245
            pasting_help = false
×
1246

1247
            while oldpos <= lastindex(input) # loop until all lines have been executed
50✔
1248
                if JL_PROMPT_PASTE[]
24✔
1249
                    # Check if the next statement starts with a prompt i.e. "julia> ", in that case
1250
                    # skip it. But first skip whitespace unless pasting in a docstring which may have
1251
                    # indented prompt examples that we don't want to execute
1252
                    while input[oldpos] in (pasting_help ? ('\n') : ('\n', ' ', '\t'))
66✔
1253
                        oldpos = nextind(input, oldpos)
28✔
1254
                        oldpos >= sizeof(input) && return
14✔
1255
                    end
14✔
1256
                    substr = SubString(input, oldpos)
48✔
1257
                    # Check if input line starts with "julia> ", remove it if we are in prompt paste mode
1258
                    if (firstline || isprompt_paste) && startswith(substr, jl_prompt_regex)
24✔
1259
                        detected_jl_prompt = match(jl_prompt_regex, substr).match
11✔
1260
                        isprompt_paste = true
×
1261
                        curr_prompt_len = sizeof(detected_jl_prompt)
11✔
1262
                        oldpos += curr_prompt_len
11✔
1263
                        transition(s, julia_prompt)
11✔
1264
                        pasting_help = false
×
1265
                    # Check if input line starts with "pkg> " or "(...) pkg> ", remove it if we are in prompt paste mode and switch mode
1266
                    elseif (firstline || isprompt_paste) && startswith(substr, pkg_prompt_regex)
13✔
1267
                        detected_pkg_prompt = match(pkg_prompt_regex, substr).match
×
1268
                        isprompt_paste = true
×
1269
                        curr_prompt_len = sizeof(detected_pkg_prompt)
×
1270
                        oldpos += curr_prompt_len
×
1271
                        Base.active_repl.interface.modes[1].keymap_dict[']'](s, o...)
×
1272
                        pasting_help = false
×
1273
                    # Check if input line starts with "shell> ", remove it if we are in prompt paste mode and switch mode
1274
                    elseif (firstline || isprompt_paste) && startswith(substr, SHELL_PROMPT)
13✔
1275
                        isprompt_paste = true
×
1276
                        oldpos += shell_prompt_len
2✔
1277
                        curr_prompt_len = shell_prompt_len
2✔
1278
                        transition(s, shell_mode)
2✔
1279
                        pasting_help = false
×
1280
                    # Check if input line starts with "help?> ", remove it if we are in prompt paste mode and switch mode
1281
                    elseif (firstline || isprompt_paste) && startswith(substr, HELP_PROMPT)
11✔
1282
                        isprompt_paste = true
×
1283
                        oldpos += help_prompt_len
1✔
1284
                        curr_prompt_len = help_prompt_len
1✔
1285
                        transition(s, help_mode)
1✔
1286
                        pasting_help = true
×
1287
                    # If we are prompt pasting and current statement does not begin with a mode prefix, skip to next line
1288
                    elseif isprompt_paste
10✔
1289
                        while input[oldpos] != '\n'
316✔
1290
                            oldpos = nextind(input, oldpos)
302✔
1291
                            oldpos >= sizeof(input) && return
151✔
1292
                        end
149✔
1293
                        continue
7✔
1294
                    end
1295
                end
1296
                dump_tail = false
15✔
1297
                nl_pos = findfirst('\n', input[oldpos:end])
30✔
1298
                if s.current_mode == julia_prompt
15✔
1299
                    ast, pos = Meta.parse(input, oldpos, raise=false, depwarn=false)
12✔
1300
                    if (isa(ast, Expr) && (ast.head === :error || ast.head === :incomplete)) ||
22✔
1301
                            (pos > ncodeunits(input) && !endswith(input, '\n'))
1302
                        # remaining text is incomplete (an error, or parser ran to the end but didn't stop with a newline):
1303
                        # Insert all the remaining text as one line (might be empty)
1304
                        dump_tail = true
×
1305
                    end
1306
                elseif isnothing(nl_pos) # no newline at end, so just dump the tail into the prompt and don't execute
6✔
1307
                    dump_tail = true
×
1308
                elseif s.current_mode == shell_mode # handle multiline shell commands
3✔
1309
                    lines = split(input[oldpos:end], '\n')
4✔
1310
                    pos = oldpos + sizeof(lines[1]) + 1
2✔
1311
                    if length(lines) > 1
2✔
1312
                        for line in lines[2:end]
2✔
1313
                            # to be recognized as a multiline shell command, the lines must be indented to the
1314
                            # same prompt position
1315
                            if !startswith(line, ' '^curr_prompt_len)
3✔
1316
                                break
2✔
1317
                            end
1318
                            pos += sizeof(line) + 1
1✔
1319
                        end
1✔
1320
                    end
1321
                else
1322
                    pos = oldpos + nl_pos
1✔
1323
                end
1324
                if dump_tail
15✔
1325
                    tail = input[oldpos:end]
10✔
1326
                    if !firstline
5✔
1327
                        # strip leading whitespace, but only if it was the result of executing something
1328
                        # (avoids modifying the user's current leading wip line)
1329
                        tail = lstrip(tail)
1✔
1330
                    end
1331
                    if isprompt_paste # remove indentation spaces corresponding to the prompt
5✔
1332
                        tail = replace(tail, r"^"m * ' '^curr_prompt_len => "")
7✔
1333
                    end
1334
                    LineEdit.replace_line(s, tail, true)
10✔
1335
                    LineEdit.refresh_line(s)
5✔
1336
                    break
5✔
1337
                end
1338
                # get the line and strip leading and trailing whitespace
1339
                line = strip(input[oldpos:prevind(input, pos)])
20✔
1340
                if !isempty(line)
10✔
1341
                    if isprompt_paste # remove indentation spaces corresponding to the prompt
10✔
1342
                        line = replace(line, r"^"m * ' '^curr_prompt_len => "")
10✔
1343
                    end
1344
                    # put the line on the screen and history
1345
                    LineEdit.replace_line(s, line)
20✔
1346
                    LineEdit.commit_line(s)
10✔
1347
                    # execute the statement
1348
                    terminal = LineEdit.terminal(s) # This is slightly ugly but ok for now
10✔
1349
                    raw!(terminal, false) && disable_bracketed_paste(terminal)
10✔
1350
                    LineEdit.mode(s).on_done(s, LineEdit.buffer(s), true)
10✔
1351
                    raw!(terminal, true) && enable_bracketed_paste(terminal)
10✔
1352
                    LineEdit.push_undo(s) # when the last line is incomplete
10✔
1353
                end
1354
                oldpos = pos
10✔
1355
                firstline = false
×
1356
            end
17✔
1357
        end,
1358

1359
        # Open the editor at the location of a stackframe or method
1360
        # This is accessing a contextual variable that gets set in
1361
        # the show_backtrace and show_method_table functions.
1362
        "^Q" => (s::MIState, o...) -> begin
1363
            linfos = repl.last_shown_line_infos
1364
            str = String(take!(LineEdit.buffer(s)))
1365
            n = tryparse(Int, str)
1366
            n === nothing && @goto writeback
1367
            if n <= 0 || n > length(linfos) || startswith(linfos[n][1], "REPL[")
1368
                @goto writeback
1369
            end
1370
            try
1371
                InteractiveUtils.edit(Base.fixup_stdlib_path(linfos[n][1]), linfos[n][2])
1372
            catch ex
1373
                ex isa ProcessFailedException || ex isa Base.IOError || ex isa SystemError || rethrow()
1374
                @info "edit failed" _exception=ex
1375
            end
1376
            LineEdit.refresh_line(s)
1377
            return
1378
            @label writeback
1379
            write(LineEdit.buffer(s), str)
1380
            return
1381
        end,
1382
    )
1383

1384
    prefix_prompt, prefix_keymap = LineEdit.setup_prefix_keymap(hp, julia_prompt)
24✔
1385

1386
    a = Dict{Any,Any}[skeymap, repl_keymap, prefix_keymap, LineEdit.history_keymap, LineEdit.default_keymap, LineEdit.escape_defaults]
144✔
1387
    prepend!(a, extra_repl_keymap)
24✔
1388

1389
    julia_prompt.keymap_dict = LineEdit.keymap(a)
24✔
1390

1391
    mk = mode_keymap(julia_prompt)
24✔
1392

1393
    b = Dict{Any,Any}[skeymap, mk, prefix_keymap, LineEdit.history_keymap, LineEdit.default_keymap, LineEdit.escape_defaults]
144✔
1394
    prepend!(b, extra_repl_keymap)
24✔
1395

1396
    shell_mode.keymap_dict = help_mode.keymap_dict = LineEdit.keymap(b)
24✔
1397

1398
    allprompts = LineEdit.TextInterface[julia_prompt, shell_mode, help_mode, search_prompt, prefix_prompt]
24✔
1399
    return ModalInterface(allprompts)
24✔
1400
end
1401

1402
function run_frontend(repl::LineEditREPL, backend::REPLBackendRef)
20✔
1403
    repl.frontend_task = current_task()
20✔
1404
    d = REPLDisplay(repl)
20✔
1405
    dopushdisplay = repl.specialdisplay === nothing && !in(d,Base.Multimedia.displays)
34✔
1406
    dopushdisplay && pushdisplay(d)
20✔
1407
    if !isdefined(repl,:interface)
20✔
1408
        interface = repl.interface = setup_interface(repl)
24✔
1409
    else
1410
        interface = repl.interface
8✔
1411
    end
1412
    repl.backendref = backend
20✔
1413
    repl.mistate = LineEdit.init_state(terminal(repl), interface)
20✔
1414
    run_interface(terminal(repl), interface, repl.mistate)
20✔
1415
    # Terminate Backend
1416
    put!(backend.repl_channel, (nothing, -1))
20✔
1417
    dopushdisplay && popdisplay(d)
20✔
1418
    nothing
×
1419
end
1420

1421
## StreamREPL ##
1422

1423
mutable struct StreamREPL <: AbstractREPL
1424
    stream::IO
1425
    prompt_color::String
1426
    input_color::String
1427
    answer_color::String
1428
    waserror::Bool
1429
    frontend_task::Task
1430
    StreamREPL(stream,pc,ic,ac) = new(stream,pc,ic,ac,false)
×
1431
end
1432
StreamREPL(stream::IO) = StreamREPL(stream, Base.text_colors[:green], Base.input_color(), Base.answer_color())
×
1433
run_repl(stream::IO) = run_repl(StreamREPL(stream))
×
1434

1435
outstream(s::StreamREPL) = s.stream
×
1436
hascolor(s::StreamREPL) = get(s.stream, :color, false)::Bool
×
1437

1438
answer_color(r::LineEditREPL) = r.envcolors ? Base.answer_color() : r.answer_color
×
1439
answer_color(r::StreamREPL) = r.answer_color
×
1440
input_color(r::LineEditREPL) = r.envcolors ? Base.input_color() : r.input_color
×
1441
input_color(r::StreamREPL) = r.input_color
×
1442

1443
let matchend = Dict("\"" => r"\"", "\"\"\"" => r"\"\"\"", "'" => r"'",
1444
    "`" => r"`", "```" => r"```", "#" => r"$"m, "#=" => r"=#|#=")
1445
    global _rm_strings_and_comments
1446
    function _rm_strings_and_comments(code::Union{String,SubString{String}})
124✔
1447
        buf = IOBuffer(sizehint = sizeof(code))
248✔
1448
        pos = 1
×
1449
        while true
164✔
1450
            i = findnext(r"\"(?!\"\")|\"\"\"|'|`(?!``)|```|#(?!=)|#=", code, pos)
328✔
1451
            isnothing(i) && break
210✔
1452
            match = SubString(code, i)
46✔
1453
            j = findnext(matchend[match]::Regex, code, nextind(code, last(i)))
92✔
1454
            if match == "#=" # possibly nested
46✔
1455
                nested = 1
×
1456
                while j !== nothing
11✔
1457
                    nested += SubString(code, j) == "#=" ? +1 : -1
10✔
1458
                    iszero(nested) && break
10✔
1459
                    j = findnext(r"=#|#=", code, nextind(code, last(j)))
12✔
1460
                end
6✔
1461
            elseif match[1] != '#' # quote match: check non-escaped
82✔
1462
                while j !== nothing
38✔
1463
                    notbackslash = findprev(!=('\\'), code, prevind(code, first(j)))::Int
66✔
1464
                    isodd(first(j) - notbackslash) && break # not escaped
33✔
1465
                    j = findnext(matchend[match]::Regex, code, nextind(code, first(j)))
14✔
1466
                end
7✔
1467
            end
1468
            isnothing(j) && break
86✔
1469
            if match[1] == '#'
80✔
1470
                print(buf, SubString(code, pos, prevind(code, first(i))))
14✔
1471
            else
1472
                print(buf, SubString(code, pos, last(i)), ' ', SubString(code, j))
26✔
1473
            end
1474
            pos = nextind(code, last(j))
40✔
1475
        end
40✔
1476
        print(buf, SubString(code, pos, lastindex(code)))
124✔
1477
        return String(take!(buf))
124✔
1478
    end
1479
end
1480

1481
# heuristic function to decide if the presence of a semicolon
1482
# at the end of the expression was intended for suppressing output
1483
ends_with_semicolon(code::AbstractString) = ends_with_semicolon(String(code))
×
1484
ends_with_semicolon(code::Union{String,SubString{String}}) =
124✔
1485
    contains(_rm_strings_and_comments(code), r";\s*$")
1486

1487
function banner(io::IO = stdout; short = false)
4✔
1488
    if Base.GIT_VERSION_INFO.tagged_commit
×
1489
        commit_string = Base.TAGGED_RELEASE_BANNER
×
1490
    elseif isempty(Base.GIT_VERSION_INFO.commit)
×
1491
        commit_string = ""
×
1492
    else
1493
        days = Int(floor((ccall(:jl_clock_now, Float64, ()) - Base.GIT_VERSION_INFO.fork_master_timestamp) / (60 * 60 * 24)))
2✔
1494
        days = max(0, days)
2✔
1495
        unit = days == 1 ? "day" : "days"
2✔
1496
        distance = Base.GIT_VERSION_INFO.fork_master_distance
×
1497
        commit = Base.GIT_VERSION_INFO.commit_short
2✔
1498

1499
        if distance == 0
×
1500
            commit_string = "Commit $(commit) ($(days) $(unit) old master)"
2✔
1501
        else
1502
            branch = Base.GIT_VERSION_INFO.branch
×
1503
            commit_string = "$(branch)/$(commit) (fork: $(distance) commits, $(days) $(unit))"
×
1504
        end
1505
    end
1506

1507
    commit_date = isempty(Base.GIT_VERSION_INFO.date_string) ? "" : " ($(split(Base.GIT_VERSION_INFO.date_string)[1]))"
×
1508

1509
    if get(io, :color, false)::Bool
2✔
1510
        c = Base.text_colors
×
1511
        tx = c[:normal] # text
×
1512
        jl = c[:normal] # julia
×
1513
        d1 = c[:bold] * c[:blue]    # first dot
×
1514
        d2 = c[:bold] * c[:red]     # second dot
×
1515
        d3 = c[:bold] * c[:green]   # third dot
×
1516
        d4 = c[:bold] * c[:magenta] # fourth dot
×
1517

1518
        if short
×
1519
            print(io,"""
×
1520
              $(d3)o$(tx)  | Version $(VERSION)$(commit_date)
1521
             $(d2)o$(tx) $(d4)o$(tx) | $(commit_string)
1522
            """)
1523
        else
1524
            print(io,"""               $(d3)_$(tx)
×
1525
               $(d1)_$(tx)       $(jl)_$(tx) $(d2)_$(d3)(_)$(d4)_$(tx)     |  Documentation: https://docs.julialang.org
1526
              $(d1)(_)$(jl)     | $(d2)(_)$(tx) $(d4)(_)$(tx)    |
1527
               $(jl)_ _   _| |_  __ _$(tx)   |  Type \"?\" for help, \"]?\" for Pkg help.
1528
              $(jl)| | | | | | |/ _` |$(tx)  |
1529
              $(jl)| | |_| | | | (_| |$(tx)  |  Version $(VERSION)$(commit_date)
1530
             $(jl)_/ |\\__'_|_|_|\\__'_|$(tx)  |  $(commit_string)
1531
            $(jl)|__/$(tx)                   |
1532

1533
            """)
1534
        end
1535
    else
1536
        if short
2✔
1537
            print(io,"""
1✔
1538
              o  |  Version $(VERSION)$(commit_date)
1539
             o o |  $(commit_string)
1540
            """)
1541
        else
1542
            print(io,"""
1✔
1543
                           _
1544
               _       _ _(_)_     |  Documentation: https://docs.julialang.org
1545
              (_)     | (_) (_)    |
1546
               _ _   _| |_  __ _   |  Type \"?\" for help, \"]?\" for Pkg help.
1547
              | | | | | | |/ _` |  |
1548
              | | |_| | | | (_| |  |  Version $(VERSION)$(commit_date)
1549
             _/ |\\__'_|_|_|\\__'_|  |  $(commit_string)
1550
            |__/                   |
1551

1552
            """)
1553
        end
1554
    end
1555
end
1556

1557
function run_frontend(repl::StreamREPL, backend::REPLBackendRef)
×
1558
    repl.frontend_task = current_task()
×
1559
    have_color = hascolor(repl)
×
1560
    banner(repl.stream)
×
1561
    d = REPLDisplay(repl)
×
1562
    dopushdisplay = !in(d,Base.Multimedia.displays)
×
1563
    dopushdisplay && pushdisplay(d)
×
1564
    while !eof(repl.stream)::Bool
×
1565
        if have_color
×
1566
            print(repl.stream,repl.prompt_color)
×
1567
        end
1568
        print(repl.stream, "julia> ")
×
1569
        if have_color
×
1570
            print(repl.stream, input_color(repl))
×
1571
        end
1572
        line = readline(repl.stream, keep=true)
×
1573
        if !isempty(line)
×
1574
            ast = Base.parse_input_line(line)
×
1575
            if have_color
×
1576
                print(repl.stream, Base.color_normal)
×
1577
            end
1578
            response = eval_with_backend(ast, backend)
×
1579
            print_response(repl, response, !ends_with_semicolon(line), have_color)
×
1580
        end
1581
    end
×
1582
    # Terminate Backend
1583
    put!(backend.repl_channel, (nothing, -1))
×
1584
    dopushdisplay && popdisplay(d)
×
1585
    nothing
×
1586
end
1587

1588
module Numbered
1589

1590
using ..REPL
1591

1592
__current_ast_transforms() = isdefined(Base, :active_repl_backend) ? Base.active_repl_backend.ast_transforms : REPL.repl_ast_transforms
×
1593

1594
function repl_eval_counter(hp)
781✔
1595
    return length(hp.history) - hp.start_idx
781✔
1596
end
1597

1598
function out_transform(@nospecialize(x), n::Ref{Int})
16✔
1599
    return Expr(:toplevel, get_usings!([], x)..., quote
16✔
1600
        let __temp_val_a72df459 = $x
1601
            $capture_result($n, __temp_val_a72df459)
1602
            __temp_val_a72df459
1603
        end
1604
    end)
1605
end
1606

1607
function get_usings!(usings, ex)
25✔
1608
    ex isa Expr || return usings
25✔
1609
    # get all `using` and `import` statements which are at the top level
1610
    for (i, arg) in enumerate(ex.args)
25✔
1611
        if Base.isexpr(arg, :toplevel)
48✔
1612
            get_usings!(usings, arg)
9✔
1613
        elseif Base.isexpr(arg, [:using, :import])
64✔
1614
            push!(usings, popat!(ex.args, i))
2✔
1615
        end
1616
    end
71✔
1617
    return usings
25✔
1618
end
1619

1620
function capture_result(n::Ref{Int}, @nospecialize(x))
16✔
1621
    n = n[]
16✔
1622
    mod = Base.MainInclude
16✔
1623
    if !isdefined(mod, :Out)
16✔
1624
        @eval mod global Out
1✔
1625
        @eval mod export Out
1✔
1626
        setglobal!(mod, :Out, Dict{Int, Any}())
1✔
1627
    end
1628
    if x !== getglobal(mod, :Out) && x !== nothing # remove this?
16✔
1629
        getglobal(mod, :Out)[n] = x
14✔
1630
    end
1631
    nothing
16✔
1632
end
1633

1634
function set_prompt(repl::LineEditREPL, n::Ref{Int})
1✔
1635
    julia_prompt = repl.interface.modes[1]
1✔
1636
    julia_prompt.prompt = function()
782✔
1637
        n[] = repl_eval_counter(julia_prompt.hist)+1
781✔
1638
        string("In [", n[], "]: ")
781✔
1639
    end
1640
    nothing
1✔
1641
end
1642

1643
function set_output_prefix(repl::LineEditREPL, n::Ref{Int})
1✔
1644
    julia_prompt = repl.interface.modes[1]
1✔
1645
    if REPL.hascolor(repl)
1✔
1646
        julia_prompt.output_prefix_prefix = Base.text_colors[:red]
1✔
1647
    end
1648
    julia_prompt.output_prefix = () -> string("Out[", n[], "]: ")
15✔
1649
    nothing
1✔
1650
end
1651

1652
function __current_ast_transforms(backend)
1✔
1653
    if backend === nothing
1✔
1654
        isdefined(Base, :active_repl_backend) ? Base.active_repl_backend.ast_transforms : REPL.repl_ast_transforms
×
1655
    else
1656
        backend.ast_transforms
1✔
1657
    end
1658
end
1659

1660
function numbered_prompt!(repl::LineEditREPL=Base.active_repl, backend=nothing)
1✔
1661
    n = Ref{Int}(0)
1✔
1662
    set_prompt(repl, n)
1✔
1663
    set_output_prefix(repl, n)
1✔
1664
    push!(__current_ast_transforms(backend), @nospecialize(ast) -> out_transform(ast, n))
17✔
1665
    return
1✔
1666
end
1667

1668
"""
1669
    Out[n]
1670

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

1674
See also [`ans`](@ref).
1675
"""
1676
Base.MainInclude.Out
1677

1678
end
1679

1680
import .Numbered.numbered_prompt!
1681

1682
# this assignment won't survive precompilation,
1683
# but will stick if REPL is baked into a sysimg.
1684
# Needs to occur after this module is finished.
1685
Base.REPL_MODULE_REF[] = REPL
1686

1687
if Base.generating_output()
1688
    include("precompile.jl")
1689
end
1690

1691
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