mirror of
https://github.com/Llewellynvdm/fzf.git
synced 2024-11-18 02:55:11 +00:00
parent
f7447aece1
commit
6ea38b4438
@ -4,6 +4,15 @@ CHANGELOG
|
||||
0.38.0
|
||||
------
|
||||
- New actions
|
||||
- `become(...)` - Replace the current fzf process with the specified
|
||||
command using `execve(2)` system call. This action enables a simpler
|
||||
alternative to using `--expect` and checking the output in the wrapping
|
||||
script.
|
||||
```sh
|
||||
# Open selected files in different editors
|
||||
fzf --multi --bind 'enter:become($EDITOR {+}),ctrl-n:become(nano {+})'
|
||||
```
|
||||
- This action is not supported on Windows
|
||||
- `show-preview`
|
||||
- `hide-preview`
|
||||
- Bug fixes
|
||||
|
@ -999,6 +999,7 @@ A key or an event can be bound to one or more of the following actions.
|
||||
\fBbackward-delete-char/eof\fR (same as \fBbackward-delete-char\fR except aborts fzf if query is empty)
|
||||
\fBbackward-kill-word\fR \fIalt-bs\fR
|
||||
\fBbackward-word\fR \fIalt-b shift-left\fR
|
||||
\fBbecome(...)\fR (replace fzf process with the specified command; see below for the details)
|
||||
\fBbeginning-of-line\fR \fIctrl-a home\fR
|
||||
\fBcancel\fR (clear query string if not empty, abort fzf otherwise)
|
||||
\fBchange-border-label(...)\fR (change \fB--border-label\fR to the given string)
|
||||
@ -1143,6 +1144,14 @@ On *nix systems, fzf runs the command with \fB$SHELL -c\fR if \fBSHELL\fR is
|
||||
set, otherwise with \fBsh -c\fR, so in this case make sure that the command is
|
||||
POSIX-compliant.
|
||||
|
||||
\fBbecome(...)\fR action is similar to \fBexecute(...)\fR, but it replaces the
|
||||
current fzf process with the specifed command using \fBexecve(2)\fR system
|
||||
call.
|
||||
|
||||
\fBfzf --bind "enter:become(vim {})"\fR
|
||||
|
||||
\fBbecome(...)\fR is not supported on Windows.
|
||||
|
||||
.SS RELOAD INPUT
|
||||
|
||||
\fBreload(...)\fR action is used to dynamically update the input list
|
||||
|
@ -10,6 +10,7 @@ import (
|
||||
|
||||
"github.com/junegunn/fzf/src/algo"
|
||||
"github.com/junegunn/fzf/src/tui"
|
||||
"github.com/junegunn/fzf/src/util"
|
||||
|
||||
"github.com/mattn/go-runewidth"
|
||||
"github.com/mattn/go-shellwords"
|
||||
@ -921,7 +922,7 @@ const (
|
||||
|
||||
func init() {
|
||||
executeRegexp = regexp.MustCompile(
|
||||
`(?si)[:+](execute(?:-multi|-silent)?|reload(?:-sync)?|preview|(?:change|transform)-(?: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)-(?:query|prompt|border-label|preview-label)|change-preview-window|change-preview|(?:re|un)bind|pos|put)`)
|
||||
splitRegexp = regexp.MustCompile("[,:]+")
|
||||
actionNameRegexp = regexp.MustCompile("(?i)^[a-z-]+")
|
||||
}
|
||||
@ -1171,6 +1172,10 @@ func parseActionList(masked string, original string, prevActions []*action, putA
|
||||
actions = append(actions, &action{t: t, a: actionArg})
|
||||
}
|
||||
switch t {
|
||||
case actBecome:
|
||||
if util.IsWindows() {
|
||||
exit("become action is not supported on Windows")
|
||||
}
|
||||
case actUnbind, actRebind:
|
||||
parseKeyChordsImpl(actionArg, spec[0:offset]+" target required", exit)
|
||||
case actChangePreviewWindow:
|
||||
@ -1223,6 +1228,8 @@ func isExecuteAction(str string) actionType {
|
||||
|
||||
prefix := actionNameRegexp.FindString(str)
|
||||
switch prefix {
|
||||
case "become":
|
||||
return actBecome
|
||||
case "reload":
|
||||
return actReload
|
||||
case "reload-sync":
|
||||
|
@ -6,6 +6,7 @@ import (
|
||||
"io/ioutil"
|
||||
"math"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/signal"
|
||||
"regexp"
|
||||
"sort"
|
||||
@ -387,6 +388,7 @@ const (
|
||||
actDeselect
|
||||
actUnbind
|
||||
actRebind
|
||||
actBecome
|
||||
)
|
||||
|
||||
type placeholderFlags struct {
|
||||
@ -2237,7 +2239,7 @@ func (t *Terminal) redraw() {
|
||||
|
||||
func (t *Terminal) executeCommand(template string, forcePlus bool, background bool, captureFirstLine bool) string {
|
||||
line := ""
|
||||
valid, list := t.buildPlusList(template, forcePlus, false)
|
||||
valid, list := t.buildPlusList(template, forcePlus)
|
||||
// captureFirstLine is used for transform-{prompt,query} and we don't want to
|
||||
// return an empty string in those cases
|
||||
if !valid && !captureFirstLine {
|
||||
@ -2297,10 +2299,10 @@ func (t *Terminal) currentItem() *Item {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *Terminal) buildPlusList(template string, forcePlus bool, forceEvaluation bool) (bool, []*Item) {
|
||||
func (t *Terminal) buildPlusList(template string, forcePlus bool) (bool, []*Item) {
|
||||
current := t.currentItem()
|
||||
slot, plus, query := hasPreviewFlags(template)
|
||||
if !forceEvaluation && !(!slot || query || (forcePlus || plus) && len(t.selected) > 0) {
|
||||
if !(!slot || query || (forcePlus || plus) && len(t.selected) > 0) {
|
||||
return current != nil, []*Item{current, current}
|
||||
}
|
||||
|
||||
@ -2625,7 +2627,7 @@ func (t *Terminal) Loop() {
|
||||
|
||||
refreshPreview := func(command string) {
|
||||
if len(command) > 0 && t.canPreview() {
|
||||
_, list := t.buildPlusList(command, false, false)
|
||||
_, list := t.buildPlusList(command, false)
|
||||
t.cancelPreview()
|
||||
t.previewBox.Set(reqPreviewEnqueue, previewRequest{command, t.pwindow, t.evaluateScrollOffset(), list})
|
||||
}
|
||||
@ -2860,6 +2862,21 @@ func (t *Terminal) Loop() {
|
||||
doAction = func(a *action) bool {
|
||||
switch a.t {
|
||||
case actIgnore:
|
||||
case actBecome:
|
||||
_, list := t.buildPlusList(a.a, false)
|
||||
command := t.replacePlaceholder(a.a, false, string(t.input), list)
|
||||
shell := os.Getenv("SHELL")
|
||||
if len(shell) == 0 {
|
||||
shell = "sh"
|
||||
}
|
||||
shellPath, err := exec.LookPath(shell)
|
||||
if err == nil {
|
||||
t.tui.Close()
|
||||
if t.history != nil {
|
||||
t.history.append(string(t.input))
|
||||
}
|
||||
syscall.Exec(shellPath, []string{shell, "-c", command}, os.Environ())
|
||||
}
|
||||
case actExecute, actExecuteSilent:
|
||||
t.executeCommand(a.a, false, a.t == actExecuteSilent, false)
|
||||
case actExecuteMulti:
|
||||
@ -2881,7 +2898,7 @@ func (t *Terminal) Loop() {
|
||||
t.activePreviewOpts.Toggle()
|
||||
updatePreviewWindow(false)
|
||||
if t.canPreview() {
|
||||
valid, list := t.buildPlusList(t.previewOpts.command, false, false)
|
||||
valid, list := t.buildPlusList(t.previewOpts.command, false)
|
||||
if valid {
|
||||
t.cancelPreview()
|
||||
t.previewBox.Set(reqPreviewEnqueue,
|
||||
@ -3360,7 +3377,7 @@ func (t *Terminal) Loop() {
|
||||
case actReload, actReloadSync:
|
||||
t.failed = nil
|
||||
|
||||
valid, list := t.buildPlusList(a.a, false, false)
|
||||
valid, list := t.buildPlusList(a.a, false)
|
||||
if !valid {
|
||||
// We run the command even when there's no match
|
||||
// 1. If the template doesn't have any slots
|
||||
|
@ -2643,6 +2643,13 @@ class TestGoFZF < TestBase
|
||||
tmux.send_keys :Space
|
||||
tmux.until { |lines| assert_includes lines, '/1/1/' }
|
||||
end
|
||||
|
||||
def test_become
|
||||
tmux.send_keys "seq 10 | #{FZF} --bind 'enter:become:seq 100 | #{FZF}'", :Enter
|
||||
tmux.until { |lines| assert_equal 10, lines.item_count }
|
||||
tmux.send_keys :Enter
|
||||
tmux.until { |lines| assert_equal 100, lines.item_count }
|
||||
end
|
||||
end
|
||||
|
||||
module TestShell
|
||||
|
Loading…
Reference in New Issue
Block a user