mirror of
https://github.com/junegunn/fzf.git
synced 2025-11-15 06:43:47 -05:00
Compare commits
110 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
334a4fa159 | ||
|
|
21f94ee800 | ||
|
|
540bfd7a72 | ||
|
|
8fbed2b13a | ||
|
|
aa17510e0a | ||
|
|
bf65e8cd12 | ||
|
|
0f5c6e8f04 | ||
|
|
b1b916ce15 | ||
|
|
a6a732e1fc | ||
|
|
a5c2f28539 | ||
|
|
18261fe31c | ||
|
|
079046863c | ||
|
|
07b965bba1 | ||
|
|
c39113ee41 | ||
|
|
14f90502a4 | ||
|
|
b0673c3563 | ||
|
|
373c6d8d55 | ||
|
|
b8fc828955 | ||
|
|
b43b040512 | ||
|
|
50b7608f9d | ||
|
|
7085e5b629 | ||
|
|
7d5985baf9 | ||
|
|
7c40a424c0 | ||
|
|
baf882ace7 | ||
|
|
ba82f0bef9 | ||
|
|
d9c6a0305b | ||
|
|
d9b1211191 | ||
|
|
99f1e02766 | ||
|
|
242c0db26b | ||
|
|
dd49e41c42 | ||
|
|
6db15e8693 | ||
|
|
4c9cab3f8a | ||
|
|
b2c0413a98 | ||
|
|
e34c7c00b1 | ||
|
|
7c447bbdc7 | ||
|
|
7bf1f2cc84 | ||
|
|
afa2c4e0af | ||
|
|
2ff7db1b36 | ||
|
|
9f0626da64 | ||
|
|
d8cb5c1cf5 | ||
|
|
dca56da0ef | ||
|
|
ec75d16ea8 | ||
|
|
5cae8ea733 | ||
|
|
1ccd8f6a64 | ||
|
|
9c293bb82b | ||
|
|
9897ee9591 | ||
|
|
5215415315 | ||
|
|
54891d11e0 | ||
|
|
567c8303bf | ||
|
|
2a60edcd52 | ||
|
|
d61ac32d7b | ||
|
|
b57e6cff7e | ||
|
|
5b99f19dac | ||
|
|
6c03571887 | ||
|
|
4fb410a93c | ||
|
|
5e1db9fdd3 | ||
|
|
9d7480ae3c | ||
|
|
f39cf6d855 | ||
|
|
001d116884 | ||
|
|
02c5e62efe | ||
|
|
446df07b62 | ||
|
|
8583b150c9 | ||
|
|
a859aa72ee | ||
|
|
0896036266 | ||
|
|
311b78ae82 | ||
|
|
f5cf4fc8fb | ||
|
|
7ceb58b2aa | ||
|
|
293dd76af1 | ||
|
|
3918c45ced | ||
|
|
4ec403347c | ||
|
|
e01266ffcb | ||
|
|
f246fb2fc2 | ||
|
|
f7b26b34cb | ||
|
|
a1bcdc225e | ||
|
|
7771241cc0 | ||
|
|
6e3af646b2 | ||
|
|
82bf8c138d | ||
|
|
e21b001116 | ||
|
|
577024f1e9 | ||
|
|
d4ad4a25db | ||
|
|
30577b0c17 | ||
|
|
212de25409 | ||
|
|
5da8bbf45a | ||
|
|
aa0e10ead7 | ||
|
|
a9906c7c29 | ||
|
|
9fefe08b3f | ||
|
|
684bfff713 | ||
|
|
3db6b88d82 | ||
|
|
8ae96774df | ||
|
|
f68017d21e | ||
|
|
2b725a4db5 | ||
|
|
af1a5f130b | ||
|
|
86e3994e87 | ||
|
|
1e6ac5590e | ||
|
|
5e42b1c9f8 | ||
|
|
9d842630c9 | ||
|
|
77cb906dfe | ||
|
|
a59e846f74 | ||
|
|
6e6340a0c9 | ||
|
|
357e82e51b | ||
|
|
394d8cfd18 | ||
|
|
ef80bd401f | ||
|
|
f51d61d57a | ||
|
|
1dd256a68a | ||
|
|
85644aa3fb | ||
|
|
effbc258bb | ||
|
|
e615600ff1 | ||
|
|
60465c4664 | ||
|
|
c03c058bd5 | ||
|
|
7238c8944d |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -7,3 +7,5 @@ Gemfile.lock
|
|||||||
doc/tags
|
doc/tags
|
||||||
vendor
|
vendor
|
||||||
gopath
|
gopath
|
||||||
|
*.zwc
|
||||||
|
fzf
|
||||||
|
|||||||
41
.travis.yml
41
.travis.yml
@@ -1,27 +1,22 @@
|
|||||||
language: go
|
language: go
|
||||||
dist: xenial
|
env: GO111MODULE=on
|
||||||
|
os:
|
||||||
|
- linux
|
||||||
|
- osx
|
||||||
|
dist: bionic # For fish >= 2.3.0 string builtin
|
||||||
addons:
|
addons:
|
||||||
apt:
|
apt:
|
||||||
sources:
|
|
||||||
- sourceline: "ppa:pi-rho/dev"
|
|
||||||
- sourceline: "ppa:fish-shell/release-2"
|
|
||||||
packages:
|
packages:
|
||||||
- tmux
|
- fish
|
||||||
- zsh
|
- zsh
|
||||||
- fish
|
homebrew:
|
||||||
|
packages:
|
||||||
env:
|
- fish
|
||||||
- GO111MODULE=on
|
- tmux
|
||||||
|
update: true
|
||||||
jobs:
|
script:
|
||||||
include:
|
- make test
|
||||||
- stage: unittest
|
# LC_ALL=C to avoid escape codes in
|
||||||
go: "1.13.x"
|
# printf %q $'\355\205\214\354\212\244\355\212\270' on macOS. Bash on
|
||||||
script: make && make test
|
# macOS is built without HANDLE_MULTIBYTE?
|
||||||
|
- make install && ./install --all && LC_ALL=C tmux new-session -d && ruby test/test_go.rb --verbose
|
||||||
- stage: cli
|
|
||||||
go: "1.13.x"
|
|
||||||
rvm: "2.5"
|
|
||||||
script: |
|
|
||||||
make install && ./install --all && tmux new "ruby test/test_go.rb > out && touch ok" && cat out && [ -e ok ]
|
|
||||||
|
|
||||||
|
|||||||
107
CHANGELOG.md
107
CHANGELOG.md
@@ -1,9 +1,114 @@
|
|||||||
CHANGELOG
|
CHANGELOG
|
||||||
=========
|
=========
|
||||||
|
|
||||||
|
0.21.1
|
||||||
|
------
|
||||||
|
- Shell extension
|
||||||
|
- CTRL-R will remove duplicate commands
|
||||||
|
- fzf-tmux
|
||||||
|
- Supports tmux popup window (require tmux 3.2 or above)
|
||||||
|
- ```sh
|
||||||
|
# 50% width and height
|
||||||
|
fzf-tmux -p
|
||||||
|
|
||||||
|
# 80% width and height
|
||||||
|
fzf-tmux -p 80%
|
||||||
|
|
||||||
|
# 80% width and 40% height
|
||||||
|
fzf-tmux -p 80%,40%
|
||||||
|
fzf-tmux -w 80% -h 40%
|
||||||
|
|
||||||
|
# Window position
|
||||||
|
fzf-tmux -w 80% -h 40% -x 0 -y 0
|
||||||
|
fzf-tmux -w 80% -h 40% -y 1000
|
||||||
|
|
||||||
|
# Write ordinary fzf options after --
|
||||||
|
fzf-tmux -p -- --reverse --info=inline --margin 2,4 --border
|
||||||
|
```
|
||||||
|
- On macOS, you can build the latest tmux from the source with
|
||||||
|
`brew install tmux --HEAD`
|
||||||
|
- Bug fixes
|
||||||
|
- Fixed Windows file traversal not to include directories
|
||||||
|
- Fixed ANSI colors with `--keep-right`
|
||||||
|
- Fixed _fzf_complete for zsh
|
||||||
|
- Built with Go 1.14.1
|
||||||
|
|
||||||
|
0.21.0
|
||||||
|
------
|
||||||
|
- `--height` option is now available on Windows as well (@kelleyma49)
|
||||||
|
- Added `--pointer` and `--marker` options
|
||||||
|
- Added `--keep-right` option that keeps the right end of the line visible
|
||||||
|
when it's too long
|
||||||
|
- Style changes
|
||||||
|
- `--border` will now print border with rounded corners around the
|
||||||
|
finder instead of printing horizontal lines above and below it.
|
||||||
|
The previous style is available via `--border=horizontal`
|
||||||
|
- Unicode spinner
|
||||||
|
- More keys and actions for `--bind`
|
||||||
|
- Added PowerShell script for downloading Windows binary
|
||||||
|
- Vim plugin: Built-in floating windows support
|
||||||
|
```vim
|
||||||
|
let g:fzf_layout = { 'window': { 'width': 0.9, 'height': 0.6 } }
|
||||||
|
```
|
||||||
|
- bash: Various improvements in key bindings (CTRL-T, CTRL-R, ALT-C)
|
||||||
|
- CTRL-R will start with the current command-line as the initial query
|
||||||
|
- CTRL-R properly supports multi-line commands
|
||||||
|
- Fuzzy completion API changed
|
||||||
|
```sh
|
||||||
|
# Previous: fzf arguments given as a single string argument
|
||||||
|
# - This style is still supported, but it's deprecated
|
||||||
|
_fzf_complete "--multi --reverse --prompt=\"doge> \"" "$@" < <(
|
||||||
|
echo foo
|
||||||
|
)
|
||||||
|
|
||||||
|
# New API: multiple fzf arguments before "--"
|
||||||
|
# - Easier to write multiple options
|
||||||
|
_fzf_complete --multi --reverse --prompt="doge> " -- "$@" < <(
|
||||||
|
echo foo
|
||||||
|
)
|
||||||
|
```
|
||||||
|
- Bug fixes and improvements
|
||||||
|
|
||||||
|
0.20.0
|
||||||
|
------
|
||||||
|
- Customizable preview window color (`preview-fg` and `preview-bg` for `--color`)
|
||||||
|
```sh
|
||||||
|
fzf --preview 'cat {}' \
|
||||||
|
--color 'fg:#bbccdd,fg+:#ddeeff,bg:#334455,preview-bg:#223344,border:#778899' \
|
||||||
|
--border --height 20 --layout reverse --info inline
|
||||||
|
```
|
||||||
|
- Removed the immediate flicking of the screen on `reload` action.
|
||||||
|
```sh
|
||||||
|
: | fzf --bind 'change:reload:seq {q}' --phony
|
||||||
|
```
|
||||||
|
- Added `clear-query` and `clear-selection` actions for `--bind`
|
||||||
|
- It is now possible to split a composite bind action over multiple `--bind`
|
||||||
|
expressions by prefixing the later ones with `+`.
|
||||||
|
```sh
|
||||||
|
fzf --bind 'ctrl-a:up+up'
|
||||||
|
|
||||||
|
# Can be now written as
|
||||||
|
fzf --bind 'ctrl-a:up' --bind 'ctrl-a:+up'
|
||||||
|
|
||||||
|
# This is useful when you need to write special execute/reload form (i.e. `execute:...`)
|
||||||
|
# to avoid parse errors and add more actions to the same key
|
||||||
|
fzf --multi --bind 'ctrl-l:select-all+execute:less {+f}' --bind 'ctrl-l:+deselect-all'
|
||||||
|
```
|
||||||
|
- Fixed parse error of `--bind` expression where concatenated execute/reload
|
||||||
|
action contains `+` character.
|
||||||
|
```sh
|
||||||
|
fzf --multi --bind 'ctrl-l:select-all+execute(less {+f})+deselect-all'
|
||||||
|
```
|
||||||
|
- Fixed bugs of reload action
|
||||||
|
- Not triggered when there's no match even when the command doesn't have
|
||||||
|
any placeholder expressions
|
||||||
|
- Screen not properly cleared when `--header-lines` not filled on reload
|
||||||
|
|
||||||
0.19.0
|
0.19.0
|
||||||
------
|
------
|
||||||
|
|
||||||
|
- Added `--phony` option which completely disables search functionality.
|
||||||
|
Useful when you want to use fzf only as a selector interface. See below.
|
||||||
- Added "reload" action for dynamically updating the input list without
|
- Added "reload" action for dynamically updating the input list without
|
||||||
restarting fzf. See https://github.com/junegunn/fzf/issues/1750 to learn
|
restarting fzf. See https://github.com/junegunn/fzf/issues/1750 to learn
|
||||||
more about it.
|
more about it.
|
||||||
@@ -11,7 +116,7 @@ CHANGELOG
|
|||||||
# Using fzf as the selector interface for ripgrep
|
# Using fzf as the selector interface for ripgrep
|
||||||
RG_PREFIX="rg --column --line-number --no-heading --color=always --smart-case "
|
RG_PREFIX="rg --column --line-number --no-heading --color=always --smart-case "
|
||||||
INITIAL_QUERY="foo"
|
INITIAL_QUERY="foo"
|
||||||
FZF_DEFAULT_COMMAND="$RG_PREFIX '$INITIAL_QUERY'" \
|
FZF_DEFAULT_COMMAND="$RG_PREFIX '$INITIAL_QUERY' || true" \
|
||||||
fzf --bind "change:reload:$RG_PREFIX {q} || true" \
|
fzf --bind "change:reload:$RG_PREFIX {q} || true" \
|
||||||
--ansi --phony --query "$INITIAL_QUERY"
|
--ansi --phony --query "$INITIAL_QUERY"
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
FROM archlinux/base:latest
|
FROM archlinux/base:latest
|
||||||
RUN pacman -Sy && pacman --noconfirm -S awk git tmux zsh fish ruby procps go make
|
RUN pacman -Sy && pacman --noconfirm -S awk git tmux zsh fish ruby procps go make gcc
|
||||||
RUN gem install --no-document minitest
|
RUN gem install --no-document minitest
|
||||||
RUN echo '. /usr/share/bash-completion/completions/git' >> ~/.bashrc
|
RUN echo '. /usr/share/bash-completion/completions/git' >> ~/.bashrc
|
||||||
RUN echo '. ~/.bashrc' >> ~/.bash_profile
|
RUN echo '. ~/.bashrc' >> ~/.bash_profile
|
||||||
|
|||||||
2
LICENSE
2
LICENSE
@@ -1,6 +1,6 @@
|
|||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2016 Junegunn Choi
|
Copyright (c) 2013-2020 Junegunn Choi
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
|||||||
359
README-VIM.md
359
README-VIM.md
@@ -1,16 +1,77 @@
|
|||||||
FZF Vim integration
|
FZF Vim integration
|
||||||
===================
|
===================
|
||||||
|
|
||||||
This repository only enables basic integration with Vim. If you're looking for
|
Installation
|
||||||
more, check out [fzf.vim](https://github.com/junegunn/fzf.vim) project.
|
------------
|
||||||
|
|
||||||
(Note: To use fzf in GVim, an external terminal emulator is required.)
|
Once you have fzf installed, you can enable it inside Vim simply by adding the
|
||||||
|
directory to `&runtimepath` in your Vim configuration file. The path may
|
||||||
|
differ depending on the package manager.
|
||||||
|
|
||||||
|
```vim
|
||||||
|
" If installed using Homebrew
|
||||||
|
set rtp+=/usr/local/opt/fzf
|
||||||
|
|
||||||
|
" If installed using git
|
||||||
|
set rtp+=~/.fzf
|
||||||
|
```
|
||||||
|
|
||||||
|
If you use [vim-plug](https://github.com/junegunn/vim-plug), the same can be
|
||||||
|
written as:
|
||||||
|
|
||||||
|
```vim
|
||||||
|
" If installed using Homebrew
|
||||||
|
Plug '/usr/local/opt/fzf'
|
||||||
|
|
||||||
|
" If installed using git
|
||||||
|
Plug '~/.fzf'
|
||||||
|
```
|
||||||
|
|
||||||
|
But if you want the latest Vim plugin file from GitHub rather than the one
|
||||||
|
included in the package, write:
|
||||||
|
|
||||||
|
```vim
|
||||||
|
Plug 'junegunn/fzf'
|
||||||
|
```
|
||||||
|
|
||||||
|
The Vim plugin will pick up fzf binary available on the system. If fzf is not
|
||||||
|
found on `$PATH`, it will ask you if it should download the latest binary for
|
||||||
|
you.
|
||||||
|
|
||||||
|
To make sure that you have the latest version of the binary, set up
|
||||||
|
post-update hook like so:
|
||||||
|
|
||||||
|
```vim
|
||||||
|
Plug 'junegunn/fzf', { 'do': { -> fzf#install() } }
|
||||||
|
```
|
||||||
|
|
||||||
|
Summary
|
||||||
|
-------
|
||||||
|
|
||||||
|
The Vim plugin of fzf provides two core functions, and `:FZF` command which is
|
||||||
|
the basic file selector command built on top of them.
|
||||||
|
|
||||||
|
1. **`fzf#run([spec dict])`**
|
||||||
|
- Starts fzf inside Vim with the given spec
|
||||||
|
- `:call fzf#run({'source': 'ls'})`
|
||||||
|
2. **`fzf#wrap([spec dict]) -> (dict)`**
|
||||||
|
- Takes a spec for `fzf#run` and returns an extended version of it with
|
||||||
|
additional options for addressing global preferences (`g:fzf_xxx`)
|
||||||
|
- `:echo fzf#wrap({'source': 'ls'})`
|
||||||
|
- We usually *wrap* a spec with `fzf#wrap` before passing it to `fzf#run`
|
||||||
|
- `:call fzf#run(fzf#wrap({'source': 'ls'}))`
|
||||||
|
3. **`:FZF [fzf_options string] [path string]`**
|
||||||
|
- Basic fuzzy file selector
|
||||||
|
- A reference implementation for those who don't want to write VimScript
|
||||||
|
to implement custom commands
|
||||||
|
- If you're looking for more such commands, check out [fzf.vim](https://github.com/junegunn/fzf.vim) project.
|
||||||
|
|
||||||
|
The most important of all is `fzf#run`, but it would be easier to understand
|
||||||
|
the whole if we start off with `:FZF` command.
|
||||||
|
|
||||||
`:FZF[!]`
|
`:FZF[!]`
|
||||||
---------
|
---------
|
||||||
|
|
||||||
If you have set up fzf for Vim, `:FZF` command will be added.
|
|
||||||
|
|
||||||
```vim
|
```vim
|
||||||
" Look for files under current directory
|
" Look for files under current directory
|
||||||
:FZF
|
:FZF
|
||||||
@@ -18,8 +79,8 @@ If you have set up fzf for Vim, `:FZF` command will be added.
|
|||||||
" Look for files under your home directory
|
" Look for files under your home directory
|
||||||
:FZF ~
|
:FZF ~
|
||||||
|
|
||||||
" With options
|
" With fzf command-line options
|
||||||
:FZF --no-sort --reverse --inline-info /tmp
|
:FZF --reverse --info=inline /tmp
|
||||||
|
|
||||||
" Bang version starts fzf in fullscreen mode
|
" Bang version starts fzf in fullscreen mode
|
||||||
:FZF!
|
:FZF!
|
||||||
@@ -75,6 +136,7 @@ let g:fzf_layout = { 'window': '-tabnew' }
|
|||||||
let g:fzf_layout = { 'window': '10new' }
|
let g:fzf_layout = { 'window': '10new' }
|
||||||
|
|
||||||
" Customize fzf colors to match your color scheme
|
" Customize fzf colors to match your color scheme
|
||||||
|
" - fzf#wrap translates this to a set of `--color` options
|
||||||
let g:fzf_colors =
|
let g:fzf_colors =
|
||||||
\ { 'fg': ['fg', 'Normal'],
|
\ { 'fg': ['fg', 'Normal'],
|
||||||
\ 'bg': ['bg', 'Normal'],
|
\ 'bg': ['bg', 'Normal'],
|
||||||
@@ -90,69 +152,109 @@ let g:fzf_colors =
|
|||||||
\ 'spinner': ['fg', 'Label'],
|
\ 'spinner': ['fg', 'Label'],
|
||||||
\ 'header': ['fg', 'Comment'] }
|
\ 'header': ['fg', 'Comment'] }
|
||||||
|
|
||||||
" Enable per-command history.
|
" Enable per-command history
|
||||||
" CTRL-N and CTRL-P will be automatically bound to next-history and
|
" - History files will be stored in the specified directory
|
||||||
" previous-history instead of down and up. If you don't like the change,
|
" - When set, CTRL-N and CTRL-P will be bound to 'next-history' and
|
||||||
" explicitly bind the keys to down and up in your $FZF_DEFAULT_OPTS.
|
" 'previous-history' instead of 'down' and 'up'.
|
||||||
let g:fzf_history_dir = '~/.local/share/fzf-history'
|
let g:fzf_history_dir = '~/.local/share/fzf-history'
|
||||||
```
|
```
|
||||||
|
|
||||||
|
##### Explanation of `g:fzf_colors`
|
||||||
|
|
||||||
|
`g:fzf_colors` is a dictionary mapping fzf elements to a color specification
|
||||||
|
list:
|
||||||
|
|
||||||
|
element: [ component, group1 [, group2, ...] ]
|
||||||
|
|
||||||
|
- `element` is an fzf element to apply a color to:
|
||||||
|
|
||||||
|
| Element | Description |
|
||||||
|
| --- | --- |
|
||||||
|
| `fg` / `bg` / `hl` | Item (foreground / background / highlight) |
|
||||||
|
| `fg+` / `bg+` / `hl+` | Current item (foreground / background / highlight) |
|
||||||
|
| `hl` / `hl+` | Highlighted substrings (normal / current) |
|
||||||
|
| `gutter` | Background of the gutter on the left |
|
||||||
|
| `pointer` | Pointer to the current line (`>`) |
|
||||||
|
| `marker` | Multi-select marker (`>`) |
|
||||||
|
| `border` | Border around the window (`--border` and `--preview`) |
|
||||||
|
| `header` | Header (`--header` or `--header-lines`) |
|
||||||
|
| `info` | Info line (match counters) |
|
||||||
|
| `spinner` | Streaming input indicator |
|
||||||
|
| `prompt` | Prompt before query (`> `) |
|
||||||
|
|
||||||
|
- `component` specifies the component (`fg` / `bg`) from which to extract the
|
||||||
|
color when considering each of the following highlight groups
|
||||||
|
|
||||||
|
- `group1 [, group2, ...]` is a list of highlight groups that are searched (in
|
||||||
|
order) for a matching color definition
|
||||||
|
|
||||||
|
For example, consider the following specification:
|
||||||
|
|
||||||
|
```vim
|
||||||
|
'prompt': ['fg', 'Conditional', 'Comment'],
|
||||||
|
```
|
||||||
|
|
||||||
|
This means we color the **prompt**
|
||||||
|
- using the `fg` attribute of the `Conditional` if it exists,
|
||||||
|
- otherwise use the `fg` attribute of the `Comment` highlight group if it exists,
|
||||||
|
- otherwise fall back to the default color settings for the **prompt**.
|
||||||
|
|
||||||
|
You can examine the color option generated according the setting by printing
|
||||||
|
the result of `fzf#wrap()` function like so:
|
||||||
|
|
||||||
|
```vim
|
||||||
|
:echo fzf#wrap()
|
||||||
|
```
|
||||||
|
|
||||||
`fzf#run`
|
`fzf#run`
|
||||||
---------
|
---------
|
||||||
|
|
||||||
For more advanced uses, you can use `fzf#run([options])` function.
|
|
||||||
|
|
||||||
`fzf#run()` function is the core of Vim integration. It takes a single
|
`fzf#run()` function is the core of Vim integration. It takes a single
|
||||||
dictionary argument. At the very least, specify `sink` option to tell what it
|
dictionary argument, *a spec*, and starts fzf process accordingly. At the very
|
||||||
should do with the selected entry.
|
least, specify `sink` option to tell what it should do with the selected
|
||||||
|
entry.
|
||||||
|
|
||||||
```vim
|
```vim
|
||||||
call fzf#run({'sink': 'e'})
|
call fzf#run({'sink': 'e'})
|
||||||
```
|
```
|
||||||
|
|
||||||
Without `source`, fzf will use find command (or `$FZF_DEFAULT_COMMAND` if
|
We haven't specified the `source`, so this is equivalent to starting fzf on
|
||||||
defined) to list the files under the current directory. When you select one,
|
command line without standard input pipe; fzf will use find command (or
|
||||||
it will open it with `:e` command. If you want to open it in a new tab, you
|
`$FZF_DEFAULT_COMMAND` if defined) to list the files under the current
|
||||||
can pass `:tabedit` command instead as the sink.
|
directory. When you select one, it will open it with the sink, `:e` command.
|
||||||
|
If you want to open it in a new tab, you can pass `:tabedit` command instead
|
||||||
|
as the sink.
|
||||||
|
|
||||||
```vim
|
```vim
|
||||||
call fzf#run({'sink': 'tabedit'})
|
call fzf#run({'sink': 'tabedit'})
|
||||||
```
|
```
|
||||||
|
|
||||||
fzf allows you to select multiple entries with `--multi` (or `-m`) option, and
|
|
||||||
you can change its bottom-up layout with `--reverse` option. Such options can
|
|
||||||
be specified as `options`.
|
|
||||||
|
|
||||||
```vim
|
|
||||||
call fzf#run({'sink': 'tabedit', 'options': '--multi --reverse'})
|
|
||||||
```
|
|
||||||
|
|
||||||
Instead of using the default find command, you can use any shell command as
|
Instead of using the default find command, you can use any shell command as
|
||||||
the source. This will list the files managed by git.
|
the source. The following example will list the files managed by git. It's
|
||||||
|
equivalent to running `git ls-files | fzf` on shell.
|
||||||
|
|
||||||
```vim
|
```vim
|
||||||
call fzf#run({'source': 'git ls-files', 'sink': 'e'})
|
call fzf#run({'source': 'git ls-files', 'sink': 'e'})
|
||||||
```
|
```
|
||||||
|
|
||||||
Pass a layout option if you don't want fzf window to take up the entire screen.
|
fzf options can be specified as `options` entry in spec dictionary.
|
||||||
|
|
||||||
|
```vim
|
||||||
|
call fzf#run({'sink': 'tabedit', 'options': '--multi --reverse'})
|
||||||
|
```
|
||||||
|
|
||||||
|
You can also pass a layout option if you don't want fzf window to take up the
|
||||||
|
entire screen.
|
||||||
|
|
||||||
```vim
|
```vim
|
||||||
" up / down / left / right / window are allowed
|
" up / down / left / right / window are allowed
|
||||||
call fzf#run({'source': 'git ls-files', 'sink': 'e', 'right': '40%'})
|
call fzf#run({'source': 'git ls-files', 'sink': 'e', 'left': '40%'})
|
||||||
call fzf#run({'source': 'git ls-files', 'sink': 'e', 'window': '30vnew'})
|
call fzf#run({'source': 'git ls-files', 'sink': 'e', 'window': '30vnew'})
|
||||||
```
|
```
|
||||||
|
|
||||||
`source` doesn't have to be an external shell command, you can pass a Vim
|
`source` doesn't have to be an external shell command, you can pass a Vim
|
||||||
array as the source. In the following example, we use the names of the open
|
array as the source. In the next example, we pass the names of color
|
||||||
buffers as the source.
|
schemes as the source to implement a color scheme selector.
|
||||||
|
|
||||||
```vim
|
|
||||||
call fzf#run({'source': map(filter(range(1, bufnr('$')), 'buflisted(v:val)'),
|
|
||||||
\ 'bufname(v:val)'),
|
|
||||||
\ 'sink': 'e', 'down': '30%'})
|
|
||||||
```
|
|
||||||
|
|
||||||
Or the names of color schemes.
|
|
||||||
|
|
||||||
```vim
|
```vim
|
||||||
call fzf#run({'source': map(split(globpath(&rtp, 'colors/*.vim')),
|
call fzf#run({'source': map(split(globpath(&rtp, 'colors/*.vim')),
|
||||||
@@ -160,85 +262,156 @@ call fzf#run({'source': map(split(globpath(&rtp, 'colors/*.vim')),
|
|||||||
\ 'sink': 'colo', 'left': '25%'})
|
\ 'sink': 'colo', 'left': '25%'})
|
||||||
```
|
```
|
||||||
|
|
||||||
The following table shows the available options.
|
The following table summarizes the available options.
|
||||||
|
|
||||||
| Option name | Type | Description |
|
| Option name | Type | Description |
|
||||||
| -------------------------- | ------------- | ---------------------------------------------------------------- |
|
| -------------------------- | ------------- | ---------------------------------------------------------------- |
|
||||||
| `source` | string | External command to generate input to fzf (e.g. `find .`) |
|
| `source` | string | External command to generate input to fzf (e.g. `find .`) |
|
||||||
| `source` | list | Vim list as input to fzf |
|
| `source` | list | Vim list as input to fzf |
|
||||||
| `sink` | string | Vim command to handle the selected item (e.g. `e`, `tabe`) |
|
| `sink` | string | Vim command to handle the selected item (e.g. `e`, `tabe`) |
|
||||||
| `sink` | funcref | Reference to function to process each selected item |
|
| `sink` | funcref | Reference to function to process each selected item |
|
||||||
| `sink*` | funcref | Similar to `sink`, but takes the list of output lines at once |
|
| `sink*` | funcref | Similar to `sink`, but takes the list of output lines at once |
|
||||||
| `options` | string/list | Options to fzf |
|
| `options` | string/list | Options to fzf |
|
||||||
| `dir` | string | Working directory |
|
| `dir` | string | Working directory |
|
||||||
| `up`/`down`/`left`/`right` | number/string | Use tmux pane with the given size (e.g. `20`, `50%`) |
|
| `up`/`down`/`left`/`right` | number/string | (Layout) Window position and size (e.g. `20`, `50%`) |
|
||||||
| `window` (Vim 8 / Neovim) | string | Command to open fzf window (e.g. `vertical aboveleft 30new`) |
|
| `window` (Vim 8 / Neovim) | string | (Layout) Command to open fzf window (e.g. `vertical aboveleft 30new`) |
|
||||||
| `launcher` | string | External terminal emulator to start fzf with (GVim only) |
|
| `window` (Vim 8 / Neovim) | dict | (Layout) Popup window settings (e.g. `{'width': 0.9, 'height': 0.6}`) |
|
||||||
| `launcher` | funcref | Function for generating `launcher` string (GVim only) |
|
|
||||||
|
|
||||||
`options` entry can be either a string or a list. For simple cases, string
|
`options` entry can be either a string or a list. For simple cases, string
|
||||||
should suffice, but prefer to use list type if you're concerned about escaping
|
should suffice, but prefer to use list type to avoid escaping issues.
|
||||||
issues on different platforms.
|
|
||||||
|
|
||||||
```vim
|
```vim
|
||||||
call fzf#run({'options': '--reverse --prompt "C:\\Program Files\\"'})
|
call fzf#run({'options': '--reverse --prompt "C:\\Program Files\\"'})
|
||||||
call fzf#run({'options': ['--reverse', '--prompt', 'C:\Program Files\']})
|
call fzf#run({'options': ['--reverse', '--prompt', 'C:\Program Files\']})
|
||||||
```
|
```
|
||||||
|
|
||||||
|
When `window` entry is a dictionary, fzf will start in a popup window. The
|
||||||
|
following options are allowed:
|
||||||
|
|
||||||
|
- Required:
|
||||||
|
- `width` [float range [0 ~ 1]]
|
||||||
|
- `height` [float range [0 ~ 1]]
|
||||||
|
- Optional:
|
||||||
|
- `yoffset` [float default 0.5 range [0 ~ 1]]
|
||||||
|
- `xoffset` [float default 0.5 range [0 ~ 1]]
|
||||||
|
- `highlight` [string default `'Comment'`]: Highlight group for border
|
||||||
|
- `border` [string default `rounded`]: Border style
|
||||||
|
- `rounded` / `sharp` / `horizontal` / `vertical` / `top` / `bottom` / `left` / `right`
|
||||||
|
|
||||||
`fzf#wrap`
|
`fzf#wrap`
|
||||||
----------
|
----------
|
||||||
|
|
||||||
`:FZF` command provided by default knows how to handle `CTRL-T`, `CTRL-X`, and
|
We have seen that several aspects of `:FZF` command can be configured with
|
||||||
`CTRL-V` and opens the selected file in a new tab, in a horizontal split, or
|
a set of global option variables; different ways to open files
|
||||||
in a vertical split respectively. And these key bindings can be configured via
|
(`g:fzf_action`), window position and size (`g:fzf_layout`), color palette
|
||||||
`g:fzf_action`. This is implemented using `--expect` option of fzf and the
|
(`g:fzf_colors`), etc.
|
||||||
smart sink function. It also understands `g:fzf_colors`, `g:fzf_layout` and
|
|
||||||
`g:fzf_history_dir`. However, `fzf#run` doesn't know about any of these
|
|
||||||
options.
|
|
||||||
|
|
||||||
By *"wrapping"* your options dictionary with `fzf#wrap` before passing it to
|
So how can we make our custom `fzf#run` calls also respect those variables?
|
||||||
`fzf#run`, you can make your command also support the options.
|
Simply by *"wrapping"* the spec dictionary with `fzf#wrap` before passing it
|
||||||
|
to `fzf#run`.
|
||||||
|
|
||||||
|
- **`fzf#wrap([name string], [spec dict], [fullscreen bool]) -> (dict)`**
|
||||||
|
- All arguments are optional. Usually we only need to pass a spec dictionary.
|
||||||
|
- `name` is for managing history files. It is ignored if
|
||||||
|
`g:fzf_history_dir` is not defined.
|
||||||
|
- `fullscreen` can be either `0` or `1` (default: 0).
|
||||||
|
|
||||||
|
`fzf#wrap` takes a spec and returns an extended version of it (also
|
||||||
|
a dictionary) with additional options for addressing global preferences. You
|
||||||
|
can examine the return value of it like so:
|
||||||
|
|
||||||
```vim
|
```vim
|
||||||
" Usage:
|
echo fzf#wrap({'source': 'ls'})
|
||||||
" fzf#wrap([name string,] [opts dict,] [fullscreen boolean])
|
|
||||||
|
|
||||||
" This command now supports CTRL-T, CTRL-V, and CTRL-X key bindings
|
|
||||||
" and opens fzf according to g:fzf_layout setting.
|
|
||||||
command! Buffers call fzf#run(fzf#wrap(
|
|
||||||
\ {'source': map(range(1, bufnr('$')), 'bufname(v:val)')}))
|
|
||||||
|
|
||||||
" This extends the above example to open fzf in fullscreen
|
|
||||||
" when the command is run with ! suffix (Buffers!)
|
|
||||||
command! -bang Buffers call fzf#run(fzf#wrap(
|
|
||||||
\ {'source': map(range(1, bufnr('$')), 'bufname(v:val)')}, <bang>0))
|
|
||||||
|
|
||||||
" You can optionally pass the name of the command as the first argument to
|
|
||||||
" fzf#wrap to make it work with g:fzf_history_dir
|
|
||||||
command! -bang Buffers call fzf#run(fzf#wrap('buffers',
|
|
||||||
\ {'source': map(range(1, bufnr('$')), 'bufname(v:val)')}, <bang>0))
|
|
||||||
```
|
```
|
||||||
|
|
||||||
fzf inside terminal buffer
|
After we *"wrap"* our spec, we pass it to `fzf#run`.
|
||||||
--------------------------
|
|
||||||
|
```vim
|
||||||
|
call fzf#run(fzf#wrap({'source': 'ls'}))
|
||||||
|
```
|
||||||
|
|
||||||
|
Now it supports `CTRL-T`, `CTRL-V`, and `CTRL-X` key bindings and it opens fzf
|
||||||
|
window according to `g:fzf_layout` setting.
|
||||||
|
|
||||||
|
To make it easier to use, let's define `LS` command.
|
||||||
|
|
||||||
|
```vim
|
||||||
|
command! LS call fzf#run(fzf#wrap({'source': 'ls'}))
|
||||||
|
```
|
||||||
|
|
||||||
|
Type `:LS` and see how it works.
|
||||||
|
|
||||||
|
We would like to make `:LS!` (bang version) open fzf in fullscreen, just like
|
||||||
|
`:FZF!`. Add `-bang` to command definition, and use `<bang>` value to set
|
||||||
|
the last `fullscreen` argument of `fzf#wrap` (see `:help <bang>`).
|
||||||
|
|
||||||
|
```vim
|
||||||
|
" On :LS!, <bang> evaluates to '!', and '!0' becomes 1
|
||||||
|
command! -bang LS call fzf#run(fzf#wrap({'source': 'ls'}, <bang>0))
|
||||||
|
```
|
||||||
|
|
||||||
|
Our `:LS` command will be much more useful if we can pass a directory argument
|
||||||
|
to it, so that something like `:LS /tmp` is possible.
|
||||||
|
|
||||||
|
```vim
|
||||||
|
command! -bang -complete=dir -nargs=* LS
|
||||||
|
\ call fzf#run(fzf#wrap({'source': 'ls', 'dir': <q-args>}, <bang>0))
|
||||||
|
```
|
||||||
|
|
||||||
|
Lastly, if you have enabled `g:fzf_history_dir`, you might want to assign
|
||||||
|
a unique name to our command and pass it as the first argument to `fzf#wrap`.
|
||||||
|
|
||||||
|
```vim
|
||||||
|
" The query history for this command will be stored as 'ls' inside g:fzf_history_dir.
|
||||||
|
" The name is ignored if g:fzf_history_dir is not defined.
|
||||||
|
command! -bang -complete=dir -nargs=* LS
|
||||||
|
\ call fzf#run(fzf#wrap('ls', {'source': 'ls', 'dir': <q-args>}, <bang>0))
|
||||||
|
```
|
||||||
|
|
||||||
|
Tips
|
||||||
|
----
|
||||||
|
|
||||||
|
### fzf inside terminal buffer
|
||||||
|
|
||||||
The latest versions of Vim and Neovim include builtin terminal emulator
|
The latest versions of Vim and Neovim include builtin terminal emulator
|
||||||
(`:terminal`) and fzf will start in a terminal buffer in the following cases:
|
(`:terminal`) and fzf will start in a terminal buffer in the following cases:
|
||||||
|
|
||||||
- On Neovim
|
- On Neovim
|
||||||
- On GVim
|
- On GVim
|
||||||
- On Terminal Vim with the non-default layout
|
- On Terminal Vim with a non-default layout
|
||||||
- `call fzf#run({'left': '30%'})` or `let g:fzf_layout = {'left': '30%'}`
|
- `call fzf#run({'left': '30%'})` or `let g:fzf_layout = {'left': '30%'}`
|
||||||
|
|
||||||
### Hide statusline
|
#### Starting fzf in a popup window
|
||||||
|
|
||||||
When fzf starts in a terminal buffer, you may want to hide the statusline of
|
|
||||||
the containing buffer.
|
|
||||||
|
|
||||||
```vim
|
```vim
|
||||||
autocmd! FileType fzf
|
" Required:
|
||||||
autocmd FileType fzf set laststatus=0 noshowmode noruler
|
" - width [float range [0 ~ 1]]
|
||||||
\| autocmd BufLeave <buffer> set laststatus=2 showmode ruler
|
" - height [float range [0 ~ 1]]
|
||||||
|
"
|
||||||
|
" Optional:
|
||||||
|
" - xoffset [float default 0.5 range [0 ~ 1]]
|
||||||
|
" - yoffset [float default 0.5 range [0 ~ 1]]
|
||||||
|
" - highlight [string default 'Comment']: Highlight group for border
|
||||||
|
" - border [string default 'rounded']: Border style
|
||||||
|
" - 'rounded' / 'sharp' / 'horizontal' / 'vertical' / 'top' / 'bottom' / 'left' / 'right'
|
||||||
|
let g:fzf_layout = { 'window': { 'width': 0.9, 'height': 0.6 } }
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Hide statusline
|
||||||
|
|
||||||
|
When fzf starts in a terminal buffer, the file type of the buffer is set to
|
||||||
|
`fzf`. So you can set up `FileType fzf` autocmd to customize the settings of
|
||||||
|
the window.
|
||||||
|
|
||||||
|
For example, if you use the default layout (`{'down': '~40%'}`) on Neovim, you
|
||||||
|
might want to temporarily disable the statusline for a cleaner look.
|
||||||
|
|
||||||
|
```vim
|
||||||
|
if has('nvim') && !exists('g:fzf_layout')
|
||||||
|
autocmd! FileType fzf
|
||||||
|
autocmd FileType fzf set laststatus=0 noshowmode noruler
|
||||||
|
\| autocmd BufLeave <buffer> set laststatus=2 showmode ruler
|
||||||
|
endif
|
||||||
```
|
```
|
||||||
|
|
||||||
[License](LICENSE)
|
[License](LICENSE)
|
||||||
@@ -246,4 +419,4 @@ autocmd FileType fzf set laststatus=0 noshowmode noruler
|
|||||||
|
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2017 Junegunn Choi
|
Copyright (c) 2013-2020 Junegunn Choi
|
||||||
|
|||||||
313
README.md
313
README.md
@@ -25,13 +25,9 @@ Table of Contents
|
|||||||
* [Installation](#installation)
|
* [Installation](#installation)
|
||||||
* [Using Homebrew or Linuxbrew](#using-homebrew-or-linuxbrew)
|
* [Using Homebrew or Linuxbrew](#using-homebrew-or-linuxbrew)
|
||||||
* [Using git](#using-git)
|
* [Using git](#using-git)
|
||||||
* [As Vim plugin](#as-vim-plugin)
|
* [Using Linux package managers](#using-linux-package-managers)
|
||||||
* [Arch Linux](#arch-linux)
|
|
||||||
* [Debian](#debian)
|
|
||||||
* [Fedora](#fedora)
|
|
||||||
* [openSUSE](#opensuse)
|
|
||||||
* [FreeBSD](#freebsd)
|
|
||||||
* [Windows](#windows)
|
* [Windows](#windows)
|
||||||
|
* [As Vim plugin](#as-vim-plugin)
|
||||||
* [Upgrading fzf](#upgrading-fzf)
|
* [Upgrading fzf](#upgrading-fzf)
|
||||||
* [Building fzf](#building-fzf)
|
* [Building fzf](#building-fzf)
|
||||||
* [Usage](#usage)
|
* [Usage](#usage)
|
||||||
@@ -51,6 +47,7 @@ Table of Contents
|
|||||||
* [Environment variables / Aliases](#environment-variables--aliases)
|
* [Environment variables / Aliases](#environment-variables--aliases)
|
||||||
* [Settings](#settings)
|
* [Settings](#settings)
|
||||||
* [Supported commands](#supported-commands)
|
* [Supported commands](#supported-commands)
|
||||||
|
* [Custom fuzzy completion](#custom-fuzzy-completion)
|
||||||
* [Vim plugin](#vim-plugin)
|
* [Vim plugin](#vim-plugin)
|
||||||
* [Advanced topics](#advanced-topics)
|
* [Advanced topics](#advanced-topics)
|
||||||
* [Performance](#performance)
|
* [Performance](#performance)
|
||||||
@@ -58,7 +55,6 @@ Table of Contents
|
|||||||
* [Preview window](#preview-window)
|
* [Preview window](#preview-window)
|
||||||
* [Tips](#tips)
|
* [Tips](#tips)
|
||||||
* [Respecting .gitignore](#respecting-gitignore)
|
* [Respecting .gitignore](#respecting-gitignore)
|
||||||
* [git ls-tree for fast traversal](#git-ls-tree-for-fast-traversal)
|
|
||||||
* [Fish shell](#fish-shell)
|
* [Fish shell](#fish-shell)
|
||||||
* [Related projects](#related-projects)
|
* [Related projects](#related-projects)
|
||||||
* [<a href="LICENSE">License</a>](#license)
|
* [<a href="LICENSE">License</a>](#license)
|
||||||
@@ -106,111 +102,55 @@ git clone --depth 1 https://github.com/junegunn/fzf.git ~/.fzf
|
|||||||
~/.fzf/install
|
~/.fzf/install
|
||||||
```
|
```
|
||||||
|
|
||||||
### As Vim plugin
|
### Using Linux package managers
|
||||||
|
|
||||||
Once you have fzf installed, you can enable it inside Vim simply by adding the
|
| Distro | Command |
|
||||||
directory to `&runtimepath` in your Vim configuration file as follows:
|
| --- | --- |
|
||||||
|
| Alpine Linux | `sudo apk add fzf` |
|
||||||
|
| Arch Linux | `sudo pacman -S fzf` |
|
||||||
|
| Debian | `sudo apt-get install fzf` |
|
||||||
|
| Fedora | `sudo dnf install fzf` |
|
||||||
|
| FreeBSD | `pkg install fzf` |
|
||||||
|
| NixOS | `nix-env -iA nixpkgs.fzf` |
|
||||||
|
| openSUSE | `sudo zypper install fzf` |
|
||||||
|
| OpenBSD | `pkg_add fzf` |
|
||||||
|
|
||||||
```vim
|
Shell extensions (key bindings and fuzzy auto-completion) and Vim/Neovim
|
||||||
" If installed using Homebrew
|
plugin may or may not be enabled by default depending on the package manager.
|
||||||
set rtp+=/usr/local/opt/fzf
|
Refer to the package documentation for more information.
|
||||||
|
|
||||||
" If installed using git
|
|
||||||
set rtp+=~/.fzf
|
|
||||||
```
|
|
||||||
|
|
||||||
If you use [vim-plug](https://github.com/junegunn/vim-plug), the same can be
|
|
||||||
written as:
|
|
||||||
|
|
||||||
```vim
|
|
||||||
" If installed using Homebrew
|
|
||||||
Plug '/usr/local/opt/fzf'
|
|
||||||
|
|
||||||
" If installed using git
|
|
||||||
Plug '~/.fzf'
|
|
||||||
```
|
|
||||||
|
|
||||||
But instead of separately installing fzf on your system (using Homebrew or
|
|
||||||
"git clone") and enabling it on Vim (adding it to `&runtimepath`), you can use
|
|
||||||
vim-plug to do both.
|
|
||||||
|
|
||||||
```vim
|
|
||||||
" PlugInstall and PlugUpdate will clone fzf in ~/.fzf and run the install script
|
|
||||||
Plug 'junegunn/fzf', { 'dir': '~/.fzf', 'do': './install --all' }
|
|
||||||
" Both options are optional. You don't have to install fzf in ~/.fzf
|
|
||||||
" and you don't have to run the install script if you use fzf only in Vim.
|
|
||||||
```
|
|
||||||
|
|
||||||
### Arch Linux
|
|
||||||
|
|
||||||
```sh
|
|
||||||
sudo pacman -S fzf
|
|
||||||
```
|
|
||||||
|
|
||||||
### Debian
|
|
||||||
|
|
||||||
fzf is available in Debian Buster and above, and can be installed using the usual
|
|
||||||
method:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
sudo apt-get install fzf
|
|
||||||
```
|
|
||||||
|
|
||||||
Read the documentation (/usr/share/doc/fzf/README.Debian) on how to enable it.
|
|
||||||
|
|
||||||
### Fedora
|
|
||||||
|
|
||||||
fzf is available in Fedora 26 and above, and can be installed using the usual
|
|
||||||
method:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
sudo dnf install fzf
|
|
||||||
```
|
|
||||||
|
|
||||||
Shell completion and plugins for vim or neovim are enabled by default. Shell
|
|
||||||
key bindings are installed but not enabled by default. See Fedora's package
|
|
||||||
documentation (/usr/share/doc/fzf/README.Fedora) for more information.
|
|
||||||
|
|
||||||
### openSUSE
|
|
||||||
|
|
||||||
fzf is available in openSUSE Tumbleweed and can be installed via zypper:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
sudo zypper install fzf
|
|
||||||
```
|
|
||||||
|
|
||||||
### FreeBSD
|
|
||||||
|
|
||||||
```sh
|
|
||||||
pkg install fzf
|
|
||||||
```
|
|
||||||
|
|
||||||
### Windows
|
### Windows
|
||||||
|
|
||||||
Pre-built binaries for Windows can be downloaded [here][bin]. fzf is also
|
Pre-built binaries for Windows can be downloaded [here][bin]. fzf is also
|
||||||
available as a [Chocolatey package][choco]:
|
available via [Chocolatey][choco] and [Scoop][scoop]:
|
||||||
|
|
||||||
|
| Package manager | Command |
|
||||||
|
| --- | --- |
|
||||||
|
| Chocolatey | `choco install fzf` |
|
||||||
|
| Scoop | `scoop install fzf` |
|
||||||
|
|
||||||
[choco]: https://chocolatey.org/packages/fzf
|
[choco]: https://chocolatey.org/packages/fzf
|
||||||
|
|
||||||
```sh
|
|
||||||
choco install fzf
|
|
||||||
```
|
|
||||||
|
|
||||||
or a [Scoop package][scoop]:
|
|
||||||
|
|
||||||
[scoop]: https://github.com/ScoopInstaller/Main/blob/master/bucket/fzf.json
|
[scoop]: https://github.com/ScoopInstaller/Main/blob/master/bucket/fzf.json
|
||||||
|
|
||||||
```sh
|
Known issues and limitations on Windows can be found on [the wiki
|
||||||
scoop install fzf
|
page][windows-wiki].
|
||||||
```
|
|
||||||
|
|
||||||
However, other components of the project may not work on Windows. Known issues
|
|
||||||
and limitations can be found on [the wiki page][windows-wiki]. You might want
|
|
||||||
to consider installing fzf on [Windows Subsystem for Linux][wsl] where
|
|
||||||
everything runs flawlessly.
|
|
||||||
|
|
||||||
[windows-wiki]: https://github.com/junegunn/fzf/wiki/Windows
|
[windows-wiki]: https://github.com/junegunn/fzf/wiki/Windows
|
||||||
[wsl]: https://blogs.msdn.microsoft.com/wsl/
|
|
||||||
|
### As Vim plugin
|
||||||
|
|
||||||
|
If you use
|
||||||
|
[vim-plug](https://github.com/junegunn/vim-plug), add this line to your Vim
|
||||||
|
configuration file:
|
||||||
|
|
||||||
|
```vim
|
||||||
|
Plug 'junegunn/fzf', { 'do': { -> fzf#install() } }
|
||||||
|
```
|
||||||
|
|
||||||
|
`fzf#install()` makes sure that you have the latest binary, but it's optional,
|
||||||
|
so you can omit it if you use a plugin manager that doesn't support hooks.
|
||||||
|
|
||||||
|
For more installation options, see [README-VIM.md](README-VIM.md).
|
||||||
|
|
||||||
Upgrading fzf
|
Upgrading fzf
|
||||||
-------------
|
-------------
|
||||||
@@ -221,6 +161,7 @@ method used.
|
|||||||
|
|
||||||
- git: `cd ~/.fzf && git pull && ./install`
|
- git: `cd ~/.fzf && git pull && ./install`
|
||||||
- brew: `brew update; brew reinstall fzf`
|
- brew: `brew update; brew reinstall fzf`
|
||||||
|
- macports: `sudo port upgrade fzf`
|
||||||
- chocolatey: `choco upgrade fzf`
|
- chocolatey: `choco upgrade fzf`
|
||||||
- vim-plug: `:PlugUpdate fzf`
|
- vim-plug: `:PlugUpdate fzf`
|
||||||
|
|
||||||
@@ -340,8 +281,10 @@ own as well.
|
|||||||
[fzf-tmux](bin/fzf-tmux) is a bash script that opens fzf in a tmux pane.
|
[fzf-tmux](bin/fzf-tmux) is a bash script that opens fzf in a tmux pane.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
# usage: fzf-tmux [-u|-d [HEIGHT[%]]] [-l|-r [WIDTH[%]]] [--] [FZF OPTIONS]
|
# usage: fzf-tmux [LAYOUT OPTIONS] [--] [FZF OPTIONS]
|
||||||
# (-[udlr]: up/down/left/right)
|
|
||||||
|
# See available options
|
||||||
|
fzf-tmux --help
|
||||||
|
|
||||||
# select git branches in horizontal split below (15 lines)
|
# select git branches in horizontal split below (15 lines)
|
||||||
git branch | fzf-tmux -d 15
|
git branch | fzf-tmux -d 15
|
||||||
@@ -350,7 +293,7 @@ git branch | fzf-tmux -d 15
|
|||||||
cat /usr/share/dict/words | fzf-tmux -l 20% --multi --reverse
|
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 `-[pudlr]`
|
||||||
options, so you can invariably use `fzf-tmux` in your scripts.
|
options, so you can invariably use `fzf-tmux` in your scripts.
|
||||||
|
|
||||||
Alternatively, you can use `--height HEIGHT[%]` option not to start fzf in
|
Alternatively, you can use `--height HEIGHT[%]` option not to start fzf in
|
||||||
@@ -377,13 +320,9 @@ fish.
|
|||||||
- Set `FZF_ALT_C_COMMAND` to override the default command
|
- Set `FZF_ALT_C_COMMAND` to override the default command
|
||||||
- Set `FZF_ALT_C_OPTS` to pass additional options
|
- Set `FZF_ALT_C_OPTS` to pass additional options
|
||||||
|
|
||||||
If you're on a tmux session, you can start fzf in a split pane by setting
|
If you're on a tmux session, you can start fzf in a tmux split pane or in
|
||||||
`FZF_TMUX` to 1, and change the height of the pane with `FZF_TMUX_HEIGHT`
|
a tmux popup window by setting `FZF_TMUX_OPTS` (e.g. `-d 40%`).
|
||||||
(e.g. `20`, `50%`).
|
See `fzf-tmux --help` for available options.
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
More tips can be found on [the wiki page](https://github.com/junegunn/fzf/wiki/Configuring-shell-key-bindings).
|
More tips can be found on [the wiki page](https://github.com/junegunn/fzf/wiki/Configuring-shell-key-bindings).
|
||||||
|
|
||||||
@@ -468,17 +407,83 @@ _fzf_compgen_path() {
|
|||||||
_fzf_compgen_dir() {
|
_fzf_compgen_dir() {
|
||||||
fd --type d --hidden --follow --exclude ".git" . "$1"
|
fd --type d --hidden --follow --exclude ".git" . "$1"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# (EXPERIMENTAL) Advanced customization of fzf options via _fzf_comprun function
|
||||||
|
# - The first argument to the function is the name of the command.
|
||||||
|
# - You should make sure to pass the rest of the arguments to fzf.
|
||||||
|
_fzf_comprun() {
|
||||||
|
local command=$1
|
||||||
|
shift
|
||||||
|
|
||||||
|
case "$command" in
|
||||||
|
cd) fzf "$@" --preview 'tree -C {} | head -200' ;;
|
||||||
|
export|unset) fzf "$@" --preview "eval 'echo \$'{}" ;;
|
||||||
|
ssh) fzf "$@" --preview 'dig {}' ;;
|
||||||
|
*) fzf "$@" ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Supported commands
|
#### Supported commands
|
||||||
|
|
||||||
On bash, fuzzy completion is enabled only for a predefined set of commands
|
On bash, fuzzy completion is enabled only for a predefined set of commands
|
||||||
(`complete | grep _fzf` to see the list). But you can enable it for other
|
(`complete | grep _fzf` to see the list). But you can enable it for other
|
||||||
commands as well as follows.
|
commands as well by using `_fzf_setup_completion` helper function.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
complete -F _fzf_path_completion -o default -o bashdefault ag
|
# usage: _fzf_setup_completion path|dir|var|alias|host COMMANDS...
|
||||||
complete -F _fzf_dir_completion -o default -o bashdefault tree
|
_fzf_setup_completion path ag git kubectl
|
||||||
|
_fzf_setup_completion dir tree
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Custom fuzzy completion
|
||||||
|
|
||||||
|
_**(Custom completion API is experimental and subject to change)**_
|
||||||
|
|
||||||
|
For a command named _"COMMAND"_, define `_fzf_complete_COMMAND` function using
|
||||||
|
`_fzf_complete` helper.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# Custom fuzzy completion for "doge" command
|
||||||
|
# e.g. doge **<TAB>
|
||||||
|
_fzf_complete_doge() {
|
||||||
|
_fzf_complete --multi --reverse --prompt="doge> " -- "$@" < <(
|
||||||
|
echo very
|
||||||
|
echo wow
|
||||||
|
echo such
|
||||||
|
echo doge
|
||||||
|
)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- The arguments before `--` are the options to fzf.
|
||||||
|
- After `--`, simply pass the original completion arguments unchanged (`"$@"`).
|
||||||
|
- Then write a set of commands that generates the completion candidates and
|
||||||
|
feed its output to the function using process substitution (`< <(...)`).
|
||||||
|
|
||||||
|
zsh will automatically pick up the function using the naming convention but in
|
||||||
|
bash you have to manually associate the function with the command using
|
||||||
|
`complete` command.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
[ -n "$BASH" ] && complete -F _fzf_complete_doge -o default -o bashdefault doge
|
||||||
|
```
|
||||||
|
|
||||||
|
If you need to post-process the output from fzf, define
|
||||||
|
`_fzf_complete_COMMAND_post` as follows.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
_fzf_complete_foo() {
|
||||||
|
_fzf_complete --multi --reverse --header-lines=3 -- "$@" < <(
|
||||||
|
ls -al
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
_fzf_complete_foo_post() {
|
||||||
|
awk '{print $NF}'
|
||||||
|
}
|
||||||
|
|
||||||
|
[ -n "$BASH" ] && complete -F _fzf_complete_foo -o default -o bashdefault foo
|
||||||
```
|
```
|
||||||
|
|
||||||
Vim plugin
|
Vim plugin
|
||||||
@@ -524,7 +529,8 @@ See *KEY BINDINGS* section of the man page for details.
|
|||||||
### Preview window
|
### Preview window
|
||||||
|
|
||||||
When `--preview` option is set, fzf automatically starts an external process with
|
When `--preview` option is set, fzf automatically starts an external process with
|
||||||
the current line as the argument and shows the result in the split window.
|
the current line as the argument and shows the result in the split window. Your
|
||||||
|
`$SHELL` is used to execute the command with `$SHELL -c COMMAND`.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# {} is replaced to the single-quoted string of the focused line
|
# {} is replaced to the single-quoted string of the focused line
|
||||||
@@ -539,37 +545,50 @@ important that the command finishes quickly.
|
|||||||
fzf --preview 'head -100 {}'
|
fzf --preview 'head -100 {}'
|
||||||
```
|
```
|
||||||
|
|
||||||
Preview window supports ANSI colors, so you can use programs that
|
Preview window supports ANSI colors, so you can use any program that
|
||||||
syntax-highlights the content of a file.
|
syntax-highlights the content of a file.
|
||||||
|
|
||||||
- Bat: https://github.com/sharkdp/bat
|
- Bat: https://github.com/sharkdp/bat
|
||||||
- Highlight: http://www.andre-simon.de/doku/highlight/en/highlight.php
|
- Highlight: http://www.andre-simon.de/doku/highlight/en/highlight.php
|
||||||
- CodeRay: http://coderay.rubychan.de/
|
|
||||||
- Rouge: https://github.com/jneen/rouge
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Try bat, highlight, coderay, rougify in turn, then fall back to cat
|
fzf --preview 'bat --style=numbers --color=always {} | head -500'
|
||||||
fzf --preview '[[ $(file --mime {}) =~ binary ]] &&
|
|
||||||
echo {} is a binary file ||
|
|
||||||
(bat --style=numbers --color=always {} ||
|
|
||||||
highlight -O ansi -l {} ||
|
|
||||||
coderay {} ||
|
|
||||||
rougify {} ||
|
|
||||||
cat {}) 2> /dev/null | head -500'
|
|
||||||
```
|
```
|
||||||
|
|
||||||
You can customize the size and position of the preview window using
|
You can customize the size, position, and border of the preview window using
|
||||||
`--preview-window` option. For example,
|
`--preview-window` option, and the foreground and background color of it with
|
||||||
|
`--color` option. For example,
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
fzf --height 40% --reverse --preview 'file {}' --preview-window down:1
|
fzf --height 40% --layout reverse --info inline --border \
|
||||||
|
--preview 'file {}' --preview-window down:1:noborder \
|
||||||
|
--color 'fg:#bbccdd,fg+:#ddeeff,bg:#334455,preview-bg:#223344,border:#778899'
|
||||||
```
|
```
|
||||||
|
|
||||||
|
See the man page (`man fzf`) for the full list of options.
|
||||||
|
|
||||||
For more advanced examples, see [Key bindings for git with fzf][fzf-git]
|
For more advanced examples, see [Key bindings for git with fzf][fzf-git]
|
||||||
([code](https://gist.github.com/junegunn/8b572b8d4b5eddd8b85e5f4d40f17236)).
|
([code](https://gist.github.com/junegunn/8b572b8d4b5eddd8b85e5f4d40f17236)).
|
||||||
|
|
||||||
[fzf-git]: https://junegunn.kr/2016/07/fzf-git/
|
[fzf-git]: https://junegunn.kr/2016/07/fzf-git/
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
Since fzf is a general-purpose text filter rather than a file finder, **it is
|
||||||
|
not a good idea to add `--preview` option to your `$FZF_DEFAULT_OPTS`**.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# *********************
|
||||||
|
# ** DO NOT DO THIS! **
|
||||||
|
# *********************
|
||||||
|
export FZF_DEFAULT_OPTS='--preview "bat --style=numbers --color=always {} | head -500"'
|
||||||
|
|
||||||
|
# bat doesn't work with any input other than the list of files
|
||||||
|
ps -ef | fzf
|
||||||
|
seq 100 | fzf
|
||||||
|
history | fzf
|
||||||
|
```
|
||||||
|
|
||||||
Tips
|
Tips
|
||||||
----
|
----
|
||||||
|
|
||||||
@@ -602,45 +621,17 @@ hidden files, use the following command:
|
|||||||
export FZF_DEFAULT_COMMAND='fd --type f --hidden --follow --exclude .git'
|
export FZF_DEFAULT_COMMAND='fd --type f --hidden --follow --exclude .git'
|
||||||
```
|
```
|
||||||
|
|
||||||
#### `git ls-tree` for fast traversal
|
|
||||||
|
|
||||||
If you're running fzf in a large git repository, `git ls-tree` can boost up the
|
|
||||||
speed of the traversal.
|
|
||||||
|
|
||||||
```sh
|
|
||||||
export FZF_DEFAULT_COMMAND='
|
|
||||||
(git ls-tree -r --name-only HEAD ||
|
|
||||||
find . -path "*/\.*" -prune -o -type f -print -o -type l -print |
|
|
||||||
sed s/^..//) 2> /dev/null'
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Fish shell
|
#### Fish shell
|
||||||
|
|
||||||
Fish shell before version 2.6.0 [doesn't allow](https://github.com/fish-shell/fish-shell/issues/1362)
|
`CTRL-T` key binding of fish, unlike those of bash and zsh, will use the last
|
||||||
reading from STDIN in command substitution, which means simple `vim (fzf)`
|
token on the command-line as the root directory for the recursive search. For
|
||||||
doesn't work as expected. The workaround for fish 2.5.0 and earlier is to use
|
instance, hitting `CTRL-T` at the end of the following command-line
|
||||||
the `read` fish command:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
fzf | read -l result; and vim $result
|
|
||||||
```
|
|
||||||
|
|
||||||
or, for multiple results:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
fzf -m | while read -l r; set result $result $r; end; and vim $result
|
|
||||||
```
|
|
||||||
|
|
||||||
The globbing system is different in fish and thus `**` completion will not work.
|
|
||||||
However, the `CTRL-T` command will use the last token on the command-line as the
|
|
||||||
root folder for the recursive search. For instance, hitting `CTRL-T` at the end
|
|
||||||
of the following command-line
|
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
ls /var/
|
ls /var/
|
||||||
```
|
```
|
||||||
|
|
||||||
will list all files and folders under `/var/`.
|
will list all files and directories under `/var/`.
|
||||||
|
|
||||||
When using a custom `FZF_CTRL_T_COMMAND`, use the unexpanded `$dir` variable to
|
When using a custom `FZF_CTRL_T_COMMAND`, use the unexpanded `$dir` variable to
|
||||||
make use of this feature. `$dir` defaults to `.` when the last token is not a
|
make use of this feature. `$dir` defaults to `.` when the last token is not a
|
||||||
@@ -660,4 +651,4 @@ https://github.com/junegunn/fzf/wiki/Related-projects
|
|||||||
|
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2017 Junegunn Choi
|
Copyright (c) 2013-2020 Junegunn Choi
|
||||||
|
|||||||
73
bin/fzf-tmux
73
bin/fzf-tmux
@@ -1,6 +1,6 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# fzf-tmux: starts fzf in a tmux pane
|
# fzf-tmux: starts fzf in a tmux pane
|
||||||
# usage: fzf-tmux [-u|-d [HEIGHT[%]]] [-l|-r [WIDTH[%]]] [--] [FZF OPTIONS]
|
# usage: fzf-tmux [LAYOUT OPTIONS] [--] [FZF OPTIONS]
|
||||||
|
|
||||||
fail() {
|
fail() {
|
||||||
>&2 echo "$1"
|
>&2 echo "$1"
|
||||||
@@ -10,6 +10,7 @@ fail() {
|
|||||||
fzf="$(command -v fzf 2> /dev/null)" || fzf="$(dirname "$0")/fzf"
|
fzf="$(command -v fzf 2> /dev/null)" || fzf="$(dirname "$0")/fzf"
|
||||||
[[ -x "$fzf" ]] || fail 'fzf executable not found'
|
[[ -x "$fzf" ]] || fail 'fzf executable not found'
|
||||||
|
|
||||||
|
tmux_args=()
|
||||||
args=()
|
args=()
|
||||||
opt=""
|
opt=""
|
||||||
skip=""
|
skip=""
|
||||||
@@ -20,15 +21,23 @@ term=""
|
|||||||
[[ -n "$COLUMNS" ]] && columns=$COLUMNS || columns=$(tput cols) || columns=$(tmux display-message -p "#{pane_width}")
|
[[ -n "$COLUMNS" ]] && columns=$COLUMNS || columns=$(tput cols) || columns=$(tmux display-message -p "#{pane_width}")
|
||||||
|
|
||||||
help() {
|
help() {
|
||||||
>&2 echo 'usage: fzf-tmux [-u|-d [HEIGHT[%]]] [-l|-r [WIDTH[%]]] [--] [FZF OPTIONS]
|
>&2 echo 'usage: fzf-tmux [LAYOUT OPTIONS] [--] [FZF OPTIONS]
|
||||||
|
|
||||||
Layout
|
LAYOUT OPTIONS:
|
||||||
-u [HEIGHT[%]] Split above (up)
|
(default layout: -d 50%)
|
||||||
-d [HEIGHT[%]] Split below (down)
|
|
||||||
-l [WIDTH[%]] Split left
|
|
||||||
-r [WIDTH[%]] Split right
|
|
||||||
|
|
||||||
(default: -d 50%)
|
Popup window (requires tmux 3.2 or above):
|
||||||
|
-p [WIDTH[%][,HEIGHT[%]]] (default: 50%)
|
||||||
|
-w WIDTH[%]
|
||||||
|
-h HEIGHT[%]
|
||||||
|
-x COL
|
||||||
|
-y ROW
|
||||||
|
|
||||||
|
Split pane:
|
||||||
|
-u [HEIGHT[%]] Split above (up)
|
||||||
|
-d [HEIGHT[%]] Split below (down)
|
||||||
|
-l [WIDTH[%]] Split left
|
||||||
|
-r [WIDTH[%]] Split right
|
||||||
'
|
'
|
||||||
exit
|
exit
|
||||||
}
|
}
|
||||||
@@ -47,8 +56,10 @@ while [[ $# -gt 0 ]]; do
|
|||||||
echo "fzf-tmux (with fzf $("$fzf" --version))"
|
echo "fzf-tmux (with fzf $("$fzf" --version))"
|
||||||
exit
|
exit
|
||||||
;;
|
;;
|
||||||
-w*|-h*|-d*|-u*|-r*|-l*)
|
-p*|-w*|-h*|-x*|-y*|-d*|-u*|-r*|-l*)
|
||||||
if [[ "$arg" =~ ^.[lrw] ]]; then
|
if [[ "$arg" =~ ^-[pwhxy] ]]; then
|
||||||
|
[[ "$opt" =~ "-K -E" ]] || opt="-K -E"
|
||||||
|
elif [[ "$arg" =~ ^.[lr] ]]; then
|
||||||
opt="-h"
|
opt="-h"
|
||||||
if [[ "$arg" =~ ^.l ]]; then
|
if [[ "$arg" =~ ^.l ]]; then
|
||||||
opt="$opt -d"
|
opt="$opt -d"
|
||||||
@@ -66,7 +77,7 @@ while [[ $# -gt 0 ]]; do
|
|||||||
if [[ ${#arg} -gt 2 ]]; then
|
if [[ ${#arg} -gt 2 ]]; then
|
||||||
size="${arg:2}"
|
size="${arg:2}"
|
||||||
else
|
else
|
||||||
if [[ "$1" =~ ^[0-9]+%?$ ]]; then
|
if [[ "$1" =~ ^[0-9%,C]+$ ]]; then
|
||||||
size="$1"
|
size="$1"
|
||||||
shift
|
shift
|
||||||
else
|
else
|
||||||
@@ -74,7 +85,15 @@ while [[ $# -gt 0 ]]; do
|
|||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ "$size" =~ %$ ]]; then
|
if [[ "$arg" =~ ^-p ]]; then
|
||||||
|
if [[ -n "$size" ]]; then
|
||||||
|
w=${size%%,*}
|
||||||
|
h=${size##*,}
|
||||||
|
opt="$opt -w$w -h$h"
|
||||||
|
fi
|
||||||
|
elif [[ "$arg" =~ ^-[whxy] ]]; then
|
||||||
|
opt="$opt ${arg:0:2}$size"
|
||||||
|
elif [[ "$size" =~ %$ ]]; then
|
||||||
size=${size:0:((${#size}-1))}
|
size=${size:0:((${#size}-1))}
|
||||||
if [[ -n "$swap" ]]; then
|
if [[ -n "$swap" ]]; then
|
||||||
opt="$opt -p $(( 100 - size ))"
|
opt="$opt -p $(( 100 - size ))"
|
||||||
@@ -100,6 +119,8 @@ while [[ $# -gt 0 ]]; do
|
|||||||
# "--" can be used to separate fzf-tmux options from fzf options to
|
# "--" can be used to separate fzf-tmux options from fzf options to
|
||||||
# avoid conflicts
|
# avoid conflicts
|
||||||
skip=1
|
skip=1
|
||||||
|
tmux_args=("${args[@]}")
|
||||||
|
args=()
|
||||||
continue
|
continue
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
@@ -109,13 +130,13 @@ while [[ $# -gt 0 ]]; do
|
|||||||
[[ -n "$skip" ]] && args+=("$arg")
|
[[ -n "$skip" ]] && args+=("$arg")
|
||||||
done
|
done
|
||||||
|
|
||||||
if [[ -z "$TMUX" || "$opt" =~ ^-h && "$columns" -le 40 || ! "$opt" =~ ^-h && "$lines" -le 15 ]]; then
|
if [[ -z "$TMUX" ]]; then
|
||||||
"$fzf" "${args[@]}"
|
"$fzf" "${args[@]}"
|
||||||
exit $?
|
exit $?
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# --height option is not allowed
|
# --height option is not allowed
|
||||||
args+=("--no-height")
|
args=("--no-height" "${args[@]}")
|
||||||
|
|
||||||
# Handle zoomed tmux pane by moving it to a temp window
|
# Handle zoomed tmux pane by moving it to a temp window
|
||||||
if tmux list-panes -F '#F' | grep -q Z; then
|
if tmux list-panes -F '#F' | grep -q Z; then
|
||||||
@@ -159,6 +180,7 @@ trap 'cleanup 1' SIGUSR1
|
|||||||
trap 'cleanup' EXIT
|
trap 'cleanup' EXIT
|
||||||
|
|
||||||
envs="env TERM=$TERM "
|
envs="env TERM=$TERM "
|
||||||
|
[[ "$opt" =~ "-K -E" ]] && FZF_DEFAULT_OPTS="--margin 0,1 $FZF_DEFAULT_OPTS"
|
||||||
[[ -n "$FZF_DEFAULT_OPTS" ]] && envs="$envs FZF_DEFAULT_OPTS=$(printf %q "$FZF_DEFAULT_OPTS")"
|
[[ -n "$FZF_DEFAULT_OPTS" ]] && envs="$envs FZF_DEFAULT_OPTS=$(printf %q "$FZF_DEFAULT_OPTS")"
|
||||||
[[ -n "$FZF_DEFAULT_COMMAND" ]] && envs="$envs FZF_DEFAULT_COMMAND=$(printf %q "$FZF_DEFAULT_COMMAND")"
|
[[ -n "$FZF_DEFAULT_COMMAND" ]] && envs="$envs FZF_DEFAULT_COMMAND=$(printf %q "$FZF_DEFAULT_COMMAND")"
|
||||||
|
|
||||||
@@ -181,21 +203,34 @@ close="; trap - EXIT SIGINT SIGTERM $close"
|
|||||||
|
|
||||||
tmux_win_opts=( $(tmux show-window-options remain-on-exit \; show-window-options synchronize-panes | sed '/ off/d; s/^/set-window-option /; s/$/ \\;/') )
|
tmux_win_opts=( $(tmux show-window-options remain-on-exit \; show-window-options synchronize-panes | sed '/ off/d; s/^/set-window-option /; s/$/ \\;/') )
|
||||||
|
|
||||||
|
if [[ "$opt" =~ "-K -E" ]]; then
|
||||||
|
cat $fifo2 &
|
||||||
|
if [[ -n "$term" ]] || [[ -t 0 ]]; then
|
||||||
|
cat <<< "\"$fzf\" $opts > $fifo2; out=\$? $close; exit \$out" >> $argsf
|
||||||
|
TMUX=$(echo $TMUX | cut -d , -f 1,2) tmux popup -d "$PWD" "${tmux_args[@]}" $opt -R "$envs bash $argsf" > /dev/null 2>&1
|
||||||
|
else
|
||||||
|
mkfifo $fifo1
|
||||||
|
cat <<< "\"$fzf\" $opts < $fifo1 > $fifo2; out=\$? $close; exit \$out" >> $argsf
|
||||||
|
cat <&0 > $fifo1 &
|
||||||
|
TMUX=$(echo $TMUX | cut -d , -f 1,2) tmux popup -d "$PWD" "${tmux_args[@]}" $opt -R "$envs bash $argsf" > /dev/null 2>&1
|
||||||
|
fi
|
||||||
|
exit $?
|
||||||
|
fi
|
||||||
|
|
||||||
if [[ -n "$term" ]] || [[ -t 0 ]]; then
|
if [[ -n "$term" ]] || [[ -t 0 ]]; then
|
||||||
cat <<< "\"$fzf\" $opts > $fifo2; echo \$? > $fifo3 $close" >> $argsf
|
cat <<< "\"$fzf\" $opts > $fifo2; echo \$? > $fifo3 $close" >> $argsf
|
||||||
TMUX=$(echo $TMUX | cut -d , -f 1,2) tmux set-window-option synchronize-panes off \;\
|
TMUX=$(echo $TMUX | cut -d , -f 1,2) tmux set-window-option synchronize-panes off \;\
|
||||||
set-window-option remain-on-exit off \;\
|
set-window-option remain-on-exit off \;\
|
||||||
split-window $opt "$envs bash -c 'cd $(printf %q "$PWD"); exec -a fzf bash $argsf'" $swap \
|
split-window $opt "${tmux_args[@]}" "$envs bash -c 'cd $(printf %q "$PWD"); exec -a fzf bash $argsf'" $swap \
|
||||||
> /dev/null 2>&1
|
> /dev/null 2>&1 || { "$fzf" "${args[@]}"; exit $?; }
|
||||||
else
|
else
|
||||||
mkfifo $fifo1
|
mkfifo $fifo1
|
||||||
cat <<< "\"$fzf\" $opts < $fifo1 > $fifo2; echo \$? > $fifo3 $close" >> $argsf
|
cat <<< "\"$fzf\" $opts < $fifo1 > $fifo2; echo \$? > $fifo3 $close" >> $argsf
|
||||||
TMUX=$(echo $TMUX | cut -d , -f 1,2) tmux set-window-option synchronize-panes off \;\
|
TMUX=$(echo $TMUX | cut -d , -f 1,2) tmux set-window-option synchronize-panes off \;\
|
||||||
set-window-option remain-on-exit off \;\
|
set-window-option remain-on-exit off \;\
|
||||||
split-window $opt "$envs bash -c 'exec -a fzf bash $argsf'" $swap \
|
split-window $opt "${tmux_args[@]}" "$envs bash -c 'exec -a fzf bash $argsf'" $swap \
|
||||||
> /dev/null 2>&1
|
> /dev/null 2>&1 || { "$fzf" "${args[@]}"; exit $?; }
|
||||||
cat <&0 > $fifo1 &
|
cat <&0 > $fifo1 &
|
||||||
fi
|
fi
|
||||||
cat $fifo2
|
cat $fifo2
|
||||||
exit "$(cat $fifo3)"
|
exit "$(cat $fifo3)"
|
||||||
|
|
||||||
|
|||||||
254
doc/fzf.txt
254
doc/fzf.txt
@@ -1,24 +1,47 @@
|
|||||||
fzf.txt fzf Last change: June 8 2019
|
fzf.txt fzf Last change: February 14 2020
|
||||||
FZF - TABLE OF CONTENTS *fzf* *fzf-toc*
|
FZF - TABLE OF CONTENTS *fzf* *fzf-toc*
|
||||||
==============================================================================
|
==============================================================================
|
||||||
|
|
||||||
FZF Vim integration
|
FZF Vim integration
|
||||||
|
Summary
|
||||||
:FZF[!]
|
:FZF[!]
|
||||||
Configuration
|
Configuration
|
||||||
Examples
|
Examples
|
||||||
fzf#run
|
fzf#run
|
||||||
fzf#wrap
|
fzf#wrap
|
||||||
fzf inside terminal buffer
|
Tips
|
||||||
Hide statusline
|
fzf inside terminal buffer
|
||||||
|
Starting fzf in a popup window
|
||||||
|
Hide statusline
|
||||||
License
|
License
|
||||||
|
|
||||||
FZF VIM INTEGRATION *fzf-vim-integration*
|
FZF VIM INTEGRATION *fzf-vim-integration*
|
||||||
==============================================================================
|
==============================================================================
|
||||||
|
|
||||||
This repository only enables basic integration with Vim. If you're looking for
|
|
||||||
more, check out {fzf.vim}{1} project.
|
|
||||||
|
|
||||||
(Note: To use fzf in GVim, an external terminal emulator is required.)
|
SUMMARY *fzf-summary*
|
||||||
|
==============================================================================
|
||||||
|
|
||||||
|
The Vim plugin of fzf provides two core functions, and `:FZF` command which is
|
||||||
|
the basic file selector command built on top of them.
|
||||||
|
|
||||||
|
1. `fzf#run([spec dict])`
|
||||||
|
- Starts fzf inside Vim with the given spec
|
||||||
|
- `:call fzf#run({'source': 'ls'})`
|
||||||
|
2. `fzf#wrap([spec dict]) -> (dict)`
|
||||||
|
- Takes a spec for `fzf#run` and returns an extended version of it with
|
||||||
|
additional options for addressing global preferences (`g:fzf_xxx`)
|
||||||
|
- `:echo fzf#wrap({'source': 'ls'})`
|
||||||
|
- We usually wrap a spec with `fzf#wrap` before passing it to `fzf#run`
|
||||||
|
- `:call fzf#run(fzf#wrap({'source': 'ls'}))`
|
||||||
|
3. `:FZF [fzf_options string] [path string]`
|
||||||
|
- Basic fuzzy file selector
|
||||||
|
- A reference implementation for those who don't want to write VimScript to
|
||||||
|
implement custom commands
|
||||||
|
- If you're looking for more such commands, check out {fzf.vim}{1} project.
|
||||||
|
|
||||||
|
The most important of all is `fzf#run`, but it would be easier to understand
|
||||||
|
the whole if we start off with `:FZF` command.
|
||||||
|
|
||||||
{1} https://github.com/junegunn/fzf.vim
|
{1} https://github.com/junegunn/fzf.vim
|
||||||
|
|
||||||
@@ -27,8 +50,6 @@ more, check out {fzf.vim}{1} project.
|
|||||||
==============================================================================
|
==============================================================================
|
||||||
|
|
||||||
*:FZF*
|
*:FZF*
|
||||||
|
|
||||||
If you have set up fzf for Vim, `:FZF` command will be added.
|
|
||||||
>
|
>
|
||||||
" Look for files under current directory
|
" Look for files under current directory
|
||||||
:FZF
|
:FZF
|
||||||
@@ -36,8 +57,8 @@ If you have set up fzf for Vim, `:FZF` command will be added.
|
|||||||
" Look for files under your home directory
|
" Look for files under your home directory
|
||||||
:FZF ~
|
:FZF ~
|
||||||
|
|
||||||
" With options
|
" With fzf command-line options
|
||||||
:FZF --no-sort --reverse --inline-info /tmp
|
:FZF --reverse --info=inline /tmp
|
||||||
|
|
||||||
" Bang version starts fzf in fullscreen mode
|
" Bang version starts fzf in fullscreen mode
|
||||||
:FZF!
|
:FZF!
|
||||||
@@ -100,6 +121,7 @@ Examples~
|
|||||||
let g:fzf_layout = { 'window': '10new' }
|
let g:fzf_layout = { 'window': '10new' }
|
||||||
|
|
||||||
" Customize fzf colors to match your color scheme
|
" Customize fzf colors to match your color scheme
|
||||||
|
" - fzf#wrap translates this to a set of `--color` options
|
||||||
let g:fzf_colors =
|
let g:fzf_colors =
|
||||||
\ { 'fg': ['fg', 'Normal'],
|
\ { 'fg': ['fg', 'Normal'],
|
||||||
\ 'bg': ['bg', 'Normal'],
|
\ 'bg': ['bg', 'Normal'],
|
||||||
@@ -115,68 +137,64 @@ Examples~
|
|||||||
\ 'spinner': ['fg', 'Label'],
|
\ 'spinner': ['fg', 'Label'],
|
||||||
\ 'header': ['fg', 'Comment'] }
|
\ 'header': ['fg', 'Comment'] }
|
||||||
|
|
||||||
" Enable per-command history.
|
" Enable per-command history
|
||||||
" CTRL-N and CTRL-P will be automatically bound to next-history and
|
" - History files will be stored in the specified directory
|
||||||
" previous-history instead of down and up. If you don't like the change,
|
" - When set, CTRL-N and CTRL-P will be bound to 'next-history' and
|
||||||
" explicitly bind the keys to down and up in your $FZF_DEFAULT_OPTS.
|
" 'previous-history' instead of 'down' and 'up'.
|
||||||
let g:fzf_history_dir = '~/.local/share/fzf-history'
|
let g:fzf_history_dir = '~/.local/share/fzf-history'
|
||||||
<
|
<
|
||||||
|
|
||||||
FZF#RUN *fzf#run*
|
FZF#RUN
|
||||||
==============================================================================
|
==============================================================================
|
||||||
|
|
||||||
For more advanced uses, you can use `fzf#run([options])` function.
|
*fzf#run*
|
||||||
|
|
||||||
`fzf#run()` function is the core of Vim integration. It takes a single
|
`fzf#run()` function is the core of Vim integration. It takes a single
|
||||||
dictionary argument. At the very least, specify `sink` option to tell what it
|
dictionary argument, a spec, and starts fzf process accordingly. At the very
|
||||||
should do with the selected entry.
|
least, specify `sink` option to tell what it should do with the selected
|
||||||
|
entry.
|
||||||
>
|
>
|
||||||
call fzf#run({'sink': 'e'})
|
call fzf#run({'sink': 'e'})
|
||||||
<
|
<
|
||||||
Without `source`, fzf will use find command (or `$FZF_DEFAULT_COMMAND` if
|
We haven't specified the `source`, so this is equivalent to starting fzf on
|
||||||
defined) to list the files under the current directory. When you select one,
|
command line without standard input pipe; fzf will use find command (or
|
||||||
it will open it with `:e` command. If you want to open it in a new tab, you
|
`$FZF_DEFAULT_COMMAND` if defined) to list the files under the current
|
||||||
can pass `:tabedit` command instead as the sink.
|
directory. When you select one, it will open it with the sink, `:e` command.
|
||||||
|
If you want to open it in a new tab, you can pass `:tabedit` command instead
|
||||||
|
as the sink.
|
||||||
>
|
>
|
||||||
call fzf#run({'sink': 'tabedit'})
|
call fzf#run({'sink': 'tabedit'})
|
||||||
<
|
<
|
||||||
fzf allows you to select multiple entries with `--multi` (or `-m`) option, and
|
|
||||||
you can change its bottom-up layout with `--reverse` option. Such options can
|
|
||||||
be specified as `options`.
|
|
||||||
>
|
|
||||||
call fzf#run({'sink': 'tabedit', 'options': '--multi --reverse'})
|
|
||||||
<
|
|
||||||
Instead of using the default find command, you can use any shell command as
|
Instead of using the default find command, you can use any shell command as
|
||||||
the source. This will list the files managed by git.
|
the source. The following example will list the files managed by git. It's
|
||||||
|
equivalent to running `git ls-files | fzf` on shell.
|
||||||
>
|
>
|
||||||
call fzf#run({'source': 'git ls-files', 'sink': 'e'})
|
call fzf#run({'source': 'git ls-files', 'sink': 'e'})
|
||||||
<
|
<
|
||||||
Pass a layout option if you don't want fzf window to take up the entire
|
fzf options can be specified as `options` entry in spec dictionary.
|
||||||
screen.
|
>
|
||||||
|
call fzf#run({'sink': 'tabedit', 'options': '--multi --reverse'})
|
||||||
|
<
|
||||||
|
You can also pass a layout option if you don't want fzf window to take up the
|
||||||
|
entire screen.
|
||||||
>
|
>
|
||||||
" up / down / left / right / window are allowed
|
" up / down / left / right / window are allowed
|
||||||
call fzf#run({'source': 'git ls-files', 'sink': 'e', 'right': '40%'})
|
call fzf#run({'source': 'git ls-files', 'sink': 'e', 'left': '40%'})
|
||||||
call fzf#run({'source': 'git ls-files', 'sink': 'e', 'window': '30vnew'})
|
call fzf#run({'source': 'git ls-files', 'sink': 'e', 'window': '30vnew'})
|
||||||
<
|
<
|
||||||
`source` doesn't have to be an external shell command, you can pass a Vim
|
`source` doesn't have to be an external shell command, you can pass a Vim
|
||||||
array as the source. In the following example, we use the names of the open
|
array as the source. In the next example, we pass the names of color schemes
|
||||||
buffers as the source.
|
as the source to implement a color scheme selector.
|
||||||
>
|
|
||||||
call fzf#run({'source': map(filter(range(1, bufnr('$')), 'buflisted(v:val)'),
|
|
||||||
\ 'bufname(v:val)'),
|
|
||||||
\ 'sink': 'e', 'down': '30%'})
|
|
||||||
<
|
|
||||||
Or the names of color schemes.
|
|
||||||
>
|
>
|
||||||
call fzf#run({'source': map(split(globpath(&rtp, 'colors/*.vim')),
|
call fzf#run({'source': map(split(globpath(&rtp, 'colors/*.vim')),
|
||||||
\ 'fnamemodify(v:val, ":t:r")'),
|
\ 'fnamemodify(v:val, ":t:r")'),
|
||||||
\ 'sink': 'colo', 'left': '25%'})
|
\ 'sink': 'colo', 'left': '25%'})
|
||||||
<
|
<
|
||||||
The following table shows the available options.
|
The following table summarizes the available options.
|
||||||
|
|
||||||
---------------------------+---------------+--------------------------------------------------------------
|
---------------------------+---------------+----------------------------------------------------------------------
|
||||||
Option name | Type | Description ~
|
Option name | Type | Description ~
|
||||||
---------------------------+---------------+--------------------------------------------------------------
|
---------------------------+---------------+----------------------------------------------------------------------
|
||||||
`source` | string | External command to generate input to fzf (e.g. `find .` )
|
`source` | string | External command to generate input to fzf (e.g. `find .` )
|
||||||
`source` | list | Vim list as input to fzf
|
`source` | list | Vim list as input to fzf
|
||||||
`sink` | string | Vim command to handle the selected item (e.g. `e` , `tabe` )
|
`sink` | string | Vim command to handle the selected item (e.g. `e` , `tabe` )
|
||||||
@@ -184,74 +202,140 @@ The following table shows the available options.
|
|||||||
`sink*` | funcref | Similar to `sink` , but takes the list of output lines at once
|
`sink*` | funcref | Similar to `sink` , but takes the list of output lines at once
|
||||||
`options` | string/list | Options to fzf
|
`options` | string/list | Options to fzf
|
||||||
`dir` | string | Working directory
|
`dir` | string | Working directory
|
||||||
`up` / `down` / `left` / `right` | number/string | Use tmux pane with the given size (e.g. `20` , `50%` )
|
`up` / `down` / `left` / `right` | number/string | (Layout) Window position and size (e.g. `20` , `50%` )
|
||||||
`window` (Vim 8 / Neovim) | string | Command to open fzf window (e.g. `vertical aboveleft 30new` )
|
`window` (Vim 8 / Neovim) | string | (Layout) Command to open fzf window (e.g. `vertical aboveleft 30new` )
|
||||||
`launcher` | string | External terminal emulator to start fzf with (GVim only)
|
`window` (Vim 8 / Neovim) | dict | (Layout) Popup window settings (e.g. `{'width': 0.9, 'height': 0.6}` )
|
||||||
`launcher` | funcref | Function for generating `launcher` string (GVim only)
|
---------------------------+---------------+----------------------------------------------------------------------
|
||||||
---------------------------+---------------+--------------------------------------------------------------
|
|
||||||
|
|
||||||
`options` entry can be either a string or a list. For simple cases, string
|
`options` entry can be either a string or a list. For simple cases, string
|
||||||
should suffice, but prefer to use list type if you're concerned about escaping
|
should suffice, but prefer to use list type to avoid escaping issues.
|
||||||
issues on different platforms.
|
|
||||||
>
|
>
|
||||||
call fzf#run({'options': '--reverse --prompt "C:\\Program Files\\"'})
|
call fzf#run({'options': '--reverse --prompt "C:\\Program Files\\"'})
|
||||||
call fzf#run({'options': ['--reverse', '--prompt', 'C:\Program Files\']})
|
call fzf#run({'options': ['--reverse', '--prompt', 'C:\Program Files\']})
|
||||||
<
|
<
|
||||||
|
When `window` entry is a dictionary, fzf will start in a popup window. The
|
||||||
|
following options are allowed:
|
||||||
|
|
||||||
FZF#WRAP *fzf#wrap*
|
- Required:
|
||||||
|
- `width` [float range [0 ~ 1]]
|
||||||
|
- `height` [float range [0 ~ 1]]
|
||||||
|
- Optional:
|
||||||
|
- `yoffset` [float default 0.5 range [0 ~ 1]]
|
||||||
|
- `xoffset` [float default 0.5 range [0 ~ 1]]
|
||||||
|
- `highlight` [string default `'Comment'`]: Highlight group for border
|
||||||
|
- `border` [string default `rounded`]: Border style
|
||||||
|
- `rounded` / `sharp` / `horizontal` / `vertical` / `top` / `bottom` / `left` / `right`
|
||||||
|
|
||||||
|
|
||||||
|
FZF#WRAP
|
||||||
==============================================================================
|
==============================================================================
|
||||||
|
|
||||||
`:FZF` command provided by default knows how to handle CTRL-T, CTRL-X, and
|
*fzf#wrap*
|
||||||
CTRL-V and opens the selected file in a new tab, in a horizontal split, or in
|
|
||||||
a vertical split respectively. And these key bindings can be configured via
|
|
||||||
`g:fzf_action`. This is implemented using `--expect` option of fzf and the
|
|
||||||
smart sink function. It also understands `g:fzf_colors`, `g:fzf_layout` and
|
|
||||||
`g:fzf_history_dir`. However, `fzf#run` doesn't know about any of these
|
|
||||||
options.
|
|
||||||
|
|
||||||
By "wrapping" your options dictionary with `fzf#wrap` before passing it to
|
We have seen that several aspects of `:FZF` command can be configured with a
|
||||||
`fzf#run`, you can make your command also support the options.
|
set of global option variables; different ways to open files (`g:fzf_action`),
|
||||||
|
window position and size (`g:fzf_layout`), color palette (`g:fzf_colors`),
|
||||||
|
etc.
|
||||||
|
|
||||||
|
So how can we make our custom `fzf#run` calls also respect those variables?
|
||||||
|
Simply by "wrapping" the spec dictionary with `fzf#wrap` before passing it to
|
||||||
|
`fzf#run`.
|
||||||
|
|
||||||
|
- `fzf#wrap([name string], [spec dict], [fullscreen bool]) -> (dict)`
|
||||||
|
- All arguments are optional. Usually we only need to pass a spec
|
||||||
|
dictionary.
|
||||||
|
- `name` is for managing history files. It is ignored if `g:fzf_history_dir`
|
||||||
|
is not defined.
|
||||||
|
- `fullscreen` can be either `0` or `1` (default: 0).
|
||||||
|
|
||||||
|
`fzf#wrap` takes a spec and returns an extended version of it (also a
|
||||||
|
dictionary) with additional options for addressing global preferences. You can
|
||||||
|
examine the return value of it like so:
|
||||||
>
|
>
|
||||||
" Usage:
|
echo fzf#wrap({'source': 'ls'})
|
||||||
" fzf#wrap([name string,] [opts dict,] [fullscreen boolean])
|
<
|
||||||
|
After we "wrap" our spec, we pass it to `fzf#run`.
|
||||||
|
>
|
||||||
|
call fzf#run(fzf#wrap({'source': 'ls'}))
|
||||||
|
<
|
||||||
|
Now it supports CTRL-T, CTRL-V, and CTRL-X key bindings and it opens fzf
|
||||||
|
window according to `g:fzf_layout` setting.
|
||||||
|
|
||||||
" This command now supports CTRL-T, CTRL-V, and CTRL-X key bindings
|
To make it easier to use, let's define `LS` command.
|
||||||
" and opens fzf according to g:fzf_layout setting.
|
>
|
||||||
command! Buffers call fzf#run(fzf#wrap(
|
command! LS call fzf#run(fzf#wrap({'source': 'ls'}))
|
||||||
\ {'source': map(range(1, bufnr('$')), 'bufname(v:val)')}))
|
<
|
||||||
|
Type `:LS` and see how it works.
|
||||||
|
|
||||||
" This extends the above example to open fzf in fullscreen
|
We would like to make `:LS!` (bang version) open fzf in fullscreen, just like
|
||||||
" when the command is run with ! suffix (Buffers!)
|
`:FZF!`. Add `-bang` to command definition, and use <bang> value to set the
|
||||||
command! -bang Buffers call fzf#run(fzf#wrap(
|
last `fullscreen` argument of `fzf#wrap` (see :help <bang>).
|
||||||
\ {'source': map(range(1, bufnr('$')), 'bufname(v:val)')}, <bang>0))
|
>
|
||||||
|
" On :LS!, <bang> evaluates to '!', and '!0' becomes 1
|
||||||
" You can optionally pass the name of the command as the first argument to
|
command! -bang LS call fzf#run(fzf#wrap({'source': 'ls'}, <bang>0))
|
||||||
" fzf#wrap to make it work with g:fzf_history_dir
|
<
|
||||||
command! -bang Buffers call fzf#run(fzf#wrap('buffers',
|
Our `:LS` command will be much more useful if we can pass a directory argument
|
||||||
\ {'source': map(range(1, bufnr('$')), 'bufname(v:val)')}, <bang>0))
|
to it, so that something like `:LS /tmp` is possible.
|
||||||
|
>
|
||||||
|
command! -bang -complete=dir -nargs=* LS
|
||||||
|
\ call fzf#run(fzf#wrap({'source': 'ls', 'dir': <q-args>}, <bang>0))
|
||||||
|
<
|
||||||
|
Lastly, if you have enabled `g:fzf_history_dir`, you might want to assign a
|
||||||
|
unique name to our command and pass it as the first argument to `fzf#wrap`.
|
||||||
|
>
|
||||||
|
" The query history for this command will be stored as 'ls' inside g:fzf_history_dir.
|
||||||
|
" The name is ignored if g:fzf_history_dir is not defined.
|
||||||
|
command! -bang -complete=dir -nargs=* LS
|
||||||
|
\ call fzf#run(fzf#wrap('ls', {'source': 'ls', 'dir': <q-args>}, <bang>0))
|
||||||
<
|
<
|
||||||
|
|
||||||
FZF INSIDE TERMINAL BUFFER *fzf-inside-terminal-buffer*
|
TIPS *fzf-tips*
|
||||||
==============================================================================
|
==============================================================================
|
||||||
|
|
||||||
|
|
||||||
|
< fzf inside terminal buffer >________________________________________________~
|
||||||
|
*fzf-inside-terminal-buffer*
|
||||||
|
|
||||||
The latest versions of Vim and Neovim include builtin terminal emulator
|
The latest versions of Vim and Neovim include builtin terminal emulator
|
||||||
(`:terminal`) and fzf will start in a terminal buffer in the following cases:
|
(`:terminal`) and fzf will start in a terminal buffer in the following cases:
|
||||||
|
|
||||||
- On Neovim
|
- On Neovim
|
||||||
- On GVim
|
- On GVim
|
||||||
- On Terminal Vim with the non-default layout
|
- On Terminal Vim with a non-default layout
|
||||||
- `call fzf#run({'left': '30%'})` or `let g:fzf_layout = {'left': '30%'}`
|
- `call fzf#run({'left': '30%'})` or `let g:fzf_layout = {'left': '30%'}`
|
||||||
|
|
||||||
|
|
||||||
< Hide statusline >___________________________________________________________~
|
Starting fzf in a popup window~
|
||||||
|
*fzf-starting-fzf-in-a-popup-window*
|
||||||
|
>
|
||||||
|
" Required:
|
||||||
|
" - width [float range [0 ~ 1]]
|
||||||
|
" - height [float range [0 ~ 1]]
|
||||||
|
"
|
||||||
|
" Optional:
|
||||||
|
" - xoffset [float default 0.5 range [0 ~ 1]]
|
||||||
|
" - yoffset [float default 0.5 range [0 ~ 1]]
|
||||||
|
" - highlight [string default 'Comment']: Highlight group for border
|
||||||
|
" - border [string default 'rounded']: Border style
|
||||||
|
" - 'rounded' / 'sharp' / 'horizontal' / 'vertical' / 'top' / 'bottom' / 'left' / 'right'
|
||||||
|
let g:fzf_layout = { 'window': { 'width': 0.9, 'height': 0.6 } }
|
||||||
|
<
|
||||||
|
|
||||||
|
Hide statusline~
|
||||||
*fzf-hide-statusline*
|
*fzf-hide-statusline*
|
||||||
|
|
||||||
When fzf starts in a terminal buffer, you may want to hide the statusline of
|
When fzf starts in a terminal buffer, the file type of the buffer is set to
|
||||||
the containing buffer.
|
`fzf`. So you can set up `FileType fzf` autocmd to customize the settings of
|
||||||
|
the window.
|
||||||
|
|
||||||
|
For example, if you use the default layout (`{'down': '~40%'}`) on Neovim, you
|
||||||
|
might want to temporarily disable the statusline for a cleaner look.
|
||||||
>
|
>
|
||||||
autocmd! FileType fzf
|
if has('nvim') && !exists('g:fzf_layout')
|
||||||
autocmd FileType fzf set laststatus=0 noshowmode noruler
|
autocmd! FileType fzf
|
||||||
\| autocmd BufLeave <buffer> set laststatus=2 showmode ruler
|
autocmd FileType fzf set laststatus=0 noshowmode noruler
|
||||||
|
\| autocmd BufLeave <buffer> set laststatus=2 showmode ruler
|
||||||
|
endif
|
||||||
<
|
<
|
||||||
|
|
||||||
LICENSE *fzf-license*
|
LICENSE *fzf-license*
|
||||||
@@ -259,7 +343,7 @@ LICENSE *fzf-license*
|
|||||||
|
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2017 Junegunn Choi
|
Copyright (c) 2013-2020 Junegunn Choi
|
||||||
|
|
||||||
==============================================================================
|
==============================================================================
|
||||||
vim:tw=78:sw=2:ts=2:ft=help:norl:nowrap:
|
vim:tw=78:sw=2:ts=2:ft=help:norl:nowrap:
|
||||||
|
|||||||
22
go.mod
22
go.mod
@@ -1,19 +1,15 @@
|
|||||||
module github.com/junegunn/fzf
|
module github.com/junegunn/fzf
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/gdamore/encoding v0.0.0-20151215212835-b23993cbb635 // indirect
|
github.com/gdamore/tcell v1.3.0
|
||||||
github.com/gdamore/tcell v0.0.0-20170915061752-0a0db94084df
|
github.com/lucasb-eyer/go-colorful v1.0.3 // indirect
|
||||||
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e // indirect
|
github.com/mattn/go-isatty v0.0.12
|
||||||
github.com/jtolds/gls v4.2.1+incompatible // indirect
|
github.com/mattn/go-runewidth v0.0.8
|
||||||
github.com/lucasb-eyer/go-colorful v0.0.0-20170223221042-c900de9dbbc7 // indirect
|
github.com/mattn/go-shellwords v1.0.9
|
||||||
github.com/mattn/go-isatty v0.0.0-20160806122752-66b8e73f3f5c
|
github.com/saracen/walker v0.0.0-20191201085201-324a081bae7e
|
||||||
github.com/mattn/go-runewidth v0.0.0-20170201023540-14207d285c6c
|
golang.org/x/crypto v0.0.0-20200128174031-69ecbb4d6d5d
|
||||||
github.com/mattn/go-shellwords v1.0.3
|
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5
|
||||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d // indirect
|
golang.org/x/text v0.3.2 // indirect
|
||||||
github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c // indirect
|
|
||||||
golang.org/x/crypto v0.0.0-20170728183002-558b6879de74
|
|
||||||
golang.org/x/sys v0.0.0-20190509141414-a5b02f93d862 // indirect
|
|
||||||
golang.org/x/text v0.0.0-20170530162606-4ee4af566555 // indirect
|
|
||||||
)
|
)
|
||||||
|
|
||||||
go 1.13
|
go 1.13
|
||||||
|
|||||||
70
go.sum
70
go.sum
@@ -1,26 +1,44 @@
|
|||||||
github.com/gdamore/encoding v0.0.0-20151215212835-b23993cbb635 h1:hheUEMzaOie/wKeIc1WPa7CDVuIO5hqQxjS+dwTQEnI=
|
github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
|
||||||
github.com/gdamore/encoding v0.0.0-20151215212835-b23993cbb635/go.mod h1:yrQYJKKDTrHmbYxI7CYi+/hbdiDT2m4Hj+t0ikCjsrQ=
|
github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko=
|
||||||
github.com/gdamore/tcell v0.0.0-20170915061752-0a0db94084df h1:tLS1QD2puA1USLvkEnGfOt+Zp2ijtNIK3z2YFaIZZR4=
|
github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
|
||||||
github.com/gdamore/tcell v0.0.0-20170915061752-0a0db94084df/go.mod h1:tqyG50u7+Ctv1w5VX67kLzKcj9YXR/JSBZQq/+mLl1A=
|
github.com/gdamore/tcell v1.3.0 h1:r35w0JBADPZCVQijYebl6YMWWtHRqVEGt7kL2eBADRM=
|
||||||
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e h1:JKmoR8x90Iww1ks85zJ1lfDGgIiMDuIptTOhJq+zKyg=
|
github.com/gdamore/tcell v1.3.0/go.mod h1:Hjvr+Ofd+gLglo7RYKxxnzCBmev3BzsS67MebKS4zMM=
|
||||||
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
github.com/lucasb-eyer/go-colorful v1.0.2 h1:mCMFu6PgSozg9tDNMMK3g18oJBX7oYGrC09mS6CXfO4=
|
||||||
github.com/jtolds/gls v4.2.1+incompatible h1:fSuqC+Gmlu6l/ZYAoZzx2pyucC8Xza35fpRVWLVmUEE=
|
github.com/lucasb-eyer/go-colorful v1.0.2/go.mod h1:0MS4r+7BZKSJ5mw4/S5MPN+qHFF1fYclkSPilDOKW0s=
|
||||||
github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
github.com/lucasb-eyer/go-colorful v1.0.3 h1:QIbQXiugsb+q10B+MI+7DI1oQLdmnep86tWFlaaUAac=
|
||||||
github.com/lucasb-eyer/go-colorful v0.0.0-20170223221042-c900de9dbbc7 h1:G52I+Gk/wPD4HKvKT0Vxxp9OUPxqKs3OK6rffSPtNkA=
|
github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||||
github.com/lucasb-eyer/go-colorful v0.0.0-20170223221042-c900de9dbbc7/go.mod h1:NXg0ArsFk0Y01623LgUqoqcouGDB+PwCCQlrwrG6xJ4=
|
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
|
||||||
github.com/mattn/go-isatty v0.0.0-20160806122752-66b8e73f3f5c h1:3nKFouDdpgGUV/uerJcYWH45ZbJzX0SiVWfTgmUeTzc=
|
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||||
github.com/mattn/go-isatty v0.0.0-20160806122752-66b8e73f3f5c/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/p7Y=
|
||||||
github.com/mattn/go-runewidth v0.0.0-20170201023540-14207d285c6c h1:eFzthqtg3W6Pihj3DMTXLAF4f+ge5r5Ie5g6HLIZAF0=
|
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||||
github.com/mattn/go-runewidth v0.0.0-20170201023540-14207d285c6c/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
github.com/mattn/go-runewidth v0.0.8 h1:3tS41NlGYSmhhe/8fhGRzc+z3AYCw1Fe1WAyLuujKs0=
|
||||||
github.com/mattn/go-shellwords v1.0.3 h1:K/VxK7SZ+cvuPgFSLKi5QPI9Vr/ipOf4C1gN+ntueUk=
|
github.com/mattn/go-runewidth v0.0.8/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||||
github.com/mattn/go-shellwords v1.0.3/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o=
|
github.com/mattn/go-shellwords v1.0.9 h1:eaB5JspOwiKKcHdqcjbfe5lA9cNn/4NRRtddXJCimqk=
|
||||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
|
github.com/mattn/go-shellwords v1.0.9/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
|
||||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
github.com/saracen/walker v0.0.0-20191201085201-324a081bae7e h1:NO86zOn5ScSKW8wRbMaSIcjDZUFpWdCQQnexRqZ9h9A=
|
||||||
github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c h1:Ho+uVpkel/udgjbwB5Lktg9BtvJSh2DT0Hi6LPSyI2w=
|
github.com/saracen/walker v0.0.0-20191201085201-324a081bae7e/go.mod h1:G0Z6yVPru183i2MuRJx1DcR4dgIZtLcTdaaE/pC1BJU=
|
||||||
github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
|
||||||
golang.org/x/crypto v0.0.0-20170728183002-558b6879de74 h1:/0jf0Cx3u07Ma4EzUA6NIGuvk9hb3Br6i9V8atthkwk=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20170728183002-558b6879de74/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
golang.org/x/crypto v0.0.0-20200128174031-69ecbb4d6d5d h1:9FCpayM9Egr1baVnV1SX0H87m+XB0B8S0hAMi99X/3U=
|
||||||
golang.org/x/sys v0.0.0-20190509141414-a5b02f93d862 h1:rM0ROo5vb9AdYJi1110yjWGMej9ITfKddS89P3Fkhug=
|
golang.org/x/crypto v0.0.0-20200128174031-69ecbb4d6d5d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/sys v0.0.0-20190509141414-a5b02f93d862/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/text v0.0.0-20170530162606-4ee4af566555 h1:pjwO9HxObpgZBurDvTLFbSinaf3+cKpTAjVfiGaHwrI=
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI=
|
||||||
golang.org/x/text v0.0.0-20170530162606-4ee4af566555/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY=
|
||||||
|
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756 h1:9nuHUbU8dRnRRfj9KjWUVrJeoexdbeMjttk6Oh1rD10=
|
||||||
|
golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5 h1:LfCXLvNmTYH9kEmVgqbnsWfruoXZIrh4YBgqVHtDvw0=
|
||||||
|
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
||||||
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
||||||
|
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20191011211836-4c025a95b26e h1:1o2bDs9pCd2xFhdwqJTrCIswAeEsn4h/PCNelWpfcsI=
|
||||||
|
golang.org/x/tools v0.0.0-20191011211836-4c025a95b26e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
|||||||
4
install
4
install
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
set -u
|
set -u
|
||||||
|
|
||||||
version=0.19.0
|
version=0.21.1
|
||||||
auto_completion=
|
auto_completion=
|
||||||
key_bindings=
|
key_bindings=
|
||||||
update_config=2
|
update_config=2
|
||||||
@@ -196,6 +196,8 @@ case "$archi" in
|
|||||||
MINGW*\ *64) download fzf-$version-windows_${binary_arch:-amd64}.zip ;;
|
MINGW*\ *64) download fzf-$version-windows_${binary_arch:-amd64}.zip ;;
|
||||||
MSYS*\ *86) download fzf-$version-windows_${binary_arch:-386}.zip ;;
|
MSYS*\ *86) download fzf-$version-windows_${binary_arch:-386}.zip ;;
|
||||||
MSYS*\ *64) download fzf-$version-windows_${binary_arch:-amd64}.zip ;;
|
MSYS*\ *64) download fzf-$version-windows_${binary_arch:-amd64}.zip ;;
|
||||||
|
Windows*\ *86) download fzf-$version-windows_${binary_arch:-386}.zip ;;
|
||||||
|
Windows*\ *64) download fzf-$version-windows_${binary_arch:-amd64}.zip ;;
|
||||||
*) binary_available=0 binary_error=1 ;;
|
*) binary_available=0 binary_error=1 ;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
|
|||||||
73
install.ps1
Normal file
73
install.ps1
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
$version="0.21.1"
|
||||||
|
|
||||||
|
if ([Environment]::Is64BitProcess) {
|
||||||
|
$binary_arch="amd64"
|
||||||
|
} else {
|
||||||
|
$binary_arch="386"
|
||||||
|
}
|
||||||
|
|
||||||
|
$fzf_base=Split-Path -Parent $MyInvocation.MyCommand.Definition
|
||||||
|
|
||||||
|
function check_binary () {
|
||||||
|
Write-Host " - Checking fzf executable ... " -NoNewline
|
||||||
|
$output=cmd /c $fzf_base\bin\fzf.exe --version 2>&1
|
||||||
|
if (-not $?) {
|
||||||
|
Write-Host "Error: $output"
|
||||||
|
$binary_error="Invalid binary"
|
||||||
|
} else {
|
||||||
|
$output=(-Split $output)[0]
|
||||||
|
if ($version -ne $output) {
|
||||||
|
Write-Host "$output != $version"
|
||||||
|
$binary_error="Invalid version"
|
||||||
|
} else {
|
||||||
|
Write-Host "$output"
|
||||||
|
$binary_error=""
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Remove-Item "$fzf_base\bin\fzf.exe"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
function download {
|
||||||
|
param($file)
|
||||||
|
Write-Host "Downloading bin/fzf ..."
|
||||||
|
if ("$version" -ne "alpha") {
|
||||||
|
if (Test-Path "$fzf_base\bin\fzf.exe") {
|
||||||
|
Write-Host " - Already exists"
|
||||||
|
if (check_binary) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (-not (Test-Path "$fzf_base\bin")) {
|
||||||
|
md "$fzf_base\bin"
|
||||||
|
}
|
||||||
|
if (-not $?) {
|
||||||
|
$binary_error="Failed to create bin directory"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
cd "$fzf_base\bin"
|
||||||
|
if ("$version" -eq "alpha") {
|
||||||
|
$url="https://github.com/junegunn/fzf-bin/releases/download/alpha/$file"
|
||||||
|
} else {
|
||||||
|
$url="https://github.com/junegunn/fzf-bin/releases/download/$version/$file"
|
||||||
|
}
|
||||||
|
$temp=$env:TMP + "\fzf.zip"
|
||||||
|
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
|
||||||
|
(New-Object Net.WebClient).DownloadFile($url, $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath("$temp"))
|
||||||
|
if ($?) {
|
||||||
|
(Expand-Archive -Path $temp -DestinationPath .); (Remove-Item $temp)
|
||||||
|
} else {
|
||||||
|
$binary_error="Failed to download with powershell"
|
||||||
|
}
|
||||||
|
if (-not (Test-Path fzf.exe)) {
|
||||||
|
$binary_error="Failed to download $file"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
check_binary >$null
|
||||||
|
}
|
||||||
|
|
||||||
|
download "fzf-$version-windows_$binary_arch.zip"
|
||||||
|
|
||||||
|
Write-Host 'For more information, see: https://github.com/junegunn/fzf'
|
||||||
6
main.go
6
main.go
@@ -1,9 +1,13 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import "github.com/junegunn/fzf/src"
|
import (
|
||||||
|
"github.com/junegunn/fzf/src"
|
||||||
|
"github.com/junegunn/fzf/src/protector"
|
||||||
|
)
|
||||||
|
|
||||||
var revision string
|
var revision string
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
protector.Protect()
|
||||||
fzf.Run(fzf.ParseOptions(), revision)
|
fzf.Run(fzf.ParseOptions(), revision)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
.ig
|
.ig
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2019 Junegunn Choi
|
Copyright (c) 2013-2020 Junegunn Choi
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
@@ -21,25 +21,39 @@ 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 "Nov 2019" "fzf 0.19.0" "fzf-tmux - open fzf in tmux split pane"
|
.TH fzf-tmux 1 "Apr 2020" "fzf 0.21.1" "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
|
||||||
|
|
||||||
.SH SYNOPSIS
|
.SH SYNOPSIS
|
||||||
.B fzf-tmux [-u|-d [HEIGHT[%]]] [-l|-r [WIDTH[%]]] [--] [FZF OPTIONS]
|
.B fzf-tmux [LAYOUT OPTIONS] [--] [FZF OPTIONS]
|
||||||
|
|
||||||
.SH DESCRIPTION
|
.SH DESCRIPTION
|
||||||
fzf-tmux is a wrapper script for fzf that opens fzf in a tmux split pane. It is
|
fzf-tmux is a wrapper script for fzf that opens fzf in a tmux split pane or in
|
||||||
designed to work just like fzf except that it does not take up the whole
|
a tmux popup window. It is designed to work just like fzf except that it does
|
||||||
screen. You can safely use fzf-tmux instead of fzf in your scripts as the extra
|
not take up the whole screen. You can safely use fzf-tmux instead of fzf in
|
||||||
options will be silently ignored if you're not on tmux.
|
your scripts as the extra options will be silently ignored if you're not on
|
||||||
|
tmux.
|
||||||
|
|
||||||
.SH OPTIONS
|
.SH LAYOUT OPTIONS
|
||||||
.SS Layout
|
|
||||||
|
|
||||||
(default: \fB-d 50%\fR)
|
(default layout: \fB-d 50%\fR)
|
||||||
|
|
||||||
|
.SS Popup window
|
||||||
|
(requires tmux 3.2 or above)
|
||||||
|
.TP
|
||||||
|
.B "-p [WIDTH[%][,HEIGHT[%]]]"
|
||||||
|
.TP
|
||||||
|
.B "-w WIDTH[%]"
|
||||||
|
.TP
|
||||||
|
.B "-h WIDTH[%]"
|
||||||
|
.TP
|
||||||
|
.B "-x COL"
|
||||||
|
.TP
|
||||||
|
.B "-y ROW"
|
||||||
|
|
||||||
|
.SS Split pane
|
||||||
.TP
|
.TP
|
||||||
.B "-u [height[%]]"
|
.B "-u [height[%]]"
|
||||||
Split above (up)
|
Split above (up)
|
||||||
|
|||||||
153
man/man1/fzf.1
153
man/man1/fzf.1
@@ -1,7 +1,7 @@
|
|||||||
.ig
|
.ig
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2019 Junegunn Choi
|
Copyright (c) 2013-2020 Junegunn Choi
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
@@ -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 "Nov 2019" "fzf 0.19.0" "fzf - a command-line fuzzy finder"
|
.TH fzf 1 "Apr 2020" "fzf 0.21.1" "fzf - a command-line fuzzy finder"
|
||||||
|
|
||||||
.SH NAME
|
.SH NAME
|
||||||
fzf - a command-line fuzzy finder
|
fzf - a command-line fuzzy finder
|
||||||
@@ -130,6 +130,10 @@ the details.
|
|||||||
.B "--cycle"
|
.B "--cycle"
|
||||||
Enable cyclic scroll
|
Enable cyclic scroll
|
||||||
.TP
|
.TP
|
||||||
|
.B "--keep-right"
|
||||||
|
Keep the right end of the line visible when it's too long. Effective only when
|
||||||
|
the query string is empty.
|
||||||
|
.TP
|
||||||
.B "--no-hscroll"
|
.B "--no-hscroll"
|
||||||
Disable horizontal scroll
|
Disable horizontal scroll
|
||||||
.TP
|
.TP
|
||||||
@@ -178,8 +182,16 @@ Choose the layout (default: default)
|
|||||||
A synonym for \fB--layout=reverse\fB
|
A synonym for \fB--layout=reverse\fB
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.B "--border"
|
.BI "--border" [=STYLE]
|
||||||
Draw border above and below the finder
|
Draw border around the finder
|
||||||
|
|
||||||
|
.br
|
||||||
|
.BR rounded " Border with rounded corners (default)"
|
||||||
|
.br
|
||||||
|
.BR sharp " Border with sharp corners"
|
||||||
|
.br
|
||||||
|
.BR horizontal " Horizontal lines above and below the finder"
|
||||||
|
.br
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.B "--no-unicode"
|
.B "--no-unicode"
|
||||||
@@ -231,6 +243,12 @@ A synonym for \fB--info=hidden\fB
|
|||||||
.BI "--prompt=" "STR"
|
.BI "--prompt=" "STR"
|
||||||
Input prompt (default: '> ')
|
Input prompt (default: '> ')
|
||||||
.TP
|
.TP
|
||||||
|
.BI "--pointer=" "STR"
|
||||||
|
Pointer to the current line (default: '>')
|
||||||
|
.TP
|
||||||
|
.BI "--marker=" "STR"
|
||||||
|
Multi-select marker (default: '>')
|
||||||
|
.TP
|
||||||
.BI "--header=" "STR"
|
.BI "--header=" "STR"
|
||||||
The given string will be printed as the sticky header. The lines are displayed
|
The given string will be printed as the sticky header. The lines are displayed
|
||||||
in the given order from top to bottom regardless of \fB--layout\fR option, and
|
in the given order from top to bottom regardless of \fB--layout\fR option, and
|
||||||
@@ -262,23 +280,25 @@ format.
|
|||||||
\fBdark \fRColor scheme for dark 256-color terminal
|
\fBdark \fRColor scheme for dark 256-color terminal
|
||||||
\fBlight \fRColor scheme for light 256-color terminal
|
\fBlight \fRColor scheme for light 256-color terminal
|
||||||
\fB16 \fRColor scheme for 16-color terminal
|
\fB16 \fRColor scheme for 16-color terminal
|
||||||
\fBbw \fRNo colors
|
\fBbw \fRNo colors (equivalent to \fB--no-color\fR)
|
||||||
|
|
||||||
.B COLOR:
|
.B COLOR:
|
||||||
\fBfg \fRText
|
\fBfg \fRText
|
||||||
\fBbg \fRBackground
|
\fBbg \fRBackground
|
||||||
\fBhl \fRHighlighted substrings
|
\fBpreview-fg \fRPreview window text
|
||||||
\fBfg+ \fRText (current line)
|
\fBpreview-bg \fRPreview window background
|
||||||
\fBbg+ \fRBackground (current line)
|
\fBhl \fRHighlighted substrings
|
||||||
\fBgutter \fRGutter on the left (defaults to \fBbg+\fR)
|
\fBfg+ \fRText (current line)
|
||||||
\fBhl+ \fRHighlighted substrings (current line)
|
\fBbg+ \fRBackground (current line)
|
||||||
\fBinfo \fRInfo
|
\fBgutter \fRGutter on the left (defaults to \fBbg+\fR)
|
||||||
\fBborder \fRBorder of the preview window and horizontal separators (\fB--border\fR)
|
\fBhl+ \fRHighlighted substrings (current line)
|
||||||
\fBprompt \fRPrompt
|
\fBinfo \fRInfo line (match counters)
|
||||||
\fBpointer \fRPointer to the current line
|
\fBborder \fRBorder around the window (\fB--border\fR and \fB--preview\fR)
|
||||||
\fBmarker \fRMulti-select marker
|
\fBprompt \fRPrompt
|
||||||
\fBspinner \fRStreaming input indicator
|
\fBpointer \fRPointer to the current line
|
||||||
\fBheader \fRHeader
|
\fBmarker \fRMulti-select marker
|
||||||
|
\fBspinner \fRStreaming input indicator
|
||||||
|
\fBheader \fRHeader
|
||||||
|
|
||||||
.B EXAMPLES:
|
.B EXAMPLES:
|
||||||
|
|
||||||
@@ -592,6 +612,8 @@ e.g.
|
|||||||
.br
|
.br
|
||||||
\fIend\fR
|
\fIend\fR
|
||||||
.br
|
.br
|
||||||
|
\fIinsert\fR
|
||||||
|
.br
|
||||||
\fIpgup\fR (\fIpage-up\fR)
|
\fIpgup\fR (\fIpage-up\fR)
|
||||||
.br
|
.br
|
||||||
\fIpgdn\fR (\fIpage-down\fR)
|
\fIpgdn\fR (\fIpage-down\fR)
|
||||||
@@ -623,60 +645,63 @@ or any single character
|
|||||||
.SS AVAILABLE ACTIONS:
|
.SS AVAILABLE ACTIONS:
|
||||||
A key or an event can be bound to one or more of the following actions.
|
A key or an event can be bound to one or more of the following actions.
|
||||||
|
|
||||||
\fBACTION: DEFAULT BINDINGS (NOTES):
|
\fBACTION: DEFAULT BINDINGS (NOTES):
|
||||||
\fBabort\fR \fIctrl-c ctrl-g ctrl-q esc\fR
|
\fBabort\fR \fIctrl-c ctrl-g ctrl-q esc\fR
|
||||||
\fBaccept\fR \fIenter double-click\fR
|
\fBaccept\fR \fIenter double-click\fR
|
||||||
\fBaccept-non-empty\fR (same as \fBaccept\fR except that it prevents fzf from exiting without selection)
|
\fBaccept-non-empty\fR (same as \fBaccept\fR except that it prevents fzf from exiting without selection)
|
||||||
\fBbackward-char\fR \fIctrl-b left\fR
|
\fBbackward-char\fR \fIctrl-b left\fR
|
||||||
\fBbackward-delete-char\fR \fIctrl-h bspace\fR
|
\fBbackward-delete-char\fR \fIctrl-h bspace\fR
|
||||||
\fBbackward-kill-word\fR \fIalt-bs\fR
|
\fBbackward-delete-char/eof\fR (same as \fBbackward-delete-char\fR except aborts fzf if query is empty)
|
||||||
\fBbackward-word\fR \fIalt-b shift-left\fR
|
\fBbackward-kill-word\fR \fIalt-bs\fR
|
||||||
\fBbeginning-of-line\fR \fIctrl-a home\fR
|
\fBbackward-word\fR \fIalt-b shift-left\fR
|
||||||
\fBcancel\fR (clears query string if not empty, aborts fzf otherwise)
|
\fBbeginning-of-line\fR \fIctrl-a home\fR
|
||||||
\fBclear-screen\fR \fIctrl-l\fR
|
\fBcancel\fR (clear query string if not empty, abort fzf otherwise)
|
||||||
\fBdelete-char\fR \fIdel\fR
|
\fBclear-screen\fR \fIctrl-l\fR
|
||||||
\fBdelete-char/eof\fR \fIctrl-d\fR
|
\fBclear-selection\fR (clear multi-selection)
|
||||||
\fBdeselect-all\fR
|
\fBclear-query\fR (clear query string)
|
||||||
\fBdown\fR \fIctrl-j ctrl-n down\fR
|
\fBdelete-char\fR \fIdel\fR
|
||||||
\fBend-of-line\fR \fIctrl-e end\fR
|
\fBdelete-char/eof\fR \fIctrl-d\fR (same as \fBdelete-char\fR except aborts fzf if query is empty)
|
||||||
\fBexecute(...)\fR (see below for the details)
|
\fBdeselect-all\fR (deselect all matches)
|
||||||
\fBexecute-silent(...)\fR (see below for the details)
|
\fBdown\fR \fIctrl-j ctrl-n down\fR
|
||||||
\fRexecute-multi(...)\fR (deprecated in favor of \fB{+}\fR expression)
|
\fBend-of-line\fR \fIctrl-e end\fR
|
||||||
\fBforward-char\fR \fIctrl-f right\fR
|
\fBexecute(...)\fR (see below for the details)
|
||||||
\fBforward-word\fR \fIalt-f shift-right\fR
|
\fBexecute-silent(...)\fR (see below for the details)
|
||||||
|
\fRexecute-multi(...)\fR (deprecated in favor of \fB{+}\fR expression)
|
||||||
|
\fBforward-char\fR \fIctrl-f right\fR
|
||||||
|
\fBforward-word\fR \fIalt-f shift-right\fR
|
||||||
\fBignore\fR
|
\fBignore\fR
|
||||||
\fBjump\fR (EasyMotion-like 2-keystroke movement)
|
\fBjump\fR (EasyMotion-like 2-keystroke movement)
|
||||||
\fBjump-accept\fR (jump and accept)
|
\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
|
||||||
\fBhalf-page-down\fR
|
\fBhalf-page-down\fR
|
||||||
\fBhalf-page-up\fR
|
\fBhalf-page-up\fR
|
||||||
\fBpreview-down\fR \fIshift-down\fR
|
\fBpreview-down\fR \fIshift-down\fR
|
||||||
\fBpreview-up\fR \fIshift-up\fR
|
\fBpreview-up\fR \fIshift-up\fR
|
||||||
\fBpreview-page-down\fR
|
\fBpreview-page-down\fR
|
||||||
\fBpreview-page-up\fR
|
\fBpreview-page-up\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)
|
\fBprint-query\fR (print query and exit)
|
||||||
\fBreload(...)\fR (see below for the details)
|
\fBreload(...)\fR (see below for the details)
|
||||||
\fBreplace-query\fR (replace query string with the current selection)
|
\fBreplace-query\fR (replace query string with the current selection)
|
||||||
\fBselect-all\fR
|
\fBselect-all\fR (select all matches)
|
||||||
\fBtoggle\fR (\fIright-click\fR)
|
\fBtoggle\fR (\fIright-click\fR)
|
||||||
\fBtoggle-all\fR
|
\fBtoggle-all\fR (toggle all matches)
|
||||||
\fBtoggle+down\fR \fIctrl-i (tab)\fR
|
\fBtoggle+down\fR \fIctrl-i (tab)\fR
|
||||||
\fBtoggle-in\fR (\fB--layout=reverse*\fR ? \fBtoggle+up\fR : \fBtoggle+down\fR)
|
\fBtoggle-in\fR (\fB--layout=reverse*\fR ? \fBtoggle+up\fR : \fBtoggle+down\fR)
|
||||||
\fBtoggle-out\fR (\fB--layout=reverse*\fR ? \fBtoggle+down\fR : \fBtoggle+up\fR)
|
\fBtoggle-out\fR (\fB--layout=reverse*\fR ? \fBtoggle+down\fR : \fBtoggle+up\fR)
|
||||||
\fBtoggle-preview\fR
|
\fBtoggle-preview\fR
|
||||||
\fBtoggle-preview-wrap\fR
|
\fBtoggle-preview-wrap\fR
|
||||||
\fBtoggle-sort\fR
|
\fBtoggle-sort\fR
|
||||||
\fBtoggle+up\fR \fIbtab (shift-tab)\fR
|
\fBtoggle+up\fR \fIbtab (shift-tab)\fR
|
||||||
\fBtop\fR (move to the top result)
|
\fBtop\fR (move to the top result)
|
||||||
\fBunix-line-discard\fR \fIctrl-u\fR
|
\fBunix-line-discard\fR \fIctrl-u\fR
|
||||||
\fBunix-word-rubout\fR \fIctrl-w\fR
|
\fBunix-word-rubout\fR \fIctrl-w\fR
|
||||||
\fBup\fR \fIctrl-k ctrl-p up\fR
|
\fBup\fR \fIctrl-k ctrl-p up\fR
|
||||||
\fByank\fR \fIctrl-y\fR
|
\fByank\fR \fIctrl-y\fR
|
||||||
|
|
||||||
.SS ACTION COMPOSITION
|
.SS ACTION COMPOSITION
|
||||||
|
|
||||||
|
|||||||
212
plugin/fzf.vim
212
plugin/fzf.vim
@@ -49,18 +49,23 @@ if s:is_win
|
|||||||
|
|
||||||
" Use utf-8 for fzf.vim commands
|
" Use utf-8 for fzf.vim commands
|
||||||
" Return array of shell commands for cmd.exe
|
" Return array of shell commands for cmd.exe
|
||||||
|
function! s:enc_to_cp(str)
|
||||||
|
if !has('iconv')
|
||||||
|
return a:str
|
||||||
|
endif
|
||||||
|
if !exists('s:codepage')
|
||||||
|
let s:codepage = libcallnr('kernel32.dll', 'GetACP', 0)
|
||||||
|
endif
|
||||||
|
return iconv(a:str, &encoding, 'cp'.s:codepage)
|
||||||
|
endfunction
|
||||||
function! s:wrap_cmds(cmds)
|
function! s:wrap_cmds(cmds)
|
||||||
let use_chcp = executable('sed')
|
|
||||||
return map([
|
return map([
|
||||||
\ '@echo off',
|
\ '@echo off',
|
||||||
\ 'setlocal enabledelayedexpansion']
|
\ 'setlocal enabledelayedexpansion']
|
||||||
\ + (use_chcp ? [
|
\ + (has('gui_running') ? ['set TERM= > nul'] : [])
|
||||||
\ 'for /f "usebackq" %%a in (`chcp ^| sed "s/[^0-9]//gp"`) do set origchcp=%%a',
|
|
||||||
\ 'chcp 65001 > nul'] : [])
|
|
||||||
\ + (type(a:cmds) == type([]) ? a:cmds : [a:cmds])
|
\ + (type(a:cmds) == type([]) ? a:cmds : [a:cmds])
|
||||||
\ + (use_chcp ? ['chcp !origchcp! > nul'] : [])
|
|
||||||
\ + ['endlocal'],
|
\ + ['endlocal'],
|
||||||
\ 'v:val."\r"')
|
\ '<SID>enc_to_cp(v:val."\r")')
|
||||||
endfunction
|
endfunction
|
||||||
else
|
else
|
||||||
let s:term_marker = ";#FZF"
|
let s:term_marker = ";#FZF"
|
||||||
@@ -72,6 +77,10 @@ else
|
|||||||
function! s:wrap_cmds(cmds)
|
function! s:wrap_cmds(cmds)
|
||||||
return a:cmds
|
return a:cmds
|
||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
|
function! s:enc_to_cp(str)
|
||||||
|
return a:str
|
||||||
|
endfunction
|
||||||
endif
|
endif
|
||||||
|
|
||||||
function! s:shellesc_cmd(arg)
|
function! s:shellesc_cmd(arg)
|
||||||
@@ -83,7 +92,7 @@ function! s:shellesc_cmd(arg)
|
|||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
function! fzf#shellescape(arg, ...)
|
function! fzf#shellescape(arg, ...)
|
||||||
let shell = get(a:000, 0, &shell)
|
let shell = get(a:000, 0, s:is_win ? 'cmd.exe' : 'sh')
|
||||||
if shell =~# 'cmd.exe$'
|
if shell =~# 'cmd.exe$'
|
||||||
return s:shellesc_cmd(a:arg)
|
return s:shellesc_cmd(a:arg)
|
||||||
endif
|
endif
|
||||||
@@ -110,30 +119,41 @@ let s:default_layout = { 'down': '~40%' }
|
|||||||
let s:layout_keys = ['window', 'up', 'down', 'left', 'right']
|
let s:layout_keys = ['window', 'up', 'down', 'left', 'right']
|
||||||
let s:fzf_go = s:base_dir.'/bin/fzf'
|
let s:fzf_go = s:base_dir.'/bin/fzf'
|
||||||
let s:fzf_tmux = s:base_dir.'/bin/fzf-tmux'
|
let s:fzf_tmux = s:base_dir.'/bin/fzf-tmux'
|
||||||
let s:install = s:base_dir.'/install'
|
|
||||||
let s:installed = 0
|
|
||||||
|
|
||||||
let s:cpo_save = &cpo
|
let s:cpo_save = &cpo
|
||||||
set cpo&vim
|
set cpo&vim
|
||||||
|
|
||||||
|
function! fzf#install()
|
||||||
|
if s:is_win && !has('win32unix')
|
||||||
|
let script = s:base_dir.'/install.ps1'
|
||||||
|
if !filereadable(script)
|
||||||
|
throw script.' not found'
|
||||||
|
endif
|
||||||
|
let script = 'powershell -ExecutionPolicy Bypass -file ' . script
|
||||||
|
else
|
||||||
|
let script = s:base_dir.'/install'
|
||||||
|
if !executable(script)
|
||||||
|
throw script.' not found'
|
||||||
|
endif
|
||||||
|
let script .= ' --bin'
|
||||||
|
endif
|
||||||
|
|
||||||
|
call s:warn('Running fzf installer ...')
|
||||||
|
call system(script)
|
||||||
|
if v:shell_error
|
||||||
|
throw 'Failed to download fzf: '.script
|
||||||
|
endif
|
||||||
|
endfunction
|
||||||
|
|
||||||
function! s:fzf_exec()
|
function! s:fzf_exec()
|
||||||
if !exists('s:exec')
|
if !exists('s:exec')
|
||||||
if executable(s:fzf_go)
|
if executable(s:fzf_go)
|
||||||
let s:exec = s:fzf_go
|
let s:exec = s:fzf_go
|
||||||
elseif executable('fzf')
|
elseif executable('fzf')
|
||||||
let s:exec = 'fzf'
|
let s:exec = 'fzf'
|
||||||
elseif s:is_win && !has('win32unix')
|
elseif input('fzf executable not found. Download binary? (y/n) ') =~? '^y'
|
||||||
call s:warn('fzf executable not found.')
|
|
||||||
call s:warn('Download fzf binary for Windows from https://github.com/junegunn/fzf-bin/releases/')
|
|
||||||
call s:warn('and place it as '.s:base_dir.'\bin\fzf.exe')
|
|
||||||
throw 'fzf executable not found'
|
|
||||||
elseif !s:installed && executable(s:install) &&
|
|
||||||
\ input('fzf executable not found. Download binary? (y/n) ') =~? '^y'
|
|
||||||
redraw
|
redraw
|
||||||
echo
|
call fzf#install()
|
||||||
call s:warn('Downloading fzf binary. Please wait ...')
|
|
||||||
let s:installed = 1
|
|
||||||
call system(s:install.' --bin')
|
|
||||||
return s:fzf_exec()
|
return s:fzf_exec()
|
||||||
else
|
else
|
||||||
redraw
|
redraw
|
||||||
@@ -144,7 +164,7 @@ function! s:fzf_exec()
|
|||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
function! s:tmux_enabled()
|
function! s:tmux_enabled()
|
||||||
if has('gui_running')
|
if has('gui_running') || !exists('$TMUX')
|
||||||
return 0
|
return 0
|
||||||
endif
|
endif
|
||||||
|
|
||||||
@@ -153,10 +173,16 @@ function! s:tmux_enabled()
|
|||||||
endif
|
endif
|
||||||
|
|
||||||
let s:tmux = 0
|
let s:tmux = 0
|
||||||
if exists('$TMUX') && executable(s:fzf_tmux)
|
if !executable(s:fzf_tmux)
|
||||||
let output = system('tmux -V')
|
if executable('fzf-tmux')
|
||||||
let s:tmux = !v:shell_error && output >= 'tmux 1.7'
|
let s:fzf_tmux = 'fzf-tmux'
|
||||||
|
else
|
||||||
|
return 0
|
||||||
|
endif
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
let output = system('tmux -V')
|
||||||
|
let s:tmux = !v:shell_error && output >= 'tmux 1.7'
|
||||||
return s:tmux
|
return s:tmux
|
||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
@@ -247,7 +273,7 @@ function! s:common_sink(action, lines) abort
|
|||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
function! s:get_color(attr, ...)
|
function! s:get_color(attr, ...)
|
||||||
let gui = has('termguicolors') && &termguicolors
|
let gui = !s:is_win && !has('win32unix') && has('termguicolors') && &termguicolors
|
||||||
let fam = gui ? 'gui' : 'cterm'
|
let fam = gui ? 'gui' : 'cterm'
|
||||||
let pat = gui ? '^#[a-f0-9]\+' : '^[0-9]\+$'
|
let pat = gui ? '^#[a-f0-9]\+' : '^[0-9]\+$'
|
||||||
for group in a:000
|
for group in a:000
|
||||||
@@ -342,19 +368,21 @@ function! fzf#wrap(...)
|
|||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
function! s:use_sh()
|
function! s:use_sh()
|
||||||
let [shell, shellslash] = [&shell, &shellslash]
|
let [shell, shellslash, shellcmdflag, shellxquote] = [&shell, &shellslash, &shellcmdflag, &shellxquote]
|
||||||
if s:is_win
|
if s:is_win
|
||||||
set shell=cmd.exe
|
set shell=cmd.exe
|
||||||
set noshellslash
|
set noshellslash
|
||||||
|
let &shellcmdflag = has('nvim') ? '/s /c' : '/c'
|
||||||
|
let &shellxquote = has('nvim') ? '"' : '('
|
||||||
else
|
else
|
||||||
set shell=sh
|
set shell=sh
|
||||||
endif
|
endif
|
||||||
return [shell, shellslash]
|
return [shell, shellslash, shellcmdflag, shellxquote]
|
||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
function! fzf#run(...) abort
|
function! fzf#run(...) abort
|
||||||
try
|
try
|
||||||
let [shell, shellslash] = s:use_sh()
|
let [shell, shellslash, shellcmdflag, shellxquote] = s:use_sh()
|
||||||
|
|
||||||
let dict = exists('a:1') ? s:upgrade(a:1) : {}
|
let dict = exists('a:1') ? s:upgrade(a:1) : {}
|
||||||
let temps = { 'result': s:fzf_tempname() }
|
let temps = { 'result': s:fzf_tempname() }
|
||||||
@@ -372,12 +400,6 @@ try
|
|||||||
let dict.dir = fnamemodify(dict.dir, ':p')
|
let dict.dir = fnamemodify(dict.dir, ':p')
|
||||||
endif
|
endif
|
||||||
|
|
||||||
if !has_key(dict, 'source') && !empty($FZF_DEFAULT_COMMAND) && !s:is_win
|
|
||||||
let temps.source = s:fzf_tempname()
|
|
||||||
call writefile(s:wrap_cmds(split($FZF_DEFAULT_COMMAND, "\n")), temps.source)
|
|
||||||
let dict.source = (empty($SHELL) ? &shell : $SHELL).' '.fzf#shellescape(temps.source)
|
|
||||||
endif
|
|
||||||
|
|
||||||
if has_key(dict, 'source')
|
if has_key(dict, 'source')
|
||||||
let source = dict.source
|
let source = dict.source
|
||||||
let type = type(source)
|
let type = type(source)
|
||||||
@@ -385,7 +407,7 @@ try
|
|||||||
let prefix = '( '.source.' )|'
|
let prefix = '( '.source.' )|'
|
||||||
elseif type == 3
|
elseif type == 3
|
||||||
let temps.input = s:fzf_tempname()
|
let temps.input = s:fzf_tempname()
|
||||||
call writefile(source, temps.input)
|
call writefile(map(source, '<SID>enc_to_cp(v:val)'), temps.input)
|
||||||
let prefix = (s:is_win ? 'type ' : 'cat ').fzf#shellescape(temps.input).'|'
|
let prefix = (s:is_win ? 'type ' : 'cat ').fzf#shellescape(temps.input).'|'
|
||||||
else
|
else
|
||||||
throw 'Invalid source type'
|
throw 'Invalid source type'
|
||||||
@@ -424,7 +446,7 @@ try
|
|||||||
call s:callback(dict, lines)
|
call s:callback(dict, lines)
|
||||||
return lines
|
return lines
|
||||||
finally
|
finally
|
||||||
let [&shell, &shellslash] = [shell, shellslash]
|
let [&shell, &shellslash, &shellcmdflag, &shellxquote] = [shell, shellslash, shellcmdflag, shellxquote]
|
||||||
endtry
|
endtry
|
||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
@@ -620,14 +642,16 @@ function! s:calc_size(max, val, dict)
|
|||||||
endif
|
endif
|
||||||
|
|
||||||
let opts = $FZF_DEFAULT_OPTS.' '.s:evaluate_opts(get(a:dict, 'options', ''))
|
let opts = $FZF_DEFAULT_OPTS.' '.s:evaluate_opts(get(a:dict, 'options', ''))
|
||||||
let margin = stridx(opts, '--inline-info') > stridx(opts, '--no-inline-info') ? 1 : 2
|
let margin = match(opts, '--inline-info\|--info[^-]\{-}inline') > match(opts, '--no-inline-info\|--info[^-]\{-}\(default\|hidden\)') ? 1 : 2
|
||||||
let margin += stridx(opts, '--border') > stridx(opts, '--no-border') ? 2 : 0
|
let margin += stridx(opts, '--border') > stridx(opts, '--no-border') ? 2 : 0
|
||||||
let margin += stridx(opts, '--header') > stridx(opts, '--no-header')
|
if stridx(opts, '--header') > stridx(opts, '--no-header')
|
||||||
|
let margin += len(split(opts, "\n"))
|
||||||
|
endif
|
||||||
return srcsz >= 0 ? min([srcsz + margin, size]) : size
|
return srcsz >= 0 ? min([srcsz + margin, size]) : size
|
||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
function! s:getpos()
|
function! s:getpos()
|
||||||
return {'tab': tabpagenr(), 'win': winnr(), 'cnt': winnr('$'), 'tcnt': tabpagenr('$')}
|
return {'tab': tabpagenr(), 'win': winnr(), 'winid': win_getid(), 'cnt': winnr('$'), 'tcnt': tabpagenr('$')}
|
||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
function! s:split(dict)
|
function! s:split(dict)
|
||||||
@@ -639,7 +663,14 @@ function! s:split(dict)
|
|||||||
let ppos = s:getpos()
|
let ppos = s:getpos()
|
||||||
try
|
try
|
||||||
if s:present(a:dict, 'window')
|
if s:present(a:dict, 'window')
|
||||||
execute 'keepalt' a:dict.window
|
if type(a:dict.window) == type({})
|
||||||
|
if !has('nvim') && !has('patch-8.2.191')
|
||||||
|
throw 'Vim 8.2.191 or later is required for pop-up window'
|
||||||
|
end
|
||||||
|
call s:popup(a:dict.window)
|
||||||
|
else
|
||||||
|
execute 'keepalt' a:dict.window
|
||||||
|
endif
|
||||||
elseif !s:splittable(a:dict)
|
elseif !s:splittable(a:dict)
|
||||||
execute (tabpagenr()-1).'tabnew'
|
execute (tabpagenr()-1).'tabnew'
|
||||||
else
|
else
|
||||||
@@ -696,8 +727,8 @@ function! s:execute_term(dict, command, temps) abort
|
|||||||
" there's no other listed buffer (nvim +'set nobuflisted')
|
" there's no other listed buffer (nvim +'set nobuflisted')
|
||||||
close
|
close
|
||||||
endif
|
endif
|
||||||
execute 'tabnext' self.ppos.tab
|
silent! execute 'tabnext' self.ppos.tab
|
||||||
execute self.ppos.win.'wincmd w'
|
silent! execute self.ppos.win.'wincmd w'
|
||||||
endif
|
endif
|
||||||
|
|
||||||
if bufexists(self.buf)
|
if bufexists(self.buf)
|
||||||
@@ -731,6 +762,9 @@ function! s:execute_term(dict, command, temps) abort
|
|||||||
if has('nvim')
|
if has('nvim')
|
||||||
call termopen(command, fzf)
|
call termopen(command, fzf)
|
||||||
else
|
else
|
||||||
|
if !len(&bufhidden)
|
||||||
|
setlocal bufhidden=hide
|
||||||
|
endif
|
||||||
let fzf.buf = term_start([&shell, &shellcmdflag, command], {'curwin': 1, 'exit_cb': function(fzf.on_exit)})
|
let fzf.buf = term_start([&shell, &shellcmdflag, command], {'curwin': 1, 'exit_cb': function(fzf.on_exit)})
|
||||||
if !has('patch-8.0.1261') && !has('nvim') && !s:is_win
|
if !has('patch-8.0.1261') && !has('nvim') && !s:is_win
|
||||||
call term_wait(fzf.buf, 20)
|
call term_wait(fzf.buf, 20)
|
||||||
@@ -787,6 +821,100 @@ function! s:callback(dict, lines) abort
|
|||||||
endif
|
endif
|
||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
|
if has('nvim')
|
||||||
|
function s:create_popup(hl, opts) abort
|
||||||
|
let buf = nvim_create_buf(v:false, v:true)
|
||||||
|
let opts = extend({'relative': 'editor', 'style': 'minimal'}, a:opts)
|
||||||
|
let border = has_key(opts, 'border') ? remove(opts, 'border') : []
|
||||||
|
let win = nvim_open_win(buf, v:true, opts)
|
||||||
|
call setwinvar(win, '&winhighlight', 'NormalFloat:'..a:hl)
|
||||||
|
call setwinvar(win, '&colorcolumn', '')
|
||||||
|
if !empty(border)
|
||||||
|
call nvim_buf_set_lines(buf, 0, -1, v:true, border)
|
||||||
|
endif
|
||||||
|
return buf
|
||||||
|
endfunction
|
||||||
|
else
|
||||||
|
function! s:create_popup(hl, opts) abort
|
||||||
|
let is_frame = has_key(a:opts, 'border')
|
||||||
|
let buf = is_frame ? '' : term_start(&shell, #{hidden: 1, term_finish: 'close'})
|
||||||
|
let id = popup_create(buf, #{
|
||||||
|
\ line: a:opts.row,
|
||||||
|
\ col: a:opts.col,
|
||||||
|
\ minwidth: a:opts.width,
|
||||||
|
\ minheight: a:opts.height,
|
||||||
|
\ zindex: 50 - is_frame,
|
||||||
|
\ })
|
||||||
|
|
||||||
|
if is_frame
|
||||||
|
call setwinvar(id, '&wincolor', a:hl)
|
||||||
|
call setbufline(winbufnr(id), 1, a:opts.border)
|
||||||
|
execute 'autocmd BufWipeout * ++once call popup_close('..id..')'
|
||||||
|
else
|
||||||
|
execute 'autocmd BufWipeout * ++once call term_sendkeys('..buf..', "exit\<CR>")'
|
||||||
|
endif
|
||||||
|
return winbufnr(id)
|
||||||
|
endfunction
|
||||||
|
endif
|
||||||
|
|
||||||
|
function! s:popup(opts) abort
|
||||||
|
" Support ambiwidth == 'double'
|
||||||
|
let ambidouble = &ambiwidth == 'double' ? 2 : 1
|
||||||
|
|
||||||
|
" Size and position
|
||||||
|
let width = min([max([0, float2nr(&columns * a:opts.width)]), &columns])
|
||||||
|
let width += width % ambidouble
|
||||||
|
let height = min([max([0, float2nr(&lines * a:opts.height)]), &lines - has('nvim')])
|
||||||
|
let row = float2nr(get(a:opts, 'yoffset', 0.5) * (&lines - height))
|
||||||
|
let col = float2nr(get(a:opts, 'xoffset', 0.5) * (&columns - width))
|
||||||
|
|
||||||
|
" Managing the differences
|
||||||
|
let row = min([max([0, row]), &lines - has('nvim') - height])
|
||||||
|
let col = min([max([0, col]), &columns - width])
|
||||||
|
let row += !has('nvim')
|
||||||
|
let col += !has('nvim')
|
||||||
|
|
||||||
|
" Border style
|
||||||
|
let style = tolower(get(a:opts, 'border', 'rounded'))
|
||||||
|
if !has_key(a:opts, 'border') && !get(a:opts, 'rounded', 1)
|
||||||
|
let style = 'sharp'
|
||||||
|
endif
|
||||||
|
|
||||||
|
if style =~ 'vertical\|left\|right'
|
||||||
|
let mid = style == 'vertical' ? '│' .. repeat(' ', width - 2 * ambidouble) .. '│' :
|
||||||
|
\ style == 'left' ? '│' .. repeat(' ', width - 1 * ambidouble)
|
||||||
|
\ : repeat(' ', width - 1 * ambidouble) .. '│'
|
||||||
|
let border = repeat([mid], height)
|
||||||
|
let shift = { 'row': 0, 'col': style == 'right' ? 0 : 2, 'width': style == 'vertical' ? -4 : -2, 'height': 0 }
|
||||||
|
elseif style =~ 'horizontal\|top\|bottom'
|
||||||
|
let hor = repeat('─', width / ambidouble)
|
||||||
|
let mid = repeat(' ', width)
|
||||||
|
let border = style == 'horizontal' ? [hor] + repeat([mid], height - 2) + [hor] :
|
||||||
|
\ style == 'top' ? [hor] + repeat([mid], height - 1)
|
||||||
|
\ : repeat([mid], height - 1) + [hor]
|
||||||
|
let shift = { 'row': style == 'bottom' ? 0 : 1, 'col': 0, 'width': 0, 'height': style == 'horizontal' ? -2 : -1 }
|
||||||
|
else
|
||||||
|
let edges = style == 'sharp' ? ['┌', '┐', '└', '┘'] : ['╭', '╮', '╰', '╯']
|
||||||
|
let bar = repeat('─', width / ambidouble - 2)
|
||||||
|
let top = edges[0] .. bar .. edges[1]
|
||||||
|
let mid = '│' .. repeat(' ', width - 2 * ambidouble) .. '│'
|
||||||
|
let bot = edges[2] .. bar .. edges[3]
|
||||||
|
let border = [top] + repeat([mid], height - 2) + [bot]
|
||||||
|
let shift = { 'row': 1, 'col': 2, 'width': -4, 'height': -2 }
|
||||||
|
endif
|
||||||
|
|
||||||
|
let highlight = get(a:opts, 'highlight', 'Comment')
|
||||||
|
let frame = s:create_popup(highlight, {
|
||||||
|
\ 'row': row, 'col': col, 'width': width, 'height': height, 'border': border
|
||||||
|
\ })
|
||||||
|
call s:create_popup('Normal', {
|
||||||
|
\ 'row': row + shift.row, 'col': col + shift.col, 'width': width + shift.width, 'height': height + shift.height
|
||||||
|
\ })
|
||||||
|
if has('nvim')
|
||||||
|
execute 'autocmd BufWipeout <buffer> bwipeout '..frame
|
||||||
|
endif
|
||||||
|
endfunction
|
||||||
|
|
||||||
let s:default_action = {
|
let s:default_action = {
|
||||||
\ 'ctrl-t': 'tab split',
|
\ 'ctrl-t': 'tab split',
|
||||||
\ 'ctrl-x': 'split',
|
\ 'ctrl-x': 'split',
|
||||||
|
|||||||
@@ -2,10 +2,10 @@
|
|||||||
# / __/___ / __/
|
# / __/___ / __/
|
||||||
# / /_/_ / / /_
|
# / /_/_ / / /_
|
||||||
# / __/ / /_/ __/
|
# / __/ / /_/ __/
|
||||||
# /_/ /___/_/-completion.bash
|
# /_/ /___/_/ completion.bash
|
||||||
#
|
#
|
||||||
# - $FZF_TMUX (default: 0)
|
# - $FZF_TMUX (default: 0)
|
||||||
# - $FZF_TMUX_HEIGHT (default: '40%')
|
# - $FZF_TMUX_OPTS (default: empty)
|
||||||
# - $FZF_COMPLETION_TRIGGER (default: '**')
|
# - $FZF_COMPLETION_TRIGGER (default: '**')
|
||||||
# - $FZF_COMPLETION_OPTS (default: empty)
|
# - $FZF_COMPLETION_OPTS (default: empty)
|
||||||
|
|
||||||
@@ -16,7 +16,7 @@ if ! declare -f _fzf_compgen_path > /dev/null; then
|
|||||||
_fzf_compgen_path() {
|
_fzf_compgen_path() {
|
||||||
echo "$1"
|
echo "$1"
|
||||||
command find -L "$1" \
|
command find -L "$1" \
|
||||||
-name .git -prune -o -name .svn -prune -o \( -type d -o -type f -o -type l \) \
|
-name .git -prune -o -name .hg -prune -o -name .svn -prune -o \( -type d -o -type f -o -type l \) \
|
||||||
-a -not -path "$1" -print 2> /dev/null | sed 's@^\./@@'
|
-a -not -path "$1" -print 2> /dev/null | sed 's@^\./@@'
|
||||||
}
|
}
|
||||||
fi
|
fi
|
||||||
@@ -24,7 +24,7 @@ fi
|
|||||||
if ! declare -f _fzf_compgen_dir > /dev/null; then
|
if ! declare -f _fzf_compgen_dir > /dev/null; then
|
||||||
_fzf_compgen_dir() {
|
_fzf_compgen_dir() {
|
||||||
command find -L "$1" \
|
command find -L "$1" \
|
||||||
-name .git -prune -o -name .svn -prune -o -type d \
|
-name .git -prune -o -name .hg -prune -o -name .svn -prune -o -type d \
|
||||||
-a -not -path "$1" -print 2> /dev/null | sed 's@^\./@@'
|
-a -not -path "$1" -print 2> /dev/null | sed 's@^\./@@'
|
||||||
}
|
}
|
||||||
fi
|
fi
|
||||||
@@ -34,9 +34,16 @@ fi
|
|||||||
# To redraw line after fzf closes (printf '\e[5n')
|
# To redraw line after fzf closes (printf '\e[5n')
|
||||||
bind '"\e[0n": redraw-current-line'
|
bind '"\e[0n": redraw-current-line'
|
||||||
|
|
||||||
__fzfcmd_complete() {
|
__fzf_comprun() {
|
||||||
[ -n "$TMUX_PANE" ] && [ "${FZF_TMUX:-0}" != 0 ] && [ ${LINES:-40} -gt 15 ] &&
|
if [ "$(type -t _fzf_comprun 2>&1)" = function ]; then
|
||||||
echo "fzf-tmux -d${FZF_TMUX_HEIGHT:-40%}" || echo "fzf"
|
_fzf_comprun "$@"
|
||||||
|
elif [ -n "$TMUX_PANE" ] && { [ "${FZF_TMUX:-0}" != 0 ] || [ -n "$FZF_TMUX_OPTS" ]; }; then
|
||||||
|
shift
|
||||||
|
fzf-tmux ${FZF_TMUX_OPTS:--d${FZF_TMUX_HEIGHT:-40%}} -- "$@"
|
||||||
|
else
|
||||||
|
shift
|
||||||
|
fzf "$@"
|
||||||
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
__fzf_orig_completion_filter() {
|
__fzf_orig_completion_filter() {
|
||||||
@@ -72,6 +79,8 @@ _fzf_opts_completion() {
|
|||||||
--margin
|
--margin
|
||||||
--inline-info
|
--inline-info
|
||||||
--prompt
|
--prompt
|
||||||
|
--pointer
|
||||||
|
--marker
|
||||||
--header
|
--header
|
||||||
--header-lines
|
--header-lines
|
||||||
--ansi
|
--ansi
|
||||||
@@ -140,8 +149,7 @@ _fzf_handle_dynamic_completion() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
__fzf_generic_path_completion() {
|
__fzf_generic_path_completion() {
|
||||||
local cur base dir leftover matches trigger cmd fzf
|
local cur base dir leftover matches trigger cmd
|
||||||
fzf="$(__fzfcmd_complete)"
|
|
||||||
cmd="${COMP_WORDS[0]//[^A-Za-z0-9_=]/_}"
|
cmd="${COMP_WORDS[0]//[^A-Za-z0-9_=]/_}"
|
||||||
COMPREPLY=()
|
COMPREPLY=()
|
||||||
trigger=${FZF_COMPLETION_TRIGGER-'**'}
|
trigger=${FZF_COMPLETION_TRIGGER-'**'}
|
||||||
@@ -157,7 +165,7 @@ __fzf_generic_path_completion() {
|
|||||||
leftover=${leftover/#\/}
|
leftover=${leftover/#\/}
|
||||||
[ -z "$dir" ] && dir='.'
|
[ -z "$dir" ] && dir='.'
|
||||||
[ "$dir" != "/" ] && dir="${dir/%\//}"
|
[ "$dir" != "/" ] && dir="${dir/%\//}"
|
||||||
matches=$(eval "$1 $(printf %q "$dir")" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse $FZF_DEFAULT_OPTS $FZF_COMPLETION_OPTS" $fzf $2 -q "$leftover" | while read -r item; do
|
matches=$(eval "$1 $(printf %q "$dir")" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse $FZF_DEFAULT_OPTS $FZF_COMPLETION_OPTS $2" __fzf_comprun "$4" -q "$leftover" | while read -r item; do
|
||||||
printf "%q$3 " "$item"
|
printf "%q$3 " "$item"
|
||||||
done)
|
done)
|
||||||
matches=${matches% }
|
matches=${matches% }
|
||||||
@@ -182,10 +190,30 @@ __fzf_generic_path_completion() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_fzf_complete() {
|
_fzf_complete() {
|
||||||
local cur selected trigger cmd fzf post
|
# Split arguments around --
|
||||||
|
local args rest str_arg i sep
|
||||||
|
args=("$@")
|
||||||
|
sep=
|
||||||
|
for i in "${!args[@]}"; do
|
||||||
|
if [[ "${args[$i]}" = -- ]]; then
|
||||||
|
sep=$i
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
if [[ -n "$sep" ]]; then
|
||||||
|
str_arg=
|
||||||
|
rest=("${args[@]:$((sep + 1)):${#args[@]}}")
|
||||||
|
args=("${args[@]:0:$sep}")
|
||||||
|
else
|
||||||
|
str_arg=$1
|
||||||
|
args=()
|
||||||
|
shift
|
||||||
|
rest=("$@")
|
||||||
|
fi
|
||||||
|
|
||||||
|
local cur selected trigger cmd post
|
||||||
post="$(caller 0 | awk '{print $2}')_post"
|
post="$(caller 0 | awk '{print $2}')_post"
|
||||||
type -t "$post" > /dev/null 2>&1 || post=cat
|
type -t "$post" > /dev/null 2>&1 || post=cat
|
||||||
fzf="$(__fzfcmd_complete)"
|
|
||||||
|
|
||||||
cmd="${COMP_WORDS[0]//[^A-Za-z0-9_=]/_}"
|
cmd="${COMP_WORDS[0]//[^A-Za-z0-9_=]/_}"
|
||||||
trigger=${FZF_COMPLETION_TRIGGER-'**'}
|
trigger=${FZF_COMPLETION_TRIGGER-'**'}
|
||||||
@@ -193,7 +221,7 @@ _fzf_complete() {
|
|||||||
if [[ "$cur" == *"$trigger" ]]; then
|
if [[ "$cur" == *"$trigger" ]]; then
|
||||||
cur=${cur:0:${#cur}-${#trigger}}
|
cur=${cur:0:${#cur}-${#trigger}}
|
||||||
|
|
||||||
selected=$(cat | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse $FZF_DEFAULT_OPTS $FZF_COMPLETION_OPTS" $fzf $1 -q "$cur" | $post | tr '\n' ' ')
|
selected=$(FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse $FZF_DEFAULT_OPTS $FZF_COMPLETION_OPTS $str_arg" __fzf_comprun "${rest[0]}" "${args[@]}" -q "$cur" | $post | tr '\n' ' ')
|
||||||
selected=${selected% } # Strip trailing space not to repeat "-o nospace"
|
selected=${selected% } # Strip trailing space not to repeat "-o nospace"
|
||||||
if [ -n "$selected" ]; then
|
if [ -n "$selected" ]; then
|
||||||
COMPREPLY=("$selected")
|
COMPREPLY=("$selected")
|
||||||
@@ -203,8 +231,7 @@ _fzf_complete() {
|
|||||||
printf '\e[5n'
|
printf '\e[5n'
|
||||||
return 0
|
return 0
|
||||||
else
|
else
|
||||||
shift
|
_fzf_handle_dynamic_completion "$cmd" "${rest[@]}"
|
||||||
_fzf_handle_dynamic_completion "$cmd" "$@"
|
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -224,9 +251,9 @@ _fzf_dir_completion() {
|
|||||||
_fzf_complete_kill() {
|
_fzf_complete_kill() {
|
||||||
[ -n "${COMP_WORDS[COMP_CWORD]}" ] && return 1
|
[ -n "${COMP_WORDS[COMP_CWORD]}" ] && return 1
|
||||||
|
|
||||||
local selected fzf
|
local selected
|
||||||
fzf="$(__fzfcmd_complete)"
|
selected=$(command ps -ef | sed 1d | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-50%} --min-height 15 --reverse $FZF_DEFAULT_OPTS $FZF_COMPLETION_OPTS --preview 'echo {}' --preview-window down:3:wrap" __fzf_comprun "kill" -m | awk '{print $2}' | tr '\n' ' ')
|
||||||
selected=$(command ps -ef | sed 1d | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-50%} --min-height 15 --reverse $FZF_DEFAULT_OPTS --preview 'echo {}' --preview-window down:3:wrap $FZF_COMPLETION_OPTS" $fzf -m | awk '{print $2}' | tr '\n' ' ')
|
selected=${selected% }
|
||||||
printf '\e[5n'
|
printf '\e[5n'
|
||||||
|
|
||||||
if [ -n "$selected" ]; then
|
if [ -n "$selected" ]; then
|
||||||
@@ -235,36 +262,23 @@ _fzf_complete_kill() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
_fzf_complete_telnet() {
|
_fzf_host_completion() {
|
||||||
_fzf_complete '+m' "$@" < <(
|
_fzf_complete +m -- "$@" < <(
|
||||||
command grep -v '^\s*\(#\|$\)' /etc/hosts | command grep -Fv '0.0.0.0' |
|
cat <(cat ~/.ssh/config ~/.ssh/config.d/* /etc/ssh/ssh_config 2> /dev/null | command grep -i '^\s*host\(name\)\? ' | awk '{for (i = 2; i <= NF; i++) print $1 " " $i}' | command grep -v '[*?]') \
|
||||||
awk '{if (length($2) > 0) {print $2}}' | sort -u
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
_fzf_complete_ssh() {
|
|
||||||
_fzf_complete '+m' "$@" < <(
|
|
||||||
cat <(cat ~/.ssh/config ~/.ssh/config.d/* /etc/ssh/ssh_config 2> /dev/null | command grep -i '^host ' | command grep -v '[*?]' | awk '{for (i = 2; i <= NF; i++) print $1 " " $i}') \
|
|
||||||
<(command grep -oE '^[[a-z0-9.,:-]+' ~/.ssh/known_hosts | tr ',' '\n' | tr -d '[' | awk '{ print $1 " " $1 }') \
|
<(command grep -oE '^[[a-z0-9.,:-]+' ~/.ssh/known_hosts | tr ',' '\n' | tr -d '[' | awk '{ print $1 " " $1 }') \
|
||||||
<(command grep -v '^\s*\(#\|$\)' /etc/hosts | command grep -Fv '0.0.0.0') |
|
<(command grep -v '^\s*\(#\|$\)' /etc/hosts | command grep -Fv '0.0.0.0') |
|
||||||
awk '{if (length($2) > 0) {print $2}}' | sort -u
|
awk '{if (length($2) > 0) {print $2}}' | sort -u
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
_fzf_complete_unset() {
|
_fzf_var_completion() {
|
||||||
_fzf_complete '-m' "$@" < <(
|
_fzf_complete -m -- "$@" < <(
|
||||||
declare -xp | sed 's/=.*//' | sed 's/.* //'
|
declare -xp | sed 's/=.*//' | sed 's/.* //'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
_fzf_complete_export() {
|
_fzf_alias_completion() {
|
||||||
_fzf_complete '-m' "$@" < <(
|
_fzf_complete -m -- "$@" < <(
|
||||||
declare -xp | sed 's/=.*//' | sed 's/.* //'
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
_fzf_complete_unalias() {
|
|
||||||
_fzf_complete '-m' "$@" < <(
|
|
||||||
alias | sed 's/=.*//' | sed 's/.* //'
|
alias | sed 's/=.*//' | sed 's/.* //'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -282,7 +296,7 @@ a_cmds="
|
|||||||
find git grep gunzip gzip hg jar
|
find git grep gunzip gzip hg jar
|
||||||
ln ls mv open rm rsync scp
|
ln ls mv open rm rsync scp
|
||||||
svn tar unzip zip"
|
svn tar unzip zip"
|
||||||
x_cmds="kill ssh telnet unset unalias export"
|
x_cmds="kill"
|
||||||
|
|
||||||
# Preserve existing completion
|
# Preserve existing completion
|
||||||
eval "$(complete |
|
eval "$(complete |
|
||||||
@@ -319,16 +333,7 @@ for cmd in $d_cmds; do
|
|||||||
done
|
done
|
||||||
|
|
||||||
# Kill completion
|
# Kill completion
|
||||||
complete -F _fzf_complete_kill -o nospace -o default -o bashdefault kill
|
complete -F _fzf_complete_kill -o default -o bashdefault kill
|
||||||
|
|
||||||
# Host completion
|
|
||||||
complete -F _fzf_complete_ssh -o default -o bashdefault ssh
|
|
||||||
complete -F _fzf_complete_telnet -o default -o bashdefault telnet
|
|
||||||
|
|
||||||
# Environment variables / Aliases
|
|
||||||
complete -F _fzf_complete_unset -o default -o bashdefault unset
|
|
||||||
complete -F _fzf_complete_export -o default -o bashdefault export
|
|
||||||
complete -F _fzf_complete_unalias -o default -o bashdefault unalias
|
|
||||||
|
|
||||||
unset cmd d_cmds a_cmds x_cmds
|
unset cmd d_cmds a_cmds x_cmds
|
||||||
|
|
||||||
@@ -337,17 +342,24 @@ _fzf_setup_completion() {
|
|||||||
kind=$1
|
kind=$1
|
||||||
fn=_fzf_${1}_completion
|
fn=_fzf_${1}_completion
|
||||||
if [[ $# -lt 2 ]] || ! type -t "$fn" > /dev/null; then
|
if [[ $# -lt 2 ]] || ! type -t "$fn" > /dev/null; then
|
||||||
echo "usage: ${FUNCNAME[0]} path|dir COMMANDS..."
|
echo "usage: ${FUNCNAME[0]} path|dir|var|alias|host COMMANDS..."
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
shift
|
shift
|
||||||
|
eval "$(complete -p "$@" 2> /dev/null | grep -v "$fn" | __fzf_orig_completion_filter)"
|
||||||
for cmd in "$@"; do
|
for cmd in "$@"; do
|
||||||
eval "$(complete -p "$cmd" 2> /dev/null | grep -v "$fn" | __fzf_orig_completion_filter)"
|
|
||||||
case "$kind" in
|
case "$kind" in
|
||||||
dir) __fzf_defc "$cmd" "$fn" "-o nospace -o dirnames" ;;
|
dir) __fzf_defc "$cmd" "$fn" "-o nospace -o dirnames" ;;
|
||||||
*) __fzf_defc "$cmd" "$fn" "-o default -o bashdefault" ;;
|
var) __fzf_defc "$cmd" "$fn" "-o default -o nospace -v" ;;
|
||||||
|
alias) __fzf_defc "$cmd" "$fn" "-a" ;;
|
||||||
|
*) __fzf_defc "$cmd" "$fn" "-o default -o bashdefault" ;;
|
||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Environment variables / Aliases / Hosts
|
||||||
|
_fzf_setup_completion 'var' export unset
|
||||||
|
_fzf_setup_completion 'alias' unalias
|
||||||
|
_fzf_setup_completion 'host' ssh telnet
|
||||||
|
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -2,21 +2,86 @@
|
|||||||
# / __/___ / __/
|
# / __/___ / __/
|
||||||
# / /_/_ / / /_
|
# / /_/_ / / /_
|
||||||
# / __/ / /_/ __/
|
# / __/ / /_/ __/
|
||||||
# /_/ /___/_/-completion.zsh
|
# /_/ /___/_/ completion.zsh
|
||||||
#
|
#
|
||||||
# - $FZF_TMUX (default: 0)
|
# - $FZF_TMUX (default: 0)
|
||||||
# - $FZF_TMUX_HEIGHT (default: '40%')
|
# - $FZF_TMUX_OPTS (default: '-d 40%')
|
||||||
# - $FZF_COMPLETION_TRIGGER (default: '**')
|
# - $FZF_COMPLETION_TRIGGER (default: '**')
|
||||||
# - $FZF_COMPLETION_OPTS (default: empty)
|
# - $FZF_COMPLETION_OPTS (default: empty)
|
||||||
|
|
||||||
if [[ $- =~ i ]]; then
|
# Both branches of the following `if` do the same thing -- define
|
||||||
|
# __fzf_completion_options such that `eval $__fzf_completion_options` sets
|
||||||
|
# all options to the same values they currently have. We'll do just that at
|
||||||
|
# the bottom of the file after changing options to what we prefer.
|
||||||
|
#
|
||||||
|
# IMPORTANT: Until we get to the `emulate` line, all words that *can* be quoted
|
||||||
|
# *must* be quoted in order to prevent alias expansion. In addition, code must
|
||||||
|
# be written in a way works with any set of zsh options. This is very tricky, so
|
||||||
|
# careful when you change it.
|
||||||
|
#
|
||||||
|
# Start by loading the builtin zsh/parameter module. It provides `options`
|
||||||
|
# associative array that stores current shell options.
|
||||||
|
if 'zmodload' 'zsh/parameter' 2>'/dev/null' && (( ${+options} )); then
|
||||||
|
# This is the fast branch and it gets taken on virtually all Zsh installations.
|
||||||
|
#
|
||||||
|
# ${(kv)options[@]} expands to array of keys (option names) and values ("on"
|
||||||
|
# or "off"). The subsequent expansion# with (j: :) flag joins all elements
|
||||||
|
# together separated by spaces. __fzf_completion_options ends up with a value
|
||||||
|
# like this: "options=(shwordsplit off aliases on ...)".
|
||||||
|
__fzf_completion_options="options=(${(j: :)${(kv)options[@]}})"
|
||||||
|
else
|
||||||
|
# This branch is much slower because it forks to get the names of all
|
||||||
|
# zsh options. It's possible to eliminate this fork but it's not worth the
|
||||||
|
# trouble because this branch gets taken only on very ancient or broken
|
||||||
|
# zsh installations.
|
||||||
|
() {
|
||||||
|
# That `()` above defines an anonymous function. This is essentially a scope
|
||||||
|
# for local parameters. We use it to avoid polluting global scope.
|
||||||
|
'local' '__fzf_opt'
|
||||||
|
__fzf_completion_options="setopt"
|
||||||
|
# `set -o` prints one line for every zsh option. Each line contains option
|
||||||
|
# name, some spaces, and then either "on" or "off". We just want option names.
|
||||||
|
# Expansion with (@f) flag splits a string into lines. The outer expansion
|
||||||
|
# removes spaces and everything that follow them on every line. __fzf_opt
|
||||||
|
# ends up iterating over option names: shwordsplit, aliases, etc.
|
||||||
|
for __fzf_opt in "${(@)${(@f)$(set -o)}%% *}"; do
|
||||||
|
if [[ -o "$__fzf_opt" ]]; then
|
||||||
|
# Option $__fzf_opt is currently on, so remember to set it back on.
|
||||||
|
__fzf_completion_options+=" -o $__fzf_opt"
|
||||||
|
else
|
||||||
|
# Option $__fzf_opt is currently off, so remember to set it back off.
|
||||||
|
__fzf_completion_options+=" +o $__fzf_opt"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
# The value of __fzf_completion_options here looks like this:
|
||||||
|
# "setopt +o shwordsplit -o aliases ..."
|
||||||
|
}
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Enable the default zsh options (those marked with <Z> in `man zshoptions`)
|
||||||
|
# but without `aliases`. Aliases in functions are expanded when functions are
|
||||||
|
# defined, so if we disable aliases here, we'll be sure to have no pesky
|
||||||
|
# aliases in any of our functions. This way we won't need prefix every
|
||||||
|
# command with `command` or to quote every word to defend against global
|
||||||
|
# aliases. Note that `aliases` is not the only option that's important to
|
||||||
|
# control. There are several others that could wreck havoc if they are set
|
||||||
|
# to values we don't expect. With the following `emulate` command we
|
||||||
|
# sidestep this issue entirely.
|
||||||
|
'emulate' 'zsh' '-o' 'no_aliases'
|
||||||
|
|
||||||
|
# This brace is the start of try-always block. The `always` part is like
|
||||||
|
# `finally` in lesser languages. We use it to *always* restore user options.
|
||||||
|
{
|
||||||
|
|
||||||
|
# Bail out if not interactive shell.
|
||||||
|
[[ -o interactive ]] || return 0
|
||||||
|
|
||||||
# To use custom commands instead of find, override _fzf_compgen_{path,dir}
|
# To use custom commands instead of find, override _fzf_compgen_{path,dir}
|
||||||
if ! declare -f _fzf_compgen_path > /dev/null; then
|
if ! declare -f _fzf_compgen_path > /dev/null; then
|
||||||
_fzf_compgen_path() {
|
_fzf_compgen_path() {
|
||||||
echo "$1"
|
echo "$1"
|
||||||
command find -L "$1" \
|
command find -L "$1" \
|
||||||
-name .git -prune -o -name .svn -prune -o \( -type d -o -type f -o -type l \) \
|
-name .git -prune -o -name .hg -prune -o -name .svn -prune -o \( -type d -o -type f -o -type l \) \
|
||||||
-a -not -path "$1" -print 2> /dev/null | sed 's@^\./@@'
|
-a -not -path "$1" -print 2> /dev/null | sed 's@^\./@@'
|
||||||
}
|
}
|
||||||
fi
|
fi
|
||||||
@@ -24,27 +89,51 @@ fi
|
|||||||
if ! declare -f _fzf_compgen_dir > /dev/null; then
|
if ! declare -f _fzf_compgen_dir > /dev/null; then
|
||||||
_fzf_compgen_dir() {
|
_fzf_compgen_dir() {
|
||||||
command find -L "$1" \
|
command find -L "$1" \
|
||||||
-name .git -prune -o -name .svn -prune -o -type d \
|
-name .git -prune -o -name .hg -prune -o -name .svn -prune -o -type d \
|
||||||
-a -not -path "$1" -print 2> /dev/null | sed 's@^\./@@'
|
-a -not -path "$1" -print 2> /dev/null | sed 's@^\./@@'
|
||||||
}
|
}
|
||||||
fi
|
fi
|
||||||
|
|
||||||
###########################################################
|
###########################################################
|
||||||
|
|
||||||
__fzfcmd_complete() {
|
__fzf_comprun() {
|
||||||
[ -n "$TMUX_PANE" ] && [ "${FZF_TMUX:-0}" != 0 ] && [ ${LINES:-40} -gt 15 ] &&
|
if [[ "$(type _fzf_comprun 2>&1)" =~ function ]]; then
|
||||||
echo "fzf-tmux -d${FZF_TMUX_HEIGHT:-40%}" || echo "fzf"
|
_fzf_comprun "$@"
|
||||||
|
elif [ -n "$TMUX_PANE" ] && { [ "${FZF_TMUX:-0}" != 0 ] || [ -n "$FZF_TMUX_OPTS" ]; }; then
|
||||||
|
shift
|
||||||
|
if [ -n "$FZF_TMUX_OPTS" ]; then
|
||||||
|
fzf-tmux ${(Q)${(Z+n+)FZF_TMUX_OPTS}} -- "$@"
|
||||||
|
else
|
||||||
|
fzf-tmux -d ${FZF_TMUX_HEIGHT:-40%} -- "$@"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
shift
|
||||||
|
fzf "$@"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Extract the name of the command. e.g. foo=1 bar baz**<tab>
|
||||||
|
__fzf_extract_command() {
|
||||||
|
local token tokens
|
||||||
|
tokens=(${(z)1})
|
||||||
|
for token in $tokens; do
|
||||||
|
if [[ "$token" =~ [[:alnum:]] && ! "$token" =~ "=" ]]; then
|
||||||
|
echo "$token"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
echo "${tokens[1]}"
|
||||||
}
|
}
|
||||||
|
|
||||||
__fzf_generic_path_completion() {
|
__fzf_generic_path_completion() {
|
||||||
local base lbuf compgen fzf_opts suffix tail fzf dir leftover matches
|
local base lbuf cmd compgen fzf_opts suffix tail dir leftover matches
|
||||||
base=$1
|
base=$1
|
||||||
lbuf=$2
|
lbuf=$2
|
||||||
|
cmd=$(__fzf_extract_command "$lbuf")
|
||||||
compgen=$3
|
compgen=$3
|
||||||
fzf_opts=$4
|
fzf_opts=$4
|
||||||
suffix=$5
|
suffix=$5
|
||||||
tail=$6
|
tail=$6
|
||||||
fzf="$(__fzfcmd_complete)"
|
|
||||||
|
|
||||||
setopt localoptions nonomatch
|
setopt localoptions nonomatch
|
||||||
eval "base=$base"
|
eval "base=$base"
|
||||||
@@ -55,7 +144,7 @@ __fzf_generic_path_completion() {
|
|||||||
leftover=${leftover/#\/}
|
leftover=${leftover/#\/}
|
||||||
[ -z "$dir" ] && dir='.'
|
[ -z "$dir" ] && dir='.'
|
||||||
[ "$dir" != "/" ] && dir="${dir/%\//}"
|
[ "$dir" != "/" ] && dir="${dir/%\//}"
|
||||||
matches=$(eval "$compgen $(printf %q "$dir")" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse $FZF_DEFAULT_OPTS $FZF_COMPLETION_OPTS" ${=fzf} ${=fzf_opts} -q "$leftover" | while read item; do
|
matches=$(eval "$compgen $(printf %q "$dir")" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse $FZF_DEFAULT_OPTS $FZF_COMPLETION_OPTS" __fzf_comprun "$cmd" ${(Q)${(Z+n+)fzf_opts}} -q "$leftover" | while read item; do
|
||||||
echo -n "${(q)item}$suffix "
|
echo -n "${(q)item}$suffix "
|
||||||
done)
|
done)
|
||||||
matches=${matches% }
|
matches=${matches% }
|
||||||
@@ -87,17 +176,37 @@ _fzf_feed_fifo() (
|
|||||||
)
|
)
|
||||||
|
|
||||||
_fzf_complete() {
|
_fzf_complete() {
|
||||||
local fifo fzf_opts lbuf fzf matches post
|
setopt localoptions ksh_arrays
|
||||||
|
# Split arguments around --
|
||||||
|
local args rest str_arg i sep
|
||||||
|
args=("$@")
|
||||||
|
sep=
|
||||||
|
for i in {0..${#args[@]}}; do
|
||||||
|
if [[ "${args[$i]}" = -- ]]; then
|
||||||
|
sep=$i
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
if [[ -n "$sep" ]]; then
|
||||||
|
str_arg=
|
||||||
|
rest=("${args[@]:$((sep + 1)):${#args[@]}}")
|
||||||
|
args=("${args[@]:0:$sep}")
|
||||||
|
else
|
||||||
|
str_arg=$1
|
||||||
|
args=()
|
||||||
|
shift
|
||||||
|
rest=("$@")
|
||||||
|
fi
|
||||||
|
|
||||||
|
local fifo lbuf cmd matches post
|
||||||
fifo="${TMPDIR:-/tmp}/fzf-complete-fifo-$$"
|
fifo="${TMPDIR:-/tmp}/fzf-complete-fifo-$$"
|
||||||
fzf_opts=$1
|
lbuf=${rest[0]}
|
||||||
lbuf=$2
|
cmd=$(__fzf_extract_command "$lbuf")
|
||||||
post="${funcstack[2]}_post"
|
post="${funcstack[1]}_post"
|
||||||
type $post > /dev/null 2>&1 || post=cat
|
type $post > /dev/null 2>&1 || post=cat
|
||||||
|
|
||||||
fzf="$(__fzfcmd_complete)"
|
|
||||||
|
|
||||||
_fzf_feed_fifo "$fifo"
|
_fzf_feed_fifo "$fifo"
|
||||||
matches=$(cat "$fifo" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse $FZF_DEFAULT_OPTS $FZF_COMPLETION_OPTS" ${=fzf} ${=fzf_opts} -q "${(Q)prefix}" | $post | tr '\n' ' ')
|
matches=$(FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse $FZF_DEFAULT_OPTS $FZF_COMPLETION_OPTS $str_arg" __fzf_comprun "$cmd" "${args[@]}" -q "${(Q)prefix}" < "$fifo" | $post | tr '\n' ' ')
|
||||||
if [ -n "$matches" ]; then
|
if [ -n "$matches" ]; then
|
||||||
LBUFFER="$lbuf$matches"
|
LBUFFER="$lbuf$matches"
|
||||||
fi
|
fi
|
||||||
@@ -106,16 +215,16 @@ _fzf_complete() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_fzf_complete_telnet() {
|
_fzf_complete_telnet() {
|
||||||
_fzf_complete '+m' "$@" < <(
|
_fzf_complete +m -- "$@" < <(
|
||||||
command grep -v '^\s*\(#\|$\)' /etc/hosts | command grep -Fv '0.0.0.0' |
|
command grep -v '^\s*\(#\|$\)' /etc/hosts | command grep -Fv '0.0.0.0' |
|
||||||
awk '{if (length($2) > 0) {print $2}}' | sort -u
|
awk '{if (length($2) > 0) {print $2}}' | sort -u
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
_fzf_complete_ssh() {
|
_fzf_complete_ssh() {
|
||||||
_fzf_complete '+m' "$@" < <(
|
_fzf_complete +m -- "$@" < <(
|
||||||
setopt localoptions nonomatch
|
setopt localoptions nonomatch
|
||||||
command cat <(cat ~/.ssh/config ~/.ssh/config.d/* /etc/ssh/ssh_config 2> /dev/null | command grep -i '^host ' | command grep -v '[*?]' | awk '{for (i = 2; i <= NF; i++) print $1 " " $i}') \
|
command cat <(cat ~/.ssh/config ~/.ssh/config.d/* /etc/ssh/ssh_config 2> /dev/null | command grep -i '^\s*host\(name\)\? ' | awk '{for (i = 2; i <= NF; i++) print $1 " " $i}' | command grep -v '[*?]') \
|
||||||
<(command grep -oE '^[[a-z0-9.,:-]+' ~/.ssh/known_hosts | tr ',' '\n' | tr -d '[' | awk '{ print $1 " " $1 }') \
|
<(command grep -oE '^[[a-z0-9.,:-]+' ~/.ssh/known_hosts | tr ',' '\n' | tr -d '[' | awk '{ print $1 " " $1 }') \
|
||||||
<(command grep -v '^\s*\(#\|$\)' /etc/hosts | command grep -Fv '0.0.0.0') |
|
<(command grep -v '^\s*\(#\|$\)' /etc/hosts | command grep -Fv '0.0.0.0') |
|
||||||
awk '{if (length($2) > 0) {print $2}}' | sort -u
|
awk '{if (length($2) > 0) {print $2}}' | sort -u
|
||||||
@@ -123,25 +232,25 @@ _fzf_complete_ssh() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_fzf_complete_export() {
|
_fzf_complete_export() {
|
||||||
_fzf_complete '-m' "$@" < <(
|
_fzf_complete -m -- "$@" < <(
|
||||||
declare -xp | sed 's/=.*//' | sed 's/.* //'
|
declare -xp | sed 's/=.*//' | sed 's/.* //'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
_fzf_complete_unset() {
|
_fzf_complete_unset() {
|
||||||
_fzf_complete '-m' "$@" < <(
|
_fzf_complete -m -- "$@" < <(
|
||||||
declare -xp | sed 's/=.*//' | sed 's/.* //'
|
declare -xp | sed 's/=.*//' | sed 's/.* //'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
_fzf_complete_unalias() {
|
_fzf_complete_unalias() {
|
||||||
_fzf_complete '+m' "$@" < <(
|
_fzf_complete +m -- "$@" < <(
|
||||||
alias | sed 's/=.*//'
|
alias | sed 's/=.*//'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fzf-completion() {
|
fzf-completion() {
|
||||||
local tokens cmd prefix trigger tail fzf matches lbuf d_cmds
|
local tokens cmd prefix trigger tail matches lbuf d_cmds
|
||||||
setopt localoptions noshwordsplit noksh_arrays noposixbuiltins
|
setopt localoptions noshwordsplit noksh_arrays noposixbuiltins
|
||||||
|
|
||||||
# http://zsh.sourceforge.net/FAQ/zshfaq03.html
|
# http://zsh.sourceforge.net/FAQ/zshfaq03.html
|
||||||
@@ -152,7 +261,7 @@ fzf-completion() {
|
|||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
|
|
||||||
cmd=${tokens[1]}
|
cmd=$(__fzf_extract_command "$LBUFFER")
|
||||||
|
|
||||||
# Explicitly allow for empty trigger.
|
# Explicitly allow for empty trigger.
|
||||||
trigger=${FZF_COMPLETION_TRIGGER-'**'}
|
trigger=${FZF_COMPLETION_TRIGGER-'**'}
|
||||||
@@ -167,8 +276,7 @@ fzf-completion() {
|
|||||||
tail=${LBUFFER:$(( ${#LBUFFER} - ${#trigger} ))}
|
tail=${LBUFFER:$(( ${#LBUFFER} - ${#trigger} ))}
|
||||||
# Kill completion (do not require trigger sequence)
|
# Kill completion (do not require trigger sequence)
|
||||||
if [ $cmd = kill -a ${LBUFFER[-1]} = ' ' ]; then
|
if [ $cmd = kill -a ${LBUFFER[-1]} = ' ' ]; then
|
||||||
fzf="$(__fzfcmd_complete)"
|
matches=$(command ps -ef | sed 1d | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-50%} --min-height 15 --reverse $FZF_DEFAULT_OPTS $FZF_COMPLETION_OPTS --preview 'echo {}' --preview-window down:3:wrap" __fzf_comprun "$cmd" -m | awk '{print $2}' | tr '\n' ' ')
|
||||||
matches=$(command ps -ef | sed 1d | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-50%} --min-height 15 --reverse $FZF_DEFAULT_OPTS --preview 'echo {}' --preview-window down:3:wrap $FZF_COMPLETION_OPTS" ${=fzf} -m | awk '{print $2}' | tr '\n' ' ')
|
|
||||||
if [ -n "$matches" ]; then
|
if [ -n "$matches" ]; then
|
||||||
LBUFFER="$LBUFFER$matches"
|
LBUFFER="$LBUFFER$matches"
|
||||||
fi
|
fi
|
||||||
@@ -181,7 +289,7 @@ fzf-completion() {
|
|||||||
[ -z "${tokens[-1]}" ] && lbuf=$LBUFFER || lbuf=${LBUFFER:0:-${#tokens[-1]}}
|
[ -z "${tokens[-1]}" ] && lbuf=$LBUFFER || lbuf=${LBUFFER:0:-${#tokens[-1]}}
|
||||||
|
|
||||||
if eval "type _fzf_complete_${cmd} > /dev/null"; then
|
if eval "type _fzf_complete_${cmd} > /dev/null"; then
|
||||||
eval "prefix=\"$prefix\" _fzf_complete_${cmd} \"$lbuf\""
|
prefix="$prefix" eval _fzf_complete_${cmd} ${(q)lbuf}
|
||||||
elif [ ${d_cmds[(i)$cmd]} -le ${#d_cmds} ]; then
|
elif [ ${d_cmds[(i)$cmd]} -le ${#d_cmds} ]; then
|
||||||
_fzf_dir_completion "$prefix" "$lbuf"
|
_fzf_dir_completion "$prefix" "$lbuf"
|
||||||
else
|
else
|
||||||
@@ -202,4 +310,8 @@ fzf-completion() {
|
|||||||
zle -N fzf-completion
|
zle -N fzf-completion
|
||||||
bindkey '^I' fzf-completion
|
bindkey '^I' fzf-completion
|
||||||
|
|
||||||
fi
|
} always {
|
||||||
|
# Restore the original options.
|
||||||
|
eval $__fzf_completion_options
|
||||||
|
'unset' '__fzf_completion_options'
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,3 +1,16 @@
|
|||||||
|
# ____ ____
|
||||||
|
# / __/___ / __/
|
||||||
|
# / /_/_ / / /_
|
||||||
|
# / __/ / /_/ __/
|
||||||
|
# /_/ /___/_/ key-bindings.bash
|
||||||
|
#
|
||||||
|
# - $FZF_TMUX_OPTS
|
||||||
|
# - $FZF_CTRL_T_COMMAND
|
||||||
|
# - $FZF_CTRL_T_OPTS
|
||||||
|
# - $FZF_CTRL_R_OPTS
|
||||||
|
# - $FZF_ALT_C_COMMAND
|
||||||
|
# - $FZF_ALT_C_OPTS
|
||||||
|
|
||||||
# Key bindings
|
# Key bindings
|
||||||
# ------------
|
# ------------
|
||||||
__fzf_select__() {
|
__fzf_select__() {
|
||||||
@@ -5,7 +18,7 @@ __fzf_select__() {
|
|||||||
-o -type f -print \
|
-o -type f -print \
|
||||||
-o -type d -print \
|
-o -type d -print \
|
||||||
-o -type l -print 2> /dev/null | cut -b3-"}"
|
-o -type l -print 2> /dev/null | cut -b3-"}"
|
||||||
eval "$cmd" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse $FZF_DEFAULT_OPTS $FZF_CTRL_T_OPTS" fzf -m "$@" | while read -r item; do
|
eval "$cmd" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse $FZF_DEFAULT_OPTS $FZF_CTRL_T_OPTS" $(__fzfcmd) -m "$@" | while read -r item; do
|
||||||
printf '%q ' "$item"
|
printf '%q ' "$item"
|
||||||
done
|
done
|
||||||
echo
|
echo
|
||||||
@@ -13,35 +26,15 @@ __fzf_select__() {
|
|||||||
|
|
||||||
if [[ $- =~ i ]]; then
|
if [[ $- =~ i ]]; then
|
||||||
|
|
||||||
__fzf_use_tmux__() {
|
|
||||||
[ -n "$TMUX_PANE" ] && [ "${FZF_TMUX:-0}" != 0 ] && [ ${LINES:-40} -gt 15 ]
|
|
||||||
}
|
|
||||||
|
|
||||||
__fzfcmd() {
|
__fzfcmd() {
|
||||||
__fzf_use_tmux__ &&
|
[ -n "$TMUX_PANE" ] && { [ "${FZF_TMUX:-0}" != 0 ] || [ -n "$FZF_TMUX_OPTS" ]; } &&
|
||||||
echo "fzf-tmux -d${FZF_TMUX_HEIGHT:-40%}" || echo "fzf"
|
echo "fzf-tmux ${FZF_TMUX_OPTS:--d${FZF_TMUX_HEIGHT:-40%}} -- " || echo "fzf"
|
||||||
}
|
|
||||||
|
|
||||||
__fzf_select_tmux__() {
|
|
||||||
local height
|
|
||||||
height=${FZF_TMUX_HEIGHT:-40%}
|
|
||||||
if [[ $height =~ %$ ]]; then
|
|
||||||
height="-p ${height%\%}"
|
|
||||||
else
|
|
||||||
height="-l $height"
|
|
||||||
fi
|
|
||||||
|
|
||||||
tmux split-window $height "cd $(printf %q "$PWD"); FZF_DEFAULT_OPTS=$(printf %q "$FZF_DEFAULT_OPTS") PATH=$(printf %q "$PATH") FZF_CTRL_T_COMMAND=$(printf %q "$FZF_CTRL_T_COMMAND") FZF_CTRL_T_OPTS=$(printf %q "$FZF_CTRL_T_OPTS") bash -c 'source \"${BASH_SOURCE[0]}\"; RESULT=\"\$(__fzf_select__ --no-height)\"; tmux setb -b fzf \"\$RESULT\" \\; pasteb -b fzf -t $TMUX_PANE \\; deleteb -b fzf || tmux send-keys -t $TMUX_PANE \"\$RESULT\"'"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fzf-file-widget() {
|
fzf-file-widget() {
|
||||||
if __fzf_use_tmux__; then
|
local selected="$(__fzf_select__)"
|
||||||
__fzf_select_tmux__
|
READLINE_LINE="${READLINE_LINE:0:$READLINE_POINT}$selected${READLINE_LINE:$READLINE_POINT}"
|
||||||
else
|
READLINE_POINT=$(( READLINE_POINT + ${#selected} ))
|
||||||
local selected="$(__fzf_select__)"
|
|
||||||
READLINE_LINE="${READLINE_LINE:0:$READLINE_POINT}$selected${READLINE_LINE:$READLINE_POINT}"
|
|
||||||
READLINE_POINT=$(( READLINE_POINT + ${#selected} ))
|
|
||||||
fi
|
|
||||||
}
|
}
|
||||||
|
|
||||||
__fzf_cd__() {
|
__fzf_cd__() {
|
||||||
@@ -51,71 +44,53 @@ __fzf_cd__() {
|
|||||||
dir=$(eval "$cmd" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse $FZF_DEFAULT_OPTS $FZF_ALT_C_OPTS" $(__fzfcmd) +m) && printf 'cd %q' "$dir"
|
dir=$(eval "$cmd" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse $FZF_DEFAULT_OPTS $FZF_ALT_C_OPTS" $(__fzfcmd) +m) && printf 'cd %q' "$dir"
|
||||||
}
|
}
|
||||||
|
|
||||||
__fzf_history__() (
|
__fzf_history__() {
|
||||||
local line
|
local output
|
||||||
shopt -u nocaseglob nocasematch
|
output=$(
|
||||||
line=$(
|
builtin fc -lnr -2147483648 |
|
||||||
HISTTIMEFORMAT= builtin history |
|
last_hist=$(HISTTIMEFORMAT='' builtin history 1) perl -n -l0 -e 'BEGIN { getc; $/ = "\n\t"; $HISTCMD = $ENV{last_hist} + 1 } s/^[ *]//; print $HISTCMD - $. . "\t$_" if !$seen{$_}++' |
|
||||||
FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} $FZF_DEFAULT_OPTS --tac --sync -n2..,.. --tiebreak=index --bind=ctrl-r:toggle-sort $FZF_CTRL_R_OPTS +m" $(__fzfcmd) |
|
FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} $FZF_DEFAULT_OPTS -n2..,.. --tiebreak=index --bind=ctrl-r:toggle-sort $FZF_CTRL_R_OPTS +m --read0" $(__fzfcmd) --query "$READLINE_LINE"
|
||||||
command grep '^ *[0-9]') &&
|
) || return
|
||||||
if [[ $- =~ H ]]; then
|
READLINE_LINE=${output#*$'\t'}
|
||||||
sed 's/^ *\([0-9]*\)\** .*/!\1/' <<< "$line"
|
if [ -z "$READLINE_POINT" ]; then
|
||||||
else
|
echo "$READLINE_LINE"
|
||||||
sed 's/^ *\([0-9]*\)\** *//' <<< "$line"
|
|
||||||
fi
|
|
||||||
)
|
|
||||||
|
|
||||||
if [[ ! -o vi ]]; then
|
|
||||||
# Required to refresh the prompt after fzf
|
|
||||||
bind '"\er": redraw-current-line'
|
|
||||||
bind '"\e^": history-expand-line'
|
|
||||||
|
|
||||||
# CTRL-T - Paste the selected file path into the command line
|
|
||||||
if [ $BASH_VERSINFO -gt 3 ]; then
|
|
||||||
bind -x '"\C-t": "fzf-file-widget"'
|
|
||||||
elif __fzf_use_tmux__; then
|
|
||||||
bind '"\C-t": " \C-u \C-a\C-k`__fzf_select_tmux__`\e\C-e\C-y\C-a\C-d\C-y\ey\C-h"'
|
|
||||||
else
|
else
|
||||||
bind '"\C-t": " \C-u \C-a\C-k`__fzf_select__`\e\C-e\C-y\C-a\C-y\ey\C-h\C-e\er \C-h"'
|
READLINE_POINT=0x7fffffff
|
||||||
fi
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Required to refresh the prompt after fzf
|
||||||
|
bind -m emacs-standard '"\er": redraw-current-line'
|
||||||
|
|
||||||
|
bind -m vi-command '"\C-z": emacs-editing-mode'
|
||||||
|
bind -m vi-insert '"\C-z": emacs-editing-mode'
|
||||||
|
bind -m emacs-standard '"\C-z": vi-editing-mode'
|
||||||
|
|
||||||
|
if [ "${BASH_VERSINFO[0]}" -lt 4 ]; then
|
||||||
|
# CTRL-T - Paste the selected file path into the command line
|
||||||
|
bind -m emacs-standard '"\C-t": " \C-b\C-k \C-u`__fzf_select__`\e\C-e\er\C-a\C-y\C-h\C-e\e \C-y\ey\C-x\C-x\C-f"'
|
||||||
|
bind -m vi-command '"\C-t": "\C-z\C-t\C-z"'
|
||||||
|
bind -m vi-insert '"\C-t": "\C-z\C-t\C-z"'
|
||||||
|
|
||||||
# CTRL-R - Paste the selected command from history into the command line
|
# CTRL-R - Paste the selected command from history into the command line
|
||||||
bind '"\C-r": " \C-e\C-u\C-y\ey\C-u`__fzf_history__`\e\C-e\er\e^"'
|
bind -m emacs-standard '"\C-r": "\C-e \C-u\C-y\ey\C-u"$(__fzf_history__)"\e\C-e\er"'
|
||||||
|
bind -m vi-command '"\C-r": "\C-z\C-r\C-z"'
|
||||||
# ALT-C - cd into the selected directory
|
bind -m vi-insert '"\C-r": "\C-z\C-r\C-z"'
|
||||||
bind '"\ec": " \C-e\C-u`__fzf_cd__`\e\C-e\er\C-m"'
|
|
||||||
else
|
else
|
||||||
# We'd usually use "\e" to enter vi-movement-mode so we can do our magic,
|
|
||||||
# but this incurs a very noticeable delay of a half second or so,
|
|
||||||
# because many other commands start with "\e".
|
|
||||||
# Instead, we bind an unused key, "\C-x\C-a",
|
|
||||||
# to also enter vi-movement-mode,
|
|
||||||
# and then use that thereafter.
|
|
||||||
# (We imagine that "\C-x\C-a" is relatively unlikely to be in use.)
|
|
||||||
bind '"\C-x\C-a": vi-movement-mode'
|
|
||||||
|
|
||||||
bind '"\C-x\C-e": shell-expand-line'
|
|
||||||
bind '"\C-x\C-r": redraw-current-line'
|
|
||||||
bind '"\C-x^": history-expand-line'
|
|
||||||
|
|
||||||
# CTRL-T - Paste the selected file path into the command line
|
# CTRL-T - Paste the selected file path into the command line
|
||||||
# - FIXME: Selected items are attached to the end regardless of cursor position
|
bind -m emacs-standard -x '"\C-t": fzf-file-widget'
|
||||||
if [ $BASH_VERSINFO -gt 3 ]; then
|
bind -m vi-command -x '"\C-t": fzf-file-widget'
|
||||||
bind -x '"\C-t": "fzf-file-widget"'
|
bind -m vi-insert -x '"\C-t": fzf-file-widget'
|
||||||
elif __fzf_use_tmux__; then
|
|
||||||
bind '"\C-t": "\C-x\C-a$a \C-x\C-addi`__fzf_select_tmux__`\C-x\C-e\C-x\C-a0P$xa"'
|
|
||||||
else
|
|
||||||
bind '"\C-t": "\C-x\C-a$a \C-x\C-addi`__fzf_select__`\C-x\C-e\C-x\C-a0Px$a \C-x\C-r\C-x\C-axa "'
|
|
||||||
fi
|
|
||||||
bind -m vi-command '"\C-t": "i\C-t"'
|
|
||||||
|
|
||||||
# CTRL-R - Paste the selected command from history into the command line
|
# CTRL-R - Paste the selected command from history into the command line
|
||||||
bind '"\C-r": "\C-x\C-addi`__fzf_history__`\C-x\C-e\C-x\C-r\C-x^\C-x\C-a$a"'
|
bind -m emacs-standard -x '"\C-r": __fzf_history__'
|
||||||
bind -m vi-command '"\C-r": "i\C-r"'
|
bind -m vi-command -x '"\C-r": __fzf_history__'
|
||||||
|
bind -m vi-insert -x '"\C-r": __fzf_history__'
|
||||||
# ALT-C - cd into the selected directory
|
|
||||||
bind '"\ec": "\C-x\C-addi`__fzf_cd__`\C-x\C-e\C-x\C-r\C-m"'
|
|
||||||
bind -m vi-command '"\ec": "ddi`__fzf_cd__`\C-x\C-e\C-x\C-r\C-m"'
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# ALT-C - cd into the selected directory
|
||||||
|
bind -m emacs-standard '"\ec": " \C-b\C-k \C-u`__fzf_cd__`\e\C-e\er\C-m\C-y\C-h\e \C-y\ey\C-x\C-x\C-d"'
|
||||||
|
bind -m vi-command '"\ec": "\C-z\ec\C-z"'
|
||||||
|
bind -m vi-insert '"\ec": "\C-z\ec\C-z"'
|
||||||
|
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -1,3 +1,16 @@
|
|||||||
|
# ____ ____
|
||||||
|
# / __/___ / __/
|
||||||
|
# / /_/_ / / /_
|
||||||
|
# / __/ / /_/ __/
|
||||||
|
# /_/ /___/_/ key-bindings.fish
|
||||||
|
#
|
||||||
|
# - $FZF_TMUX_OPTS
|
||||||
|
# - $FZF_CTRL_T_COMMAND
|
||||||
|
# - $FZF_CTRL_T_OPTS
|
||||||
|
# - $FZF_CTRL_R_OPTS
|
||||||
|
# - $FZF_ALT_C_COMMAND
|
||||||
|
# - $FZF_ALT_C_OPTS
|
||||||
|
|
||||||
# Key bindings
|
# Key bindings
|
||||||
# ------------
|
# ------------
|
||||||
function fzf_key_bindings
|
function fzf_key_bindings
|
||||||
@@ -10,13 +23,13 @@ function fzf_key_bindings
|
|||||||
|
|
||||||
# "-path \$dir'*/\\.*'" matches hidden files/folders inside $dir but not
|
# "-path \$dir'*/\\.*'" matches hidden files/folders inside $dir but not
|
||||||
# $dir itself, even if hidden.
|
# $dir itself, even if hidden.
|
||||||
set -q FZF_CTRL_T_COMMAND; or set -l FZF_CTRL_T_COMMAND "
|
test -n "$FZF_CTRL_T_COMMAND"; or set -l FZF_CTRL_T_COMMAND "
|
||||||
command find -L \$dir -mindepth 1 \\( -path \$dir'*/\\.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' \\) -prune \
|
command find -L \$dir -mindepth 1 \\( -path \$dir'*/\\.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' \\) -prune \
|
||||||
-o -type f -print \
|
-o -type f -print \
|
||||||
-o -type d -print \
|
-o -type d -print \
|
||||||
-o -type l -print 2> /dev/null | sed 's@^\./@@'"
|
-o -type l -print 2> /dev/null | sed 's@^\./@@'"
|
||||||
|
|
||||||
set -q FZF_TMUX_HEIGHT; or set FZF_TMUX_HEIGHT 40%
|
test -n "$FZF_TMUX_HEIGHT"; or set FZF_TMUX_HEIGHT 40%
|
||||||
begin
|
begin
|
||||||
set -lx FZF_DEFAULT_OPTS "--height $FZF_TMUX_HEIGHT --reverse $FZF_DEFAULT_OPTS $FZF_CTRL_T_OPTS"
|
set -lx FZF_DEFAULT_OPTS "--height $FZF_TMUX_HEIGHT --reverse $FZF_DEFAULT_OPTS $FZF_CTRL_T_OPTS"
|
||||||
eval "$FZF_CTRL_T_COMMAND | "(__fzfcmd)' -m --query "'$fzf_query'"' | while read -l r; set result $result $r; end
|
eval "$FZF_CTRL_T_COMMAND | "(__fzfcmd)' -m --query "'$fzf_query'"' | while read -l r; set result $result $r; end
|
||||||
@@ -36,7 +49,7 @@ function fzf_key_bindings
|
|||||||
end
|
end
|
||||||
|
|
||||||
function fzf-history-widget -d "Show command history"
|
function fzf-history-widget -d "Show command history"
|
||||||
set -q FZF_TMUX_HEIGHT; or set FZF_TMUX_HEIGHT 40%
|
test -n "$FZF_TMUX_HEIGHT"; or set FZF_TMUX_HEIGHT 40%
|
||||||
begin
|
begin
|
||||||
set -lx FZF_DEFAULT_OPTS "--height $FZF_TMUX_HEIGHT $FZF_DEFAULT_OPTS --tiebreak=index --bind=ctrl-r:toggle-sort $FZF_CTRL_R_OPTS +m"
|
set -lx FZF_DEFAULT_OPTS "--height $FZF_TMUX_HEIGHT $FZF_DEFAULT_OPTS --tiebreak=index --bind=ctrl-r:toggle-sort $FZF_CTRL_R_OPTS +m"
|
||||||
|
|
||||||
@@ -62,10 +75,10 @@ function fzf_key_bindings
|
|||||||
set -l dir $commandline[1]
|
set -l dir $commandline[1]
|
||||||
set -l fzf_query $commandline[2]
|
set -l fzf_query $commandline[2]
|
||||||
|
|
||||||
set -q FZF_ALT_C_COMMAND; or set -l FZF_ALT_C_COMMAND "
|
test -n "$FZF_ALT_C_COMMAND"; or set -l FZF_ALT_C_COMMAND "
|
||||||
command find -L \$dir -mindepth 1 \\( -path \$dir'*/\\.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' \\) -prune \
|
command find -L \$dir -mindepth 1 \\( -path \$dir'*/\\.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' \\) -prune \
|
||||||
-o -type d -print 2> /dev/null | sed 's@^\./@@'"
|
-o -type d -print 2> /dev/null | sed 's@^\./@@'"
|
||||||
set -q FZF_TMUX_HEIGHT; or set FZF_TMUX_HEIGHT 40%
|
test -n "$FZF_TMUX_HEIGHT"; or set FZF_TMUX_HEIGHT 40%
|
||||||
begin
|
begin
|
||||||
set -lx FZF_DEFAULT_OPTS "--height $FZF_TMUX_HEIGHT --reverse $FZF_DEFAULT_OPTS $FZF_ALT_C_OPTS"
|
set -lx FZF_DEFAULT_OPTS "--height $FZF_TMUX_HEIGHT --reverse $FZF_DEFAULT_OPTS $FZF_ALT_C_OPTS"
|
||||||
eval "$FZF_ALT_C_COMMAND | "(__fzfcmd)' +m --query "'$fzf_query'"' | read -l result
|
eval "$FZF_ALT_C_COMMAND | "(__fzfcmd)' +m --query "'$fzf_query'"' | read -l result
|
||||||
@@ -82,10 +95,12 @@ function fzf_key_bindings
|
|||||||
end
|
end
|
||||||
|
|
||||||
function __fzfcmd
|
function __fzfcmd
|
||||||
set -q FZF_TMUX; or set FZF_TMUX 0
|
test -n "$FZF_TMUX"; or set FZF_TMUX 0
|
||||||
set -q FZF_TMUX_HEIGHT; or set FZF_TMUX_HEIGHT 40%
|
test -n "$FZF_TMUX_HEIGHT"; or set FZF_TMUX_HEIGHT 40%
|
||||||
if [ $FZF_TMUX -eq 1 ]
|
if [ -n "$FZF_TMUX_OPTS" ]
|
||||||
echo "fzf-tmux -d$FZF_TMUX_HEIGHT"
|
echo "fzf-tmux $FZF_TMUX_OPTS -- "
|
||||||
|
else if [ $FZF_TMUX -eq 1 ]
|
||||||
|
echo "fzf-tmux -d$FZF_TMUX_HEIGHT -- "
|
||||||
else
|
else
|
||||||
echo "fzf"
|
echo "fzf"
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,6 +1,42 @@
|
|||||||
|
# ____ ____
|
||||||
|
# / __/___ / __/
|
||||||
|
# / /_/_ / / /_
|
||||||
|
# / __/ / /_/ __/
|
||||||
|
# /_/ /___/_/ key-bindings.zsh
|
||||||
|
#
|
||||||
|
# - $FZF_TMUX_OPTS
|
||||||
|
# - $FZF_CTRL_T_COMMAND
|
||||||
|
# - $FZF_CTRL_T_OPTS
|
||||||
|
# - $FZF_CTRL_R_OPTS
|
||||||
|
# - $FZF_ALT_C_COMMAND
|
||||||
|
# - $FZF_ALT_C_OPTS
|
||||||
|
|
||||||
# Key bindings
|
# Key bindings
|
||||||
# ------------
|
# ------------
|
||||||
if [[ $- == *i* ]]; then
|
|
||||||
|
# The code at the top and the bottom of this file is the same as in completion.zsh.
|
||||||
|
# Refer to that file for explanation.
|
||||||
|
if 'zmodload' 'zsh/parameter' 2>'/dev/null' && (( ${+options} )); then
|
||||||
|
__fzf_key_bindings_options="options=(${(j: :)${(kv)options[@]}})"
|
||||||
|
else
|
||||||
|
() {
|
||||||
|
__fzf_key_bindings_options="setopt"
|
||||||
|
'local' '__fzf_opt'
|
||||||
|
for __fzf_opt in "${(@)${(@f)$(set -o)}%% *}"; do
|
||||||
|
if [[ -o "$__fzf_opt" ]]; then
|
||||||
|
__fzf_key_bindings_options+=" -o $__fzf_opt"
|
||||||
|
else
|
||||||
|
__fzf_key_bindings_options+=" +o $__fzf_opt"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
}
|
||||||
|
fi
|
||||||
|
|
||||||
|
'emulate' 'zsh' '-o' 'no_aliases'
|
||||||
|
|
||||||
|
{
|
||||||
|
|
||||||
|
[[ -o interactive ]] || return 0
|
||||||
|
|
||||||
# CTRL-T - Paste the selected file path(s) into the command line
|
# CTRL-T - Paste the selected file path(s) into the command line
|
||||||
__fsel() {
|
__fsel() {
|
||||||
@@ -17,13 +53,9 @@ __fsel() {
|
|||||||
return $ret
|
return $ret
|
||||||
}
|
}
|
||||||
|
|
||||||
__fzf_use_tmux__() {
|
|
||||||
[ -n "$TMUX_PANE" ] && [ "${FZF_TMUX:-0}" != 0 ] && [ ${LINES:-40} -gt 15 ]
|
|
||||||
}
|
|
||||||
|
|
||||||
__fzfcmd() {
|
__fzfcmd() {
|
||||||
__fzf_use_tmux__ &&
|
[ -n "$TMUX_PANE" ] && { [ "${FZF_TMUX:-0}" != 0 ] || [ -n "$FZF_TMUX_OPTS" ]; } &&
|
||||||
echo "fzf-tmux -d${FZF_TMUX_HEIGHT:-40%}" || echo "fzf"
|
echo "fzf-tmux ${FZF_TMUX_OPTS:--d${FZF_TMUX_HEIGHT:-40%}} -- " || echo "fzf"
|
||||||
}
|
}
|
||||||
|
|
||||||
fzf-file-widget() {
|
fzf-file-widget() {
|
||||||
@@ -56,6 +88,7 @@ fzf-cd-widget() {
|
|||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
cd "$dir"
|
cd "$dir"
|
||||||
|
unset dir # ensure this doesn't end up appearing in prompt expansion
|
||||||
local ret=$?
|
local ret=$?
|
||||||
zle fzf-redraw-prompt
|
zle fzf-redraw-prompt
|
||||||
return $ret
|
return $ret
|
||||||
@@ -67,7 +100,7 @@ bindkey '\ec' fzf-cd-widget
|
|||||||
fzf-history-widget() {
|
fzf-history-widget() {
|
||||||
local selected num
|
local selected num
|
||||||
setopt localoptions noglobsubst noposixbuiltins pipefail no_aliases 2> /dev/null
|
setopt localoptions noglobsubst noposixbuiltins pipefail no_aliases 2> /dev/null
|
||||||
selected=( $(fc -rl 1 |
|
selected=( $(fc -rl 1 | perl -ne 'print if !$seen{($_ =~ s/^\s*[0-9]+\s+//r)}++' |
|
||||||
FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} $FZF_DEFAULT_OPTS -n2..,.. --tiebreak=index --bind=ctrl-r:toggle-sort $FZF_CTRL_R_OPTS --query=${(qqq)LBUFFER} +m" $(__fzfcmd)) )
|
FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} $FZF_DEFAULT_OPTS -n2..,.. --tiebreak=index --bind=ctrl-r:toggle-sort $FZF_CTRL_R_OPTS --query=${(qqq)LBUFFER} +m" $(__fzfcmd)) )
|
||||||
local ret=$?
|
local ret=$?
|
||||||
if [ -n "$selected" ]; then
|
if [ -n "$selected" ]; then
|
||||||
@@ -82,4 +115,7 @@ fzf-history-widget() {
|
|||||||
zle -N fzf-history-widget
|
zle -N fzf-history-widget
|
||||||
bindkey '^R' fzf-history-widget
|
bindkey '^R' fzf-history-widget
|
||||||
|
|
||||||
fi
|
} always {
|
||||||
|
eval $__fzf_key_bindings_options
|
||||||
|
'unset' '__fzf_key_bindings_options'
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2017 Junegunn Choi
|
Copyright (c) 2013-2020 Junegunn Choi
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
|||||||
@@ -773,12 +773,17 @@ func PrefixMatch(caseSensitive bool, normalize bool, forward bool, text *util.Ch
|
|||||||
return Result{0, 0, 0}, nil
|
return Result{0, 0, 0}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if text.Length() < len(pattern) {
|
trimmedLen := 0
|
||||||
|
if !unicode.IsSpace(pattern[0]) {
|
||||||
|
trimmedLen = text.LeadingWhitespaces()
|
||||||
|
}
|
||||||
|
|
||||||
|
if text.Length()-trimmedLen < len(pattern) {
|
||||||
return Result{-1, -1, 0}, nil
|
return Result{-1, -1, 0}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
for index, r := range pattern {
|
for index, r := range pattern {
|
||||||
char := text.Get(index)
|
char := text.Get(trimmedLen + index)
|
||||||
if !caseSensitive {
|
if !caseSensitive {
|
||||||
char = unicode.ToLower(char)
|
char = unicode.ToLower(char)
|
||||||
}
|
}
|
||||||
@@ -790,14 +795,17 @@ func PrefixMatch(caseSensitive bool, normalize bool, forward bool, text *util.Ch
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
lenPattern := len(pattern)
|
lenPattern := len(pattern)
|
||||||
score, _ := calculateScore(caseSensitive, normalize, text, pattern, 0, lenPattern, false)
|
score, _ := calculateScore(caseSensitive, normalize, text, pattern, trimmedLen, trimmedLen+lenPattern, false)
|
||||||
return Result{0, lenPattern, score}, nil
|
return Result{trimmedLen, trimmedLen + lenPattern, score}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// SuffixMatch performs suffix-match
|
// SuffixMatch performs suffix-match
|
||||||
func SuffixMatch(caseSensitive bool, normalize bool, forward bool, text *util.Chars, pattern []rune, withPos bool, slab *util.Slab) (Result, *[]int) {
|
func SuffixMatch(caseSensitive bool, normalize bool, forward bool, text *util.Chars, pattern []rune, withPos bool, slab *util.Slab) (Result, *[]int) {
|
||||||
lenRunes := text.Length()
|
lenRunes := text.Length()
|
||||||
trimmedLen := lenRunes - text.TrailingWhitespaces()
|
trimmedLen := lenRunes
|
||||||
|
if len(pattern) == 0 || !unicode.IsSpace(pattern[len(pattern)-1]) {
|
||||||
|
trimmedLen -= text.TrailingWhitespaces()
|
||||||
|
}
|
||||||
if len(pattern) == 0 {
|
if len(pattern) == 0 {
|
||||||
return Result{trimmedLen, trimmedLen, 0}, nil
|
return Result{trimmedLen, trimmedLen, 0}, nil
|
||||||
}
|
}
|
||||||
@@ -828,14 +836,30 @@ func SuffixMatch(caseSensitive bool, normalize bool, forward bool, text *util.Ch
|
|||||||
// EqualMatch performs equal-match
|
// EqualMatch performs equal-match
|
||||||
func EqualMatch(caseSensitive bool, normalize bool, forward bool, text *util.Chars, pattern []rune, withPos bool, slab *util.Slab) (Result, *[]int) {
|
func EqualMatch(caseSensitive bool, normalize bool, forward bool, text *util.Chars, pattern []rune, withPos bool, slab *util.Slab) (Result, *[]int) {
|
||||||
lenPattern := len(pattern)
|
lenPattern := len(pattern)
|
||||||
if text.Length() != lenPattern {
|
if lenPattern == 0 {
|
||||||
|
return Result{-1, -1, 0}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Strip leading whitespaces
|
||||||
|
trimmedLen := 0
|
||||||
|
if !unicode.IsSpace(pattern[0]) {
|
||||||
|
trimmedLen = text.LeadingWhitespaces()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Strip trailing whitespaces
|
||||||
|
trimmedEndLen := 0
|
||||||
|
if !unicode.IsSpace(pattern[lenPattern-1]) {
|
||||||
|
trimmedEndLen = text.TrailingWhitespaces()
|
||||||
|
}
|
||||||
|
|
||||||
|
if text.Length()-trimmedLen-trimmedEndLen != lenPattern {
|
||||||
return Result{-1, -1, 0}, nil
|
return Result{-1, -1, 0}, nil
|
||||||
}
|
}
|
||||||
match := true
|
match := true
|
||||||
if normalize {
|
if normalize {
|
||||||
runes := text.ToRunes()
|
runes := text.ToRunes()
|
||||||
for idx, pchar := range pattern {
|
for idx, pchar := range pattern {
|
||||||
char := runes[idx]
|
char := runes[trimmedLen+idx]
|
||||||
if !caseSensitive {
|
if !caseSensitive {
|
||||||
char = unicode.To(unicode.LowerCase, char)
|
char = unicode.To(unicode.LowerCase, char)
|
||||||
}
|
}
|
||||||
@@ -845,14 +869,15 @@ func EqualMatch(caseSensitive bool, normalize bool, forward bool, text *util.Cha
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
runesStr := text.ToString()
|
runes := text.ToRunes()
|
||||||
|
runesStr := string(runes[trimmedLen : len(runes)-trimmedEndLen])
|
||||||
if !caseSensitive {
|
if !caseSensitive {
|
||||||
runesStr = strings.ToLower(runesStr)
|
runesStr = strings.ToLower(runesStr)
|
||||||
}
|
}
|
||||||
match = runesStr == string(pattern)
|
match = runesStr == string(pattern)
|
||||||
}
|
}
|
||||||
if match {
|
if match {
|
||||||
return Result{0, lenPattern, (scoreMatch+bonusBoundary)*lenPattern +
|
return Result{trimmedLen, trimmedLen + lenPattern, (scoreMatch+bonusBoundary)*lenPattern +
|
||||||
(bonusFirstCharMultiplier-1)*bonusBoundary}, nil
|
(bonusFirstCharMultiplier-1)*bonusBoundary}, nil
|
||||||
}
|
}
|
||||||
return Result{-1, -1, 0}, nil
|
return Result{-1, -1, 0}, nil
|
||||||
|
|||||||
@@ -136,6 +136,10 @@ func TestPrefixMatch(t *testing.T) {
|
|||||||
assertMatch(t, PrefixMatch, false, dir, "fooBarbaz", "Foo", 0, 3, score)
|
assertMatch(t, PrefixMatch, false, dir, "fooBarbaz", "Foo", 0, 3, score)
|
||||||
assertMatch(t, PrefixMatch, false, dir, "foOBarBaZ", "foo", 0, 3, score)
|
assertMatch(t, PrefixMatch, false, dir, "foOBarBaZ", "foo", 0, 3, score)
|
||||||
assertMatch(t, PrefixMatch, false, dir, "f-oBarbaz", "f-o", 0, 3, score)
|
assertMatch(t, PrefixMatch, false, dir, "f-oBarbaz", "f-o", 0, 3, score)
|
||||||
|
|
||||||
|
assertMatch(t, PrefixMatch, false, dir, " fooBar", "foo", 1, 4, score)
|
||||||
|
assertMatch(t, PrefixMatch, false, dir, " fooBar", " fo", 0, 3, score)
|
||||||
|
assertMatch(t, PrefixMatch, false, dir, " fo", "foo", -1, -1, 0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -148,6 +152,13 @@ func TestSuffixMatch(t *testing.T) {
|
|||||||
scoreMatch*3+bonusConsecutive*2)
|
scoreMatch*3+bonusConsecutive*2)
|
||||||
assertMatch(t, SuffixMatch, false, dir, "fooBarBaZ", "baz", 6, 9,
|
assertMatch(t, SuffixMatch, false, dir, "fooBarBaZ", "baz", 6, 9,
|
||||||
(scoreMatch+bonusCamel123)*3+bonusCamel123*(bonusFirstCharMultiplier-1))
|
(scoreMatch+bonusCamel123)*3+bonusCamel123*(bonusFirstCharMultiplier-1))
|
||||||
|
|
||||||
|
// Strip trailing white space from the string
|
||||||
|
assertMatch(t, SuffixMatch, false, dir, "fooBarbaz ", "baz", 6, 9,
|
||||||
|
scoreMatch*3+bonusConsecutive*2)
|
||||||
|
// Only when the pattern doesn't end with a space
|
||||||
|
assertMatch(t, SuffixMatch, false, dir, "fooBarbaz ", "baz ", 6, 10,
|
||||||
|
scoreMatch*4+bonusConsecutive*2+bonusNonWord)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -90,10 +90,11 @@ func init() {
|
|||||||
- http://ascii-table.com/ansi-escape-sequences.php
|
- http://ascii-table.com/ansi-escape-sequences.php
|
||||||
- http://ascii-table.com/ansi-escape-sequences-vt-100.php
|
- http://ascii-table.com/ansi-escape-sequences-vt-100.php
|
||||||
- http://tldp.org/HOWTO/Bash-Prompt-HOWTO/x405.html
|
- http://tldp.org/HOWTO/Bash-Prompt-HOWTO/x405.html
|
||||||
|
- https://invisible-island.net/xterm/ctlseqs/ctlseqs.html
|
||||||
*/
|
*/
|
||||||
// The following regular expression will include not all but most of the
|
// The following regular expression will include not all but most of the
|
||||||
// frequently used ANSI sequences
|
// frequently used ANSI sequences
|
||||||
ansiRegex = regexp.MustCompile("(?:\x1b[\\[()][0-9;]*[a-zA-Z@]|\x1b.|[\x0e\x0f]|.\x08)")
|
ansiRegex = regexp.MustCompile("(?:\x1b[\\[()][0-9;]*[a-zA-Z@]|\x1b][0-9];[[:print:]]+(?:\x1b\\\\|\x07)|\x1b.|[\x0e\x0f]|.\x08)")
|
||||||
}
|
}
|
||||||
|
|
||||||
func findAnsiStart(str string) int {
|
func findAnsiStart(str string) int {
|
||||||
@@ -243,6 +244,10 @@ func interpretCode(ansiCode string, prevState *ansiState) *ansiState {
|
|||||||
state.attr = state.attr | tui.Blink
|
state.attr = state.attr | tui.Blink
|
||||||
case 7:
|
case 7:
|
||||||
state.attr = state.attr | tui.Reverse
|
state.attr = state.attr | tui.Reverse
|
||||||
|
case 23: // tput rmso
|
||||||
|
state.attr = state.attr &^ tui.Italic
|
||||||
|
case 24: // tput rmul
|
||||||
|
state.attr = state.attr &^ tui.Underline
|
||||||
case 0:
|
case 0:
|
||||||
init()
|
init()
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import (
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
// Current version
|
// Current version
|
||||||
version = "0.19.0"
|
version = "0.21.1"
|
||||||
|
|
||||||
// Core
|
// Core
|
||||||
coordinatorDelayMax time.Duration = 100 * time.Millisecond
|
coordinatorDelayMax time.Duration = 100 * time.Millisecond
|
||||||
@@ -25,7 +25,7 @@ const (
|
|||||||
// Terminal
|
// Terminal
|
||||||
initialDelay = 20 * time.Millisecond
|
initialDelay = 20 * time.Millisecond
|
||||||
initialDelayTac = 100 * time.Millisecond
|
initialDelayTac = 100 * time.Millisecond
|
||||||
spinnerDuration = 200 * time.Millisecond
|
spinnerDuration = 100 * time.Millisecond
|
||||||
previewCancelWait = 500 * time.Millisecond
|
previewCancelWait = 500 * time.Millisecond
|
||||||
maxPatternLength = 300
|
maxPatternLength = 300
|
||||||
maxMulti = math.MaxInt32
|
maxMulti = math.MaxInt32
|
||||||
@@ -62,8 +62,6 @@ func init() {
|
|||||||
defaultCommand = `set -o pipefail; command find -L . -mindepth 1 \( -path '*/\.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' -o -fstype 'proc' \) -prune -o -type f -print -o -type l -print 2> /dev/null | cut -b3-`
|
defaultCommand = `set -o pipefail; command find -L . -mindepth 1 \( -path '*/\.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' -o -fstype 'proc' \) -prune -o -type f -print -o -type l -print 2> /dev/null | cut -b3-`
|
||||||
} else if os.Getenv("TERM") == "cygwin" {
|
} else if os.Getenv("TERM") == "cygwin" {
|
||||||
defaultCommand = `sh -c "command find -L . -mindepth 1 -path '*/\.*' -prune -o -type f -print -o -type l -print 2> /dev/null | cut -b3-"`
|
defaultCommand = `sh -c "command find -L . -mindepth 1 -path '*/\.*' -prune -o -type f -print -o -type l -print 2> /dev/null | cut -b3-"`
|
||||||
} else {
|
|
||||||
defaultCommand = `for /r %P in (*) do @(set "_curfile=%P" & set "_curfile=!_curfile:%__CD__%=!" & echo !_curfile!)`
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
21
src/core.go
21
src/core.go
@@ -224,10 +224,14 @@ func Run(opts *Options, revision string) {
|
|||||||
|
|
||||||
// Event coordination
|
// Event coordination
|
||||||
reading := true
|
reading := true
|
||||||
|
clearCache := util.Once(false)
|
||||||
|
clearSelection := util.Once(false)
|
||||||
ticks := 0
|
ticks := 0
|
||||||
var nextCommand *string
|
var nextCommand *string
|
||||||
restart := func(command string) {
|
restart := func(command string) {
|
||||||
reading = true
|
reading = true
|
||||||
|
clearCache = util.Once(true)
|
||||||
|
clearSelection = util.Once(true)
|
||||||
chunkList.Clear()
|
chunkList.Clear()
|
||||||
header = make([]string, 0, opts.HeaderLines)
|
header = make([]string, 0, opts.HeaderLines)
|
||||||
go reader.restart(command)
|
go reader.restart(command)
|
||||||
@@ -250,20 +254,20 @@ func Run(opts *Options, revision string) {
|
|||||||
switch evt {
|
switch evt {
|
||||||
|
|
||||||
case EvtReadNew, EvtReadFin:
|
case EvtReadNew, EvtReadFin:
|
||||||
clearCache := false
|
|
||||||
if evt == EvtReadFin && nextCommand != nil {
|
if evt == EvtReadFin && nextCommand != nil {
|
||||||
clearCache = true
|
|
||||||
restart(*nextCommand)
|
restart(*nextCommand)
|
||||||
nextCommand = nil
|
nextCommand = nil
|
||||||
|
break
|
||||||
} else {
|
} else {
|
||||||
reading = reading && evt == EvtReadNew
|
reading = reading && evt == EvtReadNew
|
||||||
}
|
}
|
||||||
snapshot, count := chunkList.Snapshot()
|
snapshot, count := chunkList.Snapshot()
|
||||||
terminal.UpdateCount(count, !reading, value.(*string))
|
terminal.UpdateCount(count, !reading, value.(*string))
|
||||||
if opts.Sync {
|
if opts.Sync {
|
||||||
terminal.UpdateList(PassMerger(&snapshot, opts.Tac))
|
opts.Sync = false
|
||||||
|
terminal.UpdateList(PassMerger(&snapshot, opts.Tac), false)
|
||||||
}
|
}
|
||||||
matcher.Reset(snapshot, input(), false, !reading, sort, clearCache)
|
matcher.Reset(snapshot, input(), false, !reading, sort, clearCache())
|
||||||
|
|
||||||
case EvtSearchNew:
|
case EvtSearchNew:
|
||||||
var command *string
|
var command *string
|
||||||
@@ -279,9 +283,10 @@ func Run(opts *Options, revision string) {
|
|||||||
} else {
|
} else {
|
||||||
restart(*command)
|
restart(*command)
|
||||||
}
|
}
|
||||||
|
break
|
||||||
}
|
}
|
||||||
snapshot, _ := chunkList.Snapshot()
|
snapshot, _ := chunkList.Snapshot()
|
||||||
matcher.Reset(snapshot, input(), true, !reading, sort, command != nil)
|
matcher.Reset(snapshot, input(), true, !reading, sort, clearCache())
|
||||||
delay = false
|
delay = false
|
||||||
|
|
||||||
case EvtSearchProgress:
|
case EvtSearchProgress:
|
||||||
@@ -291,7 +296,9 @@ func Run(opts *Options, revision string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
case EvtHeader:
|
case EvtHeader:
|
||||||
terminal.UpdateHeader(value.([]string))
|
headerPadded := make([]string, opts.HeaderLines)
|
||||||
|
copy(headerPadded, value.([]string))
|
||||||
|
terminal.UpdateHeader(headerPadded)
|
||||||
|
|
||||||
case EvtSearchFin:
|
case EvtSearchFin:
|
||||||
switch val := value.(type) {
|
switch val := value.(type) {
|
||||||
@@ -321,7 +328,7 @@ func Run(opts *Options, revision string) {
|
|||||||
terminal.startChan <- true
|
terminal.startChan <- true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
terminal.UpdateList(val)
|
terminal.UpdateList(val, clearSelection())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
133
src/options.go
133
src/options.go
@@ -6,12 +6,13 @@ import (
|
|||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"unicode"
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
|
|
||||||
"github.com/junegunn/fzf/src/algo"
|
"github.com/junegunn/fzf/src/algo"
|
||||||
"github.com/junegunn/fzf/src/tui"
|
"github.com/junegunn/fzf/src/tui"
|
||||||
"github.com/junegunn/fzf/src/util"
|
|
||||||
|
|
||||||
|
"github.com/mattn/go-runewidth"
|
||||||
"github.com/mattn/go-shellwords"
|
"github.com/mattn/go-shellwords"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -43,6 +44,7 @@ const usage = `usage: fzf [options]
|
|||||||
--no-mouse Disable mouse
|
--no-mouse Disable mouse
|
||||||
--bind=KEYBINDS Custom key bindings. Refer to the man page.
|
--bind=KEYBINDS Custom key bindings. Refer to the man page.
|
||||||
--cycle Enable cyclic scroll
|
--cycle Enable cyclic scroll
|
||||||
|
--keep-right Keep the right end of the line visible on overflow
|
||||||
--no-hscroll Disable horizontal scroll
|
--no-hscroll Disable horizontal scroll
|
||||||
--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)
|
||||||
@@ -55,10 +57,13 @@ const usage = `usage: fzf [options]
|
|||||||
--min-height=HEIGHT Minimum height when --height is given in percent
|
--min-height=HEIGHT Minimum height when --height is given in percent
|
||||||
(default: 10)
|
(default: 10)
|
||||||
--layout=LAYOUT Choose layout: [default|reverse|reverse-list]
|
--layout=LAYOUT Choose layout: [default|reverse|reverse-list]
|
||||||
--border Draw border above and below the finder
|
--border[=STYLE] Draw border around the finder
|
||||||
|
[rounded|sharp|horizontal] (default: rounded)
|
||||||
--margin=MARGIN Screen margin (TRBL / TB,RL / T,RL,B / T,R,B,L)
|
--margin=MARGIN Screen margin (TRBL / TB,RL / T,RL,B / T,R,B,L)
|
||||||
--info=STYLE Finder info style [default|inline|hidden]
|
--info=STYLE Finder info style [default|inline|hidden]
|
||||||
--prompt=STR Input prompt (default: '> ')
|
--prompt=STR Input prompt (default: '> ')
|
||||||
|
--pointer=STR Pointer to the current line (default: '>')
|
||||||
|
--marker=STR Multi-select marker (default: '>')
|
||||||
--header=STR String to print as header
|
--header=STR String to print as header
|
||||||
--header-lines=N The first N lines of the input are treated as header
|
--header-lines=N The first N lines of the input are treated as header
|
||||||
|
|
||||||
@@ -183,12 +188,15 @@ type Options struct {
|
|||||||
MinHeight int
|
MinHeight int
|
||||||
Layout layoutType
|
Layout layoutType
|
||||||
Cycle bool
|
Cycle bool
|
||||||
|
KeepRight bool
|
||||||
Hscroll bool
|
Hscroll bool
|
||||||
HscrollOff int
|
HscrollOff int
|
||||||
FileWord bool
|
FileWord bool
|
||||||
InfoStyle infoStyle
|
InfoStyle infoStyle
|
||||||
JumpLabels string
|
JumpLabels string
|
||||||
Prompt string
|
Prompt string
|
||||||
|
Pointer string
|
||||||
|
Marker string
|
||||||
Query string
|
Query string
|
||||||
Select1 bool
|
Select1 bool
|
||||||
Exit0 bool
|
Exit0 bool
|
||||||
@@ -206,7 +214,7 @@ type Options struct {
|
|||||||
Header []string
|
Header []string
|
||||||
HeaderLines int
|
HeaderLines int
|
||||||
Margin [4]sizeSpec
|
Margin [4]sizeSpec
|
||||||
Bordered bool
|
BorderShape tui.BorderShape
|
||||||
Unicode bool
|
Unicode bool
|
||||||
Tabstop int
|
Tabstop int
|
||||||
ClearOnExit bool
|
ClearOnExit bool
|
||||||
@@ -236,12 +244,15 @@ func defaultOptions() *Options {
|
|||||||
MinHeight: 10,
|
MinHeight: 10,
|
||||||
Layout: layoutDefault,
|
Layout: layoutDefault,
|
||||||
Cycle: false,
|
Cycle: false,
|
||||||
|
KeepRight: false,
|
||||||
Hscroll: true,
|
Hscroll: true,
|
||||||
HscrollOff: 10,
|
HscrollOff: 10,
|
||||||
FileWord: false,
|
FileWord: false,
|
||||||
InfoStyle: infoDefault,
|
InfoStyle: infoDefault,
|
||||||
JumpLabels: defaultJumpLabels,
|
JumpLabels: defaultJumpLabels,
|
||||||
Prompt: "> ",
|
Prompt: "> ",
|
||||||
|
Pointer: ">",
|
||||||
|
Marker: ">",
|
||||||
Query: "",
|
Query: "",
|
||||||
Select1: false,
|
Select1: false,
|
||||||
Exit0: false,
|
Exit0: false,
|
||||||
@@ -293,12 +304,12 @@ func nextString(args []string, i *int, message string) string {
|
|||||||
return args[*i]
|
return args[*i]
|
||||||
}
|
}
|
||||||
|
|
||||||
func optionalNextString(args []string, i *int) string {
|
func optionalNextString(args []string, i *int) (bool, string) {
|
||||||
if len(args) > *i+1 && !strings.HasPrefix(args[*i+1], "-") {
|
if len(args) > *i+1 && !strings.HasPrefix(args[*i+1], "-") && !strings.HasPrefix(args[*i+1], "+") {
|
||||||
*i++
|
*i++
|
||||||
return args[*i]
|
return true, args[*i]
|
||||||
}
|
}
|
||||||
return ""
|
return false, ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func atoi(str string) int {
|
func atoi(str string) int {
|
||||||
@@ -392,6 +403,23 @@ func parseAlgo(str string) algo.Algo {
|
|||||||
return algo.FuzzyMatchV2
|
return algo.FuzzyMatchV2
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func parseBorder(str string, optional bool) tui.BorderShape {
|
||||||
|
switch str {
|
||||||
|
case "rounded":
|
||||||
|
return tui.BorderRounded
|
||||||
|
case "sharp":
|
||||||
|
return tui.BorderSharp
|
||||||
|
case "horizontal":
|
||||||
|
return tui.BorderHorizontal
|
||||||
|
default:
|
||||||
|
if optional && str == "" {
|
||||||
|
return tui.BorderRounded
|
||||||
|
}
|
||||||
|
errorExit("invalid border style (expected: rounded|sharp|horizontal)")
|
||||||
|
}
|
||||||
|
return tui.BorderNone
|
||||||
|
}
|
||||||
|
|
||||||
func parseKeyChords(str string, message string) map[int]string {
|
func parseKeyChords(str string, message string) map[int]string {
|
||||||
if len(str) == 0 {
|
if len(str) == 0 {
|
||||||
errorExit(message)
|
errorExit(message)
|
||||||
@@ -464,6 +492,8 @@ func parseKeyChords(str string, message string) map[int]string {
|
|||||||
chord = tui.Home
|
chord = tui.Home
|
||||||
case "end":
|
case "end":
|
||||||
chord = tui.End
|
chord = tui.End
|
||||||
|
case "insert":
|
||||||
|
chord = tui.Insert
|
||||||
case "pgup", "page-up":
|
case "pgup", "page-up":
|
||||||
chord = tui.PgUp
|
chord = tui.PgUp
|
||||||
case "pgdn", "page-down":
|
case "pgdn", "page-down":
|
||||||
@@ -597,6 +627,10 @@ func parseTheme(defaultTheme *tui.ColorTheme, str string) *tui.ColorTheme {
|
|||||||
theme.Fg = ansi
|
theme.Fg = ansi
|
||||||
case "bg":
|
case "bg":
|
||||||
theme.Bg = ansi
|
theme.Bg = ansi
|
||||||
|
case "preview-fg":
|
||||||
|
theme.PreviewFg = ansi
|
||||||
|
case "preview-bg":
|
||||||
|
theme.PreviewBg = ansi
|
||||||
case "fg+":
|
case "fg+":
|
||||||
theme.Current = ansi
|
theme.Current = ansi
|
||||||
case "bg+":
|
case "bg+":
|
||||||
@@ -648,14 +682,18 @@ func init() {
|
|||||||
// Backreferences are not supported.
|
// Backreferences are not supported.
|
||||||
// "~!@#$%^&*;/|".each_char.map { |c| Regexp.escape(c) }.map { |c| "#{c}[^#{c}]*#{c}" }.join('|')
|
// "~!@#$%^&*;/|".each_char.map { |c| Regexp.escape(c) }.map { |c| "#{c}[^#{c}]*#{c}" }.join('|')
|
||||||
executeRegexp = regexp.MustCompile(
|
executeRegexp = regexp.MustCompile(
|
||||||
`(?si):(execute(?:-multi|-silent)?|reload):.+|:(execute(?:-multi|-silent)?|reload)(\([^)]*\)|\[[^\]]*\]|~[^~]*~|![^!]*!|@[^@]*@|\#[^\#]*\#|\$[^\$]*\$|%[^%]*%|\^[^\^]*\^|&[^&]*&|\*[^\*]*\*|;[^;]*;|/[^/]*/|\|[^\|]*\|)`)
|
`(?si)[:+](execute(?:-multi|-silent)?|reload):.+|[:+](execute(?:-multi|-silent)?|reload)(\([^)]*\)|\[[^\]]*\]|~[^~]*~|![^!]*!|@[^@]*@|\#[^\#]*\#|\$[^\$]*\$|%[^%]*%|\^[^\^]*\^|&[^&]*&|\*[^\*]*\*|;[^;]*;|/[^/]*/|\|[^\|]*\|)`)
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseKeymap(keymap map[int][]action, str string) {
|
func parseKeymap(keymap map[int][]action, str string) {
|
||||||
masked := executeRegexp.ReplaceAllStringFunc(str, func(src string) string {
|
masked := executeRegexp.ReplaceAllStringFunc(str, func(src string) string {
|
||||||
prefix := ":execute"
|
symbol := ":"
|
||||||
if strings.HasPrefix(src, ":reload") {
|
if strings.HasPrefix(src, "+") {
|
||||||
prefix = ":reload"
|
symbol = "+"
|
||||||
|
}
|
||||||
|
prefix := symbol + "execute"
|
||||||
|
if strings.HasPrefix(src[1:], "reload") {
|
||||||
|
prefix = symbol + "reload"
|
||||||
} else if src[len(prefix)] == '-' {
|
} else if src[len(prefix)] == '-' {
|
||||||
c := src[len(prefix)+1]
|
c := src[len(prefix)+1]
|
||||||
if c == 's' || c == 'S' {
|
if c == 's' || c == 'S' {
|
||||||
@@ -722,6 +760,8 @@ func parseKeymap(keymap map[int][]action, str string) {
|
|||||||
appendAction(actBackwardChar)
|
appendAction(actBackwardChar)
|
||||||
case "backward-delete-char":
|
case "backward-delete-char":
|
||||||
appendAction(actBackwardDeleteChar)
|
appendAction(actBackwardDeleteChar)
|
||||||
|
case "backward-delete-char/eof":
|
||||||
|
appendAction(actBackwardDeleteCharEOF)
|
||||||
case "backward-word":
|
case "backward-word":
|
||||||
appendAction(actBackwardWord)
|
appendAction(actBackwardWord)
|
||||||
case "clear-screen":
|
case "clear-screen":
|
||||||
@@ -734,6 +774,10 @@ func parseKeymap(keymap map[int][]action, str string) {
|
|||||||
appendAction(actEndOfLine)
|
appendAction(actEndOfLine)
|
||||||
case "cancel":
|
case "cancel":
|
||||||
appendAction(actCancel)
|
appendAction(actCancel)
|
||||||
|
case "clear-query":
|
||||||
|
appendAction(actClearQuery)
|
||||||
|
case "clear-selection":
|
||||||
|
appendAction(actClearSelection)
|
||||||
case "forward-char":
|
case "forward-char":
|
||||||
appendAction(actForwardChar)
|
appendAction(actForwardChar)
|
||||||
case "forward-word":
|
case "forward-word":
|
||||||
@@ -805,7 +849,11 @@ func parseKeymap(keymap map[int][]action, str string) {
|
|||||||
default:
|
default:
|
||||||
t := isExecuteAction(specLower)
|
t := isExecuteAction(specLower)
|
||||||
if t == actIgnore {
|
if t == actIgnore {
|
||||||
errorExit("unknown action: " + spec)
|
if specIndex == 0 && specLower == "" {
|
||||||
|
actions = append(keymap[key], actions...)
|
||||||
|
} else {
|
||||||
|
errorExit("unknown action: " + spec)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
var offset int
|
var offset int
|
||||||
switch t {
|
switch t {
|
||||||
@@ -1025,6 +1073,8 @@ func parseOptions(opts *Options, allArgs []string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
validateJumpLabels := false
|
validateJumpLabels := false
|
||||||
|
validatePointer := false
|
||||||
|
validateMarker := 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 {
|
||||||
@@ -1068,7 +1118,7 @@ func parseOptions(opts *Options, allArgs []string) {
|
|||||||
case "--bind":
|
case "--bind":
|
||||||
parseKeymap(opts.Keymap, nextString(allArgs, &i, "bind expression required"))
|
parseKeymap(opts.Keymap, nextString(allArgs, &i, "bind expression required"))
|
||||||
case "--color":
|
case "--color":
|
||||||
spec := optionalNextString(allArgs, &i)
|
_, spec := optionalNextString(allArgs, &i)
|
||||||
if len(spec) == 0 {
|
if len(spec) == 0 {
|
||||||
opts.Theme = tui.EmptyTheme()
|
opts.Theme = tui.EmptyTheme()
|
||||||
} else {
|
} else {
|
||||||
@@ -1127,6 +1177,10 @@ func parseOptions(opts *Options, allArgs []string) {
|
|||||||
opts.Cycle = true
|
opts.Cycle = true
|
||||||
case "--no-cycle":
|
case "--no-cycle":
|
||||||
opts.Cycle = false
|
opts.Cycle = false
|
||||||
|
case "--keep-right":
|
||||||
|
opts.KeepRight = true
|
||||||
|
case "--no-keep-right":
|
||||||
|
opts.KeepRight = false
|
||||||
case "--hscroll":
|
case "--hscroll":
|
||||||
opts.Hscroll = true
|
opts.Hscroll = true
|
||||||
case "--no-hscroll":
|
case "--no-hscroll":
|
||||||
@@ -1173,6 +1227,12 @@ func parseOptions(opts *Options, allArgs []string) {
|
|||||||
opts.PrintQuery = false
|
opts.PrintQuery = false
|
||||||
case "--prompt":
|
case "--prompt":
|
||||||
opts.Prompt = nextString(allArgs, &i, "prompt string required")
|
opts.Prompt = nextString(allArgs, &i, "prompt string required")
|
||||||
|
case "--pointer":
|
||||||
|
opts.Pointer = nextString(allArgs, &i, "pointer sign string required")
|
||||||
|
validatePointer = true
|
||||||
|
case "--marker":
|
||||||
|
opts.Marker = nextString(allArgs, &i, "selected sign string required")
|
||||||
|
validateMarker = true
|
||||||
case "--sync":
|
case "--sync":
|
||||||
opts.Sync = true
|
opts.Sync = true
|
||||||
case "--no-sync":
|
case "--no-sync":
|
||||||
@@ -1210,9 +1270,10 @@ func parseOptions(opts *Options, allArgs []string) {
|
|||||||
case "--no-margin":
|
case "--no-margin":
|
||||||
opts.Margin = defaultMargin()
|
opts.Margin = defaultMargin()
|
||||||
case "--no-border":
|
case "--no-border":
|
||||||
opts.Bordered = false
|
opts.BorderShape = tui.BorderNone
|
||||||
case "--border":
|
case "--border":
|
||||||
opts.Bordered = true
|
hasArg, arg := optionalNextString(allArgs, &i)
|
||||||
|
opts.BorderShape = parseBorder(arg, !hasArg)
|
||||||
case "--no-unicode":
|
case "--no-unicode":
|
||||||
opts.Unicode = false
|
opts.Unicode = false
|
||||||
case "--unicode":
|
case "--unicode":
|
||||||
@@ -1237,8 +1298,16 @@ func parseOptions(opts *Options, allArgs []string) {
|
|||||||
opts.Filter = &value
|
opts.Filter = &value
|
||||||
} else if match, value := optString(arg, "-d", "--delimiter="); match {
|
} else if match, value := optString(arg, "-d", "--delimiter="); match {
|
||||||
opts.Delimiter = delimiterRegexp(value)
|
opts.Delimiter = delimiterRegexp(value)
|
||||||
|
} else if match, value := optString(arg, "--border="); match {
|
||||||
|
opts.BorderShape = parseBorder(value, false)
|
||||||
} else if match, value := optString(arg, "--prompt="); match {
|
} else if match, value := optString(arg, "--prompt="); match {
|
||||||
opts.Prompt = value
|
opts.Prompt = value
|
||||||
|
} else if match, value := optString(arg, "--pointer="); match {
|
||||||
|
opts.Pointer = value
|
||||||
|
validatePointer = true
|
||||||
|
} else if match, value := optString(arg, "--marker="); match {
|
||||||
|
opts.Marker = value
|
||||||
|
validateMarker = true
|
||||||
} else if match, value := optString(arg, "-n", "--nth="); match {
|
} else if match, value := optString(arg, "-n", "--nth="); match {
|
||||||
opts.Nth = splitNth(value)
|
opts.Nth = splitNth(value)
|
||||||
} else if match, value := optString(arg, "--with-nth="); match {
|
} else if match, value := optString(arg, "--with-nth="); match {
|
||||||
@@ -1287,6 +1356,7 @@ func parseOptions(opts *Options, allArgs []string) {
|
|||||||
opts.HscrollOff = atoi(value)
|
opts.HscrollOff = atoi(value)
|
||||||
} else if match, value := optString(arg, "--jump-labels="); match {
|
} else if match, value := optString(arg, "--jump-labels="); match {
|
||||||
opts.JumpLabels = value
|
opts.JumpLabels = value
|
||||||
|
validateJumpLabels = true
|
||||||
} else {
|
} else {
|
||||||
errorExit("unknown option: " + arg)
|
errorExit("unknown option: " + arg)
|
||||||
}
|
}
|
||||||
@@ -1316,11 +1386,40 @@ func parseOptions(opts *Options, allArgs []string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if validatePointer {
|
||||||
|
if err := validateSign(opts.Pointer, "pointer"); err != nil {
|
||||||
|
errorExit(err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if validateMarker {
|
||||||
|
if err := validateSign(opts.Marker, "marker"); err != nil {
|
||||||
|
errorExit(err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateSign(sign string, signOptName string) error {
|
||||||
|
if sign == "" {
|
||||||
|
return fmt.Errorf("%v cannot be empty", signOptName)
|
||||||
|
}
|
||||||
|
widthSum := 0
|
||||||
|
for _, r := range sign {
|
||||||
|
if !unicode.IsGraphic(r) {
|
||||||
|
return fmt.Errorf("invalid character in %v", signOptName)
|
||||||
|
}
|
||||||
|
widthSum += runewidth.RuneWidth(r)
|
||||||
|
if widthSum > 2 {
|
||||||
|
return fmt.Errorf("%v display width should be up to 2", signOptName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func postProcessOptions(opts *Options) {
|
func postProcessOptions(opts *Options) {
|
||||||
if util.IsWindows() && opts.Height.size > 0 {
|
if !tui.IsLightRendererSupported() && opts.Height.size > 0 {
|
||||||
errorExit("--height option is currently not supported on Windows")
|
errorExit("--height option is currently not supported on this platform")
|
||||||
}
|
}
|
||||||
// Default actions for CTRL-N / CTRL-P when --history is set
|
// Default actions for CTRL-N / CTRL-P when --history is set
|
||||||
if opts.History != nil {
|
if opts.History != nil {
|
||||||
|
|||||||
@@ -243,9 +243,10 @@ func TestBind(t *testing.T) {
|
|||||||
check(tui.CtrlA, "", actBeginningOfLine)
|
check(tui.CtrlA, "", actBeginningOfLine)
|
||||||
parseKeymap(keymap,
|
parseKeymap(keymap,
|
||||||
"ctrl-a:kill-line,ctrl-b:toggle-sort+up+down,c:page-up,alt-z:page-down,"+
|
"ctrl-a:kill-line,ctrl-b:toggle-sort+up+down,c:page-up,alt-z:page-down,"+
|
||||||
"f1:execute(ls {})+abort,f2:execute/echo {}, {}, {}/,f3:execute[echo '({})'],f4:execute;less {};,"+
|
"f1:execute(ls {+})+abort+execute(echo {+})+select-all,f2:execute/echo {}, {}, {}/,f3:execute[echo '({})'],f4:execute;less {};,"+
|
||||||
"alt-a:execute-Multi@echo (,),[,],/,:,;,%,{}@,alt-b:execute;echo (,),[,],/,:,@,%,{};,"+
|
"alt-a:execute-Multi@echo (,),[,],/,:,;,%,{}@,alt-b:execute;echo (,),[,],/,:,@,%,{};,"+
|
||||||
"x:Execute(foo+bar),X:execute/bar+baz/"+
|
"x:Execute(foo+bar),X:execute/bar+baz/"+
|
||||||
|
",f1:+top,f1:+top"+
|
||||||
",,:abort,::accept,+:execute:++\nfoobar,Y:execute(baz)+up")
|
",,:abort,::accept,+:execute:++\nfoobar,Y:execute(baz)+up")
|
||||||
check(tui.CtrlA, "", actKillLine)
|
check(tui.CtrlA, "", actKillLine)
|
||||||
check(tui.CtrlB, "", actToggleSort, actUp, actDown)
|
check(tui.CtrlB, "", actToggleSort, actUp, actDown)
|
||||||
@@ -253,7 +254,7 @@ func TestBind(t *testing.T) {
|
|||||||
check(tui.AltZ+',', "", actAbort)
|
check(tui.AltZ+',', "", actAbort)
|
||||||
check(tui.AltZ+':', "", actAccept)
|
check(tui.AltZ+':', "", actAccept)
|
||||||
check(tui.AltZ, "", actPageDown)
|
check(tui.AltZ, "", actPageDown)
|
||||||
check(tui.F1, "ls {}", actExecute, actAbort)
|
check(tui.F1, "ls {+}", actExecute, actAbort, actExecute, actSelectAll, actTop, actTop)
|
||||||
check(tui.F2, "echo {}, {}, {}", actExecute)
|
check(tui.F2, "echo {}, {}, {}", actExecute)
|
||||||
check(tui.F3, "echo '({})'", actExecute)
|
check(tui.F3, "echo '({})'", actExecute)
|
||||||
check(tui.F4, "less {}", actExecute)
|
check(tui.F4, "less {}", actExecute)
|
||||||
@@ -421,3 +422,29 @@ func TestAdditiveExpect(t *testing.T) {
|
|||||||
t.Error(opts.Expect)
|
t.Error(opts.Expect)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestValidateSign(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
inputSign string
|
||||||
|
isValid bool
|
||||||
|
}{
|
||||||
|
{"> ", true},
|
||||||
|
{"아", true},
|
||||||
|
{"😀", true},
|
||||||
|
{"", false},
|
||||||
|
{">>>", false},
|
||||||
|
{"\n", false},
|
||||||
|
{"\t", false},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, testCase := range testCases {
|
||||||
|
err := validateSign(testCase.inputSign, "")
|
||||||
|
if testCase.isValid && err != nil {
|
||||||
|
t.Errorf("Input sign `%s` caused error", testCase.inputSign)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !testCase.isValid && err == nil {
|
||||||
|
t.Errorf("Input sign `%s` did not cause error", testCase.inputSign)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -98,6 +98,9 @@ func TestEqual(t *testing.T) {
|
|||||||
}
|
}
|
||||||
match("ABC", -1, -1)
|
match("ABC", -1, -1)
|
||||||
match("AbC", 0, 3)
|
match("AbC", 0, 3)
|
||||||
|
match("AbC ", 0, 3)
|
||||||
|
match(" AbC ", 1, 4)
|
||||||
|
match(" AbC", 2, 5)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCaseSensitivity(t *testing.T) {
|
func TestCaseSensitivity(t *testing.T) {
|
||||||
|
|||||||
8
src/protector/protector.go
Normal file
8
src/protector/protector.go
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
// +build !openbsd
|
||||||
|
|
||||||
|
package protector
|
||||||
|
|
||||||
|
// Protect calls OS specific protections like pledge on OpenBSD
|
||||||
|
func Protect() {
|
||||||
|
return
|
||||||
|
}
|
||||||
10
src/protector/protector_openbsd.go
Normal file
10
src/protector/protector_openbsd.go
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
// +build openbsd
|
||||||
|
|
||||||
|
package protector
|
||||||
|
|
||||||
|
import "golang.org/x/sys/unix"
|
||||||
|
|
||||||
|
// Protect calls OS specific protections like pledge on OpenBSD
|
||||||
|
func Protect() {
|
||||||
|
unix.PledgePromises("stdio rpath tty proc exec")
|
||||||
|
}
|
||||||
@@ -2,14 +2,17 @@ package fzf
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
|
"context"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/junegunn/fzf/src/util"
|
"github.com/junegunn/fzf/src/util"
|
||||||
|
"github.com/saracen/walker"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Reader reads from command or standard input
|
// Reader reads from command or standard input
|
||||||
@@ -78,7 +81,7 @@ func (r *Reader) terminate() {
|
|||||||
r.killed = true
|
r.killed = true
|
||||||
if r.exec != nil && r.exec.Process != nil {
|
if r.exec != nil && r.exec.Process != nil {
|
||||||
util.KillCommand(r.exec)
|
util.KillCommand(r.exec)
|
||||||
} else {
|
} else if defaultCommand != "" {
|
||||||
os.Stdin.Close()
|
os.Stdin.Close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -99,7 +102,11 @@ func (r *Reader) ReadSource() {
|
|||||||
shell := "bash"
|
shell := "bash"
|
||||||
cmd := os.Getenv("FZF_DEFAULT_COMMAND")
|
cmd := os.Getenv("FZF_DEFAULT_COMMAND")
|
||||||
if len(cmd) == 0 {
|
if len(cmd) == 0 {
|
||||||
success = r.readFromCommand(&shell, defaultCommand)
|
if defaultCommand != "" {
|
||||||
|
success = r.readFromCommand(&shell, defaultCommand)
|
||||||
|
} else {
|
||||||
|
success = r.readFiles()
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
success = r.readFromCommand(nil, cmd)
|
success = r.readFromCommand(nil, cmd)
|
||||||
}
|
}
|
||||||
@@ -144,6 +151,32 @@ func (r *Reader) readFromStdin() bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *Reader) readFiles() bool {
|
||||||
|
r.killed = false
|
||||||
|
fn := func(path string, mode os.FileInfo) error {
|
||||||
|
path = filepath.Clean(path)
|
||||||
|
if path != "." {
|
||||||
|
isDir := mode.Mode().IsDir()
|
||||||
|
if isDir && filepath.Base(path)[0] == '.' {
|
||||||
|
return filepath.SkipDir
|
||||||
|
}
|
||||||
|
if !isDir && r.pusher([]byte(path)) {
|
||||||
|
atomic.StoreInt32(&r.event, int32(EvtReadNew))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
r.mutex.Lock()
|
||||||
|
defer r.mutex.Unlock()
|
||||||
|
if r.killed {
|
||||||
|
return context.Canceled
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
cb := walker.WithErrorCallback(func(pathname string, err error) error {
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
return walker.Walk(".", fn, cb) == nil
|
||||||
|
}
|
||||||
|
|
||||||
func (r *Reader) readFromCommand(shell *string, command string) bool {
|
func (r *Reader) readFromCommand(shell *string, command string) bool {
|
||||||
r.mutex.Lock()
|
r.mutex.Lock()
|
||||||
r.killed = false
|
r.killed = false
|
||||||
|
|||||||
409
src/terminal.go
409
src/terminal.go
@@ -25,6 +25,8 @@ import (
|
|||||||
var placeholder *regexp.Regexp
|
var placeholder *regexp.Regexp
|
||||||
var activeTempFiles []string
|
var activeTempFiles []string
|
||||||
|
|
||||||
|
const ellipsis string = ".."
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
placeholder = regexp.MustCompile(`\\?(?:{[+sf]*[0-9,-.]*}|{q}|{\+?f?nf?})`)
|
placeholder = regexp.MustCompile(`\\?(?:{[+sf]*[0-9,-.]*}|{q}|{\+?f?nf?})`)
|
||||||
activeTempFiles = []string{}
|
activeTempFiles = []string{}
|
||||||
@@ -59,71 +61,79 @@ var emptyLine = itemLine{}
|
|||||||
|
|
||||||
// Terminal represents terminal input/output
|
// Terminal represents terminal input/output
|
||||||
type Terminal struct {
|
type Terminal struct {
|
||||||
initDelay time.Duration
|
initDelay time.Duration
|
||||||
infoStyle infoStyle
|
infoStyle infoStyle
|
||||||
prompt string
|
spinner []string
|
||||||
promptLen int
|
prompt string
|
||||||
queryLen [2]int
|
promptLen int
|
||||||
layout layoutType
|
pointer string
|
||||||
fullscreen bool
|
pointerLen int
|
||||||
hscroll bool
|
pointerEmpty string
|
||||||
hscrollOff int
|
marker string
|
||||||
wordRubout string
|
markerLen int
|
||||||
wordNext string
|
markerEmpty string
|
||||||
cx int
|
queryLen [2]int
|
||||||
cy int
|
layout layoutType
|
||||||
offset int
|
fullscreen bool
|
||||||
xoffset int
|
keepRight bool
|
||||||
yanked []rune
|
hscroll bool
|
||||||
input []rune
|
hscrollOff int
|
||||||
multi int
|
wordRubout string
|
||||||
sort bool
|
wordNext string
|
||||||
toggleSort bool
|
cx int
|
||||||
delimiter Delimiter
|
cy int
|
||||||
expect map[int]string
|
offset int
|
||||||
keymap map[int][]action
|
xoffset int
|
||||||
pressed string
|
yanked []rune
|
||||||
printQuery bool
|
input []rune
|
||||||
history *History
|
multi int
|
||||||
cycle bool
|
sort bool
|
||||||
header []string
|
toggleSort bool
|
||||||
header0 []string
|
delimiter Delimiter
|
||||||
ansi bool
|
expect map[int]string
|
||||||
tabstop int
|
keymap map[int][]action
|
||||||
margin [4]sizeSpec
|
pressed string
|
||||||
strong tui.Attr
|
printQuery bool
|
||||||
unicode bool
|
history *History
|
||||||
bordered bool
|
cycle bool
|
||||||
cleanExit bool
|
header []string
|
||||||
border tui.Window
|
header0 []string
|
||||||
window tui.Window
|
ansi bool
|
||||||
pborder tui.Window
|
tabstop int
|
||||||
pwindow tui.Window
|
margin [4]sizeSpec
|
||||||
count int
|
strong tui.Attr
|
||||||
progress int
|
unicode bool
|
||||||
reading bool
|
borderShape tui.BorderShape
|
||||||
failed *string
|
cleanExit bool
|
||||||
jumping jumpMode
|
border tui.Window
|
||||||
jumpLabels string
|
window tui.Window
|
||||||
printer func(string)
|
pborder tui.Window
|
||||||
printsep string
|
pwindow tui.Window
|
||||||
merger *Merger
|
count int
|
||||||
selected map[int32]selectedItem
|
progress int
|
||||||
version int64
|
reading bool
|
||||||
reqBox *util.EventBox
|
failed *string
|
||||||
preview previewOpts
|
jumping jumpMode
|
||||||
previewer previewer
|
jumpLabels string
|
||||||
previewBox *util.EventBox
|
printer func(string)
|
||||||
eventBox *util.EventBox
|
printsep string
|
||||||
mutex sync.Mutex
|
merger *Merger
|
||||||
initFunc func()
|
selected map[int32]selectedItem
|
||||||
prevLines []itemLine
|
version int64
|
||||||
suppress bool
|
reqBox *util.EventBox
|
||||||
startChan chan bool
|
preview previewOpts
|
||||||
killChan chan int
|
previewer previewer
|
||||||
slab *util.Slab
|
previewBox *util.EventBox
|
||||||
theme *tui.ColorTheme
|
eventBox *util.EventBox
|
||||||
tui tui.Renderer
|
mutex sync.Mutex
|
||||||
|
initFunc func()
|
||||||
|
prevLines []itemLine
|
||||||
|
suppress bool
|
||||||
|
startChan chan bool
|
||||||
|
killChan chan int
|
||||||
|
slab *util.Slab
|
||||||
|
theme *tui.ColorTheme
|
||||||
|
tui tui.Renderer
|
||||||
}
|
}
|
||||||
|
|
||||||
type selectedItem struct {
|
type selectedItem struct {
|
||||||
@@ -145,8 +155,6 @@ func (a byTimeOrder) Less(i, j int) bool {
|
|||||||
return a[i].at.Before(a[j].at)
|
return a[i].at.Before(a[j].at)
|
||||||
}
|
}
|
||||||
|
|
||||||
var _spinner = []string{`-`, `\`, `|`, `/`, `-`, `\`, `|`, `/`}
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
reqPrompt util.EventType = iota
|
reqPrompt util.EventType = iota
|
||||||
reqInfo
|
reqInfo
|
||||||
@@ -182,9 +190,12 @@ const (
|
|||||||
actAcceptNonEmpty
|
actAcceptNonEmpty
|
||||||
actBackwardChar
|
actBackwardChar
|
||||||
actBackwardDeleteChar
|
actBackwardDeleteChar
|
||||||
|
actBackwardDeleteCharEOF
|
||||||
actBackwardWord
|
actBackwardWord
|
||||||
actCancel
|
actCancel
|
||||||
actClearScreen
|
actClearScreen
|
||||||
|
actClearQuery
|
||||||
|
actClearSelection
|
||||||
actDeleteChar
|
actDeleteChar
|
||||||
actDeleteCharEOF
|
actDeleteCharEOF
|
||||||
actEndOfLine
|
actEndOfLine
|
||||||
@@ -362,9 +373,9 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
|
|||||||
effectiveMinHeight *= 2
|
effectiveMinHeight *= 2
|
||||||
}
|
}
|
||||||
if opts.InfoStyle != infoDefault {
|
if opts.InfoStyle != infoDefault {
|
||||||
effectiveMinHeight -= 1
|
effectiveMinHeight--
|
||||||
}
|
}
|
||||||
if opts.Bordered {
|
if opts.BorderShape != tui.BorderNone {
|
||||||
effectiveMinHeight += 2
|
effectiveMinHeight += 2
|
||||||
}
|
}
|
||||||
return util.Min(termHeight, util.Max(maxHeight, effectiveMinHeight))
|
return util.Min(termHeight, util.Max(maxHeight, effectiveMinHeight))
|
||||||
@@ -378,63 +389,75 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
|
|||||||
wordRubout = fmt.Sprintf("%s[^%s]", sep, sep)
|
wordRubout = fmt.Sprintf("%s[^%s]", sep, sep)
|
||||||
wordNext = fmt.Sprintf("[^%s]%s|(.$)", sep, sep)
|
wordNext = fmt.Sprintf("[^%s]%s|(.$)", sep, sep)
|
||||||
}
|
}
|
||||||
|
spinner := []string{`⠋`, `⠙`, `⠹`, `⠸`, `⠼`, `⠴`, `⠦`, `⠧`, `⠇`, `⠏`}
|
||||||
|
if !opts.Unicode {
|
||||||
|
spinner = []string{`-`, `\`, `|`, `/`, `-`, `\`, `|`, `/`}
|
||||||
|
}
|
||||||
t := Terminal{
|
t := Terminal{
|
||||||
initDelay: delay,
|
initDelay: delay,
|
||||||
infoStyle: opts.InfoStyle,
|
infoStyle: opts.InfoStyle,
|
||||||
queryLen: [2]int{0, 0},
|
spinner: spinner,
|
||||||
layout: opts.Layout,
|
queryLen: [2]int{0, 0},
|
||||||
fullscreen: fullscreen,
|
layout: opts.Layout,
|
||||||
hscroll: opts.Hscroll,
|
fullscreen: fullscreen,
|
||||||
hscrollOff: opts.HscrollOff,
|
keepRight: opts.KeepRight,
|
||||||
wordRubout: wordRubout,
|
hscroll: opts.Hscroll,
|
||||||
wordNext: wordNext,
|
hscrollOff: opts.HscrollOff,
|
||||||
cx: len(input),
|
wordRubout: wordRubout,
|
||||||
cy: 0,
|
wordNext: wordNext,
|
||||||
offset: 0,
|
cx: len(input),
|
||||||
xoffset: 0,
|
cy: 0,
|
||||||
yanked: []rune{},
|
offset: 0,
|
||||||
input: input,
|
xoffset: 0,
|
||||||
multi: opts.Multi,
|
yanked: []rune{},
|
||||||
sort: opts.Sort > 0,
|
input: input,
|
||||||
toggleSort: opts.ToggleSort,
|
multi: opts.Multi,
|
||||||
delimiter: opts.Delimiter,
|
sort: opts.Sort > 0,
|
||||||
expect: opts.Expect,
|
toggleSort: opts.ToggleSort,
|
||||||
keymap: opts.Keymap,
|
delimiter: opts.Delimiter,
|
||||||
pressed: "",
|
expect: opts.Expect,
|
||||||
printQuery: opts.PrintQuery,
|
keymap: opts.Keymap,
|
||||||
history: opts.History,
|
pressed: "",
|
||||||
margin: opts.Margin,
|
printQuery: opts.PrintQuery,
|
||||||
unicode: opts.Unicode,
|
history: opts.History,
|
||||||
bordered: opts.Bordered,
|
margin: opts.Margin,
|
||||||
cleanExit: opts.ClearOnExit,
|
unicode: opts.Unicode,
|
||||||
strong: strongAttr,
|
borderShape: opts.BorderShape,
|
||||||
cycle: opts.Cycle,
|
cleanExit: opts.ClearOnExit,
|
||||||
header: header,
|
strong: strongAttr,
|
||||||
header0: header,
|
cycle: opts.Cycle,
|
||||||
ansi: opts.Ansi,
|
header: header,
|
||||||
tabstop: opts.Tabstop,
|
header0: header,
|
||||||
reading: true,
|
ansi: opts.Ansi,
|
||||||
failed: nil,
|
tabstop: opts.Tabstop,
|
||||||
jumping: jumpDisabled,
|
reading: true,
|
||||||
jumpLabels: opts.JumpLabels,
|
failed: nil,
|
||||||
printer: opts.Printer,
|
jumping: jumpDisabled,
|
||||||
printsep: opts.PrintSep,
|
jumpLabels: opts.JumpLabels,
|
||||||
merger: EmptyMerger,
|
printer: opts.Printer,
|
||||||
selected: make(map[int32]selectedItem),
|
printsep: opts.PrintSep,
|
||||||
reqBox: util.NewEventBox(),
|
merger: EmptyMerger,
|
||||||
preview: opts.Preview,
|
selected: make(map[int32]selectedItem),
|
||||||
previewer: previewer{"", 0, 0, previewBox != nil && !opts.Preview.hidden, false},
|
reqBox: util.NewEventBox(),
|
||||||
previewBox: previewBox,
|
preview: opts.Preview,
|
||||||
eventBox: eventBox,
|
previewer: previewer{"", 0, 0, previewBox != nil && !opts.Preview.hidden, false},
|
||||||
mutex: sync.Mutex{},
|
previewBox: previewBox,
|
||||||
suppress: true,
|
eventBox: eventBox,
|
||||||
slab: util.MakeSlab(slab16Size, slab32Size),
|
mutex: sync.Mutex{},
|
||||||
theme: opts.Theme,
|
suppress: true,
|
||||||
startChan: make(chan bool, 1),
|
slab: util.MakeSlab(slab16Size, slab32Size),
|
||||||
killChan: make(chan int),
|
theme: opts.Theme,
|
||||||
tui: renderer,
|
startChan: make(chan bool, 1),
|
||||||
initFunc: func() { renderer.Init() }}
|
killChan: make(chan int),
|
||||||
|
tui: renderer,
|
||||||
|
initFunc: func() { renderer.Init() }}
|
||||||
t.prompt, t.promptLen = t.processTabs([]rune(opts.Prompt), 0)
|
t.prompt, t.promptLen = t.processTabs([]rune(opts.Prompt), 0)
|
||||||
|
t.pointer, t.pointerLen = t.processTabs([]rune(opts.Pointer), 0)
|
||||||
|
t.marker, t.markerLen = t.processTabs([]rune(opts.Marker), 0)
|
||||||
|
// Pre-calculated empty pointer and marker signs
|
||||||
|
t.pointerEmpty = strings.Repeat(" ", t.pointerLen)
|
||||||
|
t.markerEmpty = strings.Repeat(" ", t.markerLen)
|
||||||
|
|
||||||
return &t
|
return &t
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -493,10 +516,13 @@ func (t *Terminal) UpdateProgress(progress float32) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// UpdateList updates Merger to display the list
|
// UpdateList updates Merger to display the list
|
||||||
func (t *Terminal) UpdateList(merger *Merger) {
|
func (t *Terminal) UpdateList(merger *Merger, reset bool) {
|
||||||
t.mutex.Lock()
|
t.mutex.Lock()
|
||||||
t.progress = 100
|
t.progress = 100
|
||||||
t.merger = merger
|
t.merger = merger
|
||||||
|
if reset {
|
||||||
|
t.selected = make(map[int32]selectedItem)
|
||||||
|
}
|
||||||
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)
|
||||||
@@ -573,8 +599,11 @@ func (t *Terminal) resizeWindows() {
|
|||||||
} else {
|
} else {
|
||||||
marginInt[idx] = int(sizeSpec.size)
|
marginInt[idx] = int(sizeSpec.size)
|
||||||
}
|
}
|
||||||
if t.bordered && idx%2 == 0 {
|
switch t.borderShape {
|
||||||
marginInt[idx] += 1
|
case tui.BorderHorizontal:
|
||||||
|
marginInt[idx] += 1 - idx%2
|
||||||
|
case tui.BorderRounded, tui.BorderSharp:
|
||||||
|
marginInt[idx] += 1 + idx%2
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
adjust := func(idx1 int, idx2 int, max int, min int) {
|
adjust := func(idx1 int, idx2 int, max int, min int) {
|
||||||
@@ -614,21 +643,30 @@ func (t *Terminal) resizeWindows() {
|
|||||||
|
|
||||||
width := screenWidth - marginInt[1] - marginInt[3]
|
width := screenWidth - marginInt[1] - marginInt[3]
|
||||||
height := screenHeight - marginInt[0] - marginInt[2]
|
height := screenHeight - marginInt[0] - marginInt[2]
|
||||||
if t.bordered {
|
switch t.borderShape {
|
||||||
|
case tui.BorderHorizontal:
|
||||||
t.border = t.tui.NewWindow(
|
t.border = t.tui.NewWindow(
|
||||||
marginInt[0]-1,
|
marginInt[0]-1,
|
||||||
marginInt[3],
|
marginInt[3],
|
||||||
width,
|
width,
|
||||||
height+2, tui.MakeBorderStyle(tui.BorderHorizontal, t.unicode))
|
height+2,
|
||||||
|
false, tui.MakeBorderStyle(tui.BorderHorizontal, t.unicode))
|
||||||
|
case tui.BorderRounded, tui.BorderSharp:
|
||||||
|
t.border = t.tui.NewWindow(
|
||||||
|
marginInt[0]-1,
|
||||||
|
marginInt[3]-2,
|
||||||
|
width+4,
|
||||||
|
height+2,
|
||||||
|
false, tui.MakeBorderStyle(t.borderShape, t.unicode))
|
||||||
}
|
}
|
||||||
noBorder := tui.MakeBorderStyle(tui.BorderNone, t.unicode)
|
noBorder := tui.MakeBorderStyle(tui.BorderNone, t.unicode)
|
||||||
if previewVisible {
|
if previewVisible {
|
||||||
createPreviewWindow := func(y int, x int, w int, h int) {
|
createPreviewWindow := func(y int, x int, w int, h int) {
|
||||||
previewBorder := tui.MakeBorderStyle(tui.BorderAround, t.unicode)
|
previewBorder := tui.MakeBorderStyle(tui.BorderRounded, t.unicode)
|
||||||
if !t.preview.border {
|
if !t.preview.border {
|
||||||
previewBorder = tui.MakeTransparentBorder()
|
previewBorder = tui.MakeTransparentBorder()
|
||||||
}
|
}
|
||||||
t.pborder = t.tui.NewWindow(y, x, w, h, previewBorder)
|
t.pborder = t.tui.NewWindow(y, x, w, h, true, previewBorder)
|
||||||
pwidth := w - 4
|
pwidth := w - 4
|
||||||
// ncurses auto-wraps the line when the cursor reaches the right-end of
|
// ncurses auto-wraps the line when the cursor reaches the right-end of
|
||||||
// the window. To prevent unintended line-wraps, we use the width one
|
// the window. To prevent unintended line-wraps, we use the width one
|
||||||
@@ -636,28 +674,28 @@ func (t *Terminal) resizeWindows() {
|
|||||||
if !t.preview.wrap && t.tui.DoesAutoWrap() {
|
if !t.preview.wrap && t.tui.DoesAutoWrap() {
|
||||||
pwidth += 1
|
pwidth += 1
|
||||||
}
|
}
|
||||||
t.pwindow = t.tui.NewWindow(y+1, x+2, pwidth, h-2, noBorder)
|
t.pwindow = t.tui.NewWindow(y+1, x+2, pwidth, h-2, true, noBorder)
|
||||||
}
|
}
|
||||||
switch t.preview.position {
|
switch t.preview.position {
|
||||||
case posUp:
|
case posUp:
|
||||||
pheight := calculateSize(height, t.preview.size, minHeight, 3)
|
pheight := calculateSize(height, t.preview.size, minHeight, 3)
|
||||||
t.window = t.tui.NewWindow(
|
t.window = t.tui.NewWindow(
|
||||||
marginInt[0]+pheight, marginInt[3], width, height-pheight, noBorder)
|
marginInt[0]+pheight, marginInt[3], width, height-pheight, false, noBorder)
|
||||||
createPreviewWindow(marginInt[0], marginInt[3], width, pheight)
|
createPreviewWindow(marginInt[0], marginInt[3], width, pheight)
|
||||||
case posDown:
|
case posDown:
|
||||||
pheight := calculateSize(height, t.preview.size, minHeight, 3)
|
pheight := calculateSize(height, t.preview.size, minHeight, 3)
|
||||||
t.window = t.tui.NewWindow(
|
t.window = t.tui.NewWindow(
|
||||||
marginInt[0], marginInt[3], width, height-pheight, noBorder)
|
marginInt[0], marginInt[3], width, height-pheight, false, noBorder)
|
||||||
createPreviewWindow(marginInt[0]+height-pheight, marginInt[3], width, pheight)
|
createPreviewWindow(marginInt[0]+height-pheight, marginInt[3], width, pheight)
|
||||||
case posLeft:
|
case posLeft:
|
||||||
pwidth := calculateSize(width, t.preview.size, minWidth, 5)
|
pwidth := calculateSize(width, t.preview.size, minWidth, 5)
|
||||||
t.window = t.tui.NewWindow(
|
t.window = t.tui.NewWindow(
|
||||||
marginInt[0], marginInt[3]+pwidth, width-pwidth, height, noBorder)
|
marginInt[0], marginInt[3]+pwidth, width-pwidth, height, false, noBorder)
|
||||||
createPreviewWindow(marginInt[0], marginInt[3], pwidth, height)
|
createPreviewWindow(marginInt[0], marginInt[3], pwidth, height)
|
||||||
case posRight:
|
case posRight:
|
||||||
pwidth := calculateSize(width, t.preview.size, minWidth, 5)
|
pwidth := calculateSize(width, t.preview.size, minWidth, 5)
|
||||||
t.window = t.tui.NewWindow(
|
t.window = t.tui.NewWindow(
|
||||||
marginInt[0], marginInt[3], width-pwidth, height, noBorder)
|
marginInt[0], marginInt[3], width-pwidth, height, false, noBorder)
|
||||||
createPreviewWindow(marginInt[0], marginInt[3]+width-pwidth, pwidth, height)
|
createPreviewWindow(marginInt[0], marginInt[3]+width-pwidth, pwidth, height)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -665,7 +703,7 @@ func (t *Terminal) resizeWindows() {
|
|||||||
marginInt[0],
|
marginInt[0],
|
||||||
marginInt[3],
|
marginInt[3],
|
||||||
width,
|
width,
|
||||||
height, noBorder)
|
height, false, noBorder)
|
||||||
}
|
}
|
||||||
for i := 0; i < t.window.Height(); i++ {
|
for i := 0; i < t.window.Height(); i++ {
|
||||||
t.window.MoveAndClear(i, 0)
|
t.window.MoveAndClear(i, 0)
|
||||||
@@ -738,8 +776,8 @@ func (t *Terminal) printInfo() {
|
|||||||
t.move(1, 0, true)
|
t.move(1, 0, true)
|
||||||
if t.reading {
|
if t.reading {
|
||||||
duration := int64(spinnerDuration)
|
duration := int64(spinnerDuration)
|
||||||
idx := (time.Now().UnixNano() % (duration * int64(len(_spinner)))) / duration
|
idx := (time.Now().UnixNano() % (duration * int64(len(t.spinner)))) / duration
|
||||||
t.window.CPrint(tui.ColSpinner, t.strong, _spinner[idx])
|
t.window.CPrint(tui.ColSpinner, t.strong, t.spinner[idx])
|
||||||
}
|
}
|
||||||
t.move(1, 2, false)
|
t.move(1, 2, false)
|
||||||
pos = 2
|
pos = 2
|
||||||
@@ -785,7 +823,7 @@ func (t *Terminal) printInfo() {
|
|||||||
maxWidth := t.window.Width() - pos
|
maxWidth := t.window.Width() - pos
|
||||||
if len(output) > maxWidth {
|
if len(output) > maxWidth {
|
||||||
outputRunes, _ := t.trimRight([]rune(output), maxWidth-2)
|
outputRunes, _ := t.trimRight([]rune(output), maxWidth-2)
|
||||||
output = string(outputRunes) + ".."
|
output = string(outputRunes) + strings.Repeat(".", util.Constrain(maxWidth, 0, 2))
|
||||||
}
|
}
|
||||||
t.window.CPrint(tui.ColInfo, 0, output)
|
t.window.CPrint(tui.ColInfo, 0, output)
|
||||||
}
|
}
|
||||||
@@ -842,15 +880,15 @@ func (t *Terminal) printList() {
|
|||||||
func (t *Terminal) printItem(result Result, line int, i int, current bool) {
|
func (t *Terminal) printItem(result Result, line int, i int, current bool) {
|
||||||
item := result.item
|
item := result.item
|
||||||
_, selected := t.selected[item.Index()]
|
_, selected := t.selected[item.Index()]
|
||||||
label := " "
|
label := t.pointerEmpty
|
||||||
if t.jumping != jumpDisabled {
|
if t.jumping != jumpDisabled {
|
||||||
if i < len(t.jumpLabels) {
|
if i < len(t.jumpLabels) {
|
||||||
// Striped
|
// Striped
|
||||||
current = i%2 == 0
|
current = i%2 == 0
|
||||||
label = t.jumpLabels[i : i+1]
|
label = t.jumpLabels[i:i+1] + strings.Repeat(" ", t.pointerLen-1)
|
||||||
}
|
}
|
||||||
} else if current {
|
} else if current {
|
||||||
label = ">"
|
label = t.pointer
|
||||||
}
|
}
|
||||||
|
|
||||||
// Avoid unnecessary redraw
|
// Avoid unnecessary redraw
|
||||||
@@ -869,17 +907,17 @@ func (t *Terminal) printItem(result Result, line int, i int, current bool) {
|
|||||||
if current {
|
if current {
|
||||||
t.window.CPrint(tui.ColCurrentCursor, t.strong, label)
|
t.window.CPrint(tui.ColCurrentCursor, t.strong, label)
|
||||||
if selected {
|
if selected {
|
||||||
t.window.CPrint(tui.ColCurrentSelected, t.strong, ">")
|
t.window.CPrint(tui.ColCurrentSelected, t.strong, t.marker)
|
||||||
} else {
|
} else {
|
||||||
t.window.CPrint(tui.ColCurrentSelected, t.strong, " ")
|
t.window.CPrint(tui.ColCurrentSelected, t.strong, t.markerEmpty)
|
||||||
}
|
}
|
||||||
newLine.width = t.printHighlighted(result, t.strong, tui.ColCurrent, tui.ColCurrentMatch, true, true)
|
newLine.width = t.printHighlighted(result, t.strong, tui.ColCurrent, tui.ColCurrentMatch, true, true)
|
||||||
} else {
|
} else {
|
||||||
t.window.CPrint(tui.ColCursor, t.strong, label)
|
t.window.CPrint(tui.ColCursor, t.strong, label)
|
||||||
if selected {
|
if selected {
|
||||||
t.window.CPrint(tui.ColSelected, t.strong, ">")
|
t.window.CPrint(tui.ColSelected, t.strong, t.marker)
|
||||||
} else {
|
} else {
|
||||||
t.window.Print(" ")
|
t.window.Print(t.markerEmpty)
|
||||||
}
|
}
|
||||||
newLine.width = t.printHighlighted(result, 0, tui.ColNormal, tui.ColMatch, false, true)
|
newLine.width = t.printHighlighted(result, 0, tui.ColNormal, tui.ColMatch, false, true)
|
||||||
}
|
}
|
||||||
@@ -965,34 +1003,41 @@ func (t *Terminal) printHighlighted(result Result, attr tui.Attr, col1 tui.Color
|
|||||||
maxe = util.Constrain(maxe+util.Min(maxWidth/2-2, t.hscrollOff), 0, len(text))
|
maxe = util.Constrain(maxe+util.Min(maxWidth/2-2, t.hscrollOff), 0, len(text))
|
||||||
displayWidth := t.displayWidthWithLimit(text, 0, maxWidth)
|
displayWidth := t.displayWidthWithLimit(text, 0, maxWidth)
|
||||||
if displayWidth > maxWidth {
|
if displayWidth > maxWidth {
|
||||||
|
transformOffsets := func(diff int32) {
|
||||||
|
for idx, offset := range offsets {
|
||||||
|
b, e := offset.offset[0], offset.offset[1]
|
||||||
|
b += 2 - diff
|
||||||
|
e += 2 - diff
|
||||||
|
b = util.Max32(b, 2)
|
||||||
|
offsets[idx].offset[0] = b
|
||||||
|
offsets[idx].offset[1] = util.Max32(b, e)
|
||||||
|
}
|
||||||
|
}
|
||||||
if t.hscroll {
|
if t.hscroll {
|
||||||
// Stri..
|
if t.keepRight && pos == nil {
|
||||||
if !t.overflow(text[:maxe], maxWidth-2) {
|
trimmed, diff := t.trimLeft(text, maxWidth-2)
|
||||||
|
transformOffsets(diff)
|
||||||
|
text = append([]rune(ellipsis), trimmed...)
|
||||||
|
} else if !t.overflow(text[:maxe], maxWidth-2) {
|
||||||
|
// Stri..
|
||||||
text, _ = t.trimRight(text, maxWidth-2)
|
text, _ = t.trimRight(text, maxWidth-2)
|
||||||
text = append(text, []rune("..")...)
|
text = append(text, []rune(ellipsis)...)
|
||||||
} else {
|
} else {
|
||||||
// Stri..
|
// Stri..
|
||||||
if t.overflow(text[maxe:], 2) {
|
if t.overflow(text[maxe:], 2) {
|
||||||
text = append(text[:maxe], []rune("..")...)
|
text = append(text[:maxe], []rune(ellipsis)...)
|
||||||
}
|
}
|
||||||
// ..ri..
|
// ..ri..
|
||||||
var diff int32
|
var diff int32
|
||||||
text, diff = t.trimLeft(text, maxWidth-2)
|
text, diff = t.trimLeft(text, maxWidth-2)
|
||||||
|
|
||||||
// Transform offsets
|
// Transform offsets
|
||||||
for idx, offset := range offsets {
|
transformOffsets(diff)
|
||||||
b, e := offset.offset[0], offset.offset[1]
|
text = append([]rune(ellipsis), text...)
|
||||||
b += 2 - diff
|
|
||||||
e += 2 - diff
|
|
||||||
b = util.Max32(b, 2)
|
|
||||||
offsets[idx].offset[0] = b
|
|
||||||
offsets[idx].offset[1] = util.Max32(b, e)
|
|
||||||
}
|
|
||||||
text = append([]rune(".."), text...)
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
text, _ = t.trimRight(text, maxWidth-2)
|
text, _ = t.trimRight(text, maxWidth-2)
|
||||||
text = append(text, []rune("..")...)
|
text = append(text, []rune(ellipsis)...)
|
||||||
|
|
||||||
for idx, offset := range offsets {
|
for idx, offset := range offsets {
|
||||||
offsets[idx].offset[0] = util.Min32(offset.offset[0], int32(maxWidth-2))
|
offsets[idx].offset[0] = util.Min32(offset.offset[0], int32(maxWidth-2))
|
||||||
@@ -1066,7 +1111,7 @@ func (t *Terminal) printPreview() {
|
|||||||
if t.theme != nil && ansi != nil && ansi.colored() {
|
if t.theme != nil && ansi != nil && ansi.colored() {
|
||||||
fillRet = t.pwindow.CFill(ansi.fg, ansi.bg, ansi.attr, str)
|
fillRet = t.pwindow.CFill(ansi.fg, ansi.bg, ansi.attr, str)
|
||||||
} else {
|
} else {
|
||||||
fillRet = t.pwindow.CFill(tui.ColNormal.Fg(), tui.ColNormal.Bg(), tui.AttrRegular, str)
|
fillRet = t.pwindow.CFill(tui.ColPreview.Fg(), tui.ColPreview.Bg(), tui.AttrRegular, str)
|
||||||
}
|
}
|
||||||
return fillRet == tui.FillContinue
|
return fillRet == tui.FillContinue
|
||||||
})
|
})
|
||||||
@@ -1123,7 +1168,7 @@ func (t *Terminal) refresh() {
|
|||||||
t.placeCursor()
|
t.placeCursor()
|
||||||
if !t.suppress {
|
if !t.suppress {
|
||||||
windows := make([]tui.Window, 0, 4)
|
windows := make([]tui.Window, 0, 4)
|
||||||
if t.bordered {
|
if t.borderShape != tui.BorderNone {
|
||||||
windows = append(windows, t.border)
|
windows = append(windows, t.border)
|
||||||
}
|
}
|
||||||
if t.hasPreviewWindow() {
|
if t.hasPreviewWindow() {
|
||||||
@@ -1237,7 +1282,7 @@ func parsePlaceholder(match string) (bool, string, placeholderFlags) {
|
|||||||
return false, matchWithoutFlags, flags
|
return false, matchWithoutFlags, flags
|
||||||
}
|
}
|
||||||
|
|
||||||
func hasPreviewFlags(template string) (plus bool, query bool) {
|
func hasPreviewFlags(template string) (slot bool, plus bool, query bool) {
|
||||||
for _, match := range placeholder.FindAllString(template, -1) {
|
for _, match := range placeholder.FindAllString(template, -1) {
|
||||||
_, _, flags := parsePlaceholder(match)
|
_, _, flags := parsePlaceholder(match)
|
||||||
if flags.plus {
|
if flags.plus {
|
||||||
@@ -1246,6 +1291,7 @@ func hasPreviewFlags(template string) (plus bool, query bool) {
|
|||||||
if flags.query {
|
if flags.query {
|
||||||
query = true
|
query = true
|
||||||
}
|
}
|
||||||
|
slot = true
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -1409,7 +1455,7 @@ func (t *Terminal) currentItem() *Item {
|
|||||||
|
|
||||||
func (t *Terminal) buildPlusList(template string, forcePlus bool) (bool, []*Item) {
|
func (t *Terminal) buildPlusList(template string, forcePlus bool) (bool, []*Item) {
|
||||||
current := t.currentItem()
|
current := t.currentItem()
|
||||||
plus, query := hasPreviewFlags(template)
|
_, plus, query := hasPreviewFlags(template)
|
||||||
if !(query && len(t.input) > 0 || (forcePlus || plus) && len(t.selected) > 0) {
|
if !(query && len(t.input) > 0 || (forcePlus || plus) && len(t.selected) > 0) {
|
||||||
return current != nil, []*Item{current, current}
|
return current != nil, []*Item{current, current}
|
||||||
}
|
}
|
||||||
@@ -1565,7 +1611,10 @@ func (t *Terminal) Loop() {
|
|||||||
var out bytes.Buffer
|
var out bytes.Buffer
|
||||||
cmd.Stdout = &out
|
cmd.Stdout = &out
|
||||||
cmd.Stderr = &out
|
cmd.Stderr = &out
|
||||||
cmd.Start()
|
err := cmd.Start()
|
||||||
|
if err != nil {
|
||||||
|
out.Write([]byte(err.Error()))
|
||||||
|
}
|
||||||
finishChan := make(chan bool, 1)
|
finishChan := make(chan bool, 1)
|
||||||
updateChan := make(chan bool)
|
updateChan := make(chan bool)
|
||||||
go func() {
|
go func() {
|
||||||
@@ -1820,6 +1869,13 @@ func (t *Terminal) Loop() {
|
|||||||
t.input = []rune{}
|
t.input = []rune{}
|
||||||
t.cx = 0
|
t.cx = 0
|
||||||
}
|
}
|
||||||
|
case actBackwardDeleteCharEOF:
|
||||||
|
if len(t.input) == 0 {
|
||||||
|
req(reqQuit)
|
||||||
|
} else if t.cx > 0 {
|
||||||
|
t.input = append(t.input[:t.cx-1], t.input[t.cx:]...)
|
||||||
|
t.cx--
|
||||||
|
}
|
||||||
case actForwardChar:
|
case actForwardChar:
|
||||||
if t.cx < len(t.input) {
|
if t.cx < len(t.input) {
|
||||||
t.cx++
|
t.cx++
|
||||||
@@ -1904,6 +1960,15 @@ func (t *Terminal) Loop() {
|
|||||||
}
|
}
|
||||||
case actClearScreen:
|
case actClearScreen:
|
||||||
req(reqRedraw)
|
req(reqRedraw)
|
||||||
|
case actClearQuery:
|
||||||
|
t.input = []rune{}
|
||||||
|
t.cx = 0
|
||||||
|
case actClearSelection:
|
||||||
|
if t.multi > 0 {
|
||||||
|
t.selected = make(map[int32]selectedItem)
|
||||||
|
t.version++
|
||||||
|
req(reqList, reqInfo)
|
||||||
|
}
|
||||||
case actTop:
|
case actTop:
|
||||||
t.vset(0)
|
t.vset(0)
|
||||||
req(reqList)
|
req(reqList)
|
||||||
@@ -2045,17 +2110,17 @@ func (t *Terminal) Loop() {
|
|||||||
t.failed = nil
|
t.failed = nil
|
||||||
|
|
||||||
valid, list := t.buildPlusList(a.a, false)
|
valid, list := t.buildPlusList(a.a, false)
|
||||||
// If the command template has {q}, we run the command even when the
|
|
||||||
// query string is empty.
|
|
||||||
if !valid {
|
if !valid {
|
||||||
_, query := hasPreviewFlags(a.a)
|
// We run the command even when there's no match
|
||||||
valid = query
|
// 1. If the template doesn't have any slots
|
||||||
|
// 2. If the template has {q}
|
||||||
|
slot, _, query := hasPreviewFlags(a.a)
|
||||||
|
valid = !slot || query
|
||||||
}
|
}
|
||||||
if valid {
|
if valid {
|
||||||
command := replacePlaceholder(a.a,
|
command := replacePlaceholder(a.a,
|
||||||
t.ansi, t.delimiter, t.printsep, false, string(t.input), list)
|
t.ansi, t.delimiter, t.printsep, false, string(t.input), list)
|
||||||
newCommand = &command
|
newCommand = &command
|
||||||
t.selected = make(map[int32]selectedItem)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
@@ -2095,7 +2160,7 @@ func (t *Terminal) Loop() {
|
|||||||
|
|
||||||
if queryChanged {
|
if queryChanged {
|
||||||
if t.isPreviewEnabled() {
|
if t.isPreviewEnabled() {
|
||||||
_, q := hasPreviewFlags(t.preview.command)
|
_, _, q := hasPreviewFlags(t.preview.command)
|
||||||
if q {
|
if q {
|
||||||
t.version++
|
t.version++
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,6 +39,6 @@ func (r *FullscreenRenderer) MaxY() int { return 0 }
|
|||||||
|
|
||||||
func (r *FullscreenRenderer) RefreshWindows(windows []Window) {}
|
func (r *FullscreenRenderer) RefreshWindows(windows []Window) {}
|
||||||
|
|
||||||
func (r *FullscreenRenderer) NewWindow(top int, left int, width int, height int, borderStyle BorderStyle) Window {
|
func (r *FullscreenRenderer) NewWindow(top int, left int, width int, height int, preview bool, borderStyle BorderStyle) Window {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
132
src/tui/light.go
132
src/tui/light.go
@@ -7,7 +7,6 @@ import (
|
|||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
|
||||||
"time"
|
"time"
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
|
|
||||||
@@ -23,27 +22,13 @@ const (
|
|||||||
defaultEscDelay = 100
|
defaultEscDelay = 100
|
||||||
escPollInterval = 5
|
escPollInterval = 5
|
||||||
offsetPollTries = 10
|
offsetPollTries = 10
|
||||||
|
maxInputBuffer = 10 * 1024
|
||||||
)
|
)
|
||||||
|
|
||||||
const consoleDevice string = "/dev/tty"
|
const consoleDevice string = "/dev/tty"
|
||||||
|
|
||||||
var offsetRegexp *regexp.Regexp = regexp.MustCompile("(.*)\x1b\\[([0-9]+);([0-9]+)R")
|
var offsetRegexp *regexp.Regexp = regexp.MustCompile("(.*)\x1b\\[([0-9]+);([0-9]+)R")
|
||||||
|
|
||||||
func openTtyIn() *os.File {
|
|
||||||
in, err := os.OpenFile(consoleDevice, syscall.O_RDONLY, 0)
|
|
||||||
if err != nil {
|
|
||||||
tty := ttyname()
|
|
||||||
if len(tty) > 0 {
|
|
||||||
if in, err := os.OpenFile(tty, syscall.O_RDONLY, 0); err == nil {
|
|
||||||
return in
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fmt.Fprintln(os.Stderr, "Failed to open "+consoleDevice)
|
|
||||||
os.Exit(2)
|
|
||||||
}
|
|
||||||
return in
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *LightRenderer) stderr(str string) {
|
func (r *LightRenderer) stderr(str string) {
|
||||||
r.stderrInternal(str, true)
|
r.stderrInternal(str, true)
|
||||||
}
|
}
|
||||||
@@ -100,11 +85,19 @@ type LightRenderer struct {
|
|||||||
y int
|
y int
|
||||||
x int
|
x int
|
||||||
maxHeightFunc func(int) int
|
maxHeightFunc func(int) int
|
||||||
|
|
||||||
|
// Windows only
|
||||||
|
ttyinChannel chan byte
|
||||||
|
inHandle uintptr
|
||||||
|
outHandle uintptr
|
||||||
|
origStateInput uint32
|
||||||
|
origStateOutput uint32
|
||||||
}
|
}
|
||||||
|
|
||||||
type LightWindow struct {
|
type LightWindow struct {
|
||||||
renderer *LightRenderer
|
renderer *LightRenderer
|
||||||
colored bool
|
colored bool
|
||||||
|
preview bool
|
||||||
border BorderStyle
|
border BorderStyle
|
||||||
top int
|
top int
|
||||||
left int
|
left int
|
||||||
@@ -132,10 +125,6 @@ func NewLightRenderer(theme *ColorTheme, forceBlack bool, mouse bool, tabstop in
|
|||||||
return &r
|
return &r
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *LightRenderer) fd() int {
|
|
||||||
return int(r.ttyin.Fd())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *LightRenderer) defaultTheme() *ColorTheme {
|
func (r *LightRenderer) defaultTheme() *ColorTheme {
|
||||||
if strings.Contains(os.Getenv("TERM"), "256") {
|
if strings.Contains(os.Getenv("TERM"), "256") {
|
||||||
return Dark256
|
return Dark256
|
||||||
@@ -147,22 +136,6 @@ func (r *LightRenderer) defaultTheme() *ColorTheme {
|
|||||||
return Default16
|
return Default16
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *LightRenderer) findOffset() (row int, col int) {
|
|
||||||
r.csi("6n")
|
|
||||||
r.flush()
|
|
||||||
bytes := []byte{}
|
|
||||||
for tries := 0; tries < offsetPollTries; tries++ {
|
|
||||||
bytes = r.getBytesInternal(bytes, tries > 0)
|
|
||||||
offsets := offsetRegexp.FindSubmatch(bytes)
|
|
||||||
if len(offsets) > 3 {
|
|
||||||
// add anything we skipped over to the input buffer
|
|
||||||
r.buffer = append(r.buffer, offsets[1]...)
|
|
||||||
return atoi(string(offsets[2]), 0) - 1, atoi(string(offsets[3]), 0) - 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return -1, -1
|
|
||||||
}
|
|
||||||
|
|
||||||
func repeat(r rune, times int) string {
|
func repeat(r rune, times int) string {
|
||||||
if times > 0 {
|
if times > 0 {
|
||||||
return strings.Repeat(string(r), times)
|
return strings.Repeat(string(r), times)
|
||||||
@@ -181,13 +154,9 @@ func atoi(s string, defaultValue int) int {
|
|||||||
func (r *LightRenderer) Init() {
|
func (r *LightRenderer) Init() {
|
||||||
r.escDelay = atoi(os.Getenv("ESCDELAY"), defaultEscDelay)
|
r.escDelay = atoi(os.Getenv("ESCDELAY"), defaultEscDelay)
|
||||||
|
|
||||||
fd := r.fd()
|
if err := r.initPlatform(); err != nil {
|
||||||
origState, err := terminal.GetState(fd)
|
|
||||||
if err != nil {
|
|
||||||
errorExit(err.Error())
|
errorExit(err.Error())
|
||||||
}
|
}
|
||||||
r.origState = origState
|
|
||||||
terminal.MakeRaw(fd)
|
|
||||||
r.updateTerminalSize()
|
r.updateTerminalSize()
|
||||||
initTheme(r.theme, r.defaultTheme(), r.forceBlack)
|
initTheme(r.theme, r.defaultTheme(), r.forceBlack)
|
||||||
|
|
||||||
@@ -260,28 +229,6 @@ func getEnv(name string, defaultValue int) int {
|
|||||||
return atoi(env, defaultValue)
|
return atoi(env, defaultValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *LightRenderer) updateTerminalSize() {
|
|
||||||
width, height, err := terminal.GetSize(r.fd())
|
|
||||||
if err == nil {
|
|
||||||
r.width = width
|
|
||||||
r.height = r.maxHeightFunc(height)
|
|
||||||
} else {
|
|
||||||
r.width = getEnv("COLUMNS", defaultWidth)
|
|
||||||
r.height = r.maxHeightFunc(getEnv("LINES", defaultHeight))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *LightRenderer) getch(nonblock bool) (int, bool) {
|
|
||||||
b := make([]byte, 1)
|
|
||||||
fd := r.fd()
|
|
||||||
util.SetNonblock(r.ttyin, nonblock)
|
|
||||||
_, err := util.Read(fd, b)
|
|
||||||
if err != nil {
|
|
||||||
return 0, false
|
|
||||||
}
|
|
||||||
return int(b[0]), true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *LightRenderer) getBytes() []byte {
|
func (r *LightRenderer) getBytes() []byte {
|
||||||
return r.getBytesInternal(r.buffer, false)
|
return r.getBytesInternal(r.buffer, false)
|
||||||
}
|
}
|
||||||
@@ -316,6 +263,13 @@ func (r *LightRenderer) getBytesInternal(buffer []byte, nonblock bool) []byte {
|
|||||||
}
|
}
|
||||||
buffer = append(buffer, byte(c))
|
buffer = append(buffer, byte(c))
|
||||||
pc = c
|
pc = c
|
||||||
|
|
||||||
|
// This should never happen under normal conditions,
|
||||||
|
// so terminate fzf immediately.
|
||||||
|
if len(buffer) > maxInputBuffer {
|
||||||
|
r.Close()
|
||||||
|
panic(fmt.Sprintf("Input buffer overflow (%d): %v", len(buffer), buffer))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return buffer
|
return buffer
|
||||||
@@ -453,7 +407,10 @@ func (r *LightRenderer) escSequence(sz *int) Event {
|
|||||||
*sz = 4
|
*sz = 4
|
||||||
switch r.buffer[2] {
|
switch r.buffer[2] {
|
||||||
case 50:
|
case 50:
|
||||||
if len(r.buffer) == 5 && r.buffer[4] == 126 {
|
if r.buffer[3] == 126 {
|
||||||
|
return Event{Insert, 0, nil}
|
||||||
|
}
|
||||||
|
if len(r.buffer) > 4 && r.buffer[4] == 126 {
|
||||||
*sz = 5
|
*sz = 5
|
||||||
switch r.buffer[3] {
|
switch r.buffer[3] {
|
||||||
case 48:
|
case 48:
|
||||||
@@ -467,7 +424,7 @@ func (r *LightRenderer) escSequence(sz *int) Event {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Bracketed paste mode: \e[200~ ... \e[201~
|
// Bracketed paste mode: \e[200~ ... \e[201~
|
||||||
if r.buffer[3] == '0' && (r.buffer[4] == '0' || r.buffer[4] == '1') && r.buffer[5] == '~' {
|
if len(r.buffer) > 5 && r.buffer[3] == '0' && (r.buffer[4] == '0' || r.buffer[4] == '1') && r.buffer[5] == '~' {
|
||||||
// Immediately discard the sequence from the buffer and reread input
|
// Immediately discard the sequence from the buffer and reread input
|
||||||
r.buffer = r.buffer[6:]
|
r.buffer = r.buffer[6:]
|
||||||
*sz = 0
|
*sz = 0
|
||||||
@@ -486,10 +443,18 @@ func (r *LightRenderer) escSequence(sz *int) Event {
|
|||||||
switch r.buffer[3] {
|
switch r.buffer[3] {
|
||||||
case 126:
|
case 126:
|
||||||
return Event{Home, 0, nil}
|
return Event{Home, 0, nil}
|
||||||
case 53, 55, 56, 57:
|
case 49, 50, 51, 52, 53, 55, 56, 57:
|
||||||
if len(r.buffer) == 5 && r.buffer[4] == 126 {
|
if len(r.buffer) == 5 && r.buffer[4] == 126 {
|
||||||
*sz = 5
|
*sz = 5
|
||||||
switch r.buffer[3] {
|
switch r.buffer[3] {
|
||||||
|
case 49:
|
||||||
|
return Event{F1, 0, nil}
|
||||||
|
case 50:
|
||||||
|
return Event{F2, 0, nil}
|
||||||
|
case 51:
|
||||||
|
return Event{F3, 0, nil}
|
||||||
|
case 52:
|
||||||
|
return Event{F4, 0, nil}
|
||||||
case 53:
|
case 53:
|
||||||
return Event{F5, 0, nil}
|
return Event{F5, 0, nil}
|
||||||
case 55:
|
case 55:
|
||||||
@@ -584,7 +549,7 @@ func (r *LightRenderer) rmcup() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *LightRenderer) Pause(clear bool) {
|
func (r *LightRenderer) Pause(clear bool) {
|
||||||
terminal.Restore(r.fd(), r.origState)
|
r.restoreTerminal()
|
||||||
if clear {
|
if clear {
|
||||||
if r.fullscreen {
|
if r.fullscreen {
|
||||||
r.rmcup()
|
r.rmcup()
|
||||||
@@ -597,7 +562,7 @@ func (r *LightRenderer) Pause(clear bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *LightRenderer) Resume(clear bool) {
|
func (r *LightRenderer) Resume(clear bool) {
|
||||||
terminal.MakeRaw(r.fd())
|
r.setupTerminal()
|
||||||
if clear {
|
if clear {
|
||||||
if r.fullscreen {
|
if r.fullscreen {
|
||||||
r.smcup()
|
r.smcup()
|
||||||
@@ -651,7 +616,8 @@ func (r *LightRenderer) Close() {
|
|||||||
r.csi("?1000l")
|
r.csi("?1000l")
|
||||||
}
|
}
|
||||||
r.flush()
|
r.flush()
|
||||||
terminal.Restore(r.fd(), r.origState)
|
r.closePlatform()
|
||||||
|
r.restoreTerminal()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *LightRenderer) MaxX() int {
|
func (r *LightRenderer) MaxX() int {
|
||||||
@@ -666,10 +632,11 @@ func (r *LightRenderer) DoesAutoWrap() bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *LightRenderer) NewWindow(top int, left int, width int, height int, borderStyle BorderStyle) Window {
|
func (r *LightRenderer) NewWindow(top int, left int, width int, height int, preview bool, borderStyle BorderStyle) Window {
|
||||||
w := &LightWindow{
|
w := &LightWindow{
|
||||||
renderer: r,
|
renderer: r,
|
||||||
colored: r.theme != nil,
|
colored: r.theme != nil,
|
||||||
|
preview: preview,
|
||||||
border: borderStyle,
|
border: borderStyle,
|
||||||
top: top,
|
top: top,
|
||||||
left: left,
|
left: left,
|
||||||
@@ -679,8 +646,13 @@ func (r *LightRenderer) NewWindow(top int, left int, width int, height int, bord
|
|||||||
fg: colDefault,
|
fg: colDefault,
|
||||||
bg: colDefault}
|
bg: colDefault}
|
||||||
if r.theme != nil {
|
if r.theme != nil {
|
||||||
w.fg = r.theme.Fg
|
if preview {
|
||||||
w.bg = r.theme.Bg
|
w.fg = r.theme.PreviewFg
|
||||||
|
w.bg = r.theme.PreviewBg
|
||||||
|
} else {
|
||||||
|
w.fg = r.theme.Fg
|
||||||
|
w.bg = r.theme.Bg
|
||||||
|
}
|
||||||
}
|
}
|
||||||
w.drawBorder()
|
w.drawBorder()
|
||||||
return w
|
return w
|
||||||
@@ -688,7 +660,7 @@ func (r *LightRenderer) NewWindow(top int, left int, width int, height int, bord
|
|||||||
|
|
||||||
func (w *LightWindow) drawBorder() {
|
func (w *LightWindow) drawBorder() {
|
||||||
switch w.border.shape {
|
switch w.border.shape {
|
||||||
case BorderAround:
|
case BorderRounded, BorderSharp:
|
||||||
w.drawBorderAround()
|
w.drawBorderAround()
|
||||||
case BorderHorizontal:
|
case BorderHorizontal:
|
||||||
w.drawBorderHorizontal()
|
w.drawBorderHorizontal()
|
||||||
@@ -704,16 +676,20 @@ func (w *LightWindow) drawBorderHorizontal() {
|
|||||||
|
|
||||||
func (w *LightWindow) drawBorderAround() {
|
func (w *LightWindow) drawBorderAround() {
|
||||||
w.Move(0, 0)
|
w.Move(0, 0)
|
||||||
w.CPrint(ColBorder, AttrRegular,
|
color := ColBorder
|
||||||
|
if w.preview {
|
||||||
|
color = ColPreviewBorder
|
||||||
|
}
|
||||||
|
w.CPrint(color, AttrRegular,
|
||||||
string(w.border.topLeft)+repeat(w.border.horizontal, w.width-2)+string(w.border.topRight))
|
string(w.border.topLeft)+repeat(w.border.horizontal, w.width-2)+string(w.border.topRight))
|
||||||
for y := 1; y < w.height-1; y++ {
|
for y := 1; y < w.height-1; y++ {
|
||||||
w.Move(y, 0)
|
w.Move(y, 0)
|
||||||
w.CPrint(ColBorder, AttrRegular, string(w.border.vertical))
|
w.CPrint(color, AttrRegular, string(w.border.vertical))
|
||||||
w.cprint2(colDefault, w.bg, AttrRegular, repeat(' ', w.width-2))
|
w.CPrint(color, AttrRegular, repeat(' ', w.width-2))
|
||||||
w.CPrint(ColBorder, AttrRegular, string(w.border.vertical))
|
w.CPrint(color, AttrRegular, string(w.border.vertical))
|
||||||
}
|
}
|
||||||
w.Move(w.height-1, 0)
|
w.Move(w.height-1, 0)
|
||||||
w.CPrint(ColBorder, AttrRegular,
|
w.CPrint(color, AttrRegular,
|
||||||
string(w.border.bottomLeft)+repeat(w.border.horizontal, w.width-2)+string(w.border.bottomRight))
|
string(w.border.bottomLeft)+repeat(w.border.horizontal, w.width-2)+string(w.border.bottomRight))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
97
src/tui/light_unix.go
Normal file
97
src/tui/light_unix.go
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
// +build !windows
|
||||||
|
|
||||||
|
package tui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"github.com/junegunn/fzf/src/util"
|
||||||
|
"golang.org/x/crypto/ssh/terminal"
|
||||||
|
)
|
||||||
|
|
||||||
|
func IsLightRendererSupported() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *LightRenderer) fd() int {
|
||||||
|
return int(r.ttyin.Fd())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *LightRenderer) initPlatform() error {
|
||||||
|
fd := r.fd()
|
||||||
|
origState, err := terminal.GetState(fd)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
r.origState = origState
|
||||||
|
terminal.MakeRaw(fd)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *LightRenderer) closePlatform() {
|
||||||
|
// NOOP
|
||||||
|
}
|
||||||
|
|
||||||
|
func openTtyIn() *os.File {
|
||||||
|
in, err := os.OpenFile(consoleDevice, syscall.O_RDONLY, 0)
|
||||||
|
if err != nil {
|
||||||
|
tty := ttyname()
|
||||||
|
if len(tty) > 0 {
|
||||||
|
if in, err := os.OpenFile(tty, syscall.O_RDONLY, 0); err == nil {
|
||||||
|
return in
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fmt.Fprintln(os.Stderr, "Failed to open "+consoleDevice)
|
||||||
|
os.Exit(2)
|
||||||
|
}
|
||||||
|
return in
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *LightRenderer) setupTerminal() {
|
||||||
|
terminal.MakeRaw(r.fd())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *LightRenderer) restoreTerminal() {
|
||||||
|
terminal.Restore(r.fd(), r.origState)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *LightRenderer) updateTerminalSize() {
|
||||||
|
width, height, err := terminal.GetSize(r.fd())
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
r.width = width
|
||||||
|
r.height = r.maxHeightFunc(height)
|
||||||
|
} else {
|
||||||
|
r.width = getEnv("COLUMNS", defaultWidth)
|
||||||
|
r.height = r.maxHeightFunc(getEnv("LINES", defaultHeight))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *LightRenderer) findOffset() (row int, col int) {
|
||||||
|
r.csi("6n")
|
||||||
|
r.flush()
|
||||||
|
bytes := []byte{}
|
||||||
|
for tries := 0; tries < offsetPollTries; tries++ {
|
||||||
|
bytes = r.getBytesInternal(bytes, tries > 0)
|
||||||
|
offsets := offsetRegexp.FindSubmatch(bytes)
|
||||||
|
if len(offsets) > 3 {
|
||||||
|
// Add anything we skipped over to the input buffer
|
||||||
|
r.buffer = append(r.buffer, offsets[1]...)
|
||||||
|
return atoi(string(offsets[2]), 0) - 1, atoi(string(offsets[3]), 0) - 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1, -1
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *LightRenderer) getch(nonblock bool) (int, bool) {
|
||||||
|
b := make([]byte, 1)
|
||||||
|
fd := r.fd()
|
||||||
|
util.SetNonblock(r.ttyin, nonblock)
|
||||||
|
_, err := util.Read(fd, b)
|
||||||
|
if err != nil {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
return int(b[0]), true
|
||||||
|
}
|
||||||
132
src/tui/light_windows.go
Normal file
132
src/tui/light_windows.go
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
//+build windows
|
||||||
|
|
||||||
|
package tui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"github.com/junegunn/fzf/src/util"
|
||||||
|
"golang.org/x/sys/windows"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
consoleFlagsInput = uint32(windows.ENABLE_VIRTUAL_TERMINAL_INPUT | windows.ENABLE_PROCESSED_INPUT | windows.ENABLE_EXTENDED_FLAGS)
|
||||||
|
consoleFlagsOutput = uint32(windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING | windows.ENABLE_PROCESSED_OUTPUT | windows.DISABLE_NEWLINE_AUTO_RETURN)
|
||||||
|
)
|
||||||
|
|
||||||
|
// IsLightRendererSupported checks to see if the Light renderer is supported
|
||||||
|
func IsLightRendererSupported() bool {
|
||||||
|
var oldState uint32
|
||||||
|
// enable vt100 emulation (https://docs.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences)
|
||||||
|
if windows.GetConsoleMode(windows.Stderr, &oldState) != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
// attempt to set mode to determine if we support VT 100 codes. This will work on newer Windows 10
|
||||||
|
// version:
|
||||||
|
canSetVt100 := windows.SetConsoleMode(windows.Stderr, oldState|windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING) == nil
|
||||||
|
var checkState uint32
|
||||||
|
if windows.GetConsoleMode(windows.Stderr, &checkState) != nil ||
|
||||||
|
(checkState&windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING) != windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
windows.SetConsoleMode(windows.Stderr, oldState)
|
||||||
|
return canSetVt100
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *LightRenderer) initPlatform() error {
|
||||||
|
//outHandle := windows.Stdout
|
||||||
|
outHandle, _ := syscall.Open("CONOUT$", syscall.O_RDWR, 0)
|
||||||
|
// enable vt100 emulation (https://docs.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences)
|
||||||
|
if err := windows.GetConsoleMode(windows.Handle(outHandle), &r.origStateOutput); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
r.outHandle = uintptr(outHandle)
|
||||||
|
inHandle, _ := syscall.Open("CONIN$", syscall.O_RDWR, 0)
|
||||||
|
if err := windows.GetConsoleMode(windows.Handle(inHandle), &r.origStateInput); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
r.inHandle = uintptr(inHandle)
|
||||||
|
|
||||||
|
r.setupTerminal()
|
||||||
|
|
||||||
|
// channel for non-blocking reads. Buffer to make sure
|
||||||
|
// we get the ESC sets:
|
||||||
|
r.ttyinChannel = make(chan byte, 12)
|
||||||
|
|
||||||
|
// the following allows for non-blocking IO.
|
||||||
|
// syscall.SetNonblock() is a NOOP under Windows.
|
||||||
|
go func() {
|
||||||
|
fd := int(r.inHandle)
|
||||||
|
b := make([]byte, 1)
|
||||||
|
for {
|
||||||
|
// HACK: if run from PSReadline, something resets ConsoleMode to remove ENABLE_VIRTUAL_TERMINAL_INPUT.
|
||||||
|
_ = windows.SetConsoleMode(windows.Handle(r.inHandle), consoleFlagsInput)
|
||||||
|
|
||||||
|
_, err := util.Read(fd, b)
|
||||||
|
if err == nil {
|
||||||
|
r.ttyinChannel <- b[0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *LightRenderer) closePlatform() {
|
||||||
|
windows.SetConsoleMode(windows.Handle(r.outHandle), r.origStateOutput)
|
||||||
|
windows.SetConsoleMode(windows.Handle(r.inHandle), r.origStateInput)
|
||||||
|
}
|
||||||
|
|
||||||
|
func openTtyIn() *os.File {
|
||||||
|
// not used
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *LightRenderer) setupTerminal() error {
|
||||||
|
if err := windows.SetConsoleMode(windows.Handle(r.outHandle), consoleFlagsOutput); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return windows.SetConsoleMode(windows.Handle(r.inHandle), consoleFlagsInput)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *LightRenderer) restoreTerminal() error {
|
||||||
|
if err := windows.SetConsoleMode(windows.Handle(r.inHandle), r.origStateInput); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return windows.SetConsoleMode(windows.Handle(r.outHandle), r.origStateOutput)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *LightRenderer) updateTerminalSize() {
|
||||||
|
var bufferInfo windows.ConsoleScreenBufferInfo
|
||||||
|
if err := windows.GetConsoleScreenBufferInfo(windows.Handle(r.outHandle), &bufferInfo); err != nil {
|
||||||
|
r.width = getEnv("COLUMNS", defaultWidth)
|
||||||
|
r.height = r.maxHeightFunc(getEnv("LINES", defaultHeight))
|
||||||
|
|
||||||
|
} else {
|
||||||
|
r.width = int(bufferInfo.Window.Right - bufferInfo.Window.Left)
|
||||||
|
r.height = r.maxHeightFunc(int(bufferInfo.Window.Bottom - bufferInfo.Window.Top))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *LightRenderer) findOffset() (row int, col int) {
|
||||||
|
var bufferInfo windows.ConsoleScreenBufferInfo
|
||||||
|
if err := windows.GetConsoleScreenBufferInfo(windows.Handle(r.outHandle), &bufferInfo); err != nil {
|
||||||
|
return -1, -1
|
||||||
|
}
|
||||||
|
return int(bufferInfo.CursorPosition.X), int(bufferInfo.CursorPosition.Y)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *LightRenderer) getch(nonblock bool) (int, bool) {
|
||||||
|
if nonblock {
|
||||||
|
select {
|
||||||
|
case bc := <-r.ttyinChannel:
|
||||||
|
return int(bc), true
|
||||||
|
default:
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
bc := <-r.ttyinChannel
|
||||||
|
return int(bc), true
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -28,10 +28,12 @@ type Attr tcell.Style
|
|||||||
|
|
||||||
type TcellWindow struct {
|
type TcellWindow struct {
|
||||||
color bool
|
color bool
|
||||||
|
preview bool
|
||||||
top int
|
top int
|
||||||
left int
|
left int
|
||||||
width int
|
width int
|
||||||
height int
|
height int
|
||||||
|
normal ColorPair
|
||||||
lastX int
|
lastX int
|
||||||
lastY int
|
lastY int
|
||||||
moveCursor bool
|
moveCursor bool
|
||||||
@@ -317,6 +319,8 @@ func (r *FullscreenRenderer) GetChar() Event {
|
|||||||
}
|
}
|
||||||
return Event{Right, 0, nil}
|
return Event{Right, 0, nil}
|
||||||
|
|
||||||
|
case tcell.KeyInsert:
|
||||||
|
return Event{Insert, 0, nil}
|
||||||
case tcell.KeyHome:
|
case tcell.KeyHome:
|
||||||
return Event{Home, 0, nil}
|
return Event{Home, 0, nil}
|
||||||
case tcell.KeyDelete:
|
case tcell.KeyDelete:
|
||||||
@@ -408,14 +412,19 @@ func (r *FullscreenRenderer) RefreshWindows(windows []Window) {
|
|||||||
_screen.Show()
|
_screen.Show()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *FullscreenRenderer) NewWindow(top int, left int, width int, height int, borderStyle BorderStyle) Window {
|
func (r *FullscreenRenderer) NewWindow(top int, left int, width int, height int, preview bool, borderStyle BorderStyle) Window {
|
||||||
// TODO
|
normal := ColNormal
|
||||||
|
if preview {
|
||||||
|
normal = ColPreview
|
||||||
|
}
|
||||||
return &TcellWindow{
|
return &TcellWindow{
|
||||||
color: r.theme != nil,
|
color: r.theme != nil,
|
||||||
|
preview: preview,
|
||||||
top: top,
|
top: top,
|
||||||
left: left,
|
left: left,
|
||||||
width: width,
|
width: width,
|
||||||
height: height,
|
height: height,
|
||||||
|
normal: normal,
|
||||||
borderStyle: borderStyle}
|
borderStyle: borderStyle}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -423,16 +432,16 @@ func (w *TcellWindow) Close() {
|
|||||||
// TODO
|
// TODO
|
||||||
}
|
}
|
||||||
|
|
||||||
func fill(x, y, w, h int, r rune) {
|
func fill(x, y, w, h int, n ColorPair, r rune) {
|
||||||
for ly := 0; ly <= h; ly++ {
|
for ly := 0; ly <= h; ly++ {
|
||||||
for lx := 0; lx <= w; lx++ {
|
for lx := 0; lx <= w; lx++ {
|
||||||
_screen.SetContent(x+lx, y+ly, r, nil, ColNormal.style())
|
_screen.SetContent(x+lx, y+ly, r, nil, n.style())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *TcellWindow) Erase() {
|
func (w *TcellWindow) Erase() {
|
||||||
fill(w.left-1, w.top, w.width+1, w.height, ' ')
|
fill(w.left-1, w.top, w.width+1, w.height, w.normal, ' ')
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *TcellWindow) Enclose(y int, x int) bool {
|
func (w *TcellWindow) Enclose(y int, x int) bool {
|
||||||
@@ -449,13 +458,13 @@ func (w *TcellWindow) Move(y int, x int) {
|
|||||||
func (w *TcellWindow) MoveAndClear(y int, x int) {
|
func (w *TcellWindow) MoveAndClear(y int, x int) {
|
||||||
w.Move(y, x)
|
w.Move(y, x)
|
||||||
for i := w.lastX; i < w.width; i++ {
|
for i := w.lastX; i < w.width; i++ {
|
||||||
_screen.SetContent(i+w.left, w.lastY+w.top, rune(' '), nil, ColNormal.style())
|
_screen.SetContent(i+w.left, w.lastY+w.top, rune(' '), nil, w.normal.style())
|
||||||
}
|
}
|
||||||
w.lastX = x
|
w.lastX = x
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *TcellWindow) Print(text string) {
|
func (w *TcellWindow) Print(text string) {
|
||||||
w.printString(text, ColNormal, 0)
|
w.printString(text, w.normal, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *TcellWindow) printString(text string, pair ColorPair, a Attr) {
|
func (w *TcellWindow) printString(text string, pair ColorPair, a Attr) {
|
||||||
@@ -468,7 +477,7 @@ func (w *TcellWindow) printString(text string, pair ColorPair, a Attr) {
|
|||||||
Reverse(a&Attr(tcell.AttrReverse) != 0).
|
Reverse(a&Attr(tcell.AttrReverse) != 0).
|
||||||
Underline(a&Attr(tcell.AttrUnderline) != 0)
|
Underline(a&Attr(tcell.AttrUnderline) != 0)
|
||||||
} else {
|
} else {
|
||||||
style = ColNormal.style().
|
style = w.normal.style().
|
||||||
Reverse(a&Attr(tcell.AttrReverse) != 0 || pair == ColCurrent || pair == ColCurrentMatch).
|
Reverse(a&Attr(tcell.AttrReverse) != 0 || pair == ColCurrent || pair == ColCurrentMatch).
|
||||||
Underline(a&Attr(tcell.AttrUnderline) != 0 || pair == ColMatch || pair == ColCurrentMatch)
|
Underline(a&Attr(tcell.AttrUnderline) != 0 || pair == ColMatch || pair == ColCurrentMatch)
|
||||||
}
|
}
|
||||||
@@ -519,7 +528,7 @@ func (w *TcellWindow) fillString(text string, pair ColorPair, a Attr) FillReturn
|
|||||||
if w.color {
|
if w.color {
|
||||||
style = pair.style()
|
style = pair.style()
|
||||||
} else {
|
} else {
|
||||||
style = ColNormal.style()
|
style = w.normal.style()
|
||||||
}
|
}
|
||||||
style = style.
|
style = style.
|
||||||
Blink(a&Attr(tcell.AttrBlink) != 0).
|
Blink(a&Attr(tcell.AttrBlink) != 0).
|
||||||
@@ -559,15 +568,15 @@ func (w *TcellWindow) fillString(text string, pair ColorPair, a Attr) FillReturn
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (w *TcellWindow) Fill(str string) FillReturn {
|
func (w *TcellWindow) Fill(str string) FillReturn {
|
||||||
return w.fillString(str, ColNormal, 0)
|
return w.fillString(str, w.normal, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *TcellWindow) CFill(fg Color, bg Color, a Attr, str string) FillReturn {
|
func (w *TcellWindow) CFill(fg Color, bg Color, a Attr, str string) FillReturn {
|
||||||
if fg == colDefault {
|
if fg == colDefault {
|
||||||
fg = ColNormal.Fg()
|
fg = w.normal.Fg()
|
||||||
}
|
}
|
||||||
if bg == colDefault {
|
if bg == colDefault {
|
||||||
bg = ColNormal.Bg()
|
bg = w.normal.Bg()
|
||||||
}
|
}
|
||||||
return w.fillString(str, NewColorPair(fg, bg), a)
|
return w.fillString(str, NewColorPair(fg, bg), a)
|
||||||
}
|
}
|
||||||
@@ -584,9 +593,13 @@ func (w *TcellWindow) drawBorder() {
|
|||||||
|
|
||||||
var style tcell.Style
|
var style tcell.Style
|
||||||
if w.color {
|
if w.color {
|
||||||
style = ColBorder.style()
|
if w.preview {
|
||||||
|
style = ColPreviewBorder.style()
|
||||||
|
} else {
|
||||||
|
style = ColBorder.style()
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
style = ColNormal.style()
|
style = w.normal.style()
|
||||||
}
|
}
|
||||||
|
|
||||||
for x := left; x < right; x++ {
|
for x := left; x < right; x++ {
|
||||||
@@ -594,7 +607,7 @@ func (w *TcellWindow) drawBorder() {
|
|||||||
_screen.SetContent(x, bot-1, w.borderStyle.horizontal, nil, style)
|
_screen.SetContent(x, bot-1, w.borderStyle.horizontal, nil, style)
|
||||||
}
|
}
|
||||||
|
|
||||||
if w.borderStyle.shape == BorderAround {
|
if w.borderStyle.shape != BorderHorizontal {
|
||||||
for y := top; y < bot; y++ {
|
for y := top; y < bot; y++ {
|
||||||
_screen.SetContent(left, y, w.borderStyle.vertical, nil, style)
|
_screen.SetContent(left, y, w.borderStyle.vertical, nil, style)
|
||||||
_screen.SetContent(right-1, y, w.borderStyle.vertical, nil, style)
|
_screen.SetContent(right-1, y, w.borderStyle.vertical, nil, style)
|
||||||
|
|||||||
@@ -66,6 +66,7 @@ const (
|
|||||||
Right
|
Right
|
||||||
Home
|
Home
|
||||||
End
|
End
|
||||||
|
Insert
|
||||||
|
|
||||||
SUp
|
SUp
|
||||||
SDown
|
SDown
|
||||||
@@ -173,6 +174,8 @@ func (p ColorPair) Bg() Color {
|
|||||||
type ColorTheme struct {
|
type ColorTheme struct {
|
||||||
Fg Color
|
Fg Color
|
||||||
Bg Color
|
Bg Color
|
||||||
|
PreviewFg Color
|
||||||
|
PreviewBg Color
|
||||||
DarkBg Color
|
DarkBg Color
|
||||||
Gutter Color
|
Gutter Color
|
||||||
Prompt Color
|
Prompt Color
|
||||||
@@ -207,7 +210,8 @@ type BorderShape int
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
BorderNone BorderShape = iota
|
BorderNone BorderShape = iota
|
||||||
BorderAround
|
BorderRounded
|
||||||
|
BorderSharp
|
||||||
BorderHorizontal
|
BorderHorizontal
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -225,6 +229,17 @@ type BorderCharacter int
|
|||||||
|
|
||||||
func MakeBorderStyle(shape BorderShape, unicode bool) BorderStyle {
|
func MakeBorderStyle(shape BorderShape, unicode bool) BorderStyle {
|
||||||
if unicode {
|
if unicode {
|
||||||
|
if shape == BorderRounded {
|
||||||
|
return BorderStyle{
|
||||||
|
shape: shape,
|
||||||
|
horizontal: '─',
|
||||||
|
vertical: '│',
|
||||||
|
topLeft: '╭',
|
||||||
|
topRight: '╮',
|
||||||
|
bottomLeft: '╰',
|
||||||
|
bottomRight: '╯',
|
||||||
|
}
|
||||||
|
}
|
||||||
return BorderStyle{
|
return BorderStyle{
|
||||||
shape: shape,
|
shape: shape,
|
||||||
horizontal: '─',
|
horizontal: '─',
|
||||||
@@ -248,7 +263,7 @@ func MakeBorderStyle(shape BorderShape, unicode bool) BorderStyle {
|
|||||||
|
|
||||||
func MakeTransparentBorder() BorderStyle {
|
func MakeTransparentBorder() BorderStyle {
|
||||||
return BorderStyle{
|
return BorderStyle{
|
||||||
shape: BorderAround,
|
shape: BorderRounded,
|
||||||
horizontal: ' ',
|
horizontal: ' ',
|
||||||
vertical: ' ',
|
vertical: ' ',
|
||||||
topLeft: ' ',
|
topLeft: ' ',
|
||||||
@@ -272,7 +287,7 @@ type Renderer interface {
|
|||||||
MaxY() int
|
MaxY() int
|
||||||
DoesAutoWrap() bool
|
DoesAutoWrap() bool
|
||||||
|
|
||||||
NewWindow(top int, left int, width int, height int, borderStyle BorderStyle) Window
|
NewWindow(top int, left int, width int, height int, preview bool, borderStyle BorderStyle) Window
|
||||||
}
|
}
|
||||||
|
|
||||||
type Window interface {
|
type Window interface {
|
||||||
@@ -334,12 +349,16 @@ var (
|
|||||||
ColInfo ColorPair
|
ColInfo ColorPair
|
||||||
ColHeader ColorPair
|
ColHeader ColorPair
|
||||||
ColBorder ColorPair
|
ColBorder ColorPair
|
||||||
|
ColPreview ColorPair
|
||||||
|
ColPreviewBorder ColorPair
|
||||||
)
|
)
|
||||||
|
|
||||||
func EmptyTheme() *ColorTheme {
|
func EmptyTheme() *ColorTheme {
|
||||||
return &ColorTheme{
|
return &ColorTheme{
|
||||||
Fg: colUndefined,
|
Fg: colUndefined,
|
||||||
Bg: colUndefined,
|
Bg: colUndefined,
|
||||||
|
PreviewFg: colUndefined,
|
||||||
|
PreviewBg: colUndefined,
|
||||||
DarkBg: colUndefined,
|
DarkBg: colUndefined,
|
||||||
Gutter: colUndefined,
|
Gutter: colUndefined,
|
||||||
Prompt: colUndefined,
|
Prompt: colUndefined,
|
||||||
@@ -363,8 +382,10 @@ func init() {
|
|||||||
Default16 = &ColorTheme{
|
Default16 = &ColorTheme{
|
||||||
Fg: colDefault,
|
Fg: colDefault,
|
||||||
Bg: colDefault,
|
Bg: colDefault,
|
||||||
|
PreviewFg: colUndefined,
|
||||||
|
PreviewBg: colUndefined,
|
||||||
DarkBg: colBlack,
|
DarkBg: colBlack,
|
||||||
Gutter: colBlack,
|
Gutter: colUndefined,
|
||||||
Prompt: colBlue,
|
Prompt: colBlue,
|
||||||
Match: colGreen,
|
Match: colGreen,
|
||||||
Current: colYellow,
|
Current: colYellow,
|
||||||
@@ -378,6 +399,8 @@ func init() {
|
|||||||
Dark256 = &ColorTheme{
|
Dark256 = &ColorTheme{
|
||||||
Fg: colDefault,
|
Fg: colDefault,
|
||||||
Bg: colDefault,
|
Bg: colDefault,
|
||||||
|
PreviewFg: colUndefined,
|
||||||
|
PreviewBg: colUndefined,
|
||||||
DarkBg: 236,
|
DarkBg: 236,
|
||||||
Gutter: colUndefined,
|
Gutter: colUndefined,
|
||||||
Prompt: 110,
|
Prompt: 110,
|
||||||
@@ -393,6 +416,8 @@ func init() {
|
|||||||
Light256 = &ColorTheme{
|
Light256 = &ColorTheme{
|
||||||
Fg: colDefault,
|
Fg: colDefault,
|
||||||
Bg: colDefault,
|
Bg: colDefault,
|
||||||
|
PreviewFg: colUndefined,
|
||||||
|
PreviewBg: colUndefined,
|
||||||
DarkBg: 251,
|
DarkBg: 251,
|
||||||
Gutter: colUndefined,
|
Gutter: colUndefined,
|
||||||
Prompt: 25,
|
Prompt: 25,
|
||||||
@@ -425,6 +450,8 @@ func initTheme(theme *ColorTheme, baseTheme *ColorTheme, forceBlack bool) {
|
|||||||
}
|
}
|
||||||
theme.Fg = o(baseTheme.Fg, theme.Fg)
|
theme.Fg = o(baseTheme.Fg, theme.Fg)
|
||||||
theme.Bg = o(baseTheme.Bg, theme.Bg)
|
theme.Bg = o(baseTheme.Bg, theme.Bg)
|
||||||
|
theme.PreviewFg = o(theme.Fg, o(baseTheme.PreviewFg, theme.PreviewFg))
|
||||||
|
theme.PreviewBg = o(theme.Bg, o(baseTheme.PreviewBg, theme.PreviewBg))
|
||||||
theme.DarkBg = o(baseTheme.DarkBg, theme.DarkBg)
|
theme.DarkBg = o(baseTheme.DarkBg, theme.DarkBg)
|
||||||
theme.Gutter = o(theme.DarkBg, o(baseTheme.Gutter, theme.Gutter))
|
theme.Gutter = o(theme.DarkBg, o(baseTheme.Gutter, theme.Gutter))
|
||||||
theme.Prompt = o(baseTheme.Prompt, theme.Prompt)
|
theme.Prompt = o(baseTheme.Prompt, theme.Prompt)
|
||||||
@@ -461,6 +488,8 @@ func initPalette(theme *ColorTheme) {
|
|||||||
ColInfo = pair(theme.Info, theme.Bg)
|
ColInfo = pair(theme.Info, theme.Bg)
|
||||||
ColHeader = pair(theme.Header, theme.Bg)
|
ColHeader = pair(theme.Header, theme.Bg)
|
||||||
ColBorder = pair(theme.Border, theme.Bg)
|
ColBorder = pair(theme.Border, theme.Bg)
|
||||||
|
ColPreview = pair(theme.PreviewFg, theme.PreviewBg)
|
||||||
|
ColPreviewBorder = pair(theme.Border, theme.PreviewBg)
|
||||||
} else {
|
} else {
|
||||||
ColPrompt = pair(colDefault, colDefault)
|
ColPrompt = pair(colDefault, colDefault)
|
||||||
ColNormal = pair(colDefault, colDefault)
|
ColNormal = pair(colDefault, colDefault)
|
||||||
@@ -475,6 +504,8 @@ func initPalette(theme *ColorTheme) {
|
|||||||
ColInfo = pair(colDefault, colDefault)
|
ColInfo = pair(colDefault, colDefault)
|
||||||
ColHeader = pair(colDefault, colDefault)
|
ColHeader = pair(colDefault, colDefault)
|
||||||
ColBorder = pair(colDefault, colDefault)
|
ColBorder = pair(colDefault, colDefault)
|
||||||
|
ColPreview = pair(colDefault, colDefault)
|
||||||
|
ColPreviewBorder = pair(colDefault, colDefault)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,32 +1,34 @@
|
|||||||
package util
|
package util
|
||||||
|
|
||||||
import "sync"
|
import (
|
||||||
|
"sync/atomic"
|
||||||
|
)
|
||||||
|
|
||||||
|
func convertBoolToInt32(b bool) int32 {
|
||||||
|
if b {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
// AtomicBool is a boxed-class that provides synchronized access to the
|
// AtomicBool is a boxed-class that provides synchronized access to the
|
||||||
// underlying boolean value
|
// underlying boolean value
|
||||||
type AtomicBool struct {
|
type AtomicBool struct {
|
||||||
mutex sync.Mutex
|
state int32 // "1" is true, "0" is false
|
||||||
state bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewAtomicBool returns a new AtomicBool
|
// NewAtomicBool returns a new AtomicBool
|
||||||
func NewAtomicBool(initialState bool) *AtomicBool {
|
func NewAtomicBool(initialState bool) *AtomicBool {
|
||||||
return &AtomicBool{
|
return &AtomicBool{state: convertBoolToInt32(initialState)}
|
||||||
mutex: sync.Mutex{},
|
|
||||||
state: initialState}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get returns the current boolean value synchronously
|
// Get returns the current boolean value synchronously
|
||||||
func (a *AtomicBool) Get() bool {
|
func (a *AtomicBool) Get() bool {
|
||||||
a.mutex.Lock()
|
return atomic.LoadInt32(&a.state) == 1
|
||||||
defer a.mutex.Unlock()
|
|
||||||
return a.state
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set updates the boolean value synchronously
|
// Set updates the boolean value synchronously
|
||||||
func (a *AtomicBool) Set(newState bool) bool {
|
func (a *AtomicBool) Set(newState bool) bool {
|
||||||
a.mutex.Lock()
|
atomic.StoreInt32(&a.state, convertBoolToInt32(newState))
|
||||||
defer a.mutex.Unlock()
|
return newState
|
||||||
a.state = newState
|
|
||||||
return a.state
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -130,6 +130,18 @@ func (chars *Chars) TrimLength() uint16 {
|
|||||||
return chars.trimLength
|
return chars.trimLength
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (chars *Chars) LeadingWhitespaces() int {
|
||||||
|
whitespaces := 0
|
||||||
|
for i := 0; i < chars.Length(); i++ {
|
||||||
|
char := chars.Get(i)
|
||||||
|
if !unicode.IsSpace(char) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
whitespaces++
|
||||||
|
}
|
||||||
|
return whitespaces
|
||||||
|
}
|
||||||
|
|
||||||
func (chars *Chars) TrailingWhitespaces() int {
|
func (chars *Chars) TrailingWhitespaces() int {
|
||||||
whitespaces := 0
|
whitespaces := 0
|
||||||
for i := chars.Length() - 1; i >= 0; i-- {
|
for i := chars.Length() - 1; i >= 0; i-- {
|
||||||
|
|||||||
@@ -112,3 +112,13 @@ func DurWithin(
|
|||||||
func IsTty() bool {
|
func IsTty() bool {
|
||||||
return isatty.IsTerminal(os.Stdin.Fd())
|
return isatty.IsTerminal(os.Stdin.Fd())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Once returns a function that returns the specified boolean value only once
|
||||||
|
func Once(nextResponse bool) func() bool {
|
||||||
|
state := nextResponse
|
||||||
|
return func() bool {
|
||||||
|
prevState := state
|
||||||
|
state = false
|
||||||
|
return prevState
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -20,3 +20,21 @@ func TestContrain(t *testing.T) {
|
|||||||
t.Error("Expected", 3)
|
t.Error("Expected", 3)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestOnce(t *testing.T) {
|
||||||
|
o := Once(false)
|
||||||
|
if o() {
|
||||||
|
t.Error("Expected: false")
|
||||||
|
}
|
||||||
|
if o() {
|
||||||
|
t.Error("Expected: false")
|
||||||
|
}
|
||||||
|
|
||||||
|
o = Once(true)
|
||||||
|
if !o() {
|
||||||
|
t.Error("Expected: true")
|
||||||
|
}
|
||||||
|
if o() {
|
||||||
|
t.Error("Expected: false")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
393
test/test_go.rb
393
test/test_go.rb
@@ -9,13 +9,23 @@ require 'minitest/autorun'
|
|||||||
require 'fileutils'
|
require 'fileutils'
|
||||||
require 'English'
|
require 'English'
|
||||||
require 'shellwords'
|
require 'shellwords'
|
||||||
|
require 'erb'
|
||||||
|
require 'tempfile'
|
||||||
|
|
||||||
|
TEMPLATE = DATA.read
|
||||||
|
UNSETS = %w[
|
||||||
|
FZF_DEFAULT_COMMAND FZF_DEFAULT_OPTS
|
||||||
|
FZF_CTRL_T_COMMAND FZF_CTRL_T_OPTS
|
||||||
|
FZF_ALT_C_COMMAND
|
||||||
|
FZF_ALT_C_OPTS FZF_CTRL_R_OPTS
|
||||||
|
fish_history
|
||||||
|
].freeze
|
||||||
DEFAULT_TIMEOUT = 20
|
DEFAULT_TIMEOUT = 20
|
||||||
|
|
||||||
FILE = File.expand_path(__FILE__)
|
FILE = File.expand_path(__FILE__)
|
||||||
base = File.expand_path('../../', __FILE__)
|
BASE = File.expand_path('..', __dir__)
|
||||||
Dir.chdir base
|
Dir.chdir(BASE)
|
||||||
FZF = "FZF_DEFAULT_OPTS= FZF_DEFAULT_COMMAND= #{base}/bin/fzf"
|
FZF = "FZF_DEFAULT_OPTS= FZF_DEFAULT_COMMAND= #{BASE}/bin/fzf"
|
||||||
|
|
||||||
class NilClass
|
class NilClass
|
||||||
def include?(_str)
|
def include?(_str)
|
||||||
@@ -42,96 +52,74 @@ end
|
|||||||
|
|
||||||
class Shell
|
class Shell
|
||||||
class << self
|
class << self
|
||||||
def unsets
|
|
||||||
'unset FZF_DEFAULT_COMMAND FZF_DEFAULT_OPTS FZF_CTRL_T_COMMAND FZF_CTRL_T_OPTS FZF_ALT_C_COMMAND FZF_ALT_C_OPTS FZF_CTRL_R_OPTS;'
|
|
||||||
end
|
|
||||||
|
|
||||||
def bash
|
def bash
|
||||||
'PS1= PROMPT_COMMAND= bash --rcfile ~/.fzf.bash'
|
@bash ||=
|
||||||
|
begin
|
||||||
|
bashrc = '/tmp/fzf.bash'
|
||||||
|
File.open(bashrc, 'w') do |f|
|
||||||
|
f.puts(ERB.new(TEMPLATE).result(binding))
|
||||||
|
end
|
||||||
|
|
||||||
|
"bash --rcfile #{bashrc}"
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def zsh
|
def zsh
|
||||||
FileUtils.mkdir_p '/tmp/fzf-zsh'
|
@zsh ||=
|
||||||
FileUtils.cp File.expand_path('~/.fzf.zsh'), '/tmp/fzf-zsh/.zshrc'
|
begin
|
||||||
'PS1= PROMPT_COMMAND= HISTSIZE=100 ZDOTDIR=/tmp/fzf-zsh zsh'
|
zdotdir = '/tmp/fzf-zsh'
|
||||||
|
FileUtils.rm_rf(zdotdir)
|
||||||
|
FileUtils.mkdir_p(zdotdir)
|
||||||
|
File.open("#{zdotdir}/.zshrc", 'w') do |f|
|
||||||
|
f.puts(ERB.new(TEMPLATE).result(binding))
|
||||||
|
end
|
||||||
|
"ZDOTDIR=#{zdotdir} zsh"
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def fish
|
def fish
|
||||||
'fish'
|
UNSETS.map { |v| v + '= ' }.join + 'fish'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
class Tmux
|
class Tmux
|
||||||
TEMPNAME = '/tmp/fzf-test.txt'
|
|
||||||
|
|
||||||
attr_reader :win
|
attr_reader :win
|
||||||
|
|
||||||
def initialize(shell = :bash)
|
def initialize(shell = :bash)
|
||||||
@win =
|
@win = go(%W[new-window -d -P -F #I #{Shell.send(shell)}]).first
|
||||||
case shell
|
go(%W[set-window-option -t #{@win} pane-base-index 0])
|
||||||
when :bash
|
|
||||||
go("new-window -d -P -F '#I' '#{Shell.unsets + Shell.bash}'").first
|
|
||||||
when :zsh
|
|
||||||
go("new-window -d -P -F '#I' '#{Shell.unsets + Shell.zsh}'").first
|
|
||||||
when :fish
|
|
||||||
go("new-window -d -P -F '#I' '#{Shell.unsets + Shell.fish}'").first
|
|
||||||
else
|
|
||||||
raise "Unknown shell: #{shell}"
|
|
||||||
end
|
|
||||||
go("set-window-option -t #{@win} pane-base-index 0")
|
|
||||||
@lines = `tput lines`.chomp.to_i
|
|
||||||
|
|
||||||
return unless shell == :fish
|
return unless shell == :fish
|
||||||
|
|
||||||
send_keys('function fish_prompt; end; clear', :Enter)
|
send_keys('function fish_prompt; end; clear', :Enter)
|
||||||
self.until(&:empty?)
|
self.until(&:empty?)
|
||||||
end
|
end
|
||||||
|
|
||||||
def kill
|
def kill
|
||||||
go("kill-window -t #{win} 2> /dev/null")
|
go(%W[kill-window -t #{win}])
|
||||||
|
end
|
||||||
|
|
||||||
|
def focus
|
||||||
|
go(%W[select-window -t #{win}])
|
||||||
end
|
end
|
||||||
|
|
||||||
def send_keys(*args)
|
def send_keys(*args)
|
||||||
target =
|
go(%W[send-keys -t #{win}] + args.map(&:to_s))
|
||||||
if args.last.is_a?(Hash)
|
|
||||||
hash = args.pop
|
|
||||||
go("select-window -t #{win}")
|
|
||||||
"#{win}.#{hash[:pane]}"
|
|
||||||
else
|
|
||||||
win
|
|
||||||
end
|
|
||||||
enum = (args + [nil]).each_cons(2)
|
|
||||||
loop do
|
|
||||||
pair = enum.next
|
|
||||||
if pair.first == :Escape
|
|
||||||
arg = pair.compact.map { |key| %("#{key}") }.join(' ')
|
|
||||||
go(%(send-keys -t #{target} #{arg}))
|
|
||||||
enum.next if pair.last
|
|
||||||
else
|
|
||||||
go(%(send-keys -t #{target} "#{pair.first}"))
|
|
||||||
end
|
|
||||||
break unless pair.last
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def paste(str)
|
def paste(str)
|
||||||
`tmux setb '#{str.gsub("'", "'\\''")}' \\; pasteb -t #{win} \\; send-keys -t #{win} Enter`
|
system('tmux', 'setb', str, ';', 'pasteb', '-t', win, ';', 'send-keys', '-t', win, 'Enter')
|
||||||
end
|
end
|
||||||
|
|
||||||
def capture(pane = 0)
|
def capture
|
||||||
File.unlink TEMPNAME while File.exist? TEMPNAME
|
go(%W[capture-pane -p -t #{win}]).reverse.drop_while(&:empty?).reverse
|
||||||
wait do
|
|
||||||
go("capture-pane -t #{win}.#{pane} \\; save-buffer #{TEMPNAME} 2> /dev/null")
|
|
||||||
$CHILD_STATUS.exitstatus.zero?
|
|
||||||
end
|
|
||||||
File.read(TEMPNAME).split($INPUT_RECORD_SEPARATOR)[0, @lines].reverse.drop_while(&:empty?).reverse
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def until(refresh = false, pane = 0)
|
def until(refresh = false)
|
||||||
lines = nil
|
lines = nil
|
||||||
begin
|
begin
|
||||||
wait do
|
wait do
|
||||||
lines = capture(pane)
|
lines = capture
|
||||||
class << lines
|
class << lines
|
||||||
def counts
|
def counts
|
||||||
lazy
|
lazy
|
||||||
@@ -175,7 +163,7 @@ class Tmux
|
|||||||
tries = 0
|
tries = 0
|
||||||
begin
|
begin
|
||||||
self.until do |lines|
|
self.until do |lines|
|
||||||
send_keys 'C-u', 'hello'
|
send_keys ' ', 'C-u', 'hello', :Left, :Right
|
||||||
lines[-1].end_with?('hello')
|
lines[-1].end_with?('hello')
|
||||||
end
|
end
|
||||||
rescue StandardError
|
rescue StandardError
|
||||||
@@ -186,8 +174,8 @@ class Tmux
|
|||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def go(*args)
|
def go(args)
|
||||||
`tmux #{args.join ' '}`.split($INPUT_RECORD_SEPARATOR)
|
IO.popen(['tmux'] + args) { |io| io.readlines(chomp: true) }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -491,14 +479,14 @@ class TestGoFZF < TestBase
|
|||||||
end
|
end
|
||||||
|
|
||||||
def test_query_unicode
|
def test_query_unicode
|
||||||
tmux.paste "(echo abc; echo 가나다) | #{fzf :query, '가다'}"
|
tmux.paste "(echo abc; echo $'\\352\\260\\200\\353\\202\\230\\353\\213\\244') | #{fzf :query, "$'\\352\\260\\200\\353\\213\\244'"}"
|
||||||
tmux.until { |lines| lines[-2].include? '1/2' }
|
tmux.until { |lines| lines[-2].include? '1/2' }
|
||||||
tmux.send_keys :Enter
|
tmux.send_keys :Enter
|
||||||
assert_equal ['가나다'], readonce.split($INPUT_RECORD_SEPARATOR)
|
assert_equal ['가나다'], readonce.split($INPUT_RECORD_SEPARATOR)
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_sync
|
def test_sync
|
||||||
tmux.send_keys "seq 1 100 | #{fzf! :multi} | awk '{print \\$1 \\$1}' | #{fzf :sync}", :Enter
|
tmux.send_keys "seq 1 100 | #{fzf! :multi} | awk '{print $1 $1}' | #{fzf :sync}", :Enter
|
||||||
tmux.until { |lines| lines[-1] == '>' }
|
tmux.until { |lines| lines[-1] == '>' }
|
||||||
tmux.send_keys 9
|
tmux.send_keys 9
|
||||||
tmux.until { |lines| lines[-2] == ' 19/100' }
|
tmux.until { |lines| lines[-2] == ' 19/100' }
|
||||||
@@ -779,7 +767,7 @@ class TestGoFZF < TestBase
|
|||||||
end
|
end
|
||||||
|
|
||||||
def test_invalid_cache_query_type
|
def test_invalid_cache_query_type
|
||||||
command = %[(echo 'foo\\$bar'; echo 'barfoo'; echo 'foo^bar'; echo \\"foo'1-2\\"; seq 100) | #{fzf}]
|
command = %[(echo 'foo$bar'; echo 'barfoo'; echo 'foo^bar'; echo "foo'1-2"; seq 100) | #{fzf}]
|
||||||
|
|
||||||
# Suffix match
|
# Suffix match
|
||||||
tmux.send_keys command, :Enter
|
tmux.send_keys command, :Enter
|
||||||
@@ -938,7 +926,7 @@ 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}"]
|
||||||
wait = ->(exp) { tmux.until { |lines| lines[-2].include? exp } }
|
wait = ->(exp) { tmux.until { |lines| lines[-2].include? exp } }
|
||||||
writelines tempname, %w[foo'bar foo"bar foo$bar]
|
writelines tempname, %w[foo'bar foo"bar foo$bar]
|
||||||
tmux.send_keys "cat #{tempname} | #{fzf opts}; sync", :Enter
|
tmux.send_keys "cat #{tempname} | #{fzf opts}; sync", :Enter
|
||||||
@@ -977,7 +965,7 @@ class TestGoFZF < TestBase
|
|||||||
|
|
||||||
def test_execute_multi
|
def test_execute_multi
|
||||||
output = '/tmp/fzf-test-execute-multi'
|
output = '/tmp/fzf-test-execute-multi'
|
||||||
opts = %[--multi --bind \\"alt-a:execute-multi(echo {}/{+} >> #{output}; sync)\\"]
|
opts = %[--multi --bind "alt-a:execute-multi(echo {}/{+} >> #{output}; sync)"]
|
||||||
writelines tempname, %w[foo'bar foo"bar foo$bar foobar]
|
writelines tempname, %w[foo'bar foo"bar foo$bar foobar]
|
||||||
tmux.send_keys "cat #{tempname} | #{fzf opts}", :Enter
|
tmux.send_keys "cat #{tempname} | #{fzf opts}", :Enter
|
||||||
tmux.until { |lines| lines[-2].include? '4/4' }
|
tmux.until { |lines| lines[-2].include? '4/4' }
|
||||||
@@ -1163,7 +1151,7 @@ class TestGoFZF < TestBase
|
|||||||
end
|
end
|
||||||
|
|
||||||
def test_header
|
def test_header
|
||||||
tmux.send_keys "seq 100 | #{fzf "--header \\\"\\$(head -5 #{FILE})\\\""}", :Enter
|
tmux.send_keys "seq 100 | #{fzf "--header \"$(head -5 #{FILE})\""}", :Enter
|
||||||
header = File.readlines(FILE).take(5).map(&:strip)
|
header = File.readlines(FILE).take(5).map(&:strip)
|
||||||
tmux.until do |lines|
|
tmux.until do |lines|
|
||||||
lines[-2].include?('100/100') &&
|
lines[-2].include?('100/100') &&
|
||||||
@@ -1173,7 +1161,7 @@ class TestGoFZF < TestBase
|
|||||||
end
|
end
|
||||||
|
|
||||||
def test_header_reverse
|
def test_header_reverse
|
||||||
tmux.send_keys "seq 100 | #{fzf "--header=\\\"\\$(head -5 #{FILE})\\\" --reverse"}", :Enter
|
tmux.send_keys "seq 100 | #{fzf "--header \"$(head -5 #{FILE})\" --reverse"}", :Enter
|
||||||
header = File.readlines(FILE).take(5).map(&:strip)
|
header = File.readlines(FILE).take(5).map(&:strip)
|
||||||
tmux.until do |lines|
|
tmux.until do |lines|
|
||||||
lines[1].include?('100/100') &&
|
lines[1].include?('100/100') &&
|
||||||
@@ -1183,7 +1171,7 @@ class TestGoFZF < TestBase
|
|||||||
end
|
end
|
||||||
|
|
||||||
def test_header_reverse_list
|
def test_header_reverse_list
|
||||||
tmux.send_keys "seq 100 | #{fzf "--header=\\\"\\$(head -5 #{FILE})\\\" --layout=reverse-list"}", :Enter
|
tmux.send_keys "seq 100 | #{fzf "--header \"$(head -5 #{FILE})\" --layout=reverse-list"}", :Enter
|
||||||
header = File.readlines(FILE).take(5).map(&:strip)
|
header = File.readlines(FILE).take(5).map(&:strip)
|
||||||
tmux.until do |lines|
|
tmux.until do |lines|
|
||||||
lines[-2].include?('100/100') &&
|
lines[-2].include?('100/100') &&
|
||||||
@@ -1193,7 +1181,7 @@ class TestGoFZF < TestBase
|
|||||||
end
|
end
|
||||||
|
|
||||||
def test_header_and_header_lines
|
def test_header_and_header_lines
|
||||||
tmux.send_keys "seq 100 | #{fzf "--header-lines 10 --header \\\"\\$(head -5 #{FILE})\\\""}", :Enter
|
tmux.send_keys "seq 100 | #{fzf "--header-lines 10 --header \"$(head -5 #{FILE})\""}", :Enter
|
||||||
header = File.readlines(FILE).take(5).map(&:strip)
|
header = File.readlines(FILE).take(5).map(&:strip)
|
||||||
tmux.until do |lines|
|
tmux.until do |lines|
|
||||||
lines[-2].include?('90/90') &&
|
lines[-2].include?('90/90') &&
|
||||||
@@ -1203,7 +1191,7 @@ class TestGoFZF < TestBase
|
|||||||
end
|
end
|
||||||
|
|
||||||
def test_header_and_header_lines_reverse
|
def test_header_and_header_lines_reverse
|
||||||
tmux.send_keys "seq 100 | #{fzf "--reverse --header-lines 10 --header \\\"\\$(head -5 #{FILE})\\\""}", :Enter
|
tmux.send_keys "seq 100 | #{fzf "--reverse --header-lines 10 --header \"$(head -5 #{FILE})\""}", :Enter
|
||||||
header = File.readlines(FILE).take(5).map(&:strip)
|
header = File.readlines(FILE).take(5).map(&:strip)
|
||||||
tmux.until do |lines|
|
tmux.until do |lines|
|
||||||
lines[1].include?('90/90') &&
|
lines[1].include?('90/90') &&
|
||||||
@@ -1213,7 +1201,7 @@ class TestGoFZF < TestBase
|
|||||||
end
|
end
|
||||||
|
|
||||||
def test_header_and_header_lines_reverse_list
|
def test_header_and_header_lines_reverse_list
|
||||||
tmux.send_keys "seq 100 | #{fzf "--layout=reverse-list --header-lines 10 --header \\\"\\$(head -5 #{FILE})\\\""}", :Enter
|
tmux.send_keys "seq 100 | #{fzf "--layout=reverse-list --header-lines 10 --header \"$(head -5 #{FILE})\""}", :Enter
|
||||||
header = File.readlines(FILE).take(5).map(&:strip)
|
header = File.readlines(FILE).take(5).map(&:strip)
|
||||||
tmux.until do |lines|
|
tmux.until do |lines|
|
||||||
lines[-2].include?('90/90') &&
|
lines[-2].include?('90/90') &&
|
||||||
@@ -1324,7 +1312,7 @@ class TestGoFZF < TestBase
|
|||||||
|
|
||||||
def test_exitstatus_empty
|
def test_exitstatus_empty
|
||||||
{ '99' => '0', '999' => '1' }.each do |query, status|
|
{ '99' => '0', '999' => '1' }.each do |query, status|
|
||||||
tmux.send_keys "seq 100 | #{FZF} -q #{query}; echo --\\$?--", :Enter
|
tmux.send_keys "seq 100 | #{FZF} -q #{query}; echo --$?--", :Enter
|
||||||
tmux.until { |lines| lines[-2] =~ %r{ [10]/100} }
|
tmux.until { |lines| lines[-2] =~ %r{ [10]/100} }
|
||||||
tmux.send_keys :Enter
|
tmux.send_keys :Enter
|
||||||
tmux.until { |lines| lines.last.include? "--#{status}--" }
|
tmux.until { |lines| lines.last.include? "--#{status}--" }
|
||||||
@@ -1407,6 +1395,39 @@ class TestGoFZF < TestBase
|
|||||||
assert_equal '3', readonce.chomp
|
assert_equal '3', readonce.chomp
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_pointer
|
||||||
|
pointer = '>>'
|
||||||
|
tmux.send_keys "seq 10 | #{fzf "--pointer '#{pointer}'"}", :Enter
|
||||||
|
tmux.until { |lines| lines[-2] == ' 10/10' }
|
||||||
|
lines = tmux.capture
|
||||||
|
# Assert that specified pointer is displayed
|
||||||
|
assert_equal "#{pointer} 1", lines[-3]
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_pointer_with_jump
|
||||||
|
pointer = '>>'
|
||||||
|
tmux.send_keys "seq 10 | #{fzf "--multi --jump-labels 12345 --bind 'ctrl-j:jump' --pointer '#{pointer}'"}", :Enter
|
||||||
|
tmux.until { |lines| lines[-2] == ' 10/10' }
|
||||||
|
tmux.send_keys 'C-j'
|
||||||
|
# Correctly padded jump label should appear
|
||||||
|
tmux.until { |lines| lines[-7] == '5 5' }
|
||||||
|
tmux.until { |lines| lines[-8] == ' 6' }
|
||||||
|
tmux.send_keys '5'
|
||||||
|
lines = tmux.capture
|
||||||
|
# Assert that specified pointer is displayed
|
||||||
|
assert_equal "#{pointer} 5", lines[-7]
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_marker
|
||||||
|
marker = '>>'
|
||||||
|
tmux.send_keys "seq 10 | #{fzf "--multi --marker '#{marker}'"}", :Enter
|
||||||
|
tmux.until { |lines| lines[-2] == ' 10/10' }
|
||||||
|
tmux.send_keys :BTab
|
||||||
|
lines = tmux.capture
|
||||||
|
# Assert that specified marker is displayed
|
||||||
|
assert_equal " #{marker}1", lines[-3]
|
||||||
|
end
|
||||||
|
|
||||||
def test_preview
|
def test_preview
|
||||||
tmux.send_keys %(seq 1000 | sed s/^2$// | #{FZF} -m --preview 'sleep 0.2; echo {{}-{+}}' --bind ?:toggle-preview), :Enter
|
tmux.send_keys %(seq 1000 | sed s/^2$// | #{FZF} -m --preview 'sleep 0.2; echo {{}-{+}}' --bind ?:toggle-preview), :Enter
|
||||||
tmux.until { |lines| lines[1].include?(' {1-1}') }
|
tmux.until { |lines| lines[1].include?(' {1-1}') }
|
||||||
@@ -1436,7 +1457,7 @@ class TestGoFZF < TestBase
|
|||||||
end
|
end
|
||||||
|
|
||||||
def test_preview_hidden
|
def test_preview_hidden
|
||||||
tmux.send_keys %(seq 1000 | #{FZF} --preview 'echo {{}-{}-\\$FZF_PREVIEW_LINES-\\$FZF_PREVIEW_COLUMNS}' --preview-window down:1:hidden --bind ?:toggle-preview), :Enter
|
tmux.send_keys %(seq 1000 | #{FZF} --preview 'echo {{}-{}-$FZF_PREVIEW_LINES-$FZF_PREVIEW_COLUMNS}' --preview-window down:1:hidden --bind ?:toggle-preview), :Enter
|
||||||
tmux.until { |lines| lines[-1] == '>' }
|
tmux.until { |lines| lines[-1] == '>' }
|
||||||
tmux.send_keys '?'
|
tmux.send_keys '?'
|
||||||
tmux.until { |lines| lines[-2] =~ / {1-1-1-[0-9]+}/ }
|
tmux.until { |lines| lines[-2] =~ / {1-1-1-[0-9]+}/ }
|
||||||
@@ -1640,11 +1661,80 @@ class TestGoFZF < TestBase
|
|||||||
tmux.until { |lines| lines.item_count == 553 && lines.match_count == 1 }
|
tmux.until { |lines| lines.item_count == 553 && lines.match_count == 1 }
|
||||||
tmux.until { |lines| !lines[-2].include?('(1/2)') }
|
tmux.until { |lines| !lines[-2].include?('(1/2)') }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_reload_even_when_theres_no_match
|
||||||
|
tmux.send_keys %(: | #{FZF} --bind 'space:reload(seq 10)'), :Enter
|
||||||
|
tmux.until { |lines| lines.item_count.zero? }
|
||||||
|
tmux.send_keys :Space
|
||||||
|
tmux.until { |lines| lines.item_count == 10 }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_clear_list_when_header_lines_changed_due_to_reload
|
||||||
|
tmux.send_keys %(seq 10 | #{FZF} --header 0 --header-lines 3 --bind 'space:reload(seq 1)'), :Enter
|
||||||
|
tmux.until { |lines| lines.any? { |line| line.include?('9') } }
|
||||||
|
tmux.send_keys :Space
|
||||||
|
tmux.until { |lines| lines.none? { |line| line.include?('9') } }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_clear_query
|
||||||
|
tmux.send_keys %(: | #{FZF} --query foo --bind space:clear-query), :Enter
|
||||||
|
tmux.until { |lines| lines.item_count.zero? }
|
||||||
|
tmux.until { |lines| lines.last.include?('> foo') }
|
||||||
|
tmux.send_keys 'C-a', 'bar'
|
||||||
|
tmux.until { |lines| lines.last.include?('> barfoo') }
|
||||||
|
tmux.send_keys :Space
|
||||||
|
tmux.until { |lines| lines.last == '>' }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_clear_selection
|
||||||
|
tmux.send_keys %(seq 100 | #{FZF} --multi --bind space:clear-selection), :Enter
|
||||||
|
tmux.until { |lines| lines.match_count == 100 }
|
||||||
|
tmux.send_keys :Tab
|
||||||
|
tmux.until { |lines| lines[-2].include?('(1)') }
|
||||||
|
tmux.send_keys 'foo'
|
||||||
|
tmux.until { |lines| lines.match_count.zero? }
|
||||||
|
tmux.until { |lines| lines[-2].include?('(1)') }
|
||||||
|
tmux.send_keys :Space
|
||||||
|
tmux.until { |lines| lines.match_count.zero? }
|
||||||
|
tmux.until { |lines| !lines[-2].include?('(1)') }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_backward_delete_char_eof
|
||||||
|
tmux.send_keys "seq 1000 | #{fzf "--bind 'bs:backward-delete-char/eof'"}", :Enter
|
||||||
|
tmux.until { |lines| lines[-2] == ' 1000/1000' }
|
||||||
|
tmux.send_keys '11'
|
||||||
|
tmux.until { |lines| lines[-1] == '> 11' }
|
||||||
|
tmux.send_keys :BSpace
|
||||||
|
tmux.until { |lines| lines[-1] == '> 1' }
|
||||||
|
tmux.send_keys :BSpace
|
||||||
|
tmux.until { |lines| lines[-1] == '>' }
|
||||||
|
tmux.send_keys :BSpace
|
||||||
|
tmux.prepare
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_strip_xterm_osc_sequence
|
||||||
|
%W[\x07 \x1b\\].each do |esc|
|
||||||
|
writelines tempname, [%(printf $1"\e]4;3;rgb:aa/bb/cc#{esc} "$2)]
|
||||||
|
File.chmod(0o755, tempname)
|
||||||
|
tmux.prepare
|
||||||
|
tmux.send_keys(
|
||||||
|
%(echo foo bar | #{FZF} --preview '#{tempname} {2} {1}'), :Enter
|
||||||
|
)
|
||||||
|
tmux.until { |lines| lines.any_include?('bar foo') }
|
||||||
|
tmux.send_keys :Enter
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_keep_right
|
||||||
|
tmux.send_keys("seq 10000 | #{FZF} --read0 --keep-right", :Enter)
|
||||||
|
tmux.until { |lines| lines.any_include?('9999 10000') }
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
module TestShell
|
module TestShell
|
||||||
def setup
|
def setup
|
||||||
super
|
@tmux = Tmux.new shell
|
||||||
|
tmux.prepare
|
||||||
end
|
end
|
||||||
|
|
||||||
def teardown
|
def teardown
|
||||||
@@ -1704,7 +1794,7 @@ module TestShell
|
|||||||
tmux.until { |lines| lines.select_count == 2 }
|
tmux.until { |lines| lines.select_count == 2 }
|
||||||
|
|
||||||
tmux.send_keys :Enter
|
tmux.send_keys :Enter
|
||||||
tmux.until { |lines| lines.any_include?(/echo.*fzf-unicode.*1.*fzf-unicode.*2/) }
|
tmux.until { |lines| lines.join.match(/echo.*fzf-unicode.*1.*fzf-unicode.*2/) }
|
||||||
tmux.send_keys :Enter
|
tmux.send_keys :Enter
|
||||||
tmux.until { |lines| lines.any_include?(/^fzf-unicode.*1.*fzf-unicode.*2/) }
|
tmux.until { |lines| lines.any_include?(/^fzf-unicode.*1.*fzf-unicode.*2/) }
|
||||||
end
|
end
|
||||||
@@ -1745,15 +1835,18 @@ module TestShell
|
|||||||
tmux.send_keys 'echo 1st', :Enter; tmux.prepare
|
tmux.send_keys 'echo 1st', :Enter; tmux.prepare
|
||||||
tmux.send_keys 'echo 2nd', :Enter; tmux.prepare
|
tmux.send_keys 'echo 2nd', :Enter; tmux.prepare
|
||||||
tmux.send_keys 'echo 3d', :Enter; tmux.prepare
|
tmux.send_keys 'echo 3d', :Enter; tmux.prepare
|
||||||
tmux.send_keys 'echo 3rd', :Enter; tmux.prepare
|
3.times { tmux.send_keys 'echo 3rd', :Enter; tmux.prepare }
|
||||||
tmux.send_keys 'echo 4th', :Enter
|
tmux.send_keys 'echo 4th', :Enter
|
||||||
retries do
|
retries do
|
||||||
tmux.prepare
|
tmux.prepare
|
||||||
tmux.send_keys 'C-r'
|
tmux.send_keys 'C-r'
|
||||||
tmux.until { |lines| lines.match_count.positive? }
|
tmux.until { |lines| lines.match_count.positive? }
|
||||||
end
|
end
|
||||||
|
tmux.send_keys 'e3d'
|
||||||
|
# Duplicates removed: 3d (1) + 3rd (1) => 2 matches
|
||||||
|
tmux.until { |lines| lines.match_count == 2 }
|
||||||
|
tmux.until { |lines| lines[-3].end_with? 'echo 3d' }
|
||||||
tmux.send_keys 'C-r'
|
tmux.send_keys 'C-r'
|
||||||
tmux.send_keys '3d'
|
|
||||||
tmux.until { |lines| lines[-3].end_with? 'echo 3rd' }
|
tmux.until { |lines| lines[-3].end_with? 'echo 3rd' }
|
||||||
tmux.send_keys :Enter
|
tmux.send_keys :Enter
|
||||||
tmux.until { |lines| lines[-1] == 'echo 3rd' }
|
tmux.until { |lines| lines[-1] == 'echo 3rd' }
|
||||||
@@ -1761,6 +1854,37 @@ module TestShell
|
|||||||
tmux.until { |lines| lines[-1] == '3rd' }
|
tmux.until { |lines| lines[-1] == '3rd' }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_ctrl_r_multiline
|
||||||
|
tmux.send_keys 'echo "foo', :Enter, 'bar"', :Enter
|
||||||
|
tmux.until { |lines| lines[-2..-1] == ['foo', 'bar'] }
|
||||||
|
retries do
|
||||||
|
tmux.prepare
|
||||||
|
tmux.send_keys 'C-r'
|
||||||
|
tmux.until { |lines| lines[-1] == '>' }
|
||||||
|
end
|
||||||
|
tmux.send_keys 'foo bar'
|
||||||
|
tmux.until { |lines| lines[-3].end_with? 'bar"' }
|
||||||
|
tmux.send_keys :Enter
|
||||||
|
tmux.until { |lines| lines[-1].end_with? 'bar"' }
|
||||||
|
tmux.send_keys :Enter
|
||||||
|
tmux.until { |lines| lines[-2..-1] == ['foo', 'bar'] }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_ctrl_r_abort
|
||||||
|
skip "doesn't restore the original line when search is aborted pre Bash 4" if shell == :bash && /(?<= version )\d+/.match(`#{Shell.bash} --version`).to_s.to_i < 4
|
||||||
|
%w[foo ' "].each do |query|
|
||||||
|
retries do
|
||||||
|
tmux.prepare
|
||||||
|
tmux.send_keys(:Space, 'C-e', 'C-u', query)
|
||||||
|
tmux.until { |lines| lines[-1].start_with? query }
|
||||||
|
tmux.send_keys 'C-r'
|
||||||
|
tmux.until { |lines| lines[-1] == "> #{query}" }
|
||||||
|
end
|
||||||
|
tmux.send_keys 'C-g'
|
||||||
|
tmux.until { |lines| lines[-1].start_with? query }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def retries(times = 3)
|
def retries(times = 3)
|
||||||
(times - 1).times do
|
(times - 1).times do
|
||||||
begin
|
begin
|
||||||
@@ -1890,7 +2014,7 @@ module CompletionTest
|
|||||||
end
|
end
|
||||||
|
|
||||||
def test_custom_completion
|
def test_custom_completion
|
||||||
tmux.send_keys '_fzf_compgen_path() { echo "\$1"; seq 10; }', :Enter
|
tmux.send_keys '_fzf_compgen_path() { echo "$1"; seq 10; }', :Enter
|
||||||
tmux.prepare
|
tmux.prepare
|
||||||
tmux.send_keys 'ls /tmp/**', :Tab
|
tmux.send_keys 'ls /tmp/**', :Tab
|
||||||
tmux.until { |lines| lines.match_count == 11 }
|
tmux.until { |lines| lines.match_count == 11 }
|
||||||
@@ -1913,15 +2037,16 @@ module CompletionTest
|
|||||||
|
|
||||||
# FZF_TMUX=1
|
# FZF_TMUX=1
|
||||||
new_shell
|
new_shell
|
||||||
tmux.send_keys 'unset FZFFOOBR**', :Tab, pane: 0
|
tmux.focus
|
||||||
tmux.until(false, 1) { |lines| lines.match_count == 1 }
|
tmux.send_keys 'unset FZFFOOBR**', :Tab
|
||||||
|
tmux.until { |lines| lines.match_count == 1 }
|
||||||
tmux.send_keys :Enter
|
tmux.send_keys :Enter
|
||||||
tmux.until { |lines| lines[-1].include? 'unset FZFFOOBAR' }
|
tmux.until { |lines| lines[-1].include? 'unset FZFFOOBAR' }
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_file_completion_unicode
|
def test_file_completion_unicode
|
||||||
FileUtils.mkdir_p '/tmp/fzf-test'
|
FileUtils.mkdir_p '/tmp/fzf-test'
|
||||||
tmux.paste 'cd /tmp/fzf-test; echo -n test3 > "fzf-unicode 테스트1"; echo -n test4 > "fzf-unicode 테스트2"'
|
tmux.paste "cd /tmp/fzf-test; echo -n test3 > $'fzf-unicode \\355\\205\\214\\354\\212\\244\\355\\212\\2701'; echo -n test4 > $'fzf-unicode \\355\\205\\214\\354\\212\\244\\355\\212\\2702'"
|
||||||
tmux.prepare
|
tmux.prepare
|
||||||
tmux.send_keys 'cat fzf-unicode**', :Tab
|
tmux.send_keys 'cat fzf-unicode**', :Tab
|
||||||
tmux.until { |lines| lines.match_count == 2 }
|
tmux.until { |lines| lines.match_count == 2 }
|
||||||
@@ -1944,23 +2069,41 @@ module CompletionTest
|
|||||||
tmux.send_keys :Enter
|
tmux.send_keys :Enter
|
||||||
tmux.until { |lines| lines[-1].include? 'test3test4' }
|
tmux.until { |lines| lines[-1].include? 'test3test4' }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_custom_completion_api
|
||||||
|
tmux.send_keys 'eval "_fzf$(declare -f _comprun)"', :Enter
|
||||||
|
%w[f g].each do |command|
|
||||||
|
tmux.prepare
|
||||||
|
tmux.send_keys "#{command} b**", :Tab
|
||||||
|
tmux.until do |lines|
|
||||||
|
lines.item_count == 2 && lines.match_count == 1 &&
|
||||||
|
lines.any_include?("prompt-#{command}") &&
|
||||||
|
lines.any_include?("preview-#{command}-bar")
|
||||||
|
end
|
||||||
|
tmux.send_keys :Enter
|
||||||
|
tmux.until { |lines| lines[-1].include?("#{command} #{command}barbar") }
|
||||||
|
tmux.send_keys 'C-u'
|
||||||
|
end
|
||||||
|
ensure
|
||||||
|
tmux.prepare
|
||||||
|
tmux.send_keys 'unset -f _fzf_comprun', :Enter
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
class TestBash < TestBase
|
class TestBash < TestBase
|
||||||
include TestShell
|
include TestShell
|
||||||
include CompletionTest
|
include CompletionTest
|
||||||
|
|
||||||
|
def shell
|
||||||
|
:bash
|
||||||
|
end
|
||||||
|
|
||||||
def new_shell
|
def new_shell
|
||||||
tmux.prepare
|
tmux.prepare
|
||||||
tmux.send_keys "FZF_TMUX=1 #{Shell.bash}", :Enter
|
tmux.send_keys "FZF_TMUX=1 #{Shell.bash}", :Enter
|
||||||
tmux.prepare
|
tmux.prepare
|
||||||
end
|
end
|
||||||
|
|
||||||
def setup
|
|
||||||
super
|
|
||||||
@tmux = Tmux.new :bash
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_dynamic_completion_loader
|
def test_dynamic_completion_loader
|
||||||
tmux.paste 'touch /tmp/foo; _fzf_completion_loader=1'
|
tmux.paste 'touch /tmp/foo; _fzf_completion_loader=1'
|
||||||
tmux.paste '_completion_loader() { complete -o default fake; }'
|
tmux.paste '_completion_loader() { complete -o default fake; }'
|
||||||
@@ -1983,20 +2126,23 @@ class TestZsh < TestBase
|
|||||||
include TestShell
|
include TestShell
|
||||||
include CompletionTest
|
include CompletionTest
|
||||||
|
|
||||||
|
def shell
|
||||||
|
:zsh
|
||||||
|
end
|
||||||
|
|
||||||
def new_shell
|
def new_shell
|
||||||
tmux.send_keys "FZF_TMUX=1 #{Shell.zsh}", :Enter
|
tmux.send_keys "FZF_TMUX=1 #{Shell.zsh}", :Enter
|
||||||
tmux.prepare
|
tmux.prepare
|
||||||
end
|
end
|
||||||
|
|
||||||
def setup
|
|
||||||
super
|
|
||||||
@tmux = Tmux.new :zsh
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
class TestFish < TestBase
|
class TestFish < TestBase
|
||||||
include TestShell
|
include TestShell
|
||||||
|
|
||||||
|
def shell
|
||||||
|
:fish
|
||||||
|
end
|
||||||
|
|
||||||
def new_shell
|
def new_shell
|
||||||
tmux.send_keys 'env FZF_TMUX=1 fish', :Enter
|
tmux.send_keys 'env FZF_TMUX=1 fish', :Enter
|
||||||
tmux.send_keys 'function fish_prompt; end; clear', :Enter
|
tmux.send_keys 'function fish_prompt; end; clear', :Enter
|
||||||
@@ -2008,9 +2154,60 @@ class TestFish < TestBase
|
|||||||
tmux.send_keys "set -g #{name} '#{val}'", :Enter
|
tmux.send_keys "set -g #{name} '#{val}'", :Enter
|
||||||
tmux.prepare
|
tmux.prepare
|
||||||
end
|
end
|
||||||
|
|
||||||
def setup
|
|
||||||
super
|
|
||||||
@tmux = Tmux.new :fish
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
__END__
|
||||||
|
# Setup fzf
|
||||||
|
# ---------
|
||||||
|
if [[ ! "$PATH" == *<%= BASE %>/bin* ]]; then
|
||||||
|
export PATH="${PATH:+${PATH}:}<%= BASE %>/bin"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Auto-completion
|
||||||
|
# ---------------
|
||||||
|
[[ $- == *i* ]] && source "<%= BASE %>/shell/completion.<%= __method__ %>" 2> /dev/null
|
||||||
|
|
||||||
|
# Key bindings
|
||||||
|
# ------------
|
||||||
|
source "<%= BASE %>/shell/key-bindings.<%= __method__ %>"
|
||||||
|
|
||||||
|
PS1= PROMPT_COMMAND= HISTFILE= HISTSIZE=100
|
||||||
|
unset <%= UNSETS.join(' ') %>
|
||||||
|
|
||||||
|
# Old API
|
||||||
|
_fzf_complete_f() {
|
||||||
|
_fzf_complete "+m --multi --prompt \"prompt-f> \"" "$@" < <(
|
||||||
|
echo foo
|
||||||
|
echo bar
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
# New API
|
||||||
|
_fzf_complete_g() {
|
||||||
|
_fzf_complete +m --multi --prompt "prompt-g> " -- "$@" < <(
|
||||||
|
echo foo
|
||||||
|
echo bar
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
_fzf_complete_f_post() {
|
||||||
|
awk '{print "f" $0 $0}'
|
||||||
|
}
|
||||||
|
|
||||||
|
_fzf_complete_g_post() {
|
||||||
|
awk '{print "g" $0 $0}'
|
||||||
|
}
|
||||||
|
|
||||||
|
[ -n "$BASH" ] && complete -F _fzf_complete_f -o default -o bashdefault f
|
||||||
|
[ -n "$BASH" ] && complete -F _fzf_complete_g -o default -o bashdefault g
|
||||||
|
|
||||||
|
_comprun() {
|
||||||
|
local command=$1
|
||||||
|
shift
|
||||||
|
|
||||||
|
case "$command" in
|
||||||
|
f) fzf "$@" --preview 'echo preview-f-{}' ;;
|
||||||
|
g) fzf "$@" --preview 'echo preview-g-{}' ;;
|
||||||
|
*) fzf "$@" ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user