From 0f4d7838c09f826ef3c2ca7c3cb94d435ff63628 Mon Sep 17 00:00:00 2001 From: TEC Date: Sun, 12 May 2024 11:01:31 +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 | 28 ++++++--- src/jloptions.c | 21 ++++--- stdlib/REPL/src/REPL.jl | 121 ++++++++++++++++++++++----------------- stdlib/REPL/test/repl.jl | 4 +- test/cmdlineargs.jl | 27 +++++---- 5 files changed, 120 insertions(+), 81 deletions(-) diff --git a/base/client.jl b/base/client.jl index f6b83ecd0f4a0..45cab245acd99 100644 --- a/base/client.jl +++ b/base/client.jl @@ -431,7 +431,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 == :no || REPL.banner(term, banner) if term.term_type == "dumb" repl = REPL.BasicREPL(term) quiet || @warn "Terminal not fully functional" @@ -556,12 +556,26 @@ 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 - + bval = if opts.banner == -1 # Auto + Int(interactiveinput) + else + opts.banner + end + # All the options produced by `jloptions.c`'s `case opt_banner`. + # 0=off, 1=largest, ..., N=smallest + banner = if bval == 0 + :no + elseif bval == 1 + :full + elseif bval == 2 + :narrow + elseif bval == 3 + :short + elseif bval == 4 + :tiny + else # For type stability of `banner` + :unreachable + end quiet = (opts.quiet != 0) history_file = (opts.historyfile != 0) return run_main_repl(interactiveinput, quiet, banner, history_file) diff --git a/src/jloptions.c b/src/jloptions.c index 4cdec2c7b367f..6cd393c5e3cf2 100644 --- a/src/jloptions.c +++ b/src/jloptions.c @@ -171,7 +171,8 @@ static const char opts[] = " -i, --interactive Interactive mode; REPL runs and\n" " `isinteractive()` is true.\n" " -q, --quiet Quiet startup: no banner, suppress REPL warnings\n" - " --banner={yes|no|short|auto*} Enable or disable startup banner\n" + " --banner={yes|no|auto*|} Enable or disable startup banner, or specify a\n" + " preferred (tiny, short, narrow, or full).\n" " --color={yes|no|auto*} Enable or disable color text\n" " --history-file={yes*|no} Load or save history\n\n" @@ -495,16 +496,22 @@ JL_DLLEXPORT void jl_parse_opts(int *argcp, char ***argvp) jl_options.banner = 0; break; case opt_banner: // banner - if (!strcmp(optarg, "yes")) - jl_options.banner = 1; + if (!strcmp(optarg, "auto")) + jl_options.banner = -1; else if (!strcmp(optarg, "no")) jl_options.banner = 0; - else if (!strcmp(optarg, "auto")) - jl_options.banner = -1; - else if (!strcmp(optarg, "short")) + else if (!strcmp(optarg, "yes")) + jl_options.banner = 1; + else if (!strcmp(optarg, "full")) + jl_options.banner = 1; // Same as "yes". + else if (!strcmp(optarg, "narrow")) jl_options.banner = 2; + else if (!strcmp(optarg, "short")) + jl_options.banner = 3; + else if (!strcmp(optarg, "tiny")) + jl_options.banner = 4; else - jl_errorf("julia: invalid argument to --banner={yes|no|auto|short} (%s)", optarg); + jl_errorf("julia: invalid argument to --banner={yes|no|auto|full|narrow|short|tiny} (%s)", optarg); 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 6eb22b2360f3c..7df685b18dc0e 100644 --- a/stdlib/REPL/src/REPL.jl +++ b/stdlib/REPL/src/REPL.jl @@ -1688,11 +1688,21 @@ 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, preferred::Symbol = :full) + +Print the "Julia" informative banner to `io`, using the `preferred` variant +if reasonable and known. + +!!! warning + The particular banner selected by `preferred` is liable to being changed + without warning. The current variants are: `:tiny`, `:short`, `:narrow`, and `:full`. +""" +function banner(io::IO = stdout, preferred::Symbol = :full) + 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) @@ -1701,60 +1711,65 @@ 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." + + sizenames = (:tiny, :short, :narrow, :full) + maxsize = something(findfirst(==(preferred), sizenames), length(sizenames)) + 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) > (12, 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 6427440c9d39e..ddf5a4cca8c25 100644 --- a/stdlib/REPL/test/repl.jl +++ b/stdlib/REPL/test/repl.jl @@ -1952,9 +1952,9 @@ let io = IOBuffer() seek(io, 0) @test countlines(io) == 9 take!(io) - @test REPL.banner(io; short=true) === nothing + @test REPL.banner(io, :tiny) === 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 01a8acaeaea94..cbb1849005fff 100644 --- a/test/cmdlineargs.jl +++ b/test/cmdlineargs.jl @@ -263,18 +263,21 @@ let exename = `$(Base.julia_cmd()) --startup-file=no --color=no` # --quiet, --banner let p = "print((Base.JLOptions().quiet, Base.JLOptions().banner))" - @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=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 -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 --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 -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=full -e $p`, String) == "(0, 1)" + @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=tiny -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=narrow -q -e $p`, String) == "(1, 2)" + @test read(`$exename --banner=short -q -e $p`, String) == "(1, 3)" + @test read(`$exename --banner=tiny -q -e $p`, String) == "(1, 4)" end # --home