Debounce visual feedback
CodeQL / Analyze (go) (push) Has been cancelled
build / build (push) Has been cancelled
Test fzf on macOS / build (push) Has been cancelled

This commit is contained in:
Junegunn Choi
2026-06-26 17:02:37 +09:00
parent eb487035f1
commit 57d1a3d237
+32 -10
View File
@@ -322,6 +322,7 @@ type Terminal struct {
trackSync bool trackSync bool
trackKeyCache map[int32]bool trackKeyCache map[int32]bool
waitBlocked bool waitBlocked bool
waitBlockedAt time.Time
pendingActions []*action pendingActions []*action
searchInProgress bool searchInProgress bool
pendingSelections map[string]selectedItem pendingSelections map[string]selectedItem
@@ -3274,7 +3275,7 @@ func (t *Terminal) printPrompt() {
color := tui.ColInput color := tui.ColInput
if t.paused { if t.paused {
color = tui.ColDisabled color = tui.ColDisabled
} else if t.trackBlocked || t.waitBlocked { } else if t.trackBlocked || t.waitFeedback() {
color = color.WithAttr(tui.Dim) color = color.WithAttr(tui.Dim)
} }
w.CPrint(color, string(before)) w.CPrint(color, string(before))
@@ -3366,9 +3367,6 @@ func (t *Terminal) printInfoImpl() {
output += fmt.Sprintf(" (%d/%d)", len(t.selected), t.multi) output += fmt.Sprintf(" (%d/%d)", len(t.selected), t.multi)
} }
} }
if t.progress > 0 && t.progress < 100 {
output += fmt.Sprintf(" (%d%%)", t.progress)
}
if t.toggleSort { if t.toggleSort {
if t.sort { if t.sort {
output += " +S" output += " +S"
@@ -3389,9 +3387,14 @@ func (t *Terminal) printInfoImpl() {
output += " +t" output += " +t"
} }
} }
if t.waitBlocked { if t.waitFeedback() {
output += " (..)" output += " (..)"
} }
// Keep the search progress at the end so the other indicators don't shift
// as it appears and disappears.
if t.progress > 0 && t.progress < 100 {
output += fmt.Sprintf(" (%d%%)", t.progress)
}
if t.failed != nil && t.count == 0 { if t.failed != nil && t.count == 0 {
output = fmt.Sprintf("[Command failed: %s]", *t.failed) output = fmt.Sprintf("[Command failed: %s]", *t.failed)
} }
@@ -5804,9 +5807,14 @@ func (t *Terminal) unblockTrack() {
} }
} }
// Debounce visual feedback so quick searches don't cause flashing
func (t *Terminal) waitFeedback() bool {
return t.waitBlocked && time.Since(t.waitBlockedAt) > progressMinDuration
}
func (t *Terminal) unblockWait() { func (t *Terminal) unblockWait() {
t.waitBlocked = false t.waitBlocked = false
// Only show cursor if not blocked by tracking // Restore the cursor unless it's still hidden for another reason
if !t.inputless && !t.trackBlocked { if !t.inputless && !t.trackBlocked {
t.tui.ShowCursor() t.tui.ShowCursor()
} }
@@ -6437,6 +6445,10 @@ func (t *Terminal) Loop() error {
t.printFooter() t.printFooter()
} }
} }
// Hide the cursor while the debounced waiting feedback is shown
if !t.inputless && !t.trackBlocked && t.waitFeedback() {
t.tui.HideCursor()
}
t.flush() t.flush()
t.mutex.Unlock() t.mutex.Unlock()
t.uiMutex.Unlock() t.uiMutex.Unlock()
@@ -6646,11 +6658,21 @@ func (t *Terminal) Loop() error {
// Block if search is in progress or will be triggered // Block if search is in progress or will be triggered
if changed || newCommand != nil || t.searchInProgress { if changed || newCommand != nil || t.searchInProgress {
t.waitBlocked = true t.waitBlocked = true
t.waitBlockedAt = time.Now()
t.pendingActions = actions[i+1:] t.pendingActions = actions[i+1:]
if !t.inputless { // Show the waiting feedback only if the search takes long enough,
t.tui.HideCursor() // so that quick searches don't cause flickering.
} go func() {
req(reqPrompt, reqInfo) timer := time.NewTimer(progressMinDuration)
<-timer.C
t.mutex.Lock()
blocked := t.waitBlocked
t.mutex.Unlock()
if blocked {
t.reqBox.Set(reqPrompt, nil)
t.reqBox.Set(reqInfo, nil)
}
}()
return true return true
} }
// No search, wait is a no-op; continue to next action // No search, wait is a no-op; continue to next action