m/fzf
1
0
mirror of https://github.com/junegunn/fzf.git synced 2025-11-17 15:53:39 -05: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:
Junegunn Choi
2023-12-25 23:43:46 +09:00
parent 41d4d70b98
commit 1707b8cdba
10 changed files with 255 additions and 52 deletions

127
src/actiontype_string.go Normal file
View File

@@ -0,0 +1,127 @@
// Code generated by "stringer -type=actionType"; DO NOT EDIT.
package fzf
import "strconv"
func _() {
// An "invalid array index" compiler error signifies that the constant values have changed.
// Re-run the stringer command to generate them again.
var x [1]struct{}
_ = x[actIgnore-0]
_ = x[actStart-1]
_ = x[actClick-2]
_ = x[actInvalid-3]
_ = x[actChar-4]
_ = x[actMouse-5]
_ = x[actBeginningOfLine-6]
_ = x[actAbort-7]
_ = x[actAccept-8]
_ = x[actAcceptNonEmpty-9]
_ = x[actAcceptOrPrintQuery-10]
_ = x[actBackwardChar-11]
_ = x[actBackwardDeleteChar-12]
_ = x[actBackwardDeleteCharEof-13]
_ = x[actBackwardWord-14]
_ = x[actCancel-15]
_ = x[actChangeBorderLabel-16]
_ = x[actChangeHeader-17]
_ = x[actChangePreviewLabel-18]
_ = x[actChangePrompt-19]
_ = x[actChangeQuery-20]
_ = x[actClearScreen-21]
_ = x[actClearQuery-22]
_ = x[actClearSelection-23]
_ = x[actClose-24]
_ = x[actDeleteChar-25]
_ = x[actDeleteCharEof-26]
_ = x[actEndOfLine-27]
_ = x[actForwardChar-28]
_ = x[actForwardWord-29]
_ = x[actKillLine-30]
_ = x[actKillWord-31]
_ = x[actUnixLineDiscard-32]
_ = x[actUnixWordRubout-33]
_ = x[actYank-34]
_ = x[actBackwardKillWord-35]
_ = x[actSelectAll-36]
_ = x[actDeselectAll-37]
_ = x[actToggle-38]
_ = x[actToggleSearch-39]
_ = x[actToggleAll-40]
_ = x[actToggleDown-41]
_ = x[actToggleUp-42]
_ = x[actToggleIn-43]
_ = x[actToggleOut-44]
_ = x[actToggleTrack-45]
_ = x[actToggleHeader-46]
_ = x[actTrack-47]
_ = x[actDown-48]
_ = x[actUp-49]
_ = x[actPageUp-50]
_ = x[actPageDown-51]
_ = x[actPosition-52]
_ = x[actHalfPageUp-53]
_ = x[actHalfPageDown-54]
_ = x[actOffsetUp-55]
_ = x[actOffsetDown-56]
_ = x[actJump-57]
_ = x[actJumpAccept-58]
_ = x[actPrintQuery-59]
_ = x[actRefreshPreview-60]
_ = x[actReplaceQuery-61]
_ = x[actToggleSort-62]
_ = x[actShowPreview-63]
_ = x[actHidePreview-64]
_ = x[actTogglePreview-65]
_ = x[actTogglePreviewWrap-66]
_ = x[actTransform-67]
_ = x[actTransformBorderLabel-68]
_ = x[actTransformHeader-69]
_ = x[actTransformPreviewLabel-70]
_ = x[actTransformPrompt-71]
_ = x[actTransformQuery-72]
_ = x[actPreview-73]
_ = x[actChangePreview-74]
_ = x[actChangePreviewWindow-75]
_ = x[actPreviewTop-76]
_ = x[actPreviewBottom-77]
_ = x[actPreviewUp-78]
_ = x[actPreviewDown-79]
_ = x[actPreviewPageUp-80]
_ = x[actPreviewPageDown-81]
_ = x[actPreviewHalfPageUp-82]
_ = x[actPreviewHalfPageDown-83]
_ = x[actPrevHistory-84]
_ = x[actPrevSelected-85]
_ = x[actPut-86]
_ = x[actNextHistory-87]
_ = x[actNextSelected-88]
_ = x[actExecute-89]
_ = x[actExecuteSilent-90]
_ = x[actExecuteMulti-91]
_ = x[actSigStop-92]
_ = x[actFirst-93]
_ = x[actLast-94]
_ = x[actReload-95]
_ = x[actReloadSync-96]
_ = x[actDisableSearch-97]
_ = x[actEnableSearch-98]
_ = x[actSelect-99]
_ = x[actDeselect-100]
_ = x[actUnbind-101]
_ = x[actRebind-102]
_ = x[actBecome-103]
_ = x[actResponse-104]
}
const _actionType_name = "actIgnoreactStartactClickactInvalidactCharactMouseactBeginningOfLineactAbortactAcceptactAcceptNonEmptyactAcceptOrPrintQueryactBackwardCharactBackwardDeleteCharactBackwardDeleteCharEofactBackwardWordactCancelactChangeBorderLabelactChangeHeaderactChangePreviewLabelactChangePromptactChangeQueryactClearScreenactClearQueryactClearSelectionactCloseactDeleteCharactDeleteCharEofactEndOfLineactForwardCharactForwardWordactKillLineactKillWordactUnixLineDiscardactUnixWordRuboutactYankactBackwardKillWordactSelectAllactDeselectAllactToggleactToggleSearchactToggleAllactToggleDownactToggleUpactToggleInactToggleOutactToggleTrackactToggleHeaderactTrackactDownactUpactPageUpactPageDownactPositionactHalfPageUpactHalfPageDownactOffsetUpactOffsetDownactJumpactJumpAcceptactPrintQueryactRefreshPreviewactReplaceQueryactToggleSortactShowPreviewactHidePreviewactTogglePreviewactTogglePreviewWrapactTransformactTransformBorderLabelactTransformHeaderactTransformPreviewLabelactTransformPromptactTransformQueryactPreviewactChangePreviewactChangePreviewWindowactPreviewTopactPreviewBottomactPreviewUpactPreviewDownactPreviewPageUpactPreviewPageDownactPreviewHalfPageUpactPreviewHalfPageDownactPrevHistoryactPrevSelectedactPutactNextHistoryactNextSelectedactExecuteactExecuteSilentactExecuteMultiactSigStopactFirstactLastactReloadactReloadSyncactDisableSearchactEnableSearchactSelectactDeselectactUnbindactRebindactBecomeactResponse"
var _actionType_index = [...]uint16{0, 9, 17, 25, 35, 42, 50, 68, 76, 85, 102, 123, 138, 159, 183, 198, 207, 227, 242, 263, 278, 292, 306, 319, 336, 344, 357, 373, 385, 399, 413, 424, 435, 453, 470, 477, 496, 508, 522, 531, 546, 558, 571, 582, 593, 605, 619, 634, 642, 649, 654, 663, 674, 685, 698, 713, 724, 737, 744, 757, 770, 787, 802, 815, 829, 843, 859, 879, 891, 914, 932, 956, 974, 991, 1001, 1017, 1039, 1052, 1068, 1080, 1094, 1110, 1128, 1148, 1170, 1184, 1199, 1205, 1219, 1234, 1244, 1260, 1275, 1285, 1293, 1300, 1309, 1322, 1338, 1353, 1362, 1373, 1382, 1391, 1400, 1411}
func (i actionType) String() string {
if i < 0 || i >= actionType(len(_actionType_index)-1) {
return "actionType(" + strconv.FormatInt(int64(i), 10) + ")"
}
return _actionType_name[_actionType_index[i]:_actionType_index[i+1]]
}

