mirror of
https://github.com/Llewellynvdm/fzf.git
synced 2024-11-29 00:06:29 +00:00
parent
d9c028c934
commit
540632bb9e
@ -3,6 +3,13 @@ CHANGELOG
|
|||||||
|
|
||||||
0.54.0
|
0.54.0
|
||||||
------
|
------
|
||||||
|
- Added `--info-command` option for customizing info text
|
||||||
|
```sh
|
||||||
|
# Prepend the current cursor position in yellow
|
||||||
|
fzf --info-command='echo -e "\x1b[33;1m$FZF_POS\x1b[m/$FZF_INFO 💛"'
|
||||||
|
```
|
||||||
|
- `$FZF_INFO` is set to the original info text
|
||||||
|
- ANSI color codes are supported
|
||||||
- Better cache management and improved rendering for `--tail`
|
- Better cache management and improved rendering for `--tail`
|
||||||
- Improved `--sync` behavior
|
- Improved `--sync` behavior
|
||||||
- When `--sync` is provided, fzf will not render the interface until the initial filtering and the associated actions (bound to any of `start`, `load`, `result`, or `focus`) are complete.
|
- When `--sync` is provided, fzf will not render the interface until the initial filtering and the associated actions (bound to any of `start`, `load`, `result`, or `focus`) are complete.
|
||||||
|
@ -452,6 +452,16 @@ Determines the display style of the finder info. (e.g. match counter, loading in
|
|||||||
.BR inline-right:PREFIX " On the right end of the prompt line with a custom prefix"
|
.BR inline-right:PREFIX " On the right end of the prompt line with a custom prefix"
|
||||||
.br
|
.br
|
||||||
|
|
||||||
|
.TP
|
||||||
|
.BI "--info-command=" "COMMAND"
|
||||||
|
Command to generate the finder info. The command runs synchronously and block
|
||||||
|
the UI until completion, so make sure that it's fast. ANSI color codes are
|
||||||
|
supported. \fB$FZF_INFO\f$ variable is set to the original info text.
|
||||||
|
|
||||||
|
e.g.
|
||||||
|
\fB# Prepend the current cursor position in yellow
|
||||||
|
fzf --info-command='echo -e "\\x1b[33;1m$FZF_POS\\x1b[m/$FZF_INFO 💛"'\fR
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.B "--no-info"
|
.B "--no-info"
|
||||||
A synonym for \fB--info=hidden\fB
|
A synonym for \fB--info=hidden\fB
|
||||||
|
@ -88,6 +88,7 @@ Usage: fzf [options]
|
|||||||
--padding=PADDING Padding inside border (TRBL | TB,RL | T,RL,B | T,R,B,L)
|
--padding=PADDING Padding inside border (TRBL | TB,RL | T,RL,B | T,R,B,L)
|
||||||
--info=STYLE Finder info style
|
--info=STYLE Finder info style
|
||||||
[default|right|hidden|inline[-right][:PREFIX]]
|
[default|right|hidden|inline[-right][:PREFIX]]
|
||||||
|
--info-command=COMMAND Command to generate info line
|
||||||
--separator=STR String to form horizontal separator on info line
|
--separator=STR String to form horizontal separator on info line
|
||||||
--no-separator Hide info line separator
|
--no-separator Hide info line separator
|
||||||
--scrollbar[=C1[C2]] Scrollbar character(s) (each for main and preview window)
|
--scrollbar[=C1[C2]] Scrollbar character(s) (each for main and preview window)
|
||||||
@ -443,6 +444,7 @@ type Options struct {
|
|||||||
FileWord bool
|
FileWord bool
|
||||||
InfoStyle infoStyle
|
InfoStyle infoStyle
|
||||||
InfoPrefix string
|
InfoPrefix string
|
||||||
|
InfoCommand string
|
||||||
Separator *string
|
Separator *string
|
||||||
JumpLabels string
|
JumpLabels string
|
||||||
Prompt string
|
Prompt string
|
||||||
@ -2189,6 +2191,12 @@ func parseOptions(index *int, opts *Options, allArgs []string) error {
|
|||||||
if opts.InfoStyle, opts.InfoPrefix, err = parseInfoStyle(str); err != nil {
|
if opts.InfoStyle, opts.InfoPrefix, err = parseInfoStyle(str); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
case "--info-command":
|
||||||
|
if opts.InfoCommand, err = nextString(allArgs, &i, "info command required"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
case "--no-info-command":
|
||||||
|
opts.InfoCommand = ""
|
||||||
case "--no-info":
|
case "--no-info":
|
||||||
opts.InfoStyle = infoHidden
|
opts.InfoStyle = infoHidden
|
||||||
case "--inline-info":
|
case "--inline-info":
|
||||||
@ -2543,6 +2551,8 @@ func parseOptions(index *int, opts *Options, allArgs []string) error {
|
|||||||
if opts.InfoStyle, opts.InfoPrefix, err = parseInfoStyle(value); err != nil {
|
if opts.InfoStyle, opts.InfoPrefix, err = parseInfoStyle(value); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
} else if match, value := optString(arg, "--info-command="); match {
|
||||||
|
opts.InfoCommand = value
|
||||||
} else if match, value := optString(arg, "--separator="); match {
|
} else if match, value := optString(arg, "--separator="); match {
|
||||||
opts.Separator = &value
|
opts.Separator = &value
|
||||||
} else if match, value := optString(arg, "--scrollbar="); match {
|
} else if match, value := optString(arg, "--scrollbar="); match {
|
||||||
|
@ -207,6 +207,7 @@ type Status struct {
|
|||||||
// Terminal represents terminal input/output
|
// Terminal represents terminal input/output
|
||||||
type Terminal struct {
|
type Terminal struct {
|
||||||
initDelay time.Duration
|
initDelay time.Duration
|
||||||
|
infoCommand string
|
||||||
infoStyle infoStyle
|
infoStyle infoStyle
|
||||||
infoPrefix string
|
infoPrefix string
|
||||||
separator labelPrinter
|
separator labelPrinter
|
||||||
@ -753,6 +754,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox, executor *util.Executor
|
|||||||
|
|
||||||
t := Terminal{
|
t := Terminal{
|
||||||
initDelay: delay,
|
initDelay: delay,
|
||||||
|
infoCommand: opts.InfoCommand,
|
||||||
infoStyle: opts.InfoStyle,
|
infoStyle: opts.InfoStyle,
|
||||||
infoPrefix: opts.InfoPrefix,
|
infoPrefix: opts.InfoPrefix,
|
||||||
separator: nil,
|
separator: nil,
|
||||||
@ -1840,6 +1842,12 @@ func (t *Terminal) printInfo() {
|
|||||||
if t.failed != nil && t.count == 0 {
|
if t.failed != nil && t.count == 0 {
|
||||||
output = fmt.Sprintf("[Command failed: %s]", *t.failed)
|
output = fmt.Sprintf("[Command failed: %s]", *t.failed)
|
||||||
}
|
}
|
||||||
|
var outputPrinter labelPrinter
|
||||||
|
var outputLen int
|
||||||
|
if t.infoCommand != "" {
|
||||||
|
output = t.executeCommand(t.infoCommand, false, true, true, true, output)
|
||||||
|
outputPrinter, outputLen = t.ansiLabelPrinter(output, &tui.ColInfo, false)
|
||||||
|
}
|
||||||
|
|
||||||
if t.infoStyle == infoRight {
|
if t.infoStyle == infoRight {
|
||||||
maxWidth := t.window.Width()
|
maxWidth := t.window.Width()
|
||||||
@ -1847,8 +1855,13 @@ func (t *Terminal) printInfo() {
|
|||||||
// Need space for spinner and a margin column
|
// Need space for spinner and a margin column
|
||||||
maxWidth -= 2
|
maxWidth -= 2
|
||||||
}
|
}
|
||||||
|
var fillLength int
|
||||||
|
if outputPrinter == nil {
|
||||||
output = t.trimMessage(output, maxWidth)
|
output = t.trimMessage(output, maxWidth)
|
||||||
fillLength := t.window.Width() - len(output) - 2
|
fillLength = t.window.Width() - len(output) - 2
|
||||||
|
} else {
|
||||||
|
fillLength = t.window.Width() - outputLen - 2
|
||||||
|
}
|
||||||
if t.reading {
|
if t.reading {
|
||||||
if fillLength >= 2 {
|
if fillLength >= 2 {
|
||||||
printSeparator(fillLength-2, true)
|
printSeparator(fillLength-2, true)
|
||||||
@ -1858,15 +1871,22 @@ func (t *Terminal) printInfo() {
|
|||||||
} else if fillLength >= 0 {
|
} else if fillLength >= 0 {
|
||||||
printSeparator(fillLength, true)
|
printSeparator(fillLength, true)
|
||||||
}
|
}
|
||||||
|
if outputPrinter == nil {
|
||||||
t.window.CPrint(tui.ColInfo, output)
|
t.window.CPrint(tui.ColInfo, output)
|
||||||
|
} else {
|
||||||
|
outputPrinter(t.window, maxWidth)
|
||||||
|
}
|
||||||
t.window.Print(" ") // Margin
|
t.window.Print(" ") // Margin
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if t.infoStyle == infoInlineRight {
|
if t.infoStyle == infoInlineRight {
|
||||||
|
if outputPrinter == nil {
|
||||||
|
outputLen = util.StringWidth(output)
|
||||||
|
}
|
||||||
if len(t.infoPrefix) == 0 {
|
if len(t.infoPrefix) == 0 {
|
||||||
move(line, pos, false)
|
move(line, pos, false)
|
||||||
newPos := util.Max(pos, t.window.Width()-util.StringWidth(output)-3)
|
newPos := util.Max(pos, t.window.Width()-outputLen-3)
|
||||||
t.window.Print(strings.Repeat(" ", newPos-pos))
|
t.window.Print(strings.Repeat(" ", newPos-pos))
|
||||||
pos = newPos
|
pos = newPos
|
||||||
if pos < t.window.Width() {
|
if pos < t.window.Width() {
|
||||||
@ -1878,14 +1898,18 @@ func (t *Terminal) printInfo() {
|
|||||||
pos++
|
pos++
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
pos = util.Max(pos, t.window.Width()-util.StringWidth(output)-util.StringWidth(t.infoPrefix)-1)
|
pos = util.Max(pos, t.window.Width()-outputLen-util.StringWidth(t.infoPrefix)-1)
|
||||||
printInfoPrefix()
|
printInfoPrefix()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
maxWidth := t.window.Width() - pos
|
maxWidth := t.window.Width() - pos
|
||||||
|
if outputPrinter == nil {
|
||||||
output = t.trimMessage(output, maxWidth)
|
output = t.trimMessage(output, maxWidth)
|
||||||
t.window.CPrint(tui.ColInfo, output)
|
t.window.CPrint(tui.ColInfo, output)
|
||||||
|
} else {
|
||||||
|
outputPrinter(t.window, maxWidth)
|
||||||
|
}
|
||||||
|
|
||||||
if t.infoStyle == infoInlineRight {
|
if t.infoStyle == infoInlineRight {
|
||||||
if t.separatorLen > 0 {
|
if t.separatorLen > 0 {
|
||||||
@ -1895,7 +1919,7 @@ func (t *Terminal) printInfo() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
fillLength := maxWidth - len(output) - 2
|
fillLength := maxWidth - outputLen - 2
|
||||||
if fillLength > 0 {
|
if fillLength > 0 {
|
||||||
t.window.CPrint(tui.ColSeparator, " ")
|
t.window.CPrint(tui.ColSeparator, " ")
|
||||||
printSeparator(fillLength, false)
|
printSeparator(fillLength, false)
|
||||||
@ -2983,7 +3007,7 @@ func (t *Terminal) fullRedraw() {
|
|||||||
t.printAll()
|
t.printAll()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Terminal) executeCommand(template string, forcePlus bool, background bool, capture bool, firstLineOnly bool) string {
|
func (t *Terminal) executeCommand(template string, forcePlus bool, background bool, capture bool, firstLineOnly bool, info string) string {
|
||||||
line := ""
|
line := ""
|
||||||
valid, list := t.buildPlusList(template, forcePlus)
|
valid, list := t.buildPlusList(template, forcePlus)
|
||||||
// 'capture' is used for transform-* and we don't want to
|
// 'capture' is used for transform-* and we don't want to
|
||||||
@ -2994,6 +3018,9 @@ func (t *Terminal) executeCommand(template string, forcePlus bool, background bo
|
|||||||
command, tempFiles := t.replacePlaceholder(template, forcePlus, string(t.input), list)
|
command, tempFiles := t.replacePlaceholder(template, forcePlus, string(t.input), list)
|
||||||
cmd := t.executor.ExecCommand(command, false)
|
cmd := t.executor.ExecCommand(command, false)
|
||||||
cmd.Env = t.environ()
|
cmd.Env = t.environ()
|
||||||
|
if len(info) > 0 {
|
||||||
|
cmd.Env = append(cmd.Env, "FZF_INFO="+info)
|
||||||
|
}
|
||||||
t.executing.Set(true)
|
t.executing.Set(true)
|
||||||
if !background {
|
if !background {
|
||||||
// Open a separate handle for tty input
|
// Open a separate handle for tty input
|
||||||
@ -3021,17 +3048,20 @@ func (t *Terminal) executeCommand(template string, forcePlus bool, background bo
|
|||||||
}
|
}
|
||||||
|
|
||||||
t.mutex.Unlock()
|
t.mutex.Unlock()
|
||||||
|
if len(info) == 0 {
|
||||||
t.uiMutex.Lock()
|
t.uiMutex.Lock()
|
||||||
|
}
|
||||||
t.tui.Pause(true)
|
t.tui.Pause(true)
|
||||||
cmd.Run()
|
cmd.Run()
|
||||||
t.tui.Resume(true, false)
|
t.tui.Resume(true, false)
|
||||||
t.mutex.Lock()
|
t.mutex.Lock()
|
||||||
t.fullRedraw()
|
t.fullRedraw()
|
||||||
t.flush()
|
t.flush()
|
||||||
t.uiMutex.Unlock()
|
|
||||||
} else {
|
} else {
|
||||||
t.mutex.Unlock()
|
t.mutex.Unlock()
|
||||||
|
if len(info) == 0 {
|
||||||
t.uiMutex.Lock()
|
t.uiMutex.Lock()
|
||||||
|
}
|
||||||
if capture {
|
if capture {
|
||||||
out, _ := cmd.StdoutPipe()
|
out, _ := cmd.StdoutPipe()
|
||||||
reader := bufio.NewReader(out)
|
reader := bufio.NewReader(out)
|
||||||
@ -3048,6 +3078,8 @@ func (t *Terminal) executeCommand(template string, forcePlus bool, background bo
|
|||||||
cmd.Run()
|
cmd.Run()
|
||||||
}
|
}
|
||||||
t.mutex.Lock()
|
t.mutex.Lock()
|
||||||
|
}
|
||||||
|
if len(info) == 0 {
|
||||||
t.uiMutex.Unlock()
|
t.uiMutex.Unlock()
|
||||||
}
|
}
|
||||||
t.executing.Set(false)
|
t.executing.Set(false)
|
||||||
@ -3528,13 +3560,20 @@ func (t *Terminal) Loop() error {
|
|||||||
t.printList()
|
t.printList()
|
||||||
currentIndex := t.currentIndex()
|
currentIndex := t.currentIndex()
|
||||||
focusChanged := focusedIndex != currentIndex
|
focusChanged := focusedIndex != currentIndex
|
||||||
|
printInfo := false
|
||||||
if focusChanged && t.track == trackCurrent {
|
if focusChanged && t.track == trackCurrent {
|
||||||
t.track = trackDisabled
|
t.track = trackDisabled
|
||||||
t.printInfo()
|
printInfo = true
|
||||||
}
|
}
|
||||||
if t.hasFocusActions && focusChanged && currentIndex != t.lastFocus {
|
if (t.hasFocusActions || t.infoCommand != "") && focusChanged && currentIndex != t.lastFocus {
|
||||||
t.lastFocus = currentIndex
|
t.lastFocus = currentIndex
|
||||||
t.eventChan <- tui.Focus.AsEvent()
|
t.eventChan <- tui.Focus.AsEvent()
|
||||||
|
if t.infoCommand != "" {
|
||||||
|
printInfo = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if printInfo {
|
||||||
|
t.printInfo()
|
||||||
}
|
}
|
||||||
if focusChanged || version != t.version {
|
if focusChanged || version != t.version {
|
||||||
version = t.version
|
version = t.version
|
||||||
@ -3809,9 +3848,9 @@ func (t *Terminal) Loop() error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
case actExecute, actExecuteSilent:
|
case actExecute, actExecuteSilent:
|
||||||
t.executeCommand(a.a, false, a.t == actExecuteSilent, false, false)
|
t.executeCommand(a.a, false, a.t == actExecuteSilent, false, false, "")
|
||||||
case actExecuteMulti:
|
case actExecuteMulti:
|
||||||
t.executeCommand(a.a, true, false, false, false)
|
t.executeCommand(a.a, true, false, false, false, "")
|
||||||
case actInvalid:
|
case actInvalid:
|
||||||
t.mutex.Unlock()
|
t.mutex.Unlock()
|
||||||
return false
|
return false
|
||||||
@ -3852,12 +3891,12 @@ func (t *Terminal) Loop() error {
|
|||||||
req(reqPreviewRefresh)
|
req(reqPreviewRefresh)
|
||||||
}
|
}
|
||||||
case actTransformPrompt:
|
case actTransformPrompt:
|
||||||
prompt := t.executeCommand(a.a, false, true, true, true)
|
prompt := t.executeCommand(a.a, false, true, true, true, "")
|
||||||
t.promptString = prompt
|
t.promptString = prompt
|
||||||
t.prompt, t.promptLen = t.parsePrompt(prompt)
|
t.prompt, t.promptLen = t.parsePrompt(prompt)
|
||||||
req(reqPrompt)
|
req(reqPrompt)
|
||||||
case actTransformQuery:
|
case actTransformQuery:
|
||||||
query := t.executeCommand(a.a, false, true, true, true)
|
query := t.executeCommand(a.a, false, true, true, true, "")
|
||||||
t.input = []rune(query)
|
t.input = []rune(query)
|
||||||
t.cx = len(t.input)
|
t.cx = len(t.input)
|
||||||
case actToggleSort:
|
case actToggleSort:
|
||||||
@ -3921,7 +3960,7 @@ func (t *Terminal) Loop() error {
|
|||||||
t.input = []rune(a.a)
|
t.input = []rune(a.a)
|
||||||
t.cx = len(t.input)
|
t.cx = len(t.input)
|
||||||
case actTransformHeader:
|
case actTransformHeader:
|
||||||
header := t.executeCommand(a.a, false, true, true, false)
|
header := t.executeCommand(a.a, false, true, true, false, "")
|
||||||
if t.changeHeader(header) {
|
if t.changeHeader(header) {
|
||||||
req(reqFullRedraw)
|
req(reqFullRedraw)
|
||||||
} else {
|
} else {
|
||||||
@ -3946,19 +3985,19 @@ func (t *Terminal) Loop() error {
|
|||||||
req(reqRedrawPreviewLabel)
|
req(reqRedrawPreviewLabel)
|
||||||
}
|
}
|
||||||
case actTransform:
|
case actTransform:
|
||||||
body := t.executeCommand(a.a, false, true, true, false)
|
body := t.executeCommand(a.a, false, true, true, false, "")
|
||||||
if actions, err := parseSingleActionList(strings.Trim(body, "\r\n")); err == nil {
|
if actions, err := parseSingleActionList(strings.Trim(body, "\r\n")); err == nil {
|
||||||
return doActions(actions)
|
return doActions(actions)
|
||||||
}
|
}
|
||||||
case actTransformBorderLabel:
|
case actTransformBorderLabel:
|
||||||
label := t.executeCommand(a.a, false, true, true, true)
|
label := t.executeCommand(a.a, false, true, true, true, "")
|
||||||
t.borderLabelOpts.label = label
|
t.borderLabelOpts.label = label
|
||||||
if t.border != nil {
|
if t.border != nil {
|
||||||
t.borderLabel, t.borderLabelLen = t.ansiLabelPrinter(label, &tui.ColBorderLabel, false)
|
t.borderLabel, t.borderLabelLen = t.ansiLabelPrinter(label, &tui.ColBorderLabel, false)
|
||||||
req(reqRedrawBorderLabel)
|
req(reqRedrawBorderLabel)
|
||||||
}
|
}
|
||||||
case actTransformPreviewLabel:
|
case actTransformPreviewLabel:
|
||||||
label := t.executeCommand(a.a, false, true, true, true)
|
label := t.executeCommand(a.a, false, true, true, true, "")
|
||||||
t.previewLabelOpts.label = label
|
t.previewLabelOpts.label = label
|
||||||
if t.pborder != nil {
|
if t.pborder != nil {
|
||||||
t.previewLabel, t.previewLabelLen = t.ansiLabelPrinter(label, &tui.ColPreviewLabel, false)
|
t.previewLabel, t.previewLabelLen = t.ansiLabelPrinter(label, &tui.ColPreviewLabel, false)
|
||||||
|
@ -2978,6 +2978,28 @@ class TestGoFZF < TestBase
|
|||||||
tmux.until { assert_match(%r{ 0/100000}, _1[-1]) }
|
tmux.until { assert_match(%r{ 0/100000}, _1[-1]) }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_info_command
|
||||||
|
tmux.send_keys(%(seq 10000 | #{FZF} --separator x --info-command 'echo -e "--\\x1b[33m$FZF_POS\\x1b[m/$FZF_INFO--"'), :Enter)
|
||||||
|
tmux.until { assert_match(%r{^ --1/10000/10000-- xx}, _1[-2]) }
|
||||||
|
tmux.send_keys :Up
|
||||||
|
tmux.until { assert_match(%r{^ --2/10000/10000-- xx}, _1[-2]) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_info_command_inline
|
||||||
|
tmux.send_keys(%(seq 10000 | #{FZF} --separator x --info-command 'echo -e "--\\x1b[33m$FZF_POS\\x1b[m/$FZF_INFO--"' --info inline:xx), :Enter)
|
||||||
|
tmux.until { assert_match(%r{^> xx--1/10000/10000-- xx}, _1[-1]) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_info_command_right
|
||||||
|
tmux.send_keys(%(seq 10000 | #{FZF} --separator x --info-command 'echo -e "--\\x1b[33m$FZF_POS\\x1b[m/$FZF_INFO--"' --info right), :Enter)
|
||||||
|
tmux.until { assert_match(%r{xx --1/10000/10000-- *$}, _1[-2]) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_info_command_inline_right
|
||||||
|
tmux.send_keys(%(seq 10000 | #{FZF} --info-command 'echo -e "--\\x1b[33m$FZF_POS\\x1b[m/$FZF_INFO--"' --info inline-right), :Enter)
|
||||||
|
tmux.until { assert_match(%r{ --1/10000/10000-- *$}, _1[-1]) }
|
||||||
|
end
|
||||||
|
|
||||||
def test_prev_next_selected
|
def test_prev_next_selected
|
||||||
tmux.send_keys 'seq 10 | fzf --multi --bind ctrl-n:next-selected,ctrl-p:prev-selected', :Enter
|
tmux.send_keys 'seq 10 | fzf --multi --bind ctrl-n:next-selected,ctrl-p:prev-selected', :Enter
|
||||||
tmux.until { |lines| assert_equal 10, lines.item_count }
|
tmux.until { |lines| assert_equal 10, lines.item_count }
|
||||||
|
Loading…
Reference in New Issue
Block a user