mirror of
https://github.com/junegunn/fzf.git
synced 2025-08-20 23:13:49 -07:00
Add 'transform' action to conditionally perform a series of actions
'transform' action runs an external command that prints a series of actions to perform. # Disallow selecting an empty line echo -e "1. Hello\n2. Goodbye\n\n3. Exit" | fzf --reverse --header 'Select one' \ --bind 'enter:transform:[[ -n {} ]] && echo accept || echo "change-header:Invalid selection"' # Move cursor past the empty line echo -e "1. Hello\n2. Goodbye\n\n3. Exit" | fzf --reverse --header 'Select one' \ --bind 'enter:transform:[[ -n {} ]] && echo accept || echo "change-header:Invalid selection"' \ --bind 'focus:transform:[[ -n {} ]] && exit; [[ {fzf:action} =~ up$ ]] && echo up || echo down' Close #3368 Close #2980
This commit is contained in:
@@ -52,11 +52,12 @@ var offsetComponentRegex *regexp.Regexp
|
||||
var offsetTrimCharsRegex *regexp.Regexp
|
||||
var activeTempFiles []string
|
||||
var passThroughRegex *regexp.Regexp
|
||||
var actionTypeRegex *regexp.Regexp
|
||||
|
||||
const clearCode string = "\x1b[2J"
|
||||
|
||||
func init() {
|
||||
placeholder = regexp.MustCompile(`\\?(?:{[+sf]*[0-9,-.]*}|{q}|{\+?f?nf?})`)
|
||||
placeholder = regexp.MustCompile(`\\?(?:{[+sf]*[0-9,-.]*}|{q}|{fzf:(?:query|action)}|{\+?f?nf?})`)
|
||||
whiteSuffix = regexp.MustCompile(`\s*$`)
|
||||
offsetComponentRegex = regexp.MustCompile(`([+-][0-9]+)|(-?/[1-9][0-9]*)`)
|
||||
offsetTrimCharsRegex = regexp.MustCompile(`[^0-9/+-]`)
|
||||
@@ -285,6 +286,7 @@ type Terminal struct {
|
||||
tui tui.Renderer
|
||||
executing *util.AtomicBool
|
||||
termSize tui.TermSize
|
||||
lastAction actionType
|
||||
}
|
||||
|
||||
type selectedItem struct {
|
||||
@@ -332,12 +334,15 @@ type action struct {
|
||||
a string
|
||||
}
|
||||
|
||||
//go:generate stringer -type=actionType
|
||||
type actionType int
|
||||
|
||||
const (
|
||||
actIgnore actionType = iota
|
||||
actStart
|
||||
actClick
|
||||
actInvalid
|
||||
actRune
|
||||
actChar
|
||||
actMouse
|
||||
actBeginningOfLine
|
||||
actAbort
|
||||
@@ -346,7 +351,7 @@ const (
|
||||
actAcceptOrPrintQuery
|
||||
actBackwardChar
|
||||
actBackwardDeleteChar
|
||||
actBackwardDeleteCharEOF
|
||||
actBackwardDeleteCharEof
|
||||
actBackwardWord
|
||||
actCancel
|
||||
actChangeBorderLabel
|
||||
@@ -359,7 +364,7 @@ const (
|
||||
actClearSelection
|
||||
actClose
|
||||
actDeleteChar
|
||||
actDeleteCharEOF
|
||||
actDeleteCharEof
|
||||
actEndOfLine
|
||||
actForwardChar
|
||||
actForwardWord
|
||||
@@ -400,6 +405,7 @@ const (
|
||||
actHidePreview
|
||||
actTogglePreview
|
||||
actTogglePreviewWrap
|
||||
actTransform
|
||||
actTransformBorderLabel
|
||||
actTransformHeader
|
||||
actTransformPreviewLabel
|
||||
@@ -441,13 +447,15 @@ const (
|
||||
|
||||
func processExecution(action actionType) bool {
|
||||
switch action {
|
||||
case actTransformBorderLabel,
|
||||
case actTransform,
|
||||
actTransformBorderLabel,
|
||||
actTransformHeader,
|
||||
actTransformPreviewLabel,
|
||||
actTransformPrompt,
|
||||
actTransformQuery,
|
||||
actPreview,
|
||||
actChangePreview,
|
||||
actRefreshPreview,
|
||||
actExecute,
|
||||
actExecuteSilent,
|
||||
actExecuteMulti,
|
||||
@@ -514,7 +522,7 @@ func defaultKeymap() map[tui.Event][]*action {
|
||||
add(tui.CtrlG, actAbort)
|
||||
add(tui.CtrlQ, actAbort)
|
||||
add(tui.ESC, actAbort)
|
||||
add(tui.CtrlD, actDeleteCharEOF)
|
||||
add(tui.CtrlD, actDeleteCharEof)
|
||||
add(tui.CtrlE, actEndOfLine)
|
||||
add(tui.CtrlF, actForwardChar)
|
||||
add(tui.CtrlH, actBackwardDeleteChar)
|
||||
@@ -556,7 +564,7 @@ func defaultKeymap() map[tui.Event][]*action {
|
||||
add(tui.SDown, actPreviewDown)
|
||||
|
||||
add(tui.Mouse, actMouse)
|
||||
add(tui.LeftClick, actIgnore)
|
||||
add(tui.LeftClick, actClick)
|
||||
add(tui.RightClick, actToggle)
|
||||
add(tui.SLeftClick, actToggle)
|
||||
add(tui.SRightClick, actToggle)
|
||||
@@ -740,7 +748,8 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
|
||||
eventChan: make(chan tui.Event, 3), // load / zero|one | GetChar
|
||||
tui: renderer,
|
||||
initFunc: func() { renderer.Init() },
|
||||
executing: util.NewAtomicBool(false)}
|
||||
executing: util.NewAtomicBool(false),
|
||||
lastAction: actStart}
|
||||
t.prompt, t.promptLen = t.parsePrompt(opts.Prompt)
|
||||
t.pointer, t.pointerLen = t.processTabs([]rune(opts.Pointer), 0)
|
||||
t.marker, t.markerLen = t.processTabs([]rune(opts.Marker), 0)
|
||||
@@ -2344,6 +2353,10 @@ func parsePlaceholder(match string) (bool, string, placeholderFlags) {
|
||||
return true, match[1:], flags
|
||||
}
|
||||
|
||||
if strings.HasPrefix(match, "{fzf:") {
|
||||
return false, match, flags
|
||||
}
|
||||
|
||||
skipChars := 1
|
||||
for _, char := range match[1:] {
|
||||
switch char {
|
||||
@@ -2408,7 +2421,7 @@ func cleanTemporaryFiles() {
|
||||
|
||||
func (t *Terminal) replacePlaceholder(template string, forcePlus bool, input string, list []*Item) string {
|
||||
return replacePlaceholder(
|
||||
template, t.ansi, t.delimiter, t.printsep, forcePlus, input, list)
|
||||
template, t.ansi, t.delimiter, t.printsep, forcePlus, input, list, t.lastAction)
|
||||
}
|
||||
|
||||
func (t *Terminal) evaluateScrollOffset() int {
|
||||
@@ -2446,7 +2459,7 @@ func (t *Terminal) evaluateScrollOffset() int {
|
||||
return util.Max(0, base)
|
||||
}
|
||||
|
||||
func replacePlaceholder(template string, stripAnsi bool, delimiter Delimiter, printsep string, forcePlus bool, query string, allItems []*Item) string {
|
||||
func replacePlaceholder(template string, stripAnsi bool, delimiter Delimiter, printsep string, forcePlus bool, query string, allItems []*Item, lastAction actionType) string {
|
||||
current := allItems[:1]
|
||||
selected := allItems[1:]
|
||||
if current[0] == nil {
|
||||
@@ -2467,7 +2480,16 @@ func replacePlaceholder(template string, stripAnsi bool, delimiter Delimiter, pr
|
||||
switch {
|
||||
case escaped:
|
||||
return match
|
||||
case match == "{q}":
|
||||
case match == "{fzf:action}":
|
||||
name := ""
|
||||
for i, r := range lastAction.String()[3:] {
|
||||
if i > 0 && r >= 'A' && r <= 'Z' {
|
||||
name += "-"
|
||||
}
|
||||
name += string(r)
|
||||
}
|
||||
return strings.ToLower(name)
|
||||
case match == "{q}" || match == "{fzf:query}":
|
||||
return quoteEntry(query)
|
||||
case match == "{}":
|
||||
replace = func(item *Item) string {
|
||||
@@ -3207,7 +3229,7 @@ func (t *Terminal) Loop() {
|
||||
}
|
||||
doAction = func(a *action) bool {
|
||||
switch a.t {
|
||||
case actIgnore:
|
||||
case actIgnore, actStart, actClick:
|
||||
case actResponse:
|
||||
t.serverOutputChan <- t.dumpStatus(parseGetParams(a.a))
|
||||
case actBecome:
|
||||
@@ -3354,6 +3376,10 @@ func (t *Terminal) Loop() {
|
||||
t.previewLabel, t.previewLabelLen = t.ansiLabelPrinter(a.a, &tui.ColPreviewLabel, false)
|
||||
req(reqRedrawPreviewLabel)
|
||||
}
|
||||
case actTransform:
|
||||
body := t.executeCommand(a.a, false, true, true, false)
|
||||
actions := parseSingleActionList(strings.Trim(body, "\r\n"), func(message string) {})
|
||||
t.serverInputChan <- actions
|
||||
case actTransformBorderLabel:
|
||||
if t.border != nil {
|
||||
label := t.executeCommand(a.a, false, true, true, true)
|
||||
@@ -3384,7 +3410,7 @@ func (t *Terminal) Loop() {
|
||||
req(reqQuit)
|
||||
case actDeleteChar:
|
||||
t.delChar()
|
||||
case actDeleteCharEOF:
|
||||
case actDeleteCharEof:
|
||||
if !t.delChar() && t.cx == 0 {
|
||||
req(reqQuit)
|
||||
}
|
||||
@@ -3398,7 +3424,7 @@ func (t *Terminal) Loop() {
|
||||
t.input = []rune{}
|
||||
t.cx = 0
|
||||
}
|
||||
case actBackwardDeleteCharEOF:
|
||||
case actBackwardDeleteCharEof:
|
||||
if len(t.input) == 0 {
|
||||
req(reqQuit)
|
||||
} else if t.cx > 0 {
|
||||
@@ -3617,7 +3643,7 @@ func (t *Terminal) Loop() {
|
||||
t.yanked = copySlice(t.input[t.cx:])
|
||||
t.input = t.input[:t.cx]
|
||||
}
|
||||
case actRune:
|
||||
case actChar:
|
||||
prefix := copySlice(t.input[:t.cx])
|
||||
t.input = append(append(prefix, event.Char), t.input[t.cx:]...)
|
||||
t.cx++
|
||||
@@ -3895,6 +3921,10 @@ func (t *Terminal) Loop() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !processExecution(a.t) {
|
||||
t.lastAction = a.t
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -3908,7 +3938,7 @@ func (t *Terminal) Loop() {
|
||||
actions = t.keymap[event.Comparable()]
|
||||
}
|
||||
if len(actions) == 0 && event.Type == tui.Rune {
|
||||
doAction(&action{t: actRune})
|
||||
doAction(&action{t: actChar})
|
||||
} else if !doActions(actions) {
|
||||
continue
|
||||
}
|
||||
|
Reference in New Issue
Block a user