Kill running preview process after 500ms when focus has changed

Close #1383
Close #1384
This commit is contained in:
Junegunn Choi 2018-09-27 15:27:08 +09:00
parent 27c40dc6b0
commit 0d748a0699
No known key found for this signature in database
GPG Key ID: 254BC280FEF9C627
5 changed files with 82 additions and 18 deletions

View File

@ -22,10 +22,11 @@ const (
readerPollIntervalMax = 50 * time.Millisecond readerPollIntervalMax = 50 * time.Millisecond
// Terminal // Terminal
initialDelay = 20 * time.Millisecond initialDelay = 20 * time.Millisecond
initialDelayTac = 100 * time.Millisecond initialDelayTac = 100 * time.Millisecond
spinnerDuration = 200 * time.Millisecond spinnerDuration = 200 * time.Millisecond
maxPatternLength = 300 previewCancelWait = 500 * time.Millisecond
maxPatternLength = 300
// Matcher // Matcher
numPartitionsMultiplier = 8 numPartitionsMultiplier = 8
@ -76,6 +77,7 @@ const (
) )
const ( const (
exitCancel = -1
exitOk = 0 exitOk = 0
exitNoMatch = 1 exitNoMatch = 1
exitError = 2 exitError = 2

View File

@ -103,7 +103,7 @@ func (r *Reader) readFromStdin() bool {
} }
func (r *Reader) readFromCommand(shell string, cmd string) 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() out, err := listCommand.StdoutPipe()
if err != nil { if err != nil {
return false return false

View File

@ -114,6 +114,7 @@ type Terminal struct {
prevLines []itemLine prevLines []itemLine
suppress bool suppress bool
startChan chan bool startChan chan bool
killChan chan int
slab *util.Slab slab *util.Slab
theme *tui.ColorTheme theme *tui.ColorTheme
tui tui.Renderer tui tui.Renderer
@ -414,6 +415,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
slab: util.MakeSlab(slab16Size, slab32Size), slab: util.MakeSlab(slab16Size, slab32Size),
theme: opts.Theme, theme: opts.Theme,
startChan: make(chan bool, 1), startChan: make(chan bool, 1),
killChan: make(chan int),
tui: renderer, tui: renderer,
initFunc: func() { renderer.Init() }} initFunc: func() { renderer.Init() }}
t.prompt, t.promptLen = t.processTabs([]rune(opts.Prompt), 0) 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 return
} }
command := replacePlaceholder(template, t.ansi, t.delimiter, forcePlus, string(t.input), list) command := replacePlaceholder(template, t.ansi, t.delimiter, forcePlus, string(t.input), list)
cmd := util.ExecCommand(command) cmd := util.ExecCommand(command, false)
if !background { if !background {
cmd.Stdin = os.Stdin cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout 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 // Loop is called to start Terminal I/O
func (t *Terminal) Loop() { func (t *Terminal) Loop() {
// prof := profile.Start(profile.ProfilePath("/tmp/")) // prof := profile.Start(profile.ProfilePath("/tmp/"))
@ -1458,15 +1474,43 @@ func (t *Terminal) Loop() {
if request[0] != nil { if request[0] != nil {
command := replacePlaceholder(t.preview.command, command := replacePlaceholder(t.preview.command,
t.ansi, t.delimiter, false, string(t.input), request) t.ansi, t.delimiter, false, string(t.input), request)
cmd := util.ExecCommand(command) cmd := util.ExecCommand(command, true)
if t.pwindow != nil { if t.pwindow != nil {
env := os.Environ() env := os.Environ()
env = append(env, fmt.Sprintf("LINES=%d", t.pwindow.Height())) env = append(env, fmt.Sprintf("LINES=%d", t.pwindow.Height()))
env = append(env, fmt.Sprintf("COLUMNS=%d", t.pwindow.Width())) env = append(env, fmt.Sprintf("COLUMNS=%d", t.pwindow.Width()))
cmd.Env = env cmd.Env = env
} }
out, _ := cmd.CombinedOutput() var out bytes.Buffer
t.reqBox.Set(reqPreviewDisplay, string(out)) 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 { } else {
t.reqBox.Set(reqPreviewDisplay, "") t.reqBox.Set(reqPreviewDisplay, "")
} }
@ -1484,7 +1528,7 @@ func (t *Terminal) Loop() {
t.history.append(string(t.input)) t.history.append(string(t.input))
} }
// prof.Stop() // prof.Stop()
os.Exit(code) t.killPreview(code)
} }
go func() { go func() {
@ -1511,6 +1555,7 @@ func (t *Terminal) Loop() {
focused = currentFocus focused = currentFocus
if t.isPreviewEnabled() { if t.isPreviewEnabled() {
_, list := t.buildPlusList(t.preview.command, false) _, list := t.buildPlusList(t.preview.command, false)
t.cancelPreview()
t.previewBox.Set(reqPreviewEnqueue, list) t.previewBox.Set(reqPreviewEnqueue, list)
} }
} }
@ -1620,6 +1665,7 @@ func (t *Terminal) Loop() {
if t.previewer.enabled { if t.previewer.enabled {
valid, list := t.buildPlusList(t.preview.command, false) valid, list := t.buildPlusList(t.preview.command, false)
if valid { if valid {
t.cancelPreview()
t.previewBox.Set(reqPreviewEnqueue, list) t.previewBox.Set(reqPreviewEnqueue, list)
} }
} }

View File

@ -9,17 +9,26 @@ import (
) )
// ExecCommand executes the given command with $SHELL // 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") shell := os.Getenv("SHELL")
if len(shell) == 0 { if len(shell) == 0 {
shell = "sh" shell = "sh"
} }
return ExecCommandWith(shell, command) return ExecCommandWith(shell, command, setpgid)
} }
// ExecCommandWith executes the given command with the specified shell // ExecCommandWith executes the given command with the specified shell
func ExecCommandWith(shell string, command string) *exec.Cmd { func ExecCommandWith(shell string, command string, setpgid bool) *exec.Cmd {
return exec.Command(shell, "-c", command) 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 // IsWindows returns true on Windows
@ -27,7 +36,7 @@ func IsWindows() bool {
return false return false
} }
// SetNonBlock executes syscall.SetNonblock on file descriptor // SetNonblock executes syscall.SetNonblock on file descriptor
func SetNonblock(file *os.File, nonblock bool) { func SetNonblock(file *os.File, nonblock bool) {
syscall.SetNonblock(int(file.Fd()), nonblock) syscall.SetNonblock(int(file.Fd()), nonblock)
} }

View File

@ -10,13 +10,15 @@ import (
) )
// ExecCommand executes the given command with cmd // 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) return ExecCommandWith("cmd", command)
} }
// ExecCommandWith executes the given command with cmd. _shell parameter is // ExecCommandWith executes the given command with cmd. _shell parameter is
// ignored on Windows. // 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 := exec.Command("cmd")
cmd.SysProcAttr = &syscall.SysProcAttr{ cmd.SysProcAttr = &syscall.SysProcAttr{
HideWindow: false, HideWindow: false,
@ -26,12 +28,17 @@ func ExecCommandWith(_shell string, command string) *exec.Cmd {
return 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 // IsWindows returns true on Windows
func IsWindows() bool { func IsWindows() bool {
return true return true
} }
// SetNonBlock executes syscall.SetNonblock on file descriptor // SetNonblock executes syscall.SetNonblock on file descriptor
func SetNonblock(file *os.File, nonblock bool) { func SetNonblock(file *os.File, nonblock bool) {
syscall.SetNonblock(syscall.Handle(file.Fd()), nonblock) syscall.SetNonblock(syscall.Handle(file.Fd()), nonblock)
} }