diff options
Diffstat (limited to 'src/util/util_windows.go')
-rw-r--r-- | src/util/util_windows.go | 155 |
1 files changed, 126 insertions, 29 deletions
diff --git a/src/util/util_windows.go b/src/util/util_windows.go index aa69b99d..f29e33be 100644 --- a/src/util/util_windows.go +++ b/src/util/util_windows.go @@ -6,60 +6,157 @@ import ( "fmt" "os" "os/exec" + "path/filepath" "strings" "sync/atomic" "syscall" ) -var shellPath atomic.Value +type shellType int + +const ( + shellTypeUnknown shellType = iota + shellTypeCmd + shellTypePowerShell +) + +type Executor struct { + shell string + shellType shellType + 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" + } + + shellType := shellTypeUnknown + basename := filepath.Base(shell) + if len(args) > 0 { + args = args[1:] + } else if strings.HasPrefix(basename, "cmd") { + shellType = shellTypeCmd + args = []string{"/s/c"} + } else if strings.HasPrefix(basename, "pwsh") || strings.HasPrefix(basename, "powershell") { + shellType = shellTypePowerShell + args = []string{"-NoProfile", "-Command"} + } else { + args = []string{"-c"} + } + return &Executor{shell: shell, shellType: shellType, 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) -} - -// 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") { + if x.shellType == shellTypeCmd { cmd = exec.Command(shell) cmd.SysProcAttr = &syscall.SysProcAttr{ HideWindow: false, - CmdLine: fmt.Sprintf(` /v:on/s/c "%s"`, command), + CmdLine: fmt.Sprintf(`%s "%s"`, strings.Join(x.args, " "), command), + CreationFlags: 0, + } + } else { + cmd = exec.Command(shell, append(x.args, command)...) + cmd.SysProcAttr = &syscall.SysProcAttr{ + HideWindow: false, CreationFlags: 0, } - return cmd } + return cmd +} - if strings.Contains(shell, "pwsh") || strings.Contains(shell, "powershell") { - cmd = exec.Command(shell, "-NoProfile", "-Command", command) - } else { - cmd = exec.Command(shell, "-c", command) +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()) + os.Exit(127) } - cmd.SysProcAttr = &syscall.SysProcAttr{ - HideWindow: false, - CreationFlags: 0, + err = cmd.Wait() + if err != nil { + if exitError, ok := err.(*exec.ExitError); ok { + os.Exit(exitError.ExitCode()) + } + } + os.Exit(0) +} + +func escapeArg(s string) string { + b := make([]byte, 0, len(s)+2) + b = append(b, '"') + slashes := 0 + for i := 0; i < len(s); i++ { + c := s[i] + switch c { + default: + slashes = 0 + case '\\': + slashes++ + case '"': + for ; slashes > 0; slashes-- { + b = append(b, '\\') + } + b = append(b, '\\') + } + b = append(b, c) + } + for ; slashes > 0; slashes-- { + b = append(b, '\\') + } + b = append(b, '"') + return string(b) +} + +func (x *Executor) QuoteEntry(entry string) string { + switch x.shellType { + case shellTypeCmd: + /* Manually tested with the following commands: + fzf --preview "echo {}" + fzf --preview "type {}" + echo .git\refs\| fzf --preview "dir {}" + echo .git\refs\\| fzf --preview "dir {}" + echo .git\refs\\\| fzf --preview "dir {}" + reg query HKCU | fzf --reverse --bind "enter:reload(reg query {})" + fzf --disabled --preview "echo {q} {n} {}" --query "&|<>()@^%!" + fd -H --no-ignore -td -d 4 | fzf --preview "dir {}" + fd -H --no-ignore -td -d 4 | fzf --preview "eza {}" --preview-window up + fd -H --no-ignore -td -d 4 | fzf --preview "eza --color=always --tree --level=3 --icons=always {}" + fd -H --no-ignore -td -d 4 | fzf --preview ".\eza.exe --color=always --tree --level=3 --icons=always {}" --with-shell "powershell -NoProfile -Command" + */ + return escapeArg(entry) + case shellTypePowerShell: + escaped := strings.Replace(entry, `"`, `\"`, -1) + return "'" + strings.Replace(escaped, "'", "''", -1) + "'" + default: + return "'" + strings.Replace(entry, "'", "'\\''", -1) + "'" } - return cmd } // KillCommand kills the process for the given command |