diff options
| author | Junegunn Choi <junegunn.c@gmail.com> | 2024-04-27 18:36:37 +0900 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2024-04-27 18:36:37 +0900 |
| commit | a4391aeedd4fec1865d2d646711f58d04058531b (patch) | |
| tree | 73a6862010c323f380a3105f929b41a39c7a3753 /src/util/util_windows.go | |
| parent | b86a967ee217f4c820249701218a17eaad2737ae (diff) | |
| download | fzf-a4391aeedd4fec1865d2d646711f58d04058531b.tar.gz | |
Add --with-shell for shelling out with different command and flags (#3746)
Close #3732
Diffstat (limited to 'src/util/util_windows.go')
| -rw-r--r-- | src/util/util_windows.go | 106 |
1 files changed, 74 insertions, 32 deletions
diff --git a/src/util/util_windows.go b/src/util/util_windows.go index aa69b99d..cbaa8ce0 100644 --- a/src/util/util_windows.go +++ b/src/util/util_windows.go @@ -6,60 +6,102 @@ import ( "fmt" "os" "os/exec" + "regexp" "strings" "sync/atomic" "syscall" ) -var shellPath atomic.Value +type Executor struct { + shell string + args []string + shellPath atomic.Value +} + +func NewExecutor(withShell string) *Executor { + shell := os.Getenv("SHELL") + args := strings.Fields(withShell) + if len(args) > 0 { + shell = args[0] + } else if len(shell) == 0 { + shell = "cmd" + } + + if len(args) > 0 { + args = args[1:] + } else if strings.Contains(shell, "cmd") { + args = []string{"/v:on/s/c"} + } else if strings.Contains(shell, "pwsh") || strings.Contains(shell, "powershell") { + args = []string{"-NoProfile", "-Command"} + } else { + args = []string{"-c"} + } + return &Executor{shell: shell, args: args} +} // ExecCommand executes the given command with $SHELL -func ExecCommand(command string, setpgid bool) *exec.Cmd { - var shell string - if cached := shellPath.Load(); cached != nil { +// FIXME: setpgid is unused. We set it in the Unix implementation so that we +// can kill preview process with its child processes at once. +// NOTE: For "powershell", we should ideally set output encoding to UTF8, +// but it is left as is now because no adverse effect has been observed. +func (x *Executor) ExecCommand(command string, setpgid bool) *exec.Cmd { + shell := x.shell + if cached := x.shellPath.Load(); cached != nil { shell = cached.(string) } else { - shell = os.Getenv("SHELL") - if len(shell) == 0 { - shell = "cmd" - } else if strings.Contains(shell, "/") { + if strings.Contains(shell, "/") { out, err := exec.Command("cygpath", "-w", shell).Output() if err == nil { shell = strings.Trim(string(out), "\n") } } - shellPath.Store(shell) + x.shellPath.Store(shell) } - return ExecCommandWith(shell, command, setpgid) + cmd := exec.Command(shell, append(x.args, command)...) + cmd.SysProcAttr = &syscall.SysProcAttr{ + HideWindow: false, + CreationFlags: 0, + } + return cmd } -// ExecCommandWith executes the given command with the specified shell -// FIXME: setpgid is unused. We set it in the Unix implementation so that we -// can kill preview process with its child processes at once. -// NOTE: For "powershell", we should ideally set output encoding to UTF8, -// but it is left as is now because no adverse effect has been observed. -func ExecCommandWith(shell string, command string, setpgid bool) *exec.Cmd { - var cmd *exec.Cmd - if strings.Contains(shell, "cmd") { - cmd = exec.Command(shell) - cmd.SysProcAttr = &syscall.SysProcAttr{ - HideWindow: false, - CmdLine: fmt.Sprintf(` /v:on/s/c "%s"`, command), - CreationFlags: 0, +func (x *Executor) Become(stdin *os.File, environ []string, command string) { + cmd := x.ExecCommand(command, false) + cmd.Stdin = stdin + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + cmd.Env = environ + err := cmd.Start() + if err != nil { + fmt.Fprintf(os.Stderr, "fzf (become): %s\n", err.Error()) + Exit(127) + } + err = cmd.Wait() + if err != nil { + if exitError, ok := err.(*exec.ExitError); ok { + Exit(exitError.ExitCode()) } - return cmd } + Exit(0) +} - if strings.Contains(shell, "pwsh") || strings.Contains(shell, "powershell") { - cmd = exec.Command(shell, "-NoProfile", "-Command", command) +func (x *Executor) QuoteEntry(entry string) string { + if strings.Contains(x.shell, "cmd") { + // backslash escaping is done here for applications + // (see ripgrep test case in terminal_test.go#TestWindowsCommands) + escaped := strings.Replace(entry, `\`, `\\`, -1) + escaped = `"` + strings.Replace(escaped, `"`, `\"`, -1) + `"` + // caret is the escape character for cmd shell + r, _ := regexp.Compile(`[&|<>()@^%!"]`) + return r.ReplaceAllStringFunc(escaped, func(match string) string { + return "^" + match + }) + } else if strings.Contains(x.shell, "pwsh") || strings.Contains(x.shell, "powershell") { + escaped := strings.Replace(entry, `"`, `\"`, -1) + return "'" + strings.Replace(escaped, "'", "''", -1) + "'" } else { - cmd = exec.Command(shell, "-c", command) - } - cmd.SysProcAttr = &syscall.SysProcAttr{ - HideWindow: false, - CreationFlags: 0, + return "'" + strings.Replace(entry, "'", "'\\''", -1) + "'" } - return cmd } // KillCommand kills the process for the given command |
