mirror of
https://github.com/Llewellynvdm/fzf.git
synced 2025-02-08 15:08:30 +00:00
parent
dbc854d5f4
commit
a89d8995c3
@ -223,6 +223,7 @@ e.g. \fBfzf --bind=ctrl-j:accept,ctrl-k:kill-line\fR
|
||||
\fBdown\fR \fIctrl-j ctrl-n down\fR
|
||||
\fBend-of-line\fR \fIctrl-e end\fR
|
||||
\fBexecute(...)\fR (see below for the details)
|
||||
\fBexecute-multi(...)\fR (see below for the details)
|
||||
\fBforward-char\fR \fIctrl-f right\fR
|
||||
\fBforward-word\fR \fIalt-f shift-right\fR
|
||||
\fBignore\fR
|
||||
@ -276,6 +277,12 @@ This is the special form that frees you from parse errors as it does not expect
|
||||
the closing character. The catch is that it should be the last one in the
|
||||
comma-separated list.
|
||||
.RE
|
||||
|
||||
\fBexecute-multi(...)\fR is an alternative action that executes the command
|
||||
with the selected entries when multi-select is enabled (\fB--multi\fR). With
|
||||
this action, \fB{}\fR is replaced with the double-quoted strings of the
|
||||
selected entries separated by spaces.
|
||||
|
||||
.RE
|
||||
.TP
|
||||
.BI "--history=" "HISTORY_FILE"
|
||||
|
@ -466,10 +466,13 @@ func parseKeymap(keymap map[int]actionType, execmap map[int]string, toggleSort b
|
||||
// Backreferences are not supported.
|
||||
// "~!@#$%^&*;/|".each_char.map { |c| Regexp.escape(c) }.map { |c| "#{c}[^#{c}]*#{c}" }.join('|')
|
||||
executeRegexp = regexp.MustCompile(
|
||||
"(?s):execute:.*|:execute(\\([^)]*\\)|\\[[^\\]]*\\]|~[^~]*~|![^!]*!|@[^@]*@|\\#[^\\#]*\\#|\\$[^\\$]*\\$|%[^%]*%|\\^[^\\^]*\\^|&[^&]*&|\\*[^\\*]*\\*|;[^;]*;|/[^/]*/|\\|[^\\|]*\\|)")
|
||||
"(?s):execute(-multi)?:.*|:execute(-multi)?(\\([^)]*\\)|\\[[^\\]]*\\]|~[^~]*~|![^!]*!|@[^@]*@|\\#[^\\#]*\\#|\\$[^\\$]*\\$|%[^%]*%|\\^[^\\^]*\\^|&[^&]*&|\\*[^\\*]*\\*|;[^;]*;|/[^/]*/|\\|[^\\|]*\\|)")
|
||||
}
|
||||
masked := executeRegexp.ReplaceAllStringFunc(str, func(src string) string {
|
||||
return ":execute(" + strings.Repeat(" ", len(src)-10) + ")"
|
||||
if strings.HasPrefix(src, ":execute-multi") {
|
||||
return ":execute-multi(" + strings.Repeat(" ", len(src)-len(":execute-multi()")) + ")"
|
||||
}
|
||||
return ":execute(" + strings.Repeat(" ", len(src)-len(":execute()")) + ")"
|
||||
})
|
||||
masked = strings.Replace(masked, "::", string([]rune{escapedColon, ':'}), -1)
|
||||
masked = strings.Replace(masked, ",:", string([]rune{escapedComma, ':'}), -1)
|
||||
@ -565,11 +568,18 @@ func parseKeymap(keymap map[int]actionType, execmap map[int]string, toggleSort b
|
||||
toggleSort = true
|
||||
default:
|
||||
if isExecuteAction(actLower) {
|
||||
keymap[key] = actExecute
|
||||
if act[7] == ':' {
|
||||
execmap[key] = act[8:]
|
||||
var offset int
|
||||
if strings.HasPrefix(actLower, "execute-multi") {
|
||||
keymap[key] = actExecuteMulti
|
||||
offset = len("execute-multi")
|
||||
} else {
|
||||
execmap[key] = act[8 : len(act)-1]
|
||||
keymap[key] = actExecute
|
||||
offset = len("execute")
|
||||
}
|
||||
if act[offset] == ':' {
|
||||
execmap[key] = act[offset+1:]
|
||||
} else {
|
||||
execmap[key] = act[offset+1 : len(act)-1]
|
||||
}
|
||||
} else {
|
||||
errorExit("unknown action: " + act)
|
||||
@ -580,10 +590,16 @@ func parseKeymap(keymap map[int]actionType, execmap map[int]string, toggleSort b
|
||||
}
|
||||
|
||||
func isExecuteAction(str string) bool {
|
||||
if !strings.HasPrefix(str, "execute") || len(str) < 9 {
|
||||
if !strings.HasPrefix(str, "execute") || len(str) < len("execute()") {
|
||||
return false
|
||||
}
|
||||
b := str[7]
|
||||
b := str[len("execute")]
|
||||
if strings.HasPrefix(str, "execute-multi") {
|
||||
if len(str) < len("execute-multi()") {
|
||||
return false
|
||||
}
|
||||
b = str[len("execute-multi")]
|
||||
}
|
||||
e := str[len(str)-1]
|
||||
if b == ':' || b == '(' && e == ')' || b == '[' && e == ']' ||
|
||||
b == e && strings.ContainsAny(string(b), "~!@#$%^&*;/|") {
|
||||
|
@ -132,6 +132,7 @@ const (
|
||||
actPreviousHistory
|
||||
actNextHistory
|
||||
actExecute
|
||||
actExecuteMulti
|
||||
)
|
||||
|
||||
func defaultKeymap() map[int]actionType {
|
||||
@ -305,16 +306,20 @@ func (t *Terminal) output() bool {
|
||||
found = true
|
||||
}
|
||||
} else {
|
||||
for _, sel := range t.sortSelected() {
|
||||
fmt.Println(*sel.text)
|
||||
}
|
||||
}
|
||||
return found
|
||||
}
|
||||
|
||||
func (t *Terminal) sortSelected() []selectedItem {
|
||||
sels := make([]selectedItem, 0, len(t.selected))
|
||||
for _, sel := range t.selected {
|
||||
sels = append(sels, sel)
|
||||
}
|
||||
sort.Sort(byTimeOrder(sels))
|
||||
for _, sel := range sels {
|
||||
fmt.Println(*sel.text)
|
||||
}
|
||||
}
|
||||
return found
|
||||
return sels
|
||||
}
|
||||
|
||||
func runeWidth(r rune, prefixWidth int) int {
|
||||
@ -698,8 +703,12 @@ func keyMatch(key int, event C.Event) bool {
|
||||
return event.Type == key || event.Type == C.Rune && int(event.Char) == key-C.AltZ
|
||||
}
|
||||
|
||||
func executeCommand(template string, current string) {
|
||||
command := strings.Replace(template, "{}", fmt.Sprintf("%q", current), -1)
|
||||
func quoteEntry(entry string) string {
|
||||
return fmt.Sprintf("%q", entry)
|
||||
}
|
||||
|
||||
func executeCommand(template string, replacement string) {
|
||||
command := strings.Replace(template, "{}", replacement, -1)
|
||||
cmd := exec.Command("sh", "-c", command)
|
||||
cmd.Stdin = os.Stdin
|
||||
cmd.Stdout = os.Stdout
|
||||
@ -858,7 +867,17 @@ func (t *Terminal) Loop() {
|
||||
case actExecute:
|
||||
if t.cy >= 0 && t.cy < t.merger.Length() {
|
||||
item := t.merger.Get(t.cy)
|
||||
executeCommand(t.execmap[mapkey], item.AsString(t.ansi))
|
||||
executeCommand(t.execmap[mapkey], quoteEntry(item.AsString(t.ansi)))
|
||||
}
|
||||
case actExecuteMulti:
|
||||
if len(t.selected) > 0 {
|
||||
sels := make([]string, len(t.selected))
|
||||
for i, sel := range t.sortSelected() {
|
||||
sels[i] = quoteEntry(*sel.text)
|
||||
}
|
||||
executeCommand(t.execmap[mapkey], strings.Join(sels, " "))
|
||||
} else {
|
||||
return doAction(actExecute, mapkey)
|
||||
}
|
||||
case actInvalid:
|
||||
t.mutex.Unlock()
|
||||
|
@ -713,6 +713,24 @@ class TestGoFZF < TestBase
|
||||
File.unlink output rescue nil
|
||||
end
|
||||
|
||||
def test_execute_multi
|
||||
output = '/tmp/fzf-test-execute-multi'
|
||||
opts = %[--multi --bind \\"alt-a:execute-multi(echo '[{}], @{}@' >> #{output})\\"]
|
||||
tmux.send_keys "seq 100 | #{fzf opts}", :Enter
|
||||
tmux.until { |lines| lines[-2].include? '100/100' }
|
||||
tmux.send_keys :Escape, :a
|
||||
tmux.send_keys :BTab, :BTab, :BTab
|
||||
tmux.send_keys :Escape, :a
|
||||
tmux.send_keys :Tab, :Tab
|
||||
tmux.send_keys :Escape, :a
|
||||
tmux.send_keys :Enter
|
||||
readonce
|
||||
assert_equal ['["1"], @"1"@', '["1" "2" "3"], @"1" "2" "3"@', '["1" "2" "4"], @"1" "2" "4"@'],
|
||||
File.readlines(output).map(&:chomp)
|
||||
ensure
|
||||
File.unlink output rescue nil
|
||||
end
|
||||
|
||||
def test_cycle
|
||||
tmux.send_keys "seq 8 | #{fzf :cycle}", :Enter
|
||||
tmux.until { |lines| lines[-2].include? '8/8' }
|
||||
|
Loading…
x
Reference in New Issue
Block a user