From 1ed87223beaa109660fc78e3d7fc300207c9d10a Mon Sep 17 00:00:00 2001 From: sandyspiers Date: Wed, 14 May 2025 12:12:55 +0800 Subject: [PATCH 1/7] add support for nushell --- base/client.jl | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/base/client.jl b/base/client.jl index 5bf658bead437..2279bd69b2067 100644 --- a/base/client.jl +++ b/base/client.jl @@ -64,7 +64,13 @@ function repl_cmd(cmd, out) cd(dir) println(out, pwd()) else - @static if !Sys.iswindows() + iswindows = @static Sys.iswindows() ? true : false + if shell_name == "nu" + # remove backticks and apostrophes that dont play nice with nushell + shell_escape_cmd = replace(shell_escape(cmd), r"`|'" => "") + shell_escape_cmd = "try { $shell_escape_cmd } catch { |err| \$err.rendered }" + cmd = `$shell -c $shell_escape_cmd` + elseif !iswindows if shell_name == "fish" shell_escape_cmd = "begin; $(shell_escape_posixly(cmd)); and true; end" else From df360887cff0e2a7475faadd6b0f2bdf944eb9ca Mon Sep 17 00:00:00 2001 From: Sandy Spiers <86579677+sandyspiers@users.noreply.github.com> Date: Wed, 14 May 2025 20:38:00 +0800 Subject: [PATCH 2/7] remove regex and @static macro Replace regex with explicit replace. Remove @static macro and use `Sys.iswindows()` instead Co-authored-by: Jakob Nybo Nissen --- base/client.jl | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/base/client.jl b/base/client.jl index 2279bd69b2067..6409d06359c6d 100644 --- a/base/client.jl +++ b/base/client.jl @@ -64,13 +64,12 @@ function repl_cmd(cmd, out) cd(dir) println(out, pwd()) else - iswindows = @static Sys.iswindows() ? true : false if shell_name == "nu" # remove backticks and apostrophes that dont play nice with nushell - shell_escape_cmd = replace(shell_escape(cmd), r"`|'" => "") + shell_escape_cmd = replace(shell_escape(cmd), "'" => "", "`" => "") shell_escape_cmd = "try { $shell_escape_cmd } catch { |err| \$err.rendered }" cmd = `$shell -c $shell_escape_cmd` - elseif !iswindows + elseif !Sys.iswindows() if shell_name == "fish" shell_escape_cmd = "begin; $(shell_escape_posixly(cmd)); and true; end" else From 39ffeeb763fd07bb61541d460b93f3bb0374a46f Mon Sep 17 00:00:00 2001 From: sandyspiers Date: Fri, 16 May 2025 09:53:15 +0800 Subject: [PATCH 3/7] no need to remove backticks --- base/client.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/base/client.jl b/base/client.jl index 6409d06359c6d..7bcab0c4c0d82 100644 --- a/base/client.jl +++ b/base/client.jl @@ -65,8 +65,8 @@ function repl_cmd(cmd, out) println(out, pwd()) else if shell_name == "nu" - # remove backticks and apostrophes that dont play nice with nushell - shell_escape_cmd = replace(shell_escape(cmd), "'" => "", "`" => "") + # remove apostrophes that dont play nice with nushell + shell_escape_cmd = replace(shell_escape(cmd), "'" => "") shell_escape_cmd = "try { $shell_escape_cmd } catch { |err| \$err.rendered }" cmd = `$shell -c $shell_escape_cmd` elseif !Sys.iswindows() From 251dc132a1c4ee919c8408108396c04a4e9be36c Mon Sep 17 00:00:00 2001 From: MilesCranmer Date: Sun, 25 May 2025 12:24:33 +0100 Subject: [PATCH 4/7] modularize interface for shell commands and add nushell --- base/client.jl | 52 +++++++++++++++++++++++++++++------------ stdlib/REPL/src/REPL.jl | 1 + 2 files changed, 38 insertions(+), 15 deletions(-) diff --git a/base/client.jl b/base/client.jl index 7bcab0c4c0d82..15346e70413ad 100644 --- a/base/client.jl +++ b/base/client.jl @@ -31,7 +31,38 @@ answer_color() = text_colors[repl_color("JULIA_ANSWER_COLOR", default_color_answ stackframe_lineinfo_color() = repl_color("JULIA_STACKFRAME_LINEINFO_COLOR", :bold) stackframe_function_color() = repl_color("JULIA_STACKFRAME_FUNCTION_COLOR", :bold) -function repl_cmd(cmd, out) +""" + ShellSpecification{is_windows, shell_sym} + +A type used for dispatch to select the appropriate shell command preparation logic. +It is parameterized by `is_windows::Bool` indicating the operating system, +and `shell_sym::Symbol` representing the basename of the shell executable. +""" +struct ShellSpecification{is_windows,shell} end + +""" + prepare_shell_command(spec::ShellSpecification, cmd::Cmd, raw_string::String) -> Cmd + +Returns a `Cmd` object configured for execution according to `spec`, +using the provided `cmd` (parsed command) and `raw_string` (original input). +Specialized methods for `ShellSpecification` define shell- and OS-specific behavior. +""" +function prepare_shell_command(::ShellSpecification{true,SHELL}, cmd, _) where {SHELL} + return cmd +end +function prepare_shell_command(::ShellSpecification{false,SHELL}, cmd, _) where {SHELL} + shell_escape_cmd = "$(shell_escape_posixly(cmd)) && true" + return `$SHELL -c $shell_escape_cmd` +end +function prepare_shell_command(::ShellSpecification{false,:fish}, cmd, _) + shell_escape_cmd = "begin; $(shell_escape_posixly(cmd)); and true; end" + return `fish -c $shell_escape_cmd` +end +function prepare_shell_command(::ShellSpecification{false,:nu}, _, raw_string) + return `nu -c $raw_string` +end + +function repl_cmd(cmd, raw_string, out) shell = shell_split(get(ENV, "JULIA_SHELL", get(ENV, "SHELL", "/bin/sh"))) shell_name = Base.basename(shell[1]) @@ -64,21 +95,10 @@ function repl_cmd(cmd, out) cd(dir) println(out, pwd()) else - if shell_name == "nu" - # remove apostrophes that dont play nice with nushell - shell_escape_cmd = replace(shell_escape(cmd), "'" => "") - shell_escape_cmd = "try { $shell_escape_cmd } catch { |err| \$err.rendered }" - cmd = `$shell -c $shell_escape_cmd` - elseif !Sys.iswindows() - if shell_name == "fish" - shell_escape_cmd = "begin; $(shell_escape_posixly(cmd)); and true; end" - else - shell_escape_cmd = "($(shell_escape_posixly(cmd))) && true" - end - cmd = `$shell -c $shell_escape_cmd` - end + shell_spec = ShellSpecification{Sys.iswindows(),Symbol(shell_name)}() + prepared_cmd = prepare_shell_command(shell_spec, cmd, raw_string) try - run(ignorestatus(cmd)) + run(ignorestatus(prepared_cmd)) catch # Windows doesn't shell out right now (complex issue), so Julia tries to run the program itself # Julia throws an exception if it can't find the program, but the stack trace isn't useful @@ -90,6 +110,8 @@ function repl_cmd(cmd, out) nothing end +@deprecate repl_cmd(cmd, out) repl_cmd(cmd, string(cmd), out) + # deprecated function--preserved for DocTests.jl function ip_matches_func(ip, func::Symbol) for fr in StackTraces.lookup(ip) diff --git a/stdlib/REPL/src/REPL.jl b/stdlib/REPL/src/REPL.jl index 81272ac971d40..af52c9b94038e 100644 --- a/stdlib/REPL/src/REPL.jl +++ b/stdlib/REPL/src/REPL.jl @@ -1335,6 +1335,7 @@ function setup_interface( on_done = respond(repl, julia_prompt) do line Expr(:call, :(Base.repl_cmd), :(Base.cmd_gen($(Base.shell_parse(line::String)[1]))), + line::String, outstream(repl)) end, sticky = true) From aebe20b3e512beb9a40bdca049d39bc7e674d89b Mon Sep 17 00:00:00 2001 From: MilesCranmer Date: Sun, 25 May 2025 12:24:51 +0100 Subject: [PATCH 5/7] update tests for new `repl_cmd` --- test/file.jl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/file.jl b/test/file.jl index a163bc07034ab..c91a26225847b 100644 --- a/test/file.jl +++ b/test/file.jl @@ -1925,15 +1925,15 @@ end cd(dir) do withenv("OLDPWD" => nothing) do io = IOBuffer() - Base.repl_cmd(@cmd("cd"), io) - Base.repl_cmd(@cmd("cd -"), io) + Base.repl_cmd(@cmd("cd"), "cd", io) + Base.repl_cmd(@cmd("cd -"), "cd -", io) @test realpath(pwd()) == realpath(dir) if !Sys.iswindows() # Delete the working directory and check we can cd out of it # Cannot delete the working directory on Windows rm(dir) @test_throws Base._UVError("pwd()", Base.UV_ENOENT) pwd() - Base.repl_cmd(@cmd("cd \\~"), io) + Base.repl_cmd(@cmd("cd \\~"), "cd \\~", io) end end end From e6cfe05c5c30fd433affc174976847f5002ecbde Mon Sep 17 00:00:00 2001 From: MilesCranmer Date: Sun, 25 May 2025 12:36:15 +0100 Subject: [PATCH 6/7] avoid `@deprecate` macro --- base/client.jl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/base/client.jl b/base/client.jl index 15346e70413ad..373e973216cd7 100644 --- a/base/client.jl +++ b/base/client.jl @@ -110,7 +110,8 @@ function repl_cmd(cmd, raw_string, out) nothing end -@deprecate repl_cmd(cmd, out) repl_cmd(cmd, string(cmd), out) +# For backward compatibility +repl_cmd(cmd, out) = repl_cmd(cmd, string(cmd), out) # deprecated function--preserved for DocTests.jl function ip_matches_func(ip, func::Symbol) From 1388f83a02c9fd2f4a590e7311d16f5291dbe053 Mon Sep 17 00:00:00 2001 From: MilesCranmer Date: Sun, 25 May 2025 12:58:21 +0100 Subject: [PATCH 7/7] lower case type parameter --- base/client.jl | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/base/client.jl b/base/client.jl index 373e973216cd7..54f5313d513ea 100644 --- a/base/client.jl +++ b/base/client.jl @@ -32,11 +32,11 @@ stackframe_lineinfo_color() = repl_color("JULIA_STACKFRAME_LINEINFO_COLOR", :bol stackframe_function_color() = repl_color("JULIA_STACKFRAME_FUNCTION_COLOR", :bold) """ - ShellSpecification{is_windows, shell_sym} + ShellSpecification{is_windows, shell} A type used for dispatch to select the appropriate shell command preparation logic. It is parameterized by `is_windows::Bool` indicating the operating system, -and `shell_sym::Symbol` representing the basename of the shell executable. +and `shell::Symbol` representing the basename of the shell executable. """ struct ShellSpecification{is_windows,shell} end @@ -47,12 +47,12 @@ Returns a `Cmd` object configured for execution according to `spec`, using the provided `cmd` (parsed command) and `raw_string` (original input). Specialized methods for `ShellSpecification` define shell- and OS-specific behavior. """ -function prepare_shell_command(::ShellSpecification{true,SHELL}, cmd, _) where {SHELL} +function prepare_shell_command(::ShellSpecification{true,shell}, cmd, _) where {shell} return cmd end -function prepare_shell_command(::ShellSpecification{false,SHELL}, cmd, _) where {SHELL} +function prepare_shell_command(::ShellSpecification{false,shell}, cmd, _) where {shell} shell_escape_cmd = "$(shell_escape_posixly(cmd)) && true" - return `$SHELL -c $shell_escape_cmd` + return `$shell -c $shell_escape_cmd` end function prepare_shell_command(::ShellSpecification{false,:fish}, cmd, _) shell_escape_cmd = "begin; $(shell_escape_posixly(cmd)); and true; end"