summaryrefslogtreecommitdiffstats
path: root/src/terminal.go
diff options
context:
space:
mode:
authorJunegunn Choi <junegunn.c@gmail.com>2023-12-25 23:43:46 +0900
committerJunegunn Choi <junegunn.c@gmail.com>2023-12-26 00:14:05 +0900
commit1707b8cdba42492a88b3fc43c15c20d219c5ce2b (patch)
tree268e6950fecc4bec86b38389101e52901134b3a5 /src/terminal.go
parent41d4d70b985f665c8ecc66b83aa10209c8dfbbfd (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.go62
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
}