mirror of
https://github.com/junegunn/fzf.git
synced 2025-12-08 13:44:50 +08:00
Implement asynchronous transform actions (#4419)
Close #4418 Example: fzf --bind 'focus:bg-transform-header(sleep 2; date; echo {})'
This commit is contained in:
465
src/terminal.go
465
src/terminal.go
@@ -388,6 +388,10 @@ type Terminal struct {
|
||||
startChan chan fitpad
|
||||
killChan chan bool
|
||||
serverInputChan chan []*action
|
||||
callbackChan chan func()
|
||||
bgQueue map[action][]func()
|
||||
bgSemaphore chan struct{}
|
||||
bgSemaphores map[action]chan struct{}
|
||||
keyChan chan tui.Event
|
||||
eventChan chan tui.Event
|
||||
slab *util.Slab
|
||||
@@ -489,6 +493,7 @@ const (
|
||||
actBackwardDeleteCharEof
|
||||
actBackwardWord
|
||||
actCancel
|
||||
|
||||
actChangeBorderLabel
|
||||
actChangeGhost
|
||||
actChangeHeader
|
||||
@@ -505,6 +510,7 @@ const (
|
||||
actChangePreviewWindow
|
||||
actChangePrompt
|
||||
actChangeQuery
|
||||
|
||||
actClearScreen
|
||||
actClearQuery
|
||||
actClearSelection
|
||||
@@ -561,6 +567,7 @@ const (
|
||||
actHidePreview
|
||||
actTogglePreview
|
||||
actTogglePreviewWrap
|
||||
|
||||
actTransform
|
||||
actTransformBorderLabel
|
||||
actTransformGhost
|
||||
@@ -576,6 +583,23 @@ const (
|
||||
actTransformPrompt
|
||||
actTransformQuery
|
||||
actTransformSearch
|
||||
|
||||
actBgTransform
|
||||
actBgTransformBorderLabel
|
||||
actBgTransformGhost
|
||||
actBgTransformHeader
|
||||
actBgTransformFooter
|
||||
actBgTransformHeaderLabel
|
||||
actBgTransformFooterLabel
|
||||
actBgTransformInputLabel
|
||||
actBgTransformListLabel
|
||||
actBgTransformNth
|
||||
actBgTransformPointer
|
||||
actBgTransformPreviewLabel
|
||||
actBgTransformPrompt
|
||||
actBgTransformQuery
|
||||
actBgTransformSearch
|
||||
|
||||
actSearch
|
||||
actPreview
|
||||
actPreviewTop
|
||||
@@ -613,6 +637,7 @@ const (
|
||||
actBell
|
||||
actExclude
|
||||
actExcludeMulti
|
||||
actAsync
|
||||
)
|
||||
|
||||
func (a actionType) Name() string {
|
||||
@@ -623,10 +648,34 @@ func processExecution(action actionType) bool {
|
||||
switch action {
|
||||
case actTransform,
|
||||
actTransformBorderLabel,
|
||||
actTransformGhost,
|
||||
actTransformHeader,
|
||||
actTransformFooter,
|
||||
actTransformHeaderLabel,
|
||||
actTransformFooterLabel,
|
||||
actTransformInputLabel,
|
||||
actTransformListLabel,
|
||||
actTransformNth,
|
||||
actTransformPointer,
|
||||
actTransformPreviewLabel,
|
||||
actTransformPrompt,
|
||||
actTransformQuery,
|
||||
actTransformSearch,
|
||||
actBgTransform,
|
||||
actBgTransformBorderLabel,
|
||||
actBgTransformGhost,
|
||||
actBgTransformHeader,
|
||||
actBgTransformFooter,
|
||||
actBgTransformHeaderLabel,
|
||||
actBgTransformFooterLabel,
|
||||
actBgTransformInputLabel,
|
||||
actBgTransformListLabel,
|
||||
actBgTransformNth,
|
||||
actBgTransformPointer,
|
||||
actBgTransformPreviewLabel,
|
||||
actBgTransformPrompt,
|
||||
actBgTransformQuery,
|
||||
actBgTransformSearch,
|
||||
actPreview,
|
||||
actChangePreview,
|
||||
actRefreshPreview,
|
||||
@@ -773,7 +822,7 @@ func mayTriggerPreview(opts *Options) bool {
|
||||
for _, actions := range opts.Keymap {
|
||||
for _, action := range actions {
|
||||
switch action.t {
|
||||
case actPreview, actChangePreview, actTransform:
|
||||
case actPreview, actChangePreview, actTransform, actBgTransform:
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -987,6 +1036,10 @@ func NewTerminal(opts *Options, eventBox *util.EventBox, executor *util.Executor
|
||||
startChan: make(chan fitpad, 1),
|
||||
killChan: make(chan bool),
|
||||
serverInputChan: make(chan []*action, 100),
|
||||
callbackChan: make(chan func(), maxBgProcesses),
|
||||
bgQueue: make(map[action][]func()),
|
||||
bgSemaphore: make(chan struct{}, maxBgProcesses),
|
||||
bgSemaphores: make(map[action]chan struct{}),
|
||||
keyChan: make(chan tui.Event),
|
||||
eventChan: make(chan tui.Event, 6), // start | (load + result + zero|one) | (focus) | (resize)
|
||||
tui: renderer,
|
||||
@@ -2578,7 +2631,9 @@ func (t *Terminal) printPrompt() {
|
||||
|
||||
before, after := t.updatePromptOffset()
|
||||
if len(before) == 0 && len(after) == 0 && len(t.ghost) > 0 {
|
||||
w.CPrint(tui.ColGhost, t.ghost)
|
||||
maxWidth := util.Max(1, w.Width()-t.promptLen-1)
|
||||
runes, _ := t.trimRight([]rune(t.ghost), maxWidth)
|
||||
w.CPrint(tui.ColGhost, string(runes))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -4291,6 +4346,75 @@ func (t *Terminal) captureLines(template string) string {
|
||||
return t.executeCommand(template, false, true, true, false, "")
|
||||
}
|
||||
|
||||
func (t *Terminal) captureAsync(a action, firstLineOnly bool, callback func(string)) {
|
||||
_, list := t.buildPlusList(a.a, false)
|
||||
command, tempFiles := t.replacePlaceholder(a.a, false, string(t.input), list)
|
||||
item := func() {
|
||||
cmd := t.executor.ExecCommand(command, false)
|
||||
cmd.Env = t.environ()
|
||||
|
||||
out, _ := cmd.StdoutPipe()
|
||||
reader := bufio.NewReader(out)
|
||||
var output string
|
||||
if err := cmd.Start(); err == nil {
|
||||
if firstLineOnly {
|
||||
output, _ = reader.ReadString('\n')
|
||||
output = strings.TrimRight(output, "\r\n")
|
||||
} else {
|
||||
bytes, _ := io.ReadAll(reader)
|
||||
output = string(bytes)
|
||||
}
|
||||
cmd.Wait()
|
||||
}
|
||||
removeFiles(tempFiles)
|
||||
|
||||
t.callbackChan <- func() { callback(output) }
|
||||
}
|
||||
queue, prs := t.bgQueue[a]
|
||||
if !prs {
|
||||
queue = []func(){}
|
||||
}
|
||||
queue = append(queue, item)
|
||||
t.bgQueue[a] = queue
|
||||
}
|
||||
|
||||
func (t *Terminal) dispatchAsync() {
|
||||
Loop:
|
||||
for a, queue := range t.bgQueue {
|
||||
delete(t.bgQueue, a)
|
||||
if len(queue) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
semaphore, prs := t.bgSemaphores[a]
|
||||
if !prs {
|
||||
semaphore = make(chan struct{}, maxBgProcessesPerAction)
|
||||
t.bgSemaphores[a] = semaphore
|
||||
}
|
||||
for _, item := range queue {
|
||||
select {
|
||||
// Acquire local semaphore
|
||||
case semaphore <- struct{}{}:
|
||||
default:
|
||||
// Failed to acquire local semaphore, putting only the last one back to the queue
|
||||
t.bgQueue[a] = queue[len(queue)-1:]
|
||||
continue Loop
|
||||
}
|
||||
todo := item
|
||||
go func() {
|
||||
// Acquire global semaphore
|
||||
t.bgSemaphore <- struct{}{}
|
||||
|
||||
todo()
|
||||
// Release local semaphore
|
||||
<-semaphore
|
||||
// Release global semaphore
|
||||
<-t.bgSemaphore
|
||||
}()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Terminal) executeCommand(template string, forcePlus bool, background bool, capture bool, firstLineOnly bool, info string) string {
|
||||
line := ""
|
||||
valid, list := t.buildPlusList(template, forcePlus)
|
||||
@@ -5089,11 +5213,27 @@ func (t *Terminal) Loop() error {
|
||||
barrier <- true
|
||||
needBarrier = false
|
||||
}
|
||||
|
||||
// These variables are defined outside the loop to be accessible from closures
|
||||
events := []util.EventType{}
|
||||
changed := false
|
||||
var newNth *[]Range
|
||||
req := func(evts ...util.EventType) {
|
||||
for _, event := range evts {
|
||||
events = append(events, event)
|
||||
if event == reqClose || event == reqQuit {
|
||||
looping = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The main event loop
|
||||
for loopIndex := int64(0); looping; loopIndex++ {
|
||||
var newCommand *commandSpec
|
||||
var newNth *[]Range
|
||||
var reloadSync bool
|
||||
changed := false
|
||||
events = []util.EventType{}
|
||||
changed = false
|
||||
newNth = nil
|
||||
beof := false
|
||||
queryChanged := false
|
||||
denylist := []int32{}
|
||||
@@ -5110,6 +5250,7 @@ func (t *Terminal) Loop() error {
|
||||
|
||||
var event tui.Event
|
||||
actions := []*action{}
|
||||
callbacks := []func(){}
|
||||
select {
|
||||
case event = <-t.keyChan:
|
||||
needBarrier = true
|
||||
@@ -5141,6 +5282,20 @@ func (t *Terminal) Loop() error {
|
||||
}
|
||||
}
|
||||
}
|
||||
case callback := <-t.callbackChan:
|
||||
event = tui.Invalid.AsEvent()
|
||||
actions = append(actions, &action{t: actAsync})
|
||||
callbacks = append(callbacks, callback)
|
||||
DrainCallback:
|
||||
for {
|
||||
select {
|
||||
case callback = <-t.callbackChan:
|
||||
callbacks = append(callbacks, callback)
|
||||
continue DrainCallback
|
||||
default:
|
||||
break DrainCallback
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
t.mutex.Lock()
|
||||
@@ -5155,15 +5310,6 @@ func (t *Terminal) Loop() error {
|
||||
previousInput := t.input
|
||||
previousCx := t.cx
|
||||
t.lastKey = event.KeyName()
|
||||
events := []util.EventType{}
|
||||
req := func(evts ...util.EventType) {
|
||||
for _, event := range evts {
|
||||
events = append(events, event)
|
||||
if event == reqClose || event == reqQuit {
|
||||
looping = false
|
||||
}
|
||||
}
|
||||
}
|
||||
updatePreviewWindow := func(forcePreview bool) {
|
||||
t.resizeWindows(forcePreview, false)
|
||||
req(reqPrompt, reqList, reqInfo, reqHeader, reqFooter)
|
||||
@@ -5238,9 +5384,29 @@ func (t *Terminal) Loop() error {
|
||||
// actions to allow changing the query even when the input is hidden
|
||||
// e.g. fzf --no-input --bind 'space:show-input+change-query(foo)+hide-input'
|
||||
currentInput := t.input
|
||||
capture := func(firstLineOnly bool, callback func(string)) {
|
||||
if a.t >= actBgTransform {
|
||||
// bg-transform-*
|
||||
t.captureAsync(*a, firstLineOnly, callback)
|
||||
} else if a.t >= actTransform {
|
||||
// transform-*
|
||||
if firstLineOnly {
|
||||
callback(t.captureLine(a.a))
|
||||
} else {
|
||||
callback(t.captureLines(a.a))
|
||||
}
|
||||
} else {
|
||||
// change-*
|
||||
callback(a.a)
|
||||
}
|
||||
}
|
||||
Action:
|
||||
switch a.t {
|
||||
case actIgnore, actStart, actClick:
|
||||
case actAsync:
|
||||
for _, callback := range callbacks {
|
||||
callback()
|
||||
}
|
||||
case actBecome:
|
||||
valid, list := t.buildPlusList(a.a, false)
|
||||
if valid {
|
||||
@@ -5333,15 +5499,17 @@ func (t *Terminal) Loop() error {
|
||||
t.previewed.version = 0
|
||||
req(reqPreviewRefresh)
|
||||
}
|
||||
case actTransformPrompt:
|
||||
prompt := t.captureLine(a.a)
|
||||
t.promptString = prompt
|
||||
t.prompt, t.promptLen = t.parsePrompt(prompt)
|
||||
req(reqPrompt)
|
||||
case actTransformQuery:
|
||||
query := t.captureLine(a.a)
|
||||
t.input = []rune(query)
|
||||
t.cx = len(t.input)
|
||||
case actTransformPrompt, actBgTransformPrompt:
|
||||
capture(true, func(prompt string) {
|
||||
t.promptString = prompt
|
||||
t.prompt, t.promptLen = t.parsePrompt(prompt)
|
||||
req(reqPrompt)
|
||||
})
|
||||
case actTransformQuery, actBgTransformQuery:
|
||||
capture(true, func(query string) {
|
||||
t.input = []rune(query)
|
||||
t.cx = len(t.input)
|
||||
})
|
||||
case actToggleSort:
|
||||
t.sort = !t.sort
|
||||
changed = true
|
||||
@@ -5399,119 +5567,102 @@ func (t *Terminal) Loop() error {
|
||||
}
|
||||
t.multi = multi
|
||||
req(reqList, reqInfo)
|
||||
case actChangeNth, actTransformNth:
|
||||
expr := a.a
|
||||
if a.t == actTransformNth {
|
||||
expr = t.captureLine(a.a)
|
||||
}
|
||||
|
||||
// Split nth expression
|
||||
tokens := strings.Split(expr, "|")
|
||||
if nth, err := splitNth(tokens[0]); err == nil {
|
||||
// Changed
|
||||
newNth = &nth
|
||||
} else {
|
||||
// The default
|
||||
newNth = &t.nth
|
||||
}
|
||||
// Cycle
|
||||
if len(tokens) > 1 {
|
||||
a.a = strings.Join(append(tokens[1:], tokens[0]), "|")
|
||||
}
|
||||
if !compareRanges(t.nthCurrent, *newNth) {
|
||||
changed = true
|
||||
t.nthCurrent = *newNth
|
||||
t.forceRerenderList()
|
||||
}
|
||||
case actChangeNth, actTransformNth, actBgTransformNth:
|
||||
capture(true, func(expr string) {
|
||||
// Split nth expression
|
||||
tokens := strings.Split(expr, "|")
|
||||
if nth, err := splitNth(tokens[0]); err == nil {
|
||||
// Changed
|
||||
newNth = &nth
|
||||
} else {
|
||||
// The default
|
||||
newNth = &t.nth
|
||||
}
|
||||
// Cycle
|
||||
if len(tokens) > 1 {
|
||||
a.a = strings.Join(append(tokens[1:], tokens[0]), "|")
|
||||
}
|
||||
if !compareRanges(t.nthCurrent, *newNth) {
|
||||
changed = true
|
||||
t.nthCurrent = *newNth
|
||||
t.forceRerenderList()
|
||||
}
|
||||
})
|
||||
case actChangeQuery:
|
||||
t.input = []rune(a.a)
|
||||
t.cx = len(t.input)
|
||||
case actChangeHeader, actTransformHeader:
|
||||
header := a.a
|
||||
if a.t == actTransformHeader {
|
||||
header = t.captureLines(a.a)
|
||||
}
|
||||
if t.changeHeader(header) {
|
||||
if t.headerWindow != nil {
|
||||
// Need to resize header window
|
||||
case actChangeHeader, actTransformHeader, actBgTransformHeader:
|
||||
capture(false, func(header string) {
|
||||
if t.changeHeader(header) {
|
||||
if t.headerWindow != nil {
|
||||
// Need to resize header window
|
||||
req(reqFullRedraw)
|
||||
} else {
|
||||
req(reqHeader, reqList, reqPrompt, reqInfo)
|
||||
}
|
||||
} else {
|
||||
req(reqHeader)
|
||||
}
|
||||
})
|
||||
case actChangeFooter, actTransformFooter, actBgTransformFooter:
|
||||
capture(false, func(footer string) {
|
||||
if t.changeFooter(footer) {
|
||||
req(reqFullRedraw)
|
||||
} else {
|
||||
req(reqHeader, reqList, reqPrompt, reqInfo)
|
||||
req(reqFooter)
|
||||
}
|
||||
} else {
|
||||
req(reqHeader)
|
||||
}
|
||||
case actChangeFooter, actTransformFooter:
|
||||
footer := a.a
|
||||
if a.t == actTransformFooter {
|
||||
footer = t.captureLines(a.a)
|
||||
}
|
||||
if t.changeFooter(footer) {
|
||||
req(reqFullRedraw)
|
||||
} else {
|
||||
req(reqFooter)
|
||||
}
|
||||
case actChangeHeaderLabel, actTransformHeaderLabel:
|
||||
label := a.a
|
||||
if a.t == actTransformHeaderLabel {
|
||||
label = t.captureLine(a.a)
|
||||
}
|
||||
t.headerLabelOpts.label = label
|
||||
t.headerLabel, t.headerLabelLen = t.ansiLabelPrinter(label, &tui.ColHeaderLabel, false)
|
||||
req(reqRedrawHeaderLabel)
|
||||
case actChangeFooterLabel, actTransformFooterLabel:
|
||||
label := a.a
|
||||
if a.t == actTransformFooterLabel {
|
||||
label = t.captureLine(a.a)
|
||||
}
|
||||
t.footerLabelOpts.label = label
|
||||
t.footerLabel, t.footerLabelLen = t.ansiLabelPrinter(label, &tui.ColFooterLabel, false)
|
||||
req(reqRedrawFooterLabel)
|
||||
case actChangeInputLabel, actTransformInputLabel:
|
||||
label := a.a
|
||||
if a.t == actTransformInputLabel {
|
||||
label = t.captureLine(a.a)
|
||||
}
|
||||
t.inputLabelOpts.label = label
|
||||
if t.inputBorder != nil {
|
||||
t.inputLabel, t.inputLabelLen = t.ansiLabelPrinter(label, &tui.ColInputLabel, false)
|
||||
req(reqRedrawInputLabel)
|
||||
}
|
||||
case actChangeListLabel, actTransformListLabel:
|
||||
label := a.a
|
||||
if a.t == actTransformListLabel {
|
||||
label = t.captureLine(a.a)
|
||||
}
|
||||
t.listLabelOpts.label = label
|
||||
if t.wborder != nil {
|
||||
t.listLabel, t.listLabelLen = t.ansiLabelPrinter(label, &tui.ColListLabel, false)
|
||||
req(reqRedrawListLabel)
|
||||
}
|
||||
case actChangeBorderLabel, actTransformBorderLabel:
|
||||
label := a.a
|
||||
if a.t == actTransformBorderLabel {
|
||||
label = t.captureLine(a.a)
|
||||
}
|
||||
t.borderLabelOpts.label = label
|
||||
if t.border != nil {
|
||||
t.borderLabel, t.borderLabelLen = t.ansiLabelPrinter(label, &tui.ColBorderLabel, false)
|
||||
req(reqRedrawBorderLabel)
|
||||
}
|
||||
case actChangePreviewLabel, actTransformPreviewLabel:
|
||||
label := a.a
|
||||
if a.t == actTransformPreviewLabel {
|
||||
label = t.captureLine(a.a)
|
||||
}
|
||||
t.previewLabelOpts.label = label
|
||||
if t.pborder != nil {
|
||||
t.previewLabel, t.previewLabelLen = t.ansiLabelPrinter(label, &tui.ColPreviewLabel, false)
|
||||
req(reqRedrawPreviewLabel)
|
||||
}
|
||||
case actTransform:
|
||||
body := t.captureLines(a.a)
|
||||
if actions, err := parseSingleActionList(strings.Trim(body, "\r\n")); err == nil {
|
||||
return doActions(actions)
|
||||
}
|
||||
})
|
||||
case actChangeHeaderLabel, actTransformHeaderLabel, actBgTransformHeaderLabel:
|
||||
capture(true, func(label string) {
|
||||
t.headerLabelOpts.label = label
|
||||
t.headerLabel, t.headerLabelLen = t.ansiLabelPrinter(label, &tui.ColHeaderLabel, false)
|
||||
req(reqRedrawHeaderLabel)
|
||||
})
|
||||
case actChangeFooterLabel, actTransformFooterLabel, actBgTransformFooterLabel:
|
||||
capture(true, func(label string) {
|
||||
t.footerLabelOpts.label = label
|
||||
t.footerLabel, t.footerLabelLen = t.ansiLabelPrinter(label, &tui.ColFooterLabel, false)
|
||||
req(reqRedrawFooterLabel)
|
||||
})
|
||||
case actChangeInputLabel, actTransformInputLabel, actBgTransformInputLabel:
|
||||
capture(true, func(label string) {
|
||||
t.inputLabelOpts.label = label
|
||||
if t.inputBorder != nil {
|
||||
t.inputLabel, t.inputLabelLen = t.ansiLabelPrinter(label, &tui.ColInputLabel, false)
|
||||
req(reqRedrawInputLabel)
|
||||
}
|
||||
})
|
||||
case actChangeListLabel, actTransformListLabel, actBgTransformListLabel:
|
||||
capture(true, func(label string) {
|
||||
t.listLabelOpts.label = label
|
||||
if t.wborder != nil {
|
||||
t.listLabel, t.listLabelLen = t.ansiLabelPrinter(label, &tui.ColListLabel, false)
|
||||
req(reqRedrawListLabel)
|
||||
}
|
||||
})
|
||||
case actChangeBorderLabel, actTransformBorderLabel, actBgTransformBorderLabel:
|
||||
capture(true, func(label string) {
|
||||
t.borderLabelOpts.label = label
|
||||
if t.border != nil {
|
||||
t.borderLabel, t.borderLabelLen = t.ansiLabelPrinter(label, &tui.ColBorderLabel, false)
|
||||
req(reqRedrawBorderLabel)
|
||||
}
|
||||
})
|
||||
case actChangePreviewLabel, actTransformPreviewLabel, actBgTransformPreviewLabel:
|
||||
capture(true, func(label string) {
|
||||
t.previewLabelOpts.label = label
|
||||
if t.pborder != nil {
|
||||
t.previewLabel, t.previewLabelLen = t.ansiLabelPrinter(label, &tui.ColPreviewLabel, false)
|
||||
req(reqRedrawPreviewLabel)
|
||||
}
|
||||
})
|
||||
case actTransform, actBgTransform:
|
||||
capture(false, func(body string) {
|
||||
if actions, err := parseSingleActionList(strings.Trim(body, "\r\n")); err == nil {
|
||||
// NOTE: We're not properly passing the return value here
|
||||
doActions(actions)
|
||||
}
|
||||
})
|
||||
case actChangePrompt:
|
||||
t.promptString = a.a
|
||||
t.prompt, t.promptLen = t.parsePrompt(a.a)
|
||||
@@ -5933,10 +6084,12 @@ func (t *Terminal) Loop() error {
|
||||
override := []rune(a.a)
|
||||
t.inputOverride = &override
|
||||
changed = true
|
||||
case actTransformSearch:
|
||||
override := []rune(t.captureLine(a.a))
|
||||
t.inputOverride = &override
|
||||
changed = true
|
||||
case actTransformSearch, actBgTransformSearch:
|
||||
capture(true, func(query string) {
|
||||
override := []rune(query)
|
||||
t.inputOverride = &override
|
||||
changed = true
|
||||
})
|
||||
case actEnableSearch:
|
||||
t.paused = false
|
||||
changed = true
|
||||
@@ -6276,30 +6429,26 @@ func (t *Terminal) Loop() error {
|
||||
}
|
||||
}
|
||||
}
|
||||
case actChangeGhost, actTransformGhost:
|
||||
ghost := a.a
|
||||
if a.t == actTransformGhost {
|
||||
ghost = t.captureLine(a.a)
|
||||
}
|
||||
t.ghost = ghost
|
||||
if len(t.input) == 0 {
|
||||
req(reqPrompt)
|
||||
}
|
||||
case actChangePointer, actTransformPointer:
|
||||
pointer := a.a
|
||||
if a.t == actTransformPointer {
|
||||
pointer = t.captureLine(a.a)
|
||||
}
|
||||
length := uniseg.StringWidth(pointer)
|
||||
if length <= 2 {
|
||||
if length != t.pointerLen {
|
||||
t.forceRerenderList()
|
||||
case actChangeGhost, actTransformGhost, actBgTransformGhost:
|
||||
capture(true, func(ghost string) {
|
||||
t.ghost = ghost
|
||||
if len(t.input) == 0 {
|
||||
req(reqPrompt)
|
||||
}
|
||||
t.pointer = pointer
|
||||
t.pointerLen = length
|
||||
t.pointerEmpty = strings.Repeat(" ", t.pointerLen)
|
||||
req(reqList)
|
||||
}
|
||||
})
|
||||
case actChangePointer, actTransformPointer, actBgTransformPointer:
|
||||
capture(true, func(pointer string) {
|
||||
length := uniseg.StringWidth(pointer)
|
||||
if length <= 2 {
|
||||
if length != t.pointerLen {
|
||||
t.forceRerenderList()
|
||||
}
|
||||
t.pointer = pointer
|
||||
t.pointerLen = length
|
||||
t.pointerEmpty = strings.Repeat(" ", t.pointerLen)
|
||||
req(reqList)
|
||||
}
|
||||
})
|
||||
case actChangePreview:
|
||||
if t.previewOpts.command != a.a {
|
||||
t.previewOpts.command = a.a
|
||||
@@ -6451,6 +6600,10 @@ func (t *Terminal) Loop() error {
|
||||
if reload {
|
||||
reloadRequest = &searchRequest{sort: t.sort, sync: reloadSync, nth: newNth, command: newCommand, environ: t.environ(), changed: changed, denylist: denylist, revision: t.merger.Revision()}
|
||||
}
|
||||
|
||||
// Dispatch queued background requests
|
||||
t.dispatchAsync()
|
||||
|
||||
t.mutex.Unlock() // Must be unlocked before touching reqBox
|
||||
|
||||
if reload {
|
||||
|
||||
Reference in New Issue
Block a user