View File

@@ -979,7 +979,7 @@ const (
func init() {
executeRegexp = regexp.MustCompile(
`(?si)[:+](become|execute(?:-multi|-silent)?|reload(?:-sync)?|preview|(?:change|transform)-(?:header|query|prompt|border-label|preview-label)|change-preview-window|change-preview|(?:re|un)bind|pos|put)`)
`(?si)[:+](become|execute(?:-multi|-silent)?|reload(?:-sync)?|preview|(?:change|transform)-(?:header|query|prompt|border-label|preview-label)|transform|change-preview-window|change-preview|(?:re|un)bind|pos|put)`)
splitRegexp = regexp.MustCompile("[,:]+")
actionNameRegexp = regexp.MustCompile("(?i)^[a-z-]+")
}
@@ -1086,7 +1086,7 @@ func parseActionList(masked string, original string, prevActions []*action, putA
case "backward-delete-char":
appendAction(actBackwardDeleteChar)
case "backward-delete-char/eof":
appendAction(actBackwardDeleteCharEOF)
appendAction(actBackwardDeleteCharEof)
case "backward-word":
appendAction(actBackwardWord)
case "clear-screen":
@@ -1094,7 +1094,7 @@ func parseActionList(masked string, original string, prevActions []*action, putA
case "delete-char":
appendAction(actDeleteChar)
case "delete-char/eof":
appendAction(actDeleteCharEOF)
appendAction(actDeleteCharEof)
case "deselect":
appendAction(actDeselect)
case "end-of-line":
@@ -1213,7 +1213,7 @@ func parseActionList(masked string, original string, prevActions []*action, putA
appendAction(actDisableSearch)
case "put":
if putAllowed {
appendAction(actRune)
appendAction(actChar)
} else {
exit("unable to put non-printable character")
}
@@ -1333,6 +1333,8 @@ func isExecuteAction(str string) actionType {
return actExecuteMulti
case "put":
return actPut
case "transform":
return actTransform
case "transform-border-label":
return actTransformBorderLabel
case "transform-preview-label":

View File

@@ -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
}

View File

@@ -52,90 +52,90 @@ func TestReplacePlaceholder(t *testing.T) {
*/
// {}, preserve ansi
result = replacePlaceholder("echo {}", false, Delimiter{}, printsep, false, "query", items1)
result = replacePlaceholder("echo {}", false, Delimiter{}, printsep, false, "query", items1, actIgnore)
checkFormat("echo {{.O}} foo{{.I}}bar \x1b[31mbaz\x1b[m{{.O}}")
// {}, strip ansi
result = replacePlaceholder("echo {}", true, Delimiter{}, printsep, false, "query", items1)
result = replacePlaceholder("echo {}", true, Delimiter{}, printsep, false, "query", items1, actIgnore)
checkFormat("echo {{.O}} foo{{.I}}bar baz{{.O}}")
// {}, with multiple items
result = replacePlaceholder("echo {}", true, Delimiter{}, printsep, false, "query", items2)
result = replacePlaceholder("echo {}", true, Delimiter{}, printsep, false, "query", items2, actIgnore)
checkFormat("echo {{.O}}foo{{.I}}bar baz{{.O}}")
// {..}, strip leading whitespaces, preserve ansi
result = replacePlaceholder("echo {..}", false, Delimiter{}, printsep, false, "query", items1)
result = replacePlaceholder("echo {..}", false, Delimiter{}, printsep, false, "query", items1, actIgnore)
checkFormat("echo {{.O}}foo{{.I}}bar \x1b[31mbaz\x1b[m{{.O}}")
// {..}, strip leading whitespaces, strip ansi
result = replacePlaceholder("echo {..}", true, Delimiter{}, printsep, false, "query", items1)
result = replacePlaceholder("echo {..}", true, Delimiter{}, printsep, false, "query", items1, actIgnore)
checkFormat("echo {{.O}}foo{{.I}}bar baz{{.O}}")
// {q}
result = replacePlaceholder("echo {} {q}", true, Delimiter{}, printsep, false, "query", items1)
result = replacePlaceholder("echo {} {q}", true, Delimiter{}, printsep, false, "query", items1, actIgnore)
checkFormat("echo {{.O}} foo{{.I}}bar baz{{.O}} {{.O}}query{{.O}}")
// {q}, multiple items
result = replacePlaceholder("echo {+}{q}{+}", true, Delimiter{}, printsep, false, "query 'string'", items2)
result = replacePlaceholder("echo {+}{q}{+}", true, Delimiter{}, printsep, false, "query 'string'", items2, actIgnore)
checkFormat("echo {{.O}}foo{{.I}}bar baz{{.O}} {{.O}}FOO{{.I}}BAR BAZ{{.O}}{{.O}}query {{.I}}string{{.I}}{{.O}}{{.O}}foo{{.I}}bar baz{{.O}} {{.O}}FOO{{.I}}BAR BAZ{{.O}}")
result = replacePlaceholder("echo {}{q}{}", true, Delimiter{}, printsep, false, "query 'string'", items2)
result = replacePlaceholder("echo {}{q}{}", true, Delimiter{}, printsep, false, "query 'string'", items2, actIgnore)
checkFormat("echo {{.O}}foo{{.I}}bar baz{{.O}}{{.O}}query {{.I}}string{{.I}}{{.O}}{{.O}}foo{{.I}}bar baz{{.O}}")
result = replacePlaceholder("echo {1}/{2}/{2,1}/{-1}/{-2}/{}/{..}/{n.t}/\\{}/\\{1}/\\{q}/{3}", true, Delimiter{}, printsep, false, "query", items1)
result = replacePlaceholder("echo {1}/{2}/{2,1}/{-1}/{-2}/{}/{..}/{n.t}/\\{}/\\{1}/\\{q}/{3}", true, Delimiter{}, printsep, false, "query", items1, actIgnore)
checkFormat("echo {{.O}}foo{{.I}}bar{{.O}}/{{.O}}baz{{.O}}/{{.O}}bazfoo{{.I}}bar{{.O}}/{{.O}}baz{{.O}}/{{.O}}foo{{.I}}bar{{.O}}/{{.O}} foo{{.I}}bar baz{{.O}}/{{.O}}foo{{.I}}bar baz{{.O}}/{n.t}/{}/{1}/{q}/{{.O}}{{.O}}")
result = replacePlaceholder("echo {1}/{2}/{-1}/{-2}/{..}/{n.t}/\\{}/\\{1}/\\{q}/{3}", true, Delimiter{}, printsep, false, "query", items2)
result = replacePlaceholder("echo {1}/{2}/{-1}/{-2}/{..}/{n.t}/\\{}/\\{1}/\\{q}/{3}", true, Delimiter{}, printsep, false, "query", items2, actIgnore)
checkFormat("echo {{.O}}foo{{.I}}bar{{.O}}/{{.O}}baz{{.O}}/{{.O}}baz{{.O}}/{{.O}}foo{{.I}}bar{{.O}}/{{.O}}foo{{.I}}bar baz{{.O}}/{n.t}/{}/{1}/{q}/{{.O}}{{.O}}")
result = replacePlaceholder("echo {+1}/{+2}/{+-1}/{+-2}/{+..}/{n.t}/\\{}/\\{1}/\\{q}/{+3}", true, Delimiter{}, printsep, false, "query", items2)
result = replacePlaceholder("echo {+1}/{+2}/{+-1}/{+-2}/{+..}/{n.t}/\\{}/\\{1}/\\{q}/{+3}", true, Delimiter{}, printsep, false, "query", items2, actIgnore)
checkFormat("echo {{.O}}foo{{.I}}bar{{.O}} {{.O}}FOO{{.I}}BAR{{.O}}/{{.O}}baz{{.O}} {{.O}}BAZ{{.O}}/{{.O}}baz{{.O}} {{.O}}BAZ{{.O}}/{{.O}}foo{{.I}}bar{{.O}} {{.O}}FOO{{.I}}BAR{{.O}}/{{.O}}foo{{.I}}bar baz{{.O}} {{.O}}FOO{{.I}}BAR BAZ{{.O}}/{n.t}/{}/{1}/{q}/{{.O}}{{.O}} {{.O}}{{.O}}")
// forcePlus
result = replacePlaceholder("echo {1}/{2}/{-1}/{-2}/{..}/{n.t}/\\{}/\\{1}/\\{q}/{3}", true, Delimiter{}, printsep, true, "query", items2)
result = replacePlaceholder("echo {1}/{2}/{-1}/{-2}/{..}/{n.t}/\\{}/\\{1}/\\{q}/{3}", true, Delimiter{}, printsep, true, "query", items2, actIgnore)
checkFormat("echo {{.O}}foo{{.I}}bar{{.O}} {{.O}}FOO{{.I}}BAR{{.O}}/{{.O}}baz{{.O}} {{.O}}BAZ{{.O}}/{{.O}}baz{{.O}} {{.O}}BAZ{{.O}}/{{.O}}foo{{.I}}bar{{.O}} {{.O}}FOO{{.I}}BAR{{.O}}/{{.O}}foo{{.I}}bar baz{{.O}} {{.O}}FOO{{.I}}BAR BAZ{{.O}}/{n.t}/{}/{1}/{q}/{{.O}}{{.O}} {{.O}}{{.O}}")
// Whitespace preserving flag with "'" delimiter
result = replacePlaceholder("echo {s1}", true, Delimiter{str: &delim}, printsep, false, "query", items1)
result = replacePlaceholder("echo {s1}", true, Delimiter{str: &delim}, printsep, false, "query", items1, actIgnore)
checkFormat("echo {{.O}} foo{{.O}}")
result = replacePlaceholder("echo {s2}", true, Delimiter{str: &delim}, printsep, false, "query", items1)
result = replacePlaceholder("echo {s2}", true, Delimiter{str: &delim}, printsep, false, "query", items1, actIgnore)
checkFormat("echo {{.O}}bar baz{{.O}}")
result = replacePlaceholder("echo {s}", true, Delimiter{str: &delim}, printsep, false, "query", items1)
result = replacePlaceholder("echo {s}", true, Delimiter{str: &delim}, printsep, false, "query", items1, actIgnore)
checkFormat("echo {{.O}} foo{{.I}}bar baz{{.O}}")
result = replacePlaceholder("echo {s..}", true, Delimiter{str: &delim}, printsep, false, "query", items1)
result = replacePlaceholder("echo {s..}", true, Delimiter{str: &delim}, printsep, false, "query", items1, actIgnore)
checkFormat("echo {{.O}} foo{{.I}}bar baz{{.O}}")
// Whitespace preserving flag with regex delimiter
regex = regexp.MustCompile(`\w+`)
result = replacePlaceholder("echo {s1}", true, Delimiter{regex: regex}, printsep, false, "query", items1)
result = replacePlaceholder("echo {s1}", true, Delimiter{regex: regex}, printsep, false, "query", items1, actIgnore)
checkFormat("echo {{.O}} {{.O}}")
result = replacePlaceholder("echo {s2}", true, Delimiter{regex: regex}, printsep, false, "query", items1)
result = replacePlaceholder("echo {s2}", true, Delimiter{regex: regex}, printsep, false, "query", items1, actIgnore)
checkFormat("echo {{.O}}{{.I}}{{.O}}")
result = replacePlaceholder("echo {s3}", true, Delimiter{regex: regex}, printsep, false, "query", items1)
result = replacePlaceholder("echo {s3}", true, Delimiter{regex: regex}, printsep, false, "query", items1, actIgnore)
checkFormat("echo {{.O}} {{.O}}")
// No match
result = replacePlaceholder("echo {}/{+}", true, Delimiter{}, printsep, false, "query", []*Item{nil, nil})
result = replacePlaceholder("echo {}/{+}", true, Delimiter{}, printsep, false, "query", []*Item{nil, nil}, actIgnore)
check("echo /")
// No match, but with selections
result = replacePlaceholder("echo {}/{+}", true, Delimiter{}, printsep, false, "query", []*Item{nil, item1})
result = replacePlaceholder("echo {}/{+}", true, Delimiter{}, printsep, false, "query", []*Item{nil, item1}, actIgnore)
checkFormat("echo /{{.O}} foo{{.I}}bar baz{{.O}}")
// String delimiter
result = replacePlaceholder("echo {}/{1}/{2}", true, Delimiter{str: &delim}, printsep, false, "query", items1)
result = replacePlaceholder("echo {}/{1}/{2}", true, Delimiter{str: &delim}, printsep, false, "query", items1, actIgnore)
checkFormat("echo {{.O}} foo{{.I}}bar baz{{.O}}/{{.O}}foo{{.O}}/{{.O}}bar baz{{.O}}")
// Regex delimiter
regex = regexp.MustCompile("[oa]+")
// foo'bar baz
result = replacePlaceholder("echo {}/{1}/{3}/{2..3}", true, Delimiter{regex: regex}, printsep, false, "query", items1)
result = replacePlaceholder("echo {}/{1}/{3}/{2..3}", true, Delimiter{regex: regex}, printsep, false, "query", items1, actIgnore)
checkFormat("echo {{.O}} foo{{.I}}bar baz{{.O}}/{{.O}}f{{.O}}/{{.O}}r b{{.O}}/{{.O}}{{.I}}bar b{{.O}}")
/*
@@ -198,18 +198,23 @@ func TestReplacePlaceholder(t *testing.T) {
// query flag is not removed after parsing, so it gets doubled
// while the double q is invalid, it is useful here for testing purposes
templateToOutput[`{q}`] = "{{.O}}" + query + "{{.O}}"
templateToOutput[`{fzf:query}`] = "{{.O}}" + query + "{{.O}}"
templateToOutput[`{fzf:action}`] = "backward-delete-char-eof"
// IV. escaping placeholder
templateToOutput[`\{}`] = `{}`
templateToOutput[`\{q}`] = `{q}`
templateToOutput[`\{fzf:query}`] = `{fzf:query}`
templateToOutput[`\{fzf:action}`] = `{fzf:action}`
templateToOutput[`\{++}`] = `{++}`
templateToOutput[`{++}`] = templateToOutput[`{+}`]
for giveTemplate, wantOutput := range templateToOutput {
result = replacePlaceholder(giveTemplate, stripAnsi, Delimiter{}, printsep, forcePlus, query, items3)
result = replacePlaceholder(giveTemplate, stripAnsi, Delimiter{}, printsep, forcePlus, query, items3, actBackwardDeleteCharEof)
checkFormat(wantOutput)
}
for giveTemplate, wantOutput := range templateToFile {
path := replacePlaceholder(giveTemplate, stripAnsi, Delimiter{}, printsep, forcePlus, query, items3)
path := replacePlaceholder(giveTemplate, stripAnsi, Delimiter{}, printsep, forcePlus, query, items3, actIgnore)
data, err := readFile(path)
if err != nil {
@@ -566,7 +571,7 @@ func testCommands(t *testing.T, tests []testCase) {
gotOutput := replacePlaceholder(
test.give.template, stripAnsi, delimiter, printsep, forcePlus,
test.give.query,
test.give.allItems)
test.give.allItems, actIgnore)
switch {
case test.want.output != "":
if gotOutput != test.want.output {