mirror of
https://github.com/junegunn/fzf.git
synced 2026-08-02 22:18:35 +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
|
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 {
|
type previewer struct {
|
||||||
version int64
|
version int64
|
||||||
lines []string
|
lines []string
|
||||||
@@ -321,10 +328,7 @@ type Terminal struct {
|
|||||||
trackBlocked bool
|
trackBlocked bool
|
||||||
trackSync bool
|
trackSync bool
|
||||||
trackKeyCache map[int32]bool
|
trackKeyCache map[int32]bool
|
||||||
waitBlocked bool
|
wait waitState
|
||||||
waitBlockedAt time.Time
|
|
||||||
pendingActions []*action
|
|
||||||
searchInProgress bool
|
|
||||||
pendingSelections map[string]selectedItem
|
pendingSelections map[string]selectedItem
|
||||||
targetIndex int32
|
targetIndex int32
|
||||||
delimiter Delimiter
|
delimiter Delimiter
|
||||||
@@ -1171,8 +1175,8 @@ func NewTerminal(opts *Options, eventBox *util.EventBox, executor *util.Executor
|
|||||||
lastActivity: time.Now(),
|
lastActivity: time.Now(),
|
||||||
// The initial load counts as a search in progress ('start:wait').
|
// The initial load counts as a search in progress ('start:wait').
|
||||||
// Set before the reader starts so the first final result clears it.
|
// Set before the reader starts so the first final result clears it.
|
||||||
searchInProgress: true,
|
wait: waitState{searching: true},
|
||||||
numLinesCache: make(map[int32]numLinesCacheValue)}
|
numLinesCache: make(map[int32]numLinesCacheValue)}
|
||||||
if opts.AcceptNth != nil {
|
if opts.AcceptNth != nil {
|
||||||
t.acceptNth = opts.AcceptNth(t.delimiter)
|
t.acceptNth = opts.AcceptNth(t.delimiter)
|
||||||
}
|
}
|
||||||
@@ -1884,19 +1888,19 @@ func (t *Terminal) UpdateProgress(progress float32) {
|
|||||||
func (t *Terminal) UpdateList(result MatchResult) {
|
func (t *Terminal) UpdateList(result MatchResult) {
|
||||||
merger := result.merger
|
merger := result.merger
|
||||||
t.mutex.Lock()
|
t.mutex.Lock()
|
||||||
waitWasBlocked := t.waitBlocked
|
waitWasBlocked := t.wait.blocked
|
||||||
wakeUp := false
|
wakeUp := false
|
||||||
if result.final() {
|
if result.final() {
|
||||||
t.searchInProgress = false
|
t.wait.searching = false
|
||||||
// If waiting, unblock so main loop can execute pending actions.
|
// If waiting, unblock so main loop can execute pending actions.
|
||||||
// Note: any final result unblocks the wait, not just the one for the
|
// Note: any final result unblocks the wait, not just the one for the
|
||||||
// search that armed it. Back-to-back searches (--listen, bg-transform
|
// search that armed it. Back-to-back searches (--listen, bg-transform
|
||||||
// callbacks, reload+wait) can therefore unblock early and run the
|
// callbacks, reload+wait) can therefore unblock early and run the
|
||||||
// pending actions on the previous result set. Accepted; a per-search
|
// pending actions on the previous result set. Accepted; a per-search
|
||||||
// generation token through the matcher isn't worth the complexity.
|
// generation token through the matcher isn't worth the complexity.
|
||||||
if t.waitBlocked {
|
if t.wait.blocked {
|
||||||
t.unblockWait()
|
t.unblockWait()
|
||||||
wakeUp = len(t.pendingActions) > 0
|
wakeUp = len(t.wait.pending) > 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
prevIndex := minItem.Index()
|
prevIndex := minItem.Index()
|
||||||
@@ -2064,7 +2068,7 @@ func (t *Terminal) UpdateList(result MatchResult) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
updateList := !t.trackBlocked && !t.pendingReqList
|
updateList := !t.trackBlocked && !t.pendingReqList
|
||||||
updatePrompt := (trackWasBlocked && !t.trackBlocked) || (waitWasBlocked && !t.waitBlocked)
|
updatePrompt := (trackWasBlocked && !t.trackBlocked) || (waitWasBlocked && !t.wait.blocked)
|
||||||
t.mutex.Unlock()
|
t.mutex.Unlock()
|
||||||
|
|
||||||
// Wake up the main loop to execute pending actions after wait unblocks.
|
// 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
|
// The wait state machine. Invariant: arming captures every action after
|
||||||
func (t *Terminal) waitFeedback() bool {
|
// 'wait' at any nesting level into wait.pending; while blocked, actions are
|
||||||
return t.waitBlocked && time.Since(t.waitBlockedAt) > progressMinDuration
|
// 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() {
|
func (t *Terminal) unblockWait() {
|
||||||
t.waitBlocked = false
|
t.wait.blocked = false
|
||||||
// Restore the cursor unless it's still hidden for another reason
|
// 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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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 {
|
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'
|
* 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
|
// Already waiting. Actions parsed from a bg-transform
|
||||||
// result join the current wait; user input can't reset
|
// result join the current wait; user input can't reset
|
||||||
// the blocked state
|
// the blocked state
|
||||||
if t.waitBlocked {
|
if t.wait.blocked {
|
||||||
if inBgCallback {
|
if inBgCallback {
|
||||||
t.pendingActions = append(t.pendingActions, actions[i+1:]...)
|
t.wait.pending = append(t.wait.pending, actions[i+1:]...)
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
// 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 || queryBefore != string(t.input) {
|
if changed || newCommand != nil || t.wait.searching || queryBefore != string(t.input) {
|
||||||
t.waitBlocked = true
|
t.blockWait(actions[i+1:])
|
||||||
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)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
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
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
blockedBefore := t.waitBlocked
|
blockedBefore := t.wait.blocked
|
||||||
if !doAction(action) {
|
if !doAction(action) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
// If this action armed the wait through a nested list
|
// If this action armed the wait through a nested list
|
||||||
// (e.g. via trigger), defer the rest of this list too
|
// (e.g. via trigger), defer the rest of this list too
|
||||||
if !blockedBefore && t.waitBlocked {
|
if !blockedBefore && t.wait.blocked {
|
||||||
t.pendingActions = append(t.pendingActions, actions[i+1:]...)
|
t.wait.pending = append(t.wait.pending, actions[i+1:]...)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
// A terminal action performed. We should stop processing more.
|
// A terminal action performed. We should stop processing more.
|
||||||
@@ -6777,10 +6801,9 @@ func (t *Terminal) Loop() error {
|
|||||||
passthrough := inBgCallback || a.t == actAsync ||
|
passthrough := inBgCallback || a.t == actAsync ||
|
||||||
a.t == actBracketedPasteBegin || a.t == actBracketedPasteEnd
|
a.t == actBracketedPasteBegin || a.t == actBracketedPasteEnd
|
||||||
// When wait-blocked, only allow abort/cancel
|
// When wait-blocked, only allow abort/cancel
|
||||||
if t.waitBlocked && !passthrough {
|
if t.wait.blocked && !passthrough {
|
||||||
if a.t == actAbort || a.t == actCancel {
|
if a.t == actAbort || a.t == actCancel {
|
||||||
t.unblockWait()
|
t.cancelWait()
|
||||||
t.pendingActions = nil
|
|
||||||
req(reqPrompt, reqInfo)
|
req(reqPrompt, reqInfo)
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
@@ -7673,7 +7696,7 @@ func (t *Terminal) Loop() error {
|
|||||||
req(reqPrompt)
|
req(reqPrompt)
|
||||||
case actTrigger:
|
case actTrigger:
|
||||||
if _, chords, err := parseKeyChords(a.a, ""); err == nil {
|
if _, chords, err := parseKeyChords(a.a, ""); err == nil {
|
||||||
blockedBefore := t.waitBlocked
|
blockedBefore := t.wait.blocked
|
||||||
for ci, chord := range chords {
|
for ci, chord := range chords {
|
||||||
if _, prs := triggering[chord]; prs {
|
if _, prs := triggering[chord]; prs {
|
||||||
// Avoid recursive triggering
|
// Avoid recursive triggering
|
||||||
@@ -7685,14 +7708,14 @@ func (t *Terminal) Loop() error {
|
|||||||
delete(triggering, chord)
|
delete(triggering, chord)
|
||||||
}
|
}
|
||||||
// If this chord armed the wait, defer the remaining chords
|
// 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:] {
|
for _, rest := range chords[ci+1:] {
|
||||||
if _, prs := triggering[rest]; prs {
|
if _, prs := triggering[rest]; prs {
|
||||||
// Avoid recursive triggering
|
// Avoid recursive triggering
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if acts, prs := t.keymap[rest]; prs {
|
if acts, prs := t.keymap[rest]; prs {
|
||||||
t.pendingActions = append(t.pendingActions, acts...)
|
t.wait.pending = append(t.wait.pending, acts...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
@@ -8214,10 +8237,10 @@ func (t *Terminal) Loop() error {
|
|||||||
// state first so that a pending 'jump' action isn't cancelled right
|
// state first so that a pending 'jump' action isn't cancelled right
|
||||||
// away by the wake-up event below.
|
// away by the wake-up event below.
|
||||||
jumpingBefore := t.jumping
|
jumpingBefore := t.jumping
|
||||||
if len(t.pendingActions) > 0 && !t.waitBlocked {
|
if len(t.wait.pending) > 0 && !t.wait.blocked {
|
||||||
pendingActions := t.pendingActions
|
pending := t.wait.pending
|
||||||
t.pendingActions = nil
|
t.wait.pending = nil
|
||||||
if !doActions(pendingActions) {
|
if !doActions(pending) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -8284,7 +8307,7 @@ func (t *Terminal) Loop() error {
|
|||||||
|
|
||||||
reload := changed || newCommand != nil
|
reload := changed || newCommand != nil
|
||||||
if reload {
|
if reload {
|
||||||
t.searchInProgress = true
|
t.wait.searching = true
|
||||||
}
|
}
|
||||||
var reloadRequest *searchRequest
|
var reloadRequest *searchRequest
|
||||||
if reload {
|
if reload {
|
||||||
|
|||||||
Reference in New Issue
Block a user