mirror of
https://github.com/Llewellynvdm/fzf.git
synced 2024-11-09 23:30:56 +00:00
execute: Open separate handles to /dev/tty (in, out, err)
# This will no longer cause 'Vim: Warning: Output is not to a terminal' fzf --bind 'enter:execute:vim {}' > /tmp/foo
This commit is contained in:
parent
d4216b0dcc
commit
3dee8778d0
@ -60,7 +60,7 @@ func runProxy(commandPrefix string, cmdBuilder func(temp string) *exec.Cmd, opts
|
||||
|
||||
var command string
|
||||
commandPrefix += ` --proxy-script "$0"`
|
||||
if opts.Input == nil && util.IsTty() {
|
||||
if opts.Input == nil && util.IsTty(os.Stdin) {
|
||||
command = fmt.Sprintf(`%s > %q`, commandPrefix, output)
|
||||
} else {
|
||||
input, err := fifo("proxy-input")
|
||||
@ -131,7 +131,11 @@ func runProxy(commandPrefix string, cmdBuilder func(temp string) *exec.Cmd, opts
|
||||
env = elems[1:]
|
||||
}
|
||||
executor := util.NewExecutor(opts.WithShell)
|
||||
executor.Become(tui.TtyIn(), env, command)
|
||||
ttyin, err := tui.TtyIn()
|
||||
if err != nil {
|
||||
return ExitError, err
|
||||
}
|
||||
executor.Become(ttyin, env, command)
|
||||
}
|
||||
return code, err
|
||||
}
|
||||
|
@ -113,7 +113,7 @@ func (r *Reader) ReadSource(inputChan chan string, root string, opts walkerOpts,
|
||||
var success bool
|
||||
if inputChan != nil {
|
||||
success = r.readChannel(inputChan)
|
||||
} else if util.IsTty() {
|
||||
} else if util.IsTty(os.Stdin) {
|
||||
cmd := os.Getenv("FZF_DEFAULT_COMMAND")
|
||||
if len(cmd) == 0 {
|
||||
success = r.readFiles(root, opts, ignores)
|
||||
|
@ -51,6 +51,7 @@ var whiteSuffix *regexp.Regexp
|
||||
var offsetComponentRegex *regexp.Regexp
|
||||
var offsetTrimCharsRegex *regexp.Regexp
|
||||
var passThroughRegex *regexp.Regexp
|
||||
var ttyin *os.File
|
||||
|
||||
const clearCode string = "\x1b[2J"
|
||||
|
||||
@ -691,11 +692,19 @@ func NewTerminal(opts *Options, eventBox *util.EventBox, executor *util.Executor
|
||||
var renderer tui.Renderer
|
||||
fullscreen := !opts.Height.auto && (opts.Height.size == 0 || opts.Height.percent && opts.Height.size == 100)
|
||||
var err error
|
||||
// Reuse ttyin if available to avoid having multiple file descriptors open
|
||||
// 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 {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if fullscreen {
|
||||
if tui.HasFullscreenRenderer() {
|
||||
renderer = tui.NewFullscreenRenderer(opts.Theme, opts.Black, opts.Mouse)
|
||||
} else {
|
||||
renderer, err = tui.NewLightRenderer(opts.Theme, opts.Black, opts.Mouse, opts.Tabstop, opts.ClearOnExit,
|
||||
renderer, err = tui.NewLightRenderer(ttyin, opts.Theme, opts.Black, opts.Mouse, opts.Tabstop, opts.ClearOnExit,
|
||||
true, func(h int) int { return h })
|
||||
}
|
||||
} else {
|
||||
@ -711,7 +720,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(opts.Theme, opts.Black, opts.Mouse, opts.Tabstop, opts.ClearOnExit, false, maxHeightFunc)
|
||||
renderer, err = tui.NewLightRenderer(ttyin, opts.Theme, opts.Black, opts.Mouse, opts.Tabstop, opts.ClearOnExit, false, maxHeightFunc)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -818,7 +827,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox, executor *util.Executor
|
||||
serverOutputChan: make(chan string),
|
||||
eventChan: make(chan tui.Event, 6), // (load + result + zero|one) | (focus) | (resize) | (GetChar)
|
||||
tui: renderer,
|
||||
ttyin: tui.TtyIn(),
|
||||
ttyin: ttyin,
|
||||
initFunc: func() error { return renderer.Init() },
|
||||
executing: util.NewAtomicBool(false),
|
||||
lastAction: actStart,
|
||||
@ -2874,9 +2883,30 @@ func (t *Terminal) executeCommand(template string, forcePlus bool, background bo
|
||||
cmd.Env = t.environ()
|
||||
t.executing.Set(true)
|
||||
if !background {
|
||||
cmd.Stdin = t.ttyin
|
||||
// Open a separate handle for tty input
|
||||
if in, _ := tui.TtyIn(); in != nil {
|
||||
cmd.Stdin = in
|
||||
if in != os.Stdin {
|
||||
defer in.Close()
|
||||
}
|
||||
}
|
||||
|
||||
cmd.Stdout = os.Stdout
|
||||
if !util.IsTty(os.Stdout) {
|
||||
if out, _ := tui.TtyOut(); out != nil {
|
||||
cmd.Stdout = out
|
||||
defer out.Close()
|
||||
}
|
||||
}
|
||||
|
||||
cmd.Stderr = os.Stderr
|
||||
if !util.IsTty(os.Stderr) {
|
||||
if out, _ := tui.TtyOut(); out != nil {
|
||||
cmd.Stderr = out
|
||||
defer out.Close()
|
||||
}
|
||||
}
|
||||
|
||||
t.tui.Pause(true)
|
||||
cmd.Run()
|
||||
t.tui.Resume(true, false)
|
||||
|
@ -127,11 +127,7 @@ type LightWindow struct {
|
||||
bg Color
|
||||
}
|
||||
|
||||
func NewLightRenderer(theme *ColorTheme, forceBlack bool, mouse bool, tabstop int, clearOnExit bool, fullscreen bool, maxHeightFunc func(int) int) (Renderer, error) {
|
||||
in, err := openTtyIn()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
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()
|
||||
if err != nil {
|
||||
out = os.Stderr
|
||||
@ -142,7 +138,7 @@ func NewLightRenderer(theme *ColorTheme, forceBlack bool, mouse bool, tabstop in
|
||||
forceBlack: forceBlack,
|
||||
mouse: mouse,
|
||||
clearOnExit: clearOnExit,
|
||||
ttyin: in,
|
||||
ttyin: ttyin,
|
||||
ttyout: out,
|
||||
yoffset: 0,
|
||||
tabstop: tabstop,
|
||||
|
@ -7,7 +7,6 @@ import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
|
||||
"github.com/junegunn/fzf/src/util"
|
||||
@ -15,13 +14,6 @@ import (
|
||||
"golang.org/x/term"
|
||||
)
|
||||
|
||||
var (
|
||||
tty string
|
||||
ttyin *os.File
|
||||
ttyout *os.File
|
||||
mutex sync.Mutex
|
||||
)
|
||||
|
||||
func IsLightRendererSupported() bool {
|
||||
return true
|
||||
}
|
||||
@ -53,15 +45,13 @@ func (r *LightRenderer) initPlatform() error {
|
||||
}
|
||||
|
||||
func (r *LightRenderer) closePlatform() {
|
||||
// NOOP
|
||||
r.ttyout.Close()
|
||||
}
|
||||
|
||||
func openTty(mode int) (*os.File, error) {
|
||||
in, err := os.OpenFile(consoleDevice, mode, 0)
|
||||
if err != nil {
|
||||
if len(tty) == 0 {
|
||||
tty = ttyname()
|
||||
}
|
||||
tty := ttyname()
|
||||
if len(tty) > 0 {
|
||||
if in, err := os.OpenFile(tty, mode, 0); err == nil {
|
||||
return in, nil
|
||||
@ -73,31 +63,11 @@ func openTty(mode int) (*os.File, error) {
|
||||
}
|
||||
|
||||
func openTtyIn() (*os.File, error) {
|
||||
mutex.Lock()
|
||||
defer mutex.Unlock()
|
||||
|
||||
if ttyin != nil {
|
||||
return ttyin, nil
|
||||
}
|
||||
in, err := openTty(syscall.O_RDONLY)
|
||||
if err == nil {
|
||||
ttyin = in
|
||||
}
|
||||
return in, err
|
||||
return openTty(syscall.O_RDONLY)
|
||||
}
|
||||
|
||||
func openTtyOut() (*os.File, error) {
|
||||
mutex.Lock()
|
||||
defer mutex.Unlock()
|
||||
|
||||
if ttyout != nil {
|
||||
return ttyout, nil
|
||||
}
|
||||
out, err := openTty(syscall.O_WRONLY)
|
||||
if err == nil {
|
||||
ttyout = out
|
||||
}
|
||||
return out, err
|
||||
return openTty(syscall.O_WRONLY)
|
||||
}
|
||||
|
||||
func (r *LightRenderer) setupTerminal() {
|
||||
|
@ -3,6 +3,7 @@
|
||||
package tui
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/gdamore/tcell/v2"
|
||||
@ -20,7 +21,7 @@ func assert(t *testing.T, context string, got interface{}, want interface{}) boo
|
||||
|
||||
// Test the handling of the tcell keyboard events.
|
||||
func TestGetCharEventKey(t *testing.T) {
|
||||
if util.ToTty() {
|
||||
if util.IsTty(os.Stdout) {
|
||||
// This test is skipped when output goes to terminal, because it causes
|
||||
// some glitches:
|
||||
// - output lines may not start at the beginning of a row which makes
|
||||
|
@ -4,12 +4,19 @@ package tui
|
||||
|
||||
import (
|
||||
"os"
|
||||
"sync/atomic"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
var devPrefixes = [...]string{"/dev/pts/", "/dev/"}
|
||||
|
||||
var tty atomic.Value
|
||||
|
||||
func ttyname() string {
|
||||
if cached := tty.Load(); cached != nil {
|
||||
return cached.(string)
|
||||
}
|
||||
|
||||
var stderr syscall.Stat_t
|
||||
if syscall.Fstat(2, &stderr) != nil {
|
||||
return ""
|
||||
@ -27,17 +34,21 @@ func ttyname() string {
|
||||
continue
|
||||
}
|
||||
if stat, ok := info.Sys().(*syscall.Stat_t); ok && stat.Rdev == stderr.Rdev {
|
||||
return prefix + file.Name()
|
||||
value := prefix + file.Name()
|
||||
tty.Store(value)
|
||||
return value
|
||||
}
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// TtyIn returns terminal device to be used as STDIN, falls back to os.Stdin
|
||||
func TtyIn() *os.File {
|
||||
if in, err := openTtyIn(); err == nil {
|
||||
return in
|
||||
}
|
||||
return os.Stdin
|
||||
// TtyIn returns terminal device to read user input
|
||||
func TtyIn() (*os.File, error) {
|
||||
return openTtyIn()
|
||||
}
|
||||
|
||||
// TtyIn returns terminal device to write to
|
||||
func TtyOut() (*os.File, error) {
|
||||
return openTtyOut()
|
||||
}
|
||||
|
@ -2,13 +2,20 @@
|
||||
|
||||
package tui
|
||||
|
||||
import "os"
|
||||
import (
|
||||
"os"
|
||||
)
|
||||
|
||||
func ttyname() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// TtyIn on Windows returns os.Stdin
|
||||
func TtyIn() *os.File {
|
||||
return os.Stdin
|
||||
func TtyIn() (*os.File, error) {
|
||||
return os.Stdin, nil
|
||||
}
|
||||
|
||||
// TtyIn on Windows returns nil
|
||||
func TtyOut() (*os.File, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
@ -138,17 +138,12 @@ func DurWithin(
|
||||
return val
|
||||
}
|
||||
|
||||
// IsTty returns true if stdin is a terminal
|
||||
func IsTty() bool {
|
||||
fd := os.Stdin.Fd()
|
||||
// IsTty returns true if the file is a terminal
|
||||
func IsTty(file *os.File) bool {
|
||||
fd := file.Fd()
|
||||
return isatty.IsTerminal(fd) || isatty.IsCygwinTerminal(fd)
|
||||
}
|
||||
|
||||
// ToTty returns true if stdout is a terminal
|
||||
func ToTty() bool {
|
||||
return isatty.IsTerminal(os.Stdout.Fd())
|
||||
}
|
||||
|
||||
// Once returns a function that returns the specified boolean value only once
|
||||
func Once(nextResponse bool) func() bool {
|
||||
state := nextResponse
|
||||
|
Loading…
Reference in New Issue
Block a user