From 75c6468310f786e5761384d780e96facfbfcdf8f Mon Sep 17 00:00:00 2001 From: TEC Date: Thu, 26 Oct 2023 00:10:36 +0800 Subject: [PATCH] Make the REPL banner resize to avoid wrapping We also spritz it up a bit using the new StyledStrings library, namely: - colouring the Help and Pkg key prompts - making the docs link a terminal link, and Pkg a link to the Pkg docs - using box drawing characters for the dividing line - making branch status more colourful - making the official version text more subdued With these change the four banners (from smallest to largest) are: 1. A one-liner, for extreme circumstances (new) 2. The short banner 3. The large banner, with the description stacked vertically (new) 4. The large banner, with the description stacked horizontally --- base/client.jl | 15 +++-- src/jloptions.c | 19 +++--- stdlib/REPL/src/REPL.jl | 121 ++++++++++++++++++++++----------------- stdlib/REPL/test/repl.jl | 4 +- test/cmdlineargs.jl | 9 +-- test/precompile.jl | 1 + 6 files changed, 95 insertions(+), 74 deletions(-) diff --git a/base/client.jl b/base/client.jl index 201792c786b51..bf6ba0d966244 100644 --- a/base/client.jl +++ b/base/client.jl @@ -411,7 +411,7 @@ end global active_repl # run the requested sort of evaluation loop on stdio -function run_main_repl(interactive::Bool, quiet::Bool, banner::Symbol, history_file::Bool, color_set::Bool) +function run_main_repl(interactive::Bool, quiet::Bool, banner::Int, history_file::Bool, color_set::Bool) fallback_repl = parse(Bool, get(ENV, "JULIA_FALLBACK_REPL", "false")) if !fallback_repl && interactive load_InteractiveUtils() @@ -424,7 +424,7 @@ function run_main_repl(interactive::Bool, quiet::Bool, banner::Symbol, history_f invokelatest(REPL_MODULE_REF[]) do REPL term_env = get(ENV, "TERM", @static Sys.iswindows() ? "" : "dumb") term = REPL.Terminals.TTYTerminal(term_env, stdin, stdout, stderr) - banner == :no || REPL.banner(term, short=banner==:short) + banner > 0 && REPL.banner(term, banner) if term.term_type == "dumb" repl = REPL.BasicREPL(term) quiet || @warn "Terminal not fully functional" @@ -549,12 +549,11 @@ end function repl_main(_) opts = Base.JLOptions() interactiveinput = isa(stdin, Base.TTY) - b = opts.banner - auto = b == -1 - banner = b == 0 || (auto && !interactiveinput) ? :no : - b == 1 || (auto && interactiveinput) ? :yes : - :short # b == 2 - + banner = if opts.banner == -1 + 10 * Int(interactiveinput) + else + Int(opts.banner) + end quiet = (opts.quiet != 0) history_file = (opts.historyfile != 0) color_set = (opts.color != 0) # --color!=auto diff --git a/src/jloptions.c b/src/jloptions.c index fa5d4ff43ee04..02fb5fc966855 100644 --- a/src/jloptions.c +++ b/src/jloptions.c @@ -140,8 +140,8 @@ static const char opts[] = // interactive options " -i, --interactive Interactive mode; REPL runs and `isinteractive()` is true\n" " -q, --quiet Quiet startup: no banner, suppress REPL warnings\n" - " --banner={yes|no|short|auto*}\n" - " Enable or disable startup banner\n" + " --banner={yes|no|0-10|auto*}\n" + " Enable or disable startup banner, optionally setting a maximum ordinal size\n" " --color={yes|no|auto*} Enable or disable color text\n" " --history-file={yes*|no} Load or save history\n\n" @@ -439,15 +439,20 @@ JL_DLLEXPORT void jl_parse_opts(int *argcp, char ***argvp) break; case opt_banner: // banner if (!strcmp(optarg, "yes")) - jl_options.banner = 1; + jl_options.banner = 10; else if (!strcmp(optarg, "no")) jl_options.banner = 0; else if (!strcmp(optarg, "auto")) jl_options.banner = -1; - else if (!strcmp(optarg, "short")) - jl_options.banner = 2; - else - jl_errorf("julia: invalid argument to --banner={yes|no|auto|short} (%s)", optarg); + else { + errno = 0; + int bval = strtol(optarg, NULL, 10); + if (errno == ERANGE) + jl_errorf("julia: invalid argument to --banner={yes|no|0-10|auto} (%s)", optarg); + else + jl_options.banner = bval; + break; + } break; case opt_sysimage_native_code: if (!strcmp(optarg,"yes")) diff --git a/stdlib/REPL/src/REPL.jl b/stdlib/REPL/src/REPL.jl index f39aed51cebaf..7c57d1d8e9a22 100644 --- a/stdlib/REPL/src/REPL.jl +++ b/stdlib/REPL/src/REPL.jl @@ -1480,11 +1480,23 @@ ends_with_semicolon(code::AbstractString) = ends_with_semicolon(String(code)) ends_with_semicolon(code::Union{String,SubString{String}}) = contains(_rm_strings_and_comments(code), r";\s*$") -function banner(io::IO = stdout; short = false) - if Base.GIT_VERSION_INFO.tagged_commit - commit_string = Base.TAGGED_RELEASE_BANNER +""" + banner(io::IO = stdout, maxsize:Int = 10) + +Print the "Julia" informative banner to `io`, using a size variant no larger +than `maxsize`, where `maxsize` is an ordinal value correspanding to a +particular banner size. + +!!! warning + The particular banner selected by `maxsize` is liable to being changed + without warning. `1` is the smallest banner, with each following size larger + than the previous. Currently there are four sizes available. +""" +function banner(io::IO = stdout, maxsize::Int = 10) + commit_string = if Base.GIT_VERSION_INFO.tagged_commit + Base.AnnotatedString(TAGGED_RELEASE_BANNER, :face => :shadow) elseif isempty(Base.GIT_VERSION_INFO.commit) - commit_string = "" + styled"" else days = Int(floor((ccall(:jl_clock_now, Float64, ()) - Base.GIT_VERSION_INFO.fork_master_timestamp) / (60 * 60 * 24))) days = max(0, days) @@ -1493,60 +1505,63 @@ function banner(io::IO = stdout; short = false) commit = Base.GIT_VERSION_INFO.commit_short if distance == 0 - commit_string = "Commit $(commit) ($(days) $(unit) old master)" + styled"""Commit {grey:$commit} \ + ({warning:⌛ {italic:$days $unit}} old master)""" else branch = Base.GIT_VERSION_INFO.branch - commit_string = "$(branch)/$(commit) (fork: $(distance) commits, $(days) $(unit))" + styled"""{emphasis:$branch}/{grey:$commit} \ + ({italic:{success:{bold,(slant=normal):↑} $distance commits}, \ + {warning:{(slant=normal):⌛} $days $unit}})""" end end - commit_date = isempty(Base.GIT_VERSION_INFO.date_string) ? "" : " ($(split(Base.GIT_VERSION_INFO.date_string)[1]))" - - if get(io, :color, false)::Bool - c = Base.text_colors - tx = c[:normal] # text - jl = c[:normal] # julia - d1 = c[:bold] * c[:blue] # first dot - d2 = c[:bold] * c[:red] # second dot - d3 = c[:bold] * c[:green] # third dot - d4 = c[:bold] * c[:magenta] # fourth dot - - if short - print(io,""" - $(d3)o$(tx) | Version $(VERSION)$(commit_date) - $(d2)o$(tx) $(d4)o$(tx) | $(commit_string) - """) - else - print(io,""" $(d3)_$(tx) - $(d1)_$(tx) $(jl)_$(tx) $(d2)_$(d3)(_)$(d4)_$(tx) | Documentation: https://docs.julialang.org - $(d1)(_)$(jl) | $(d2)(_)$(tx) $(d4)(_)$(tx) | - $(jl)_ _ _| |_ __ _$(tx) | Type \"?\" for help, \"]?\" for Pkg help. - $(jl)| | | | | | |/ _` |$(tx) | - $(jl)| | |_| | | | (_| |$(tx) | Version $(VERSION)$(commit_date) - $(jl)_/ |\\__'_|_|_|\\__'_|$(tx) | $(commit_string) - $(jl)|__/$(tx) | - - """) - end - else - if short - print(io,""" - o | Version $(VERSION)$(commit_date) - o o | $(commit_string) - """) - else - print(io,""" - _ - _ _ _(_)_ | Documentation: https://docs.julialang.org - (_) | (_) (_) | - _ _ _| |_ __ _ | Type \"?\" for help, \"]?\" for Pkg help. - | | | | | | |/ _` | | - | | |_| | | | (_| | | Version $(VERSION)$(commit_date) - _/ |\\__'_|_|_|\\__'_| | $(commit_string) - |__/ | - - """) - end + commit_date = isempty(Base.GIT_VERSION_INFO.date_string) ? "" : styled" {light:($(split(Base.GIT_VERSION_INFO.date_string)[1]))}" + doclink = styled"{bold:Documentation:} {(underline=grey),link={https://docs.julialang.org}:https://docs.julialang.org}" + help = styled"Type {repl_prompt_help:?} for help, {repl_prompt_pkg:]?} for {(underline=grey),link={https://pkgdocs.julialang.org/}:Pkg} help." + + size = min(if all(displaysize(io) .>= (8, 70)); 4 # Full size + elseif all(displaysize(io) .>= (8, 45)); 3 # Narrower + elseif all(displaysize(io) .>= (3, 50)); 2 # Tiny + else 1 end, + max(0, maxsize)) + + if size == 4 # Full size + print(io, styled""" + {bold,green:_} + {bold,blue:_} _ {bold:{red:_}{green:(_)}{magenta:_}} {shadow:│} $doclink + {bold,blue:(_)} | {bold:{red:(_)} {magenta:(_)}} {shadow:│} + _ _ _| |_ __ _ {shadow:│} $help + | | | | | | |/ _` | {shadow:│} + | | |_| | | | (_| | {shadow:│} Version {bold:$VERSION}$commit_date + _/ |\\__'_|_|_|\\__'_| {shadow:│} $commit_string + |__/ {shadow:│} + \n""") + elseif size == 3 # Rotated + print(io, styled""" + {bold,green:_} + {bold,blue:_} _ {bold:{red:_}{green:(_)}{magenta:_}} + {bold,blue:(_)} | {bold:{red:(_)} {magenta:(_)}} + _ _ _| |_ __ _ + | | | | | | |/ _` | + | | |_| | | | (_| | + _/ |\\__'_|_|_|\\__'_| + |__/ + + $doclink + $help + + Version {bold:$VERSION}$commit_date + $commit_string + \n""") + elseif size == 2 # Tiny + print(io, styled""" + {bold,green:o} {shadow:│} Version {bold:$VERSION}$commit_date + {bold:{red:o} {magenta:o}} {shadow:│} $commit_string + """, ifelse(displaysize(io) > (4, 0), "\n", "")) + elseif size == 1 && Base.GIT_VERSION_INFO.tagged_commit # Text only + print(io, styled"""{bold:{blue:∴} {magenta:Julia} $VERSION}$commit_date\n""") + elseif size == 1 # Text only + print(io, styled"""{bold:{blue:∴} {magenta:Julia} $VERSION}$commit_date $commit_string\n""") end end diff --git a/stdlib/REPL/test/repl.jl b/stdlib/REPL/test/repl.jl index 83df34a056578..0a59ffc1c4bbb 100644 --- a/stdlib/REPL/test/repl.jl +++ b/stdlib/REPL/test/repl.jl @@ -1766,9 +1766,9 @@ let io = IOBuffer() seek(io, 0) @test countlines(io) == 9 take!(io) - @test REPL.banner(io; short=true) === nothing + @test REPL.banner(io, 1) === nothing seek(io, 0) - @test countlines(io) == 2 + @test countlines(io) == 1 end @testset "Docstrings" begin diff --git a/test/cmdlineargs.jl b/test/cmdlineargs.jl index 92bada23cb258..060d41babb200 100644 --- a/test/cmdlineargs.jl +++ b/test/cmdlineargs.jl @@ -262,15 +262,16 @@ let exename = `$(Base.julia_cmd()) --startup-file=no --color=no` @test read(`$exename -e $p`, String) == "(0, -1)" @test read(`$exename -q -e $p`, String) == "(1, 0)" @test read(`$exename --quiet -e $p`, String) == "(1, 0)" + @test read(`$exename --banner=auto -e $p`, String) == "(0, -1)" @test read(`$exename --banner=no -e $p`, String) == "(0, 0)" - @test read(`$exename --banner=yes -e $p`, String) == "(0, 1)" - @test read(`$exename --banner=short -e $p`, String) == "(0, 2)" + @test read(`$exename --banner=yes -e $p`, String) == "(0, 10)" + @test read(`$exename --banner=4 -e $p`, String) == "(1, 4)" @test read(`$exename -q --banner=no -e $p`, String) == "(1, 0)" @test read(`$exename -q --banner=yes -e $p`, String) == "(1, 1)" - @test read(`$exename -q --banner=short -e $p`, String) == "(1, 2)" + @test read(`$exename -q --banner=4 -e $p`, String) == "(1, 4)" @test read(`$exename --banner=no -q -e $p`, String) == "(1, 0)" @test read(`$exename --banner=yes -q -e $p`, String) == "(1, 1)" - @test read(`$exename --banner=short -q -e $p`, String) == "(1, 2)" + @test read(`$exename --banner=4 -q -e $p`, String) == "(1, 4)" end # --home diff --git a/test/precompile.jl b/test/precompile.jl index 90086ff7b7385..e947fe2f0a4b5 100644 --- a/test/precompile.jl +++ b/test/precompile.jl @@ -472,6 +472,7 @@ precompile_test_harness(false) do dir Dict(Base.PkgId(Base.root_module(Base, :StyledStrings)) => Base.module_build_id(Base.root_module(Base, :StyledStrings))), # and their dependencies Dict(Base.PkgId(Base.root_module(Base, :Base64)) => Base.module_build_id(Base.root_module(Base, :Base64))), + Dict(Base.PkgId(Base.root_module(Base, :StyledStrings)) => Base.module_build_id(Base.root_module(Base, :StyledStrings))), ) @test Dict(modules) == modules_ok