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
|
\fBdown\fR \fIctrl-j ctrl-n down\fR
|
||||||
\fBend-of-line\fR \fIctrl-e end\fR
|
\fBend-of-line\fR \fIctrl-e end\fR
|
||||||
\fBexecute(...)\fR (see below for the details)
|
\fBexecute(...)\fR (see below for the details)
|
||||||
|
\fBexecute-multi(...)\fR (see below for the details)
|
||||||
\fBforward-char\fR \fIctrl-f right\fR
|
\fBforward-char\fR \fIctrl-f right\fR
|
||||||
\fBforward-word\fR \fIalt-f shift-right\fR
|
\fBforward-word\fR \fIalt-f shift-right\fR
|
||||||
\fBignore\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
|
the closing character. The catch is that it should be the last one in the
|
||||||
comma-separated list.
|
comma-separated list.
|
||||||
.RE
|
.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
|
.RE
|
||||||
.TP
|
.TP
|
||||||
.BI "--history=" "HISTORY_FILE"
|
.BI "--history=" "HISTORY_FILE"
|
||||||
|
@ -466,10 +466,13 @@ func parseKeymap(keymap map[int]actionType, execmap map[int]string, toggleSort b
|
|||||||
// Backreferences are not supported.
|
// Backreferences are not supported.
|
||||||
// "~!@#$%^&*;/|".each_char.map { |c| Regexp.escape(c) }.map { |c| "#{c}[^#{c}]*#{c}" }.join('|')
|
// "~!@#$%^&*;/|".each_char.map { |c| Regexp.escape(c) }.map { |c| "#{c}[^#{c}]*#{c}" }.join('|')
|
||||||
executeRegexp = regexp.MustCompile(
|
executeRegexp = regexp.MustCompile(
|
||||||
"(?s):execute:.*|:execute(\\([^)]*\\)|\\[[^\\]]*\\]|~[^~]*~|![^!]*!|@[^@]*@|\\#[^\\#]*\\#|\\$[^\\$]*\\$|%[^%]*%|\\^[^\\^]*\\^|&[^&]*&|\\*[^\\*]*\\*|;[^;]*;|/[^/]*/|\\|[^\\|]*\\|)")
|
"(?s):execute(-multi)?:.*|:execute(-multi)?(\\([^)]*\\)|\\[[^\\]]*\\]|~[^~]*~|![^!]*!|@[^@]*@|\\#[^\\#]*\\#|\\$[^\\$]*\\$|%[^%]*%|\\^[^\\^]*\\^|&[^&]*&|\\*[^\\*]*\\*|;[^;]*;|/[^/]*/|\\|[^\\|]*\\|)")
|
||||||
}
|
}
|
||||||
masked := executeRegexp.ReplaceAllStringFunc(str, func(src string) string {
|
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{escapedColon, ':'}), -1)
|
||||||
masked = strings.Replace(masked, ",:", string([]rune{escapedComma, ':'}), -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
|
toggleSort = true
|
||||||
default:
|
default:
|
||||||
if isExecuteAction(actLower) {
|
if isExecuteAction(actLower) {
|
||||||
keymap[key] = actExecute
|
var offset int
|
||||||
if act[7] == ':' {
|
if strings.HasPrefix(actLower, "execute-multi") {
|
||||||
execmap[key] = act[8:]
|
keymap[key] = actExecuteMulti
|
||||||
|
offset = len("execute-multi")
|
||||||
} else {
|
} 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 {
|
} else {
|
||||||
errorExit("unknown action: " + act)
|
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 {
|
func isExecuteAction(str string) bool {
|
||||||
if !strings.HasPrefix(str, "execute") || len(str) < 9 {
|
if !strings.HasPrefix(str, "execute") || len(str) < len("execute()") {
|
||||||
return false
|
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]
|
e := str[len(str)-1]
|
||||||
if b == ':' || b == '(' && e == ')' || b == '[' && e == ']' ||
|
if b == ':' || b == '(' && e == ')' || b == '[' && e == ']' ||
|
||||||
b == e && strings.ContainsAny(string(b), "~!@#$%^&*;/|") {
|
b == e && strings.ContainsAny(string(b), "~!@#$%^&*;/|") {
|
||||||
|
@ -132,6 +132,7 @@ const (
|
|||||||
actPreviousHistory
|
actPreviousHistory
|
||||||
actNextHistory
|
actNextHistory
|
||||||
actExecute
|
actExecute
|
||||||
|
actExecuteMulti
|
||||||
)
|
)
|
||||||
|
|
||||||
func defaultKeymap() map[int]actionType {
|
func defaultKeymap() map[int]actionType {
|
||||||
@ -305,18 +306,22 @@ func (t *Terminal) output() bool {
|
|||||||
found = true
|
found = true
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
sels := make([]selectedItem, 0, len(t.selected))
|
for _, sel := range t.sortSelected() {
|
||||||
for _, sel := range t.selected {
|
|
||||||
sels = append(sels, sel)
|
|
||||||
}
|
|
||||||
sort.Sort(byTimeOrder(sels))
|
|
||||||
for _, sel := range sels {
|
|
||||||
fmt.Println(*sel.text)
|
fmt.Println(*sel.text)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return found
|
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))
|
||||||
|
return sels
|
||||||
|
}
|
||||||
|
|
||||||
func runeWidth(r rune, prefixWidth int) int {
|
func runeWidth(r rune, prefixWidth int) int {
|
||||||
if r == '\t' {
|
if r == '\t' {
|
||||||
return 8 - prefixWidth%8
|
return 8 - prefixWidth%8
|
||||||
@ -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
|
return event.Type == key || event.Type == C.Rune && int(event.Char) == key-C.AltZ
|
||||||
}
|
}
|
||||||
|
|
||||||
func executeCommand(template string, current string) {
|
func quoteEntry(entry string) string {
|
||||||
command := strings.Replace(template, "{}", fmt.Sprintf("%q", current), -1)
|
return fmt.Sprintf("%q", entry)
|
||||||
|
}
|
||||||
|
|
||||||
|
func executeCommand(template string, replacement string) {
|
||||||
|
command := strings.Replace(template, "{}", replacement, -1)
|
||||||
cmd := exec.Command("sh", "-c", command)
|
cmd := exec.Command("sh", "-c", command)
|
||||||
cmd.Stdin = os.Stdin
|
cmd.Stdin = os.Stdin
|
||||||
cmd.Stdout = os.Stdout
|
cmd.Stdout = os.Stdout
|
||||||
@ -858,7 +867,17 @@ func (t *Terminal) Loop() {
|
|||||||
case actExecute:
|
case actExecute:
|
||||||
if t.cy >= 0 && t.cy < t.merger.Length() {
|
if t.cy >= 0 && t.cy < t.merger.Length() {
|
||||||
item := t.merger.Get(t.cy)
|
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:
|
case actInvalid:
|
||||||
t.mutex.Unlock()
|
t.mutex.Unlock()
|
||||||
|
@ -713,6 +713,24 @@ class TestGoFZF < TestBase
|
|||||||
File.unlink output rescue nil
|
File.unlink output rescue nil
|
||||||
end
|
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
|
def test_cycle
|
||||||
tmux.send_keys "seq 8 | #{fzf :cycle}", :Enter
|
tmux.send_keys "seq 8 | #{fzf :cycle}", :Enter
|
||||||
tmux.until { |lines| lines[-2].include? '8/8' }
|
tmux.until { |lines| lines[-2].include? '8/8' }
|
||||||
|
Loading…
x
Reference in New Issue
Block a user