mirror of
https://github.com/junegunn/fzf.git
synced 2025-11-14 22:33:47 -05:00
Compare commits
16 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2023012408 | ||
|
|
95a7661bb1 | ||
|
|
618d317803 | ||
|
|
ae897c8cdb | ||
|
|
d0a0f3c052 | ||
|
|
91b9591b10 | ||
|
|
aa7361337d | ||
|
|
284d77fe2e | ||
|
|
826178f1e2 | ||
|
|
acccf8a9b8 | ||
|
|
57c066f0be | ||
|
|
e44f64ae92 | ||
|
|
d51980a3f5 | ||
|
|
c3d73e7ecb | ||
|
|
b077f6821d | ||
|
|
a79de11af7 |
27
CHANGELOG.md
27
CHANGELOG.md
@@ -1,6 +1,33 @@
|
||||
CHANGELOG
|
||||
=========
|
||||
|
||||
0.37.0
|
||||
------
|
||||
- Added a way to customize the separator of inline info
|
||||
```sh
|
||||
fzf --info 'inline: ╱ ' --prompt '╱ ' --color prompt:bright-yellow
|
||||
```
|
||||
- New event
|
||||
- `focus` - Triggered when the focus changes due to a vertical cursor
|
||||
movement or a search result update
|
||||
```sh
|
||||
fzf --bind 'focus:transform-preview-label:echo [ {} ]' --preview 'cat {}'
|
||||
|
||||
# Any action bound to the event runs synchronously and thus can make the interface sluggish
|
||||
# e.g. lolcat isn't one of the fastest programs, and every cursor movement in
|
||||
# fzf will be noticeably affected by its execution time
|
||||
fzf --bind 'focus:transform-preview-label:echo [ {} ] | lolcat -f' --preview 'cat {}'
|
||||
|
||||
# Beware not to introduce an infinite loop
|
||||
seq 10 | fzf --bind 'focus:up' --cycle
|
||||
```
|
||||
- New actions
|
||||
- `change-border-label`
|
||||
- `change-preview-label`
|
||||
- `transform-border-label`
|
||||
- `transform-preview-label`
|
||||
- Bug fixes and improvements
|
||||
|
||||
0.36.0
|
||||
------
|
||||
- Added `--listen=HTTP_PORT` option to start HTTP server. It allows external
|
||||
|
||||
30
README.md
30
README.md
@@ -202,7 +202,7 @@ files excluding hidden ones. (You can override the default command with
|
||||
vim $(fzf)
|
||||
```
|
||||
|
||||
#### Using the finder
|
||||
### Using the finder
|
||||
|
||||
- `CTRL-K` / `CTRL-J` (or `CTRL-P` / `CTRL-N`) to move cursor up and down
|
||||
- `Enter` key to select the item, `CTRL-C` / `CTRL-G` / `ESC` to exit
|
||||
@@ -211,7 +211,7 @@ vim $(fzf)
|
||||
- Mouse: scroll, click, double-click; shift-click and shift-scroll on
|
||||
multi-select mode
|
||||
|
||||
#### Layout
|
||||
### Layout
|
||||
|
||||
fzf by default starts in fullscreen mode, but you can make it start below the
|
||||
cursor with `--height` option.
|
||||
@@ -234,7 +234,7 @@ default. For example,
|
||||
export FZF_DEFAULT_OPTS='--height 40% --layout=reverse --border'
|
||||
```
|
||||
|
||||
#### Search syntax
|
||||
### Search syntax
|
||||
|
||||
Unless otherwise specified, fzf starts in "extended-search mode" where you can
|
||||
type in multiple search terms delimited by spaces. e.g. `^music .mp3$ sbtrkt
|
||||
@@ -262,7 +262,7 @@ or `py`.
|
||||
^core go$ | rb$ | py$
|
||||
```
|
||||
|
||||
#### Environment variables
|
||||
### Environment variables
|
||||
|
||||
- `FZF_DEFAULT_COMMAND`
|
||||
- Default command to use when input is tty
|
||||
@@ -278,11 +278,11 @@ or `py`.
|
||||
- Default options
|
||||
- e.g. `export FZF_DEFAULT_OPTS="--layout=reverse --inline-info"`
|
||||
|
||||
#### Options
|
||||
### Options
|
||||
|
||||
See the man page (`man fzf`) for the full list of options.
|
||||
|
||||
#### Demo
|
||||
### Demo
|
||||
If you learn by watching videos, check out this screencast by [@samoshkin](https://github.com/samoshkin) to explore `fzf` features.
|
||||
|
||||
<a title="fzf - command-line fuzzy finder" href="https://www.youtube.com/watch?v=qgG5Jhi_Els">
|
||||
@@ -335,7 +335,7 @@ fish.
|
||||
- Set `FZF_CTRL_T_COMMAND` to override the default command
|
||||
- Set `FZF_CTRL_T_OPTS` to pass additional options to fzf
|
||||
```sh
|
||||
# Preview file content using bat (https://github.com/sharkdp/fd)
|
||||
# Preview file content using bat (https://github.com/sharkdp/bat)
|
||||
export FZF_CTRL_T_OPTS="
|
||||
--preview 'bat -n --color=always {}'
|
||||
--bind 'ctrl-/:change-preview-window(down|hidden|)'"
|
||||
@@ -363,7 +363,7 @@ fish.
|
||||
```
|
||||
|
||||
If you're on a tmux session, you can start fzf in a tmux split-pane or in
|
||||
a tmux popup window by setting `FZF_TMUX_OPTS` (e.g. `-d 40%`).
|
||||
a tmux popup window by setting `FZF_TMUX_OPTS` (e.g. `export FZF_TMUX_OPTS='-p80%,60%'`).
|
||||
See `fzf-tmux --help` for available options.
|
||||
|
||||
More tips can be found on [the wiki page](https://github.com/junegunn/fzf/wiki/Configuring-shell-key-bindings).
|
||||
@@ -371,7 +371,7 @@ More tips can be found on [the wiki page](https://github.com/junegunn/fzf/wiki/C
|
||||
Fuzzy completion for bash and zsh
|
||||
---------------------------------
|
||||
|
||||
#### Files and directories
|
||||
### Files and directories
|
||||
|
||||
Fuzzy completion for files and directories can be triggered if the word before
|
||||
the cursor ends with the trigger sequence, which is by default `**`.
|
||||
@@ -400,7 +400,7 @@ cd **<TAB>
|
||||
cd ~/github/fzf**<TAB>
|
||||
```
|
||||
|
||||
#### Process IDs
|
||||
### Process IDs
|
||||
|
||||
Fuzzy completion for PIDs is provided for kill command.
|
||||
|
||||
@@ -409,7 +409,7 @@ Fuzzy completion for PIDs is provided for kill command.
|
||||
kill -9 **<TAB>
|
||||
```
|
||||
|
||||
#### Host names
|
||||
### Host names
|
||||
|
||||
For ssh and telnet commands, fuzzy completion for hostnames is provided. The
|
||||
names are extracted from /etc/hosts and ~/.ssh/config.
|
||||
@@ -419,7 +419,7 @@ ssh **<TAB>
|
||||
telnet **<TAB>
|
||||
```
|
||||
|
||||
#### Environment variables / Aliases
|
||||
### Environment variables / Aliases
|
||||
|
||||
```sh
|
||||
unset **<TAB>
|
||||
@@ -427,7 +427,7 @@ export **<TAB>
|
||||
unalias **<TAB>
|
||||
```
|
||||
|
||||
#### Settings
|
||||
### Settings
|
||||
|
||||
```sh
|
||||
# Use ~~ as the trigger sequence instead of the default **
|
||||
@@ -465,7 +465,7 @@ _fzf_comprun() {
|
||||
}
|
||||
```
|
||||
|
||||
#### Supported commands
|
||||
### Supported commands
|
||||
|
||||
On bash, fuzzy completion is enabled only for a predefined set of commands
|
||||
(`complete | grep _fzf` to see the list). But you can enable it for other
|
||||
@@ -477,7 +477,7 @@ _fzf_setup_completion path ag git kubectl
|
||||
_fzf_setup_completion dir tree
|
||||
```
|
||||
|
||||
#### Custom fuzzy completion
|
||||
### Custom fuzzy completion
|
||||
|
||||
_**(Custom completion API is experimental and subject to change)**_
|
||||
|
||||
|
||||
2
install
2
install
@@ -2,7 +2,7 @@
|
||||
|
||||
set -u
|
||||
|
||||
version=0.36.0
|
||||
version=0.37.0
|
||||
auto_completion=
|
||||
key_bindings=
|
||||
update_config=2
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
$version="0.36.0"
|
||||
$version="0.37.0"
|
||||
|
||||
$fzf_base=Split-Path -Parent $MyInvocation.MyCommand.Definition
|
||||
|
||||
|
||||
2
main.go
2
main.go
@@ -5,7 +5,7 @@ import (
|
||||
"github.com/junegunn/fzf/src/protector"
|
||||
)
|
||||
|
||||
var version string = "0.36"
|
||||
var version string = "0.37"
|
||||
var revision string = "devel"
|
||||
|
||||
func main() {
|
||||
|
||||
@@ -21,7 +21,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
..
|
||||
.TH fzf-tmux 1 "Jan 2023" "fzf 0.36.0" "fzf-tmux - open fzf in tmux split pane"
|
||||
.TH fzf-tmux 1 "Jan 2023" "fzf 0.37.0" "fzf-tmux - open fzf in tmux split pane"
|
||||
|
||||
.SH NAME
|
||||
fzf-tmux - open fzf in tmux split pane
|
||||
|
||||
@@ -21,7 +21,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
..
|
||||
.TH fzf 1 "Jan 2023" "fzf 0.36.0" "fzf - a command-line fuzzy finder"
|
||||
.TH fzf 1 "Jan 2023" "fzf 0.37.0" "fzf - a command-line fuzzy finder"
|
||||
|
||||
.SH NAME
|
||||
fzf - a command-line fuzzy finder
|
||||
@@ -339,7 +339,9 @@ Determines the display style of finder info (match counters).
|
||||
.br
|
||||
.BR default " Display on the next line to the prompt"
|
||||
.br
|
||||
.BR inline " Display on the same line"
|
||||
.BR inline " Display on the same line with the default separator ' < '"
|
||||
.br
|
||||
.BR inline:SEPARATOR " Display on the same line with a non-default separator"
|
||||
.br
|
||||
.BR hidden " Do not display finder info"
|
||||
.br
|
||||
@@ -959,6 +961,22 @@ e.g.
|
||||
\fB# Move cursor to the first entry whenever the query is changed
|
||||
fzf --bind change:first\fR
|
||||
.RE
|
||||
\fIfocus\fR
|
||||
.RS
|
||||
Triggered when the focus changes due to a vertical cursor movement or a search
|
||||
result update.
|
||||
|
||||
e.g.
|
||||
\fBfzf --bind 'focus:transform-preview-label:echo [ {} ]' --preview 'cat {}'
|
||||
|
||||
# Any action bound to the event runs synchronously and thus can make the interface sluggish
|
||||
# e.g. lolcat isn't one of the fastest programs, and every cursor movement in
|
||||
# fzf will be noticeably affected by its execution time
|
||||
fzf --bind 'focus:transform-preview-label:echo [ {} ] | lolcat -f' --preview 'cat {}'
|
||||
|
||||
# Beware not to introduce an infinite loop
|
||||
seq 10 | fzf --bind 'focus:up' --cycle\fR
|
||||
.RE
|
||||
|
||||
\fIbackward-eof\fR
|
||||
.RS
|
||||
@@ -983,7 +1001,9 @@ A key or an event can be bound to one or more of the following actions.
|
||||
\fBbackward-word\fR \fIalt-b shift-left\fR
|
||||
\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)
|
||||
\fBchange-preview(...)\fR (change \fB--preview\fR option)
|
||||
\fBchange-preview-label(...)\fR (change \fB--preview-label\fR to the given string)
|
||||
\fBchange-preview-window(...)\fR (change \fB--preview-window\fR option; rotate through the multiple option sets separated by '|')
|
||||
\fBchange-prompt(...)\fR (change prompt to the given string)
|
||||
\fBchange-query(...)\fR (change query string to the given string)
|
||||
@@ -1048,6 +1068,8 @@ A key or an event can be bound to one or more of the following actions.
|
||||
\fBtoggle-search\fR (toggle search functionality)
|
||||
\fBtoggle-sort\fR
|
||||
\fBtoggle+up\fR \fIbtab (shift-tab)\fR
|
||||
\fBtransform-border-label(...)\fR (transform border label using an external command)
|
||||
\fBtransform-preview-label(...)\fR (transform preview label using an external command)
|
||||
\fBtransform-prompt(...)\fR (transform prompt string using an external command)
|
||||
\fBtransform-query(...)\fR (transform query string using an external command)
|
||||
\fBunbind(...)\fR (unbind bindings)
|
||||
|
||||
@@ -310,7 +310,7 @@ complete -o default -F _fzf_opts_completion fzf-tmux
|
||||
d_cmds="${FZF_COMPLETION_DIR_COMMANDS:-cd pushd rmdir}"
|
||||
a_cmds="
|
||||
awk cat diff diff3
|
||||
emacs emacsclient ex file ftp g++ gcc gvim head hg java
|
||||
emacs emacsclient ex file ftp g++ gcc gvim head hg hx java
|
||||
javac ld less more mvim nvim patch perl python ruby
|
||||
sed sftp sort source tail tee uniq vi view vim wc xdg-open
|
||||
basename bunzip2 bzip2 chmod chown curl cp dirname du
|
||||
|
||||
@@ -70,7 +70,7 @@ const usage = `usage: fzf [options]
|
||||
(default: 0 or center)
|
||||
--margin=MARGIN Screen margin (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 [default|inline|hidden]
|
||||
--info=STYLE Finder info style [default|hidden|inline|inline:SEPARATOR]
|
||||
--separator=STR String to form horizontal separator on info line
|
||||
--no-separator Hide info line separator
|
||||
--scrollbar[=CHAR] Scrollbar character
|
||||
@@ -125,6 +125,8 @@ const usage = `usage: fzf [options]
|
||||
|
||||
`
|
||||
|
||||
const defaultInfoSep = " < "
|
||||
|
||||
// Case denotes case-sensitivity of search
|
||||
type Case int
|
||||
|
||||
@@ -246,6 +248,10 @@ func (a previewOpts) sameContentLayout(b previewOpts) bool {
|
||||
return a.wrap == b.wrap && a.headerLines == b.headerLines
|
||||
}
|
||||
|
||||
func firstLine(s string) string {
|
||||
return strings.SplitN(s, "\n", 2)[0]
|
||||
}
|
||||
|
||||
// Options stores the values of command-line options
|
||||
type Options struct {
|
||||
Fuzzy bool
|
||||
@@ -277,6 +283,7 @@ type Options struct {
|
||||
ScrollOff int
|
||||
FileWord bool
|
||||
InfoStyle infoStyle
|
||||
InfoSep string
|
||||
Separator *string
|
||||
JumpLabels string
|
||||
Prompt string
|
||||
@@ -609,6 +616,8 @@ func parseKeyChordsImpl(str string, message string, exit func(string)) map[tui.E
|
||||
add(tui.Start)
|
||||
case "load":
|
||||
add(tui.Load)
|
||||
case "focus":
|
||||
add(tui.Focus)
|
||||
case "alt-enter", "alt-return":
|
||||
chords[tui.CtrlAltKey('m')] = key
|
||||
case "alt-space":
|
||||
@@ -912,7 +921,7 @@ const (
|
||||
|
||||
func init() {
|
||||
executeRegexp = regexp.MustCompile(
|
||||
`(?si)[:+](execute(?:-multi|-silent)?|reload(?:-sync)?|preview|(?:change|transform)-(?:query|prompt)|change-preview-window|change-preview|(?:re|un)bind|pos|put)`)
|
||||
`(?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)`)
|
||||
splitRegexp = regexp.MustCompile("[,:]+")
|
||||
actionNameRegexp = regexp.MustCompile("(?i)^[a-z-]+")
|
||||
}
|
||||
@@ -954,7 +963,7 @@ Loop:
|
||||
ce = regexp.QuoteMeta(ce)
|
||||
|
||||
// @$ or @+
|
||||
loc = regexp.MustCompile(fmt.Sprintf(`^%s.*?(%s[+,]|%s$)`, cs, ce, ce)).FindStringIndex(action)
|
||||
loc = regexp.MustCompile(fmt.Sprintf(`(?s)^%s.*?(%s[+,]|%s$)`, cs, ce, ce)).FindStringIndex(action)
|
||||
if loc == nil {
|
||||
masked += action
|
||||
break
|
||||
@@ -1220,6 +1229,10 @@ func isExecuteAction(str string) actionType {
|
||||
return actRebind
|
||||
case "preview":
|
||||
return actPreview
|
||||
case "change-border-label":
|
||||
return actChangeBorderLabel
|
||||
case "change-preview-label":
|
||||
return actChangePreviewLabel
|
||||
case "change-preview-window":
|
||||
return actChangePreviewWindow
|
||||
case "change-preview":
|
||||
@@ -1238,6 +1251,10 @@ func isExecuteAction(str string) actionType {
|
||||
return actExecuteMulti
|
||||
case "put":
|
||||
return actPut
|
||||
case "transform-border-label":
|
||||
return actTransformBorderLabel
|
||||
case "transform-preview-label":
|
||||
return actTransformPreviewLabel
|
||||
case "transform-prompt":
|
||||
return actTransformPrompt
|
||||
case "transform-query":
|
||||
@@ -1309,18 +1326,22 @@ func parseLayout(str string) layoutType {
|
||||
return layoutDefault
|
||||
}
|
||||
|
||||
func parseInfoStyle(str string) infoStyle {
|
||||
func parseInfoStyle(str string) (infoStyle, string) {
|
||||
switch str {
|
||||
case "default":
|
||||
return infoDefault
|
||||
return infoDefault, ""
|
||||
case "inline":
|
||||
return infoInline
|
||||
return infoInline, defaultInfoSep
|
||||
case "hidden":
|
||||
return infoHidden
|
||||
return infoHidden, ""
|
||||
default:
|
||||
errorExit("invalid info style (expected: default|inline|hidden)")
|
||||
prefix := "inline:"
|
||||
if strings.HasPrefix(str, prefix) {
|
||||
return infoInline, strings.ReplaceAll(str[len(prefix):], "\n", " ")
|
||||
}
|
||||
return infoDefault
|
||||
errorExit("invalid info style (expected: default|hidden|inline|inline:SEPARATOR)")
|
||||
}
|
||||
return infoDefault, ""
|
||||
}
|
||||
|
||||
func parsePreviewWindow(opts *previewOpts, input string) {
|
||||
@@ -1588,12 +1609,13 @@ func parseOptions(opts *Options, allArgs []string) {
|
||||
case "--no-filepath-word":
|
||||
opts.FileWord = false
|
||||
case "--info":
|
||||
opts.InfoStyle = parseInfoStyle(
|
||||
opts.InfoStyle, opts.InfoSep = parseInfoStyle(
|
||||
nextString(allArgs, &i, "info style required"))
|
||||
case "--no-info":
|
||||
opts.InfoStyle = infoHidden
|
||||
case "--inline-info":
|
||||
opts.InfoStyle = infoInline
|
||||
opts.InfoSep = defaultInfoSep
|
||||
case "--no-inline-info":
|
||||
opts.InfoStyle = infoDefault
|
||||
case "--separator":
|
||||
@@ -1640,10 +1662,10 @@ func parseOptions(opts *Options, allArgs []string) {
|
||||
case "--prompt":
|
||||
opts.Prompt = nextString(allArgs, &i, "prompt string required")
|
||||
case "--pointer":
|
||||
opts.Pointer = nextString(allArgs, &i, "pointer sign string required")
|
||||
opts.Pointer = firstLine(nextString(allArgs, &i, "pointer sign string required"))
|
||||
validatePointer = true
|
||||
case "--marker":
|
||||
opts.Marker = nextString(allArgs, &i, "selected sign string required")
|
||||
opts.Marker = firstLine(nextString(allArgs, &i, "selected sign string required"))
|
||||
validateMarker = true
|
||||
case "--sync":
|
||||
opts.Sync = true
|
||||
@@ -1758,10 +1780,10 @@ func parseOptions(opts *Options, allArgs []string) {
|
||||
} else if match, value := optString(arg, "--prompt="); match {
|
||||
opts.Prompt = value
|
||||
} else if match, value := optString(arg, "--pointer="); match {
|
||||
opts.Pointer = value
|
||||
opts.Pointer = firstLine(value)
|
||||
validatePointer = true
|
||||
} else if match, value := optString(arg, "--marker="); match {
|
||||
opts.Marker = value
|
||||
opts.Marker = firstLine(value)
|
||||
validateMarker = true
|
||||
} else if match, value := optString(arg, "-n", "--nth="); match {
|
||||
opts.Nth = splitNth(value)
|
||||
@@ -1778,7 +1800,7 @@ func parseOptions(opts *Options, allArgs []string) {
|
||||
} else if match, value := optString(arg, "--layout="); match {
|
||||
opts.Layout = parseLayout(value)
|
||||
} else if match, value := optString(arg, "--info="); match {
|
||||
opts.InfoStyle = parseInfoStyle(value)
|
||||
opts.InfoStyle, opts.InfoSep = parseInfoStyle(value)
|
||||
} else if match, value := optString(arg, "--separator="); match {
|
||||
opts.Separator = &value
|
||||
} else if match, value := optString(arg, "--scrollbar="); match {
|
||||
|
||||
@@ -268,7 +268,7 @@ func TestBind(t *testing.T) {
|
||||
}
|
||||
parseKeymap(keymap,
|
||||
"ctrl-a:kill-line,ctrl-b:toggle-sort+up+down,c:page-up,alt-z:page-down,"+
|
||||
"f1:execute(ls {+})+abort+execute(echo {+})+select-all,f2:execute/echo {}, {}, {}/,f3:execute[echo '({})'],f4:execute;less {};,"+
|
||||
"f1:execute(ls {+})+abort+execute(echo \n{+})+select-all,f2:execute/echo {}, {}, {}/,f3:execute[echo '({})'],f4:execute;less {};,"+
|
||||
"alt-a:execute-Multi@echo (,),[,],/,:,;,%,{}@,alt-b:execute;echo (,),[,],/,:,@,%,{};,"+
|
||||
"x:Execute(foo+bar),X:execute/bar+baz/"+
|
||||
",f1:+first,f1:+top"+
|
||||
|
||||
@@ -147,6 +147,7 @@ type labelPrinter func(tui.Window, int)
|
||||
type Terminal struct {
|
||||
initDelay time.Duration
|
||||
infoStyle infoStyle
|
||||
infoSep string
|
||||
separator labelPrinter
|
||||
separatorLen int
|
||||
spinner []string
|
||||
@@ -276,6 +277,8 @@ const (
|
||||
reqRefresh
|
||||
reqReinit
|
||||
reqFullRedraw
|
||||
reqRedrawBorderLabel
|
||||
reqRedrawPreviewLabel
|
||||
reqClose
|
||||
reqPrintQuery
|
||||
reqPreviewEnqueue
|
||||
@@ -306,6 +309,8 @@ const (
|
||||
actBackwardDeleteCharEOF
|
||||
actBackwardWord
|
||||
actCancel
|
||||
actChangeBorderLabel
|
||||
actChangePreviewLabel
|
||||
actChangePrompt
|
||||
actChangeQuery
|
||||
actClearScreen
|
||||
@@ -347,6 +352,8 @@ const (
|
||||
actToggleSort
|
||||
actTogglePreview
|
||||
actTogglePreviewWrap
|
||||
actTransformBorderLabel
|
||||
actTransformPreviewLabel
|
||||
actTransformPrompt
|
||||
actTransformQuery
|
||||
actPreview
|
||||
@@ -573,6 +580,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
|
||||
t := Terminal{
|
||||
initDelay: delay,
|
||||
infoStyle: opts.InfoStyle,
|
||||
infoSep: opts.InfoSep,
|
||||
separator: nil,
|
||||
spinner: makeSpinner(opts.Unicode),
|
||||
queryLen: [2]int{0, 0},
|
||||
@@ -723,6 +731,7 @@ func (t *Terminal) ansiLabelPrinter(str string, color *tui.ColorPair, fill bool)
|
||||
}
|
||||
|
||||
// Extract ANSI color codes
|
||||
str = firstLine(str)
|
||||
text, colors, _ := extractColor(str, nil, nil)
|
||||
runes := []rune(text)
|
||||
|
||||
@@ -777,6 +786,7 @@ func (t *Terminal) ansiLabelPrinter(str string, color *tui.ColorPair, fill bool)
|
||||
|
||||
func (t *Terminal) parsePrompt(prompt string) (func(), int) {
|
||||
var state *ansiState
|
||||
prompt = firstLine(prompt)
|
||||
trimmed, colors, _ := extractColor(prompt, state, nil)
|
||||
item := &Item{text: util.ToChars([]byte(trimmed)), colors: colors}
|
||||
|
||||
@@ -1250,13 +1260,27 @@ func (t *Terminal) resizeWindows(forcePreview bool) {
|
||||
}
|
||||
|
||||
// Print border label
|
||||
printLabel := func(window tui.Window, render labelPrinter, opts labelOpts, length int, borderShape tui.BorderShape) {
|
||||
if window == nil || render == nil {
|
||||
t.printLabel(t.border, t.borderLabel, t.borderLabelOpts, t.borderLabelLen, t.borderShape, false)
|
||||
t.printLabel(t.pborder, t.previewLabel, t.previewLabelOpts, t.previewLabelLen, t.previewOpts.border, false)
|
||||
|
||||
for i := 0; i < t.window.Height(); i++ {
|
||||
t.window.MoveAndClear(i, 0)
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Terminal) printLabel(window tui.Window, render labelPrinter, opts labelOpts, length int, borderShape tui.BorderShape, redrawBorder bool) {
|
||||
if window == nil {
|
||||
return
|
||||
}
|
||||
|
||||
switch borderShape {
|
||||
case tui.BorderHorizontal, tui.BorderTop, tui.BorderBottom, tui.BorderRounded, tui.BorderSharp, tui.BorderBold, tui.BorderDouble:
|
||||
if redrawBorder {
|
||||
window.DrawHBorder()
|
||||
}
|
||||
if render == nil {
|
||||
return
|
||||
}
|
||||
var col int
|
||||
if opts.column == 0 {
|
||||
col = util.Max(0, (window.Width()-length)/2)
|
||||
@@ -1272,13 +1296,6 @@ func (t *Terminal) resizeWindows(forcePreview bool) {
|
||||
window.Move(row, col)
|
||||
render(window, window.Width())
|
||||
}
|
||||
}
|
||||
printLabel(t.border, t.borderLabel, t.borderLabelOpts, t.borderLabelLen, t.borderShape)
|
||||
printLabel(t.pborder, t.previewLabel, t.previewLabelOpts, t.previewLabelLen, t.previewOpts.border)
|
||||
|
||||
for i := 0; i < t.window.Height(); i++ {
|
||||
t.window.MoveAndClear(i, 0)
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Terminal) move(y int, x int, clear bool) {
|
||||
@@ -1380,16 +1397,21 @@ func (t *Terminal) printInfo() {
|
||||
pos = 2
|
||||
case infoInline:
|
||||
pos = t.promptLen + t.queryLen[0] + t.queryLen[1] + 1
|
||||
if pos+len(" < ") > t.window.Width() {
|
||||
return
|
||||
str := t.infoSep
|
||||
maxWidth := t.window.Width() - pos
|
||||
width := runewidth.StringWidth(str)
|
||||
if width > maxWidth {
|
||||
trimmed, _ := t.trimRight([]rune(str), maxWidth)
|
||||
str = string(trimmed)
|
||||
width = maxWidth
|
||||
}
|
||||
t.move(line, pos, t.separatorLen == 0)
|
||||
if t.reading {
|
||||
t.window.CPrint(tui.ColSpinner, " < ")
|
||||
t.window.CPrint(tui.ColSpinner, str)
|
||||
} else {
|
||||
t.window.CPrint(tui.ColPrompt, " < ")
|
||||
t.window.CPrint(tui.ColPrompt, str)
|
||||
}
|
||||
pos += len(" < ")
|
||||
pos += width
|
||||
case infoHidden:
|
||||
return
|
||||
}
|
||||
@@ -2231,7 +2253,6 @@ func (t *Terminal) executeCommand(template string, forcePlus bool, background bo
|
||||
t.redraw()
|
||||
t.refresh()
|
||||
} else {
|
||||
t.tui.Pause(false)
|
||||
if captureFirstLine {
|
||||
out, _ := cmd.StdoutPipe()
|
||||
reader := bufio.NewReader(out)
|
||||
@@ -2242,7 +2263,6 @@ func (t *Terminal) executeCommand(template string, forcePlus bool, background bo
|
||||
} else {
|
||||
cmd.Run()
|
||||
}
|
||||
t.tui.Resume(false, false)
|
||||
}
|
||||
t.executing.Set(false)
|
||||
cleanTemporaryFiles()
|
||||
@@ -2608,6 +2628,11 @@ func (t *Terminal) Loop() {
|
||||
}
|
||||
}
|
||||
|
||||
var onFocus []*action
|
||||
if actions, prs := t.keymap[tui.Focus.AsEvent()]; prs {
|
||||
onFocus = actions
|
||||
}
|
||||
|
||||
go func() {
|
||||
var focusedIndex int32 = minItem.Index()
|
||||
var version int64 = -1
|
||||
@@ -2643,7 +2668,11 @@ func (t *Terminal) Loop() {
|
||||
if currentItem != nil {
|
||||
currentIndex = currentItem.Index()
|
||||
}
|
||||
if focusedIndex != currentIndex || version != t.version {
|
||||
focusChanged := focusedIndex != currentIndex
|
||||
if onFocus != nil && focusChanged {
|
||||
t.serverChan <- onFocus
|
||||
}
|
||||
if focusChanged || version != t.version {
|
||||
version = t.version
|
||||
focusedIndex = currentIndex
|
||||
refreshPreview(t.previewOpts.command)
|
||||
@@ -2657,6 +2686,10 @@ func (t *Terminal) Loop() {
|
||||
t.printHeader()
|
||||
case reqRefresh:
|
||||
t.suppress = false
|
||||
case reqRedrawBorderLabel:
|
||||
t.printLabel(t.border, t.borderLabel, t.borderLabelOpts, t.borderLabelLen, t.borderShape, true)
|
||||
case reqRedrawPreviewLabel:
|
||||
t.printLabel(t.pborder, t.previewLabel, t.previewLabelOpts, t.previewLabelLen, t.previewOpts.border, true)
|
||||
case reqReinit:
|
||||
t.tui.Resume(t.fullscreen, t.sigstop)
|
||||
t.redraw()
|
||||
@@ -2909,6 +2942,28 @@ func (t *Terminal) Loop() {
|
||||
case actChangeQuery:
|
||||
t.input = []rune(a.a)
|
||||
t.cx = len(t.input)
|
||||
case actChangeBorderLabel:
|
||||
if t.border != nil {
|
||||
t.borderLabel, t.borderLabelLen = t.ansiLabelPrinter(a.a, &tui.ColBorderLabel, false)
|
||||
req(reqRedrawBorderLabel)
|
||||
}
|
||||
case actChangePreviewLabel:
|
||||
if t.pborder != nil {
|
||||
t.previewLabel, t.previewLabelLen = t.ansiLabelPrinter(a.a, &tui.ColPreviewLabel, false)
|
||||
req(reqRedrawPreviewLabel)
|
||||
}
|
||||
case actTransformBorderLabel:
|
||||
if t.border != nil {
|
||||
label := t.executeCommand(a.a, false, true, true)
|
||||
t.borderLabel, t.borderLabelLen = t.ansiLabelPrinter(label, &tui.ColBorderLabel, false)
|
||||
req(reqRedrawBorderLabel)
|
||||
}
|
||||
case actTransformPreviewLabel:
|
||||
if t.pborder != nil {
|
||||
label := t.executeCommand(a.a, false, true, true)
|
||||
t.previewLabel, t.previewLabelLen = t.ansiLabelPrinter(label, &tui.ColPreviewLabel, false)
|
||||
req(reqRedrawPreviewLabel)
|
||||
}
|
||||
case actChangePrompt:
|
||||
t.prompt, t.promptLen = t.parsePrompt(a.a)
|
||||
req(reqPrompt)
|
||||
|
||||
@@ -174,11 +174,7 @@ func (r *LightRenderer) Init() {
|
||||
}
|
||||
}
|
||||
|
||||
if r.mouse {
|
||||
r.csi("?1000h")
|
||||
r.csi("?1002h")
|
||||
r.csi("?1006h")
|
||||
}
|
||||
r.enableMouse()
|
||||
r.csi(fmt.Sprintf("%dA", r.MaxY()-1))
|
||||
r.csi("G")
|
||||
r.csi("K")
|
||||
@@ -609,6 +605,7 @@ func (r *LightRenderer) rmcup() {
|
||||
}
|
||||
|
||||
func (r *LightRenderer) Pause(clear bool) {
|
||||
r.disableMouse()
|
||||
r.restoreTerminal()
|
||||
if clear {
|
||||
if r.fullscreen {
|
||||
@@ -621,6 +618,22 @@ func (r *LightRenderer) Pause(clear bool) {
|
||||
}
|
||||
}
|
||||
|
||||
func (r *LightRenderer) enableMouse() {
|
||||
if r.mouse {
|
||||
r.csi("?1000h")
|
||||
r.csi("?1002h")
|
||||
r.csi("?1006h")
|
||||
}
|
||||
}
|
||||
|
||||
func (r *LightRenderer) disableMouse() {
|
||||
if r.mouse {
|
||||
r.csi("?1000l")
|
||||
r.csi("?1002l")
|
||||
r.csi("?1006l")
|
||||
}
|
||||
}
|
||||
|
||||
func (r *LightRenderer) Resume(clear bool, sigcont bool) {
|
||||
r.setupTerminal()
|
||||
if clear {
|
||||
@@ -629,14 +642,13 @@ func (r *LightRenderer) Resume(clear bool, sigcont bool) {
|
||||
} else {
|
||||
r.rmcup()
|
||||
}
|
||||
r.enableMouse()
|
||||
r.flush()
|
||||
} else if sigcont && !r.fullscreen && r.mouse {
|
||||
// NOTE: SIGCONT (Coming back from CTRL-Z):
|
||||
// It's highly likely that the offset we obtained at the beginning is
|
||||
// no longer correct, so we simply disable mouse input.
|
||||
r.csi("?1000l")
|
||||
r.csi("?1002l")
|
||||
r.csi("?1006l")
|
||||
r.disableMouse()
|
||||
r.mouse = false
|
||||
}
|
||||
}
|
||||
@@ -678,11 +690,7 @@ func (r *LightRenderer) Close() {
|
||||
} else if !r.fullscreen {
|
||||
r.csi("u")
|
||||
}
|
||||
if r.mouse {
|
||||
r.csi("?1000l")
|
||||
r.csi("?1002l")
|
||||
r.csi("?1006l")
|
||||
}
|
||||
r.disableMouse()
|
||||
r.flush()
|
||||
r.closePlatform()
|
||||
r.restoreTerminal()
|
||||
@@ -719,25 +727,38 @@ func (r *LightRenderer) NewWindow(top int, left int, width int, height int, prev
|
||||
w.fg = r.theme.Fg.Color
|
||||
w.bg = r.theme.Bg.Color
|
||||
}
|
||||
w.drawBorder()
|
||||
w.drawBorder(false)
|
||||
return w
|
||||
}
|
||||
|
||||
func (w *LightWindow) drawBorder() {
|
||||
func (w *LightWindow) DrawHBorder() {
|
||||
w.drawBorder(true)
|
||||
}
|
||||
|
||||
func (w *LightWindow) drawBorder(onlyHorizontal bool) {
|
||||
switch w.border.shape {
|
||||
case BorderRounded, BorderSharp, BorderBold, BorderDouble:
|
||||
w.drawBorderAround()
|
||||
w.drawBorderAround(onlyHorizontal)
|
||||
case BorderHorizontal:
|
||||
w.drawBorderHorizontal(true, true)
|
||||
case BorderVertical:
|
||||
if onlyHorizontal {
|
||||
return
|
||||
}
|
||||
w.drawBorderVertical(true, true)
|
||||
case BorderTop:
|
||||
w.drawBorderHorizontal(true, false)
|
||||
case BorderBottom:
|
||||
w.drawBorderHorizontal(false, true)
|
||||
case BorderLeft:
|
||||
if onlyHorizontal {
|
||||
return
|
||||
}
|
||||
w.drawBorderVertical(true, false)
|
||||
case BorderRight:
|
||||
if onlyHorizontal {
|
||||
return
|
||||
}
|
||||
w.drawBorderVertical(false, true)
|
||||
}
|
||||
}
|
||||
@@ -779,24 +800,26 @@ func (w *LightWindow) drawBorderVertical(left, right bool) {
|
||||
}
|
||||
}
|
||||
|
||||
func (w *LightWindow) drawBorderAround() {
|
||||
func (w *LightWindow) drawBorderAround(onlyHorizontal bool) {
|
||||
w.Move(0, 0)
|
||||
color := ColBorder
|
||||
if w.preview {
|
||||
color = ColPreviewBorder
|
||||
}
|
||||
hw := runewidth.RuneWidth(w.border.horizontal)
|
||||
vw := runewidth.RuneWidth(w.border.vertical)
|
||||
tcw := runewidth.RuneWidth(w.border.topLeft) + runewidth.RuneWidth(w.border.topRight)
|
||||
bcw := runewidth.RuneWidth(w.border.bottomLeft) + runewidth.RuneWidth(w.border.bottomRight)
|
||||
rem := (w.width - tcw) % hw
|
||||
w.CPrint(color, string(w.border.topLeft)+repeat(w.border.horizontal, (w.width-tcw)/hw)+repeat(' ', rem)+string(w.border.topRight))
|
||||
if !onlyHorizontal {
|
||||
vw := runewidth.RuneWidth(w.border.vertical)
|
||||
for y := 1; y < w.height-1; y++ {
|
||||
w.Move(y, 0)
|
||||
w.CPrint(color, string(w.border.vertical))
|
||||
w.CPrint(color, repeat(' ', w.width-vw*2))
|
||||
w.CPrint(color, string(w.border.vertical))
|
||||
}
|
||||
}
|
||||
w.Move(w.height-1, 0)
|
||||
rem = (w.width - bcw) % hw
|
||||
w.CPrint(color, string(w.border.bottomLeft)+repeat(w.border.horizontal, (w.width-bcw)/hw)+repeat(' ', rem)+string(w.border.bottomRight))
|
||||
@@ -1040,7 +1063,7 @@ func (w *LightWindow) FinishFill() {
|
||||
}
|
||||
|
||||
func (w *LightWindow) Erase() {
|
||||
w.drawBorder()
|
||||
w.drawBorder(false)
|
||||
// We don't erase the window here to avoid flickering during scroll
|
||||
w.Move(0, 0)
|
||||
}
|
||||
|
||||
@@ -512,7 +512,7 @@ func (r *FullscreenRenderer) NewWindow(top int, left int, width int, height int,
|
||||
height: height,
|
||||
normal: normal,
|
||||
borderStyle: borderStyle}
|
||||
w.drawBorder()
|
||||
w.drawBorder(false)
|
||||
return w
|
||||
}
|
||||
|
||||
@@ -670,7 +670,11 @@ func (w *TcellWindow) CFill(fg Color, bg Color, a Attr, str string) FillReturn {
|
||||
return w.fillString(str, NewColorPair(fg, bg, a))
|
||||
}
|
||||
|
||||
func (w *TcellWindow) drawBorder() {
|
||||
func (w *TcellWindow) DrawHBorder() {
|
||||
w.drawBorder(true)
|
||||
}
|
||||
|
||||
func (w *TcellWindow) drawBorder(onlyHorizontal bool) {
|
||||
shape := w.borderStyle.shape
|
||||
if shape == BorderNone {
|
||||
return
|
||||
@@ -718,6 +722,7 @@ func (w *TcellWindow) drawBorder() {
|
||||
_screen.SetContent(x, bot-1, w.borderStyle.horizontal, nil, style)
|
||||
}
|
||||
}
|
||||
if !onlyHorizontal {
|
||||
switch shape {
|
||||
case BorderRounded, BorderSharp, BorderBold, BorderDouble, BorderVertical, BorderLeft:
|
||||
for y := top; y < bot; y++ {
|
||||
@@ -731,6 +736,7 @@ func (w *TcellWindow) drawBorder() {
|
||||
_screen.SetContent(right-vw, y, w.borderStyle.vertical, nil, style)
|
||||
}
|
||||
}
|
||||
}
|
||||
switch shape {
|
||||
case BorderRounded, BorderSharp, BorderBold, BorderDouble:
|
||||
_screen.SetContent(left, top, w.borderStyle.topLeft, nil, style)
|
||||
|
||||
@@ -92,6 +92,7 @@ const (
|
||||
BackwardEOF
|
||||
Start
|
||||
Load
|
||||
Focus
|
||||
|
||||
AltBS
|
||||
|
||||
@@ -426,6 +427,7 @@ type Window interface {
|
||||
Width() int
|
||||
Height() int
|
||||
|
||||
DrawHBorder()
|
||||
Refresh()
|
||||
FinishFill()
|
||||
Close()
|
||||
|
||||
@@ -115,7 +115,7 @@ func TestAsUint16(t *testing.T) {
|
||||
if AsUint16(math.MinInt16) != 0 {
|
||||
t.Error("Expected", 0)
|
||||
}
|
||||
if AsUint16(math.MaxUint32) != math.MaxUint16 {
|
||||
if AsUint16(math.MaxUint16+1) != math.MaxUint16 {
|
||||
t.Error("Expected", math.MaxUint16)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1587,6 +1587,11 @@ class TestGoFZF < TestBase
|
||||
tmux.until { |lines| assert_equal '> 1', lines[-2] }
|
||||
end
|
||||
|
||||
def test_info_inline_separator
|
||||
tmux.send_keys 'seq 10 | fzf --info=inline:___ --no-separator', :Enter
|
||||
tmux.until { |lines| assert_equal '> ___10/10', lines[-1] }
|
||||
end
|
||||
|
||||
def test_change_first_last
|
||||
tmux.send_keys %(seq 1000 | #{FZF} --bind change:first,alt-Z:last), :Enter
|
||||
tmux.until { |lines| assert_equal 1000, lines.match_count }
|
||||
@@ -2473,12 +2478,31 @@ class TestGoFZF < TestBase
|
||||
end
|
||||
end
|
||||
|
||||
def test_focus_event
|
||||
tmux.send_keys 'seq 100 | fzf --bind "focus:transform-prompt(echo [[{}]])"', :Enter
|
||||
tmux.until { |lines| assert_includes(lines[-1], '[[1]]') }
|
||||
tmux.send_keys :Up
|
||||
tmux.until { |lines| assert_includes(lines[-1], '[[2]]') }
|
||||
tmux.send_keys :X
|
||||
tmux.until { |lines| assert_includes(lines[-1], '[[]]') }
|
||||
end
|
||||
|
||||
def test_labels_center
|
||||
tmux.send_keys ': | fzf --border --border-label foobar --preview : --preview-label barfoo', :Enter
|
||||
tmux.send_keys 'echo x | fzf --border --border-label foobar --preview : --preview-label barfoo --bind "space:change-border-label(foobarfoo)+change-preview-label(barfoobar),enter:transform-border-label(echo foo{}foo)+transform-preview-label(echo bar{}bar)"', :Enter
|
||||
tmux.until do
|
||||
assert_includes(_1[0], '─foobar─')
|
||||
assert_includes(_1[1], '─barfoo─')
|
||||
end
|
||||
tmux.send_keys :space
|
||||
tmux.until do
|
||||
assert_includes(_1[0], '─foobarfoo─')
|
||||
assert_includes(_1[1], '─barfoobar─')
|
||||
end
|
||||
tmux.send_keys :Enter
|
||||
tmux.until do
|
||||
assert_includes(_1[0], '─fooxfoo─')
|
||||
assert_includes(_1[1], '─barxbar─')
|
||||
end
|
||||
end
|
||||
|
||||
def test_labels_left
|
||||
|
||||
Reference in New Issue
Block a user