mirror of
https://github.com/junegunn/fzf.git
synced 2025-11-15 14:53:47 -05:00
Compare commits
24 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2f364c62f4 | ||
|
|
7ed9f83662 | ||
|
|
f498a9b3fb | ||
|
|
13330738b8 | ||
|
|
e53535cc61 | ||
|
|
c62fc5e75c | ||
|
|
70245ad98c | ||
|
|
6d235bceee | ||
|
|
4adebfc856 | ||
|
|
faccc0a410 | ||
|
|
9078688baf | ||
|
|
9bd8b1d25f | ||
|
|
dd4be1da38 | ||
|
|
66f86e1870 | ||
|
|
4ab75b68dc | ||
|
|
73cb70dbb3 | ||
|
|
d082cccb6d | ||
|
|
88a80e3c2c | ||
|
|
24516bcf4d | ||
|
|
b4c4a642ed | ||
|
|
0231617857 | ||
|
|
7f64fba80f | ||
|
|
633aec38f5 | ||
|
|
d1b402a23c |
14
CHANGELOG.md
14
CHANGELOG.md
@@ -1,6 +1,20 @@
|
|||||||
CHANGELOG
|
CHANGELOG
|
||||||
=========
|
=========
|
||||||
|
|
||||||
|
0.12.2
|
||||||
|
------
|
||||||
|
|
||||||
|
- 256-color capability detection does not require `256` in `$TERM`
|
||||||
|
- Added `print-query` action
|
||||||
|
- More named keys for binding; <kbd>F1</kbd> ~ <kbd>F10</kbd>,
|
||||||
|
<kbd>ALT-/</kbd>, <kbd>ALT-space</kbd>, and <kbd>ALT-enter</kbd>
|
||||||
|
- Added `jump` and `jump-accept` actions that implement [EasyMotion][em]-like
|
||||||
|
movement
|
||||||
|
![][jump]
|
||||||
|
|
||||||
|
[em]: https://github.com/easymotion/vim-easymotion
|
||||||
|
[jump]: https://cloud.githubusercontent.com/assets/700826/15367574/b3999dc4-1d64-11e6-85da-28ceeb1a9bc2.png
|
||||||
|
|
||||||
0.12.1
|
0.12.1
|
||||||
------
|
------
|
||||||
|
|
||||||
|
|||||||
43
README.md
43
README.md
@@ -151,27 +151,6 @@ Many useful examples can be found on [the wiki
|
|||||||
page](https://github.com/junegunn/fzf/wiki/examples). Feel free to add your
|
page](https://github.com/junegunn/fzf/wiki/examples). Feel free to add your
|
||||||
own as well.
|
own as well.
|
||||||
|
|
||||||
Key bindings for command line
|
|
||||||
-----------------------------
|
|
||||||
|
|
||||||
The install script will setup the following key bindings for bash, zsh, and
|
|
||||||
fish.
|
|
||||||
|
|
||||||
- `CTRL-T` - Paste the selected files and directories onto the command line
|
|
||||||
- Set `FZF_CTRL_T_COMMAND` to override the default command
|
|
||||||
- `CTRL-R` - Paste the selected command from history onto the command line
|
|
||||||
- Sort is disabled by default to respect chronological ordering
|
|
||||||
- Press `CTRL-R` again to toggle sort
|
|
||||||
- `ALT-C` - cd into the selected directory
|
|
||||||
|
|
||||||
If you're on a tmux session, fzf will start in a split pane. You may disable
|
|
||||||
this tmux integration by setting `FZF_TMUX` to 0, or change the height of the
|
|
||||||
pane with `FZF_TMUX_HEIGHT` (e.g. `20`, `50%`).
|
|
||||||
|
|
||||||
If you use vi mode on bash, you need to add `set -o vi` *before* `source
|
|
||||||
~/.fzf.bash` in your .bashrc, so that it correctly sets up key bindings for vi
|
|
||||||
mode.
|
|
||||||
|
|
||||||
`fzf-tmux` script
|
`fzf-tmux` script
|
||||||
-----------------
|
-----------------
|
||||||
|
|
||||||
@@ -191,6 +170,28 @@ cat /usr/share/dict/words | fzf-tmux -l 20% --multi --reverse
|
|||||||
It will still work even when you're not on tmux, silently ignoring `-[udlr]`
|
It will still work even when you're not on tmux, silently ignoring `-[udlr]`
|
||||||
options, so you can invariably use `fzf-tmux` in your scripts.
|
options, so you can invariably use `fzf-tmux` in your scripts.
|
||||||
|
|
||||||
|
Key bindings for command line
|
||||||
|
-----------------------------
|
||||||
|
|
||||||
|
The install script will setup the following key bindings for bash, zsh, and
|
||||||
|
fish.
|
||||||
|
|
||||||
|
- `CTRL-T` - Paste the selected files and directories onto the command line
|
||||||
|
- Set `FZF_CTRL_T_COMMAND` to override the default command
|
||||||
|
- `CTRL-R` - Paste the selected command from history onto the command line
|
||||||
|
- Sort is disabled by default to respect chronological ordering
|
||||||
|
- Press `CTRL-R` again to toggle sort
|
||||||
|
- `ALT-C` - cd into the selected directory
|
||||||
|
- Set `FZF_ALT_C_COMMAND` to override the default command
|
||||||
|
|
||||||
|
If you're on a tmux session, fzf will start in a split pane. You may disable
|
||||||
|
this tmux integration by setting `FZF_TMUX` to 0, or change the height of the
|
||||||
|
pane with `FZF_TMUX_HEIGHT` (e.g. `20`, `50%`).
|
||||||
|
|
||||||
|
If you use vi mode on bash, you need to add `set -o vi` *before* `source
|
||||||
|
~/.fzf.bash` in your .bashrc, so that it correctly sets up key bindings for vi
|
||||||
|
mode.
|
||||||
|
|
||||||
Fuzzy completion for bash and zsh
|
Fuzzy completion for bash and zsh
|
||||||
---------------------------------
|
---------------------------------
|
||||||
|
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ while [ $# -gt 0 ]; do
|
|||||||
shift
|
shift
|
||||||
done
|
done
|
||||||
|
|
||||||
if ! [ -n "$TMUX_PANE" -a $lines -gt 15 ]; then
|
if ! [ -n "$TMUX" -a "$lines" -gt 15 ]; then
|
||||||
fzf "${args[@]}"
|
fzf "${args[@]}"
|
||||||
exit $?
|
exit $?
|
||||||
fi
|
fi
|
||||||
@@ -134,6 +134,7 @@ mkfifo -m o+w $fifo3
|
|||||||
# Build arguments to fzf
|
# Build arguments to fzf
|
||||||
opts=""
|
opts=""
|
||||||
for arg in "${args[@]}"; do
|
for arg in "${args[@]}"; do
|
||||||
|
arg="${arg//\\/\\\\}"
|
||||||
arg="${arg//\"/\\\"}"
|
arg="${arg//\"/\\\"}"
|
||||||
arg="${arg//\`/\\\`}"
|
arg="${arg//\`/\\\`}"
|
||||||
opts="$opts \"$arg\""
|
opts="$opts \"$arg\""
|
||||||
|
|||||||
14
install
14
install
@@ -2,8 +2,8 @@
|
|||||||
|
|
||||||
set -u
|
set -u
|
||||||
|
|
||||||
[[ "$@" =~ --pre ]] && version=0.12.1 pre=1 ||
|
[[ "$@" =~ --pre ]] && version=0.12.2 pre=1 ||
|
||||||
version=0.12.1 pre=0
|
version=0.12.2 pre=0
|
||||||
|
|
||||||
auto_completion=
|
auto_completion=
|
||||||
key_bindings=
|
key_bindings=
|
||||||
@@ -230,10 +230,14 @@ if [ -n "$binary_error" ]; then
|
|||||||
if [ $binary_available -eq 0 ]; then
|
if [ $binary_available -eq 0 ]; then
|
||||||
echo "No prebuilt binary for $archi ..."
|
echo "No prebuilt binary for $archi ..."
|
||||||
if command -v go > /dev/null; then
|
if command -v go > /dev/null; then
|
||||||
echo -n "Building binary (go get github.com/junegunn/fzf/src/fzf) ... "
|
echo -n "Building binary (go get -u github.com/junegunn/fzf/src/fzf) ... "
|
||||||
if go get github.com/junegunn/fzf/src/fzf; then
|
if [ -z "${GOPATH-}" ]; then
|
||||||
|
export GOPATH="${TMPDIR:-/tmp}/fzf-gopath"
|
||||||
|
mkdir -p "$GOPATH"
|
||||||
|
fi
|
||||||
|
if go get -u github.com/junegunn/fzf/src/fzf; then
|
||||||
echo "OK"
|
echo "OK"
|
||||||
link_fzf_in_path
|
cp "$GOPATH/bin/fzf" "$fzf_base/bin/"
|
||||||
else
|
else
|
||||||
echo "Failed to build binary ..."
|
echo "Failed to build binary ..."
|
||||||
install_ruby_fzf
|
install_ruby_fzf
|
||||||
|
|||||||
@@ -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 2016" "fzf 0.12.1" "fzf - a command-line fuzzy finder"
|
.TH fzf 1 "May 2016" "fzf 0.12.2" "fzf - a command-line fuzzy finder"
|
||||||
|
|
||||||
.SH NAME
|
.SH NAME
|
||||||
fzf - a command-line fuzzy finder
|
fzf - a command-line fuzzy finder
|
||||||
@@ -183,6 +183,9 @@ on the center of the screen.
|
|||||||
.B "--inline-info"
|
.B "--inline-info"
|
||||||
Display finder info inline with the query
|
Display finder info inline with the query
|
||||||
.TP
|
.TP
|
||||||
|
.BI "--jump-labels=" "CHARS"
|
||||||
|
Label characters for \fBjump\fR and \fBjump-accept\fR
|
||||||
|
.TP
|
||||||
.BI "--prompt=" "STR"
|
.BI "--prompt=" "STR"
|
||||||
Input prompt (default: '> ')
|
Input prompt (default: '> ')
|
||||||
.TP
|
.TP
|
||||||
@@ -200,11 +203,14 @@ e.g. \fBfzf --bind=ctrl-j:accept,ctrl-k:kill-line\fR
|
|||||||
.B AVAILABLE KEYS: (SYNONYMS)
|
.B AVAILABLE KEYS: (SYNONYMS)
|
||||||
\fIctrl-[a-z]\fR
|
\fIctrl-[a-z]\fR
|
||||||
\fIalt-[a-z]\fR
|
\fIalt-[a-z]\fR
|
||||||
\fIf[1-4]\fR
|
\fIf[1-10]\fR
|
||||||
\fIenter\fR (\fIreturn\fR \fIctrl-m\fR)
|
\fIenter\fR (\fIreturn\fR \fIctrl-m\fR)
|
||||||
\fIspace\fR
|
\fIspace\fR
|
||||||
\fIbspace\fR (\fIbs\fR)
|
\fIbspace\fR (\fIbs\fR)
|
||||||
|
\fIalt-enter\fR
|
||||||
|
\fIalt-space\fR
|
||||||
\fIalt-bspace\fR (\fIalt-bs\fR)
|
\fIalt-bspace\fR (\fIalt-bs\fR)
|
||||||
|
\fIalt-/\fR
|
||||||
\fItab\fR
|
\fItab\fR
|
||||||
\fIbtab\fR (\fIshift-tab\fR)
|
\fIbtab\fR (\fIshift-tab\fR)
|
||||||
\fIesc\fR
|
\fIesc\fR
|
||||||
@@ -244,12 +250,15 @@ e.g. \fBfzf --bind=ctrl-j:accept,ctrl-k:kill-line\fR
|
|||||||
\fBforward-char\fR \fIctrl-f right\fR
|
\fBforward-char\fR \fIctrl-f right\fR
|
||||||
\fBforward-word\fR \fIalt-f shift-right\fR
|
\fBforward-word\fR \fIalt-f shift-right\fR
|
||||||
\fBignore\fR
|
\fBignore\fR
|
||||||
|
\fBjump\fR (EasyMotion-like 2-keystroke movement)
|
||||||
|
\fBjump-accept\fR (jump and accept)
|
||||||
\fBkill-line\fR
|
\fBkill-line\fR
|
||||||
\fBkill-word\fR \fIalt-d\fR
|
\fBkill-word\fR \fIalt-d\fR
|
||||||
\fBnext-history\fR (\fIctrl-n\fR on \fB--history\fR)
|
\fBnext-history\fR (\fIctrl-n\fR on \fB--history\fR)
|
||||||
\fBpage-down\fR \fIpgdn\fR
|
\fBpage-down\fR \fIpgdn\fR
|
||||||
\fBpage-up\fR \fIpgup\fR
|
\fBpage-up\fR \fIpgup\fR
|
||||||
\fBprevious-history\fR (\fIctrl-p\fR on \fB--history\fR)
|
\fBprevious-history\fR (\fIctrl-p\fR on \fB--history\fR)
|
||||||
|
\fBprint-query\fR (print query and exit)
|
||||||
\fBselect-all\fR
|
\fBselect-all\fR
|
||||||
\fBtoggle\fR
|
\fBtoggle\fR
|
||||||
\fBtoggle-all\fR
|
\fBtoggle-all\fR
|
||||||
|
|||||||
@@ -298,7 +298,7 @@ function! s:split(dict)
|
|||||||
\ 'down': ['botright', 'resize', &lines],
|
\ 'down': ['botright', 'resize', &lines],
|
||||||
\ 'left': ['vertical topleft', 'vertical resize', &columns],
|
\ 'left': ['vertical topleft', 'vertical resize', &columns],
|
||||||
\ 'right': ['vertical botright', 'vertical resize', &columns] }
|
\ 'right': ['vertical botright', 'vertical resize', &columns] }
|
||||||
let s:ppos = s:getpos()
|
let ppos = s:getpos()
|
||||||
try
|
try
|
||||||
for [dir, triple] in items(directions)
|
for [dir, triple] in items(directions)
|
||||||
let val = get(a:dict, dir, '')
|
let val = get(a:dict, dir, '')
|
||||||
@@ -311,7 +311,7 @@ function! s:split(dict)
|
|||||||
endif
|
endif
|
||||||
execute cmd sz.'new'
|
execute cmd sz.'new'
|
||||||
execute resz sz
|
execute resz sz
|
||||||
return {}
|
return [ppos, {}]
|
||||||
endif
|
endif
|
||||||
endfor
|
endfor
|
||||||
if s:present(a:dict, 'window')
|
if s:present(a:dict, 'window')
|
||||||
@@ -319,36 +319,44 @@ function! s:split(dict)
|
|||||||
else
|
else
|
||||||
execute (tabpagenr()-1).'tabnew'
|
execute (tabpagenr()-1).'tabnew'
|
||||||
endif
|
endif
|
||||||
return { '&l:wfw': &l:wfw, '&l:wfh': &l:wfh }
|
return [ppos, { '&l:wfw': &l:wfw, '&l:wfh': &l:wfh }]
|
||||||
finally
|
finally
|
||||||
setlocal winfixwidth winfixheight
|
setlocal winfixwidth winfixheight
|
||||||
endtry
|
endtry
|
||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
function! s:execute_term(dict, command, temps) abort
|
function! s:execute_term(dict, command, temps) abort
|
||||||
let winopts = s:split(a:dict)
|
let [ppos, winopts] = s:split(a:dict)
|
||||||
|
let fzf = { 'buf': bufnr('%'), 'ppos': ppos, 'dict': a:dict, 'temps': a:temps,
|
||||||
let fzf = { 'buf': bufnr('%'), 'dict': a:dict, 'temps': a:temps, 'name': 'FZF', 'winopts': winopts }
|
\ 'name': 'FZF', 'winopts': winopts, 'command': a:command }
|
||||||
let s:command = a:command
|
function! fzf.switch_back(inplace)
|
||||||
|
if a:inplace && bufnr('') == self.buf
|
||||||
|
" FIXME: Can't re-enter normal mode from terminal mode
|
||||||
|
" execute "normal! \<c-^>"
|
||||||
|
b #
|
||||||
|
" No other listed buffer
|
||||||
|
if bufnr('') == self.buf
|
||||||
|
enew
|
||||||
|
endif
|
||||||
|
endif
|
||||||
|
endfunction
|
||||||
function! fzf.on_exit(id, code)
|
function! fzf.on_exit(id, code)
|
||||||
let pos = s:getpos()
|
if s:getpos() == self.ppos " {'window': 'enew'}
|
||||||
let inplace = pos == s:ppos " {'window': 'enew'}
|
|
||||||
if inplace
|
|
||||||
for [opt, val] in items(self.winopts)
|
for [opt, val] in items(self.winopts)
|
||||||
execute 'let' opt '=' val
|
execute 'let' opt '=' val
|
||||||
endfor
|
endfor
|
||||||
|
call self.switch_back(1)
|
||||||
else
|
else
|
||||||
if bufnr('') == self.buf
|
if bufnr('') == self.buf
|
||||||
" We use close instead of bd! since Vim does not close the split when
|
" We use close instead of bd! since Vim does not close the split when
|
||||||
" there's no other listed buffer (nvim +'set nobuflisted')
|
" there's no other listed buffer (nvim +'set nobuflisted')
|
||||||
close
|
close
|
||||||
endif
|
endif
|
||||||
if pos.tab == s:ppos.tab
|
execute 'tabnext' self.ppos.tab
|
||||||
wincmd p
|
execute self.ppos.win.'wincmd w'
|
||||||
endif
|
|
||||||
endif
|
endif
|
||||||
|
|
||||||
if !s:exit_handler(a:code, s:command, 1)
|
if !s:exit_handler(a:code, self.command, 1)
|
||||||
return
|
return
|
||||||
endif
|
endif
|
||||||
|
|
||||||
@@ -356,14 +364,7 @@ function! s:execute_term(dict, command, temps) abort
|
|||||||
let ret = []
|
let ret = []
|
||||||
try
|
try
|
||||||
let ret = s:callback(self.dict, self.temps)
|
let ret = s:callback(self.dict, self.temps)
|
||||||
|
call self.switch_back(s:getpos() == self.ppos)
|
||||||
if inplace && bufnr('') == self.buf
|
|
||||||
execute "normal! \<c-^>"
|
|
||||||
" No other listed buffer
|
|
||||||
if bufnr('') == self.buf
|
|
||||||
bd!
|
|
||||||
endif
|
|
||||||
endif
|
|
||||||
finally
|
finally
|
||||||
call s:popd(self.dict, ret)
|
call s:popd(self.dict, ret)
|
||||||
endtry
|
endtry
|
||||||
|
|||||||
37
src/Makefile
37
src/Makefile
@@ -7,10 +7,6 @@ else ifeq ($(UNAME_S),Linux)
|
|||||||
endif
|
endif
|
||||||
endif
|
endif
|
||||||
|
|
||||||
ifneq ($(shell uname -m),x86_64)
|
|
||||||
$(error "Build on $(UNAME_M) is not supported, yet.")
|
|
||||||
endif
|
|
||||||
|
|
||||||
SOURCES := $(wildcard *.go */*.go)
|
SOURCES := $(wildcard *.go */*.go)
|
||||||
ROOTDIR := $(shell dirname $(realpath $(lastword $(MAKEFILE_LIST))))
|
ROOTDIR := $(shell dirname $(realpath $(lastword $(MAKEFILE_LIST))))
|
||||||
BINDIR := $(shell dirname $(ROOTDIR))/bin
|
BINDIR := $(shell dirname $(ROOTDIR))/bin
|
||||||
@@ -26,15 +22,22 @@ RELEASE64 := fzf-$(VERSION)-$(GOOS)_amd64
|
|||||||
RELEASEARM7 := fzf-$(VERSION)-$(GOOS)_arm7
|
RELEASEARM7 := fzf-$(VERSION)-$(GOOS)_arm7
|
||||||
export GOPATH
|
export GOPATH
|
||||||
|
|
||||||
all: release
|
UNAME_M := $(shell uname -m)
|
||||||
|
ifeq ($(UNAME_M),x86_64)
|
||||||
|
BINARY := $(BINARY64)
|
||||||
|
else ifeq ($(UNAME_M),i686)
|
||||||
|
BINARY := $(BINARY32)
|
||||||
|
else
|
||||||
|
$(error "Build on $(UNAME_M) is not supported, yet.")
|
||||||
|
endif
|
||||||
|
|
||||||
release: test build
|
all: fzf/$(BINARY)
|
||||||
|
|
||||||
|
release: test fzf/$(BINARY32) fzf/$(BINARY64)
|
||||||
-cd fzf && cp $(BINARY32) $(RELEASE32) && tar -czf $(RELEASE32).tgz $(RELEASE32)
|
-cd fzf && cp $(BINARY32) $(RELEASE32) && tar -czf $(RELEASE32).tgz $(RELEASE32)
|
||||||
cd fzf && cp $(BINARY64) $(RELEASE64) && tar -czf $(RELEASE64).tgz $(RELEASE64) && \
|
cd fzf && cp $(BINARY64) $(RELEASE64) && tar -czf $(RELEASE64).tgz $(RELEASE64) && \
|
||||||
rm -f $(RELEASE32) $(RELEASE64)
|
rm -f $(RELEASE32) $(RELEASE64)
|
||||||
|
|
||||||
build: fzf/$(BINARY32) fzf/$(BINARY64)
|
|
||||||
|
|
||||||
$(SRCDIR):
|
$(SRCDIR):
|
||||||
mkdir -p $(shell dirname $(SRCDIR))
|
mkdir -p $(shell dirname $(SRCDIR))
|
||||||
ln -s $(ROOTDIR) $(SRCDIR)
|
ln -s $(ROOTDIR) $(SRCDIR)
|
||||||
@@ -44,7 +47,7 @@ deps: $(SRCDIR) $(SOURCES)
|
|||||||
|
|
||||||
android-build: $(SRCDIR)
|
android-build: $(SRCDIR)
|
||||||
cd $(SRCDIR) && GOARCH=arm GOARM=7 CGO_ENABLED=1 go get
|
cd $(SRCDIR) && GOARCH=arm GOARM=7 CGO_ENABLED=1 go get
|
||||||
cd $(SRCDIR)/fzf && GOARCH=arm GOARM=7 CGO_ENABLED=1 go build -a -ldflags="-extldflags=-pie" -o $(BINARYARM7)
|
cd $(SRCDIR)/fzf && GOARCH=arm GOARM=7 CGO_ENABLED=1 go build -a -ldflags="-w -extldflags=-pie" -o $(BINARYARM7)
|
||||||
cd $(SRCDIR)/fzf && cp $(BINARYARM7) $(RELEASEARM7) && tar -czf $(RELEASEARM7).tgz $(RELEASEARM7) && \
|
cd $(SRCDIR)/fzf && cp $(BINARYARM7) $(RELEASEARM7) && tar -czf $(RELEASEARM7).tgz $(RELEASEARM7) && \
|
||||||
rm -f $(RELEASEARM7)
|
rm -f $(RELEASEARM7)
|
||||||
|
|
||||||
@@ -54,20 +57,20 @@ test: deps
|
|||||||
install: $(BINDIR)/fzf
|
install: $(BINDIR)/fzf
|
||||||
|
|
||||||
uninstall:
|
uninstall:
|
||||||
rm -f $(BINDIR)/fzf $(BINDIR)/$(BINARY64)
|
rm -f $(BINDIR)/fzf $(BINDIR)/$(BINARY)
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
cd fzf && rm -f fzf-*
|
cd fzf && rm -f fzf-*
|
||||||
|
|
||||||
fzf/$(BINARY32): deps
|
fzf/$(BINARY32): deps
|
||||||
cd fzf && GOARCH=386 CGO_ENABLED=1 go build -a -tags "$(TAGS)" -o $(BINARY32)
|
cd fzf && GOARCH=386 CGO_ENABLED=1 go build -a -ldflags -w -tags "$(TAGS)" -o $(BINARY32)
|
||||||
|
|
||||||
fzf/$(BINARY64): deps
|
fzf/$(BINARY64): deps
|
||||||
cd fzf && go build -a -tags "$(TAGS)" -o $(BINARY64)
|
cd fzf && go build -a -ldflags -w -tags "$(TAGS)" -o $(BINARY64)
|
||||||
|
|
||||||
$(BINDIR)/fzf: fzf/$(BINARY64) | $(BINDIR)
|
$(BINDIR)/fzf: fzf/$(BINARY) | $(BINDIR)
|
||||||
cp -f fzf/$(BINARY64) $(BINDIR)
|
cp -f fzf/$(BINARY) $(BINDIR)
|
||||||
cd $(BINDIR) && ln -sf $(BINARY64) fzf
|
cd $(BINDIR) && ln -sf $(BINARY) fzf
|
||||||
|
|
||||||
$(BINDIR):
|
$(BINDIR):
|
||||||
mkdir -p $@
|
mkdir -p $@
|
||||||
@@ -98,7 +101,7 @@ centos: docker-centos
|
|||||||
|
|
||||||
linux: docker-centos
|
linux: docker-centos
|
||||||
docker run $(DOCKEROPTS) junegunn/centos-sandbox \
|
docker run $(DOCKEROPTS) junegunn/centos-sandbox \
|
||||||
/bin/bash -ci 'cd /fzf/src; make TAGS=static'
|
/bin/bash -ci 'cd /fzf/src; make TAGS=static release'
|
||||||
|
|
||||||
ubuntu-android: docker-android
|
ubuntu-android: docker-android
|
||||||
docker run $(DOCKEROPTS) junegunn/android-sandbox \
|
docker run $(DOCKEROPTS) junegunn/android-sandbox \
|
||||||
@@ -108,6 +111,6 @@ android: docker-android
|
|||||||
docker run $(DOCKEROPTS) junegunn/android-sandbox \
|
docker run $(DOCKEROPTS) junegunn/android-sandbox \
|
||||||
/bin/bash -ci 'cd /fzf/src; GOOS=android make android-build'
|
/bin/bash -ci 'cd /fzf/src; GOOS=android make android-build'
|
||||||
|
|
||||||
.PHONY: all build deps release test install uninstall clean \
|
.PHONY: all deps release test install uninstall clean \
|
||||||
linux arch ubuntu centos docker-arch docker-ubuntu docker-centos \
|
linux arch ubuntu centos docker-arch docker-ubuntu docker-centos \
|
||||||
android-build docker-android ubuntu-android android
|
android-build docker-android ubuntu-android android
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ Build
|
|||||||
|
|
||||||
```sh
|
```sh
|
||||||
# Build fzf executables and tarballs
|
# Build fzf executables and tarballs
|
||||||
make
|
make release
|
||||||
|
|
||||||
# Install the executable to ../bin directory
|
# Install the executable to ../bin directory
|
||||||
make install
|
make install
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import (
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
// Current version
|
// Current version
|
||||||
version = "0.12.1"
|
version = "0.12.2"
|
||||||
|
|
||||||
// Core
|
// Core
|
||||||
coordinatorDelayMax time.Duration = 100 * time.Millisecond
|
coordinatorDelayMax time.Duration = 100 * time.Millisecond
|
||||||
@@ -36,6 +36,9 @@ const (
|
|||||||
|
|
||||||
// History
|
// History
|
||||||
defaultHistoryMax int = 1000
|
defaultHistoryMax int = 1000
|
||||||
|
|
||||||
|
// Jump labels
|
||||||
|
defaultJumpLabels string = "asdfghjklqwertyuiopzxcvbnm1234567890ASDFGHJKLQWERTYUIOPZXCVBNM`~;:,<.>/?'\"!@#$%^&*()[{]}-_=+"
|
||||||
)
|
)
|
||||||
|
|
||||||
// fzf events
|
// fzf events
|
||||||
|
|||||||
@@ -80,7 +80,16 @@ const (
|
|||||||
F2
|
F2
|
||||||
F3
|
F3
|
||||||
F4
|
F4
|
||||||
|
F5
|
||||||
|
F6
|
||||||
|
F7
|
||||||
|
F8
|
||||||
|
F9
|
||||||
|
F10
|
||||||
|
|
||||||
|
AltEnter
|
||||||
|
AltSpace
|
||||||
|
AltSlash
|
||||||
AltBS
|
AltBS
|
||||||
AltA
|
AltA
|
||||||
AltB
|
AltB
|
||||||
@@ -109,6 +118,8 @@ const (
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
doubleClickDuration = 500 * time.Millisecond
|
doubleClickDuration = 500 * time.Millisecond
|
||||||
|
colDefault = -1
|
||||||
|
colUndefined = -2
|
||||||
)
|
)
|
||||||
|
|
||||||
type ColorTheme struct {
|
type ColorTheme struct {
|
||||||
@@ -159,6 +170,23 @@ var (
|
|||||||
DarkBG int
|
DarkBG int
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func EmptyTheme() *ColorTheme {
|
||||||
|
return &ColorTheme{
|
||||||
|
UseDefault: true,
|
||||||
|
Fg: colUndefined,
|
||||||
|
Bg: colUndefined,
|
||||||
|
DarkBg: colUndefined,
|
||||||
|
Prompt: colUndefined,
|
||||||
|
Match: colUndefined,
|
||||||
|
Current: colUndefined,
|
||||||
|
CurrentMatch: colUndefined,
|
||||||
|
Spinner: colUndefined,
|
||||||
|
Info: colUndefined,
|
||||||
|
Cursor: colUndefined,
|
||||||
|
Selected: colUndefined,
|
||||||
|
Header: colUndefined}
|
||||||
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
_prevDownTime = time.Unix(0, 0)
|
_prevDownTime = time.Unix(0, 0)
|
||||||
_clickY = []int{}
|
_clickY = []int{}
|
||||||
@@ -280,44 +308,58 @@ func Init(theme *ColorTheme, black bool, mouse bool) {
|
|||||||
|
|
||||||
if theme != nil {
|
if theme != nil {
|
||||||
C.start_color()
|
C.start_color()
|
||||||
initPairs(theme, black)
|
var baseTheme *ColorTheme
|
||||||
|
if C.tigetnum(C.CString("colors")) >= 256 {
|
||||||
|
baseTheme = Dark256
|
||||||
|
} else {
|
||||||
|
baseTheme = Default16
|
||||||
|
}
|
||||||
|
initPairs(baseTheme, theme, black)
|
||||||
_color = attrColored
|
_color = attrColored
|
||||||
} else {
|
} else {
|
||||||
_color = attrMono
|
_color = attrMono
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func initPairs(theme *ColorTheme, black bool) {
|
func override(a int16, b int16) C.short {
|
||||||
fg := C.short(theme.Fg)
|
if b == colUndefined {
|
||||||
bg := C.short(theme.Bg)
|
return C.short(a)
|
||||||
|
}
|
||||||
|
return C.short(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func initPairs(baseTheme *ColorTheme, theme *ColorTheme, black bool) {
|
||||||
|
fg := override(baseTheme.Fg, theme.Fg)
|
||||||
|
bg := override(baseTheme.Bg, theme.Bg)
|
||||||
if black {
|
if black {
|
||||||
bg = C.COLOR_BLACK
|
bg = C.COLOR_BLACK
|
||||||
} else if theme.UseDefault {
|
} else if theme.UseDefault {
|
||||||
fg = -1
|
fg = colDefault
|
||||||
bg = -1
|
bg = colDefault
|
||||||
C.use_default_colors()
|
C.use_default_colors()
|
||||||
}
|
}
|
||||||
if theme.UseDefault {
|
if theme.UseDefault {
|
||||||
FG = -1
|
FG = colDefault
|
||||||
BG = -1
|
BG = colDefault
|
||||||
} else {
|
} else {
|
||||||
FG = int(fg)
|
FG = int(fg)
|
||||||
BG = int(bg)
|
BG = int(bg)
|
||||||
C.assume_default_colors(C.int(theme.Fg), C.int(bg))
|
C.assume_default_colors(C.int(override(baseTheme.Fg, theme.Fg)), C.int(bg))
|
||||||
}
|
}
|
||||||
|
|
||||||
CurrentFG = int(theme.Current)
|
currentFG := override(baseTheme.Current, theme.Current)
|
||||||
DarkBG = int(theme.DarkBg)
|
darkBG := override(baseTheme.DarkBg, theme.DarkBg)
|
||||||
darkBG := C.short(DarkBG)
|
CurrentFG = int(currentFG)
|
||||||
C.init_pair(ColPrompt, C.short(theme.Prompt), bg)
|
DarkBG = int(darkBG)
|
||||||
C.init_pair(ColMatch, C.short(theme.Match), bg)
|
C.init_pair(ColPrompt, override(baseTheme.Prompt, theme.Prompt), bg)
|
||||||
C.init_pair(ColCurrent, C.short(theme.Current), darkBG)
|
C.init_pair(ColMatch, override(baseTheme.Match, theme.Match), bg)
|
||||||
C.init_pair(ColCurrentMatch, C.short(theme.CurrentMatch), darkBG)
|
C.init_pair(ColCurrent, currentFG, darkBG)
|
||||||
C.init_pair(ColSpinner, C.short(theme.Spinner), bg)
|
C.init_pair(ColCurrentMatch, override(baseTheme.CurrentMatch, theme.CurrentMatch), darkBG)
|
||||||
C.init_pair(ColInfo, C.short(theme.Info), bg)
|
C.init_pair(ColSpinner, override(baseTheme.Spinner, theme.Spinner), bg)
|
||||||
C.init_pair(ColCursor, C.short(theme.Cursor), darkBG)
|
C.init_pair(ColInfo, override(baseTheme.Info, theme.Info), bg)
|
||||||
C.init_pair(ColSelected, C.short(theme.Selected), darkBG)
|
C.init_pair(ColCursor, override(baseTheme.Cursor, theme.Cursor), darkBG)
|
||||||
C.init_pair(ColHeader, C.short(theme.Header), bg)
|
C.init_pair(ColSelected, override(baseTheme.Selected, theme.Selected), darkBG)
|
||||||
|
C.init_pair(ColHeader, override(baseTheme.Header, theme.Header), bg)
|
||||||
}
|
}
|
||||||
|
|
||||||
func Close() {
|
func Close() {
|
||||||
@@ -384,6 +426,12 @@ func escSequence(sz *int) Event {
|
|||||||
}
|
}
|
||||||
*sz = 2
|
*sz = 2
|
||||||
switch _buf[1] {
|
switch _buf[1] {
|
||||||
|
case 13:
|
||||||
|
return Event{AltEnter, 0, nil}
|
||||||
|
case 32:
|
||||||
|
return Event{AltSpace, 0, nil}
|
||||||
|
case 47:
|
||||||
|
return Event{AltSlash, 0, nil}
|
||||||
case 98:
|
case 98:
|
||||||
return Event{AltB, 0, nil}
|
return Event{AltB, 0, nil}
|
||||||
case 100:
|
case 100:
|
||||||
@@ -429,6 +477,15 @@ func escSequence(sz *int) Event {
|
|||||||
*sz = 4
|
*sz = 4
|
||||||
switch _buf[2] {
|
switch _buf[2] {
|
||||||
case 50:
|
case 50:
|
||||||
|
if len(_buf) == 5 && _buf[4] == 126 {
|
||||||
|
*sz = 5
|
||||||
|
switch _buf[3] {
|
||||||
|
case 48:
|
||||||
|
return Event{F9, 0, nil}
|
||||||
|
case 49:
|
||||||
|
return Event{F10, 0, nil}
|
||||||
|
}
|
||||||
|
}
|
||||||
return Event{Invalid, 0, nil} // INS
|
return Event{Invalid, 0, nil} // INS
|
||||||
case 51:
|
case 51:
|
||||||
return Event{Del, 0, nil}
|
return Event{Del, 0, nil}
|
||||||
@@ -442,6 +499,21 @@ func escSequence(sz *int) Event {
|
|||||||
switch _buf[3] {
|
switch _buf[3] {
|
||||||
case 126:
|
case 126:
|
||||||
return Event{Home, 0, nil}
|
return Event{Home, 0, nil}
|
||||||
|
case 53, 55, 56, 57:
|
||||||
|
if len(_buf) == 5 && _buf[4] == 126 {
|
||||||
|
*sz = 5
|
||||||
|
switch _buf[3] {
|
||||||
|
case 53:
|
||||||
|
return Event{F5, 0, nil}
|
||||||
|
case 55:
|
||||||
|
return Event{F6, 0, nil}
|
||||||
|
case 56:
|
||||||
|
return Event{F7, 0, nil}
|
||||||
|
case 57:
|
||||||
|
return Event{F8, 0, nil}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Event{Invalid, 0, nil}
|
||||||
case 59:
|
case 59:
|
||||||
if len(_buf) != 6 {
|
if len(_buf) != 6 {
|
||||||
return Event{Invalid, 0, nil}
|
return Event{Invalid, 0, nil}
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ const usage = `usage: fzf [options]
|
|||||||
--hscroll-off=COL Number of screen columns to keep to the right of the
|
--hscroll-off=COL Number of screen columns to keep to the right of the
|
||||||
highlighted substring (default: 10)
|
highlighted substring (default: 10)
|
||||||
--inline-info Display finder info inline with the query
|
--inline-info Display finder info inline with the query
|
||||||
|
--jump-labels=CHARS Label characters for jump and jump-accept
|
||||||
--prompt=STR Input prompt (default: '> ')
|
--prompt=STR Input prompt (default: '> ')
|
||||||
--bind=KEYBINDS Custom key bindings. Refer to the man page.
|
--bind=KEYBINDS Custom key bindings. Refer to the man page.
|
||||||
--history=FILE History file
|
--history=FILE History file
|
||||||
@@ -112,6 +113,7 @@ type Options struct {
|
|||||||
Hscroll bool
|
Hscroll bool
|
||||||
HscrollOff int
|
HscrollOff int
|
||||||
InlineInfo bool
|
InlineInfo bool
|
||||||
|
JumpLabels string
|
||||||
Prompt string
|
Prompt string
|
||||||
Query string
|
Query string
|
||||||
Select1 bool
|
Select1 bool
|
||||||
@@ -132,13 +134,6 @@ type Options struct {
|
|||||||
Version bool
|
Version bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func defaultTheme() *curses.ColorTheme {
|
|
||||||
if strings.Contains(os.Getenv("TERM"), "256") {
|
|
||||||
return curses.Dark256
|
|
||||||
}
|
|
||||||
return curses.Default16
|
|
||||||
}
|
|
||||||
|
|
||||||
func defaultOptions() *Options {
|
func defaultOptions() *Options {
|
||||||
return &Options{
|
return &Options{
|
||||||
Fuzzy: true,
|
Fuzzy: true,
|
||||||
@@ -153,13 +148,14 @@ func defaultOptions() *Options {
|
|||||||
Multi: false,
|
Multi: false,
|
||||||
Ansi: false,
|
Ansi: false,
|
||||||
Mouse: true,
|
Mouse: true,
|
||||||
Theme: defaultTheme(),
|
Theme: curses.EmptyTheme(),
|
||||||
Black: false,
|
Black: false,
|
||||||
Reverse: false,
|
Reverse: false,
|
||||||
Cycle: false,
|
Cycle: false,
|
||||||
Hscroll: true,
|
Hscroll: true,
|
||||||
HscrollOff: 10,
|
HscrollOff: 10,
|
||||||
InlineInfo: false,
|
InlineInfo: false,
|
||||||
|
JumpLabels: defaultJumpLabels,
|
||||||
Prompt: "> ",
|
Prompt: "> ",
|
||||||
Query: "",
|
Query: "",
|
||||||
Select1: false,
|
Select1: false,
|
||||||
@@ -322,6 +318,12 @@ func parseKeyChords(str string, message string) map[int]string {
|
|||||||
chord = curses.AltZ + int(' ')
|
chord = curses.AltZ + int(' ')
|
||||||
case "bspace", "bs":
|
case "bspace", "bs":
|
||||||
chord = curses.BSpace
|
chord = curses.BSpace
|
||||||
|
case "alt-enter", "alt-return":
|
||||||
|
chord = curses.AltEnter
|
||||||
|
case "alt-space":
|
||||||
|
chord = curses.AltSpace
|
||||||
|
case "alt-/":
|
||||||
|
chord = curses.AltSlash
|
||||||
case "alt-bs", "alt-bspace":
|
case "alt-bs", "alt-bspace":
|
||||||
chord = curses.AltBS
|
chord = curses.AltBS
|
||||||
case "tab":
|
case "tab":
|
||||||
@@ -346,12 +348,14 @@ func parseKeyChords(str string, message string) map[int]string {
|
|||||||
chord = curses.SRight
|
chord = curses.SRight
|
||||||
case "double-click":
|
case "double-click":
|
||||||
chord = curses.DoubleClick
|
chord = curses.DoubleClick
|
||||||
|
case "f10":
|
||||||
|
chord = curses.F10
|
||||||
default:
|
default:
|
||||||
if len(key) == 6 && strings.HasPrefix(lkey, "ctrl-") && isAlphabet(lkey[5]) {
|
if len(key) == 6 && strings.HasPrefix(lkey, "ctrl-") && isAlphabet(lkey[5]) {
|
||||||
chord = curses.CtrlA + int(lkey[5]) - 'a'
|
chord = curses.CtrlA + int(lkey[5]) - 'a'
|
||||||
} else if len(key) == 5 && strings.HasPrefix(lkey, "alt-") && isAlphabet(lkey[4]) {
|
} else if len(key) == 5 && strings.HasPrefix(lkey, "alt-") && isAlphabet(lkey[4]) {
|
||||||
chord = curses.AltA + int(lkey[4]) - 'a'
|
chord = curses.AltA + int(lkey[4]) - 'a'
|
||||||
} else if len(key) == 2 && strings.HasPrefix(lkey, "f") && key[1] >= '1' && key[1] <= '4' {
|
} else if len(key) == 2 && strings.HasPrefix(lkey, "f") && key[1] >= '1' && key[1] <= '9' {
|
||||||
chord = curses.F1 + int(key[1]) - '1'
|
chord = curses.F1 + int(key[1]) - '1'
|
||||||
} else if utf8.RuneCountInString(key) == 1 {
|
} else if utf8.RuneCountInString(key) == 1 {
|
||||||
chord = curses.AltZ + int([]rune(key)[0])
|
chord = curses.AltZ + int([]rune(key)[0])
|
||||||
@@ -534,6 +538,8 @@ func parseKeymap(keymap map[int]actionType, execmap map[int]string, str string)
|
|||||||
keymap[key] = actAbort
|
keymap[key] = actAbort
|
||||||
case "accept":
|
case "accept":
|
||||||
keymap[key] = actAccept
|
keymap[key] = actAccept
|
||||||
|
case "print-query":
|
||||||
|
keymap[key] = actPrintQuery
|
||||||
case "backward-char":
|
case "backward-char":
|
||||||
keymap[key] = actBackwardChar
|
keymap[key] = actBackwardChar
|
||||||
case "backward-delete-char":
|
case "backward-delete-char":
|
||||||
@@ -554,6 +560,10 @@ func parseKeymap(keymap map[int]actionType, execmap map[int]string, str string)
|
|||||||
keymap[key] = actForwardChar
|
keymap[key] = actForwardChar
|
||||||
case "forward-word":
|
case "forward-word":
|
||||||
keymap[key] = actForwardWord
|
keymap[key] = actForwardWord
|
||||||
|
case "jump":
|
||||||
|
keymap[key] = actJump
|
||||||
|
case "jump-accept":
|
||||||
|
keymap[key] = actJumpAccept
|
||||||
case "kill-line":
|
case "kill-line":
|
||||||
keymap[key] = actKillLine
|
keymap[key] = actKillLine
|
||||||
case "kill-word":
|
case "kill-word":
|
||||||
@@ -714,6 +724,7 @@ func parseOptions(opts *Options, allArgs []string) {
|
|||||||
opts.History.maxSize = historyMax
|
opts.History.maxSize = historyMax
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
validateJumpLabels := false
|
||||||
for i := 0; i < len(allArgs); i++ {
|
for i := 0; i < len(allArgs); i++ {
|
||||||
arg := allArgs[i]
|
arg := allArgs[i]
|
||||||
switch arg {
|
switch arg {
|
||||||
@@ -745,7 +756,7 @@ func parseOptions(opts *Options, allArgs []string) {
|
|||||||
case "--color":
|
case "--color":
|
||||||
spec := optionalNextString(allArgs, &i)
|
spec := optionalNextString(allArgs, &i)
|
||||||
if len(spec) == 0 {
|
if len(spec) == 0 {
|
||||||
opts.Theme = defaultTheme()
|
opts.Theme = curses.EmptyTheme()
|
||||||
} else {
|
} else {
|
||||||
opts.Theme = parseTheme(opts.Theme, spec)
|
opts.Theme = parseTheme(opts.Theme, spec)
|
||||||
}
|
}
|
||||||
@@ -805,6 +816,9 @@ func parseOptions(opts *Options, allArgs []string) {
|
|||||||
opts.InlineInfo = true
|
opts.InlineInfo = true
|
||||||
case "--no-inline-info":
|
case "--no-inline-info":
|
||||||
opts.InlineInfo = false
|
opts.InlineInfo = false
|
||||||
|
case "--jump-labels":
|
||||||
|
opts.JumpLabels = nextString(allArgs, &i, "label characters required")
|
||||||
|
validateJumpLabels = true
|
||||||
case "-1", "--select-1":
|
case "-1", "--select-1":
|
||||||
opts.Select1 = true
|
opts.Select1 = true
|
||||||
case "+1", "--no-select-1":
|
case "+1", "--no-select-1":
|
||||||
@@ -892,6 +906,8 @@ func parseOptions(opts *Options, allArgs []string) {
|
|||||||
opts.Tabstop = atoi(value)
|
opts.Tabstop = atoi(value)
|
||||||
} else if match, value := optString(arg, "--hscroll-off="); match {
|
} else if match, value := optString(arg, "--hscroll-off="); match {
|
||||||
opts.HscrollOff = atoi(value)
|
opts.HscrollOff = atoi(value)
|
||||||
|
} else if match, value := optString(arg, "--jump-labels="); match {
|
||||||
|
opts.JumpLabels = value
|
||||||
} else {
|
} else {
|
||||||
errorExit("unknown option: " + arg)
|
errorExit("unknown option: " + arg)
|
||||||
}
|
}
|
||||||
@@ -909,6 +925,18 @@ func parseOptions(opts *Options, allArgs []string) {
|
|||||||
if opts.Tabstop < 1 {
|
if opts.Tabstop < 1 {
|
||||||
errorExit("tab stop must be a positive integer")
|
errorExit("tab stop must be a positive integer")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(opts.JumpLabels) == 0 {
|
||||||
|
errorExit("empty jump labels")
|
||||||
|
}
|
||||||
|
|
||||||
|
if validateJumpLabels {
|
||||||
|
for _, r := range opts.JumpLabels {
|
||||||
|
if r < 32 || r > 126 {
|
||||||
|
errorExit("non-ascii jump labels are not allowed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func postProcessOptions(opts *Options) {
|
func postProcessOptions(opts *Options) {
|
||||||
|
|||||||
@@ -123,14 +123,14 @@ func TestIrrelevantNth(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestParseKeys(t *testing.T) {
|
func TestParseKeys(t *testing.T) {
|
||||||
pairs := parseKeyChords("ctrl-z,alt-z,f2,@,Alt-a,!,ctrl-G,J,g", "")
|
pairs := parseKeyChords("ctrl-z,alt-z,f2,@,Alt-a,!,ctrl-G,J,g,ALT-enter,alt-SPACE", "")
|
||||||
check := func(i int, s string) {
|
check := func(i int, s string) {
|
||||||
if pairs[i] != s {
|
if pairs[i] != s {
|
||||||
t.Errorf("%s != %s", pairs[i], s)
|
t.Errorf("%s != %s", pairs[i], s)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(pairs) != 9 {
|
if len(pairs) != 11 {
|
||||||
t.Error(9)
|
t.Error(11)
|
||||||
}
|
}
|
||||||
check(curses.CtrlZ, "ctrl-z")
|
check(curses.CtrlZ, "ctrl-z")
|
||||||
check(curses.AltZ, "alt-z")
|
check(curses.AltZ, "alt-z")
|
||||||
@@ -141,6 +141,8 @@ func TestParseKeys(t *testing.T) {
|
|||||||
check(curses.CtrlA+'g'-'a', "ctrl-G")
|
check(curses.CtrlA+'g'-'a', "ctrl-G")
|
||||||
check(curses.AltZ+'J', "J")
|
check(curses.AltZ+'J', "J")
|
||||||
check(curses.AltZ+'g', "g")
|
check(curses.AltZ+'g', "g")
|
||||||
|
check(curses.AltEnter, "ALT-enter")
|
||||||
|
check(curses.AltSpace, "alt-SPACE")
|
||||||
|
|
||||||
// Synonyms
|
// Synonyms
|
||||||
pairs = parseKeyChords("enter,Return,space,tab,btab,esc,up,down,left,right", "")
|
pairs = parseKeyChords("enter,Return,space,tab,btab,esc,up,down,left,right", "")
|
||||||
|
|||||||
@@ -19,6 +19,14 @@ import (
|
|||||||
"github.com/junegunn/go-runewidth"
|
"github.com/junegunn/go-runewidth"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type jumpMode int
|
||||||
|
|
||||||
|
const (
|
||||||
|
jumpDisabled jumpMode = iota
|
||||||
|
jumpEnabled
|
||||||
|
jumpAcceptEnabled
|
||||||
|
)
|
||||||
|
|
||||||
// Terminal represents terminal input/output
|
// Terminal represents terminal input/output
|
||||||
type Terminal struct {
|
type Terminal struct {
|
||||||
initDelay time.Duration
|
initDelay time.Duration
|
||||||
@@ -50,6 +58,8 @@ type Terminal struct {
|
|||||||
count int
|
count int
|
||||||
progress int
|
progress int
|
||||||
reading bool
|
reading bool
|
||||||
|
jumping jumpMode
|
||||||
|
jumpLabels string
|
||||||
merger *Merger
|
merger *Merger
|
||||||
selected map[int32]selectedItem
|
selected map[int32]selectedItem
|
||||||
reqBox *util.EventBox
|
reqBox *util.EventBox
|
||||||
@@ -88,9 +98,11 @@ const (
|
|||||||
reqInfo
|
reqInfo
|
||||||
reqHeader
|
reqHeader
|
||||||
reqList
|
reqList
|
||||||
|
reqJump
|
||||||
reqRefresh
|
reqRefresh
|
||||||
reqRedraw
|
reqRedraw
|
||||||
reqClose
|
reqClose
|
||||||
|
reqPrintQuery
|
||||||
reqQuit
|
reqQuit
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -132,6 +144,9 @@ const (
|
|||||||
actUp
|
actUp
|
||||||
actPageUp
|
actPageUp
|
||||||
actPageDown
|
actPageDown
|
||||||
|
actJump
|
||||||
|
actJumpAccept
|
||||||
|
actPrintQuery
|
||||||
actToggleSort
|
actToggleSort
|
||||||
actPreviousHistory
|
actPreviousHistory
|
||||||
actNextHistory
|
actNextHistory
|
||||||
@@ -233,6 +248,8 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
|
|||||||
header0: header,
|
header0: header,
|
||||||
ansi: opts.Ansi,
|
ansi: opts.Ansi,
|
||||||
reading: true,
|
reading: true,
|
||||||
|
jumping: jumpDisabled,
|
||||||
|
jumpLabels: opts.JumpLabels,
|
||||||
merger: EmptyMerger,
|
merger: EmptyMerger,
|
||||||
selected: make(map[int32]selectedItem),
|
selected: make(map[int32]selectedItem),
|
||||||
reqBox: util.NewEventBox(),
|
reqBox: util.NewEventBox(),
|
||||||
@@ -495,15 +512,25 @@ func (t *Terminal) printList() {
|
|||||||
}
|
}
|
||||||
t.move(line, 0, true)
|
t.move(line, 0, true)
|
||||||
if i < count {
|
if i < count {
|
||||||
t.printItem(t.merger.Get(i+t.offset), i == t.cy-t.offset)
|
t.printItem(t.merger.Get(i+t.offset), i, i == t.cy-t.offset)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Terminal) printItem(item *Item, current bool) {
|
func (t *Terminal) printItem(item *Item, i int, current bool) {
|
||||||
_, selected := t.selected[item.Index()]
|
_, selected := t.selected[item.Index()]
|
||||||
|
label := " "
|
||||||
|
if t.jumping != jumpDisabled {
|
||||||
|
if i < len(t.jumpLabels) {
|
||||||
|
// Striped
|
||||||
|
current = i%2 == 0
|
||||||
|
label = t.jumpLabels[i : i+1]
|
||||||
|
}
|
||||||
|
} else if current {
|
||||||
|
label = ">"
|
||||||
|
}
|
||||||
|
C.CPrint(C.ColCursor, true, label)
|
||||||
if current {
|
if current {
|
||||||
C.CPrint(C.ColCursor, true, ">")
|
|
||||||
if selected {
|
if selected {
|
||||||
C.CPrint(C.ColSelected, true, ">")
|
C.CPrint(C.ColSelected, true, ">")
|
||||||
} else {
|
} else {
|
||||||
@@ -511,7 +538,6 @@ func (t *Terminal) printItem(item *Item, current bool) {
|
|||||||
}
|
}
|
||||||
t.printHighlighted(item, true, C.ColCurrent, C.ColCurrentMatch, true)
|
t.printHighlighted(item, true, C.ColCurrent, C.ColCurrentMatch, true)
|
||||||
} else {
|
} else {
|
||||||
C.CPrint(C.ColCursor, true, " ")
|
|
||||||
if selected {
|
if selected {
|
||||||
C.CPrint(C.ColSelected, true, ">")
|
C.CPrint(C.ColSelected, true, ">")
|
||||||
} else {
|
} else {
|
||||||
@@ -804,6 +830,11 @@ func (t *Terminal) Loop() {
|
|||||||
t.printInfo()
|
t.printInfo()
|
||||||
case reqList:
|
case reqList:
|
||||||
t.printList()
|
t.printList()
|
||||||
|
case reqJump:
|
||||||
|
if t.merger.Length() == 0 {
|
||||||
|
t.jumping = jumpDisabled
|
||||||
|
}
|
||||||
|
t.printList()
|
||||||
case reqHeader:
|
case reqHeader:
|
||||||
t.printHeader()
|
t.printHeader()
|
||||||
case reqRefresh:
|
case reqRefresh:
|
||||||
@@ -819,6 +850,10 @@ func (t *Terminal) Loop() {
|
|||||||
exit(exitOk)
|
exit(exitOk)
|
||||||
}
|
}
|
||||||
exit(exitNoMatch)
|
exit(exitNoMatch)
|
||||||
|
case reqPrintQuery:
|
||||||
|
C.Close()
|
||||||
|
fmt.Println(string(t.input))
|
||||||
|
exit(exitOk)
|
||||||
case reqQuit:
|
case reqQuit:
|
||||||
C.Close()
|
C.Close()
|
||||||
exit(exitInterrupt)
|
exit(exitInterrupt)
|
||||||
@@ -906,6 +941,8 @@ func (t *Terminal) Loop() {
|
|||||||
if t.cx > 0 {
|
if t.cx > 0 {
|
||||||
t.cx--
|
t.cx--
|
||||||
}
|
}
|
||||||
|
case actPrintQuery:
|
||||||
|
req(reqPrintQuery)
|
||||||
case actAbort:
|
case actAbort:
|
||||||
req(reqQuit)
|
req(reqQuit)
|
||||||
case actDeleteChar:
|
case actDeleteChar:
|
||||||
@@ -1017,6 +1054,12 @@ func (t *Terminal) Loop() {
|
|||||||
case actPageDown:
|
case actPageDown:
|
||||||
t.vmove(-(t.maxItems() - 1))
|
t.vmove(-(t.maxItems() - 1))
|
||||||
req(reqList)
|
req(reqList)
|
||||||
|
case actJump:
|
||||||
|
t.jumping = jumpEnabled
|
||||||
|
req(reqJump)
|
||||||
|
case actJumpAccept:
|
||||||
|
t.jumping = jumpAcceptEnabled
|
||||||
|
req(reqJump)
|
||||||
case actBackwardWord:
|
case actBackwardWord:
|
||||||
t.cx = findLastMatch("[^[:alnum:]][[:alnum:]]", string(t.input[:t.cx])) + 1
|
t.cx = findLastMatch("[^[:alnum:]][[:alnum:]]", string(t.input[:t.cx])) + 1
|
||||||
case actForwardWord:
|
case actForwardWord:
|
||||||
@@ -1096,9 +1139,11 @@ func (t *Terminal) Loop() {
|
|||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
action := t.keymap[event.Type]
|
changed := false
|
||||||
mapkey := event.Type
|
mapkey := event.Type
|
||||||
if event.Type == C.Rune {
|
if t.jumping == jumpDisabled {
|
||||||
|
action := t.keymap[mapkey]
|
||||||
|
if mapkey == C.Rune {
|
||||||
mapkey = int(event.Char) + int(C.AltZ)
|
mapkey = int(event.Char) + int(C.AltZ)
|
||||||
if act, prs := t.keymap[mapkey]; prs {
|
if act, prs := t.keymap[mapkey]; prs {
|
||||||
action = act
|
action = act
|
||||||
@@ -1107,7 +1152,19 @@ func (t *Terminal) Loop() {
|
|||||||
if !doAction(action, mapkey) {
|
if !doAction(action, mapkey) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
changed := string(previousInput) != string(t.input)
|
changed = string(previousInput) != string(t.input)
|
||||||
|
} else {
|
||||||
|
if mapkey == C.Rune {
|
||||||
|
if idx := strings.IndexRune(t.jumpLabels, event.Char); idx >= 0 && idx < t.maxItems() && idx < t.merger.Length() {
|
||||||
|
t.cy = idx + t.offset
|
||||||
|
if t.jumping == jumpAcceptEnabled {
|
||||||
|
req(reqClose)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t.jumping = jumpDisabled
|
||||||
|
req(reqList)
|
||||||
|
}
|
||||||
t.mutex.Unlock() // Must be unlocked before touching reqBox
|
t.mutex.Unlock() // Must be unlocked before touching reqBox
|
||||||
|
|
||||||
if changed {
|
if changed {
|
||||||
|
|||||||
@@ -430,6 +430,10 @@ class TestGoFZF < TestBase
|
|||||||
test.call 'f3', 'f3'
|
test.call 'f3', 'f3'
|
||||||
test.call 'f2,f4', 'f2', 'f2'
|
test.call 'f2,f4', 'f2', 'f2'
|
||||||
test.call 'f2,f4', 'f4', 'f4'
|
test.call 'f2,f4', 'f4', 'f4'
|
||||||
|
test.call 'alt-/', [:Escape, :/]
|
||||||
|
%w[f5 f6 f7 f8 f9 f10].each do |key|
|
||||||
|
test.call 'f5,f6,f7,f8,f9,f10', key, key
|
||||||
|
end
|
||||||
test.call '@', '@'
|
test.call '@', '@'
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -771,6 +775,13 @@ class TestGoFZF < TestBase
|
|||||||
assert_equal %w[4 5 6 9], readonce.split($/)
|
assert_equal %w[4 5 6 9], readonce.split($/)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_bind_print_query
|
||||||
|
tmux.send_keys "seq 1 1000 | #{fzf '-m --bind=ctrl-j:print-query'}", :Enter
|
||||||
|
tmux.until { |lines| lines[-2].end_with? '/1000' }
|
||||||
|
tmux.send_keys 'print-my-query', 'C-j'
|
||||||
|
assert_equal %w[print-my-query], readonce.split($/)
|
||||||
|
end
|
||||||
|
|
||||||
def test_long_line
|
def test_long_line
|
||||||
data = '.' * 256 * 1024
|
data = '.' * 256 * 1024
|
||||||
File.open(tempname, 'w') do |f|
|
File.open(tempname, 'w') do |f|
|
||||||
@@ -859,16 +870,27 @@ class TestGoFZF < TestBase
|
|||||||
def test_execute
|
def test_execute
|
||||||
output = '/tmp/fzf-test-execute'
|
output = '/tmp/fzf-test-execute'
|
||||||
opts = %[--bind \\"alt-a:execute(echo '[{}]' >> #{output}),alt-b:execute[echo '({}), ({})' >> #{output}],C:execute:echo '({}), [{}], @{}@' >> #{output}\\"]
|
opts = %[--bind \\"alt-a:execute(echo '[{}]' >> #{output}),alt-b:execute[echo '({}), ({})' >> #{output}],C:execute:echo '({}), [{}], @{}@' >> #{output}\\"]
|
||||||
tmux.send_keys "seq 100 | #{fzf opts}", :Enter
|
wait = lambda { |exp| tmux.until { |lines| lines[-2].include? exp } }
|
||||||
tmux.until { |lines| lines[-2].include? '100/100' }
|
tmux.send_keys "seq 100 | #{fzf opts}; sync", :Enter
|
||||||
tmux.send_keys :Escape, :a, :Escape, :a
|
wait['100/100']
|
||||||
|
tmux.send_keys :Escape, :a
|
||||||
|
wait['/100']
|
||||||
|
tmux.send_keys :Escape, :a
|
||||||
|
wait['/100']
|
||||||
tmux.send_keys :Up
|
tmux.send_keys :Up
|
||||||
tmux.send_keys :Escape, :b, :Escape, :b
|
tmux.send_keys :Escape, :b
|
||||||
|
wait['/100']
|
||||||
|
tmux.send_keys :Escape, :b
|
||||||
|
wait['/100']
|
||||||
tmux.send_keys :Up
|
tmux.send_keys :Up
|
||||||
tmux.send_keys :C
|
tmux.send_keys :C
|
||||||
|
wait['100/100']
|
||||||
tmux.send_keys 'foobar'
|
tmux.send_keys 'foobar'
|
||||||
tmux.until { |lines| lines[-2].include? '0/100' }
|
wait['0/100']
|
||||||
tmux.send_keys :Escape, :a, :Escape, :b, :Escape, :c
|
tmux.send_keys :Escape, :a
|
||||||
|
wait['/100']
|
||||||
|
tmux.send_keys :Escape, :b
|
||||||
|
wait['/100']
|
||||||
tmux.send_keys :Enter
|
tmux.send_keys :Enter
|
||||||
readonce
|
readonce
|
||||||
assert_equal ['["1"]', '["1"]', '("2"), ("2")', '("2"), ("2")', '("3"), ["3"], @"3"@'],
|
assert_equal ['["1"]', '["1"]', '("2"), ("2")', '("2"), ("2")', '("3"), ["3"], @"3"@'],
|
||||||
@@ -1026,7 +1048,7 @@ class TestGoFZF < TestBase
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_canel
|
def test_cancel
|
||||||
tmux.send_keys "seq 10 | #{fzf "--bind 2:cancel"}", :Enter
|
tmux.send_keys "seq 10 | #{fzf "--bind 2:cancel"}", :Enter
|
||||||
tmux.until { |lines| lines[-2].include?('10/10') }
|
tmux.until { |lines| lines[-2].include?('10/10') }
|
||||||
tmux.send_keys '123'
|
tmux.send_keys '123'
|
||||||
@@ -1163,6 +1185,43 @@ class TestGoFZF < TestBase
|
|||||||
tmux.send_keys :Enter
|
tmux.send_keys :Enter
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_jump
|
||||||
|
tmux.send_keys "seq 1000 | #{fzf "--multi --jump-labels 12345 --bind 'ctrl-j:jump'"}", :Enter
|
||||||
|
tmux.until { |lines| lines[-2] == ' 1000/1000' }
|
||||||
|
tmux.send_keys 'C-j'
|
||||||
|
tmux.until { |lines| lines[-7] == '5 5' }
|
||||||
|
tmux.until { |lines| lines[-8] == ' 6' }
|
||||||
|
tmux.send_keys '5'
|
||||||
|
tmux.until { |lines| lines[-7] == '> 5' }
|
||||||
|
tmux.send_keys :Tab
|
||||||
|
tmux.until { |lines| lines[-7] == ' >5' }
|
||||||
|
tmux.send_keys 'C-j'
|
||||||
|
tmux.until { |lines| lines[-7] == '5>5' }
|
||||||
|
tmux.send_keys '2'
|
||||||
|
tmux.until { |lines| lines[-4] == '> 2' }
|
||||||
|
tmux.send_keys :Tab
|
||||||
|
tmux.until { |lines| lines[-4] == ' >2' }
|
||||||
|
tmux.send_keys 'C-j'
|
||||||
|
tmux.until { |lines| lines[-7] == '5>5' }
|
||||||
|
|
||||||
|
# Press any key other than jump labels to cancel jump
|
||||||
|
tmux.send_keys '6'
|
||||||
|
tmux.until { |lines| lines[-3] == '> 1' }
|
||||||
|
tmux.send_keys :Tab
|
||||||
|
tmux.until { |lines| lines[-3] == '>>1' }
|
||||||
|
tmux.send_keys :Enter
|
||||||
|
assert_equal %w[5 2 1], readonce.split($/)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_jump_accept
|
||||||
|
tmux.send_keys "seq 1000 | #{fzf "--multi --jump-labels 12345 --bind 'ctrl-j:jump-accept'"}", :Enter
|
||||||
|
tmux.until { |lines| lines[-2] == ' 1000/1000' }
|
||||||
|
tmux.send_keys 'C-j'
|
||||||
|
tmux.until { |lines| lines[-7] == '5 5' }
|
||||||
|
tmux.send_keys '3'
|
||||||
|
assert_equal '3', readonce.chomp
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
def writelines path, lines
|
def writelines path, lines
|
||||||
File.unlink path while File.exists? path
|
File.unlink path while File.exists? path
|
||||||
@@ -1229,7 +1288,7 @@ module TestShell
|
|||||||
tmux.send_keys :BTab, :BTab, pane: 1
|
tmux.send_keys :BTab, :BTab, pane: 1
|
||||||
tmux.until(1) { |lines| lines[-2].include? '(2)' }
|
tmux.until(1) { |lines| lines[-2].include? '(2)' }
|
||||||
tmux.send_keys :Enter, pane: 1
|
tmux.send_keys :Enter, pane: 1
|
||||||
tmux.until { |lines| lines[-1].include? 'cat' }
|
tmux.until { |lines| lines[-1].include?('cat') || lines[-2].include?('cat') }
|
||||||
tmux.send_keys :Enter
|
tmux.send_keys :Enter
|
||||||
tmux.until { |lines| lines[-1].include? 'test1test2' }
|
tmux.until { |lines| lines[-1].include? 'test1test2' }
|
||||||
end
|
end
|
||||||
@@ -1444,7 +1503,7 @@ module CompletionTest
|
|||||||
tmux.send_keys :BTab, :BTab, pane: 1
|
tmux.send_keys :BTab, :BTab, pane: 1
|
||||||
tmux.until(1) { |lines| lines[-2].include? '(2)' }
|
tmux.until(1) { |lines| lines[-2].include? '(2)' }
|
||||||
tmux.send_keys :Enter, pane: 1
|
tmux.send_keys :Enter, pane: 1
|
||||||
tmux.until { |lines| lines[-1].include? 'cat' }
|
tmux.until { |lines| lines[-1].include?('cat') || lines[-2].include?('cat') }
|
||||||
tmux.send_keys :Enter
|
tmux.send_keys :Enter
|
||||||
tmux.until { |lines| lines[-1].include? 'test3test4' }
|
tmux.until { |lines| lines[-1].include? 'test3test4' }
|
||||||
end
|
end
|
||||||
|
|||||||
Reference in New Issue
Block a user