mirror of
https://github.com/junegunn/fzf.git
synced 2025-08-14 11:45:48 -07:00
Compare commits
15 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
fb76893e18 | ||
|
88d812fe82 | ||
|
77f9f4664a | ||
|
5c2f85c39e | ||
|
ac4d22cd12 | ||
|
cf95e44cb4 | ||
|
65dd2bb429 | ||
|
6be855be6a | ||
|
b6e3f4423b | ||
|
0c61d81713 | ||
|
7c6f5dba63 | ||
|
44cfc7e62a | ||
|
96670d5f16 | ||
|
36b971ee4e | ||
|
f1a9629652 |
34
CHANGELOG.md
34
CHANGELOG.md
@@ -1,6 +1,38 @@
|
|||||||
CHANGELOG
|
CHANGELOG
|
||||||
=========
|
=========
|
||||||
|
|
||||||
|
0.40.0
|
||||||
|
------
|
||||||
|
- Added `zero` event that is triggered when there's no match
|
||||||
|
```sh
|
||||||
|
# Reload the candidate list when there's no match
|
||||||
|
echo $RANDOM | fzf --bind 'zero:reload(echo $RANDOM)+clear-query' --height 3
|
||||||
|
```
|
||||||
|
- New actions
|
||||||
|
- Added `track` action which makes fzf track the current item when the
|
||||||
|
search result is updated. If the user manually moves the cursor, or the
|
||||||
|
item is not in the updated search result, tracking is automatically
|
||||||
|
disabled. Tracking is useful when you want to see the surrounding items
|
||||||
|
by deleting the query string.
|
||||||
|
```sh
|
||||||
|
# Narrow down the list with a query, point to a command,
|
||||||
|
# and hit CTRL-T to see its surrounding commands.
|
||||||
|
export FZF_CTRL_R_OPTS="
|
||||||
|
--preview 'echo {}' --preview-window up:3:hidden:wrap
|
||||||
|
--bind 'ctrl-/:toggle-preview'
|
||||||
|
--bind 'ctrl-t:track+clear-query'
|
||||||
|
--bind 'ctrl-y:execute-silent(echo -n {2..} | pbcopy)+abort'
|
||||||
|
--color header:italic
|
||||||
|
--header 'Press CTRL-Y to copy command into clipboard'"
|
||||||
|
```
|
||||||
|
- Added `change-header(...)`
|
||||||
|
- Added `transform-header(...)`
|
||||||
|
- Added `toggle-track` action
|
||||||
|
- Fixed `--track` behavior when used with `--tac`
|
||||||
|
- However, using `--track` with `--tac` is not recommended. The resulting
|
||||||
|
behavior can be very confusing.
|
||||||
|
- Bug fixes and improvements
|
||||||
|
|
||||||
0.39.0
|
0.39.0
|
||||||
------
|
------
|
||||||
- Added `one` event that is triggered when there's only one match
|
- Added `one` event that is triggered when there's only one match
|
||||||
@@ -176,7 +208,7 @@ CHANGELOG
|
|||||||
- Added color name `preview-label` for `--preview-label` (defaults to `label`
|
- Added color name `preview-label` for `--preview-label` (defaults to `label`
|
||||||
for `--border-label`)
|
for `--border-label`)
|
||||||
- Better support for (Windows) terminals where each box-drawing character
|
- Better support for (Windows) terminals where each box-drawing character
|
||||||
takes 2 columns. Set `RUNEWIDTH_EASTASIAN` environment variable to `1`.
|
takes 2 columns. Set `RUNEWIDTH_EASTASIAN` environment variable to `0` or `1`.
|
||||||
- On Vim, the variable will be automatically set if `&ambiwidth` is `double`
|
- On Vim, the variable will be automatically set if `&ambiwidth` is `double`
|
||||||
- Behavior changes
|
- Behavior changes
|
||||||
- fzf will always execute the preview command if the command template
|
- fzf will always execute the preview command if the command template
|
||||||
|
@@ -180,7 +180,7 @@ trap 'cleanup' EXIT
|
|||||||
envs="export TERM=$TERM "
|
envs="export TERM=$TERM "
|
||||||
if [[ "$opt" =~ "-E" ]]; then
|
if [[ "$opt" =~ "-E" ]]; then
|
||||||
tmux_version=$(tmux -V | sed 's/[^0-9.]//g')
|
tmux_version=$(tmux -V | sed 's/[^0-9.]//g')
|
||||||
if [[ $(bc -l <<< "$tmux_version > 3.2") = 1 ]]; then
|
if [[ $(awk '{print ($1 > 3.2)}' <<< "$tmux_version" 2> /dev/null || bc -l <<< "$tmux_version > 3.2") = 1 ]]; then
|
||||||
FZF_DEFAULT_OPTS="--border $FZF_DEFAULT_OPTS"
|
FZF_DEFAULT_OPTS="--border $FZF_DEFAULT_OPTS"
|
||||||
opt="-B $opt"
|
opt="-B $opt"
|
||||||
elif [[ $tmux_version = 3.2 ]]; then
|
elif [[ $tmux_version = 3.2 ]]; then
|
||||||
|
4
go.mod
4
go.mod
@@ -7,8 +7,8 @@ require (
|
|||||||
github.com/mattn/go-shellwords v1.0.12
|
github.com/mattn/go-shellwords v1.0.12
|
||||||
github.com/rivo/uniseg v0.4.4
|
github.com/rivo/uniseg v0.4.4
|
||||||
github.com/saracen/walker v0.1.3
|
github.com/saracen/walker v0.1.3
|
||||||
golang.org/x/sys v0.6.0
|
golang.org/x/sys v0.7.0
|
||||||
golang.org/x/term v0.6.0
|
golang.org/x/term v0.7.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
8
go.sum
8
go.sum
@@ -32,12 +32,12 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
|
golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/term v0.6.0 h1:clScbb1cHjoCkyRbWwBEUZ5H/tIFu5TAXIqaZD0Gcjw=
|
golang.org/x/term v0.7.0 h1:BEvjmm5fURWqcfbSKTdpkDXYBrUS1c0m8agp14W48vQ=
|
||||||
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
|
golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
|
2
install
2
install
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
set -u
|
set -u
|
||||||
|
|
||||||
version=0.39.0
|
version=0.40.0
|
||||||
auto_completion=
|
auto_completion=
|
||||||
key_bindings=
|
key_bindings=
|
||||||
update_config=2
|
update_config=2
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
$version="0.39.0"
|
$version="0.40.0"
|
||||||
|
|
||||||
$fzf_base=Split-Path -Parent $MyInvocation.MyCommand.Definition
|
$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"
|
"github.com/junegunn/fzf/src/protector"
|
||||||
)
|
)
|
||||||
|
|
||||||
var version string = "0.39"
|
var version string = "0.40"
|
||||||
var revision string = "devel"
|
var revision string = "devel"
|
||||||
|
|
||||||
func main() {
|
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
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
THE SOFTWARE.
|
THE SOFTWARE.
|
||||||
..
|
..
|
||||||
.TH fzf-tmux 1 "Apr 2023" "fzf 0.39.0" "fzf-tmux - open fzf in tmux split pane"
|
.TH fzf-tmux 1 "May 2023" "fzf 0.40.0" "fzf-tmux - open fzf in tmux split pane"
|
||||||
|
|
||||||
.SH NAME
|
.SH NAME
|
||||||
fzf-tmux - open fzf in tmux split pane
|
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
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
THE SOFTWARE.
|
THE SOFTWARE.
|
||||||
..
|
..
|
||||||
.TH fzf 1 "Apr 2023" "fzf 0.39.0" "fzf - a command-line fuzzy finder"
|
.TH fzf 1 "May 2023" "fzf 0.40.0" "fzf - a command-line fuzzy finder"
|
||||||
|
|
||||||
.SH NAME
|
.SH NAME
|
||||||
fzf - a command-line fuzzy finder
|
fzf - a command-line fuzzy finder
|
||||||
@@ -94,7 +94,10 @@ Do not sort the result
|
|||||||
.TP
|
.TP
|
||||||
.B "--track"
|
.B "--track"
|
||||||
Make fzf track the current selection when the result list is updated.
|
Make fzf track the current selection when the result list is updated.
|
||||||
This can be useful when browsing logs using fzf with sorting disabled.
|
This can be useful when browsing logs using fzf with sorting disabled. It is
|
||||||
|
not recommended to use this option with \fB--tac\fR as the resulting behavior
|
||||||
|
can be confusing. Also, consider using \fBtrack\fR action instead of this
|
||||||
|
option.
|
||||||
|
|
||||||
.RS
|
.RS
|
||||||
e.g.
|
e.g.
|
||||||
@@ -241,8 +244,9 @@ Draw border around the finder
|
|||||||
.br
|
.br
|
||||||
|
|
||||||
If you use a terminal emulator where each box-drawing character takes
|
If you use a terminal emulator where each box-drawing character takes
|
||||||
2 columns, try setting \fBRUNEWIDTH_EASTASIAN\fR to \fB1\fR. If the border is
|
2 columns, try setting \fBRUNEWIDTH_EASTASIAN\fR environment variable to
|
||||||
still not properly rendered, set \fB--no-unicode\fR.
|
\fB0\fR or \fB1\fR. If the border is still not properly rendered, set
|
||||||
|
\fB--no-unicode\fR.
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.BI "--border-label" [=LABEL]
|
.BI "--border-label" [=LABEL]
|
||||||
@@ -1004,6 +1008,17 @@ e.g.
|
|||||||
\fB# Automatically select the only match
|
\fB# Automatically select the only match
|
||||||
seq 10 | fzf --bind one:accept\fR
|
seq 10 | fzf --bind one:accept\fR
|
||||||
.RE
|
.RE
|
||||||
|
\fIzero\fR
|
||||||
|
.RS
|
||||||
|
Triggered when there's no match. \fBzero:abort\fR binding is comparable to
|
||||||
|
\fB--exit-0\fR option, but the difference is that \fB--exit-0\fR is only
|
||||||
|
effective before the interactive finder starts but \fBzero\fR event is
|
||||||
|
triggered by the interactive finder.
|
||||||
|
|
||||||
|
e.g.
|
||||||
|
\fB# Reload the candidate list when there's no match
|
||||||
|
echo $RANDOM | fzf --bind 'zero:reload(echo $RANDOM)+clear-query' --height 3\fR
|
||||||
|
.RE
|
||||||
|
|
||||||
\fIbackward-eof\fR
|
\fIbackward-eof\fR
|
||||||
.RS
|
.RS
|
||||||
@@ -1030,6 +1045,7 @@ A key or an event can be bound to one or more of the following actions.
|
|||||||
\fBbeginning-of-line\fR \fIctrl-a home\fR
|
\fBbeginning-of-line\fR \fIctrl-a home\fR
|
||||||
\fBcancel\fR (clear query string if not empty, abort fzf otherwise)
|
\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-border-label(...)\fR (change \fB--border-label\fR to the given string)
|
||||||
|
\fBchange-header(...)\fR (change header to the given string; doesn't affect \fB--header-lines\fR)
|
||||||
\fBchange-preview(...)\fR (change \fB--preview\fR option)
|
\fBchange-preview(...)\fR (change \fB--preview\fR option)
|
||||||
\fBchange-preview-label(...)\fR (change \fB--preview-label\fR to the given string)
|
\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-preview-window(...)\fR (change \fB--preview-window\fR option; rotate through the multiple option sets separated by '|')
|
||||||
@@ -1097,8 +1113,11 @@ A key or an event can be bound to one or more of the following actions.
|
|||||||
\fBtoggle-preview-wrap\fR
|
\fBtoggle-preview-wrap\fR
|
||||||
\fBtoggle-search\fR (toggle search functionality)
|
\fBtoggle-search\fR (toggle search functionality)
|
||||||
\fBtoggle-sort\fR
|
\fBtoggle-sort\fR
|
||||||
|
\fBtoggle-track\fR
|
||||||
\fBtoggle+up\fR \fIbtab (shift-tab)\fR
|
\fBtoggle+up\fR \fIbtab (shift-tab)\fR
|
||||||
|
\fBtrack\fR (track the current item; automatically disabled if focus changes)
|
||||||
\fBtransform-border-label(...)\fR (transform border label using an external command)
|
\fBtransform-border-label(...)\fR (transform border label using an external command)
|
||||||
|
\fBtransform-header(...)\fR (transform header using an external command)
|
||||||
\fBtransform-preview-label(...)\fR (transform preview 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-prompt(...)\fR (transform prompt string using an external command)
|
||||||
\fBtransform-query(...)\fR (transform query string using an external command)
|
\fBtransform-query(...)\fR (transform query string using an external command)
|
||||||
|
@@ -164,7 +164,7 @@ function s:get_version(bin)
|
|||||||
if has_key(s:versions, a:bin)
|
if has_key(s:versions, a:bin)
|
||||||
return s:versions[a:bin]
|
return s:versions[a:bin]
|
||||||
end
|
end
|
||||||
let command = (&shell =~ 'powershell' ? '&' : '') . s:fzf_call('shellescape', a:bin) . ' --version --no-height'
|
let command = (&shell =~ 'powershell\|pwsh' ? '&' : '') . s:fzf_call('shellescape', a:bin) . ' --version --no-height'
|
||||||
let output = systemlist(command)
|
let output = systemlist(command)
|
||||||
if v:shell_error || empty(output)
|
if v:shell_error || empty(output)
|
||||||
return ''
|
return ''
|
||||||
|
11
src/core.go
11
src/core.go
@@ -299,10 +299,12 @@ func Run(opts *Options, version string, revision string) {
|
|||||||
|
|
||||||
case EvtSearchNew:
|
case EvtSearchNew:
|
||||||
var command *string
|
var command *string
|
||||||
|
var changed bool
|
||||||
switch val := value.(type) {
|
switch val := value.(type) {
|
||||||
case searchRequest:
|
case searchRequest:
|
||||||
sort = val.sort
|
sort = val.sort
|
||||||
command = val.command
|
command = val.command
|
||||||
|
changed = val.changed
|
||||||
if command != nil {
|
if command != nil {
|
||||||
useSnapshot = val.sync
|
useSnapshot = val.sync
|
||||||
}
|
}
|
||||||
@@ -314,10 +316,17 @@ func Run(opts *Options, version string, revision string) {
|
|||||||
} else {
|
} else {
|
||||||
restart(*command)
|
restart(*command)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
if !changed {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
if !useSnapshot {
|
if !useSnapshot {
|
||||||
snapshot, _ = chunkList.Snapshot()
|
newSnapshot, _ := chunkList.Snapshot()
|
||||||
|
// We want to avoid showing empty list when reload is triggered
|
||||||
|
// and the query string is changed at the same time i.e. command != nil && changed
|
||||||
|
if command == nil || len(newSnapshot) > 0 {
|
||||||
|
snapshot = newSnapshot
|
||||||
|
}
|
||||||
}
|
}
|
||||||
reset := !useSnapshot && clearCache()
|
reset := !useSnapshot && clearCache()
|
||||||
matcher.Reset(snapshot, input(reset), true, !reading, sort, reset)
|
matcher.Reset(snapshot, input(reset), true, !reading, sort, reset)
|
||||||
|
@@ -60,17 +60,30 @@ func (mg *Merger) Length() int {
|
|||||||
return mg.count
|
return mg.count
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (mg *Merger) First() Result {
|
||||||
|
if mg.tac && !mg.sorted {
|
||||||
|
return mg.Get(mg.count - 1)
|
||||||
|
}
|
||||||
|
return mg.Get(0)
|
||||||
|
}
|
||||||
|
|
||||||
// FindIndex returns the index of the item with the given item index
|
// FindIndex returns the index of the item with the given item index
|
||||||
func (mg *Merger) FindIndex(itemIndex int32) int {
|
func (mg *Merger) FindIndex(itemIndex int32) int {
|
||||||
|
index := -1
|
||||||
if mg.pass {
|
if mg.pass {
|
||||||
return int(itemIndex)
|
index = int(itemIndex)
|
||||||
|
if mg.tac {
|
||||||
|
index = mg.count - index - 1
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
for i := 0; i < mg.count; i++ {
|
for i := 0; i < mg.count; i++ {
|
||||||
if mg.Get(i).item.Index() == itemIndex {
|
if mg.Get(i).item.Index() == itemIndex {
|
||||||
return i
|
index = i
|
||||||
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return -1
|
}
|
||||||
|
return index
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get returns the pointer to the Result object indexed by the given integer
|
// Get returns the pointer to the Result object indexed by the given integer
|
||||||
|
@@ -165,6 +165,14 @@ func defaultMargin() [4]sizeSpec {
|
|||||||
return [4]sizeSpec{}
|
return [4]sizeSpec{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type trackOption int
|
||||||
|
|
||||||
|
const (
|
||||||
|
trackDisabled trackOption = iota
|
||||||
|
trackEnabled
|
||||||
|
trackCurrent
|
||||||
|
)
|
||||||
|
|
||||||
type windowPosition int
|
type windowPosition int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -267,7 +275,7 @@ type Options struct {
|
|||||||
WithNth []Range
|
WithNth []Range
|
||||||
Delimiter Delimiter
|
Delimiter Delimiter
|
||||||
Sort int
|
Sort int
|
||||||
Track bool
|
Track trackOption
|
||||||
Tac bool
|
Tac bool
|
||||||
Criteria []criterion
|
Criteria []criterion
|
||||||
Multi int
|
Multi int
|
||||||
@@ -340,7 +348,7 @@ func defaultOptions() *Options {
|
|||||||
WithNth: make([]Range, 0),
|
WithNth: make([]Range, 0),
|
||||||
Delimiter: Delimiter{},
|
Delimiter: Delimiter{},
|
||||||
Sort: 1000,
|
Sort: 1000,
|
||||||
Track: false,
|
Track: trackDisabled,
|
||||||
Tac: false,
|
Tac: false,
|
||||||
Criteria: []criterion{byScore, byLength},
|
Criteria: []criterion{byScore, byLength},
|
||||||
Multi: 0,
|
Multi: 0,
|
||||||
@@ -624,6 +632,8 @@ func parseKeyChordsImpl(str string, message string, exit func(string)) map[tui.E
|
|||||||
add(tui.Focus)
|
add(tui.Focus)
|
||||||
case "one":
|
case "one":
|
||||||
add(tui.One)
|
add(tui.One)
|
||||||
|
case "zero":
|
||||||
|
add(tui.Zero)
|
||||||
case "alt-enter", "alt-return":
|
case "alt-enter", "alt-return":
|
||||||
chords[tui.CtrlAltKey('m')] = key
|
chords[tui.CtrlAltKey('m')] = key
|
||||||
case "alt-space":
|
case "alt-space":
|
||||||
@@ -927,7 +937,7 @@ const (
|
|||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
executeRegexp = regexp.MustCompile(
|
executeRegexp = regexp.MustCompile(
|
||||||
`(?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)`)
|
`(?si)[:+](become|execute(?:-multi|-silent)?|reload(?:-sync)?|preview|(?:change|transform)-(?:header|query|prompt|border-label|preview-label)|change-preview-window|change-preview|(?:re|un)bind|pos|put)`)
|
||||||
splitRegexp = regexp.MustCompile("[,:]+")
|
splitRegexp = regexp.MustCompile("[,:]+")
|
||||||
actionNameRegexp = regexp.MustCompile("(?i)^[a-z-]+")
|
actionNameRegexp = regexp.MustCompile("(?i)^[a-z-]+")
|
||||||
}
|
}
|
||||||
@@ -1083,6 +1093,10 @@ func parseActionList(masked string, original string, prevActions []*action, putA
|
|||||||
appendAction(actToggleAll)
|
appendAction(actToggleAll)
|
||||||
case "toggle-search":
|
case "toggle-search":
|
||||||
appendAction(actToggleSearch)
|
appendAction(actToggleSearch)
|
||||||
|
case "toggle-track":
|
||||||
|
appendAction(actToggleTrack)
|
||||||
|
case "track":
|
||||||
|
appendAction(actTrack)
|
||||||
case "select":
|
case "select":
|
||||||
appendAction(actSelect)
|
appendAction(actSelect)
|
||||||
case "select-all":
|
case "select-all":
|
||||||
@@ -1247,6 +1261,8 @@ func isExecuteAction(str string) actionType {
|
|||||||
return actPreview
|
return actPreview
|
||||||
case "change-border-label":
|
case "change-border-label":
|
||||||
return actChangeBorderLabel
|
return actChangeBorderLabel
|
||||||
|
case "change-header":
|
||||||
|
return actChangeHeader
|
||||||
case "change-preview-label":
|
case "change-preview-label":
|
||||||
return actChangePreviewLabel
|
return actChangePreviewLabel
|
||||||
case "change-preview-window":
|
case "change-preview-window":
|
||||||
@@ -1271,6 +1287,8 @@ func isExecuteAction(str string) actionType {
|
|||||||
return actTransformBorderLabel
|
return actTransformBorderLabel
|
||||||
case "transform-preview-label":
|
case "transform-preview-label":
|
||||||
return actTransformPreviewLabel
|
return actTransformPreviewLabel
|
||||||
|
case "transform-header":
|
||||||
|
return actTransformHeader
|
||||||
case "transform-prompt":
|
case "transform-prompt":
|
||||||
return actTransformPrompt
|
return actTransformPrompt
|
||||||
case "transform-query":
|
case "transform-query":
|
||||||
@@ -1568,9 +1586,9 @@ func parseOptions(opts *Options, allArgs []string) {
|
|||||||
case "+s", "--no-sort":
|
case "+s", "--no-sort":
|
||||||
opts.Sort = 0
|
opts.Sort = 0
|
||||||
case "--track":
|
case "--track":
|
||||||
opts.Track = true
|
opts.Track = trackEnabled
|
||||||
case "--no-track":
|
case "--no-track":
|
||||||
opts.Track = false
|
opts.Track = trackDisabled
|
||||||
case "--tac":
|
case "--tac":
|
||||||
opts.Tac = true
|
opts.Tac = true
|
||||||
case "--no-tac":
|
case "--no-tac":
|
||||||
|
121
src/terminal.go
121
src/terminal.go
@@ -3,6 +3,7 @@ package fzf
|
|||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"math"
|
"math"
|
||||||
"os"
|
"os"
|
||||||
@@ -183,7 +184,7 @@ type Terminal struct {
|
|||||||
multi int
|
multi int
|
||||||
sort bool
|
sort bool
|
||||||
toggleSort bool
|
toggleSort bool
|
||||||
track bool
|
track trackOption
|
||||||
delimiter Delimiter
|
delimiter Delimiter
|
||||||
expect map[tui.Event]string
|
expect map[tui.Event]string
|
||||||
keymap map[tui.Event][]*action
|
keymap map[tui.Event][]*action
|
||||||
@@ -310,6 +311,7 @@ const (
|
|||||||
actBackwardWord
|
actBackwardWord
|
||||||
actCancel
|
actCancel
|
||||||
actChangeBorderLabel
|
actChangeBorderLabel
|
||||||
|
actChangeHeader
|
||||||
actChangePreviewLabel
|
actChangePreviewLabel
|
||||||
actChangePrompt
|
actChangePrompt
|
||||||
actChangeQuery
|
actChangeQuery
|
||||||
@@ -337,6 +339,8 @@ const (
|
|||||||
actToggleUp
|
actToggleUp
|
||||||
actToggleIn
|
actToggleIn
|
||||||
actToggleOut
|
actToggleOut
|
||||||
|
actToggleTrack
|
||||||
|
actTrack
|
||||||
actDown
|
actDown
|
||||||
actUp
|
actUp
|
||||||
actPageUp
|
actPageUp
|
||||||
@@ -355,6 +359,7 @@ const (
|
|||||||
actTogglePreview
|
actTogglePreview
|
||||||
actTogglePreviewWrap
|
actTogglePreviewWrap
|
||||||
actTransformBorderLabel
|
actTransformBorderLabel
|
||||||
|
actTransformHeader
|
||||||
actTransformPreviewLabel
|
actTransformPreviewLabel
|
||||||
actTransformPrompt
|
actTransformPrompt
|
||||||
actTransformQuery
|
actTransformQuery
|
||||||
@@ -403,6 +408,7 @@ type searchRequest struct {
|
|||||||
sort bool
|
sort bool
|
||||||
sync bool
|
sync bool
|
||||||
command *string
|
command *string
|
||||||
|
changed bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type previewRequest struct {
|
type previewRequest struct {
|
||||||
@@ -623,7 +629,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
|
|||||||
cycle: opts.Cycle,
|
cycle: opts.Cycle,
|
||||||
headerFirst: opts.HeaderFirst,
|
headerFirst: opts.HeaderFirst,
|
||||||
headerLines: opts.HeaderLines,
|
headerLines: opts.HeaderLines,
|
||||||
header: header,
|
header: []string{},
|
||||||
header0: header,
|
header0: header,
|
||||||
ellipsis: opts.Ellipsis,
|
ellipsis: opts.Ellipsis,
|
||||||
ansi: opts.Ansi,
|
ansi: opts.Ansi,
|
||||||
@@ -882,10 +888,21 @@ func reverseStringArray(input []string) []string {
|
|||||||
return reversed
|
return reversed
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *Terminal) changeHeader(header string) bool {
|
||||||
|
lines := strings.Split(strings.TrimSuffix(header, "\n"), "\n")
|
||||||
|
switch t.layout {
|
||||||
|
case layoutDefault, layoutReverseList:
|
||||||
|
lines = reverseStringArray(lines)
|
||||||
|
}
|
||||||
|
needFullRedraw := len(t.header0) != len(lines)
|
||||||
|
t.header0 = lines
|
||||||
|
return needFullRedraw
|
||||||
|
}
|
||||||
|
|
||||||
// UpdateHeader updates the header
|
// UpdateHeader updates the header
|
||||||
func (t *Terminal) UpdateHeader(header []string) {
|
func (t *Terminal) UpdateHeader(header []string) {
|
||||||
t.mutex.Lock()
|
t.mutex.Lock()
|
||||||
t.header = append(append([]string{}, t.header0...), header...)
|
t.header = header
|
||||||
t.mutex.Unlock()
|
t.mutex.Unlock()
|
||||||
t.reqBox.Set(reqHeader, nil)
|
t.reqBox.Set(reqHeader, nil)
|
||||||
}
|
}
|
||||||
@@ -907,8 +924,12 @@ func (t *Terminal) UpdateProgress(progress float32) {
|
|||||||
func (t *Terminal) UpdateList(merger *Merger, reset bool) {
|
func (t *Terminal) UpdateList(merger *Merger, reset bool) {
|
||||||
t.mutex.Lock()
|
t.mutex.Lock()
|
||||||
var prevIndex int32 = -1
|
var prevIndex int32 = -1
|
||||||
if !reset && t.track && t.merger.Length() > 0 {
|
if !reset && t.track != trackDisabled {
|
||||||
|
if t.merger.Length() > 0 {
|
||||||
prevIndex = t.merger.Get(t.cy).item.Index()
|
prevIndex = t.merger.Get(t.cy).item.Index()
|
||||||
|
} else if merger.Length() > 0 {
|
||||||
|
prevIndex = merger.First().item.Index()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
t.progress = 100
|
t.progress = 100
|
||||||
t.merger = merger
|
t.merger = merger
|
||||||
@@ -916,7 +937,7 @@ func (t *Terminal) UpdateList(merger *Merger, reset bool) {
|
|||||||
t.selected = make(map[int32]selectedItem)
|
t.selected = make(map[int32]selectedItem)
|
||||||
t.version++
|
t.version++
|
||||||
}
|
}
|
||||||
if t.hasLoadActions && t.triggerLoad {
|
if t.triggerLoad {
|
||||||
t.triggerLoad = false
|
t.triggerLoad = false
|
||||||
t.eventChan <- tui.Load.AsEvent()
|
t.eventChan <- tui.Load.AsEvent()
|
||||||
}
|
}
|
||||||
@@ -927,17 +948,29 @@ func (t *Terminal) UpdateList(merger *Merger, reset bool) {
|
|||||||
if i >= 0 {
|
if i >= 0 {
|
||||||
t.cy = i
|
t.cy = i
|
||||||
t.offset = t.cy - pos
|
t.offset = t.cy - pos
|
||||||
|
} else if t.track == trackCurrent {
|
||||||
|
t.track = trackDisabled
|
||||||
|
t.cy = pos
|
||||||
|
t.offset = 0
|
||||||
} else if t.cy > count {
|
} else if t.cy > count {
|
||||||
// Try to keep the vertical position when the list shrinks
|
// Try to keep the vertical position when the list shrinks
|
||||||
t.cy = count - util.Min(count, t.maxItems()) + pos
|
t.cy = count - util.Min(count, t.maxItems()) + pos
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !t.reading && t.merger.Length() == 1 {
|
if !t.reading {
|
||||||
|
switch t.merger.Length() {
|
||||||
|
case 0:
|
||||||
|
zero := tui.Zero.AsEvent()
|
||||||
|
if _, prs := t.keymap[zero]; prs {
|
||||||
|
t.eventChan <- zero
|
||||||
|
}
|
||||||
|
case 1:
|
||||||
one := tui.One.AsEvent()
|
one := tui.One.AsEvent()
|
||||||
if _, prs := t.keymap[one]; prs {
|
if _, prs := t.keymap[one]; prs {
|
||||||
t.eventChan <- one
|
t.eventChan <- one
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
t.mutex.Unlock()
|
t.mutex.Unlock()
|
||||||
t.reqBox.Set(reqInfo, nil)
|
t.reqBox.Set(reqInfo, nil)
|
||||||
t.reqBox.Set(reqList, nil)
|
t.reqBox.Set(reqList, nil)
|
||||||
@@ -1340,7 +1373,7 @@ func (t *Terminal) move(y int, x int, clear bool) {
|
|||||||
case layoutDefault:
|
case layoutDefault:
|
||||||
y = h - y - 1
|
y = h - y - 1
|
||||||
case layoutReverseList:
|
case layoutReverseList:
|
||||||
n := 2 + len(t.header)
|
n := 2 + len(t.header0) + len(t.header)
|
||||||
if t.noInfoLine() {
|
if t.noInfoLine() {
|
||||||
n--
|
n--
|
||||||
}
|
}
|
||||||
@@ -1460,6 +1493,9 @@ func (t *Terminal) printInfo() {
|
|||||||
output += " -S"
|
output += " -S"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if t.track != trackDisabled {
|
||||||
|
output += " +T"
|
||||||
|
}
|
||||||
if t.multi > 0 {
|
if t.multi > 0 {
|
||||||
if t.multi == maxMulti {
|
if t.multi == maxMulti {
|
||||||
output += fmt.Sprintf(" (%d)", len(t.selected))
|
output += fmt.Sprintf(" (%d)", len(t.selected))
|
||||||
@@ -1485,7 +1521,7 @@ func (t *Terminal) printInfo() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *Terminal) printHeader() {
|
func (t *Terminal) printHeader() {
|
||||||
if len(t.header) == 0 {
|
if len(t.header0)+len(t.header) == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
max := t.window.Height()
|
max := t.window.Height()
|
||||||
@@ -1496,7 +1532,7 @@ func (t *Terminal) printHeader() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
var state *ansiState
|
var state *ansiState
|
||||||
for idx, lineStr := range t.header {
|
for idx, lineStr := range append(append([]string{}, t.header0...), t.header...) {
|
||||||
line := idx
|
line := idx
|
||||||
if !t.headerFirst {
|
if !t.headerFirst {
|
||||||
line++
|
line++
|
||||||
@@ -1530,7 +1566,7 @@ func (t *Terminal) printList() {
|
|||||||
if t.layout == layoutDefault {
|
if t.layout == layoutDefault {
|
||||||
i = maxy - 1 - j
|
i = maxy - 1 - j
|
||||||
}
|
}
|
||||||
line := i + 2 + len(t.header)
|
line := i + 2 + len(t.header0) + len(t.header)
|
||||||
if t.noInfoLine() {
|
if t.noInfoLine() {
|
||||||
line--
|
line--
|
||||||
}
|
}
|
||||||
@@ -1836,7 +1872,7 @@ func (t *Terminal) renderPreviewText(height int, lines []string, lineNo int, unc
|
|||||||
if ansi != nil {
|
if ansi != nil {
|
||||||
ansi.lbg = -1
|
ansi.lbg = -1
|
||||||
}
|
}
|
||||||
line = strings.TrimSuffix(line, "\n")
|
line = strings.TrimRight(line, "\r\n")
|
||||||
if lineNo >= height || t.pwindow.Y() == height-1 && t.pwindow.X() > 0 {
|
if lineNo >= height || t.pwindow.Y() == height-1 && t.pwindow.X() > 0 {
|
||||||
t.previewed.filled = true
|
t.previewed.filled = true
|
||||||
t.previewer.scrollable = true
|
t.previewer.scrollable = true
|
||||||
@@ -2268,12 +2304,12 @@ func (t *Terminal) redraw() {
|
|||||||
t.printAll()
|
t.printAll()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Terminal) executeCommand(template string, forcePlus bool, background bool, captureFirstLine bool) string {
|
func (t *Terminal) executeCommand(template string, forcePlus bool, background bool, capture bool, firstLineOnly bool) string {
|
||||||
line := ""
|
line := ""
|
||||||
valid, list := t.buildPlusList(template, forcePlus)
|
valid, list := t.buildPlusList(template, forcePlus)
|
||||||
// captureFirstLine is used for transform-{prompt,query} and we don't want to
|
// 'capture' is used for transform-* and we don't want to
|
||||||
// return an empty string in those cases
|
// return an empty string in those cases
|
||||||
if !valid && !captureFirstLine {
|
if !valid && !capture {
|
||||||
return line
|
return line
|
||||||
}
|
}
|
||||||
command := t.replacePlaceholder(template, forcePlus, string(t.input), list)
|
command := t.replacePlaceholder(template, forcePlus, string(t.input), list)
|
||||||
@@ -2290,12 +2326,17 @@ func (t *Terminal) executeCommand(template string, forcePlus bool, background bo
|
|||||||
t.redraw()
|
t.redraw()
|
||||||
t.refresh()
|
t.refresh()
|
||||||
} else {
|
} else {
|
||||||
if captureFirstLine {
|
if capture {
|
||||||
out, _ := cmd.StdoutPipe()
|
out, _ := cmd.StdoutPipe()
|
||||||
reader := bufio.NewReader(out)
|
reader := bufio.NewReader(out)
|
||||||
cmd.Start()
|
cmd.Start()
|
||||||
|
if firstLineOnly {
|
||||||
line, _ = reader.ReadString('\n')
|
line, _ = reader.ReadString('\n')
|
||||||
line = strings.TrimRight(line, "\r\n")
|
line = strings.TrimRight(line, "\r\n")
|
||||||
|
} else {
|
||||||
|
bytes, _ := io.ReadAll(reader)
|
||||||
|
line = string(bytes)
|
||||||
|
}
|
||||||
cmd.Wait()
|
cmd.Wait()
|
||||||
} else {
|
} else {
|
||||||
cmd.Run()
|
cmd.Run()
|
||||||
@@ -2706,6 +2747,10 @@ func (t *Terminal) Loop() {
|
|||||||
currentIndex = currentItem.Index()
|
currentIndex = currentItem.Index()
|
||||||
}
|
}
|
||||||
focusChanged := focusedIndex != currentIndex
|
focusChanged := focusedIndex != currentIndex
|
||||||
|
if focusChanged && t.track == trackCurrent {
|
||||||
|
t.track = trackDisabled
|
||||||
|
t.printInfo()
|
||||||
|
}
|
||||||
if onFocus != nil && focusChanged {
|
if onFocus != nil && focusChanged {
|
||||||
t.serverChan <- onFocus
|
t.serverChan <- onFocus
|
||||||
}
|
}
|
||||||
@@ -2818,7 +2863,7 @@ func (t *Terminal) Loop() {
|
|||||||
}
|
}
|
||||||
select {
|
select {
|
||||||
case event = <-t.eventChan:
|
case event = <-t.eventChan:
|
||||||
needBarrier = event != tui.Load.AsEvent()
|
needBarrier = !event.Is(tui.Load, tui.One, tui.Zero)
|
||||||
case actions = <-t.serverChan:
|
case actions = <-t.serverChan:
|
||||||
event = tui.Invalid.AsEvent()
|
event = tui.Invalid.AsEvent()
|
||||||
needBarrier = false
|
needBarrier = false
|
||||||
@@ -2913,9 +2958,9 @@ func (t *Terminal) Loop() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
case actExecute, actExecuteSilent:
|
case actExecute, actExecuteSilent:
|
||||||
t.executeCommand(a.a, false, a.t == actExecuteSilent, false)
|
t.executeCommand(a.a, false, a.t == actExecuteSilent, false, false)
|
||||||
case actExecuteMulti:
|
case actExecuteMulti:
|
||||||
t.executeCommand(a.a, true, false, false)
|
t.executeCommand(a.a, true, false, false, false)
|
||||||
case actInvalid:
|
case actInvalid:
|
||||||
t.mutex.Unlock()
|
t.mutex.Unlock()
|
||||||
return false
|
return false
|
||||||
@@ -2949,11 +2994,11 @@ func (t *Terminal) Loop() {
|
|||||||
req(reqPreviewRefresh)
|
req(reqPreviewRefresh)
|
||||||
}
|
}
|
||||||
case actTransformPrompt:
|
case actTransformPrompt:
|
||||||
prompt := t.executeCommand(a.a, false, true, true)
|
prompt := t.executeCommand(a.a, false, true, true, true)
|
||||||
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)
|
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:
|
||||||
@@ -3002,6 +3047,19 @@ func (t *Terminal) Loop() {
|
|||||||
case actChangeQuery:
|
case actChangeQuery:
|
||||||
t.input = []rune(a.a)
|
t.input = []rune(a.a)
|
||||||
t.cx = len(t.input)
|
t.cx = len(t.input)
|
||||||
|
case actTransformHeader:
|
||||||
|
header := t.executeCommand(a.a, false, true, true, false)
|
||||||
|
if t.changeHeader(header) {
|
||||||
|
req(reqFullRedraw)
|
||||||
|
} else {
|
||||||
|
req(reqHeader)
|
||||||
|
}
|
||||||
|
case actChangeHeader:
|
||||||
|
if t.changeHeader(a.a) {
|
||||||
|
req(reqFullRedraw)
|
||||||
|
} else {
|
||||||
|
req(reqHeader)
|
||||||
|
}
|
||||||
case actChangeBorderLabel:
|
case actChangeBorderLabel:
|
||||||
if t.border != nil {
|
if t.border != nil {
|
||||||
t.borderLabel, t.borderLabelLen = t.ansiLabelPrinter(a.a, &tui.ColBorderLabel, false)
|
t.borderLabel, t.borderLabelLen = t.ansiLabelPrinter(a.a, &tui.ColBorderLabel, false)
|
||||||
@@ -3014,13 +3072,13 @@ func (t *Terminal) Loop() {
|
|||||||
}
|
}
|
||||||
case actTransformBorderLabel:
|
case actTransformBorderLabel:
|
||||||
if t.border != nil {
|
if t.border != nil {
|
||||||
label := t.executeCommand(a.a, false, true, true)
|
label := t.executeCommand(a.a, false, true, true, true)
|
||||||
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:
|
||||||
if t.pborder != nil {
|
if t.pborder != nil {
|
||||||
label := t.executeCommand(a.a, false, true, true)
|
label := t.executeCommand(a.a, false, true, true, true)
|
||||||
t.previewLabel, t.previewLabelLen = t.ansiLabelPrinter(label, &tui.ColPreviewLabel, false)
|
t.previewLabel, t.previewLabelLen = t.ansiLabelPrinter(label, &tui.ColPreviewLabel, false)
|
||||||
req(reqRedrawPreviewLabel)
|
req(reqRedrawPreviewLabel)
|
||||||
}
|
}
|
||||||
@@ -3270,6 +3328,19 @@ func (t *Terminal) Loop() {
|
|||||||
t.paused = !t.paused
|
t.paused = !t.paused
|
||||||
changed = !t.paused
|
changed = !t.paused
|
||||||
req(reqPrompt)
|
req(reqPrompt)
|
||||||
|
case actToggleTrack:
|
||||||
|
switch t.track {
|
||||||
|
case trackEnabled:
|
||||||
|
t.track = trackDisabled
|
||||||
|
case trackDisabled:
|
||||||
|
t.track = trackEnabled
|
||||||
|
}
|
||||||
|
req(reqInfo)
|
||||||
|
case actTrack:
|
||||||
|
if t.track == trackDisabled {
|
||||||
|
t.track = trackCurrent
|
||||||
|
}
|
||||||
|
req(reqInfo)
|
||||||
case actEnableSearch:
|
case actEnableSearch:
|
||||||
t.paused = false
|
t.paused = false
|
||||||
changed = true
|
changed = true
|
||||||
@@ -3347,7 +3418,7 @@ func (t *Terminal) Loop() {
|
|||||||
// Translate coordinates
|
// Translate coordinates
|
||||||
mx -= t.window.Left()
|
mx -= t.window.Left()
|
||||||
my -= t.window.Top()
|
my -= t.window.Top()
|
||||||
min := 2 + len(t.header)
|
min := 2 + len(t.header0) + len(t.header)
|
||||||
if t.noInfoLine() {
|
if t.noInfoLine() {
|
||||||
min--
|
min--
|
||||||
}
|
}
|
||||||
@@ -3552,7 +3623,7 @@ func (t *Terminal) Loop() {
|
|||||||
t.mutex.Unlock() // Must be unlocked before touching reqBox
|
t.mutex.Unlock() // Must be unlocked before touching reqBox
|
||||||
|
|
||||||
if changed || newCommand != nil {
|
if changed || newCommand != nil {
|
||||||
t.eventBox.Set(EvtSearchNew, searchRequest{sort: t.sort, sync: reloadSync, command: newCommand})
|
t.eventBox.Set(EvtSearchNew, searchRequest{sort: t.sort, sync: reloadSync, command: newCommand, changed: changed})
|
||||||
}
|
}
|
||||||
for _, event := range events {
|
for _, event := range events {
|
||||||
t.reqBox.Set(event, nil)
|
t.reqBox.Set(event, nil)
|
||||||
@@ -3616,7 +3687,7 @@ func (t *Terminal) vset(o int) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *Terminal) maxItems() int {
|
func (t *Terminal) maxItems() int {
|
||||||
max := t.window.Height() - 2 - len(t.header)
|
max := t.window.Height() - 2 - len(t.header0) - len(t.header)
|
||||||
if t.noInfoLine() {
|
if t.noInfoLine() {
|
||||||
max++
|
max++
|
||||||
}
|
}
|
||||||
|
@@ -94,6 +94,7 @@ const (
|
|||||||
Load
|
Load
|
||||||
Focus
|
Focus
|
||||||
One
|
One
|
||||||
|
Zero
|
||||||
|
|
||||||
AltBS
|
AltBS
|
||||||
|
|
||||||
@@ -283,6 +284,15 @@ type Event struct {
|
|||||||
MouseEvent *MouseEvent
|
MouseEvent *MouseEvent
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (e Event) Is(types ...EventType) bool {
|
||||||
|
for _, t := range types {
|
||||||
|
if e.Type == t {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
type MouseEvent struct {
|
type MouseEvent struct {
|
||||||
Y int
|
Y int
|
||||||
X int
|
X int
|
||||||
|
149
test/test_go.rb
149
test/test_go.rb
@@ -1865,6 +1865,67 @@ class TestGoFZF < TestBase
|
|||||||
tmux.until { |lines| assert_equal '>', lines.last }
|
tmux.until { |lines| assert_equal '>', lines.last }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_change_and_transform_header
|
||||||
|
[
|
||||||
|
'space:change-header:$(seq 4)',
|
||||||
|
'space:transform-header:seq 4'
|
||||||
|
].each_with_index do |binding, i|
|
||||||
|
tmux.send_keys %(seq 3 | #{FZF} --header-lines 2 --header bar --bind "#{binding}"), :Enter
|
||||||
|
expected = <<~OUTPUT
|
||||||
|
> 3
|
||||||
|
2
|
||||||
|
1
|
||||||
|
bar
|
||||||
|
1/1
|
||||||
|
>
|
||||||
|
OUTPUT
|
||||||
|
tmux.until { assert_block(expected, _1) }
|
||||||
|
tmux.send_keys :Space
|
||||||
|
expected = <<~OUTPUT
|
||||||
|
> 3
|
||||||
|
2
|
||||||
|
1
|
||||||
|
1
|
||||||
|
2
|
||||||
|
3
|
||||||
|
4
|
||||||
|
1/1
|
||||||
|
>
|
||||||
|
OUTPUT
|
||||||
|
tmux.until { assert_block(expected, _1) }
|
||||||
|
next unless i.zero?
|
||||||
|
|
||||||
|
teardown
|
||||||
|
setup
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_change_header
|
||||||
|
tmux.send_keys %(seq 3 | #{FZF} --header-lines 2 --header bar --bind "space:change-header:$(seq 4)"), :Enter
|
||||||
|
expected = <<~OUTPUT
|
||||||
|
> 3
|
||||||
|
2
|
||||||
|
1
|
||||||
|
bar
|
||||||
|
1/1
|
||||||
|
>
|
||||||
|
OUTPUT
|
||||||
|
tmux.until { assert_block(expected, _1) }
|
||||||
|
tmux.send_keys :Space
|
||||||
|
expected = <<~OUTPUT
|
||||||
|
> 3
|
||||||
|
2
|
||||||
|
1
|
||||||
|
1
|
||||||
|
2
|
||||||
|
3
|
||||||
|
4
|
||||||
|
1/1
|
||||||
|
>
|
||||||
|
OUTPUT
|
||||||
|
tmux.until { assert_block(expected, _1) }
|
||||||
|
end
|
||||||
|
|
||||||
def test_change_query
|
def test_change_query
|
||||||
tmux.send_keys %(: | #{FZF} --query foo --bind space:change-query:foobar), :Enter
|
tmux.send_keys %(: | #{FZF} --query foo --bind space:change-query:foobar), :Enter
|
||||||
tmux.until { |lines| assert_equal 0, lines.item_count }
|
tmux.until { |lines| assert_equal 0, lines.item_count }
|
||||||
@@ -2681,7 +2742,7 @@ class TestGoFZF < TestBase
|
|||||||
end
|
end
|
||||||
|
|
||||||
def test_track
|
def test_track
|
||||||
tmux.send_keys "seq 1000 | #{FZF} --query 555 --track", :Enter
|
tmux.send_keys "seq 1000 | #{FZF} --query 555 --track --bind t:toggle-track", :Enter
|
||||||
tmux.until do |lines|
|
tmux.until do |lines|
|
||||||
assert_equal 1, lines.match_count
|
assert_equal 1, lines.match_count
|
||||||
assert_includes lines, '> 555'
|
assert_includes lines, '> 555'
|
||||||
@@ -2701,20 +2762,97 @@ class TestGoFZF < TestBase
|
|||||||
assert_equal 1000, lines.match_count
|
assert_equal 1000, lines.match_count
|
||||||
assert_equal '> 555', lines[index]
|
assert_equal '> 555', lines[index]
|
||||||
end
|
end
|
||||||
|
tmux.send_keys '555'
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_equal 1, lines.match_count
|
||||||
|
assert_includes lines, '> 555'
|
||||||
|
assert_includes lines[-2], '+T'
|
||||||
|
end
|
||||||
|
tmux.send_keys 't'
|
||||||
|
tmux.until do |lines|
|
||||||
|
refute_includes lines[-2], '+T'
|
||||||
|
end
|
||||||
|
tmux.send_keys :BSpace
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_equal 28, lines.match_count
|
||||||
|
assert_includes lines, '> 55'
|
||||||
|
end
|
||||||
|
tmux.send_keys :BSpace
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_equal 271, lines.match_count
|
||||||
|
assert_includes lines, '> 5'
|
||||||
|
end
|
||||||
|
tmux.send_keys 't'
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_includes lines[-2], '+T'
|
||||||
|
end
|
||||||
|
tmux.send_keys :BSpace
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_equal 1000, lines.match_count
|
||||||
|
assert_includes lines, '> 5'
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_one
|
def test_track_action
|
||||||
tmux.send_keys "seq 10 | #{FZF} --bind 'one:preview:echo {} is the only match'", :Enter
|
tmux.send_keys "seq 1000 | #{FZF} --query 555 --bind t:track", :Enter
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_equal 1, lines.match_count
|
||||||
|
assert_includes lines, '> 555'
|
||||||
|
end
|
||||||
|
tmux.send_keys :BSpace
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_equal 28, lines.match_count
|
||||||
|
assert_includes lines, '> 55'
|
||||||
|
end
|
||||||
|
tmux.send_keys :t
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_includes lines[-2], '+T'
|
||||||
|
end
|
||||||
|
tmux.send_keys :BSpace
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_equal 271, lines.match_count
|
||||||
|
assert_includes lines, '> 55'
|
||||||
|
end
|
||||||
|
|
||||||
|
# Automatically disabled when the tracking item is no longer visible
|
||||||
|
tmux.send_keys '4'
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_equal 28, lines.match_count
|
||||||
|
refute_includes lines[-2], '+T'
|
||||||
|
end
|
||||||
|
tmux.send_keys :BSpace
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_equal 271, lines.match_count
|
||||||
|
assert_includes lines, '> 5'
|
||||||
|
end
|
||||||
|
tmux.send_keys :t
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_includes lines[-2], '+T'
|
||||||
|
end
|
||||||
|
tmux.send_keys :Up
|
||||||
|
tmux.until do |lines|
|
||||||
|
refute_includes lines[-2], '+T'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_one_and_zero
|
||||||
|
tmux.send_keys "seq 10 | #{FZF} --bind 'zero:preview(echo no match),one:preview(echo {} is the only match)'", :Enter
|
||||||
tmux.send_keys '1'
|
tmux.send_keys '1'
|
||||||
tmux.until do |lines|
|
tmux.until do |lines|
|
||||||
assert_equal 2, lines.match_count
|
assert_equal 2, lines.match_count
|
||||||
refute(lines.any? { _1.include?('only match') })
|
refute(lines.any? { _1.include?('only match') })
|
||||||
|
refute(lines.any? { _1.include?('no match') })
|
||||||
end
|
end
|
||||||
tmux.send_keys '0'
|
tmux.send_keys '0'
|
||||||
tmux.until do |lines|
|
tmux.until do |lines|
|
||||||
assert_equal 1, lines.match_count
|
assert_equal 1, lines.match_count
|
||||||
assert(lines.any? { _1.include?('only match') })
|
assert(lines.any? { _1.include?('only match') })
|
||||||
end
|
end
|
||||||
|
tmux.send_keys '0'
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_equal 0, lines.match_count
|
||||||
|
assert(lines.any? { _1.include?('no match') })
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_height_range_with_exit_0
|
def test_height_range_with_exit_0
|
||||||
@@ -2723,6 +2861,11 @@ class TestGoFZF < TestBase
|
|||||||
tmux.send_keys :c
|
tmux.send_keys :c
|
||||||
tmux.until { |lines| assert_equal 0, lines.match_count }
|
tmux.until { |lines| assert_equal 0, lines.match_count }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_reload_and_change
|
||||||
|
tmux.send_keys "(echo foo; echo bar) | #{FZF} --bind 'load:reload-sync(sleep 60)+change-query(bar)'", :Enter
|
||||||
|
tmux.until { |lines| assert_equal 1, lines.match_count }
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
module TestShell
|
module TestShell
|
||||||
|
Reference in New Issue
Block a user