summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJunegunn Choi <junegunn.c@gmail.com>2018-09-27 15:27:08 +0900
committerJunegunn Choi <junegunn.c@gmail.com>2018-09-28 10:33:52 +0900
commit0d748a0699760003444efca219eb4bb245996008 (patch)
tree791e4849d9bdfb06d013dac96c2197fa2739c0e5
parent27c40dc6b0cc0402b1602b76202be80841329a1d (diff)
downloadfzf-0d748a0699760003444efca219eb4bb245996008.tar.gz
Kill running preview process after 500ms when focus has changed
Close #1383 Close #1384
-rw-r--r--src/constants.go10
-rw-r--r--src/reader.go2
-rw-r--r--src/terminal.go56
-rw-r--r--src/util/util_unix.go19
-rw-r--r--src/util/util_windows.go13
5 files changed, 82 insertions, 18 deletions
diff --git a/src/constants.go b/src/constants.go
index a8e20910..5f583e4c 100644
--- a/src/constants.go
+++ b/src/constants.go
@@ -22,10 +22,11 @@ const (
readerPollIntervalMax = 50 * time.Millisecond
// Terminal
- initialDelay = 20 * time.Millisecond
- initialDelayTac = 100 * time.Millisecond
- spinnerDuration = 200 * time.Millisecond
- maxPatternLength = 300
+ initialDelay = 20 * time.Millisecond
+ initialDelayTac = 100 * time.Millisecond
+ spinnerDuration = 200 * time.Millisecond
+ previewCancelWait = 500 * time.Millisecond
+ maxPatternLength = 300
// Matcher
numPartitionsMultiplier = 8
@@ -76,6 +77,7 @@ const (
)
const (
+ exitCancel = -1
exitOk = 0
exitNoMatch = 1
exitError = 2
diff --git a/src/reader.go b/src/reader.go
index 5fd6d876..b418f549 100644
--- a/src/reader.go
+++ b/src/reader.go
@@ -103,7 +103,7 @@ func (r *Reader) readFromStdin() bool {
}
func (r *Reader) readFromCommand(shell string, cmd string) bool {
- listCommand := util.ExecCommandWith(shell, cmd)
+ listCommand := util.ExecCommandWith(shell, cmd, false)
out, err := listCommand.StdoutPipe()
if err != nil {
return false
diff --git a/src/terminal.go b/src/terminal.go
index 139aacad..cae349dc 100644
--- a/src/terminal.go
+++ b/src/terminal.go
@@ -114,6 +114,7 @@ type Terminal struct {
prevLines []itemLine
suppress bool
startChan chan bool
+ killChan chan int
slab *util.Slab
theme *tui.ColorTheme
tui tui.Renderer
@@ -414,6 +415,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
slab: util.MakeSlab(slab16Size, slab32Size),
theme: opts.Theme,
startChan: make(chan bool, 1),
+ killChan: make(chan int),
tui: renderer,
initFunc: func() { renderer.Init() }}
t.prompt, t.promptLen = t.processTabs([]rune(opts.Prompt), 0)
@@ -1298,7 +1300,7 @@ func (t *Terminal) executeCommand(template string, forcePlus bool, background bo
return
}
command := replacePlaceholder(template, t.ansi, t.delimiter, forcePlus, string(t.input), list)
- cmd := util.ExecCommand(command)
+ cmd := util.ExecCommand(command, false)
if !background {
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
@@ -1381,6 +1383,20 @@ func (t *Terminal) toggleItem(item *Item) {
}
}
+func (t *Terminal) killPreview(code int) {
+ select {
+ case t.killChan <- code:
+ default:
+ if code != exitCancel {
+ os.Exit(code)
+ }
+ }
+}
+
+func (t *Terminal) cancelPreview() {
+ t.killPreview(exitCancel)
+}
+
// Loop is called to start Terminal I/O
func (t *Terminal) Loop() {
// prof := profile.Start(profile.ProfilePath("/tmp/"))
@@ -1458,15 +1474,43 @@ func (t *Terminal) Loop() {
if request[0] != nil {
command := replacePlaceholder(t.preview.command,
t.ansi, t.delimiter, false, string(t.input), request)
- cmd := util.ExecCommand(command)
+ cmd := util.ExecCommand(command, true)
if t.pwindow != nil {
env := os.Environ()
env = append(env, fmt.Sprintf("LINES=%d", t.pwindow.Height()))
env = append(env, fmt.Sprintf("COLUMNS=%d", t.pwindow.Width()))
cmd.Env = env
}
- out, _ := cmd.CombinedOutput()
- t.reqBox.Set(reqPreviewDisplay, string(out))
+ var out bytes.Buffer
+ cmd.Stdout = &out
+ cmd.Stderr = &out
+ cmd.Start()
+ finishChan := make(chan bool, 1)
+ updateChan := make(chan bool)
+ go func() {
+ select {
+ case code := <-t.killChan:
+ if code != exitCancel {
+ util.KillCommand(cmd)
+ os.Exit(code)
+ } else {
+ select {
+ case <-time.After(previewCancelWait):
+ util.KillCommand(cmd)
+ updateChan <- true
+ case <-finishChan:
+ updateChan <- false
+ }
+ }
+ case <-finishChan:
+ updateChan <- false
+ }
+ }()
+ cmd.Wait()
+ finishChan <- true
+ if out.Len() > 0 || !<-updateChan {
+ t.reqBox.Set(reqPreviewDisplay, string(out.Bytes()))
+ }
} else {
t.reqBox.Set(reqPreviewDisplay, "")
}
@@ -1484,7 +1528,7 @@ func (t *Terminal) Loop() {
t.history.append(string(t.input))
}
// prof.Stop()
- os.Exit(code)
+ t.killPreview(code)
}
go func() {
@@ -1511,6 +1555,7 @@ func (t *Terminal) Loop() {
focused = currentFocus
if t.isPreviewEnabled() {
_, list := t.buildPlusList(t.preview.command, false)
+ t.cancelPreview()
t.previewBox.Set(reqPreviewEnqueue, list)
}
}
@@ -1620,6 +1665,7 @@ func (t *Terminal) Loop() {
if t.previewer.enabled {
valid, list := t.buildPlusList(t.preview.command, false)
if valid {
+ t.cancelPreview()
t.previewBox.Set(reqPreviewEnqueue, list)
}
}
diff --git a/src/util/util_unix.go b/src/util/util_unix.go
index fc63c027..6331275c 100644
--- a/src/util/util_unix.go
+++ b/src/util/util_unix.go
@@ -9,17 +9,26 @@ import (
)
// ExecCommand executes the given command with $SHELL
-func ExecCommand(command string) *exec.Cmd {
+func ExecCommand(command string, setpgid bool) *exec.Cmd {
shell := os.Getenv("SHELL")
if len(shell) == 0 {
shell = "sh"
}
- return ExecCommandWith(shell, command)
+ return ExecCommandWith(shell, command, setpgid)
}
// ExecCommandWith executes the given command with the specified shell
-func ExecCommandWith(shell string, command string) *exec.Cmd {
- return exec.Command(shell, "-c", command)
+func ExecCommandWith(shell string, command string, setpgid bool) *exec.Cmd {
+ cmd := exec.Command(shell, "-c", command)
+ if setpgid {
+ cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
+ }
+ return cmd
+}
+
+// KillCommand kills the process for the given command
+func KillCommand(cmd *exec.Cmd) error {
+ return syscall.Kill(-cmd.Process.Pid, syscall.SIGKILL)
}
// IsWindows returns true on Windows
@@ -27,7 +36,7 @@ func IsWindows() bool {
return false
}
-// SetNonBlock executes syscall.SetNonblock on file descriptor
+// SetNonblock executes syscall.SetNonblock on file descriptor
func SetNonblock(file *os.File, nonblock bool) {
syscall.SetNonblock(int(file.Fd()), nonblock)
}
diff --git a/src/util/util_windows.go b/src/util/util_windows.go
index 41a9a5c7..51715cd8 100644
--- a/src/util/util_windows.go
+++ b/src/util/util_windows.go
@@ -10,13 +10,15 @@ import (
)
// ExecCommand executes the given command with cmd
-func ExecCommand(command string) *exec.Cmd {
+func ExecCommand(command string, setpgid bool) *exec.Cmd {
return ExecCommandWith("cmd", command)
}
// ExecCommandWith executes the given command with cmd. _shell parameter is
// ignored on Windows.
-func ExecCommandWith(_shell string, command string) *exec.Cmd {
+// FIXME: setpgid is unused. We set it in the Unix implementation so that we
+// can kill preview process with its child processes at once.
+func ExecCommandWith(_shell string, command string, setpgid bool) *exec.Cmd {
cmd := exec.Command("cmd")
cmd.SysProcAttr = &syscall.SysProcAttr{
HideWindow: false,
@@ -26,12 +28,17 @@ func ExecCommandWith(_shell string, command string) *exec.Cmd {
return cmd
}
+// KillCommand kills the process for the given command
+func KillCommand(cmd *exec.Cmd) error {
+ return cmd.Process.Kill()
+}
+
// IsWindows returns true on Windows
func IsWindows() bool {
return true
}
-// SetNonBlock executes syscall.SetNonblock on file descriptor
+// SetNonblock executes syscall.SetNonblock on file descriptor
func SetNonblock(file *os.File, nonblock bool) {
syscall.SetNonblock(syscall.Handle(file.Fd()), nonblock)
}