parent
ed57dcb924
commit
421b9b271a
@ -8,6 +8,10 @@ CHANGELOG
|
|||||||
- Placeholder expression used in `--preview` and `execute` action can
|
- Placeholder expression used in `--preview` and `execute` action can
|
||||||
optionally take `+` flag to be used with multiple selections
|
optionally take `+` flag to be used with multiple selections
|
||||||
- e.g. `git log --oneline | fzf --multi --preview 'git show {+1}'`
|
- e.g. `git log --oneline | fzf --multi --preview 'git show {+1}'`
|
||||||
|
- Added `execute-silent` action for executing a command silently without
|
||||||
|
switching to the alternate screen. This is useful when the process is
|
||||||
|
short-lived and you're not interested in its output.
|
||||||
|
- e.g. `fzf --bind 'ctrl-y:execute!(echo -n {} | pbcopy)'`
|
||||||
|
|
||||||
0.16.2
|
0.16.2
|
||||||
------
|
------
|
||||||
|
@ -469,6 +469,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-silent(...)\fR (see below for the details)
|
||||||
\fRexecute-multi(...)\fR (deprecated in favor of \fB{+}\fR expression)
|
\fRexecute-multi(...)\fR (deprecated in favor of \fB{+}\fR expression)
|
||||||
\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
|
||||||
@ -538,10 +539,10 @@ the closing character. The catch is that it should be the last one in the
|
|||||||
comma-separated list of key-action pairs.
|
comma-separated list of key-action pairs.
|
||||||
.RE
|
.RE
|
||||||
|
|
||||||
\fBexecute-multi(...)\fR is an alternative action that executes the command
|
fzf switches to the alternate screen when executing a command. However, if the
|
||||||
with the selected entries when multi-select is enabled (\fB--multi\fR). With
|
process is expected to complete quickly, and you are not interested in its
|
||||||
this action, \fB{}\fR is replaced with the quoted strings of the selected
|
output, you might want to use \fBexecute-silent\fR instead, which silently
|
||||||
entries separated by spaces.
|
executes the command without switching.
|
||||||
|
|
||||||
.SH AUTHOR
|
.SH AUTHOR
|
||||||
Junegunn Choi (\fIjunegunn.c@gmail.com\fR)
|
Junegunn Choi (\fIjunegunn.c@gmail.com\fR)
|
||||||
|
@ -581,18 +581,25 @@ const (
|
|||||||
escapedPlus = 2
|
escapedPlus = 2
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
// Backreferences are not supported.
|
||||||
|
// "~!@#$%^&*;/|".each_char.map { |c| Regexp.escape(c) }.map { |c| "#{c}[^#{c}]*#{c}" }.join('|')
|
||||||
|
executeRegexp = regexp.MustCompile(
|
||||||
|
"(?si):(execute(?:-multi|-silent)?):.+|:(execute(?:-multi|-silent)?)(\\([^)]*\\)|\\[[^\\]]*\\]|~[^~]*~|![^!]*!|@[^@]*@|\\#[^\\#]*\\#|\\$[^\\$]*\\$|%[^%]*%|\\^[^\\^]*\\^|&[^&]*&|\\*[^\\*]*\\*|;[^;]*;|/[^/]*/|\\|[^\\|]*\\|)")
|
||||||
|
}
|
||||||
|
|
||||||
func parseKeymap(keymap map[int][]action, str string) {
|
func parseKeymap(keymap map[int][]action, str string) {
|
||||||
if executeRegexp == nil {
|
|
||||||
// Backreferences are not supported.
|
|
||||||
// "~!@#$%^&*;/|".each_char.map { |c| Regexp.escape(c) }.map { |c| "#{c}[^#{c}]*#{c}" }.join('|')
|
|
||||||
executeRegexp = regexp.MustCompile(
|
|
||||||
"(?si):execute(-multi)?:.+|:execute(-multi)?(\\([^)]*\\)|\\[[^\\]]*\\]|~[^~]*~|![^!]*!|@[^@]*@|\\#[^\\#]*\\#|\\$[^\\$]*\\$|%[^%]*%|\\^[^\\^]*\\^|&[^&]*&|\\*[^\\*]*\\*|;[^;]*;|/[^/]*/|\\|[^\\|]*\\|)")
|
|
||||||
}
|
|
||||||
masked := executeRegexp.ReplaceAllStringFunc(str, func(src string) string {
|
masked := executeRegexp.ReplaceAllStringFunc(str, func(src string) string {
|
||||||
if src[len(":execute")] == '-' {
|
prefix := ":execute"
|
||||||
return ":execute-multi(" + strings.Repeat(" ", len(src)-len(":execute-multi()")) + ")"
|
if src[len(prefix)] == '-' {
|
||||||
|
c := src[len(prefix)+1]
|
||||||
|
if c == 's' || c == 'S' {
|
||||||
|
prefix += "-silent"
|
||||||
|
} else {
|
||||||
|
prefix += "-multi"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return ":execute(" + strings.Repeat(" ", len(src)-len(":execute()")) + ")"
|
return prefix + "(" + strings.Repeat(" ", len(src)-len(prefix)-2) + ")"
|
||||||
})
|
})
|
||||||
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)
|
||||||
@ -728,9 +735,12 @@ func parseKeymap(keymap map[int][]action, str string) {
|
|||||||
errorExit("unknown action: " + spec)
|
errorExit("unknown action: " + spec)
|
||||||
} else {
|
} else {
|
||||||
var offset int
|
var offset int
|
||||||
if t == actExecuteMulti {
|
switch t {
|
||||||
|
case actExecuteSilent:
|
||||||
|
offset = len("execute-silent")
|
||||||
|
case actExecuteMulti:
|
||||||
offset = len("execute-multi")
|
offset = len("execute-multi")
|
||||||
} else {
|
default:
|
||||||
offset = len("execute")
|
offset = len("execute")
|
||||||
}
|
}
|
||||||
if spec[offset] == ':' {
|
if spec[offset] == ':' {
|
||||||
@ -752,23 +762,21 @@ func parseKeymap(keymap map[int][]action, str string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func isExecuteAction(str string) actionType {
|
func isExecuteAction(str string) actionType {
|
||||||
t := actExecute
|
matches := executeRegexp.FindAllStringSubmatch(":"+str, -1)
|
||||||
if !strings.HasPrefix(str, "execute") || len(str) < len("execute(") {
|
if matches == nil || len(matches) != 1 {
|
||||||
return actIgnore
|
return actIgnore
|
||||||
}
|
}
|
||||||
|
prefix := matches[0][1]
|
||||||
b := str[len("execute")]
|
if len(prefix) == 0 {
|
||||||
if strings.HasPrefix(str, "execute-multi") {
|
prefix = matches[0][2]
|
||||||
if len(str) < len("execute-multi(") {
|
|
||||||
return actIgnore
|
|
||||||
}
|
|
||||||
t = actExecuteMulti
|
|
||||||
b = str[len("execute-multi")]
|
|
||||||
}
|
}
|
||||||
e := str[len(str)-1]
|
switch prefix {
|
||||||
if b == ':' || b == '(' && e == ')' || b == '[' && e == ']' ||
|
case "execute":
|
||||||
b == e && strings.ContainsAny(string(b), "~!@#$%^&*;/|") {
|
return actExecute
|
||||||
return t
|
case "execute-silent":
|
||||||
|
return actExecuteSilent
|
||||||
|
case "execute-multi":
|
||||||
|
return actExecuteMulti
|
||||||
}
|
}
|
||||||
return actIgnore
|
return actIgnore
|
||||||
}
|
}
|
||||||
|
@ -204,7 +204,8 @@ const (
|
|||||||
actPreviousHistory
|
actPreviousHistory
|
||||||
actNextHistory
|
actNextHistory
|
||||||
actExecute
|
actExecute
|
||||||
actExecuteMulti
|
actExecuteSilent
|
||||||
|
actExecuteMulti // Deprecated
|
||||||
)
|
)
|
||||||
|
|
||||||
func toActions(types ...actionType) []action {
|
func toActions(types ...actionType) []action {
|
||||||
@ -1126,22 +1127,26 @@ func replacePlaceholder(template string, stripAnsi bool, delimiter Delimiter, fo
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Terminal) executeCommand(template string, forcePlus bool) {
|
func (t *Terminal) executeCommand(template string, forcePlus bool, background bool) {
|
||||||
valid, list := t.buildPlusList(template, forcePlus)
|
valid, list := t.buildPlusList(template, forcePlus)
|
||||||
if !valid {
|
if !valid {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
command := replacePlaceholder(template, t.ansi, t.delimiter, forcePlus, string(t.input), list)
|
command := replacePlaceholder(template, t.ansi, t.delimiter, forcePlus, string(t.input), list)
|
||||||
cmd := util.ExecCommand(command)
|
cmd := util.ExecCommand(command)
|
||||||
cmd.Stdin = os.Stdin
|
if !background {
|
||||||
cmd.Stdout = os.Stdout
|
cmd.Stdin = os.Stdin
|
||||||
cmd.Stderr = os.Stderr
|
cmd.Stdout = os.Stdout
|
||||||
t.tui.Pause()
|
cmd.Stderr = os.Stderr
|
||||||
cmd.Run()
|
t.tui.Pause()
|
||||||
if t.tui.Resume() {
|
cmd.Run()
|
||||||
t.printAll()
|
if t.tui.Resume() {
|
||||||
|
t.printAll()
|
||||||
|
}
|
||||||
|
t.refresh()
|
||||||
|
} else {
|
||||||
|
cmd.Run()
|
||||||
}
|
}
|
||||||
t.refresh()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Terminal) hasPreviewer() bool {
|
func (t *Terminal) hasPreviewer() bool {
|
||||||
@ -1390,10 +1395,10 @@ func (t *Terminal) Loop() {
|
|||||||
doAction = func(a action, mapkey int) bool {
|
doAction = func(a action, mapkey int) bool {
|
||||||
switch a.t {
|
switch a.t {
|
||||||
case actIgnore:
|
case actIgnore:
|
||||||
case actExecute:
|
case actExecute, actExecuteSilent:
|
||||||
t.executeCommand(a.a, false)
|
t.executeCommand(a.a, false, a.t == actExecuteSilent)
|
||||||
case actExecuteMulti:
|
case actExecuteMulti:
|
||||||
t.executeCommand(a.a, true)
|
t.executeCommand(a.a, true, false)
|
||||||
case actInvalid:
|
case actInvalid:
|
||||||
t.mutex.Unlock()
|
t.mutex.Unlock()
|
||||||
return false
|
return false
|
||||||
|
@ -14,8 +14,10 @@ func newItem(str string) *Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestReplacePlaceholder(t *testing.T) {
|
func TestReplacePlaceholder(t *testing.T) {
|
||||||
items1 := []*Item{newItem(" foo'bar \x1b[31mbaz\x1b[m")}
|
item1 := newItem(" foo'bar \x1b[31mbaz\x1b[m")
|
||||||
|
items1 := []*Item{item1, item1}
|
||||||
items2 := []*Item{
|
items2 := []*Item{
|
||||||
|
newItem("foo'bar \x1b[31mbaz\x1b[m"),
|
||||||
newItem("foo'bar \x1b[31mbaz\x1b[m"),
|
newItem("foo'bar \x1b[31mbaz\x1b[m"),
|
||||||
newItem("FOO'BAR \x1b[31mBAZ\x1b[m")}
|
newItem("FOO'BAR \x1b[31mBAZ\x1b[m")}
|
||||||
|
|
||||||
@ -27,47 +29,65 @@ func TestReplacePlaceholder(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// {}, preserve ansi
|
// {}, preserve ansi
|
||||||
result = replacePlaceholder("echo {}", false, Delimiter{}, "query", items1)
|
result = replacePlaceholder("echo {}", false, Delimiter{}, false, "query", items1)
|
||||||
check("echo ' foo'\\''bar \x1b[31mbaz\x1b[m'")
|
check("echo ' foo'\\''bar \x1b[31mbaz\x1b[m'")
|
||||||
|
|
||||||
// {}, strip ansi
|
// {}, strip ansi
|
||||||
result = replacePlaceholder("echo {}", true, Delimiter{}, "query", items1)
|
result = replacePlaceholder("echo {}", true, Delimiter{}, false, "query", items1)
|
||||||
check("echo ' foo'\\''bar baz'")
|
check("echo ' foo'\\''bar baz'")
|
||||||
|
|
||||||
// {}, with multiple items
|
// {}, with multiple items
|
||||||
result = replacePlaceholder("echo {}", true, Delimiter{}, "query", items2)
|
result = replacePlaceholder("echo {}", true, Delimiter{}, false, "query", items2)
|
||||||
check("echo 'foo'\\''bar baz' 'FOO'\\''BAR BAZ'")
|
check("echo 'foo'\\''bar baz'")
|
||||||
|
|
||||||
// {..}, strip leading whitespaces, preserve ansi
|
// {..}, strip leading whitespaces, preserve ansi
|
||||||
result = replacePlaceholder("echo {..}", false, Delimiter{}, "query", items1)
|
result = replacePlaceholder("echo {..}", false, Delimiter{}, false, "query", items1)
|
||||||
check("echo 'foo'\\''bar \x1b[31mbaz\x1b[m'")
|
check("echo 'foo'\\''bar \x1b[31mbaz\x1b[m'")
|
||||||
|
|
||||||
// {..}, strip leading whitespaces, strip ansi
|
// {..}, strip leading whitespaces, strip ansi
|
||||||
result = replacePlaceholder("echo {..}", true, Delimiter{}, "query", items1)
|
result = replacePlaceholder("echo {..}", true, Delimiter{}, false, "query", items1)
|
||||||
check("echo 'foo'\\''bar baz'")
|
check("echo 'foo'\\''bar baz'")
|
||||||
|
|
||||||
// {q}
|
// {q}
|
||||||
result = replacePlaceholder("echo {} {q}", true, Delimiter{}, "query", items1)
|
result = replacePlaceholder("echo {} {q}", true, Delimiter{}, false, "query", items1)
|
||||||
check("echo ' foo'\\''bar baz' 'query'")
|
check("echo ' foo'\\''bar baz' 'query'")
|
||||||
|
|
||||||
// {q}, multiple items
|
// {q}, multiple items
|
||||||
result = replacePlaceholder("echo {}{q}{}", true, Delimiter{}, "query 'string'", items2)
|
result = replacePlaceholder("echo {+}{q}{+}", true, Delimiter{}, false, "query 'string'", items2)
|
||||||
check("echo 'foo'\\''bar baz' 'FOO'\\''BAR BAZ''query '\\''string'\\''''foo'\\''bar baz' 'FOO'\\''BAR BAZ'")
|
check("echo 'foo'\\''bar baz' 'FOO'\\''BAR BAZ''query '\\''string'\\''''foo'\\''bar baz' 'FOO'\\''BAR BAZ'")
|
||||||
|
|
||||||
result = replacePlaceholder("echo {1}/{2}/{2,1}/{-1}/{-2}/{}/{..}/{n.t}/\\{}/\\{1}/\\{q}/{3}", true, Delimiter{}, "query", items1)
|
result = replacePlaceholder("echo {}{q}{}", true, Delimiter{}, false, "query 'string'", items2)
|
||||||
|
check("echo 'foo'\\''bar baz''query '\\''string'\\''''foo'\\''bar baz'")
|
||||||
|
|
||||||
|
result = replacePlaceholder("echo {1}/{2}/{2,1}/{-1}/{-2}/{}/{..}/{n.t}/\\{}/\\{1}/\\{q}/{3}", true, Delimiter{}, false, "query", items1)
|
||||||
check("echo 'foo'\\''bar'/'baz'/'bazfoo'\\''bar'/'baz'/'foo'\\''bar'/' foo'\\''bar baz'/'foo'\\''bar baz'/{n.t}/{}/{1}/{q}/''")
|
check("echo 'foo'\\''bar'/'baz'/'bazfoo'\\''bar'/'baz'/'foo'\\''bar'/' foo'\\''bar baz'/'foo'\\''bar baz'/{n.t}/{}/{1}/{q}/''")
|
||||||
|
|
||||||
result = replacePlaceholder("echo {1}/{2}/{-1}/{-2}/{..}/{n.t}/\\{}/\\{1}/\\{q}/{3}", true, Delimiter{}, "query", items2)
|
result = replacePlaceholder("echo {1}/{2}/{-1}/{-2}/{..}/{n.t}/\\{}/\\{1}/\\{q}/{3}", true, Delimiter{}, false, "query", items2)
|
||||||
|
check("echo 'foo'\\''bar'/'baz'/'baz'/'foo'\\''bar'/'foo'\\''bar baz'/{n.t}/{}/{1}/{q}/''")
|
||||||
|
|
||||||
|
result = replacePlaceholder("echo {+1}/{+2}/{+-1}/{+-2}/{+..}/{n.t}/\\{}/\\{1}/\\{q}/{+3}", true, Delimiter{}, false, "query", items2)
|
||||||
check("echo 'foo'\\''bar' 'FOO'\\''BAR'/'baz' 'BAZ'/'baz' 'BAZ'/'foo'\\''bar' 'FOO'\\''BAR'/'foo'\\''bar baz' 'FOO'\\''BAR BAZ'/{n.t}/{}/{1}/{q}/'' ''")
|
check("echo 'foo'\\''bar' 'FOO'\\''BAR'/'baz' 'BAZ'/'baz' 'BAZ'/'foo'\\''bar' 'FOO'\\''BAR'/'foo'\\''bar baz' 'FOO'\\''BAR BAZ'/{n.t}/{}/{1}/{q}/'' ''")
|
||||||
|
|
||||||
|
// forcePlus
|
||||||
|
result = replacePlaceholder("echo {1}/{2}/{-1}/{-2}/{..}/{n.t}/\\{}/\\{1}/\\{q}/{3}", true, Delimiter{}, true, "query", items2)
|
||||||
|
check("echo 'foo'\\''bar' 'FOO'\\''BAR'/'baz' 'BAZ'/'baz' 'BAZ'/'foo'\\''bar' 'FOO'\\''BAR'/'foo'\\''bar baz' 'FOO'\\''BAR BAZ'/{n.t}/{}/{1}/{q}/'' ''")
|
||||||
|
|
||||||
|
// No match
|
||||||
|
result = replacePlaceholder("echo {}/{+}", true, Delimiter{}, false, "query", []*Item{nil, nil})
|
||||||
|
check("echo /")
|
||||||
|
|
||||||
|
// No match, but with selections
|
||||||
|
result = replacePlaceholder("echo {}/{+}", true, Delimiter{}, false, "query", []*Item{nil, item1})
|
||||||
|
check("echo /' foo'\\''bar baz'")
|
||||||
|
|
||||||
// String delimiter
|
// String delimiter
|
||||||
delim := "'"
|
delim := "'"
|
||||||
result = replacePlaceholder("echo {}/{1}/{2}", true, Delimiter{str: &delim}, "query", items1)
|
result = replacePlaceholder("echo {}/{1}/{2}", true, Delimiter{str: &delim}, false, "query", items1)
|
||||||
check("echo ' foo'\\''bar baz'/'foo'/'bar baz'")
|
check("echo ' foo'\\''bar baz'/'foo'/'bar baz'")
|
||||||
|
|
||||||
// Regex delimiter
|
// Regex delimiter
|
||||||
regex := regexp.MustCompile("[oa]+")
|
regex := regexp.MustCompile("[oa]+")
|
||||||
// foo'bar baz
|
// foo'bar baz
|
||||||
result = replacePlaceholder("echo {}/{1}/{3}/{2..3}", true, Delimiter{regex: regex}, "query", items1)
|
result = replacePlaceholder("echo {}/{1}/{3}/{2..3}", true, Delimiter{regex: regex}, false, "query", items1)
|
||||||
check("echo ' foo'\\''bar baz'/'f'/'r b'/''\\''bar b'")
|
check("echo ' foo'\\''bar baz'/'f'/'r b'/''\\''bar b'")
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user