mirror of
https://github.com/Llewellynvdm/fzf.git
synced 2024-12-22 10:58:59 +00:00
parent
d9c028c934
commit
540632bb9e
@ -3,6 +3,13 @@ CHANGELOG
|
||||
|
||||
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`
|
||||
- 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.
|
||||
|
@ -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
|
||||
|
||||
.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
|
||||
.B "--no-info"
|
||||
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)
|
||||
--info=STYLE Finder info style
|
||||
[default|right|hidden|inline[-right][:PREFIX]]
|
||||
--info-command=COMMAND Command to generate info line
|
||||
--separator=STR String to form horizontal separator on info line
|
||||
--no-separator Hide info line separator
|
||||
--scrollbar[=C1[C2]] Scrollbar character(s) (each for main and preview window)
|
||||
@ -443,6 +444,7 @@ type Options struct {
|
||||
FileWord bool
|
||||
InfoStyle infoStyle
|
||||
InfoPrefix string
|
||||
InfoCommand string
|
||||
Separator *string
|
||||
JumpLabels 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 {
|
||||
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":
|
||||
opts.InfoStyle = infoHidden
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
} else if match, value := optString(arg, "--info-command="); match {
|
||||
opts.InfoCommand = value
|
||||
} else if match, value := optString(arg, "--separator="); match {
|
||||
opts.Separator = &value
|
||||
} else if match, value := optString(arg, "--scrollbar="); match {
|
||||
|
@ -207,6 +207,7 @@ type Status struct {
|
||||
// Terminal represents terminal input/output
|
||||
type Terminal struct {
|
||||
initDelay time.Duration
|
||||
infoCommand string
|
||||
infoStyle infoStyle
|
||||
infoPrefix string
|
||||
separator labelPrinter
|
||||
@ -753,6 +754,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox, executor *util.Executor
|
||||
|
||||
t := Terminal{
|
||||
initDelay: delay,
|
||||
infoCommand: opts.InfoCommand,
|
||||
infoStyle: opts.InfoStyle,
|
||||
infoPrefix: opts.InfoPrefix,
|
||||
separator: nil,
|
||||
@ -1840,6 +1842,12 @@ func (t *Terminal) printInfo() {
|
||||
if t.failed != nil && t.count == 0 {
|
||||
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 {
|
||||
maxWidth := t.window.Width()
|
||||
@ -1847,8 +1855,13 @@ func (t *Terminal) printInfo() {
|
||||
// Need space for spinner and a margin column
|
||||
maxWidth -= 2
|
||||
}
|
||||
output = t.trimMessage(output, maxWidth)
|
||||
fillLength := t.window.Width() - len(output) - 2
|
||||
var fillLength int
|
||||
if outputPrinter == nil {
|
||||
output = t.trimMessage(output, maxWidth)
|
||||
fillLength = t.window.Width() - len(output) - 2
|
||||
} else {
|
||||
fillLength = t.window.Width() - outputLen - 2
|
||||
}
|
||||
if t.reading {
|
||||
if fillLength >= 2 {
|
||||
printSeparator(fillLength-2, true)
|
||||
@ -1858,15 +1871,22 @@ func (t *Terminal) printInfo() {
|
||||
} else if fillLength >= 0 {
|
||||
printSeparator(fillLength, true)
|
||||
}
|
||||
t.window.CPrint(tui.ColInfo, output)
|
||||
if outputPrinter == nil {
|
||||
t.window.CPrint(tui.ColInfo, output)
|
||||
} else {
|
||||
outputPrinter(t.window, maxWidth)
|
||||
}
|
||||
t.window.Print(" ") // Margin
|
||||
return
|
||||
}
|
||||
|
||||
if t.infoStyle == infoInlineRight {
|
||||
if outputPrinter == nil {
|
||||
outputLen = util.StringWidth(output)
|
||||
}
|
||||
if len(t.infoPrefix) == 0 {
|
||||
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))
|
||||
pos = newPos
|
||||
if pos < t.window.Width() {
|
||||
@ -1878,14 +1898,18 @@ func (t *Terminal) printInfo() {
|
||||
pos++
|
||||
}
|
||||
} 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()
|
||||
}
|
||||
}
|
||||
|
||||
maxWidth := t.window.Width() - pos
|
||||
output = t.trimMessage(output, maxWidth)
|
||||
t.window.CPrint(tui.ColInfo, output)
|
||||
if outputPrinter == nil {
|
||||
output = t.trimMessage(output, maxWidth)
|
||||
t.window.CPrint(tui.ColInfo, output)
|
||||
} else {
|
||||
outputPrinter(t.window, maxWidth)
|
||||
}
|
||||
|
||||
if t.infoStyle == infoInlineRight {
|
||||
if t.separatorLen > 0 {
|
||||
@ -1895,7 +1919,7 @@ func (t *Terminal) printInfo() {
|
||||
return
|
||||
}
|
||||
|
||||
fillLength := maxWidth - len(output) - 2
|
||||
fillLength := maxWidth - outputLen - 2
|
||||
if fillLength > 0 {
|
||||
t.window.CPrint(tui.ColSeparator, " ")
|
||||
printSeparator(fillLength, false)
|
||||
@ -2983,7 +3007,7 @@ func (t *Terminal) fullRedraw() {
|
||||
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 := ""
|
||||
valid, list := t.buildPlusList(template, forcePlus)
|
||||
// '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)
|
||||
cmd := t.executor.ExecCommand(command, false)
|
||||
cmd.Env = t.environ()
|
||||
if len(info) > 0 {
|
||||
cmd.Env = append(cmd.Env, "FZF_INFO="+info)
|
||||
}
|
||||
t.executing.Set(true)
|
||||
if !background {
|
||||
// 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.uiMutex.Lock()
|
||||
if len(info) == 0 {
|
||||
t.uiMutex.Lock()
|
||||
}
|
||||
t.tui.Pause(true)
|
||||
cmd.Run()
|
||||
t.tui.Resume(true, false)
|
||||
t.mutex.Lock()
|
||||
t.fullRedraw()
|
||||
t.flush()
|
||||
t.uiMutex.Unlock()
|
||||
} else {
|
||||
t.mutex.Unlock()
|
||||
t.uiMutex.Lock()
|
||||
if len(info) == 0 {
|
||||
t.uiMutex.Lock()
|
||||
}
|
||||
if capture {
|
||||
out, _ := cmd.StdoutPipe()
|
||||
reader := bufio.NewReader(out)
|
||||
@ -3048,6 +3078,8 @@ func (t *Terminal) executeCommand(template string, forcePlus bool, background bo
|
||||
cmd.Run()
|
||||
}
|
||||
t.mutex.Lock()
|
||||
}
|
||||
if len(info) == 0 {
|
||||
t.uiMutex.Unlock()
|
||||
}
|
||||
t.executing.Set(false)
|
||||
@ -3528,13 +3560,20 @@ func (t *Terminal) Loop() error {
|
||||
t.printList()
|
||||
currentIndex := t.currentIndex()
|
||||
focusChanged := focusedIndex != currentIndex
|
||||
printInfo := false
|
||||
if focusChanged && t.track == trackCurrent {
|
||||
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.eventChan <- tui.Focus.AsEvent()
|
||||
if t.infoCommand != "" {
|
||||
printInfo = true
|
||||
}
|
||||
}
|
||||
if printInfo {
|
||||
t.printInfo()
|
||||
}
|
||||
if focusChanged || version != t.version {
|
||||
version = t.version
|
||||
@ -3809,9 +3848,9 @@ func (t *Terminal) Loop() error {
|
||||
}
|
||||
}
|
||||
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:
|
||||
t.executeCommand(a.a, true, false, false, false)
|
||||
t.executeCommand(a.a, true, false, false, false, "")
|
||||
case actInvalid:
|
||||
t.mutex.Unlock()
|
||||
return false
|
||||
@ -3852,12 +3891,12 @@ func (t *Terminal) Loop() error {
|
||||
req(reqPreviewRefresh)
|
||||
}
|
||||
case actTransformPrompt:
|
||||
prompt := t.executeCommand(a.a, false, true, true, true)
|
||||
prompt := t.executeCommand(a.a, false, true, true, true, "")
|
||||
t.promptString = prompt
|
||||
t.prompt, t.promptLen = t.parsePrompt(prompt)
|
||||
req(reqPrompt)
|
||||
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.cx = len(t.input)
|
||||
case actToggleSort:
|
||||
@ -3921,7 +3960,7 @@ func (t *Terminal) Loop() error {
|
||||
t.input = []rune(a.a)
|
||||
t.cx = len(t.input)
|
||||
case actTransformHeader:
|
||||
header := t.executeCommand(a.a, false, true, true, false)
|
||||
header := t.executeCommand(a.a, false, true, true, false, "")
|
||||
if t.changeHeader(header) {
|
||||
req(reqFullRedraw)
|
||||
} else {
|
||||
@ -3946,19 +3985,19 @@ func (t *Terminal) Loop() error {
|
||||
req(reqRedrawPreviewLabel)
|
||||
}
|
||||
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 {
|
||||
return doActions(actions)
|
||||
}
|
||||
case actTransformBorderLabel:
|
||||
label := t.executeCommand(a.a, false, true, true, true)
|
||||
label := t.executeCommand(a.a, false, true, true, true, "")
|
||||
t.borderLabelOpts.label = label
|
||||
if t.border != nil {
|
||||
t.borderLabel, t.borderLabelLen = t.ansiLabelPrinter(label, &tui.ColBorderLabel, false)
|
||||
req(reqRedrawBorderLabel)
|
||||
}
|
||||
case actTransformPreviewLabel:
|
||||
label := t.executeCommand(a.a, false, true, true, true)
|
||||
label := t.executeCommand(a.a, false, true, true, true, "")
|
||||
t.previewLabelOpts.label = label
|
||||
if t.pborder != nil {
|
||||
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]) }
|
||||
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
|
||||
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 }
|
||||
|
Loading…
Reference in New Issue
Block a user