summaryrefslogtreecommitdiffstats
path: root/src/util/util_windows.go
diff options
context:
space:
mode:
Diffstat (limited to 'src/util/util_windows.go')
-rw-r--r--src/util/util_windows.go155
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