mirror of
https://github.com/junegunn/fzf.git
synced 2026-07-09 18:19:13 +08:00
Group wait state into waitState struct
- All wait state under t.wait.* - State machine as named methods: blockWait, unblockWait, cancelWait, waitFeedback, with the invariant documented above them
This commit is contained in:
+73
-50
@@ -140,6 +140,13 @@ type quitSignal struct {
|
||||
err error
|
||||
}
|
||||
|
||||
type waitState struct {
|
||||
blocked bool
|
||||
blockedAt time.Time
|
||||
pending []*action
|
||||
searching bool // a search is in progress or the input is still loading
|
||||
}
|
||||
|
||||
type previewer struct {
|
||||
version int64
|
||||
lines []string
|
||||
@@ -321,10 +328,7 @@ type Terminal struct {
|
||||
trackBlocked bool
|
||||
trackSync bool
|
||||
trackKeyCache map[int32]bool
|
||||
waitBlocked bool
|
||||
waitBlockedAt time.Time
|
||||
pendingActions []*action
|
||||
searchInProgress bool
|
||||
wait waitState
|
||||
pendingSelections map[string]selectedItem
|
||||
targetIndex int32
|
||||
delimiter Delimiter
|
||||
@@ -1171,8 +1175,8 @@ func NewTerminal(opts *Options, eventBox *util.EventBox, executor *util.Executor
|
||||
lastActivity: time.Now(),
|
||||
// The initial load counts as a search in progress ('start:wait').
|
||||
// Set before the reader starts so the first final result clears it.
|
||||
searchInProgress: true,
|
||||
numLinesCache: make(map[int32]numLinesCacheValue)}
|
||||
wait: waitState{searching: true},
|
||||
numLinesCache: make(map[int32]numLinesCacheValue)}
|
||||
if opts.AcceptNth != nil {
|
||||
t.acceptNth = opts.AcceptNth(t.delimiter)
|
||||
}
|
||||
@@ -1884,19 +1888,19 @@ func (t *Terminal) UpdateProgress(progress float32) {
|
||||
func (t *Terminal) UpdateList(result MatchResult) {
|
||||
merger := result.merger
|
||||
t.mutex.Lock()
|
||||
waitWasBlocked := t.waitBlocked
|
||||
waitWasBlocked := t.wait.blocked
|
||||
wakeUp := false
|
||||
if result.final() {
|
||||
t.searchInProgress = false
|
||||
t.wait.searching = false
|
||||
// If waiting, unblock so main loop can execute pending actions.
|
||||
// Note: any final result unblocks the wait, not just the one for the
|
||||
// search that armed it. Back-to-back searches (--listen, bg-transform
|
||||
// callbacks, reload+wait) can therefore unblock early and run the
|
||||
// pending actions on the previous result set. Accepted; a per-search
|
||||
// generation token through the matcher isn't worth the complexity.
|
||||
if t.waitBlocked {
|
||||
if t.wait.blocked {
|
||||
t.unblockWait()
|
||||
wakeUp = len(t.pendingActions) > 0
|
||||
wakeUp = len(t.wait.pending) > 0
|
||||
}
|
||||
}
|
||||
prevIndex := minItem.Index()
|
||||
@@ -2064,7 +2068,7 @@ func (t *Terminal) UpdateList(result MatchResult) {
|
||||
}
|
||||
}
|
||||
updateList := !t.trackBlocked && !t.pendingReqList
|
||||
updatePrompt := (trackWasBlocked && !t.trackBlocked) || (waitWasBlocked && !t.waitBlocked)
|
||||
updatePrompt := (trackWasBlocked && !t.trackBlocked) || (waitWasBlocked && !t.wait.blocked)
|
||||
t.mutex.Unlock()
|
||||
|
||||
// Wake up the main loop to execute pending actions after wait unblocks.
|
||||
@@ -5825,19 +5829,56 @@ 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
|
||||
// The wait state machine. Invariant: arming captures every action after
|
||||
// 'wait' at any nesting level into wait.pending; while blocked, actions are
|
||||
// dropped unless they are results of work started before the block
|
||||
// (bg-transform callbacks, bracketed paste bookkeeping); abort/cancel
|
||||
// discards everything.
|
||||
|
||||
// blockWait blocks action execution and defers the given actions until the
|
||||
// current search completes (see UpdateList)
|
||||
func (t *Terminal) blockWait(pending []*action) {
|
||||
t.wait.blocked = true
|
||||
t.wait.blockedAt = time.Now()
|
||||
// Clone so that later joins don't append into the backing array of the
|
||||
// bound action list
|
||||
t.wait.pending = slices.Clone(pending)
|
||||
// Show the waiting feedback only if the search takes long enough,
|
||||
// so that quick searches don't cause flickering
|
||||
go func() {
|
||||
timer := time.NewTimer(progressMinDuration)
|
||||
<-timer.C
|
||||
t.mutex.Lock()
|
||||
blocked := t.wait.blocked
|
||||
t.mutex.Unlock()
|
||||
if blocked {
|
||||
t.reqBox.Set(reqPrompt, nil)
|
||||
t.reqBox.Set(reqInfo, nil)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// unblockWait lifts the block, leaving the pending actions to the caller:
|
||||
// UpdateList keeps them for the main loop to replay, cancelWait discards them
|
||||
func (t *Terminal) unblockWait() {
|
||||
t.waitBlocked = false
|
||||
t.wait.blocked = false
|
||||
// Restore the cursor unless it's still hidden for another reason
|
||||
if !t.inputless && !t.trackBlocked {
|
||||
t.tui.ShowCursor()
|
||||
}
|
||||
}
|
||||
|
||||
// cancelWait unblocks and discards the pending actions (user abort)
|
||||
func (t *Terminal) cancelWait() {
|
||||
t.unblockWait()
|
||||
t.wait.pending = nil
|
||||
}
|
||||
|
||||
// Debounce visual feedback so quick searches don't cause flashing
|
||||
func (t *Terminal) waitFeedback() bool {
|
||||
return t.wait.blocked && time.Since(t.wait.blockedAt) > progressMinDuration
|
||||
}
|
||||
|
||||
func (t *Terminal) addClickHeaderWord(env []string) []string {
|
||||
/*
|
||||
* echo $'HL1\nHL2' | fzf --header-lines 3 --header $'H1\nH2' --header-lines-border --bind 'click-header:preview:env | grep FZF_CLICK'
|
||||
@@ -6684,45 +6725,28 @@ func (t *Terminal) Loop() error {
|
||||
// Already waiting. Actions parsed from a bg-transform
|
||||
// result join the current wait; user input can't reset
|
||||
// the blocked state
|
||||
if t.waitBlocked {
|
||||
if t.wait.blocked {
|
||||
if inBgCallback {
|
||||
t.pendingActions = append(t.pendingActions, actions[i+1:]...)
|
||||
t.wait.pending = append(t.wait.pending, actions[i+1:]...)
|
||||
}
|
||||
return true
|
||||
}
|
||||
// Block if search is in progress or will be triggered
|
||||
if changed || newCommand != nil || t.searchInProgress || queryBefore != string(t.input) {
|
||||
t.waitBlocked = true
|
||||
t.waitBlockedAt = time.Now()
|
||||
// Clone so that later joins don't append into the
|
||||
// backing array of the bound action list
|
||||
t.pendingActions = slices.Clone(actions[i+1:])
|
||||
// Show the waiting feedback only if the search takes long enough,
|
||||
// so that quick searches don't cause flickering.
|
||||
go func() {
|
||||
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)
|
||||
}
|
||||
}()
|
||||
if changed || newCommand != nil || t.wait.searching || queryBefore != string(t.input) {
|
||||
t.blockWait(actions[i+1:])
|
||||
return true
|
||||
}
|
||||
// No search, wait is a no-op; continue to next action
|
||||
continue
|
||||
}
|
||||
blockedBefore := t.waitBlocked
|
||||
blockedBefore := t.wait.blocked
|
||||
if !doAction(action) {
|
||||
return false
|
||||
}
|
||||
// If this action armed the wait through a nested list
|
||||
// (e.g. via trigger), defer the rest of this list too
|
||||
if !blockedBefore && t.waitBlocked {
|
||||
t.pendingActions = append(t.pendingActions, actions[i+1:]...)
|
||||
if !blockedBefore && t.wait.blocked {
|
||||
t.wait.pending = append(t.wait.pending, actions[i+1:]...)
|
||||
return true
|
||||
}
|
||||
// A terminal action performed. We should stop processing more.
|
||||
@@ -6777,10 +6801,9 @@ func (t *Terminal) Loop() error {
|
||||
passthrough := inBgCallback || a.t == actAsync ||
|
||||
a.t == actBracketedPasteBegin || a.t == actBracketedPasteEnd
|
||||
// When wait-blocked, only allow abort/cancel
|
||||
if t.waitBlocked && !passthrough {
|
||||
if t.wait.blocked && !passthrough {
|
||||
if a.t == actAbort || a.t == actCancel {
|
||||
t.unblockWait()
|
||||
t.pendingActions = nil
|
||||
t.cancelWait()
|
||||
req(reqPrompt, reqInfo)
|
||||
}
|
||||
return true
|
||||
@@ -7673,7 +7696,7 @@ func (t *Terminal) Loop() error {
|
||||
req(reqPrompt)
|
||||
case actTrigger:
|
||||
if _, chords, err := parseKeyChords(a.a, ""); err == nil {
|
||||
blockedBefore := t.waitBlocked
|
||||
blockedBefore := t.wait.blocked
|
||||
for ci, chord := range chords {
|
||||
if _, prs := triggering[chord]; prs {
|
||||
// Avoid recursive triggering
|
||||
@@ -7685,14 +7708,14 @@ func (t *Terminal) Loop() error {
|
||||
delete(triggering, chord)
|
||||
}
|
||||
// If this chord armed the wait, defer the remaining chords
|
||||
if !blockedBefore && t.waitBlocked {
|
||||
if !blockedBefore && t.wait.blocked {
|
||||
for _, rest := range chords[ci+1:] {
|
||||
if _, prs := triggering[rest]; prs {
|
||||
// Avoid recursive triggering
|
||||
continue
|
||||
}
|
||||
if acts, prs := t.keymap[rest]; prs {
|
||||
t.pendingActions = append(t.pendingActions, acts...)
|
||||
t.wait.pending = append(t.wait.pending, acts...)
|
||||
}
|
||||
}
|
||||
break
|
||||
@@ -8214,10 +8237,10 @@ func (t *Terminal) Loop() error {
|
||||
// state first so that a pending 'jump' action isn't cancelled right
|
||||
// away by the wake-up event below.
|
||||
jumpingBefore := t.jumping
|
||||
if len(t.pendingActions) > 0 && !t.waitBlocked {
|
||||
pendingActions := t.pendingActions
|
||||
t.pendingActions = nil
|
||||
if !doActions(pendingActions) {
|
||||
if len(t.wait.pending) > 0 && !t.wait.blocked {
|
||||
pending := t.wait.pending
|
||||
t.wait.pending = nil
|
||||
if !doActions(pending) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
@@ -8284,7 +8307,7 @@ func (t *Terminal) Loop() error {
|
||||
|
||||
reload := changed || newCommand != nil
|
||||
if reload {
|
||||
t.searchInProgress = true
|
||||
t.wait.searching = true
|
||||
}
|
||||
var reloadRequest *searchRequest
|
||||
if reload {
|
||||
|
||||
Reference in New Issue
Block a user