diff options
author | Junegunn Choi <junegunn.c@gmail.com> | 2023-12-25 23:43:46 +0900 |
---|---|---|
committer | Junegunn Choi <junegunn.c@gmail.com> | 2023-12-26 00:14:05 +0900 |
commit | 1707b8cdba42492a88b3fc43c15c20d219c5ce2b (patch) | |
tree | 268e6950fecc4bec86b38389101e52901134b3a5 /src/terminal.go | |
parent | 41d4d70b985f665c8ecc66b83aa10209c8dfbbfd (diff) |
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
Diffstat (limited to 'src/terminal.go')
-rw-r--r-- | src/terminal.go | 62 |
1 files changed, 46 insertions, 16 deletions
diff --git a/src/terminal.go b/src/terminal.go index 394be4fc..31339d3e 100644 --- a/src/terminal.go +++ b/src/terminal.go @@ -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 } |