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 | |
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')
-rw-r--r-- | src/actiontype_string.go | 127 | ||||
-rw-r--r-- | src/options.go | 10 | ||||
-rw-r--r-- | src/terminal.go | 62 | ||||
-rw-r--r-- | src/terminal_test.go | 57 |
4 files changed, 210 insertions, 46 deletions
diff --git a/src/actiontype_string.go b/src/actiontype_string.go new file mode 100644 index 00000000..90fa909a --- /dev/null +++ b/src/actiontype_string.go @@ -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]] +} diff --git a/src/options.go b/src/options.go index ca8b2476..0822817a 100644 --- a/src/options.go +++ b/src/options.go @@ -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": 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 } diff --git a/src/terminal_test.go b/src/terminal_test.go index ebb6bda5..271c64a3 100644 --- a/src/terminal_test.go +++ b/src/terminal_test.go @@ -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 { |