summaryrefslogtreecommitdiff
path: root/src/proxy.go
diff options
context:
space:
mode:
authorJunegunn Choi <junegunn.c@gmail.com>2024-05-20 17:06:44 +0900
committerJunegunn Choi <junegunn.c@gmail.com>2024-05-20 18:24:14 +0900
commit573df524fed1c493ce7d8ea893f06ab90f2ca18a (patch)
tree0bd1185bf827de5860aaf6a4944c2fd1c8ed69ef /src/proxy.go
parentaee417c46a2f6d2aa87ea3fcc799fdc7bc830dfe (diff)
downloadfzf-573df524fed1c493ce7d8ea893f06ab90f2ca18a.tar.gz
Use winpty to launch fzf in Git bash (mintty)
Close #3806 Known limitation: * --height cannot be used
Diffstat (limited to 'src/proxy.go')
-rw-r--r--src/proxy.go132
1 files changed, 132 insertions, 0 deletions
diff --git a/src/proxy.go b/src/proxy.go
new file mode 100644
index 00000000..bbac0292
--- /dev/null
+++ b/src/proxy.go
@@ -0,0 +1,132 @@
+package fzf
+
+import (
+ "bufio"
+ "errors"
+ "fmt"
+ "io"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "strings"
+ "time"
+
+ "github.com/junegunn/fzf/src/tui"
+ "github.com/junegunn/fzf/src/util"
+)
+
+const becomeSuffix = ".become"
+
+func escapeSingleQuote(str string) string {
+ return "'" + strings.ReplaceAll(str, "'", "'\\''") + "'"
+}
+
+func fifo(name string) (string, error) {
+ ns := time.Now().UnixNano()
+ output := filepath.Join(os.TempDir(), fmt.Sprintf("fzf-%s-%d", name, ns))
+ output, err := mkfifo(output, 0600)
+ if err != nil {
+ return output, err
+ }
+ return output, nil
+}
+
+func runProxy(commandPrefix string, cmdBuilder func(temp string) *exec.Cmd, opts *Options, withExports bool) (int, error) {
+ output, err := fifo("proxy-output")
+ if err != nil {
+ return ExitError, err
+ }
+ defer os.Remove(output)
+
+ // Take the output
+ go func() {
+ withOutputPipe(output, func(outputFile io.ReadCloser) {
+ if opts.Output == nil {
+ io.Copy(os.Stdout, outputFile)
+ } else {
+ reader := bufio.NewReader(outputFile)
+ sep := opts.PrintSep[0]
+ for {
+ item, err := reader.ReadString(sep)
+ if err != nil {
+ break
+ }
+ opts.Output <- item
+ }
+ }
+ })
+ }()
+
+ var command string
+ commandPrefix += ` --proxy-script "$0"`
+ if opts.Input == nil && util.IsTty() {
+ command = fmt.Sprintf(`%s > %q`, commandPrefix, output)
+ } else {
+ input, err := fifo("proxy-input")
+ if err != nil {
+ return ExitError, err
+ }
+ defer os.Remove(input)
+
+ go func() {
+ withInputPipe(input, func(inputFile io.WriteCloser) {
+ if opts.Input == nil {
+ io.Copy(inputFile, os.Stdin)
+ } else {
+ for item := range opts.Input {
+ fmt.Fprint(inputFile, item+opts.PrintSep)
+ }
+ }
+ })
+ }()
+
+ if withExports {
+ command = fmt.Sprintf(`%s < %q > %q`, commandPrefix, input, output)
+ } else {
+ // For mintty: cannot directly read named pipe from Go code
+ command = fmt.Sprintf(`command cat %q | %s > %q`, input, commandPrefix, output)
+ }
+ }
+
+ // To ensure that the options are processed by a POSIX-compliant shell,
+ // we need to write the command to a temporary file and execute it with sh.
+ var exports []string
+ if withExports {
+ exports = os.Environ()
+ for idx, pairStr := range exports {
+ pair := strings.SplitN(pairStr, "=", 2)
+ exports[idx] = fmt.Sprintf("export %s=%s", pair[0], escapeSingleQuote(pair[1]))
+ }
+ }
+ temp := writeTemporaryFile(append(exports, command), "\n")
+ defer os.Remove(temp)
+
+ cmd := cmdBuilder(temp)
+ if err := cmd.Run(); err != nil {
+ if exitError, ok := err.(*exec.ExitError); ok {
+ code := exitError.ExitCode()
+ if code == ExitBecome {
+ becomeFile := temp + becomeSuffix
+ data, err := os.ReadFile(becomeFile)
+ os.Remove(becomeFile)
+ if err != nil {
+ return ExitError, err
+ }
+ elems := strings.Split(string(data), "\x00")
+ if len(elems) < 1 {
+ return ExitError, errors.New("invalid become command")
+ }
+ command := elems[0]
+ env := []string{}
+ if len(elems) > 1 {
+ env = elems[1:]
+ }
+ executor := util.NewExecutor(opts.WithShell)
+ executor.Become(tui.TtyIn(), env, command)
+ }
+ return code, err
+ }
+ }
+
+ return ExitOk, nil
+}