mirror of
https://github.com/junegunn/fzf.git
synced 2025-11-14 14:23:47 -05:00
Compare commits
25 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ee5aeb80a4 | ||
|
|
02ceae15a2 | ||
|
|
e514739280 | ||
|
|
72265298f9 | ||
|
|
4b700192c1 | ||
|
|
fe83589ade | ||
|
|
fcf63c74f1 | ||
|
|
c95bb109c8 | ||
|
|
bd9c46ee34 | ||
|
|
736aeaa1d3 | ||
|
|
dd1f26522c | ||
|
|
712b7b2188 | ||
|
|
5b749e2d5c | ||
|
|
d85a69a709 | ||
|
|
a425e96fb2 | ||
|
|
7763fdf6ba | ||
|
|
dd156b59fc | ||
|
|
36dceecd58 | ||
|
|
421b9b271a | ||
|
|
ed57dcb924 | ||
|
|
95c77bfb98 | ||
|
|
2e3e721344 | ||
|
|
da2c28d5c2 | ||
|
|
dbddee9de9 | ||
|
|
8731d75607 |
56
BUILD.md
56
BUILD.md
@@ -25,65 +25,22 @@ make install
|
|||||||
# Build 32-bit and 64-bit executables and tarballs
|
# Build 32-bit and 64-bit executables and tarballs
|
||||||
make release
|
make release
|
||||||
|
|
||||||
# Build executables and tarballs for Linux using Docker
|
# Make release archives for all supported platforms
|
||||||
make linux
|
make release-all
|
||||||
```
|
```
|
||||||
|
|
||||||
### Using `go get`
|
### Using `go get`
|
||||||
|
|
||||||
Alternatively, you can build fzf directly with `go get` command without
|
Alternatively, you can build fzf directly with `go get` command without
|
||||||
cloning the repository.
|
manually cloning the repository.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
go get -u github.com/junegunn/fzf/src/fzf
|
go get -u github.com/junegunn/fzf/src/fzf
|
||||||
```
|
```
|
||||||
|
|
||||||
Build options
|
|
||||||
-------------
|
|
||||||
|
|
||||||
### With ncurses 6
|
|
||||||
|
|
||||||
The official binaries of fzf are built with ncurses 5 because it's widely
|
|
||||||
supported by different platforms. However ncurses 5 is old and has a number of
|
|
||||||
limitations.
|
|
||||||
|
|
||||||
1. Does not support more than 256 color pairs (See [357][357])
|
|
||||||
2. Does not support italics
|
|
||||||
3. Does not support 24-bit color
|
|
||||||
|
|
||||||
[357]: https://github.com/junegunn/fzf/issues/357
|
|
||||||
|
|
||||||
But you can manually build fzf with ncurses 6 to overcome some of these
|
|
||||||
limitations. ncurses 6 supports up to 32767 color pairs (1), and supports
|
|
||||||
italics (2). To build fzf with ncurses 6, you have to install it first. On
|
|
||||||
macOS, you can use Homebrew to install it.
|
|
||||||
|
|
||||||
```sh
|
|
||||||
brew install homebrew/dupes/ncurses
|
|
||||||
LDFLAGS="-L/usr/local/opt/ncurses/lib" make install
|
|
||||||
```
|
|
||||||
|
|
||||||
### With tcell
|
|
||||||
|
|
||||||
[tcell][tcell] is a portable alternative to ncurses and we currently use it to
|
|
||||||
build Windows binaries. tcell has many benefits but most importantly, it
|
|
||||||
supports 24-bit colors. To build fzf with tcell:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
TAGS=tcell make install
|
|
||||||
```
|
|
||||||
|
|
||||||
However, note that tcell has its own issues.
|
|
||||||
|
|
||||||
- Poor rendering performance compared to ncurses
|
|
||||||
- Does not support bracketed-paste mode
|
|
||||||
- Does not support italics unlike ncurses 6
|
|
||||||
- Some wide characters are not correctly displayed
|
|
||||||
|
|
||||||
Third-party libraries used
|
Third-party libraries used
|
||||||
--------------------------
|
--------------------------
|
||||||
|
|
||||||
- [ncurses][ncurses]
|
|
||||||
- [mattn/go-runewidth](https://github.com/mattn/go-runewidth)
|
- [mattn/go-runewidth](https://github.com/mattn/go-runewidth)
|
||||||
- Licensed under [MIT](http://mattn.mit-license.org)
|
- Licensed under [MIT](http://mattn.mit-license.org)
|
||||||
- [mattn/go-shellwords](https://github.com/mattn/go-shellwords)
|
- [mattn/go-shellwords](https://github.com/mattn/go-shellwords)
|
||||||
@@ -97,10 +54,3 @@ License
|
|||||||
-------
|
-------
|
||||||
|
|
||||||
[MIT](LICENSE)
|
[MIT](LICENSE)
|
||||||
|
|
||||||
[install]: https://github.com/junegunn/fzf#installation
|
|
||||||
[go]: https://golang.org/
|
|
||||||
[gil]: http://en.wikipedia.org/wiki/Global_Interpreter_Lock
|
|
||||||
[ncurses]: https://www.gnu.org/software/ncurses/
|
|
||||||
[req]: http://golang.org/doc/install
|
|
||||||
[tcell]: https://github.com/gdamore/tcell
|
|
||||||
|
|||||||
18
CHANGELOG.md
18
CHANGELOG.md
@@ -1,6 +1,24 @@
|
|||||||
CHANGELOG
|
CHANGELOG
|
||||||
=========
|
=========
|
||||||
|
|
||||||
|
0.16.4
|
||||||
|
------
|
||||||
|
- Added `--border` option to draw border above and below the finder
|
||||||
|
- Bug fixes and improvements
|
||||||
|
|
||||||
|
0.16.3
|
||||||
|
------
|
||||||
|
- Fixed a bug where fzf incorrectly display the lines when straddling tab
|
||||||
|
characters are trimmed
|
||||||
|
- Placeholder expression used in `--preview` and `execute` action can
|
||||||
|
optionally take `+` flag to be used with multiple selections
|
||||||
|
- e.g. `git log --oneline | fzf --multi --preview 'git show {+1}'`
|
||||||
|
- Added `execute-silent` action for executing a command silently without
|
||||||
|
switching to the alternate screen. This is useful when the process is
|
||||||
|
short-lived and you're not interested in its output.
|
||||||
|
- e.g. `fzf --bind 'ctrl-y:execute!(echo -n {} | pbcopy)'`
|
||||||
|
- `ctrl-space` is allowed in `--bind`
|
||||||
|
|
||||||
0.16.2
|
0.16.2
|
||||||
------
|
------
|
||||||
- Dropped ncurses dependency
|
- Dropped ncurses dependency
|
||||||
|
|||||||
41
README.md
41
README.md
@@ -72,16 +72,6 @@ But it's recommended that you use a plugin manager like
|
|||||||
Plug 'junegunn/fzf', { 'dir': '~/.fzf', 'do': './install --all' }
|
Plug 'junegunn/fzf', { 'dir': '~/.fzf', 'do': './install --all' }
|
||||||
```
|
```
|
||||||
|
|
||||||
### Upgrading fzf
|
|
||||||
|
|
||||||
fzf is being actively developed and you might want to upgrade it once in a
|
|
||||||
while. Please follow the instruction below depending on the installation
|
|
||||||
method used.
|
|
||||||
|
|
||||||
- git: `cd ~/.fzf && git pull && ./install`
|
|
||||||
- brew: `brew update; brew reinstall fzf`
|
|
||||||
- vim-plug: `:PlugUpdate fzf`
|
|
||||||
|
|
||||||
### Windows
|
### Windows
|
||||||
|
|
||||||
Pre-built binaries for Windows can be downloaded [here][bin]. However, other
|
Pre-built binaries for Windows can be downloaded [here][bin]. However, other
|
||||||
@@ -91,6 +81,17 @@ flawlessly.
|
|||||||
|
|
||||||
[wsl]: https://blogs.msdn.microsoft.com/wsl/
|
[wsl]: https://blogs.msdn.microsoft.com/wsl/
|
||||||
|
|
||||||
|
Upgrading fzf
|
||||||
|
-------------
|
||||||
|
|
||||||
|
fzf is being actively developed and you might want to upgrade it once in a
|
||||||
|
while. Please follow the instruction below depending on the installation
|
||||||
|
method used.
|
||||||
|
|
||||||
|
- git: `cd ~/.fzf && git pull && ./install`
|
||||||
|
- brew: `brew update; brew reinstall fzf`
|
||||||
|
- vim-plug: `:PlugUpdate fzf`
|
||||||
|
|
||||||
Building fzf
|
Building fzf
|
||||||
------------
|
------------
|
||||||
|
|
||||||
@@ -140,10 +141,10 @@ vim $(fzf --height 40% --reverse)
|
|||||||
```
|
```
|
||||||
|
|
||||||
You can add these options to `$FZF_DEFAULT_OPTS` so that they're applied by
|
You can add these options to `$FZF_DEFAULT_OPTS` so that they're applied by
|
||||||
default.
|
default. For example,
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
export FZF_DEFAULT_OPTS='--height 40% --reverse'
|
export FZF_DEFAULT_OPTS='--height 40% --reverse --border'
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Search syntax
|
#### Search syntax
|
||||||
@@ -357,7 +358,7 @@ If you have set up fzf for Vim, `:FZF` command will be added.
|
|||||||
" With options
|
" With options
|
||||||
:FZF --no-sort --reverse --inline-info /tmp
|
:FZF --no-sort --reverse --inline-info /tmp
|
||||||
|
|
||||||
" Bang version starts in fullscreen instead of using tmux pane or Neovim split
|
" Bang version starts fzf in fullscreen mode
|
||||||
:FZF!
|
:FZF!
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -408,20 +409,6 @@ command! -bang MyStuff
|
|||||||
Tips
|
Tips
|
||||||
----
|
----
|
||||||
|
|
||||||
#### Rendering issues
|
|
||||||
|
|
||||||
If you have any rendering issues, check the following:
|
|
||||||
|
|
||||||
1. Make sure `$TERM` is correctly set. fzf will use 256-color only if it
|
|
||||||
contains `256` (e.g. `xterm-256color`)
|
|
||||||
2. If you're on screen or tmux, `$TERM` should be either `screen` or
|
|
||||||
`screen-256color`
|
|
||||||
3. Some terminal emulators (e.g. mintty) have problem displaying default
|
|
||||||
background color and make some text unable to read. In that case, try
|
|
||||||
`--black` option. And if it solves your problem, I recommend including it
|
|
||||||
in `FZF_DEFAULT_OPTS` for further convenience.
|
|
||||||
4. If you still have problem, try `--no-256` option or even `--no-color`.
|
|
||||||
|
|
||||||
#### Respecting `.gitignore`, `.hgignore`, and `svn:ignore`
|
#### Respecting `.gitignore`, `.hgignore`, and `svn:ignore`
|
||||||
|
|
||||||
[ag](https://github.com/ggreer/the_silver_searcher) or
|
[ag](https://github.com/ggreer/the_silver_searcher) or
|
||||||
|
|||||||
20
install
20
install
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
set -u
|
set -u
|
||||||
|
|
||||||
version=0.16.2
|
version=0.16.4
|
||||||
auto_completion=
|
auto_completion=
|
||||||
key_bindings=
|
key_bindings=
|
||||||
update_config=2
|
update_config=2
|
||||||
@@ -88,17 +88,6 @@ check_binary() {
|
|||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
symlink() {
|
|
||||||
echo " - Creating symlink: bin/$1 -> bin/fzf"
|
|
||||||
(cd "$fzf_base"/bin &&
|
|
||||||
rm -f fzf &&
|
|
||||||
ln -sf $1 fzf)
|
|
||||||
if [ $? -ne 0 ]; then
|
|
||||||
binary_error="Failed to create symlink"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
link_fzf_in_path() {
|
link_fzf_in_path() {
|
||||||
if which_fzf="$(command -v fzf)"; then
|
if which_fzf="$(command -v fzf)"; then
|
||||||
echo " - Found in \$PATH"
|
echo " - Found in \$PATH"
|
||||||
@@ -124,9 +113,6 @@ download() {
|
|||||||
echo " - Already exists"
|
echo " - Already exists"
|
||||||
check_binary && return
|
check_binary && return
|
||||||
fi
|
fi
|
||||||
if [ -x "$fzf_base"/bin/$1 ]; then
|
|
||||||
symlink $1 && check_binary && return
|
|
||||||
fi
|
|
||||||
link_fzf_in_path && return
|
link_fzf_in_path && return
|
||||||
fi
|
fi
|
||||||
mkdir -p "$fzf_base"/bin && cd "$fzf_base"/bin
|
mkdir -p "$fzf_base"/bin && cd "$fzf_base"/bin
|
||||||
@@ -147,12 +133,12 @@ download() {
|
|||||||
fi
|
fi
|
||||||
set +o pipefail
|
set +o pipefail
|
||||||
|
|
||||||
if [ ! -f $1 ]; then
|
if [ ! -f fzf ]; then
|
||||||
binary_error="Failed to download ${1}"
|
binary_error="Failed to download ${1}"
|
||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
|
|
||||||
chmod +x $1 && symlink $1 && check_binary
|
chmod +x fzf && check_binary
|
||||||
}
|
}
|
||||||
|
|
||||||
# Try to download binary executable
|
# Try to download binary executable
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
THE SOFTWARE.
|
THE SOFTWARE.
|
||||||
..
|
..
|
||||||
.TH fzf-tmux 1 "Jan 2017" "fzf 0.16.2" "fzf-tmux - open fzf in tmux split pane"
|
.TH fzf-tmux 1 "Feb 2017" "fzf 0.16.4" "fzf-tmux - open fzf in tmux split pane"
|
||||||
|
|
||||||
.SH NAME
|
.SH NAME
|
||||||
fzf-tmux - open fzf in tmux split pane
|
fzf-tmux - open fzf in tmux split pane
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
THE SOFTWARE.
|
THE SOFTWARE.
|
||||||
..
|
..
|
||||||
.TH fzf 1 "Jan 2017" "fzf 0.16.2" "fzf - a command-line fuzzy finder"
|
.TH fzf 1 "Feb 2017" "fzf 0.16.4" "fzf - a command-line fuzzy finder"
|
||||||
|
|
||||||
.SH NAME
|
.SH NAME
|
||||||
fzf - a command-line fuzzy finder
|
fzf - a command-line fuzzy finder
|
||||||
@@ -156,6 +156,9 @@ Ignored when \fB--height\fR is not specified.
|
|||||||
.B "--reverse"
|
.B "--reverse"
|
||||||
Reverse orientation
|
Reverse orientation
|
||||||
.TP
|
.TP
|
||||||
|
.B "--border"
|
||||||
|
Draw border above and below the finder
|
||||||
|
.TP
|
||||||
.BI "--margin=" MARGIN
|
.BI "--margin=" MARGIN
|
||||||
Comma-separated expression for margins around the finder.
|
Comma-separated expression for margins around the finder.
|
||||||
.br
|
.br
|
||||||
@@ -262,13 +265,21 @@ Execute the given command for the current line and display the result on the
|
|||||||
preview window. \fB{}\fR in the command is the placeholder that is replaced to
|
preview window. \fB{}\fR in the command is the placeholder that is replaced to
|
||||||
the single-quoted string of the current line. To transform the replacement
|
the single-quoted string of the current line. To transform the replacement
|
||||||
string, specify field index expressions between the braces (See \fBFIELD INDEX
|
string, specify field index expressions between the braces (See \fBFIELD INDEX
|
||||||
EXPRESSION\fR for the details). Also, \fB{q}\fR is replaced to the current
|
EXPRESSION\fR for the details).
|
||||||
query string.
|
|
||||||
|
|
||||||
.RS
|
.RS
|
||||||
e.g. \fBfzf --preview="head -$LINES {}"\fR
|
e.g. \fBfzf --preview="head -$LINES {}"\fR
|
||||||
\fBls -l | fzf --preview="echo user={3} when={-4..-2}; cat {-1}" --header-lines=1\fR
|
\fBls -l | fzf --preview="echo user={3} when={-4..-2}; cat {-1}" --header-lines=1\fR
|
||||||
|
|
||||||
|
A placeholder expression starting with \fB+\fR flag will be replaced to the
|
||||||
|
space-separated list of the selected lines (or the current line if no selection
|
||||||
|
was made) individually quoted.
|
||||||
|
|
||||||
|
e.g. \fBfzf --multi --preview="head -10 {+}"\fR
|
||||||
|
\fBgit log --oneline | fzf --multi --preview 'git show {+1}'\fR
|
||||||
|
|
||||||
|
Also, \fB{q}\fR is replaced to the current query string.
|
||||||
|
|
||||||
Note that you can escape a placeholder pattern by prepending a backslash.
|
Note that you can escape a placeholder pattern by prepending a backslash.
|
||||||
.RE
|
.RE
|
||||||
.TP
|
.TP
|
||||||
@@ -323,10 +334,10 @@ e.g. \fBfzf --expect=ctrl-v,ctrl-t,alt-s,f1,f2,~,@\fR
|
|||||||
.RE
|
.RE
|
||||||
.TP
|
.TP
|
||||||
.B "--read0"
|
.B "--read0"
|
||||||
Read input delimited by ASCII NUL character instead of newline character
|
Read input delimited by ASCII NUL characters instead of newline characters
|
||||||
.TP
|
.TP
|
||||||
.B "--print0"
|
.B "--print0"
|
||||||
Print output delimited by ASCII NUL character instead of newline character
|
Print output delimited by ASCII NUL characters instead of newline characters
|
||||||
.TP
|
.TP
|
||||||
.B "--sync"
|
.B "--sync"
|
||||||
Synchronous search for multi-staged filtering. If specified, fzf will launch
|
Synchronous search for multi-staged filtering. If specified, fzf will launch
|
||||||
@@ -418,6 +429,7 @@ e.g. \fBfzf --bind=ctrl-j:accept,ctrl-k:kill-line\fR
|
|||||||
|
|
||||||
.B AVAILABLE KEYS: (SYNONYMS)
|
.B AVAILABLE KEYS: (SYNONYMS)
|
||||||
\fIctrl-[a-z]\fR
|
\fIctrl-[a-z]\fR
|
||||||
|
\fIctrl-space\fR
|
||||||
\fIalt-[a-z]\fR
|
\fIalt-[a-z]\fR
|
||||||
\fIalt-[0-9]\fR
|
\fIalt-[0-9]\fR
|
||||||
\fIf[1-12]\fR
|
\fIf[1-12]\fR
|
||||||
@@ -461,7 +473,8 @@ e.g. \fBfzf --bind=ctrl-j:accept,ctrl-k:kill-line\fR
|
|||||||
\fBdown\fR \fIctrl-j ctrl-n down\fR
|
\fBdown\fR \fIctrl-j ctrl-n down\fR
|
||||||
\fBend-of-line\fR \fIctrl-e end\fR
|
\fBend-of-line\fR \fIctrl-e end\fR
|
||||||
\fBexecute(...)\fR (see below for the details)
|
\fBexecute(...)\fR (see below for the details)
|
||||||
\fBexecute-multi(...)\fR (see below for the details)
|
\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-char\fR \fIctrl-f right\fR
|
||||||
\fBforward-word\fR \fIalt-f shift-right\fR
|
\fBforward-word\fR \fIalt-f shift-right\fR
|
||||||
\fBignore\fR
|
\fBignore\fR
|
||||||
@@ -487,7 +500,7 @@ e.g. \fBfzf --bind=ctrl-j:accept,ctrl-k:kill-line\fR
|
|||||||
\fBtoggle-in\fR (\fB--reverse\fR ? \fBtoggle+up\fR : \fBtoggle+down\fR)
|
\fBtoggle-in\fR (\fB--reverse\fR ? \fBtoggle+up\fR : \fBtoggle+down\fR)
|
||||||
\fBtoggle-out\fR (\fB--reverse\fR ? \fBtoggle+down\fR : \fBtoggle+up\fR)
|
\fBtoggle-out\fR (\fB--reverse\fR ? \fBtoggle+down\fR : \fBtoggle+up\fR)
|
||||||
\fBtoggle-preview\fR
|
\fBtoggle-preview\fR
|
||||||
\fBtoggle-sort\fR (equivalent to \fB--toggle-sort\fR)
|
\fBtoggle-sort\fR
|
||||||
\fBtoggle+up\fR \fIbtab (shift-tab)\fR
|
\fBtoggle+up\fR \fIbtab (shift-tab)\fR
|
||||||
\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
|
||||||
@@ -530,10 +543,12 @@ the closing character. The catch is that it should be the last one in the
|
|||||||
comma-separated list of key-action pairs.
|
comma-separated list of key-action pairs.
|
||||||
.RE
|
.RE
|
||||||
|
|
||||||
\fBexecute-multi(...)\fR is an alternative action that executes the command
|
fzf switches to the alternate screen when executing a command. However, if the
|
||||||
with the selected entries when multi-select is enabled (\fB--multi\fR). With
|
command is expected to complete quickly, and you are not interested in its
|
||||||
this action, \fB{}\fR is replaced with the quoted strings of the selected
|
output, you might want to use \fBexecute-silent\fR instead, which silently
|
||||||
entries separated by spaces.
|
executes the command without the switching. Note that fzf will not be
|
||||||
|
responsible until the command is complete. For asynchronous execution, start
|
||||||
|
your command as a background process (i.e. appending \fB&\fR).
|
||||||
|
|
||||||
.SH AUTHOR
|
.SH AUTHOR
|
||||||
Junegunn Choi (\fIjunegunn.c@gmail.com\fR)
|
Junegunn Choi (\fIjunegunn.c@gmail.com\fR)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
" Copyright (c) 2016 Junegunn Choi
|
" Copyright (c) 2017 Junegunn Choi
|
||||||
"
|
"
|
||||||
" MIT License
|
" MIT License
|
||||||
"
|
"
|
||||||
@@ -28,10 +28,12 @@ let g:loaded_fzf = 1
|
|||||||
|
|
||||||
let s:default_layout = { 'down': '~40%' }
|
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 = expand('<sfile>:h:h').'/bin/fzf'
|
let s:is_win = has('win32') || has('win64')
|
||||||
let s:install = expand('<sfile>:h:h').'/install'
|
let s:base_dir = expand('<sfile>:h:h')
|
||||||
|
let s:fzf_go = s:base_dir.'/bin/fzf'
|
||||||
|
let s:fzf_tmux = s:base_dir.'/bin/fzf-tmux'
|
||||||
|
let s:install = s:base_dir.'/install'
|
||||||
let s:installed = 0
|
let s:installed = 0
|
||||||
let s:fzf_tmux = expand('<sfile>:h:h').'/bin/fzf-tmux'
|
|
||||||
|
|
||||||
let s:cpo_save = &cpo
|
let s:cpo_save = &cpo
|
||||||
set cpo&vim
|
set cpo&vim
|
||||||
@@ -42,6 +44,11 @@ function! s:fzf_exec()
|
|||||||
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
|
||||||
|
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) &&
|
elseif !s:installed && executable(s:install) &&
|
||||||
\ input('fzf executable not found. Download binary? (y/n) ') =~? '^y'
|
\ input('fzf executable not found. Download binary? (y/n) ') =~? '^y'
|
||||||
redraw
|
redraw
|
||||||
@@ -201,6 +208,10 @@ function! fzf#wrap(...)
|
|||||||
endfor
|
endfor
|
||||||
let [name, opts, bang] = args
|
let [name, opts, bang] = args
|
||||||
|
|
||||||
|
if len(name)
|
||||||
|
let opts.name = name
|
||||||
|
end
|
||||||
|
|
||||||
" Layout: g:fzf_layout (and deprecated g:fzf_height)
|
" Layout: g:fzf_layout (and deprecated g:fzf_height)
|
||||||
if bang
|
if bang
|
||||||
for key in s:layout_keys
|
for key in s:layout_keys
|
||||||
@@ -242,7 +253,7 @@ function! fzf#wrap(...)
|
|||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
function! fzf#shellescape(path)
|
function! fzf#shellescape(path)
|
||||||
if has('win32') || has('win64')
|
if s:is_win
|
||||||
let shellslash = &shellslash
|
let shellslash = &shellslash
|
||||||
try
|
try
|
||||||
set noshellslash
|
set noshellslash
|
||||||
@@ -259,7 +270,7 @@ try
|
|||||||
let oshell = &shell
|
let oshell = &shell
|
||||||
let useshellslash = &shellslash
|
let useshellslash = &shellslash
|
||||||
|
|
||||||
if has('win32') || has('win64')
|
if s:is_win
|
||||||
set shell=cmd.exe
|
set shell=cmd.exe
|
||||||
set noshellslash
|
set noshellslash
|
||||||
else
|
else
|
||||||
@@ -301,23 +312,28 @@ try
|
|||||||
let prefix = ''
|
let prefix = ''
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
let prefer_tmux = get(g:, 'fzf_prefer_tmux', 0)
|
||||||
let use_height = has_key(dict, 'down') &&
|
let use_height = has_key(dict, 'down') &&
|
||||||
\ !(has('nvim') || has('win32') || has('win64') || s:present(dict, 'up', 'left', 'right')) &&
|
\ !(has('nvim') || s:is_win || s:present(dict, 'up', 'left', 'right')) &&
|
||||||
\ executable('tput') && filereadable('/dev/tty')
|
\ executable('tput') && filereadable('/dev/tty')
|
||||||
let tmux = !use_height && (!has('nvim') || get(g:, 'fzf_prefer_tmux', 0)) && s:tmux_enabled() && s:splittable(dict)
|
let use_term = has('nvim')
|
||||||
let term = has('nvim') && !tmux
|
let use_tmux = (!use_height && !use_term || prefer_tmux) && s:tmux_enabled() && s:splittable(dict)
|
||||||
|
if prefer_tmux && use_tmux
|
||||||
|
let use_height = 0
|
||||||
|
let use_term = 0
|
||||||
|
endif
|
||||||
if use_height
|
if use_height
|
||||||
let optstr .= ' --height='.s:calc_size(&lines, dict.down, dict)
|
let optstr .= ' --height='.s:calc_size(&lines, dict.down, dict)
|
||||||
elseif term
|
elseif use_term
|
||||||
let optstr .= ' --no-height'
|
let optstr .= ' --no-height'
|
||||||
endif
|
endif
|
||||||
let command = prefix.(tmux ? s:fzf_tmux(dict) : fzf_exec).' '.optstr.' > '.temps.result
|
let command = prefix.(use_tmux ? s:fzf_tmux(dict) : fzf_exec).' '.optstr.' > '.temps.result
|
||||||
|
|
||||||
if term
|
if use_term
|
||||||
return s:execute_term(dict, command, temps)
|
return s:execute_term(dict, command, temps)
|
||||||
endif
|
endif
|
||||||
|
|
||||||
let lines = tmux ? s:execute_tmux(dict, command, temps)
|
let lines = use_tmux ? s:execute_tmux(dict, command, temps)
|
||||||
\ : s:execute(dict, command, use_height, temps)
|
\ : s:execute(dict, command, use_height, temps)
|
||||||
call s:callback(dict, lines)
|
call s:callback(dict, lines)
|
||||||
return lines
|
return lines
|
||||||
@@ -396,7 +412,7 @@ function! s:xterm_launcher()
|
|||||||
\ &columns, &lines/2, getwinposx(), getwinposy())
|
\ &columns, &lines/2, getwinposx(), getwinposy())
|
||||||
endfunction
|
endfunction
|
||||||
unlet! s:launcher
|
unlet! s:launcher
|
||||||
if has('win32') || has('win64')
|
if s:is_win
|
||||||
let s:launcher = '%s'
|
let s:launcher = '%s'
|
||||||
else
|
else
|
||||||
let s:launcher = function('s:xterm_launcher')
|
let s:launcher = function('s:xterm_launcher')
|
||||||
@@ -516,6 +532,7 @@ function! s:execute_term(dict, command, temps) abort
|
|||||||
let winrest = winrestcmd()
|
let winrest = winrestcmd()
|
||||||
let pbuf = bufnr('')
|
let pbuf = bufnr('')
|
||||||
let [ppos, winopts] = s:split(a:dict)
|
let [ppos, winopts] = s:split(a:dict)
|
||||||
|
let b:fzf = a:dict
|
||||||
let fzf = { 'buf': bufnr(''), 'pbuf': pbuf, 'ppos': ppos, 'dict': a:dict, 'temps': a:temps,
|
let fzf = { 'buf': bufnr(''), 'pbuf': pbuf, 'ppos': ppos, 'dict': a:dict, 'temps': a:temps,
|
||||||
\ 'winopts': winopts, 'winrest': winrest, 'lines': &lines,
|
\ 'winopts': winopts, 'winrest': winrest, 'lines': &lines,
|
||||||
\ 'columns': &columns, 'command': a:command }
|
\ 'columns': &columns, 'command': a:command }
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ __fzf_history__() (
|
|||||||
shopt -u nocaseglob nocasematch
|
shopt -u nocaseglob nocasematch
|
||||||
line=$(
|
line=$(
|
||||||
HISTTIMEFORMAT= history |
|
HISTTIMEFORMAT= history |
|
||||||
FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} $FZF_DEFAULT_OPTS +s --tac -n2..,.. --tiebreak=index --toggle-sort=ctrl-r $FZF_CTRL_R_OPTS +m" $(__fzfcmd) |
|
FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} $FZF_DEFAULT_OPTS +s --tac -n2..,.. --tiebreak=index --bind=ctrl-r:toggle-sort $FZF_CTRL_R_OPTS +m" $(__fzfcmd) |
|
||||||
command grep '^ *[0-9]') &&
|
command grep '^ *[0-9]') &&
|
||||||
if [[ $- =~ H ]]; then
|
if [[ $- =~ H ]]; then
|
||||||
sed 's/^ *\([0-9]*\)\** .*/!\1/' <<< "$line"
|
sed 's/^ *\([0-9]*\)\** .*/!\1/' <<< "$line"
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ function fzf_key_bindings
|
|||||||
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%
|
set -q FZF_TMUX_HEIGHT; or set FZF_TMUX_HEIGHT 40%
|
||||||
begin
|
begin
|
||||||
set -lx FZF_DEFAULT_OPTS "--height $FZF_TMUX_HEIGHT $FZF_DEFAULT_OPTS +s --tiebreak=index $FZF_CTRL_R_OPTS +m"
|
set -lx FZF_DEFAULT_OPTS "--height $FZF_TMUX_HEIGHT $FZF_DEFAULT_OPTS +s --tiebreak=index --bind=ctrl-r:toggle-sort $FZF_CTRL_R_OPTS +m"
|
||||||
history | eval (__fzfcmd) -q '(commandline)' | read -l result
|
history | eval (__fzfcmd) -q '(commandline)' | read -l result
|
||||||
and commandline -- $result
|
and commandline -- $result
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ fzf-history-widget() {
|
|||||||
local selected num
|
local selected num
|
||||||
setopt localoptions noglobsubst pipefail 2> /dev/null
|
setopt localoptions noglobsubst pipefail 2> /dev/null
|
||||||
selected=( $(fc -l 1 |
|
selected=( $(fc -l 1 |
|
||||||
FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} $FZF_DEFAULT_OPTS +s --tac -n2..,.. --tiebreak=index --toggle-sort=ctrl-r $FZF_CTRL_R_OPTS --query=${(q)LBUFFER} +m" $(__fzfcmd)) )
|
FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} $FZF_DEFAULT_OPTS +s --tac -n2..,.. --tiebreak=index --bind=ctrl-r:toggle-sort $FZF_CTRL_R_OPTS --query=${(q)LBUFFER} +m" $(__fzfcmd)) )
|
||||||
local ret=$?
|
local ret=$?
|
||||||
if [ -n "$selected" ]; then
|
if [ -n "$selected" ]; then
|
||||||
num=$selected[1]
|
num=$selected[1]
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2016 Junegunn Choi
|
Copyright (c) 2017 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
|
||||||
|
|||||||
26
src/Makefile
26
src/Makefile
@@ -52,23 +52,23 @@ all: fzf/$(BINARY)
|
|||||||
|
|
||||||
ifeq ($(GOOS),windows)
|
ifeq ($(GOOS),windows)
|
||||||
release: fzf/$(BINARY32) fzf/$(BINARY64)
|
release: fzf/$(BINARY32) fzf/$(BINARY64)
|
||||||
cd fzf && cp $(BINARY32) $(RELEASE32).exe && zip $(RELEASE32).zip $(RELEASE32).exe
|
cd fzf && cp -f $(BINARY32) fzf.exe && zip $(RELEASE32).zip fzf.exe
|
||||||
cd fzf && cp $(BINARY64) $(RELEASE64).exe && zip $(RELEASE64).zip $(RELEASE64).exe
|
cd fzf && cp -f $(BINARY64) fzf.exe && zip $(RELEASE64).zip fzf.exe
|
||||||
cd fzf && rm -f $(RELEASE32).exe $(RELEASE64).exe
|
cd fzf && rm -f fzf.exe
|
||||||
else ifeq ($(GOOS),linux)
|
else ifeq ($(GOOS),linux)
|
||||||
release: fzf/$(BINARY32) fzf/$(BINARY64) fzf/$(BINARYARM5) fzf/$(BINARYARM6) fzf/$(BINARYARM7) fzf/$(BINARYARM8)
|
release: fzf/$(BINARY32) fzf/$(BINARY64) fzf/$(BINARYARM5) fzf/$(BINARYARM6) fzf/$(BINARYARM7) fzf/$(BINARYARM8)
|
||||||
cd fzf && cp $(BINARY32) $(RELEASE32) && tar -czf $(RELEASE32).tgz $(RELEASE32)
|
cd fzf && cp -f $(BINARY32) fzf && tar -czf $(RELEASE32).tgz fzf
|
||||||
cd fzf && cp $(BINARY64) $(RELEASE64) && tar -czf $(RELEASE64).tgz $(RELEASE64)
|
cd fzf && cp -f $(BINARY64) fzf && tar -czf $(RELEASE64).tgz fzf
|
||||||
cd fzf && cp $(BINARYARM5) $(RELEASEARM5) && tar -czf $(RELEASEARM5).tgz $(RELEASEARM5)
|
cd fzf && cp -f $(BINARYARM5) fzf && tar -czf $(RELEASEARM5).tgz fzf
|
||||||
cd fzf && cp $(BINARYARM6) $(RELEASEARM6) && tar -czf $(RELEASEARM6).tgz $(RELEASEARM6)
|
cd fzf && cp -f $(BINARYARM6) fzf && tar -czf $(RELEASEARM6).tgz fzf
|
||||||
cd fzf && cp $(BINARYARM7) $(RELEASEARM7) && tar -czf $(RELEASEARM7).tgz $(RELEASEARM7)
|
cd fzf && cp -f $(BINARYARM7) fzf && tar -czf $(RELEASEARM7).tgz fzf
|
||||||
cd fzf && cp $(BINARYARM8) $(RELEASEARM8) && tar -czf $(RELEASEARM8).tgz $(RELEASEARM8)
|
cd fzf && cp -f $(BINARYARM8) fzf && tar -czf $(RELEASEARM8).tgz fzf
|
||||||
cd fzf && rm -f $(RELEASE32) $(RELEASE64) $(RELEASEARM5) $(RELEASEARM6) $(RELEASEARM7) $(RELEASEARM8)
|
cd fzf && rm -f fzf
|
||||||
else
|
else
|
||||||
release: fzf/$(BINARY32) fzf/$(BINARY64)
|
release: fzf/$(BINARY32) fzf/$(BINARY64)
|
||||||
cd fzf && cp $(BINARY32) $(RELEASE32) && tar -czf $(RELEASE32).tgz $(RELEASE32)
|
cd fzf && cp -f $(BINARY32) fzf && tar -czf $(RELEASE32).tgz fzf
|
||||||
cd fzf && cp $(BINARY64) $(RELEASE64) && tar -czf $(RELEASE64).tgz $(RELEASE64)
|
cd fzf && cp -f $(BINARY64) fzf && tar -czf $(RELEASE64).tgz fzf
|
||||||
cd fzf && rm -f $(RELEASE32) $(RELEASE64)
|
cd fzf && rm -f fzf
|
||||||
endif
|
endif
|
||||||
|
|
||||||
release-all: clean test
|
release-all: clean test
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ func init() {
|
|||||||
*/
|
*/
|
||||||
// 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.|[\x08\x0e\x0f]")
|
ansiRegex = regexp.MustCompile("\x1b[\\[()][0-9;]*[a-zA-Z@]|\x1b.|[\x0e\x0f]|.\x08")
|
||||||
}
|
}
|
||||||
|
|
||||||
func extractColor(str string, state *ansiState, proc func(string, *ansiState) bool) (string, *[]ansiOffset, *ansiState) {
|
func extractColor(str string, state *ansiState, proc func(string, *ansiState) bool) (string, *[]ansiOffset, *ansiState) {
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import (
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
// Current version
|
// Current version
|
||||||
version = "0.16.2"
|
version = "0.16.4"
|
||||||
|
|
||||||
// Core
|
// Core
|
||||||
coordinatorDelayMax time.Duration = 100 * time.Millisecond
|
coordinatorDelayMax time.Duration = 100 * time.Millisecond
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ Package fzf implements fzf, a command-line fuzzy finder.
|
|||||||
|
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2016 Junegunn Choi
|
Copyright (c) 2017 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
|
||||||
|
|||||||
2
src/deps
2
src/deps
@@ -13,6 +13,6 @@ reset() (
|
|||||||
)
|
)
|
||||||
|
|
||||||
reset github.com/junegunn/go-isatty 66b8e73f3f5cda9f96b69efd03dd3d7fc4a5cdb8
|
reset github.com/junegunn/go-isatty 66b8e73f3f5cda9f96b69efd03dd3d7fc4a5cdb8
|
||||||
reset github.com/junegunn/go-runewidth 63c378b851290989b19ca955468386485f118c65
|
reset github.com/junegunn/go-runewidth 14207d285c6c197daabb5c9793d63e7af9ab2d50
|
||||||
reset github.com/junegunn/go-shellwords 33bd8f1ebe16d6e5eb688cc885749a63059e9167
|
reset github.com/junegunn/go-shellwords 33bd8f1ebe16d6e5eb688cc885749a63059e9167
|
||||||
reset golang.org/x/crypto abc5fa7ad02123a41f02bf1391c9760f7586e608
|
reset golang.org/x/crypto abc5fa7ad02123a41f02bf1391c9760f7586e608
|
||||||
|
|||||||
@@ -54,6 +54,7 @@ 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)
|
||||||
--reverse Reverse orientation
|
--reverse Reverse orientation
|
||||||
|
--border Draw border above and below the finder
|
||||||
--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)
|
||||||
--inline-info Display finder info inline with the query
|
--inline-info Display finder info inline with the query
|
||||||
--prompt=STR Input prompt (default: '> ')
|
--prompt=STR Input prompt (default: '> ')
|
||||||
@@ -82,6 +83,8 @@ const usage = `usage: fzf [options]
|
|||||||
-f, --filter=STR Filter mode. Do not start interactive finder.
|
-f, --filter=STR Filter mode. Do not start interactive finder.
|
||||||
--print-query Print query as the first line
|
--print-query Print query as the first line
|
||||||
--expect=KEYS Comma-separated list of keys to complete fzf
|
--expect=KEYS Comma-separated list of keys to complete fzf
|
||||||
|
--read0 Read input delimited by ASCII NUL characters
|
||||||
|
--print0 Print output delimited by ASCII NUL characters
|
||||||
--sync Synchronous search for multi-staged filtering
|
--sync Synchronous search for multi-staged filtering
|
||||||
|
|
||||||
Environment variables
|
Environment variables
|
||||||
@@ -181,6 +184,7 @@ type Options struct {
|
|||||||
Header []string
|
Header []string
|
||||||
HeaderLines int
|
HeaderLines int
|
||||||
Margin [4]sizeSpec
|
Margin [4]sizeSpec
|
||||||
|
Bordered bool
|
||||||
Tabstop int
|
Tabstop int
|
||||||
Version bool
|
Version bool
|
||||||
}
|
}
|
||||||
@@ -391,6 +395,8 @@ func parseKeyChords(str string, message string) map[int]string {
|
|||||||
chord = tui.AltZ + int(' ')
|
chord = tui.AltZ + int(' ')
|
||||||
case "bspace", "bs":
|
case "bspace", "bs":
|
||||||
chord = tui.BSpace
|
chord = tui.BSpace
|
||||||
|
case "ctrl-space":
|
||||||
|
chord = tui.CtrlSpace
|
||||||
case "alt-enter", "alt-return":
|
case "alt-enter", "alt-return":
|
||||||
chord = tui.AltEnter
|
chord = tui.AltEnter
|
||||||
case "alt-space":
|
case "alt-space":
|
||||||
@@ -579,18 +585,25 @@ const (
|
|||||||
escapedPlus = 2
|
escapedPlus = 2
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
// Backreferences are not supported.
|
||||||
|
// "~!@#$%^&*;/|".each_char.map { |c| Regexp.escape(c) }.map { |c| "#{c}[^#{c}]*#{c}" }.join('|')
|
||||||
|
executeRegexp = regexp.MustCompile(
|
||||||
|
"(?si):(execute(?:-multi|-silent)?):.+|:(execute(?:-multi|-silent)?)(\\([^)]*\\)|\\[[^\\]]*\\]|~[^~]*~|![^!]*!|@[^@]*@|\\#[^\\#]*\\#|\\$[^\\$]*\\$|%[^%]*%|\\^[^\\^]*\\^|&[^&]*&|\\*[^\\*]*\\*|;[^;]*;|/[^/]*/|\\|[^\\|]*\\|)")
|
||||||
|
}
|
||||||
|
|
||||||
func parseKeymap(keymap map[int][]action, str string) {
|
func parseKeymap(keymap map[int][]action, str string) {
|
||||||
if executeRegexp == nil {
|
|
||||||
// Backreferences are not supported.
|
|
||||||
// "~!@#$%^&*;/|".each_char.map { |c| Regexp.escape(c) }.map { |c| "#{c}[^#{c}]*#{c}" }.join('|')
|
|
||||||
executeRegexp = regexp.MustCompile(
|
|
||||||
"(?si):execute(-multi)?:.+|:execute(-multi)?(\\([^)]*\\)|\\[[^\\]]*\\]|~[^~]*~|![^!]*!|@[^@]*@|\\#[^\\#]*\\#|\\$[^\\$]*\\$|%[^%]*%|\\^[^\\^]*\\^|&[^&]*&|\\*[^\\*]*\\*|;[^;]*;|/[^/]*/|\\|[^\\|]*\\|)")
|
|
||||||
}
|
|
||||||
masked := executeRegexp.ReplaceAllStringFunc(str, func(src string) string {
|
masked := executeRegexp.ReplaceAllStringFunc(str, func(src string) string {
|
||||||
if src[len(":execute")] == '-' {
|
prefix := ":execute"
|
||||||
return ":execute-multi(" + strings.Repeat(" ", len(src)-len(":execute-multi()")) + ")"
|
if src[len(prefix)] == '-' {
|
||||||
|
c := src[len(prefix)+1]
|
||||||
|
if c == 's' || c == 'S' {
|
||||||
|
prefix += "-silent"
|
||||||
|
} else {
|
||||||
|
prefix += "-multi"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return ":execute(" + strings.Repeat(" ", len(src)-len(":execute()")) + ")"
|
return prefix + "(" + strings.Repeat(" ", len(src)-len(prefix)-2) + ")"
|
||||||
})
|
})
|
||||||
masked = strings.Replace(masked, "::", string([]rune{escapedColon, ':'}), -1)
|
masked = strings.Replace(masked, "::", string([]rune{escapedColon, ':'}), -1)
|
||||||
masked = strings.Replace(masked, ",:", string([]rune{escapedComma, ':'}), -1)
|
masked = strings.Replace(masked, ",:", string([]rune{escapedComma, ':'}), -1)
|
||||||
@@ -726,9 +739,12 @@ func parseKeymap(keymap map[int][]action, str string) {
|
|||||||
errorExit("unknown action: " + spec)
|
errorExit("unknown action: " + spec)
|
||||||
} else {
|
} else {
|
||||||
var offset int
|
var offset int
|
||||||
if t == actExecuteMulti {
|
switch t {
|
||||||
|
case actExecuteSilent:
|
||||||
|
offset = len("execute-silent")
|
||||||
|
case actExecuteMulti:
|
||||||
offset = len("execute-multi")
|
offset = len("execute-multi")
|
||||||
} else {
|
default:
|
||||||
offset = len("execute")
|
offset = len("execute")
|
||||||
}
|
}
|
||||||
if spec[offset] == ':' {
|
if spec[offset] == ':' {
|
||||||
@@ -750,23 +766,21 @@ func parseKeymap(keymap map[int][]action, str string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func isExecuteAction(str string) actionType {
|
func isExecuteAction(str string) actionType {
|
||||||
t := actExecute
|
matches := executeRegexp.FindAllStringSubmatch(":"+str, -1)
|
||||||
if !strings.HasPrefix(str, "execute") || len(str) < len("execute(") {
|
if matches == nil || len(matches) != 1 {
|
||||||
return actIgnore
|
return actIgnore
|
||||||
}
|
}
|
||||||
|
prefix := matches[0][1]
|
||||||
b := str[len("execute")]
|
if len(prefix) == 0 {
|
||||||
if strings.HasPrefix(str, "execute-multi") {
|
prefix = matches[0][2]
|
||||||
if len(str) < len("execute-multi(") {
|
|
||||||
return actIgnore
|
|
||||||
}
|
|
||||||
t = actExecuteMulti
|
|
||||||
b = str[len("execute-multi")]
|
|
||||||
}
|
}
|
||||||
e := str[len(str)-1]
|
switch prefix {
|
||||||
if b == ':' || b == '(' && e == ')' || b == '[' && e == ']' ||
|
case "execute":
|
||||||
b == e && strings.ContainsAny(string(b), "~!@#$%^&*;/|") {
|
return actExecute
|
||||||
return t
|
case "execute-silent":
|
||||||
|
return actExecuteSilent
|
||||||
|
case "execute-multi":
|
||||||
|
return actExecuteMulti
|
||||||
}
|
}
|
||||||
return actIgnore
|
return actIgnore
|
||||||
}
|
}
|
||||||
@@ -1074,6 +1088,10 @@ func parseOptions(opts *Options, allArgs []string) {
|
|||||||
opts.Height = sizeSpec{}
|
opts.Height = sizeSpec{}
|
||||||
case "--no-margin":
|
case "--no-margin":
|
||||||
opts.Margin = defaultMargin()
|
opts.Margin = defaultMargin()
|
||||||
|
case "--no-border":
|
||||||
|
opts.Bordered = false
|
||||||
|
case "--border":
|
||||||
|
opts.Bordered = true
|
||||||
case "--margin":
|
case "--margin":
|
||||||
opts.Margin = parseMargin(
|
opts.Margin = parseMargin(
|
||||||
nextString(allArgs, &i, "margin required (TRBL / TB,RL / T,RL,B / T,R,B,L)"))
|
nextString(allArgs, &i, "margin required (TRBL / TB,RL / T,RL,B / T,R,B,L)"))
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ func BuildPattern(fuzzy bool, fuzzyAlgo algo.Algo, extended bool, caseMode Case,
|
|||||||
for idx, term := range termSet {
|
for idx, term := range termSet {
|
||||||
// If the query contains inverse search terms or OR operators,
|
// If the query contains inverse search terms or OR operators,
|
||||||
// we cannot cache the search scope
|
// we cannot cache the search scope
|
||||||
if !cacheable || idx > 0 || term.inv {
|
if !cacheable || idx > 0 || term.inv || !fuzzy && term.typ != termExact {
|
||||||
cacheable = false
|
cacheable = false
|
||||||
break Loop
|
break Loop
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -186,3 +186,21 @@ func TestCacheKey(t *testing.T) {
|
|||||||
test(true, "foo | bar !baz", "", false)
|
test(true, "foo | bar !baz", "", false)
|
||||||
test(true, "| | | foo", "foo", true)
|
test(true, "| | | foo", "foo", true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCacheable(t *testing.T) {
|
||||||
|
test := func(fuzzy bool, str string, cacheable bool) {
|
||||||
|
clearPatternCache()
|
||||||
|
pat := BuildPattern(fuzzy, algo.FuzzyMatchV2, true, CaseSmart, true, true, true, []Range{}, Delimiter{}, []rune(str))
|
||||||
|
if cacheable != pat.cacheable {
|
||||||
|
t.Errorf("Invalid Pattern.cacheable for \"%s\": %v (expected: %v)", str, pat.cacheable, cacheable)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
test(true, "foo bar", true)
|
||||||
|
test(true, "foo 'bar", true)
|
||||||
|
test(true, "foo !bar", false)
|
||||||
|
|
||||||
|
test(false, "foo bar", true)
|
||||||
|
test(false, "foo '", true)
|
||||||
|
test(false, "foo 'bar", false)
|
||||||
|
test(false, "foo !bar", false)
|
||||||
|
}
|
||||||
|
|||||||
@@ -37,12 +37,14 @@ func buildResult(item *Item, offsets []Offset, score int, trimLen int) *Result {
|
|||||||
result := Result{item: item, rank: rank{index: item.index}}
|
result := Result{item: item, rank: rank{index: item.index}}
|
||||||
numChars := item.text.Length()
|
numChars := item.text.Length()
|
||||||
minBegin := math.MaxUint16
|
minBegin := math.MaxUint16
|
||||||
|
minEnd := math.MaxUint16
|
||||||
maxEnd := 0
|
maxEnd := 0
|
||||||
validOffsetFound := false
|
validOffsetFound := false
|
||||||
for _, offset := range offsets {
|
for _, offset := range offsets {
|
||||||
b, e := int(offset[0]), int(offset[1])
|
b, e := int(offset[0]), int(offset[1])
|
||||||
if b < e {
|
if b < e {
|
||||||
minBegin = util.Min(b, minBegin)
|
minBegin = util.Min(b, minBegin)
|
||||||
|
minEnd = util.Min(e, minEnd)
|
||||||
maxEnd = util.Max(e, maxEnd)
|
maxEnd = util.Max(e, maxEnd)
|
||||||
validOffsetFound = true
|
validOffsetFound = true
|
||||||
}
|
}
|
||||||
@@ -68,7 +70,7 @@ func buildResult(item *Item, offsets []Offset, score int, trimLen int) *Result {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if criterion == byBegin {
|
if criterion == byBegin {
|
||||||
val = util.AsUint16(minBegin - whitePrefixLen)
|
val = util.AsUint16(minEnd - whitePrefixLen)
|
||||||
} else {
|
} else {
|
||||||
val = util.AsUint16(math.MaxUint16 - math.MaxUint16*(maxEnd-whitePrefixLen)/trimLen)
|
val = util.AsUint16(math.MaxUint16 - math.MaxUint16*(maxEnd-whitePrefixLen)/trimLen)
|
||||||
}
|
}
|
||||||
|
|||||||
203
src/terminal.go
203
src/terminal.go
@@ -24,7 +24,7 @@ import (
|
|||||||
var placeholder *regexp.Regexp
|
var placeholder *regexp.Regexp
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
placeholder = regexp.MustCompile("\\\\?(?:{[0-9,-.]*}|{q})")
|
placeholder = regexp.MustCompile("\\\\?(?:{\\+?[0-9,-.]*}|{q})")
|
||||||
}
|
}
|
||||||
|
|
||||||
type jumpMode int
|
type jumpMode int
|
||||||
@@ -83,8 +83,10 @@ type Terminal struct {
|
|||||||
tabstop int
|
tabstop int
|
||||||
margin [4]sizeSpec
|
margin [4]sizeSpec
|
||||||
strong tui.Attr
|
strong tui.Attr
|
||||||
|
bordered bool
|
||||||
|
border tui.Window
|
||||||
window tui.Window
|
window tui.Window
|
||||||
bwindow tui.Window
|
pborder tui.Window
|
||||||
pwindow tui.Window
|
pwindow tui.Window
|
||||||
count int
|
count int
|
||||||
progress int
|
progress int
|
||||||
@@ -204,7 +206,8 @@ const (
|
|||||||
actPreviousHistory
|
actPreviousHistory
|
||||||
actNextHistory
|
actNextHistory
|
||||||
actExecute
|
actExecute
|
||||||
actExecuteMulti
|
actExecuteSilent
|
||||||
|
actExecuteMulti // Deprecated
|
||||||
)
|
)
|
||||||
|
|
||||||
func toActions(types ...actionType) []action {
|
func toActions(types ...actionType) []action {
|
||||||
@@ -294,15 +297,22 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
|
|||||||
maxHeightFunc := func(termHeight int) int {
|
maxHeightFunc := func(termHeight int) int {
|
||||||
var maxHeight int
|
var maxHeight int
|
||||||
if opts.Height.percent {
|
if opts.Height.percent {
|
||||||
maxHeight = util.Min(termHeight,
|
maxHeight = util.Max(int(opts.Height.size*float64(termHeight)/100.0), opts.MinHeight)
|
||||||
util.Max(int(opts.Height.size*float64(termHeight)/100.0), opts.MinHeight))
|
|
||||||
} else {
|
} else {
|
||||||
maxHeight = util.Min(termHeight, int(opts.Height.size))
|
maxHeight = int(opts.Height.size)
|
||||||
|
}
|
||||||
|
|
||||||
|
effectiveMinHeight := minHeight
|
||||||
|
if previewBox != nil && (opts.Preview.position == posUp || opts.Preview.position == posDown) {
|
||||||
|
effectiveMinHeight *= 2
|
||||||
}
|
}
|
||||||
if opts.InlineInfo {
|
if opts.InlineInfo {
|
||||||
return util.Max(maxHeight, minHeight-1)
|
effectiveMinHeight -= 1
|
||||||
}
|
}
|
||||||
return util.Max(maxHeight, minHeight)
|
if opts.Bordered {
|
||||||
|
effectiveMinHeight += 2
|
||||||
|
}
|
||||||
|
return util.Min(termHeight, util.Max(maxHeight, effectiveMinHeight))
|
||||||
}
|
}
|
||||||
renderer = tui.NewLightRenderer(opts.Theme, opts.Black, opts.Mouse, opts.Tabstop, maxHeightFunc)
|
renderer = tui.NewLightRenderer(opts.Theme, opts.Black, opts.Mouse, opts.Tabstop, maxHeightFunc)
|
||||||
} else if tui.HasFullscreenRenderer() {
|
} else if tui.HasFullscreenRenderer() {
|
||||||
@@ -342,6 +352,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
|
|||||||
printQuery: opts.PrintQuery,
|
printQuery: opts.PrintQuery,
|
||||||
history: opts.History,
|
history: opts.History,
|
||||||
margin: opts.Margin,
|
margin: opts.Margin,
|
||||||
|
bordered: opts.Bordered,
|
||||||
strong: strongAttr,
|
strong: strongAttr,
|
||||||
cycle: opts.Cycle,
|
cycle: opts.Cycle,
|
||||||
header: header,
|
header: header,
|
||||||
@@ -436,9 +447,9 @@ func (t *Terminal) output() bool {
|
|||||||
}
|
}
|
||||||
found := len(t.selected) > 0
|
found := len(t.selected) > 0
|
||||||
if !found {
|
if !found {
|
||||||
cnt := t.merger.Length()
|
current := t.currentItem()
|
||||||
if cnt > 0 && cnt > t.cy {
|
if current != nil {
|
||||||
t.printer(t.current())
|
t.printer(current.AsString(t.ansi))
|
||||||
found = true
|
found = true
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -498,6 +509,9 @@ func (t *Terminal) resizeWindows() {
|
|||||||
} else {
|
} else {
|
||||||
marginInt[idx] = int(sizeSpec.size)
|
marginInt[idx] = int(sizeSpec.size)
|
||||||
}
|
}
|
||||||
|
if t.bordered && idx%2 == 0 {
|
||||||
|
marginInt[idx] += 1
|
||||||
|
}
|
||||||
}
|
}
|
||||||
adjust := func(idx1 int, idx2 int, max int, min int) {
|
adjust := func(idx1 int, idx2 int, max int, min int) {
|
||||||
if max >= min {
|
if max >= min {
|
||||||
@@ -523,19 +537,29 @@ func (t *Terminal) resizeWindows() {
|
|||||||
}
|
}
|
||||||
adjust(1, 3, screenWidth, minAreaWidth)
|
adjust(1, 3, screenWidth, minAreaWidth)
|
||||||
adjust(0, 2, screenHeight, minAreaHeight)
|
adjust(0, 2, screenHeight, minAreaHeight)
|
||||||
|
if t.border != nil {
|
||||||
|
t.border.Close()
|
||||||
|
}
|
||||||
if t.window != nil {
|
if t.window != nil {
|
||||||
t.window.Close()
|
t.window.Close()
|
||||||
}
|
}
|
||||||
if t.bwindow != nil {
|
if t.pborder != nil {
|
||||||
t.bwindow.Close()
|
t.pborder.Close()
|
||||||
t.pwindow.Close()
|
t.pwindow.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
||||||
|
t.border = t.tui.NewWindow(
|
||||||
|
marginInt[0]-1,
|
||||||
|
marginInt[3],
|
||||||
|
width,
|
||||||
|
height+2, tui.BorderHorizontal)
|
||||||
|
}
|
||||||
if previewVisible {
|
if previewVisible {
|
||||||
createPreviewWindow := func(y int, x int, w int, h int) {
|
createPreviewWindow := func(y int, x int, w int, h int) {
|
||||||
t.bwindow = t.tui.NewWindow(y, x, w, h, true)
|
t.pborder = t.tui.NewWindow(y, x, w, h, tui.BorderAround)
|
||||||
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
|
||||||
@@ -543,28 +567,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, false)
|
t.pwindow = t.tui.NewWindow(y+1, x+2, pwidth, h-2, tui.BorderNone)
|
||||||
}
|
}
|
||||||
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, false)
|
marginInt[0]+pheight, marginInt[3], width, height-pheight, tui.BorderNone)
|
||||||
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, false)
|
marginInt[0], marginInt[3], width, height-pheight, tui.BorderNone)
|
||||||
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, false)
|
marginInt[0], marginInt[3]+pwidth, width-pwidth, height, tui.BorderNone)
|
||||||
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, false)
|
marginInt[0], marginInt[3], width-pwidth, height, tui.BorderNone)
|
||||||
createPreviewWindow(marginInt[0], marginInt[3]+width-pwidth, pwidth, height)
|
createPreviewWindow(marginInt[0], marginInt[3]+width-pwidth, pwidth, height)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -572,7 +596,7 @@ func (t *Terminal) resizeWindows() {
|
|||||||
marginInt[0],
|
marginInt[0],
|
||||||
marginInt[3],
|
marginInt[3],
|
||||||
width,
|
width,
|
||||||
height, false)
|
height, tui.BorderNone)
|
||||||
}
|
}
|
||||||
if !t.tui.IsOptimized() && t.theme != nil && t.theme.HasBg() {
|
if !t.tui.IsOptimized() && t.theme != nil && t.theme.HasBg() {
|
||||||
for i := 0; i < t.window.Height(); i++ {
|
for i := 0; i < t.window.Height(); i++ {
|
||||||
@@ -847,6 +871,7 @@ func (t *Terminal) printHighlighted(result *Result, attr tui.Attr, col1 tui.Colo
|
|||||||
offsets[idx].offset[1] = util.Min32(offset.offset[1], int32(maxWidth))
|
offsets[idx].offset[1] = util.Min32(offset.offset[1], int32(maxWidth))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
displayWidth = t.displayWidthWithLimit(text, 0, displayWidth)
|
||||||
}
|
}
|
||||||
|
|
||||||
var index int32
|
var index int32
|
||||||
@@ -976,11 +1001,15 @@ func (t *Terminal) printAll() {
|
|||||||
|
|
||||||
func (t *Terminal) refresh() {
|
func (t *Terminal) refresh() {
|
||||||
if !t.suppress {
|
if !t.suppress {
|
||||||
if t.hasPreviewWindow() {
|
windows := make([]tui.Window, 0, 4)
|
||||||
t.tui.RefreshWindows([]tui.Window{t.bwindow, t.pwindow, t.window})
|
if t.bordered {
|
||||||
} else {
|
windows = append(windows, t.border)
|
||||||
t.tui.RefreshWindows([]tui.Window{t.window})
|
|
||||||
}
|
}
|
||||||
|
if t.hasPreviewWindow() {
|
||||||
|
windows = append(windows, t.pborder, t.pwindow)
|
||||||
|
}
|
||||||
|
windows = append(windows, t.window)
|
||||||
|
t.tui.RefreshWindows(windows)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1043,7 +1072,27 @@ func quoteEntry(entry string) string {
|
|||||||
return "'" + strings.Replace(entry, "'", "'\\''", -1) + "'"
|
return "'" + strings.Replace(entry, "'", "'\\''", -1) + "'"
|
||||||
}
|
}
|
||||||
|
|
||||||
func replacePlaceholder(template string, stripAnsi bool, delimiter Delimiter, query string, items []*Item) string {
|
func hasPlusFlag(template string) bool {
|
||||||
|
for _, match := range placeholder.FindAllString(template, -1) {
|
||||||
|
if match[0] == '\\' {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if match[1] == '+' {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func replacePlaceholder(template string, stripAnsi bool, delimiter Delimiter, forcePlus bool, query string, allItems []*Item) string {
|
||||||
|
current := allItems[:1]
|
||||||
|
selected := allItems[1:]
|
||||||
|
if current[0] == nil {
|
||||||
|
current = []*Item{}
|
||||||
|
}
|
||||||
|
if selected[0] == nil {
|
||||||
|
selected = []*Item{}
|
||||||
|
}
|
||||||
return placeholder.ReplaceAllStringFunc(template, func(match string) string {
|
return placeholder.ReplaceAllStringFunc(template, func(match string) string {
|
||||||
// Escaped pattern
|
// Escaped pattern
|
||||||
if match[0] == '\\' {
|
if match[0] == '\\' {
|
||||||
@@ -1055,6 +1104,16 @@ func replacePlaceholder(template string, stripAnsi bool, delimiter Delimiter, qu
|
|||||||
return quoteEntry(query)
|
return quoteEntry(query)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
plusFlag := forcePlus
|
||||||
|
if match[1] == '+' {
|
||||||
|
match = "{" + match[2:]
|
||||||
|
plusFlag = true
|
||||||
|
}
|
||||||
|
items := current
|
||||||
|
if plusFlag {
|
||||||
|
items = selected
|
||||||
|
}
|
||||||
|
|
||||||
replacements := make([]string, len(items))
|
replacements := make([]string, len(items))
|
||||||
|
|
||||||
if match == "{}" {
|
if match == "{}" {
|
||||||
@@ -1095,18 +1154,27 @@ func replacePlaceholder(template string, stripAnsi bool, delimiter Delimiter, qu
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Terminal) executeCommand(template string, items []*Item) {
|
func (t *Terminal) executeCommand(template string, forcePlus bool, background bool) {
|
||||||
command := replacePlaceholder(template, t.ansi, t.delimiter, string(t.input), items)
|
valid, list := t.buildPlusList(template, forcePlus)
|
||||||
cmd := util.ExecCommand(command)
|
if !valid {
|
||||||
cmd.Stdin = os.Stdin
|
return
|
||||||
cmd.Stdout = os.Stdout
|
}
|
||||||
cmd.Stderr = os.Stderr
|
command := replacePlaceholder(template, t.ansi, t.delimiter, forcePlus, string(t.input), list)
|
||||||
t.tui.Pause()
|
cmd := util.ExecCommand(command)
|
||||||
cmd.Run()
|
if !background {
|
||||||
if t.tui.Resume() {
|
cmd.Stdin = os.Stdin
|
||||||
t.printAll()
|
cmd.Stdout = os.Stdout
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
|
t.tui.Pause()
|
||||||
|
cmd.Run()
|
||||||
|
if t.tui.Resume() {
|
||||||
|
t.tui.Clear()
|
||||||
|
t.printAll()
|
||||||
|
}
|
||||||
|
t.refresh()
|
||||||
|
} else {
|
||||||
|
cmd.Run()
|
||||||
}
|
}
|
||||||
t.refresh()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Terminal) hasPreviewer() bool {
|
func (t *Terminal) hasPreviewer() bool {
|
||||||
@@ -1122,11 +1190,24 @@ func (t *Terminal) hasPreviewWindow() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *Terminal) currentItem() *Item {
|
func (t *Terminal) currentItem() *Item {
|
||||||
return t.merger.Get(t.cy).item
|
cnt := t.merger.Length()
|
||||||
|
if cnt > 0 && cnt > t.cy {
|
||||||
|
return t.merger.Get(t.cy).item
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Terminal) current() string {
|
func (t *Terminal) buildPlusList(template string, forcePlus bool) (bool, []*Item) {
|
||||||
return t.currentItem().AsString(t.ansi)
|
current := t.currentItem()
|
||||||
|
if !forcePlus && !hasPlusFlag(template) || len(t.selected) == 0 {
|
||||||
|
return current != nil, []*Item{current, current}
|
||||||
|
}
|
||||||
|
sels := make([]*Item, len(t.selected)+1)
|
||||||
|
sels[0] = current
|
||||||
|
for i, sel := range t.sortSelected() {
|
||||||
|
sels[i+1] = sel.item
|
||||||
|
}
|
||||||
|
return true, sels
|
||||||
}
|
}
|
||||||
|
|
||||||
// Loop is called to start Terminal I/O
|
// Loop is called to start Terminal I/O
|
||||||
@@ -1183,19 +1264,20 @@ func (t *Terminal) Loop() {
|
|||||||
if t.hasPreviewer() {
|
if t.hasPreviewer() {
|
||||||
go func() {
|
go func() {
|
||||||
for {
|
for {
|
||||||
var request *Item
|
var request []*Item
|
||||||
t.previewBox.Wait(func(events *util.Events) {
|
t.previewBox.Wait(func(events *util.Events) {
|
||||||
for req, value := range *events {
|
for req, value := range *events {
|
||||||
switch req {
|
switch req {
|
||||||
case reqPreviewEnqueue:
|
case reqPreviewEnqueue:
|
||||||
request = value.(*Item)
|
request = value.([]*Item)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
events.Clear()
|
events.Clear()
|
||||||
})
|
})
|
||||||
if request != nil {
|
// We don't display preview window if no match
|
||||||
|
if request[0] != nil {
|
||||||
command := replacePlaceholder(t.preview.command,
|
command := replacePlaceholder(t.preview.command,
|
||||||
t.ansi, t.delimiter, string(t.input), []*Item{request})
|
t.ansi, t.delimiter, false, string(t.input), request)
|
||||||
cmd := util.ExecCommand(command)
|
cmd := util.ExecCommand(command)
|
||||||
out, _ := cmd.CombinedOutput()
|
out, _ := cmd.CombinedOutput()
|
||||||
t.reqBox.Set(reqPreviewDisplay, string(out))
|
t.reqBox.Set(reqPreviewDisplay, string(out))
|
||||||
@@ -1231,17 +1313,12 @@ func (t *Terminal) Loop() {
|
|||||||
t.printInfo()
|
t.printInfo()
|
||||||
case reqList:
|
case reqList:
|
||||||
t.printList()
|
t.printList()
|
||||||
cnt := t.merger.Length()
|
currentFocus := t.currentItem()
|
||||||
var currentFocus *Item
|
|
||||||
if cnt > 0 && cnt > t.cy {
|
|
||||||
currentFocus = t.currentItem()
|
|
||||||
} else {
|
|
||||||
currentFocus = nil
|
|
||||||
}
|
|
||||||
if currentFocus != focused {
|
if currentFocus != focused {
|
||||||
focused = currentFocus
|
focused = currentFocus
|
||||||
if t.isPreviewEnabled() {
|
if t.isPreviewEnabled() {
|
||||||
t.previewBox.Set(reqPreviewEnqueue, focused)
|
_, list := t.buildPlusList(t.preview.command, false)
|
||||||
|
t.previewBox.Set(reqPreviewEnqueue, list)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case reqJump:
|
case reqJump:
|
||||||
@@ -1346,20 +1423,10 @@ func (t *Terminal) Loop() {
|
|||||||
doAction = func(a action, mapkey int) bool {
|
doAction = func(a action, mapkey int) bool {
|
||||||
switch a.t {
|
switch a.t {
|
||||||
case actIgnore:
|
case actIgnore:
|
||||||
case actExecute:
|
case actExecute, actExecuteSilent:
|
||||||
if t.cy >= 0 && t.cy < t.merger.Length() {
|
t.executeCommand(a.a, false, a.t == actExecuteSilent)
|
||||||
t.executeCommand(a.a, []*Item{t.currentItem()})
|
|
||||||
}
|
|
||||||
case actExecuteMulti:
|
case actExecuteMulti:
|
||||||
if len(t.selected) > 0 {
|
t.executeCommand(a.a, true, false)
|
||||||
sels := make([]*Item, len(t.selected))
|
|
||||||
for i, sel := range t.sortSelected() {
|
|
||||||
sels[i] = sel.item
|
|
||||||
}
|
|
||||||
t.executeCommand(a.a, sels)
|
|
||||||
} else {
|
|
||||||
return doAction(action{t: actExecute, a: a.a}, mapkey)
|
|
||||||
}
|
|
||||||
case actInvalid:
|
case actInvalid:
|
||||||
t.mutex.Unlock()
|
t.mutex.Unlock()
|
||||||
return false
|
return false
|
||||||
@@ -1368,9 +1435,11 @@ func (t *Terminal) Loop() {
|
|||||||
t.previewer.enabled = !t.previewer.enabled
|
t.previewer.enabled = !t.previewer.enabled
|
||||||
t.tui.Clear()
|
t.tui.Clear()
|
||||||
t.resizeWindows()
|
t.resizeWindows()
|
||||||
cnt := t.merger.Length()
|
if t.previewer.enabled {
|
||||||
if t.previewer.enabled && cnt > 0 && cnt > t.cy {
|
valid, list := t.buildPlusList(t.preview.command, false)
|
||||||
t.previewBox.Set(reqPreviewEnqueue, t.currentItem())
|
if valid {
|
||||||
|
t.previewBox.Set(reqPreviewEnqueue, list)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
req(reqList, reqInfo, reqHeader)
|
req(reqList, reqInfo, reqHeader)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,8 +14,10 @@ func newItem(str string) *Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestReplacePlaceholder(t *testing.T) {
|
func TestReplacePlaceholder(t *testing.T) {
|
||||||
items1 := []*Item{newItem(" foo'bar \x1b[31mbaz\x1b[m")}
|
item1 := newItem(" foo'bar \x1b[31mbaz\x1b[m")
|
||||||
|
items1 := []*Item{item1, item1}
|
||||||
items2 := []*Item{
|
items2 := []*Item{
|
||||||
|
newItem("foo'bar \x1b[31mbaz\x1b[m"),
|
||||||
newItem("foo'bar \x1b[31mbaz\x1b[m"),
|
newItem("foo'bar \x1b[31mbaz\x1b[m"),
|
||||||
newItem("FOO'BAR \x1b[31mBAZ\x1b[m")}
|
newItem("FOO'BAR \x1b[31mBAZ\x1b[m")}
|
||||||
|
|
||||||
@@ -27,47 +29,65 @@ func TestReplacePlaceholder(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// {}, preserve ansi
|
// {}, preserve ansi
|
||||||
result = replacePlaceholder("echo {}", false, Delimiter{}, "query", items1)
|
result = replacePlaceholder("echo {}", false, Delimiter{}, false, "query", items1)
|
||||||
check("echo ' foo'\\''bar \x1b[31mbaz\x1b[m'")
|
check("echo ' foo'\\''bar \x1b[31mbaz\x1b[m'")
|
||||||
|
|
||||||
// {}, strip ansi
|
// {}, strip ansi
|
||||||
result = replacePlaceholder("echo {}", true, Delimiter{}, "query", items1)
|
result = replacePlaceholder("echo {}", true, Delimiter{}, false, "query", items1)
|
||||||
check("echo ' foo'\\''bar baz'")
|
check("echo ' foo'\\''bar baz'")
|
||||||
|
|
||||||
// {}, with multiple items
|
// {}, with multiple items
|
||||||
result = replacePlaceholder("echo {}", true, Delimiter{}, "query", items2)
|
result = replacePlaceholder("echo {}", true, Delimiter{}, false, "query", items2)
|
||||||
check("echo 'foo'\\''bar baz' 'FOO'\\''BAR BAZ'")
|
check("echo 'foo'\\''bar baz'")
|
||||||
|
|
||||||
// {..}, strip leading whitespaces, preserve ansi
|
// {..}, strip leading whitespaces, preserve ansi
|
||||||
result = replacePlaceholder("echo {..}", false, Delimiter{}, "query", items1)
|
result = replacePlaceholder("echo {..}", false, Delimiter{}, false, "query", items1)
|
||||||
check("echo 'foo'\\''bar \x1b[31mbaz\x1b[m'")
|
check("echo 'foo'\\''bar \x1b[31mbaz\x1b[m'")
|
||||||
|
|
||||||
// {..}, strip leading whitespaces, strip ansi
|
// {..}, strip leading whitespaces, strip ansi
|
||||||
result = replacePlaceholder("echo {..}", true, Delimiter{}, "query", items1)
|
result = replacePlaceholder("echo {..}", true, Delimiter{}, false, "query", items1)
|
||||||
check("echo 'foo'\\''bar baz'")
|
check("echo 'foo'\\''bar baz'")
|
||||||
|
|
||||||
// {q}
|
// {q}
|
||||||
result = replacePlaceholder("echo {} {q}", true, Delimiter{}, "query", items1)
|
result = replacePlaceholder("echo {} {q}", true, Delimiter{}, false, "query", items1)
|
||||||
check("echo ' foo'\\''bar baz' 'query'")
|
check("echo ' foo'\\''bar baz' 'query'")
|
||||||
|
|
||||||
// {q}, multiple items
|
// {q}, multiple items
|
||||||
result = replacePlaceholder("echo {}{q}{}", true, Delimiter{}, "query 'string'", items2)
|
result = replacePlaceholder("echo {+}{q}{+}", true, Delimiter{}, false, "query 'string'", items2)
|
||||||
check("echo 'foo'\\''bar baz' 'FOO'\\''BAR BAZ''query '\\''string'\\''''foo'\\''bar baz' 'FOO'\\''BAR BAZ'")
|
check("echo 'foo'\\''bar baz' 'FOO'\\''BAR BAZ''query '\\''string'\\''''foo'\\''bar baz' 'FOO'\\''BAR BAZ'")
|
||||||
|
|
||||||
result = replacePlaceholder("echo {1}/{2}/{2,1}/{-1}/{-2}/{}/{..}/{n.t}/\\{}/\\{1}/\\{q}/{3}", true, Delimiter{}, "query", items1)
|
result = replacePlaceholder("echo {}{q}{}", true, Delimiter{}, false, "query 'string'", items2)
|
||||||
|
check("echo 'foo'\\''bar baz''query '\\''string'\\''''foo'\\''bar baz'")
|
||||||
|
|
||||||
|
result = replacePlaceholder("echo {1}/{2}/{2,1}/{-1}/{-2}/{}/{..}/{n.t}/\\{}/\\{1}/\\{q}/{3}", true, Delimiter{}, false, "query", items1)
|
||||||
check("echo 'foo'\\''bar'/'baz'/'bazfoo'\\''bar'/'baz'/'foo'\\''bar'/' foo'\\''bar baz'/'foo'\\''bar baz'/{n.t}/{}/{1}/{q}/''")
|
check("echo 'foo'\\''bar'/'baz'/'bazfoo'\\''bar'/'baz'/'foo'\\''bar'/' foo'\\''bar baz'/'foo'\\''bar baz'/{n.t}/{}/{1}/{q}/''")
|
||||||
|
|
||||||
result = replacePlaceholder("echo {1}/{2}/{-1}/{-2}/{..}/{n.t}/\\{}/\\{1}/\\{q}/{3}", true, Delimiter{}, "query", items2)
|
result = replacePlaceholder("echo {1}/{2}/{-1}/{-2}/{..}/{n.t}/\\{}/\\{1}/\\{q}/{3}", true, Delimiter{}, false, "query", items2)
|
||||||
|
check("echo 'foo'\\''bar'/'baz'/'baz'/'foo'\\''bar'/'foo'\\''bar baz'/{n.t}/{}/{1}/{q}/''")
|
||||||
|
|
||||||
|
result = replacePlaceholder("echo {+1}/{+2}/{+-1}/{+-2}/{+..}/{n.t}/\\{}/\\{1}/\\{q}/{+3}", true, Delimiter{}, false, "query", items2)
|
||||||
check("echo 'foo'\\''bar' 'FOO'\\''BAR'/'baz' 'BAZ'/'baz' 'BAZ'/'foo'\\''bar' 'FOO'\\''BAR'/'foo'\\''bar baz' 'FOO'\\''BAR BAZ'/{n.t}/{}/{1}/{q}/'' ''")
|
check("echo 'foo'\\''bar' 'FOO'\\''BAR'/'baz' 'BAZ'/'baz' 'BAZ'/'foo'\\''bar' 'FOO'\\''BAR'/'foo'\\''bar baz' 'FOO'\\''BAR BAZ'/{n.t}/{}/{1}/{q}/'' ''")
|
||||||
|
|
||||||
|
// forcePlus
|
||||||
|
result = replacePlaceholder("echo {1}/{2}/{-1}/{-2}/{..}/{n.t}/\\{}/\\{1}/\\{q}/{3}", true, Delimiter{}, true, "query", items2)
|
||||||
|
check("echo 'foo'\\''bar' 'FOO'\\''BAR'/'baz' 'BAZ'/'baz' 'BAZ'/'foo'\\''bar' 'FOO'\\''BAR'/'foo'\\''bar baz' 'FOO'\\''BAR BAZ'/{n.t}/{}/{1}/{q}/'' ''")
|
||||||
|
|
||||||
|
// No match
|
||||||
|
result = replacePlaceholder("echo {}/{+}", true, Delimiter{}, false, "query", []*Item{nil, nil})
|
||||||
|
check("echo /")
|
||||||
|
|
||||||
|
// No match, but with selections
|
||||||
|
result = replacePlaceholder("echo {}/{+}", true, Delimiter{}, false, "query", []*Item{nil, item1})
|
||||||
|
check("echo /' foo'\\''bar baz'")
|
||||||
|
|
||||||
// String delimiter
|
// String delimiter
|
||||||
delim := "'"
|
delim := "'"
|
||||||
result = replacePlaceholder("echo {}/{1}/{2}", true, Delimiter{str: &delim}, "query", items1)
|
result = replacePlaceholder("echo {}/{1}/{2}", true, Delimiter{str: &delim}, false, "query", items1)
|
||||||
check("echo ' foo'\\''bar baz'/'foo'/'bar baz'")
|
check("echo ' foo'\\''bar baz'/'foo'/'bar baz'")
|
||||||
|
|
||||||
// Regex delimiter
|
// Regex delimiter
|
||||||
regex := regexp.MustCompile("[oa]+")
|
regex := regexp.MustCompile("[oa]+")
|
||||||
// foo'bar baz
|
// foo'bar baz
|
||||||
result = replacePlaceholder("echo {}/{1}/{3}/{2..3}", true, Delimiter{regex: regex}, "query", items1)
|
result = replacePlaceholder("echo {}/{1}/{3}/{2..3}", true, Delimiter{regex: regex}, false, "query", items1)
|
||||||
check("echo ' foo'\\''bar baz'/'f'/'r b'/''\\''bar b'")
|
check("echo ' foo'\\''bar baz'/'f'/'r b'/''\\''bar b'")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,6 +40,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, border bool) Window {
|
func (r *FullscreenRenderer) NewWindow(top int, left int, width int, height int, borderStyle BorderStyle) Window {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ type LightRenderer struct {
|
|||||||
type LightWindow struct {
|
type LightWindow struct {
|
||||||
renderer *LightRenderer
|
renderer *LightRenderer
|
||||||
colored bool
|
colored bool
|
||||||
border bool
|
border BorderStyle
|
||||||
top int
|
top int
|
||||||
left int
|
left int
|
||||||
width int
|
width int
|
||||||
@@ -322,6 +322,8 @@ func (r *LightRenderer) GetChar() Event {
|
|||||||
return Event{CtrlQ, 0, nil}
|
return Event{CtrlQ, 0, nil}
|
||||||
case 127:
|
case 127:
|
||||||
return Event{BSpace, 0, nil}
|
return Event{BSpace, 0, nil}
|
||||||
|
case 0:
|
||||||
|
return Event{CtrlSpace, 0, nil}
|
||||||
case ESC:
|
case ESC:
|
||||||
ev := r.escSequence(&sz)
|
ev := r.escSequence(&sz)
|
||||||
// Second chance
|
// Second chance
|
||||||
@@ -532,6 +534,7 @@ func (r *LightRenderer) Pause() {
|
|||||||
r.rmcup()
|
r.rmcup()
|
||||||
} else {
|
} else {
|
||||||
r.smcup()
|
r.smcup()
|
||||||
|
r.csi("H")
|
||||||
}
|
}
|
||||||
r.flush()
|
r.flush()
|
||||||
}
|
}
|
||||||
@@ -597,11 +600,11 @@ func (r *LightRenderer) IsOptimized() bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *LightRenderer) NewWindow(top int, left int, width int, height int, border bool) Window {
|
func (r *LightRenderer) NewWindow(top int, left int, width int, height int, borderStyle BorderStyle) Window {
|
||||||
w := &LightWindow{
|
w := &LightWindow{
|
||||||
renderer: r,
|
renderer: r,
|
||||||
colored: r.theme != nil,
|
colored: r.theme != nil,
|
||||||
border: border,
|
border: borderStyle,
|
||||||
top: top,
|
top: top,
|
||||||
left: left,
|
left: left,
|
||||||
width: width,
|
width: width,
|
||||||
@@ -611,13 +614,27 @@ func (r *LightRenderer) NewWindow(top int, left int, width int, height int, bord
|
|||||||
if r.theme != nil {
|
if r.theme != nil {
|
||||||
w.bg = r.theme.Bg
|
w.bg = r.theme.Bg
|
||||||
}
|
}
|
||||||
if w.border {
|
w.drawBorder()
|
||||||
w.drawBorder()
|
|
||||||
}
|
|
||||||
return w
|
return w
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *LightWindow) drawBorder() {
|
func (w *LightWindow) drawBorder() {
|
||||||
|
switch w.border {
|
||||||
|
case BorderAround:
|
||||||
|
w.drawBorderAround()
|
||||||
|
case BorderHorizontal:
|
||||||
|
w.drawBorderHorizontal()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *LightWindow) drawBorderHorizontal() {
|
||||||
|
w.Move(0, 0)
|
||||||
|
w.CPrint(ColBorder, AttrRegular, repeat("─", w.width))
|
||||||
|
w.Move(w.height-1, 0)
|
||||||
|
w.CPrint(ColBorder, AttrRegular, repeat("─", w.width))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *LightWindow) drawBorderAround() {
|
||||||
w.Move(0, 0)
|
w.Move(0, 0)
|
||||||
w.CPrint(ColBorder, AttrRegular, "┌"+repeat("─", w.width-2)+"┐")
|
w.CPrint(ColBorder, AttrRegular, "┌"+repeat("─", w.width-2)+"┐")
|
||||||
for y := 1; y < w.height-1; y++ {
|
for y := 1; y < w.height-1; y++ {
|
||||||
@@ -745,13 +762,17 @@ func (w *LightWindow) Print(text string) {
|
|||||||
w.cprint2(colDefault, w.bg, AttrRegular, text)
|
w.cprint2(colDefault, w.bg, AttrRegular, text)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func cleanse(str string) string {
|
||||||
|
return strings.Replace(str, "\x1b", "?", -1)
|
||||||
|
}
|
||||||
|
|
||||||
func (w *LightWindow) CPrint(pair ColorPair, attr Attr, text string) {
|
func (w *LightWindow) CPrint(pair ColorPair, attr Attr, text string) {
|
||||||
if !w.colored {
|
if !w.colored {
|
||||||
w.csiColor(colDefault, colDefault, attrFor(pair, attr))
|
w.csiColor(colDefault, colDefault, attrFor(pair, attr))
|
||||||
} else {
|
} else {
|
||||||
w.csiColor(pair.Fg(), pair.Bg(), attr)
|
w.csiColor(pair.Fg(), pair.Bg(), attr)
|
||||||
}
|
}
|
||||||
w.stderrInternal(text, false)
|
w.stderrInternal(cleanse(text), false)
|
||||||
w.csi("m")
|
w.csi("m")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -759,7 +780,7 @@ func (w *LightWindow) cprint2(fg Color, bg Color, attr Attr, text string) {
|
|||||||
if w.csiColor(fg, bg, attr) {
|
if w.csiColor(fg, bg, attr) {
|
||||||
defer w.csi("m")
|
defer w.csi("m")
|
||||||
}
|
}
|
||||||
w.stderrInternal(text, false)
|
w.stderrInternal(cleanse(text), false)
|
||||||
}
|
}
|
||||||
|
|
||||||
type wrappedLine struct {
|
type wrappedLine struct {
|
||||||
@@ -847,9 +868,7 @@ func (w *LightWindow) FinishFill() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (w *LightWindow) Erase() {
|
func (w *LightWindow) Erase() {
|
||||||
if w.border {
|
w.drawBorder()
|
||||||
w.drawBorder()
|
|
||||||
}
|
|
||||||
// We don't erase the window here to avoid flickering during scroll
|
// We don't erase the window here to avoid flickering during scroll
|
||||||
w.Move(0, 0)
|
w.Move(0, 0)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -189,12 +189,13 @@ func (r *FullscreenRenderer) Close() {
|
|||||||
C.delscreen(_screen)
|
C.delscreen(_screen)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *FullscreenRenderer) NewWindow(top int, left int, width int, height int, border bool) Window {
|
func (r *FullscreenRenderer) NewWindow(top int, left int, width int, height int, borderStyle BorderStyle) Window {
|
||||||
win := C.newwin(C.int(height), C.int(width), C.int(top), C.int(left))
|
win := C.newwin(C.int(height), C.int(width), C.int(top), C.int(left))
|
||||||
if r.theme != nil {
|
if r.theme != nil {
|
||||||
C.wbkgd(win, C.chtype(C.COLOR_PAIR(C.int(ColNormal.index()))))
|
C.wbkgd(win, C.chtype(C.COLOR_PAIR(C.int(ColNormal.index()))))
|
||||||
}
|
}
|
||||||
if border {
|
// FIXME Does not implement BorderHorizontal
|
||||||
|
if borderStyle != BorderNone {
|
||||||
pair, attr := _colorFn(ColBorder, 0)
|
pair, attr := _colorFn(ColBorder, 0)
|
||||||
C.wcolor_set(win, pair, nil)
|
C.wcolor_set(win, pair, nil)
|
||||||
C.wattron(win, attr)
|
C.wattron(win, attr)
|
||||||
@@ -475,6 +476,8 @@ func (r *FullscreenRenderer) GetChar() Event {
|
|||||||
return escSequence()
|
return escSequence()
|
||||||
case 127:
|
case 127:
|
||||||
return Event{BSpace, 0, nil}
|
return Event{BSpace, 0, nil}
|
||||||
|
case 0:
|
||||||
|
return Event{CtrlSpace, 0, nil}
|
||||||
}
|
}
|
||||||
// CTRL-A ~ CTRL-Z
|
// CTRL-A ~ CTRL-Z
|
||||||
if c >= CtrlA && c <= CtrlZ {
|
if c >= CtrlA && c <= CtrlZ {
|
||||||
|
|||||||
@@ -27,15 +27,15 @@ func (p ColorPair) style() tcell.Style {
|
|||||||
type Attr tcell.Style
|
type Attr tcell.Style
|
||||||
|
|
||||||
type TcellWindow struct {
|
type TcellWindow struct {
|
||||||
color bool
|
color bool
|
||||||
top int
|
top int
|
||||||
left int
|
left int
|
||||||
width int
|
width int
|
||||||
height int
|
height int
|
||||||
lastX int
|
lastX int
|
||||||
lastY int
|
lastY int
|
||||||
moveCursor bool
|
moveCursor bool
|
||||||
border bool
|
borderStyle BorderStyle
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *TcellWindow) Top() int {
|
func (w *TcellWindow) Top() int {
|
||||||
@@ -61,8 +61,11 @@ func (w *TcellWindow) Refresh() {
|
|||||||
}
|
}
|
||||||
w.lastX = 0
|
w.lastX = 0
|
||||||
w.lastY = 0
|
w.lastY = 0
|
||||||
if w.border {
|
switch w.borderStyle {
|
||||||
w.drawBorder()
|
case BorderAround:
|
||||||
|
w.drawBorder(true)
|
||||||
|
case BorderHorizontal:
|
||||||
|
w.drawBorder(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -270,6 +273,8 @@ func (r *FullscreenRenderer) GetChar() Event {
|
|||||||
return Event{CtrlY, 0, nil}
|
return Event{CtrlY, 0, nil}
|
||||||
case tcell.KeyCtrlZ:
|
case tcell.KeyCtrlZ:
|
||||||
return Event{CtrlZ, 0, nil}
|
return Event{CtrlZ, 0, nil}
|
||||||
|
case tcell.KeyCtrlSpace:
|
||||||
|
return Event{CtrlSpace, 0, nil}
|
||||||
case tcell.KeyBackspace, tcell.KeyBackspace2:
|
case tcell.KeyBackspace, tcell.KeyBackspace2:
|
||||||
if alt {
|
if alt {
|
||||||
return Event{AltBS, 0, nil}
|
return Event{AltBS, 0, nil}
|
||||||
@@ -375,15 +380,15 @@ func (r *FullscreenRenderer) RefreshWindows(windows []Window) {
|
|||||||
_screen.Show()
|
_screen.Show()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *FullscreenRenderer) NewWindow(top int, left int, width int, height int, border bool) Window {
|
func (r *FullscreenRenderer) NewWindow(top int, left int, width int, height int, borderStyle BorderStyle) Window {
|
||||||
// TODO
|
// TODO
|
||||||
return &TcellWindow{
|
return &TcellWindow{
|
||||||
color: r.theme != nil,
|
color: r.theme != nil,
|
||||||
top: top,
|
top: top,
|
||||||
left: left,
|
left: left,
|
||||||
width: width,
|
width: width,
|
||||||
height: height,
|
height: height,
|
||||||
border: border}
|
borderStyle: borderStyle}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *TcellWindow) Close() {
|
func (w *TcellWindow) Close() {
|
||||||
@@ -534,7 +539,7 @@ func (w *TcellWindow) CFill(fg Color, bg Color, a Attr, str string) FillReturn {
|
|||||||
return w.fillString(str, ColorPair{fg, bg, -1}, a)
|
return w.fillString(str, ColorPair{fg, bg, -1}, a)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *TcellWindow) drawBorder() {
|
func (w *TcellWindow) drawBorder(around bool) {
|
||||||
left := w.left
|
left := w.left
|
||||||
right := left + w.width
|
right := left + w.width
|
||||||
top := w.top
|
top := w.top
|
||||||
@@ -552,13 +557,15 @@ func (w *TcellWindow) drawBorder() {
|
|||||||
_screen.SetContent(x, bot-1, tcell.RuneHLine, nil, style)
|
_screen.SetContent(x, bot-1, tcell.RuneHLine, nil, style)
|
||||||
}
|
}
|
||||||
|
|
||||||
for y := top; y < bot; y++ {
|
if around {
|
||||||
_screen.SetContent(left, y, tcell.RuneVLine, nil, style)
|
for y := top; y < bot; y++ {
|
||||||
_screen.SetContent(right-1, y, tcell.RuneVLine, nil, style)
|
_screen.SetContent(left, y, tcell.RuneVLine, nil, style)
|
||||||
}
|
_screen.SetContent(right-1, y, tcell.RuneVLine, nil, style)
|
||||||
|
}
|
||||||
|
|
||||||
_screen.SetContent(left, top, tcell.RuneULCorner, nil, style)
|
_screen.SetContent(left, top, tcell.RuneULCorner, nil, style)
|
||||||
_screen.SetContent(right-1, top, tcell.RuneURCorner, nil, style)
|
_screen.SetContent(right-1, top, tcell.RuneURCorner, nil, style)
|
||||||
_screen.SetContent(left, bot-1, tcell.RuneLLCorner, nil, style)
|
_screen.SetContent(left, bot-1, tcell.RuneLLCorner, nil, style)
|
||||||
_screen.SetContent(right-1, bot-1, tcell.RuneLRCorner, nil, style)
|
_screen.SetContent(right-1, bot-1, tcell.RuneLRCorner, nil, style)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ const (
|
|||||||
CtrlY
|
CtrlY
|
||||||
CtrlZ
|
CtrlZ
|
||||||
ESC
|
ESC
|
||||||
|
CtrlSpace
|
||||||
|
|
||||||
Invalid
|
Invalid
|
||||||
Resize
|
Resize
|
||||||
@@ -194,6 +195,14 @@ type MouseEvent struct {
|
|||||||
Mod bool
|
Mod bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type BorderStyle int
|
||||||
|
|
||||||
|
const (
|
||||||
|
BorderNone BorderStyle = iota
|
||||||
|
BorderAround
|
||||||
|
BorderHorizontal
|
||||||
|
)
|
||||||
|
|
||||||
type Renderer interface {
|
type Renderer interface {
|
||||||
Init()
|
Init()
|
||||||
Pause()
|
Pause()
|
||||||
@@ -210,7 +219,7 @@ type Renderer interface {
|
|||||||
DoesAutoWrap() bool
|
DoesAutoWrap() bool
|
||||||
IsOptimized() bool
|
IsOptimized() bool
|
||||||
|
|
||||||
NewWindow(top int, left int, width int, height int, border bool) Window
|
NewWindow(top int, left int, width int, height int, borderStyle BorderStyle) Window
|
||||||
}
|
}
|
||||||
|
|
||||||
type Window interface {
|
type Window interface {
|
||||||
|
|||||||
@@ -617,6 +617,17 @@ class TestGoFZF < TestBase
|
|||||||
], `#{FZF} -foo --tiebreak=begin,length < #{tempname}`.split($/)
|
], `#{FZF} -foo --tiebreak=begin,length < #{tempname}`.split($/)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_tiebreak_begin_algo_v2
|
||||||
|
writelines tempname, [
|
||||||
|
'baz foo bar',
|
||||||
|
'foo bar baz',
|
||||||
|
]
|
||||||
|
assert_equal [
|
||||||
|
'foo bar baz',
|
||||||
|
'baz foo bar',
|
||||||
|
], `#{FZF} -fbar --tiebreak=begin --algo=v2 < #{tempname}`.split($/)
|
||||||
|
end
|
||||||
|
|
||||||
def test_tiebreak_end
|
def test_tiebreak_end
|
||||||
writelines tempname, [
|
writelines tempname, [
|
||||||
'xoxxxxxxxx',
|
'xoxxxxxxxx',
|
||||||
@@ -879,7 +890,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' }
|
||||||
@@ -902,6 +913,43 @@ class TestGoFZF < TestBase
|
|||||||
File.unlink output rescue nil
|
File.unlink output rescue nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_execute_plus_flag
|
||||||
|
output = tempname + ".tmp"
|
||||||
|
File.unlink output rescue nil
|
||||||
|
writelines tempname, ["foo bar", "123 456"]
|
||||||
|
|
||||||
|
tmux.send_keys "cat #{tempname} | #{FZF} --multi --bind 'x:execute(echo {+}/{}/{+2}/{2} >> #{output})'", :Enter
|
||||||
|
|
||||||
|
execute = lambda do
|
||||||
|
tmux.send_keys 'x', 'y'
|
||||||
|
tmux.until { |lines| lines[-2].include? '0/2' }
|
||||||
|
tmux.send_keys :BSpace
|
||||||
|
tmux.until { |lines| lines[-2].include? '2/2' }
|
||||||
|
end
|
||||||
|
|
||||||
|
tmux.until { |lines| lines[-2].include? '2/2' }
|
||||||
|
execute.call
|
||||||
|
|
||||||
|
tmux.send_keys :Up
|
||||||
|
tmux.send_keys :Tab
|
||||||
|
execute.call
|
||||||
|
|
||||||
|
tmux.send_keys :Tab
|
||||||
|
execute.call
|
||||||
|
|
||||||
|
tmux.send_keys :Enter
|
||||||
|
tmux.prepare
|
||||||
|
readonce
|
||||||
|
|
||||||
|
assert_equal [
|
||||||
|
%[foo bar/foo bar/bar/bar],
|
||||||
|
%[123 456/foo bar/456/bar],
|
||||||
|
%[123 456 foo bar/foo bar/456 bar/bar]
|
||||||
|
], File.readlines(output).map(&:chomp)
|
||||||
|
rescue
|
||||||
|
File.unlink output rescue nil
|
||||||
|
end
|
||||||
|
|
||||||
def test_execute_shell
|
def test_execute_shell
|
||||||
# Custom script to use as $SHELL
|
# Custom script to use as $SHELL
|
||||||
output = tempname + '.out'
|
output = tempname + '.out'
|
||||||
@@ -1198,7 +1246,7 @@ class TestGoFZF < TestBase
|
|||||||
end
|
end
|
||||||
|
|
||||||
def test_preview
|
def test_preview
|
||||||
tmux.send_keys %[seq 1000 | sed s/^2$// | #{FZF} --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}') }
|
||||||
tmux.send_keys :Up
|
tmux.send_keys :Up
|
||||||
tmux.until { |lines| lines[1].include?(' {-}') }
|
tmux.until { |lines| lines[1].include?(' {-}') }
|
||||||
@@ -1212,6 +1260,17 @@ class TestGoFZF < TestBase
|
|||||||
tmux.until { |lines| lines[-2].start_with? ' 28/1000' }
|
tmux.until { |lines| lines[-2].start_with? ' 28/1000' }
|
||||||
tmux.send_keys 'foobar'
|
tmux.send_keys 'foobar'
|
||||||
tmux.until { |lines| !lines[1].include?('{') }
|
tmux.until { |lines| !lines[1].include?('{') }
|
||||||
|
tmux.send_keys 'C-u'
|
||||||
|
tmux.until { |lines| lines.match_count == 1000 }
|
||||||
|
tmux.until { |lines| lines[1].include?(' {1-1}') }
|
||||||
|
tmux.send_keys :BTab
|
||||||
|
tmux.until { |lines| lines[1].include?(' {-1}') }
|
||||||
|
tmux.send_keys :BTab
|
||||||
|
tmux.until { |lines| lines[1].include?(' {3-1 }') }
|
||||||
|
tmux.send_keys :BTab
|
||||||
|
tmux.until { |lines| lines[1].include?(' {4-1 3}') }
|
||||||
|
tmux.send_keys :BTab
|
||||||
|
tmux.until { |lines| lines[1].include?(' {5-1 3 4}') }
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_preview_hidden
|
def test_preview_hidden
|
||||||
@@ -1495,7 +1554,7 @@ module CompletionTest
|
|||||||
tmux.prepare
|
tmux.prepare
|
||||||
|
|
||||||
# Using tmux
|
# Using tmux
|
||||||
tmux.send_keys 'unset FZFFOO**', :Tab
|
tmux.send_keys 'unset FZFFOOBR**', :Tab
|
||||||
tmux.until { |lines| lines.match_count == 1 }
|
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' }
|
||||||
@@ -1503,7 +1562,7 @@ module CompletionTest
|
|||||||
|
|
||||||
# FZF_TMUX=1
|
# FZF_TMUX=1
|
||||||
new_shell
|
new_shell
|
||||||
tmux.send_keys 'unset FZFFO**', :Tab, pane: 0
|
tmux.send_keys 'unset FZFFOOBR**', :Tab, pane: 0
|
||||||
tmux.until(false, 1) { |lines| lines.match_count == 1 }
|
tmux.until(false, 1) { |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' }
|
||||||
|
|||||||
Reference in New Issue
Block a user