From 1d761684c510a04f78349e8e64aa7ebd26578807 Mon Sep 17 00:00:00 2001 From: Junegunn Choi Date: Sun, 20 Apr 2025 11:24:50 +0900 Subject: [PATCH] Add --tty-default=/dev/tty and --no-tty-default option (#4352) Fix #4242. Use --no-tty-default, if you want fzf to perform a TTY look-up instead of defaulting to /dev/tty. --- man/man1/fzf.1 | 7 +++++++ src/options.go | 8 ++++++++ src/proxy.go | 2 +- src/terminal.go | 14 ++++++++------ src/tui/light.go | 8 ++++---- src/tui/light_unix.go | 25 +++++++++++++++++-------- src/tui/light_windows.go | 4 ++-- src/tui/ttyname_unix.go | 8 ++++---- src/tui/ttyname_windows.go | 4 ++-- 9 files changed, 53 insertions(+), 27 deletions(-) diff --git a/man/man1/fzf.1 b/man/man1/fzf.1 index b64b6100..5d4d653e 100644 --- a/man/man1/fzf.1 +++ b/man/man1/fzf.1 @@ -228,6 +228,13 @@ e.g. \fB# Avoid rendering both fzf instances at the same time (sleep 1; seq 1000000; sleep 1) | fzf \-\-sync \-\-query 5 \-\-listen \-\-bind start:up,load:up,result:up,focus:change\-header:Ready\fR .RE +.TP +.B "\-\-no\-tty\-default" +Make fzf search for the current TTY device via standard error instead of using +\fB/dev/tty\fR. This option was added to avoid the problem when trying to open +emacsclient from within fzf. Alternativly, you can change the default TTY +device by setting \fB--tty-default=DEVICE_NAME\fR. + .SS GLOBAL STYLE .TP .BI "\-\-style=" "PRESET" diff --git a/src/options.go b/src/options.go index c250fb59..e94e82f9 100644 --- a/src/options.go +++ b/src/options.go @@ -631,6 +631,7 @@ type Options struct { MEMProfile string BlockProfile string MutexProfile string + TtyDefault string } func filterNonEmpty(input []string) []string { @@ -730,6 +731,7 @@ func defaultOptions() *Options { WalkerOpts: walkerOpts{file: true, hidden: true, follow: true}, WalkerRoot: []string{"."}, WalkerSkip: []string{".git", "node_modules"}, + TtyDefault: tui.DefaultTtyDevice, Help: false, Version: false} } @@ -2336,6 +2338,12 @@ func parseOptions(index *int, opts *Options, allArgs []string) error { } case "--no-tmux": opts.Tmux = nil + case "--tty-default": + if opts.TtyDefault, err = nextString("tty device name required"); err != nil { + return err + } + case "--no-tty-default": + opts.TtyDefault = "" case "--force-tty-in": // NOTE: We need this because `system('fzf --tmux < /dev/tty')` doesn't // work on Neovim. Same as '-' option of fzf-tmux. diff --git a/src/proxy.go b/src/proxy.go index daeb680b..47c41474 100644 --- a/src/proxy.go +++ b/src/proxy.go @@ -145,7 +145,7 @@ func runProxy(commandPrefix string, cmdBuilder func(temp string, needBash bool) env = elems[1:] } executor := util.NewExecutor(opts.WithShell) - ttyin, err := tui.TtyIn() + ttyin, err := tui.TtyIn(opts.TtyDefault) if err != nil { return ExitError, err } diff --git a/src/terminal.go b/src/terminal.go index 7d826fa3..4ed3019b 100644 --- a/src/terminal.go +++ b/src/terminal.go @@ -381,6 +381,7 @@ type Terminal struct { slab *util.Slab theme *tui.ColorTheme tui tui.Renderer + ttyDefault string ttyin *os.File executing *util.AtomicBool termSize tui.TermSize @@ -809,7 +810,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox, executor *util.Executor // when you run fzf multiple times in your Go program. Closing it is known to // cause problems with 'become' action and invalid terminal state after exit. if ttyin == nil { - if ttyin, err = tui.TtyIn(); err != nil { + if ttyin, err = tui.TtyIn(opts.TtyDefault); err != nil { return nil, err } } @@ -817,7 +818,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox, executor *util.Executor if tui.HasFullscreenRenderer() { renderer = tui.NewFullscreenRenderer(opts.Theme, opts.Black, opts.Mouse) } else { - renderer, err = tui.NewLightRenderer(ttyin, opts.Theme, opts.Black, opts.Mouse, opts.Tabstop, opts.ClearOnExit, + renderer, err = tui.NewLightRenderer(opts.TtyDefault, ttyin, opts.Theme, opts.Black, opts.Mouse, opts.Tabstop, opts.ClearOnExit, true, func(h int) int { return h }) } } else { @@ -833,7 +834,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox, executor *util.Executor effectiveMinHeight += borderLines(opts.BorderShape) return util.Min(termHeight, util.Max(evaluateHeight(opts, termHeight), effectiveMinHeight)) } - renderer, err = tui.NewLightRenderer(ttyin, opts.Theme, opts.Black, opts.Mouse, opts.Tabstop, opts.ClearOnExit, false, maxHeightFunc) + renderer, err = tui.NewLightRenderer(opts.TtyDefault, ttyin, opts.Theme, opts.Black, opts.Mouse, opts.Tabstop, opts.ClearOnExit, false, maxHeightFunc) } if err != nil { return nil, err @@ -967,6 +968,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox, executor *util.Executor keyChan: make(chan tui.Event), eventChan: make(chan tui.Event, 6), // start | (load + result + zero|one) | (focus) | (resize) tui: renderer, + ttyDefault: opts.TtyDefault, ttyin: ttyin, initFunc: func() error { return renderer.Init() }, executing: util.NewAtomicBool(false), @@ -4042,7 +4044,7 @@ func (t *Terminal) executeCommand(template string, forcePlus bool, background bo t.executing.Set(true) if !background { // Open a separate handle for tty input - if in, _ := tui.TtyIn(); in != nil { + if in, _ := tui.TtyIn(t.ttyDefault); in != nil { cmd.Stdin = in if in != os.Stdin { defer in.Close() @@ -4051,7 +4053,7 @@ func (t *Terminal) executeCommand(template string, forcePlus bool, background bo cmd.Stdout = os.Stdout if !util.IsTty(os.Stdout) { - if out, _ := tui.TtyOut(); out != nil { + if out, _ := tui.TtyOut(t.ttyDefault); out != nil { cmd.Stdout = out defer out.Close() } @@ -4059,7 +4061,7 @@ func (t *Terminal) executeCommand(template string, forcePlus bool, background bo cmd.Stderr = os.Stderr if !util.IsTty(os.Stderr) { - if out, _ := tui.TtyOut(); out != nil { + if out, _ := tui.TtyOut(t.ttyDefault); out != nil { cmd.Stderr = out defer out.Close() } diff --git a/src/tui/light.go b/src/tui/light.go index 4f5ae555..eb3de098 100644 --- a/src/tui/light.go +++ b/src/tui/light.go @@ -28,7 +28,7 @@ const ( maxInputBuffer = 1024 * 1024 ) -const consoleDevice string = "/dev/tty" +const DefaultTtyDevice string = "/dev/tty" var offsetRegexp = regexp.MustCompile("(.*?)\x00?\x1b\\[([0-9]+);([0-9]+)R") var offsetRegexpBegin = regexp.MustCompile("^\x1b\\[[0-9]+;[0-9]+R") @@ -146,8 +146,8 @@ type LightWindow struct { wrapSignWidth int } -func NewLightRenderer(ttyin *os.File, theme *ColorTheme, forceBlack bool, mouse bool, tabstop int, clearOnExit bool, fullscreen bool, maxHeightFunc func(int) int) (Renderer, error) { - out, err := openTtyOut() +func NewLightRenderer(ttyDefault string, ttyin *os.File, theme *ColorTheme, forceBlack bool, mouse bool, tabstop int, clearOnExit bool, fullscreen bool, maxHeightFunc func(int) int) (Renderer, error) { + out, err := openTtyOut(ttyDefault) if err != nil { out = os.Stderr } @@ -271,7 +271,7 @@ func (r *LightRenderer) getBytesInternal(buffer []byte, nonblock bool) ([]byte, c, ok := r.getch(nonblock) if !nonblock && !ok { r.Close() - return nil, errors.New("failed to read " + consoleDevice) + return nil, errors.New("failed to read " + DefaultTtyDevice) } retries := 0 diff --git a/src/tui/light_unix.go b/src/tui/light_unix.go index 76aac2eb..02fbf436 100644 --- a/src/tui/light_unix.go +++ b/src/tui/light_unix.go @@ -42,26 +42,35 @@ func (r *LightRenderer) closePlatform() { r.ttyout.Close() } -func openTty(mode int) (*os.File, error) { - in, err := os.OpenFile(consoleDevice, mode, 0) - if err != nil { +func openTty(ttyDefault string, mode int) (*os.File, error) { + var in *os.File + var err error + if len(ttyDefault) > 0 { + in, err = os.OpenFile(ttyDefault, mode, 0) + } + if in == nil || err != nil || ttyDefault != DefaultTtyDevice && !util.IsTty(in) { tty := ttyname() if len(tty) > 0 { if in, err := os.OpenFile(tty, mode, 0); err == nil { return in, nil } } - return nil, errors.New("failed to open " + consoleDevice) + if ttyDefault != DefaultTtyDevice { + if in, err = os.OpenFile(DefaultTtyDevice, mode, 0); err == nil { + return in, nil + } + } + return nil, errors.New("failed to open " + DefaultTtyDevice) } return in, nil } -func openTtyIn() (*os.File, error) { - return openTty(syscall.O_RDONLY) +func openTtyIn(ttyDefault string) (*os.File, error) { + return openTty(ttyDefault, syscall.O_RDONLY) } -func openTtyOut() (*os.File, error) { - return openTty(syscall.O_WRONLY) +func openTtyOut(ttyDefault string) (*os.File, error) { + return openTty(ttyDefault, syscall.O_WRONLY) } func (r *LightRenderer) setupTerminal() { diff --git a/src/tui/light_windows.go b/src/tui/light_windows.go index f29e018c..fd5cc142 100644 --- a/src/tui/light_windows.go +++ b/src/tui/light_windows.go @@ -76,12 +76,12 @@ func (r *LightRenderer) closePlatform() { windows.SetConsoleMode(windows.Handle(r.inHandle), r.origStateInput) } -func openTtyIn() (*os.File, error) { +func openTtyIn(ttyDefault string) (*os.File, error) { // not used return nil, nil } -func openTtyOut() (*os.File, error) { +func openTtyOut(ttyDefault string) (*os.File, error) { return os.Stderr, nil } diff --git a/src/tui/ttyname_unix.go b/src/tui/ttyname_unix.go index d0350a0b..9655aa98 100644 --- a/src/tui/ttyname_unix.go +++ b/src/tui/ttyname_unix.go @@ -44,11 +44,11 @@ func ttyname() string { } // TtyIn returns terminal device to read user input -func TtyIn() (*os.File, error) { - return openTtyIn() +func TtyIn(ttyDefault string) (*os.File, error) { + return openTtyIn(ttyDefault) } // TtyIn returns terminal device to write to -func TtyOut() (*os.File, error) { - return openTtyOut() +func TtyOut(ttyDefault string) (*os.File, error) { + return openTtyOut(ttyDefault) } diff --git a/src/tui/ttyname_windows.go b/src/tui/ttyname_windows.go index dfe89eb3..dbe97739 100644 --- a/src/tui/ttyname_windows.go +++ b/src/tui/ttyname_windows.go @@ -11,11 +11,11 @@ func ttyname() string { } // TtyIn on Windows returns os.Stdin -func TtyIn() (*os.File, error) { +func TtyIn(ttyDefault string) (*os.File, error) { return os.Stdin, nil } // TtyOut on Windows returns nil -func TtyOut() (*os.File, error) { +func TtyOut(ttyDefault string) (*os.File, error) { return nil, nil }