mirror of
https://github.com/junegunn/fzf.git
synced 2024-09-11 14:35:26 +03:00
Make transform*, --info-command, and execute-silent cancellable
Users can press CTRL-C after 1 second to terminate the command. Close #3883
This commit is contained in:
parent
db01e7dab6
commit
7c2ffd3fef
@ -457,6 +457,8 @@ Determines the display style of the finder info. (e.g. match counter, loading in
|
|||||||
Command to generate the finder info line. The command runs synchronously and
|
Command to generate the finder info line. The command runs synchronously and
|
||||||
blocks the UI until completion, so make sure that it's fast. ANSI color codes
|
blocks the UI until completion, so make sure that it's fast. ANSI color codes
|
||||||
are supported. \fB$FZF_INFO\fR variable is set to the original info text.
|
are supported. \fB$FZF_INFO\fR variable is set to the original info text.
|
||||||
|
For additional environment variables available to the command, see the section
|
||||||
|
ENVIRONMENT VARIABLES EXPORTED TO CHILD PROCESSES.
|
||||||
|
|
||||||
e.g.
|
e.g.
|
||||||
\fB# Prepend the current cursor position in yellow
|
\fB# Prepend the current cursor position in yellow
|
||||||
|
@ -15,6 +15,7 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -58,6 +59,10 @@ const clearCode string = "\x1b[2J"
|
|||||||
// Number of maximum focus events to process synchronously
|
// Number of maximum focus events to process synchronously
|
||||||
const maxFocusEvents = 10000
|
const maxFocusEvents = 10000
|
||||||
|
|
||||||
|
// execute-silent and transform* actions will block user input for this duration.
|
||||||
|
// After this duration, users can press CTRL-C to terminate the command.
|
||||||
|
const blockDuration = 1 * time.Second
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
placeholder = regexp.MustCompile(`\\?(?:{[+sf]*[0-9,-.]*}|{q}|{fzf:(?:query|action|prompt)}|{\+?f?nf?})`)
|
placeholder = regexp.MustCompile(`\\?(?:{[+sf]*[0-9,-.]*}|{q}|{fzf:(?:query|action|prompt)}|{\+?f?nf?})`)
|
||||||
whiteSuffix = regexp.MustCompile(`\s*$`)
|
whiteSuffix = regexp.MustCompile(`\s*$`)
|
||||||
@ -1792,20 +1797,8 @@ func (t *Terminal) printInfo() {
|
|||||||
t.window.Print(strings.Repeat(" ", fillLength+1))
|
t.window.Print(strings.Repeat(" ", fillLength+1))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
switch t.infoStyle {
|
|
||||||
case infoDefault:
|
if t.infoStyle == infoHidden {
|
||||||
move(line+1, 0, t.separatorLen == 0)
|
|
||||||
printSpinner()
|
|
||||||
t.window.Print(" ") // Margin
|
|
||||||
pos = 2
|
|
||||||
case infoRight:
|
|
||||||
move(line+1, 0, false)
|
|
||||||
case infoInlineRight:
|
|
||||||
pos = t.promptLen + t.queryLen[0] + t.queryLen[1] + 1
|
|
||||||
case infoInline:
|
|
||||||
pos = t.promptLen + t.queryLen[0] + t.queryLen[1] + 1
|
|
||||||
printInfoPrefix()
|
|
||||||
case infoHidden:
|
|
||||||
if t.separatorLen > 0 {
|
if t.separatorLen > 0 {
|
||||||
move(line+1, 0, false)
|
move(line+1, 0, false)
|
||||||
printSeparator(t.window.Width()-1, false)
|
printSeparator(t.window.Width()-1, false)
|
||||||
@ -1849,6 +1842,21 @@ func (t *Terminal) printInfo() {
|
|||||||
outputPrinter, outputLen = t.ansiLabelPrinter(output, &tui.ColInfo, false)
|
outputPrinter, outputLen = t.ansiLabelPrinter(output, &tui.ColInfo, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
switch t.infoStyle {
|
||||||
|
case infoDefault:
|
||||||
|
move(line+1, 0, t.separatorLen == 0)
|
||||||
|
printSpinner()
|
||||||
|
t.window.Print(" ") // Margin
|
||||||
|
pos = 2
|
||||||
|
case infoRight:
|
||||||
|
move(line+1, 0, false)
|
||||||
|
case infoInlineRight:
|
||||||
|
pos = t.promptLen + t.queryLen[0] + t.queryLen[1] + 1
|
||||||
|
case infoInline:
|
||||||
|
pos = t.promptLen + t.queryLen[0] + t.queryLen[1] + 1
|
||||||
|
printInfoPrefix()
|
||||||
|
}
|
||||||
|
|
||||||
if t.infoStyle == infoRight {
|
if t.infoStyle == infoRight {
|
||||||
maxWidth := t.window.Width()
|
maxWidth := t.window.Width()
|
||||||
if t.reading {
|
if t.reading {
|
||||||
@ -3055,6 +3063,7 @@ func (t *Terminal) executeCommand(template string, forcePlus bool, background bo
|
|||||||
cmd.Run()
|
cmd.Run()
|
||||||
t.tui.Resume(true, false)
|
t.tui.Resume(true, false)
|
||||||
t.mutex.Lock()
|
t.mutex.Lock()
|
||||||
|
// NOTE: Using t.reqBox.Set(reqFullRedraw...) instead can cause a deadlock
|
||||||
t.fullRedraw()
|
t.fullRedraw()
|
||||||
t.flush()
|
t.flush()
|
||||||
} else {
|
} else {
|
||||||
@ -3062,6 +3071,18 @@ func (t *Terminal) executeCommand(template string, forcePlus bool, background bo
|
|||||||
if len(info) == 0 {
|
if len(info) == 0 {
|
||||||
t.uiMutex.Lock()
|
t.uiMutex.Lock()
|
||||||
}
|
}
|
||||||
|
paused := atomic.Int32{}
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
go func() {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
|
case <-time.After(blockDuration):
|
||||||
|
if paused.CompareAndSwap(0, 1) {
|
||||||
|
t.tui.Pause(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
if capture {
|
if capture {
|
||||||
out, _ := cmd.StdoutPipe()
|
out, _ := cmd.StdoutPipe()
|
||||||
reader := bufio.NewReader(out)
|
reader := bufio.NewReader(out)
|
||||||
@ -3077,7 +3098,20 @@ func (t *Terminal) executeCommand(template string, forcePlus bool, background bo
|
|||||||
} else {
|
} else {
|
||||||
cmd.Run()
|
cmd.Run()
|
||||||
}
|
}
|
||||||
|
cancel()
|
||||||
|
if paused.CompareAndSwap(1, 2) {
|
||||||
|
t.tui.Resume(false, false)
|
||||||
|
}
|
||||||
t.mutex.Lock()
|
t.mutex.Lock()
|
||||||
|
|
||||||
|
// Redraw prompt in case the user has typed something after blockDuration
|
||||||
|
if paused.Load() > 0 {
|
||||||
|
// NOTE: Using t.reqBox.Set(reqXXX...) instead can cause a deadlock
|
||||||
|
t.printPrompt()
|
||||||
|
if t.infoStyle == infoInline || t.infoStyle == infoInlineRight {
|
||||||
|
t.printInfo()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if len(info) == 0 {
|
if len(info) == 0 {
|
||||||
t.uiMutex.Unlock()
|
t.uiMutex.Unlock()
|
||||||
@ -3300,11 +3334,11 @@ func (t *Terminal) Loop() error {
|
|||||||
t.termSize = t.tui.Size()
|
t.termSize = t.tui.Size()
|
||||||
t.resizeWindows(false)
|
t.resizeWindows(false)
|
||||||
t.window.Erase()
|
t.window.Erase()
|
||||||
t.printPrompt()
|
|
||||||
t.printInfo()
|
|
||||||
t.printHeader()
|
|
||||||
t.flush()
|
|
||||||
t.mutex.Unlock()
|
t.mutex.Unlock()
|
||||||
|
|
||||||
|
t.reqBox.Set(reqPrompt, nil)
|
||||||
|
t.reqBox.Set(reqInfo, nil)
|
||||||
|
t.reqBox.Set(reqHeader, nil)
|
||||||
if t.initDelay > 0 {
|
if t.initDelay > 0 {
|
||||||
go func() {
|
go func() {
|
||||||
timer := time.NewTimer(t.initDelay)
|
timer := time.NewTimer(t.initDelay)
|
||||||
@ -3530,6 +3564,14 @@ func (t *Terminal) Loop() error {
|
|||||||
t.reqBox.Wait(func(events *util.Events) {
|
t.reqBox.Wait(func(events *util.Events) {
|
||||||
defer events.Clear()
|
defer events.Clear()
|
||||||
|
|
||||||
|
// Sort events.
|
||||||
|
// e.g. Make sure that reqPrompt is processed before reqInfo
|
||||||
|
keys := make([]int, 0, len(*events))
|
||||||
|
for key := range *events {
|
||||||
|
keys = append(keys, int(key))
|
||||||
|
}
|
||||||
|
sort.Ints(keys)
|
||||||
|
|
||||||
// t.uiMutex must be locked first to avoid deadlock. Execute actions
|
// t.uiMutex must be locked first to avoid deadlock. Execute actions
|
||||||
// will 1. unlock t.mutex to allow GET endpoint and 2. lock t.uiMutex
|
// will 1. unlock t.mutex to allow GET endpoint and 2. lock t.uiMutex
|
||||||
// to block rendering during the execution.
|
// to block rendering during the execution.
|
||||||
@ -3547,33 +3589,36 @@ func (t *Terminal) Loop() error {
|
|||||||
// U t.uiMutex |
|
// U t.uiMutex |
|
||||||
t.uiMutex.Lock()
|
t.uiMutex.Lock()
|
||||||
t.mutex.Lock()
|
t.mutex.Lock()
|
||||||
for req, value := range *events {
|
printInfo := util.RunOnce(t.printInfo)
|
||||||
|
for _, key := range keys {
|
||||||
|
req := util.EventType(key)
|
||||||
|
value := (*events)[req]
|
||||||
switch req {
|
switch req {
|
||||||
case reqPrompt:
|
case reqPrompt:
|
||||||
t.printPrompt()
|
t.printPrompt()
|
||||||
if t.infoStyle == infoInline || t.infoStyle == infoInlineRight {
|
if t.infoStyle == infoInline || t.infoStyle == infoInlineRight {
|
||||||
t.printInfo()
|
printInfo()
|
||||||
}
|
}
|
||||||
case reqInfo:
|
case reqInfo:
|
||||||
t.printInfo()
|
printInfo()
|
||||||
case reqList:
|
case reqList:
|
||||||
t.printList()
|
t.printList()
|
||||||
currentIndex := t.currentIndex()
|
currentIndex := t.currentIndex()
|
||||||
focusChanged := focusedIndex != currentIndex
|
focusChanged := focusedIndex != currentIndex
|
||||||
printInfo := false
|
info := false
|
||||||
if focusChanged && t.track == trackCurrent {
|
if focusChanged && t.track == trackCurrent {
|
||||||
t.track = trackDisabled
|
t.track = trackDisabled
|
||||||
printInfo = true
|
info = true
|
||||||
}
|
}
|
||||||
if (t.hasFocusActions || t.infoCommand != "") && focusChanged && currentIndex != t.lastFocus {
|
if (t.hasFocusActions || t.infoCommand != "") && focusChanged && currentIndex != t.lastFocus {
|
||||||
t.lastFocus = currentIndex
|
t.lastFocus = currentIndex
|
||||||
t.eventChan <- tui.Focus.AsEvent()
|
t.eventChan <- tui.Focus.AsEvent()
|
||||||
if t.infoCommand != "" {
|
if t.infoCommand != "" {
|
||||||
printInfo = true
|
info = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if printInfo {
|
if info {
|
||||||
t.printInfo()
|
printInfo()
|
||||||
}
|
}
|
||||||
if focusChanged || version != t.version {
|
if focusChanged || version != t.version {
|
||||||
version = t.version
|
version = t.version
|
||||||
|
@ -144,12 +144,22 @@ func IsTty(file *os.File) bool {
|
|||||||
return isatty.IsTerminal(fd) || isatty.IsCygwinTerminal(fd)
|
return isatty.IsTerminal(fd) || isatty.IsCygwinTerminal(fd)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RunOnce runs the given function only once
|
||||||
|
func RunOnce(f func()) func() {
|
||||||
|
once := Once(true)
|
||||||
|
return func() {
|
||||||
|
if once() {
|
||||||
|
f()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Once returns a function that returns the specified boolean value only once
|
// Once returns a function that returns the specified boolean value only once
|
||||||
func Once(nextResponse bool) func() bool {
|
func Once(nextResponse bool) func() bool {
|
||||||
state := nextResponse
|
state := nextResponse
|
||||||
return func() bool {
|
return func() bool {
|
||||||
prevState := state
|
prevState := state
|
||||||
state = false
|
state = !nextResponse
|
||||||
return prevState
|
return prevState
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -137,8 +137,11 @@ func TestOnce(t *testing.T) {
|
|||||||
if o() {
|
if o() {
|
||||||
t.Error("Expected: false")
|
t.Error("Expected: false")
|
||||||
}
|
}
|
||||||
if o() {
|
if !o() {
|
||||||
t.Error("Expected: false")
|
t.Error("Expected: true")
|
||||||
|
}
|
||||||
|
if !o() {
|
||||||
|
t.Error("Expected: true")
|
||||||
}
|
}
|
||||||
|
|
||||||
o = Once(true)
|
o = Once(true)
|
||||||
@ -148,6 +151,9 @@ func TestOnce(t *testing.T) {
|
|||||||
if o() {
|
if o() {
|
||||||
t.Error("Expected: false")
|
t.Error("Expected: false")
|
||||||
}
|
}
|
||||||
|
if o() {
|
||||||
|
t.Error("Expected: false")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRunesWidth(t *testing.T) {
|
func TestRunesWidth(t *testing.T) {
|
||||||
|
Loading…
Reference in New Issue
Block a user