mirror of
https://github.com/junegunn/fzf.git
synced 2025-09-01 12:53:50 -07:00
Compare commits
95 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
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
|
||||
vendor
|
||||
gopath
|
||||
*.zwc
|
||||
fzf
|
||||
|
41
.travis.yml
41
.travis.yml
@@ -1,27 +1,22 @@
|
||||
language: go
|
||||
dist: xenial
|
||||
env: GO111MODULE=on
|
||||
os:
|
||||
- linux
|
||||
- osx
|
||||
dist: bionic # For fish >= 2.3.0 string builtin
|
||||
addons:
|
||||
apt:
|
||||
sources:
|
||||
- sourceline: "ppa:pi-rho/dev"
|
||||
- sourceline: "ppa:fish-shell/release-2"
|
||||
packages:
|
||||
- tmux
|
||||
- zsh
|
||||
- fish
|
||||
|
||||
env:
|
||||
- GO111MODULE=on
|
||||
|
||||
jobs:
|
||||
include:
|
||||
- stage: unittest
|
||||
go: "1.13.x"
|
||||
script: make && make test
|
||||
|
||||
- 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 ]
|
||||
|
||||
- fish
|
||||
- zsh
|
||||
homebrew:
|
||||
packages:
|
||||
- fish
|
||||
- tmux
|
||||
update: true
|
||||
script:
|
||||
- make test
|
||||
# LC_ALL=C to avoid escape codes in
|
||||
# printf %q $'\355\205\214\354\212\244\355\212\270' on macOS. Bash on
|
||||
# macOS is built without HANDLE_MULTIBYTE?
|
||||
- make install && ./install --all && LC_ALL=C tmux new-session -d && ruby test/test_go.rb --verbose
|
||||
|
75
CHANGELOG.md
75
CHANGELOG.md
@@ -1,9 +1,82 @@
|
||||
CHANGELOG
|
||||
=========
|
||||
|
||||
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
|
||||
------
|
||||
|
||||
- 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
|
||||
restarting fzf. See https://github.com/junegunn/fzf/issues/1750 to learn
|
||||
more about it.
|
||||
@@ -11,7 +84,7 @@ CHANGELOG
|
||||
# Using fzf as the selector interface for ripgrep
|
||||
RG_PREFIX="rg --column --line-number --no-heading --color=always --smart-case "
|
||||
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" \
|
||||
--ansi --phony --query "$INITIAL_QUERY"
|
||||
```
|
||||
|
@@ -1,5 +1,5 @@
|
||||
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 echo '. /usr/share/bash-completion/completions/git' >> ~/.bashrc
|
||||
RUN echo '. ~/.bashrc' >> ~/.bash_profile
|
||||
|
2
LICENSE
2
LICENSE
@@ -1,6 +1,6 @@
|
||||
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
|
||||
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
|
||||
===================
|
||||
|
||||
This repository only enables basic integration with Vim. If you're looking for
|
||||
more, check out [fzf.vim](https://github.com/junegunn/fzf.vim) project.
|
||||
Installation
|
||||
------------
|
||||
|
||||
(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[!]`
|
||||
---------
|
||||
|
||||
If you have set up fzf for Vim, `:FZF` command will be added.
|
||||
|
||||
```vim
|
||||
" Look for files under current directory
|
||||
: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
|
||||
:FZF ~
|
||||
|
||||
" With options
|
||||
:FZF --no-sort --reverse --inline-info /tmp
|
||||
" With fzf command-line options
|
||||
:FZF --reverse --info=inline /tmp
|
||||
|
||||
" Bang version starts fzf in fullscreen mode
|
||||
:FZF!
|
||||
@@ -75,6 +136,7 @@ let g:fzf_layout = { 'window': '-tabnew' }
|
||||
let g:fzf_layout = { 'window': '10new' }
|
||||
|
||||
" Customize fzf colors to match your color scheme
|
||||
" - fzf#wrap translates this to a set of `--color` options
|
||||
let g:fzf_colors =
|
||||
\ { 'fg': ['fg', 'Normal'],
|
||||
\ 'bg': ['bg', 'Normal'],
|
||||
@@ -90,69 +152,109 @@ let g:fzf_colors =
|
||||
\ 'spinner': ['fg', 'Label'],
|
||||
\ 'header': ['fg', 'Comment'] }
|
||||
|
||||
" Enable per-command history.
|
||||
" CTRL-N and CTRL-P will be automatically bound to next-history and
|
||||
" previous-history instead of down and up. If you don't like the change,
|
||||
" explicitly bind the keys to down and up in your $FZF_DEFAULT_OPTS.
|
||||
" Enable per-command history
|
||||
" - History files will be stored in the specified directory
|
||||
" - When set, CTRL-N and CTRL-P will be bound to 'next-history' and
|
||||
" 'previous-history' instead of 'down' and 'up'.
|
||||
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`
|
||||
---------
|
||||
|
||||
For more advanced uses, you can use `fzf#run([options])` function.
|
||||
|
||||
`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
|
||||
should do with the selected entry.
|
||||
dictionary argument, *a spec*, and starts fzf process accordingly. At the very
|
||||
least, specify `sink` option to tell what it should do with the selected
|
||||
entry.
|
||||
|
||||
```vim
|
||||
call fzf#run({'sink': 'e'})
|
||||
```
|
||||
|
||||
Without `source`, fzf will use find command (or `$FZF_DEFAULT_COMMAND` if
|
||||
defined) to list the files under the current directory. When you select one,
|
||||
it will open it with `:e` command. If you want to open it in a new tab, you
|
||||
can pass `:tabedit` command instead as the sink.
|
||||
We haven't specified the `source`, so this is equivalent to starting fzf on
|
||||
command line without standard input pipe; fzf will use find command (or
|
||||
`$FZF_DEFAULT_COMMAND` if defined) to list the files under the current
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
" 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'})
|
||||
```
|
||||
|
||||
`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
|
||||
buffers as the source.
|
||||
|
||||
```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.
|
||||
array as the source. In the next example, we pass the names of color
|
||||
schemes as the source to implement a color scheme selector.
|
||||
|
||||
```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%'})
|
||||
```
|
||||
|
||||
The following table shows the available options.
|
||||
The following table summarizes the available options.
|
||||
|
||||
| Option name | Type | Description |
|
||||
| -------------------------- | ------------- | ---------------------------------------------------------------- |
|
||||
| `source` | string | External command to generate input to fzf (e.g. `find .`) |
|
||||
| `source` | list | Vim list as input to fzf |
|
||||
| `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 | Similar to `sink`, but takes the list of output lines at once |
|
||||
| `options` | string/list | Options to fzf |
|
||||
| `dir` | string | Working directory |
|
||||
| `up`/`down`/`left`/`right` | number/string | Use tmux pane with the given size (e.g. `20`, `50%`) |
|
||||
| `window` (Vim 8 / Neovim) | string | Command to open fzf window (e.g. `vertical aboveleft 30new`) |
|
||||
| `launcher` | string | External terminal emulator to start fzf with (GVim only) |
|
||||
| `launcher` | funcref | Function for generating `launcher` string (GVim only) |
|
||||
| Option name | Type | Description |
|
||||
| -------------------------- | ------------- | ---------------------------------------------------------------- |
|
||||
| `source` | string | External command to generate input to fzf (e.g. `find .`) |
|
||||
| `source` | list | Vim list as input to fzf |
|
||||
| `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 | Similar to `sink`, but takes the list of output lines at once |
|
||||
| `options` | string/list | Options to fzf |
|
||||
| `dir` | string | Working directory |
|
||||
| `up`/`down`/`left`/`right` | number/string | (Layout) Window position and size (e.g. `20`, `50%`) |
|
||||
| `window` (Vim 8 / Neovim) | string | (Layout) Command to open fzf window (e.g. `vertical aboveleft 30new`) |
|
||||
| `window` (Vim 8 / Neovim) | dict | (Layout) Popup window settings (e.g. `{'width': 0.9, 'height': 0.6}`) |
|
||||
|
||||
`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
|
||||
issues on different platforms.
|
||||
should suffice, but prefer to use list type to avoid escaping issues.
|
||||
|
||||
```vim
|
||||
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` command provided by default knows how to handle `CTRL-T`, `CTRL-X`, and
|
||||
`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.
|
||||
We have seen that several aspects of `:FZF` command can be configured with
|
||||
a 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.
|
||||
|
||||
By *"wrapping"* your options dictionary with `fzf#wrap` before passing it to
|
||||
`fzf#run`, you can make your command also support the options.
|
||||
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:
|
||||
|
||||
```vim
|
||||
" Usage:
|
||||
" 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))
|
||||
echo fzf#wrap({'source': 'ls'})
|
||||
```
|
||||
|
||||
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
|
||||
(`:terminal`) and fzf will start in a terminal buffer in the following cases:
|
||||
|
||||
- On Neovim
|
||||
- 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%'}`
|
||||
|
||||
### Hide statusline
|
||||
|
||||
When fzf starts in a terminal buffer, you may want to hide the statusline of
|
||||
the containing buffer.
|
||||
#### Starting fzf in a popup window
|
||||
|
||||
```vim
|
||||
autocmd! FileType fzf
|
||||
autocmd FileType fzf set laststatus=0 noshowmode noruler
|
||||
\| autocmd BufLeave <buffer> set laststatus=2 showmode ruler
|
||||
" 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
|
||||
|
||||
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)
|
||||
@@ -246,4 +419,4 @@ autocmd FileType fzf set laststatus=0 noshowmode noruler
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2017 Junegunn Choi
|
||||
Copyright (c) 2013-2020 Junegunn Choi
|
||||
|
299
README.md
299
README.md
@@ -25,13 +25,9 @@ Table of Contents
|
||||
* [Installation](#installation)
|
||||
* [Using Homebrew or Linuxbrew](#using-homebrew-or-linuxbrew)
|
||||
* [Using git](#using-git)
|
||||
* [As Vim plugin](#as-vim-plugin)
|
||||
* [Arch Linux](#arch-linux)
|
||||
* [Debian](#debian)
|
||||
* [Fedora](#fedora)
|
||||
* [openSUSE](#opensuse)
|
||||
* [FreeBSD](#freebsd)
|
||||
* [Using Linux package managers](#using-linux-package-managers)
|
||||
* [Windows](#windows)
|
||||
* [As Vim plugin](#as-vim-plugin)
|
||||
* [Upgrading fzf](#upgrading-fzf)
|
||||
* [Building fzf](#building-fzf)
|
||||
* [Usage](#usage)
|
||||
@@ -51,6 +47,7 @@ Table of Contents
|
||||
* [Environment variables / Aliases](#environment-variables--aliases)
|
||||
* [Settings](#settings)
|
||||
* [Supported commands](#supported-commands)
|
||||
* [Custom fuzzy completion](#custom-fuzzy-completion)
|
||||
* [Vim plugin](#vim-plugin)
|
||||
* [Advanced topics](#advanced-topics)
|
||||
* [Performance](#performance)
|
||||
@@ -58,7 +55,6 @@ Table of Contents
|
||||
* [Preview window](#preview-window)
|
||||
* [Tips](#tips)
|
||||
* [Respecting .gitignore](#respecting-gitignore)
|
||||
* [git ls-tree for fast traversal](#git-ls-tree-for-fast-traversal)
|
||||
* [Fish shell](#fish-shell)
|
||||
* [Related projects](#related-projects)
|
||||
* [<a href="LICENSE">License</a>](#license)
|
||||
@@ -106,111 +102,55 @@ git clone --depth 1 https://github.com/junegunn/fzf.git ~/.fzf
|
||||
~/.fzf/install
|
||||
```
|
||||
|
||||
### As Vim plugin
|
||||
### Using Linux package managers
|
||||
|
||||
Once you have fzf installed, you can enable it inside Vim simply by adding the
|
||||
directory to `&runtimepath` in your Vim configuration file as follows:
|
||||
| Distro | Command |
|
||||
| --- | --- |
|
||||
| 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
|
||||
" 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 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
|
||||
```
|
||||
Shell extensions (key bindings and fuzzy auto-completion) and Vim/Neovim
|
||||
plugin may or may not be enabled by default depending on the package manager.
|
||||
Refer to the package documentation for more information.
|
||||
|
||||
### Windows
|
||||
|
||||
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
|
||||
|
||||
```sh
|
||||
choco install fzf
|
||||
```
|
||||
|
||||
or a [Scoop package][scoop]:
|
||||
|
||||
[scoop]: https://github.com/ScoopInstaller/Main/blob/master/bucket/fzf.json
|
||||
|
||||
```sh
|
||||
scoop install fzf
|
||||
```
|
||||
|
||||
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.
|
||||
Known issues and limitations on Windows can be found on [the wiki
|
||||
page][windows-wiki].
|
||||
|
||||
[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
|
||||
-------------
|
||||
@@ -221,6 +161,7 @@ method used.
|
||||
|
||||
- git: `cd ~/.fzf && git pull && ./install`
|
||||
- brew: `brew update; brew reinstall fzf`
|
||||
- macports: `sudo port upgrade fzf`
|
||||
- chocolatey: `choco upgrade fzf`
|
||||
- vim-plug: `:PlugUpdate fzf`
|
||||
|
||||
@@ -381,10 +322,6 @@ If you're on a tmux session, you can start fzf in a split pane by setting
|
||||
`FZF_TMUX` to 1, and change the height of the pane with `FZF_TMUX_HEIGHT`
|
||||
(e.g. `20`, `50%`).
|
||||
|
||||
If you use vi mode on bash, you need to add `set -o vi` *before* `source
|
||||
~/.fzf.bash` in your .bashrc, so that it correctly sets up key bindings for vi
|
||||
mode.
|
||||
|
||||
More tips can be found on [the wiki page](https://github.com/junegunn/fzf/wiki/Configuring-shell-key-bindings).
|
||||
|
||||
Fuzzy completion for bash and zsh
|
||||
@@ -468,17 +405,83 @@ _fzf_compgen_path() {
|
||||
_fzf_compgen_dir() {
|
||||
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
|
||||
|
||||
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
|
||||
commands as well as follows.
|
||||
commands as well by using `_fzf_setup_completion` helper function.
|
||||
|
||||
```sh
|
||||
complete -F _fzf_path_completion -o default -o bashdefault ag
|
||||
complete -F _fzf_dir_completion -o default -o bashdefault tree
|
||||
# usage: _fzf_setup_completion path|dir|var|alias|host COMMANDS...
|
||||
_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
|
||||
@@ -524,7 +527,8 @@ See *KEY BINDINGS* section of the man page for details.
|
||||
### Preview window
|
||||
|
||||
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
|
||||
# {} is replaced to the single-quoted string of the focused line
|
||||
@@ -539,37 +543,50 @@ important that the command finishes quickly.
|
||||
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.
|
||||
|
||||
- Bat: https://github.com/sharkdp/bat
|
||||
- Highlight: http://www.andre-simon.de/doku/highlight/en/highlight.php
|
||||
- CodeRay: http://coderay.rubychan.de/
|
||||
- Rouge: https://github.com/jneen/rouge
|
||||
|
||||
```bash
|
||||
# Try bat, highlight, coderay, rougify in turn, then fall back to cat
|
||||
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'
|
||||
fzf --preview 'bat --style=numbers --color=always {} | head -500'
|
||||
```
|
||||
|
||||
You can customize the size and position of the preview window using
|
||||
`--preview-window` option. For example,
|
||||
You can customize the size, position, and border of the preview window using
|
||||
`--preview-window` option, and the foreground and background color of it with
|
||||
`--color` option. For example,
|
||||
|
||||
```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]
|
||||
([code](https://gist.github.com/junegunn/8b572b8d4b5eddd8b85e5f4d40f17236)).
|
||||
|
||||
[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
|
||||
----
|
||||
|
||||
@@ -602,45 +619,17 @@ hidden files, use the following command:
|
||||
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 before version 2.6.0 [doesn't allow](https://github.com/fish-shell/fish-shell/issues/1362)
|
||||
reading from STDIN in command substitution, which means simple `vim (fzf)`
|
||||
doesn't work as expected. The workaround for fish 2.5.0 and earlier is to use
|
||||
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
|
||||
`CTRL-T` key binding of fish, unlike those of bash and zsh, will use the last
|
||||
token on the command-line as the root directory for the recursive search. For
|
||||
instance, hitting `CTRL-T` at the end of the following command-line
|
||||
|
||||
```sh
|
||||
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
|
||||
make use of this feature. `$dir` defaults to `.` when the last token is not a
|
||||
@@ -660,4 +649,4 @@ https://github.com/junegunn/fzf/wiki/Related-projects
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2017 Junegunn Choi
|
||||
Copyright (c) 2013-2020 Junegunn Choi
|
||||
|
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 Vim integration
|
||||
Summary
|
||||
:FZF[!]
|
||||
Configuration
|
||||
Examples
|
||||
fzf#run
|
||||
fzf#wrap
|
||||
fzf inside terminal buffer
|
||||
Hide statusline
|
||||
Tips
|
||||
fzf inside terminal buffer
|
||||
Starting fzf in a popup window
|
||||
Hide statusline
|
||||
License
|
||||
|
||||
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
|
||||
|
||||
@@ -27,8 +50,6 @@ more, check out {fzf.vim}{1} project.
|
||||
==============================================================================
|
||||
|
||||
*:FZF*
|
||||
|
||||
If you have set up fzf for Vim, `:FZF` command will be added.
|
||||
>
|
||||
" Look for files under current directory
|
||||
: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
|
||||
:FZF ~
|
||||
|
||||
" With options
|
||||
:FZF --no-sort --reverse --inline-info /tmp
|
||||
" With fzf command-line options
|
||||
:FZF --reverse --info=inline /tmp
|
||||
|
||||
" Bang version starts fzf in fullscreen mode
|
||||
:FZF!
|
||||
@@ -100,6 +121,7 @@ Examples~
|
||||
let g:fzf_layout = { 'window': '10new' }
|
||||
|
||||
" Customize fzf colors to match your color scheme
|
||||
" - fzf#wrap translates this to a set of `--color` options
|
||||
let g:fzf_colors =
|
||||
\ { 'fg': ['fg', 'Normal'],
|
||||
\ 'bg': ['bg', 'Normal'],
|
||||
@@ -115,68 +137,64 @@ Examples~
|
||||
\ 'spinner': ['fg', 'Label'],
|
||||
\ 'header': ['fg', 'Comment'] }
|
||||
|
||||
" Enable per-command history.
|
||||
" CTRL-N and CTRL-P will be automatically bound to next-history and
|
||||
" previous-history instead of down and up. If you don't like the change,
|
||||
" explicitly bind the keys to down and up in your $FZF_DEFAULT_OPTS.
|
||||
" Enable per-command history
|
||||
" - History files will be stored in the specified directory
|
||||
" - When set, CTRL-N and CTRL-P will be bound to 'next-history' and
|
||||
" 'previous-history' instead of 'down' and 'up'.
|
||||
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
|
||||
dictionary argument. At the very least, specify `sink` option to tell what it
|
||||
should do with the selected entry.
|
||||
dictionary argument, a spec, and starts fzf process accordingly. At the very
|
||||
least, specify `sink` option to tell what it should do with the selected
|
||||
entry.
|
||||
>
|
||||
call fzf#run({'sink': 'e'})
|
||||
<
|
||||
Without `source`, fzf will use find command (or `$FZF_DEFAULT_COMMAND` if
|
||||
defined) to list the files under the current directory. When you select one,
|
||||
it will open it with `:e` command. If you want to open it in a new tab, you
|
||||
can pass `:tabedit` command instead as the sink.
|
||||
We haven't specified the `source`, so this is equivalent to starting fzf on
|
||||
command line without standard input pipe; fzf will use find command (or
|
||||
`$FZF_DEFAULT_COMMAND` if defined) to list the files under the current
|
||||
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'})
|
||||
<
|
||||
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
|
||||
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'})
|
||||
<
|
||||
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.
|
||||
>
|
||||
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
|
||||
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'})
|
||||
<
|
||||
`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
|
||||
buffers as the source.
|
||||
>
|
||||
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.
|
||||
array as the source. In the next example, we pass the names of color schemes
|
||||
as the source to implement a color scheme selector.
|
||||
>
|
||||
call fzf#run({'source': map(split(globpath(&rtp, 'colors/*.vim')),
|
||||
\ 'fnamemodify(v:val, ":t:r")'),
|
||||
\ '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` | list | Vim list as input to fzf
|
||||
`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
|
||||
`options` | string/list | Options to fzf
|
||||
`dir` | string | Working directory
|
||||
`up` / `down` / `left` / `right` | number/string | Use tmux pane with the given size (e.g. `20` , `50%` )
|
||||
`window` (Vim 8 / Neovim) | string | Command to open fzf window (e.g. `vertical aboveleft 30new` )
|
||||
`launcher` | string | External terminal emulator to start fzf with (GVim only)
|
||||
`launcher` | funcref | Function for generating `launcher` string (GVim only)
|
||||
---------------------------+---------------+--------------------------------------------------------------
|
||||
`up` / `down` / `left` / `right` | number/string | (Layout) Window position and size (e.g. `20` , `50%` )
|
||||
`window` (Vim 8 / Neovim) | string | (Layout) Command to open fzf window (e.g. `vertical aboveleft 30new` )
|
||||
`window` (Vim 8 / Neovim) | dict | (Layout) Popup window settings (e.g. `{'width': 0.9, 'height': 0.6}` )
|
||||
---------------------------+---------------+----------------------------------------------------------------------
|
||||
|
||||
`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
|
||||
issues on different platforms.
|
||||
should suffice, but prefer to use list type to avoid escaping issues.
|
||||
>
|
||||
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
|
||||
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.
|
||||
*fzf#wrap*
|
||||
|
||||
By "wrapping" your options dictionary with `fzf#wrap` before passing it to
|
||||
`fzf#run`, you can make your command also support the options.
|
||||
We have seen that several aspects of `:FZF` command can be configured with a
|
||||
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:
|
||||
" fzf#wrap([name string,] [opts dict,] [fullscreen boolean])
|
||||
echo fzf#wrap({'source': 'ls'})
|
||||
<
|
||||
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
|
||||
" and opens fzf according to g:fzf_layout setting.
|
||||
command! Buffers call fzf#run(fzf#wrap(
|
||||
\ {'source': map(range(1, bufnr('$')), 'bufname(v:val)')}))
|
||||
To make it easier to use, let's define `LS` command.
|
||||
>
|
||||
command! LS call fzf#run(fzf#wrap({'source': 'ls'}))
|
||||
<
|
||||
Type `:LS` and see how it works.
|
||||
|
||||
" 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))
|
||||
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>).
|
||||
>
|
||||
" 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.
|
||||
>
|
||||
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
|
||||
(`:terminal`) and fzf will start in a terminal buffer in the following cases:
|
||||
|
||||
- On Neovim
|
||||
- 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%'}`
|
||||
|
||||
|
||||
< 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*
|
||||
|
||||
When fzf starts in a terminal buffer, you may want to hide the statusline of
|
||||
the containing buffer.
|
||||
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.
|
||||
>
|
||||
autocmd! FileType fzf
|
||||
autocmd FileType fzf set laststatus=0 noshowmode noruler
|
||||
\| autocmd BufLeave <buffer> set laststatus=2 showmode ruler
|
||||
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 *fzf-license*
|
||||
@@ -259,7 +343,7 @@ LICENSE *fzf-license*
|
||||
|
||||
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:
|
||||
|
22
go.mod
22
go.mod
@@ -1,19 +1,15 @@
|
||||
module github.com/junegunn/fzf
|
||||
|
||||
require (
|
||||
github.com/gdamore/encoding v0.0.0-20151215212835-b23993cbb635 // indirect
|
||||
github.com/gdamore/tcell v0.0.0-20170915061752-0a0db94084df
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e // indirect
|
||||
github.com/jtolds/gls v4.2.1+incompatible // indirect
|
||||
github.com/lucasb-eyer/go-colorful v0.0.0-20170223221042-c900de9dbbc7 // indirect
|
||||
github.com/mattn/go-isatty v0.0.0-20160806122752-66b8e73f3f5c
|
||||
github.com/mattn/go-runewidth v0.0.0-20170201023540-14207d285c6c
|
||||
github.com/mattn/go-shellwords v1.0.3
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d // 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
|
||||
github.com/gdamore/tcell v1.3.0
|
||||
github.com/lucasb-eyer/go-colorful v1.0.3 // indirect
|
||||
github.com/mattn/go-isatty v0.0.12
|
||||
github.com/mattn/go-runewidth v0.0.8
|
||||
github.com/mattn/go-shellwords v1.0.9
|
||||
github.com/saracen/walker v0.0.0-20191201085201-324a081bae7e
|
||||
golang.org/x/crypto v0.0.0-20200128174031-69ecbb4d6d5d
|
||||
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5
|
||||
golang.org/x/text v0.3.2 // indirect
|
||||
)
|
||||
|
||||
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/gdamore/encoding v0.0.0-20151215212835-b23993cbb635/go.mod h1:yrQYJKKDTrHmbYxI7CYi+/hbdiDT2m4Hj+t0ikCjsrQ=
|
||||
github.com/gdamore/tcell v0.0.0-20170915061752-0a0db94084df h1:tLS1QD2puA1USLvkEnGfOt+Zp2ijtNIK3z2YFaIZZR4=
|
||||
github.com/gdamore/tcell v0.0.0-20170915061752-0a0db94084df/go.mod h1:tqyG50u7+Ctv1w5VX67kLzKcj9YXR/JSBZQq/+mLl1A=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e h1:JKmoR8x90Iww1ks85zJ1lfDGgIiMDuIptTOhJq+zKyg=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/jtolds/gls v4.2.1+incompatible h1:fSuqC+Gmlu6l/ZYAoZzx2pyucC8Xza35fpRVWLVmUEE=
|
||||
github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||
github.com/lucasb-eyer/go-colorful v0.0.0-20170223221042-c900de9dbbc7 h1:G52I+Gk/wPD4HKvKT0Vxxp9OUPxqKs3OK6rffSPtNkA=
|
||||
github.com/lucasb-eyer/go-colorful v0.0.0-20170223221042-c900de9dbbc7/go.mod h1:NXg0ArsFk0Y01623LgUqoqcouGDB+PwCCQlrwrG6xJ4=
|
||||
github.com/mattn/go-isatty v0.0.0-20160806122752-66b8e73f3f5c h1:3nKFouDdpgGUV/uerJcYWH45ZbJzX0SiVWfTgmUeTzc=
|
||||
github.com/mattn/go-isatty v0.0.0-20160806122752-66b8e73f3f5c/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
github.com/mattn/go-runewidth v0.0.0-20170201023540-14207d285c6c h1:eFzthqtg3W6Pihj3DMTXLAF4f+ge5r5Ie5g6HLIZAF0=
|
||||
github.com/mattn/go-runewidth v0.0.0-20170201023540-14207d285c6c/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||
github.com/mattn/go-shellwords v1.0.3 h1:K/VxK7SZ+cvuPgFSLKi5QPI9Vr/ipOf4C1gN+ntueUk=
|
||||
github.com/mattn/go-shellwords v1.0.3/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||
github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c h1:Ho+uVpkel/udgjbwB5Lktg9BtvJSh2DT0Hi6LPSyI2w=
|
||||
github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s=
|
||||
golang.org/x/crypto v0.0.0-20170728183002-558b6879de74 h1:/0jf0Cx3u07Ma4EzUA6NIGuvk9hb3Br6i9V8atthkwk=
|
||||
golang.org/x/crypto v0.0.0-20170728183002-558b6879de74/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/sys v0.0.0-20190509141414-a5b02f93d862 h1:rM0ROo5vb9AdYJi1110yjWGMej9ITfKddS89P3Fkhug=
|
||||
golang.org/x/sys v0.0.0-20190509141414-a5b02f93d862/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.0.0-20170530162606-4ee4af566555 h1:pjwO9HxObpgZBurDvTLFbSinaf3+cKpTAjVfiGaHwrI=
|
||||
golang.org/x/text v0.0.0-20170530162606-4ee4af566555/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
|
||||
github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko=
|
||||
github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
|
||||
github.com/gdamore/tcell v1.3.0 h1:r35w0JBADPZCVQijYebl6YMWWtHRqVEGt7kL2eBADRM=
|
||||
github.com/gdamore/tcell v1.3.0/go.mod h1:Hjvr+Ofd+gLglo7RYKxxnzCBmev3BzsS67MebKS4zMM=
|
||||
github.com/lucasb-eyer/go-colorful v1.0.2 h1:mCMFu6PgSozg9tDNMMK3g18oJBX7oYGrC09mS6CXfO4=
|
||||
github.com/lucasb-eyer/go-colorful v1.0.2/go.mod h1:0MS4r+7BZKSJ5mw4/S5MPN+qHFF1fYclkSPilDOKW0s=
|
||||
github.com/lucasb-eyer/go-colorful v1.0.3 h1:QIbQXiugsb+q10B+MI+7DI1oQLdmnep86tWFlaaUAac=
|
||||
github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/p7Y=
|
||||
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||
github.com/mattn/go-runewidth v0.0.8 h1:3tS41NlGYSmhhe/8fhGRzc+z3AYCw1Fe1WAyLuujKs0=
|
||||
github.com/mattn/go-runewidth v0.0.8/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
github.com/mattn/go-shellwords v1.0.9 h1:eaB5JspOwiKKcHdqcjbfe5lA9cNn/4NRRtddXJCimqk=
|
||||
github.com/mattn/go-shellwords v1.0.9/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
|
||||
github.com/saracen/walker v0.0.0-20191201085201-324a081bae7e h1:NO86zOn5ScSKW8wRbMaSIcjDZUFpWdCQQnexRqZ9h9A=
|
||||
github.com/saracen/walker v0.0.0-20191201085201-324a081bae7e/go.mod h1:G0Z6yVPru183i2MuRJx1DcR4dgIZtLcTdaaE/pC1BJU=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20200128174031-69ecbb4d6d5d h1:9FCpayM9Egr1baVnV1SX0H87m+XB0B8S0hAMi99X/3U=
|
||||
golang.org/x/crypto v0.0.0-20200128174031-69ecbb4d6d5d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI=
|
||||
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
|
||||
|
||||
version=0.19.0
|
||||
version=0.21.0
|
||||
auto_completion=
|
||||
key_bindings=
|
||||
update_config=2
|
||||
@@ -196,6 +196,8 @@ case "$archi" in
|
||||
MINGW*\ *64) download fzf-$version-windows_${binary_arch:-amd64}.zip ;;
|
||||
MSYS*\ *86) download fzf-$version-windows_${binary_arch:-386}.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 ;;
|
||||
esac
|
||||
|
||||
|
73
install.ps1
Normal file
73
install.ps1
Normal file
@@ -0,0 +1,73 @@
|
||||
$version="0.21.0"
|
||||
|
||||
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
|
||||
|
||||
import "github.com/junegunn/fzf/src"
|
||||
import (
|
||||
"github.com/junegunn/fzf/src"
|
||||
"github.com/junegunn/fzf/src/protector"
|
||||
)
|
||||
|
||||
var revision string
|
||||
|
||||
func main() {
|
||||
protector.Protect()
|
||||
fzf.Run(fzf.ParseOptions(), revision)
|
||||
}
|
||||
|
@@ -1,7 +1,7 @@
|
||||
.ig
|
||||
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
|
||||
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
|
||||
THE SOFTWARE.
|
||||
..
|
||||
.TH fzf-tmux 1 "Nov 2019" "fzf 0.19.0" "fzf-tmux - open fzf in tmux split pane"
|
||||
.TH fzf-tmux 1 "Mar 2020" "fzf 0.21.0" "fzf-tmux - open fzf in tmux split pane"
|
||||
|
||||
.SH NAME
|
||||
fzf-tmux - open fzf in tmux split pane
|
||||
|
153
man/man1/fzf.1
153
man/man1/fzf.1
@@ -1,7 +1,7 @@
|
||||
.ig
|
||||
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
|
||||
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
|
||||
THE SOFTWARE.
|
||||
..
|
||||
.TH fzf 1 "Nov 2019" "fzf 0.19.0" "fzf - a command-line fuzzy finder"
|
||||
.TH fzf 1 "Mar 2020" "fzf 0.21.0" "fzf - a command-line fuzzy finder"
|
||||
|
||||
.SH NAME
|
||||
fzf - a command-line fuzzy finder
|
||||
@@ -130,6 +130,10 @@ the details.
|
||||
.B "--cycle"
|
||||
Enable cyclic scroll
|
||||
.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"
|
||||
Disable horizontal scroll
|
||||
.TP
|
||||
@@ -178,8 +182,16 @@ Choose the layout (default: default)
|
||||
A synonym for \fB--layout=reverse\fB
|
||||
|
||||
.TP
|
||||
.B "--border"
|
||||
Draw border above and below the finder
|
||||
.BI "--border" [=STYLE]
|
||||
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
|
||||
.B "--no-unicode"
|
||||
@@ -231,6 +243,12 @@ A synonym for \fB--info=hidden\fB
|
||||
.BI "--prompt=" "STR"
|
||||
Input prompt (default: '> ')
|
||||
.TP
|
||||
.BI "--pointer=" "STR"
|
||||
Pointer to the current line (default: '>')
|
||||
.TP
|
||||
.BI "--marker=" "STR"
|
||||
Multi-select marker (default: '>')
|
||||
.TP
|
||||
.BI "--header=" "STR"
|
||||
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
|
||||
@@ -262,23 +280,25 @@ format.
|
||||
\fBdark \fRColor scheme for dark 256-color terminal
|
||||
\fBlight \fRColor scheme for light 256-color terminal
|
||||
\fB16 \fRColor scheme for 16-color terminal
|
||||
\fBbw \fRNo colors
|
||||
\fBbw \fRNo colors (equivalent to \fB--no-color\fR)
|
||||
|
||||
.B COLOR:
|
||||
\fBfg \fRText
|
||||
\fBbg \fRBackground
|
||||
\fBhl \fRHighlighted substrings
|
||||
\fBfg+ \fRText (current line)
|
||||
\fBbg+ \fRBackground (current line)
|
||||
\fBgutter \fRGutter on the left (defaults to \fBbg+\fR)
|
||||
\fBhl+ \fRHighlighted substrings (current line)
|
||||
\fBinfo \fRInfo
|
||||
\fBborder \fRBorder of the preview window and horizontal separators (\fB--border\fR)
|
||||
\fBprompt \fRPrompt
|
||||
\fBpointer \fRPointer to the current line
|
||||
\fBmarker \fRMulti-select marker
|
||||
\fBspinner \fRStreaming input indicator
|
||||
\fBheader \fRHeader
|
||||
\fBfg \fRText
|
||||
\fBbg \fRBackground
|
||||
\fBpreview-fg \fRPreview window text
|
||||
\fBpreview-bg \fRPreview window background
|
||||
\fBhl \fRHighlighted substrings
|
||||
\fBfg+ \fRText (current line)
|
||||
\fBbg+ \fRBackground (current line)
|
||||
\fBgutter \fRGutter on the left (defaults to \fBbg+\fR)
|
||||
\fBhl+ \fRHighlighted substrings (current line)
|
||||
\fBinfo \fRInfo line (match counters)
|
||||
\fBborder \fRBorder around the window (\fB--border\fR and \fB--preview\fR)
|
||||
\fBprompt \fRPrompt
|
||||
\fBpointer \fRPointer to the current line
|
||||
\fBmarker \fRMulti-select marker
|
||||
\fBspinner \fRStreaming input indicator
|
||||
\fBheader \fRHeader
|
||||
|
||||
.B EXAMPLES:
|
||||
|
||||
@@ -592,6 +612,8 @@ e.g.
|
||||
.br
|
||||
\fIend\fR
|
||||
.br
|
||||
\fIinsert\fR
|
||||
.br
|
||||
\fIpgup\fR (\fIpage-up\fR)
|
||||
.br
|
||||
\fIpgdn\fR (\fIpage-down\fR)
|
||||
@@ -623,60 +645,63 @@ or any single character
|
||||
.SS AVAILABLE ACTIONS:
|
||||
A key or an event can be bound to one or more of the following actions.
|
||||
|
||||
\fBACTION: DEFAULT BINDINGS (NOTES):
|
||||
\fBabort\fR \fIctrl-c ctrl-g ctrl-q esc\fR
|
||||
\fBaccept\fR \fIenter double-click\fR
|
||||
\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-delete-char\fR \fIctrl-h bspace\fR
|
||||
\fBbackward-kill-word\fR \fIalt-bs\fR
|
||||
\fBbackward-word\fR \fIalt-b shift-left\fR
|
||||
\fBbeginning-of-line\fR \fIctrl-a home\fR
|
||||
\fBcancel\fR (clears query string if not empty, aborts fzf otherwise)
|
||||
\fBclear-screen\fR \fIctrl-l\fR
|
||||
\fBdelete-char\fR \fIdel\fR
|
||||
\fBdelete-char/eof\fR \fIctrl-d\fR
|
||||
\fBdeselect-all\fR
|
||||
\fBdown\fR \fIctrl-j ctrl-n down\fR
|
||||
\fBend-of-line\fR \fIctrl-e end\fR
|
||||
\fBexecute(...)\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-word\fR \fIalt-f shift-right\fR
|
||||
\fBACTION: DEFAULT BINDINGS (NOTES):
|
||||
\fBabort\fR \fIctrl-c ctrl-g ctrl-q esc\fR
|
||||
\fBaccept\fR \fIenter double-click\fR
|
||||
\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-delete-char\fR \fIctrl-h bspace\fR
|
||||
\fBbackward-delete-char/eof\fR (same as \fBbackward-delete-char\fR except aborts fzf if query is empty)
|
||||
\fBbackward-kill-word\fR \fIalt-bs\fR
|
||||
\fBbackward-word\fR \fIalt-b shift-left\fR
|
||||
\fBbeginning-of-line\fR \fIctrl-a home\fR
|
||||
\fBcancel\fR (clear query string if not empty, abort fzf otherwise)
|
||||
\fBclear-screen\fR \fIctrl-l\fR
|
||||
\fBclear-selection\fR (clear multi-selection)
|
||||
\fBclear-query\fR (clear query string)
|
||||
\fBdelete-char\fR \fIdel\fR
|
||||
\fBdelete-char/eof\fR \fIctrl-d\fR (same as \fBdelete-char\fR except aborts fzf if query is empty)
|
||||
\fBdeselect-all\fR (deselect all matches)
|
||||
\fBdown\fR \fIctrl-j ctrl-n down\fR
|
||||
\fBend-of-line\fR \fIctrl-e end\fR
|
||||
\fBexecute(...)\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-word\fR \fIalt-f shift-right\fR
|
||||
\fBignore\fR
|
||||
\fBjump\fR (EasyMotion-like 2-keystroke movement)
|
||||
\fBjump-accept\fR (jump and accept)
|
||||
\fBjump\fR (EasyMotion-like 2-keystroke movement)
|
||||
\fBjump-accept\fR (jump and accept)
|
||||
\fBkill-line\fR
|
||||
\fBkill-word\fR \fIalt-d\fR
|
||||
\fBnext-history\fR (\fIctrl-n\fR on \fB--history\fR)
|
||||
\fBpage-down\fR \fIpgdn\fR
|
||||
\fBpage-up\fR \fIpgup\fR
|
||||
\fBkill-word\fR \fIalt-d\fR
|
||||
\fBnext-history\fR (\fIctrl-n\fR on \fB--history\fR)
|
||||
\fBpage-down\fR \fIpgdn\fR
|
||||
\fBpage-up\fR \fIpgup\fR
|
||||
\fBhalf-page-down\fR
|
||||
\fBhalf-page-up\fR
|
||||
\fBpreview-down\fR \fIshift-down\fR
|
||||
\fBpreview-up\fR \fIshift-up\fR
|
||||
\fBpreview-down\fR \fIshift-down\fR
|
||||
\fBpreview-up\fR \fIshift-up\fR
|
||||
\fBpreview-page-down\fR
|
||||
\fBpreview-page-up\fR
|
||||
\fBprevious-history\fR (\fIctrl-p\fR on \fB--history\fR)
|
||||
\fBprint-query\fR (print query and exit)
|
||||
\fBreload(...)\fR (see below for the details)
|
||||
\fBreplace-query\fR (replace query string with the current selection)
|
||||
\fBselect-all\fR
|
||||
\fBtoggle\fR (\fIright-click\fR)
|
||||
\fBtoggle-all\fR
|
||||
\fBtoggle+down\fR \fIctrl-i (tab)\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)
|
||||
\fBprevious-history\fR (\fIctrl-p\fR on \fB--history\fR)
|
||||
\fBprint-query\fR (print query and exit)
|
||||
\fBreload(...)\fR (see below for the details)
|
||||
\fBreplace-query\fR (replace query string with the current selection)
|
||||
\fBselect-all\fR (select all matches)
|
||||
\fBtoggle\fR (\fIright-click\fR)
|
||||
\fBtoggle-all\fR (toggle all matches)
|
||||
\fBtoggle+down\fR \fIctrl-i (tab)\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-preview\fR
|
||||
\fBtoggle-preview-wrap\fR
|
||||
\fBtoggle-sort\fR
|
||||
\fBtoggle+up\fR \fIbtab (shift-tab)\fR
|
||||
\fBtop\fR (move to the top result)
|
||||
\fBunix-line-discard\fR \fIctrl-u\fR
|
||||
\fBunix-word-rubout\fR \fIctrl-w\fR
|
||||
\fBup\fR \fIctrl-k ctrl-p up\fR
|
||||
\fByank\fR \fIctrl-y\fR
|
||||
\fBtoggle+up\fR \fIbtab (shift-tab)\fR
|
||||
\fBtop\fR (move to the top result)
|
||||
\fBunix-line-discard\fR \fIctrl-u\fR
|
||||
\fBunix-word-rubout\fR \fIctrl-w\fR
|
||||
\fBup\fR \fIctrl-k ctrl-p up\fR
|
||||
\fByank\fR \fIctrl-y\fR
|
||||
|
||||
.SS ACTION COMPOSITION
|
||||
|
||||
|
206
plugin/fzf.vim
206
plugin/fzf.vim
@@ -49,18 +49,23 @@ if s:is_win
|
||||
|
||||
" Use utf-8 for fzf.vim commands
|
||||
" 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)
|
||||
let use_chcp = executable('sed')
|
||||
return map([
|
||||
\ '@echo off',
|
||||
\ 'setlocal enabledelayedexpansion']
|
||||
\ + (use_chcp ? [
|
||||
\ 'for /f "usebackq" %%a in (`chcp ^| sed "s/[^0-9]//gp"`) do set origchcp=%%a',
|
||||
\ 'chcp 65001 > nul'] : [])
|
||||
\ + (has('gui_running') ? ['set TERM= > nul'] : [])
|
||||
\ + (type(a:cmds) == type([]) ? a:cmds : [a:cmds])
|
||||
\ + (use_chcp ? ['chcp !origchcp! > nul'] : [])
|
||||
\ + ['endlocal'],
|
||||
\ 'v:val."\r"')
|
||||
\ '<SID>enc_to_cp(v:val."\r")')
|
||||
endfunction
|
||||
else
|
||||
let s:term_marker = ";#FZF"
|
||||
@@ -72,6 +77,10 @@ else
|
||||
function! s:wrap_cmds(cmds)
|
||||
return a:cmds
|
||||
endfunction
|
||||
|
||||
function! s:enc_to_cp(str)
|
||||
return a:str
|
||||
endfunction
|
||||
endif
|
||||
|
||||
function! s:shellesc_cmd(arg)
|
||||
@@ -83,7 +92,7 @@ function! s:shellesc_cmd(arg)
|
||||
endfunction
|
||||
|
||||
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$'
|
||||
return s:shellesc_cmd(a:arg)
|
||||
endif
|
||||
@@ -110,30 +119,41 @@ let s:default_layout = { 'down': '~40%' }
|
||||
let s:layout_keys = ['window', 'up', 'down', 'left', 'right']
|
||||
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:cpo_save = &cpo
|
||||
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()
|
||||
if !exists('s:exec')
|
||||
if executable(s:fzf_go)
|
||||
let s:exec = s:fzf_go
|
||||
elseif executable('fzf')
|
||||
let s:exec = 'fzf'
|
||||
elseif s:is_win && !has('win32unix')
|
||||
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'
|
||||
elseif input('fzf executable not found. Download binary? (y/n) ') =~? '^y'
|
||||
redraw
|
||||
echo
|
||||
call s:warn('Downloading fzf binary. Please wait ...')
|
||||
let s:installed = 1
|
||||
call system(s:install.' --bin')
|
||||
call fzf#install()
|
||||
return s:fzf_exec()
|
||||
else
|
||||
redraw
|
||||
@@ -144,7 +164,7 @@ function! s:fzf_exec()
|
||||
endfunction
|
||||
|
||||
function! s:tmux_enabled()
|
||||
if has('gui_running')
|
||||
if has('gui_running') || !exists('$TMUX')
|
||||
return 0
|
||||
endif
|
||||
|
||||
@@ -153,10 +173,16 @@ function! s:tmux_enabled()
|
||||
endif
|
||||
|
||||
let s:tmux = 0
|
||||
if exists('$TMUX') && executable(s:fzf_tmux)
|
||||
let output = system('tmux -V')
|
||||
let s:tmux = !v:shell_error && output >= 'tmux 1.7'
|
||||
if !executable(s:fzf_tmux)
|
||||
if executable('fzf-tmux')
|
||||
let s:fzf_tmux = 'fzf-tmux'
|
||||
else
|
||||
return 0
|
||||
endif
|
||||
endif
|
||||
|
||||
let output = system('tmux -V')
|
||||
let s:tmux = !v:shell_error && output >= 'tmux 1.7'
|
||||
return s:tmux
|
||||
endfunction
|
||||
|
||||
@@ -247,7 +273,7 @@ function! s:common_sink(action, lines) abort
|
||||
endfunction
|
||||
|
||||
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 pat = gui ? '^#[a-f0-9]\+' : '^[0-9]\+$'
|
||||
for group in a:000
|
||||
@@ -342,19 +368,21 @@ function! fzf#wrap(...)
|
||||
endfunction
|
||||
|
||||
function! s:use_sh()
|
||||
let [shell, shellslash] = [&shell, &shellslash]
|
||||
let [shell, shellslash, shellcmdflag, shellxquote] = [&shell, &shellslash, &shellcmdflag, &shellxquote]
|
||||
if s:is_win
|
||||
set shell=cmd.exe
|
||||
set noshellslash
|
||||
let &shellcmdflag = has('nvim') ? '/s /c' : '/c'
|
||||
let &shellxquote = has('nvim') ? '"' : '('
|
||||
else
|
||||
set shell=sh
|
||||
endif
|
||||
return [shell, shellslash]
|
||||
return [shell, shellslash, shellcmdflag, shellxquote]
|
||||
endfunction
|
||||
|
||||
function! fzf#run(...) abort
|
||||
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 temps = { 'result': s:fzf_tempname() }
|
||||
@@ -372,12 +400,6 @@ try
|
||||
let dict.dir = fnamemodify(dict.dir, ':p')
|
||||
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')
|
||||
let source = dict.source
|
||||
let type = type(source)
|
||||
@@ -385,7 +407,7 @@ try
|
||||
let prefix = '( '.source.' )|'
|
||||
elseif type == 3
|
||||
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).'|'
|
||||
else
|
||||
throw 'Invalid source type'
|
||||
@@ -424,7 +446,7 @@ try
|
||||
call s:callback(dict, lines)
|
||||
return lines
|
||||
finally
|
||||
let [&shell, &shellslash] = [shell, shellslash]
|
||||
let [&shell, &shellslash, &shellcmdflag, &shellxquote] = [shell, shellslash, shellcmdflag, shellxquote]
|
||||
endtry
|
||||
endfunction
|
||||
|
||||
@@ -620,9 +642,11 @@ function! s:calc_size(max, val, dict)
|
||||
endif
|
||||
|
||||
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, '--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
|
||||
endfunction
|
||||
|
||||
@@ -639,7 +663,14 @@ function! s:split(dict)
|
||||
let ppos = s:getpos()
|
||||
try
|
||||
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)
|
||||
execute (tabpagenr()-1).'tabnew'
|
||||
else
|
||||
@@ -731,6 +762,9 @@ function! s:execute_term(dict, command, temps) abort
|
||||
if has('nvim')
|
||||
call termopen(command, fzf)
|
||||
else
|
||||
if !len(&bufhidden)
|
||||
setlocal bufhidden=hide
|
||||
endif
|
||||
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
|
||||
call term_wait(fzf.buf, 20)
|
||||
@@ -787,6 +821,100 @@ function! s:callback(dict, lines) abort
|
||||
endif
|
||||
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})
|
||||
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 bwipeout! '..buf
|
||||
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 = {
|
||||
\ 'ctrl-t': 'tab split',
|
||||
\ 'ctrl-x': 'split',
|
||||
|
@@ -16,7 +16,7 @@ if ! declare -f _fzf_compgen_path > /dev/null; then
|
||||
_fzf_compgen_path() {
|
||||
echo "$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@^\./@@'
|
||||
}
|
||||
fi
|
||||
@@ -24,7 +24,7 @@ fi
|
||||
if ! declare -f _fzf_compgen_dir > /dev/null; then
|
||||
_fzf_compgen_dir() {
|
||||
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@^\./@@'
|
||||
}
|
||||
fi
|
||||
@@ -34,9 +34,16 @@ fi
|
||||
# To redraw line after fzf closes (printf '\e[5n')
|
||||
bind '"\e[0n": redraw-current-line'
|
||||
|
||||
__fzfcmd_complete() {
|
||||
[ -n "$TMUX_PANE" ] && [ "${FZF_TMUX:-0}" != 0 ] && [ ${LINES:-40} -gt 15 ] &&
|
||||
echo "fzf-tmux -d${FZF_TMUX_HEIGHT:-40%}" || echo "fzf"
|
||||
__fzf_comprun() {
|
||||
if [ "$(type -t _fzf_comprun 2>&1)" = function ]; then
|
||||
_fzf_comprun "$@"
|
||||
elif [ -n "$TMUX_PANE" ] && [ "${FZF_TMUX:-0}" != 0 ] && [ ${LINES:-40} -gt 15 ]; then
|
||||
shift
|
||||
fzf-tmux -d "${FZF_TMUX_HEIGHT:-40%}" "$@"
|
||||
else
|
||||
shift
|
||||
fzf "$@"
|
||||
fi
|
||||
}
|
||||
|
||||
__fzf_orig_completion_filter() {
|
||||
@@ -72,6 +79,8 @@ _fzf_opts_completion() {
|
||||
--margin
|
||||
--inline-info
|
||||
--prompt
|
||||
--pointer
|
||||
--marker
|
||||
--header
|
||||
--header-lines
|
||||
--ansi
|
||||
@@ -140,8 +149,7 @@ _fzf_handle_dynamic_completion() {
|
||||
}
|
||||
|
||||
__fzf_generic_path_completion() {
|
||||
local cur base dir leftover matches trigger cmd fzf
|
||||
fzf="$(__fzfcmd_complete)"
|
||||
local cur base dir leftover matches trigger cmd
|
||||
cmd="${COMP_WORDS[0]//[^A-Za-z0-9_=]/_}"
|
||||
COMPREPLY=()
|
||||
trigger=${FZF_COMPLETION_TRIGGER-'**'}
|
||||
@@ -157,7 +165,7 @@ __fzf_generic_path_completion() {
|
||||
leftover=${leftover/#\/}
|
||||
[ -z "$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"
|
||||
done)
|
||||
matches=${matches% }
|
||||
@@ -182,10 +190,30 @@ __fzf_generic_path_completion() {
|
||||
}
|
||||
|
||||
_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"
|
||||
type -t "$post" > /dev/null 2>&1 || post=cat
|
||||
fzf="$(__fzfcmd_complete)"
|
||||
|
||||
cmd="${COMP_WORDS[0]//[^A-Za-z0-9_=]/_}"
|
||||
trigger=${FZF_COMPLETION_TRIGGER-'**'}
|
||||
@@ -193,7 +221,7 @@ _fzf_complete() {
|
||||
if [[ "$cur" == *"$trigger" ]]; then
|
||||
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"
|
||||
if [ -n "$selected" ]; then
|
||||
COMPREPLY=("$selected")
|
||||
@@ -203,8 +231,7 @@ _fzf_complete() {
|
||||
printf '\e[5n'
|
||||
return 0
|
||||
else
|
||||
shift
|
||||
_fzf_handle_dynamic_completion "$cmd" "$@"
|
||||
_fzf_handle_dynamic_completion "$cmd" "${rest[@]}"
|
||||
fi
|
||||
}
|
||||
|
||||
@@ -224,9 +251,9 @@ _fzf_dir_completion() {
|
||||
_fzf_complete_kill() {
|
||||
[ -n "${COMP_WORDS[COMP_CWORD]}" ] && return 1
|
||||
|
||||
local selected fzf
|
||||
fzf="$(__fzfcmd_complete)"
|
||||
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' ' ')
|
||||
local selected
|
||||
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=${selected% }
|
||||
printf '\e[5n'
|
||||
|
||||
if [ -n "$selected" ]; then
|
||||
@@ -235,36 +262,23 @@ _fzf_complete_kill() {
|
||||
fi
|
||||
}
|
||||
|
||||
_fzf_complete_telnet() {
|
||||
_fzf_complete '+m' "$@" < <(
|
||||
command grep -v '^\s*\(#\|$\)' /etc/hosts | command grep -Fv '0.0.0.0' |
|
||||
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}') \
|
||||
_fzf_host_completion() {
|
||||
_fzf_complete +m -- "$@" < <(
|
||||
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 -v '^\s*\(#\|$\)' /etc/hosts | command grep -Fv '0.0.0.0') |
|
||||
awk '{if (length($2) > 0) {print $2}}' | sort -u
|
||||
)
|
||||
}
|
||||
|
||||
_fzf_complete_unset() {
|
||||
_fzf_complete '-m' "$@" < <(
|
||||
_fzf_var_completion() {
|
||||
_fzf_complete -m -- "$@" < <(
|
||||
declare -xp | sed 's/=.*//' | sed 's/.* //'
|
||||
)
|
||||
}
|
||||
|
||||
_fzf_complete_export() {
|
||||
_fzf_complete '-m' "$@" < <(
|
||||
declare -xp | sed 's/=.*//' | sed 's/.* //'
|
||||
)
|
||||
}
|
||||
|
||||
_fzf_complete_unalias() {
|
||||
_fzf_complete '-m' "$@" < <(
|
||||
_fzf_alias_completion() {
|
||||
_fzf_complete -m -- "$@" < <(
|
||||
alias | sed 's/=.*//' | sed 's/.* //'
|
||||
)
|
||||
}
|
||||
@@ -282,7 +296,7 @@ a_cmds="
|
||||
find git grep gunzip gzip hg jar
|
||||
ln ls mv open rm rsync scp
|
||||
svn tar unzip zip"
|
||||
x_cmds="kill ssh telnet unset unalias export"
|
||||
x_cmds="kill"
|
||||
|
||||
# Preserve existing completion
|
||||
eval "$(complete |
|
||||
@@ -319,16 +333,7 @@ for cmd in $d_cmds; do
|
||||
done
|
||||
|
||||
# Kill completion
|
||||
complete -F _fzf_complete_kill -o nospace -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
|
||||
complete -F _fzf_complete_kill -o default -o bashdefault kill
|
||||
|
||||
unset cmd d_cmds a_cmds x_cmds
|
||||
|
||||
@@ -337,17 +342,24 @@ _fzf_setup_completion() {
|
||||
kind=$1
|
||||
fn=_fzf_${1}_completion
|
||||
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
|
||||
fi
|
||||
shift
|
||||
eval "$(complete -p "$@" 2> /dev/null | grep -v "$fn" | __fzf_orig_completion_filter)"
|
||||
for cmd in "$@"; do
|
||||
eval "$(complete -p "$cmd" 2> /dev/null | grep -v "$fn" | __fzf_orig_completion_filter)"
|
||||
case "$kind" in
|
||||
dir) __fzf_defc "$cmd" "$fn" "-o nospace -o dirnames" ;;
|
||||
*) __fzf_defc "$cmd" "$fn" "-o default -o bashdefault" ;;
|
||||
dir) __fzf_defc "$cmd" "$fn" "-o nospace -o dirnames" ;;
|
||||
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
|
||||
done
|
||||
}
|
||||
|
||||
# Environment variables / Aliases / Hosts
|
||||
_fzf_setup_completion 'var' export unset
|
||||
_fzf_setup_completion 'alias' unalias
|
||||
_fzf_setup_completion 'host' ssh telnet
|
||||
|
||||
fi
|
||||
|
@@ -16,7 +16,7 @@ if ! declare -f _fzf_compgen_path > /dev/null; then
|
||||
_fzf_compgen_path() {
|
||||
echo "$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@^\./@@'
|
||||
}
|
||||
fi
|
||||
@@ -24,27 +24,47 @@ fi
|
||||
if ! declare -f _fzf_compgen_dir > /dev/null; then
|
||||
_fzf_compgen_dir() {
|
||||
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@^\./@@'
|
||||
}
|
||||
fi
|
||||
|
||||
###########################################################
|
||||
|
||||
__fzfcmd_complete() {
|
||||
[ -n "$TMUX_PANE" ] && [ "${FZF_TMUX:-0}" != 0 ] && [ ${LINES:-40} -gt 15 ] &&
|
||||
echo "fzf-tmux -d${FZF_TMUX_HEIGHT:-40%}" || echo "fzf"
|
||||
__fzf_comprun() {
|
||||
if [[ "$(type _fzf_comprun 2>&1)" =~ function ]]; then
|
||||
_fzf_comprun "$@"
|
||||
elif [ -n "$TMUX_PANE" ] && [ "${FZF_TMUX:-0}" != 0 ] && [ ${LINES:-40} -gt 15 ]; then
|
||||
shift
|
||||
fzf-tmux -d "${FZF_TMUX_HEIGHT:-40%}" "$@"
|
||||
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() {
|
||||
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
|
||||
lbuf=$2
|
||||
cmd=$(__fzf_extract_command "$lbuf")
|
||||
compgen=$3
|
||||
fzf_opts=$4
|
||||
suffix=$5
|
||||
tail=$6
|
||||
fzf="$(__fzfcmd_complete)"
|
||||
|
||||
setopt localoptions nonomatch
|
||||
eval "base=$base"
|
||||
@@ -55,7 +75,7 @@ __fzf_generic_path_completion() {
|
||||
leftover=${leftover/#\/}
|
||||
[ -z "$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 "
|
||||
done)
|
||||
matches=${matches% }
|
||||
@@ -87,17 +107,37 @@ _fzf_feed_fifo() (
|
||||
)
|
||||
|
||||
_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-$$"
|
||||
fzf_opts=$1
|
||||
lbuf=$2
|
||||
post="${funcstack[2]}_post"
|
||||
lbuf=${rest[0]}
|
||||
cmd=$(__fzf_extract_command "$lbuf")
|
||||
post="${funcstack[1]}_post"
|
||||
type $post > /dev/null 2>&1 || post=cat
|
||||
|
||||
fzf="$(__fzfcmd_complete)"
|
||||
|
||||
_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
|
||||
LBUFFER="$lbuf$matches"
|
||||
fi
|
||||
@@ -106,16 +146,16 @@ _fzf_complete() {
|
||||
}
|
||||
|
||||
_fzf_complete_telnet() {
|
||||
_fzf_complete '+m' "$@" < <(
|
||||
_fzf_complete +m -- "$@" < <(
|
||||
command grep -v '^\s*\(#\|$\)' /etc/hosts | command grep -Fv '0.0.0.0' |
|
||||
awk '{if (length($2) > 0) {print $2}}' | sort -u
|
||||
)
|
||||
}
|
||||
|
||||
_fzf_complete_ssh() {
|
||||
_fzf_complete '+m' "$@" < <(
|
||||
_fzf_complete +m -- "$@" < <(
|
||||
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 -v '^\s*\(#\|$\)' /etc/hosts | command grep -Fv '0.0.0.0') |
|
||||
awk '{if (length($2) > 0) {print $2}}' | sort -u
|
||||
@@ -123,25 +163,25 @@ _fzf_complete_ssh() {
|
||||
}
|
||||
|
||||
_fzf_complete_export() {
|
||||
_fzf_complete '-m' "$@" < <(
|
||||
_fzf_complete -m -- "$@" < <(
|
||||
declare -xp | sed 's/=.*//' | sed 's/.* //'
|
||||
)
|
||||
}
|
||||
|
||||
_fzf_complete_unset() {
|
||||
_fzf_complete '-m' "$@" < <(
|
||||
_fzf_complete -m -- "$@" < <(
|
||||
declare -xp | sed 's/=.*//' | sed 's/.* //'
|
||||
)
|
||||
}
|
||||
|
||||
_fzf_complete_unalias() {
|
||||
_fzf_complete '+m' "$@" < <(
|
||||
_fzf_complete +m -- "$@" < <(
|
||||
alias | sed 's/=.*//'
|
||||
)
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
# http://zsh.sourceforge.net/FAQ/zshfaq03.html
|
||||
@@ -152,7 +192,7 @@ fzf-completion() {
|
||||
return
|
||||
fi
|
||||
|
||||
cmd=${tokens[1]}
|
||||
cmd=$(__fzf_extract_command "$LBUFFER")
|
||||
|
||||
# Explicitly allow for empty trigger.
|
||||
trigger=${FZF_COMPLETION_TRIGGER-'**'}
|
||||
@@ -167,8 +207,7 @@ fzf-completion() {
|
||||
tail=${LBUFFER:$(( ${#LBUFFER} - ${#trigger} ))}
|
||||
# Kill completion (do not require trigger sequence)
|
||||
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 --preview 'echo {}' --preview-window down:3:wrap $FZF_COMPLETION_OPTS" ${=fzf} -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 $FZF_COMPLETION_OPTS --preview 'echo {}' --preview-window down:3:wrap" __fzf_comprun "$cmd" -m | awk '{print $2}' | tr '\n' ' ')
|
||||
if [ -n "$matches" ]; then
|
||||
LBUFFER="$LBUFFER$matches"
|
||||
fi
|
||||
@@ -181,7 +220,7 @@ fzf-completion() {
|
||||
[ -z "${tokens[-1]}" ] && lbuf=$LBUFFER || lbuf=${LBUFFER:0:-${#tokens[-1]}}
|
||||
|
||||
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
|
||||
_fzf_dir_completion "$prefix" "$lbuf"
|
||||
else
|
||||
|
@@ -51,71 +51,57 @@ __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"
|
||||
}
|
||||
|
||||
__fzf_history__() (
|
||||
local line
|
||||
shopt -u nocaseglob nocasematch
|
||||
line=$(
|
||||
HISTTIMEFORMAT= builtin history |
|
||||
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) |
|
||||
command grep '^ *[0-9]') &&
|
||||
if [[ $- =~ H ]]; then
|
||||
sed 's/^ *\([0-9]*\)\** .*/!\1/' <<< "$line"
|
||||
else
|
||||
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"'
|
||||
__fzf_history__() {
|
||||
local output
|
||||
output=$(
|
||||
builtin fc -lnr -2147483648 |
|
||||
last_hist=$(HISTTIMEFORMAT='' builtin history 1) perl -p -l0 -e 'BEGIN { getc; $/ = "\n\t"; $HISTCMD = $ENV{last_hist} + 1 } s/^[ *]//; $_ = $HISTCMD - $. . "\t$_"' |
|
||||
FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} $FZF_DEFAULT_OPTS --tiebreak=index --bind=ctrl-r:toggle-sort $FZF_CTRL_R_OPTS +m --read0" $(__fzfcmd) --query "$READLINE_LINE"
|
||||
) || return
|
||||
READLINE_LINE=${output#*$'\t'}
|
||||
if [ -z "$READLINE_POINT" ]; then
|
||||
echo "$READLINE_LINE"
|
||||
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
|
||||
}
|
||||
|
||||
# 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
|
||||
if __fzf_use_tmux__; then
|
||||
bind -m emacs-standard '"\C-t": " \C-b\C-k \C-u`__fzf_select_tmux__`\e\C-e\C-a\C-y\C-h\C-e\e \C-y\ey\C-x\C-x\C-f"'
|
||||
else
|
||||
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"'
|
||||
fi
|
||||
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
|
||||
bind '"\C-r": " \C-e\C-u\C-y\ey\C-u`__fzf_history__`\e\C-e\er\e^"'
|
||||
|
||||
# ALT-C - cd into the selected directory
|
||||
bind '"\ec": " \C-e\C-u`__fzf_cd__`\e\C-e\er\C-m"'
|
||||
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"'
|
||||
bind -m vi-insert '"\C-r": "\C-z\C-r\C-z"'
|
||||
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
|
||||
# - FIXME: Selected items are attached to the end regardless of cursor position
|
||||
if [ $BASH_VERSINFO -gt 3 ]; then
|
||||
bind -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"'
|
||||
bind -m emacs-standard -x '"\C-t": fzf-file-widget'
|
||||
bind -m vi-command -x '"\C-t": fzf-file-widget'
|
||||
bind -m vi-insert -x '"\C-t": fzf-file-widget'
|
||||
|
||||
# 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 vi-command '"\C-r": "i\C-r"'
|
||||
|
||||
# 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"'
|
||||
bind -m emacs-standard -x '"\C-r": __fzf_history__'
|
||||
bind -m vi-command -x '"\C-r": __fzf_history__'
|
||||
bind -m vi-insert -x '"\C-r": __fzf_history__'
|
||||
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
|
||||
|
@@ -10,13 +10,13 @@ function fzf_key_bindings
|
||||
|
||||
# "-path \$dir'*/\\.*'" matches hidden files/folders inside $dir but not
|
||||
# $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 \
|
||||
-o -type f -print \
|
||||
-o -type d -print \
|
||||
-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
|
||||
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
|
||||
@@ -36,7 +36,7 @@ function fzf_key_bindings
|
||||
end
|
||||
|
||||
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
|
||||
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 +62,10 @@ function fzf_key_bindings
|
||||
set -l dir $commandline[1]
|
||||
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 \
|
||||
-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
|
||||
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
|
||||
@@ -82,8 +82,8 @@ function fzf_key_bindings
|
||||
end
|
||||
|
||||
function __fzfcmd
|
||||
set -q FZF_TMUX; or set FZF_TMUX 0
|
||||
set -q FZF_TMUX_HEIGHT; or set FZF_TMUX_HEIGHT 40%
|
||||
test -n "$FZF_TMUX"; or set FZF_TMUX 0
|
||||
test -n "$FZF_TMUX_HEIGHT"; or set FZF_TMUX_HEIGHT 40%
|
||||
if [ $FZF_TMUX -eq 1 ]
|
||||
echo "fzf-tmux -d$FZF_TMUX_HEIGHT"
|
||||
else
|
||||
|
@@ -56,6 +56,7 @@ fzf-cd-widget() {
|
||||
return 0
|
||||
fi
|
||||
cd "$dir"
|
||||
unset dir # ensure this doesn't end up appearing in prompt expansion
|
||||
local ret=$?
|
||||
zle fzf-redraw-prompt
|
||||
return $ret
|
||||
|
@@ -1,6 +1,6 @@
|
||||
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
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
for index, r := range pattern {
|
||||
char := text.Get(index)
|
||||
char := text.Get(trimmedLen + index)
|
||||
if !caseSensitive {
|
||||
char = unicode.ToLower(char)
|
||||
}
|
||||
@@ -790,14 +795,17 @@ func PrefixMatch(caseSensitive bool, normalize bool, forward bool, text *util.Ch
|
||||
}
|
||||
}
|
||||
lenPattern := len(pattern)
|
||||
score, _ := calculateScore(caseSensitive, normalize, text, pattern, 0, lenPattern, false)
|
||||
return Result{0, lenPattern, score}, nil
|
||||
score, _ := calculateScore(caseSensitive, normalize, text, pattern, trimmedLen, trimmedLen+lenPattern, false)
|
||||
return Result{trimmedLen, trimmedLen + lenPattern, score}, nil
|
||||
}
|
||||
|
||||
// SuffixMatch performs suffix-match
|
||||
func SuffixMatch(caseSensitive bool, normalize bool, forward bool, text *util.Chars, pattern []rune, withPos bool, slab *util.Slab) (Result, *[]int) {
|
||||
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 {
|
||||
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
|
||||
func EqualMatch(caseSensitive bool, normalize bool, forward bool, text *util.Chars, pattern []rune, withPos bool, slab *util.Slab) (Result, *[]int) {
|
||||
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
|
||||
}
|
||||
match := true
|
||||
if normalize {
|
||||
runes := text.ToRunes()
|
||||
for idx, pchar := range pattern {
|
||||
char := runes[idx]
|
||||
char := runes[trimmedLen+idx]
|
||||
if !caseSensitive {
|
||||
char = unicode.To(unicode.LowerCase, char)
|
||||
}
|
||||
@@ -845,14 +869,15 @@ func EqualMatch(caseSensitive bool, normalize bool, forward bool, text *util.Cha
|
||||
}
|
||||
}
|
||||
} else {
|
||||
runesStr := text.ToString()
|
||||
runes := text.ToRunes()
|
||||
runesStr := string(runes[trimmedLen : len(runes)-trimmedEndLen])
|
||||
if !caseSensitive {
|
||||
runesStr = strings.ToLower(runesStr)
|
||||
}
|
||||
match = runesStr == string(pattern)
|
||||
}
|
||||
if match {
|
||||
return Result{0, lenPattern, (scoreMatch+bonusBoundary)*lenPattern +
|
||||
return Result{trimmedLen, trimmedLen + lenPattern, (scoreMatch+bonusBoundary)*lenPattern +
|
||||
(bonusFirstCharMultiplier-1)*bonusBoundary}, 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, "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)
|
||||
assertMatch(t, SuffixMatch, false, dir, "fooBarBaZ", "baz", 6, 9,
|
||||
(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-vt-100.php
|
||||
- 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
|
||||
// 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 {
|
||||
@@ -243,6 +244,10 @@ func interpretCode(ansiCode string, prevState *ansiState) *ansiState {
|
||||
state.attr = state.attr | tui.Blink
|
||||
case 7:
|
||||
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:
|
||||
init()
|
||||
default:
|
||||
|
@@ -10,7 +10,7 @@ import (
|
||||
|
||||
const (
|
||||
// Current version
|
||||
version = "0.19.0"
|
||||
version = "0.21.0"
|
||||
|
||||
// Core
|
||||
coordinatorDelayMax time.Duration = 100 * time.Millisecond
|
||||
@@ -25,7 +25,7 @@ const (
|
||||
// Terminal
|
||||
initialDelay = 20 * time.Millisecond
|
||||
initialDelayTac = 100 * time.Millisecond
|
||||
spinnerDuration = 200 * time.Millisecond
|
||||
spinnerDuration = 100 * time.Millisecond
|
||||
previewCancelWait = 500 * time.Millisecond
|
||||
maxPatternLength = 300
|
||||
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-`
|
||||
} 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-"`
|
||||
} 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
|
||||
reading := true
|
||||
clearCache := util.Once(false)
|
||||
clearSelection := util.Once(false)
|
||||
ticks := 0
|
||||
var nextCommand *string
|
||||
restart := func(command string) {
|
||||
reading = true
|
||||
clearCache = util.Once(true)
|
||||
clearSelection = util.Once(true)
|
||||
chunkList.Clear()
|
||||
header = make([]string, 0, opts.HeaderLines)
|
||||
go reader.restart(command)
|
||||
@@ -250,20 +254,20 @@ func Run(opts *Options, revision string) {
|
||||
switch evt {
|
||||
|
||||
case EvtReadNew, EvtReadFin:
|
||||
clearCache := false
|
||||
if evt == EvtReadFin && nextCommand != nil {
|
||||
clearCache = true
|
||||
restart(*nextCommand)
|
||||
nextCommand = nil
|
||||
break
|
||||
} else {
|
||||
reading = reading && evt == EvtReadNew
|
||||
}
|
||||
snapshot, count := chunkList.Snapshot()
|
||||
terminal.UpdateCount(count, !reading, value.(*string))
|
||||
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:
|
||||
var command *string
|
||||
@@ -279,9 +283,10 @@ func Run(opts *Options, revision string) {
|
||||
} else {
|
||||
restart(*command)
|
||||
}
|
||||
break
|
||||
}
|
||||
snapshot, _ := chunkList.Snapshot()
|
||||
matcher.Reset(snapshot, input(), true, !reading, sort, command != nil)
|
||||
matcher.Reset(snapshot, input(), true, !reading, sort, clearCache())
|
||||
delay = false
|
||||
|
||||
case EvtSearchProgress:
|
||||
@@ -291,7 +296,9 @@ func Run(opts *Options, revision string) {
|
||||
}
|
||||
|
||||
case EvtHeader:
|
||||
terminal.UpdateHeader(value.([]string))
|
||||
headerPadded := make([]string, opts.HeaderLines)
|
||||
copy(headerPadded, value.([]string))
|
||||
terminal.UpdateHeader(headerPadded)
|
||||
|
||||
case EvtSearchFin:
|
||||
switch val := value.(type) {
|
||||
@@ -321,7 +328,7 @@ func Run(opts *Options, revision string) {
|
||||
terminal.startChan <- true
|
||||
}
|
||||
}
|
||||
terminal.UpdateList(val)
|
||||
terminal.UpdateList(val, clearSelection())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
133
src/options.go
133
src/options.go
@@ -6,12 +6,13 @@ import (
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/junegunn/fzf/src/algo"
|
||||
"github.com/junegunn/fzf/src/tui"
|
||||
"github.com/junegunn/fzf/src/util"
|
||||
|
||||
"github.com/mattn/go-runewidth"
|
||||
"github.com/mattn/go-shellwords"
|
||||
)
|
||||
|
||||
@@ -43,6 +44,7 @@ const usage = `usage: fzf [options]
|
||||
--no-mouse Disable mouse
|
||||
--bind=KEYBINDS Custom key bindings. Refer to the man page.
|
||||
--cycle Enable cyclic scroll
|
||||
--keep-right Keep the right end of the line visible on overflow
|
||||
--no-hscroll Disable horizontal scroll
|
||||
--hscroll-off=COL Number of screen columns to keep to the right of the
|
||||
highlighted substring (default: 10)
|
||||
@@ -55,10 +57,13 @@ const usage = `usage: fzf [options]
|
||||
--min-height=HEIGHT Minimum height when --height is given in percent
|
||||
(default: 10)
|
||||
--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)
|
||||
--info=STYLE Finder info style [default|inline|hidden]
|
||||
--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-lines=N The first N lines of the input are treated as header
|
||||
|
||||
@@ -183,12 +188,15 @@ type Options struct {
|
||||
MinHeight int
|
||||
Layout layoutType
|
||||
Cycle bool
|
||||
KeepRight bool
|
||||
Hscroll bool
|
||||
HscrollOff int
|
||||
FileWord bool
|
||||
InfoStyle infoStyle
|
||||
JumpLabels string
|
||||
Prompt string
|
||||
Pointer string
|
||||
Marker string
|
||||
Query string
|
||||
Select1 bool
|
||||
Exit0 bool
|
||||
@@ -206,7 +214,7 @@ type Options struct {
|
||||
Header []string
|
||||
HeaderLines int
|
||||
Margin [4]sizeSpec
|
||||
Bordered bool
|
||||
BorderShape tui.BorderShape
|
||||
Unicode bool
|
||||
Tabstop int
|
||||
ClearOnExit bool
|
||||
@@ -236,12 +244,15 @@ func defaultOptions() *Options {
|
||||
MinHeight: 10,
|
||||
Layout: layoutDefault,
|
||||
Cycle: false,
|
||||
KeepRight: false,
|
||||
Hscroll: true,
|
||||
HscrollOff: 10,
|
||||
FileWord: false,
|
||||
InfoStyle: infoDefault,
|
||||
JumpLabels: defaultJumpLabels,
|
||||
Prompt: "> ",
|
||||
Pointer: ">",
|
||||
Marker: ">",
|
||||
Query: "",
|
||||
Select1: false,
|
||||
Exit0: false,
|
||||
@@ -293,12 +304,12 @@ func nextString(args []string, i *int, message string) string {
|
||||
return args[*i]
|
||||
}
|
||||
|
||||
func optionalNextString(args []string, i *int) string {
|
||||
if len(args) > *i+1 && !strings.HasPrefix(args[*i+1], "-") {
|
||||
func optionalNextString(args []string, i *int) (bool, string) {
|
||||
if len(args) > *i+1 && !strings.HasPrefix(args[*i+1], "-") && !strings.HasPrefix(args[*i+1], "+") {
|
||||
*i++
|
||||
return args[*i]
|
||||
return true, args[*i]
|
||||
}
|
||||
return ""
|
||||
return false, ""
|
||||
}
|
||||
|
||||
func atoi(str string) int {
|
||||
@@ -392,6 +403,23 @@ func parseAlgo(str string) algo.Algo {
|
||||
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 {
|
||||
if len(str) == 0 {
|
||||
errorExit(message)
|
||||
@@ -464,6 +492,8 @@ func parseKeyChords(str string, message string) map[int]string {
|
||||
chord = tui.Home
|
||||
case "end":
|
||||
chord = tui.End
|
||||
case "insert":
|
||||
chord = tui.Insert
|
||||
case "pgup", "page-up":
|
||||
chord = tui.PgUp
|
||||
case "pgdn", "page-down":
|
||||
@@ -597,6 +627,10 @@ func parseTheme(defaultTheme *tui.ColorTheme, str string) *tui.ColorTheme {
|
||||
theme.Fg = ansi
|
||||
case "bg":
|
||||
theme.Bg = ansi
|
||||
case "preview-fg":
|
||||
theme.PreviewFg = ansi
|
||||
case "preview-bg":
|
||||
theme.PreviewBg = ansi
|
||||
case "fg+":
|
||||
theme.Current = ansi
|
||||
case "bg+":
|
||||
@@ -648,14 +682,18 @@ 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)?|reload):.+|:(execute(?:-multi|-silent)?|reload)(\([^)]*\)|\[[^\]]*\]|~[^~]*~|![^!]*!|@[^@]*@|\#[^\#]*\#|\$[^\$]*\$|%[^%]*%|\^[^\^]*\^|&[^&]*&|\*[^\*]*\*|;[^;]*;|/[^/]*/|\|[^\|]*\|)`)
|
||||
`(?si)[:+](execute(?:-multi|-silent)?|reload):.+|[:+](execute(?:-multi|-silent)?|reload)(\([^)]*\)|\[[^\]]*\]|~[^~]*~|![^!]*!|@[^@]*@|\#[^\#]*\#|\$[^\$]*\$|%[^%]*%|\^[^\^]*\^|&[^&]*&|\*[^\*]*\*|;[^;]*;|/[^/]*/|\|[^\|]*\|)`)
|
||||
}
|
||||
|
||||
func parseKeymap(keymap map[int][]action, str string) {
|
||||
masked := executeRegexp.ReplaceAllStringFunc(str, func(src string) string {
|
||||
prefix := ":execute"
|
||||
if strings.HasPrefix(src, ":reload") {
|
||||
prefix = ":reload"
|
||||
symbol := ":"
|
||||
if strings.HasPrefix(src, "+") {
|
||||
symbol = "+"
|
||||
}
|
||||
prefix := symbol + "execute"
|
||||
if strings.HasPrefix(src[1:], "reload") {
|
||||
prefix = symbol + "reload"
|
||||
} else if src[len(prefix)] == '-' {
|
||||
c := src[len(prefix)+1]
|
||||
if c == 's' || c == 'S' {
|
||||
@@ -722,6 +760,8 @@ func parseKeymap(keymap map[int][]action, str string) {
|
||||
appendAction(actBackwardChar)
|
||||
case "backward-delete-char":
|
||||
appendAction(actBackwardDeleteChar)
|
||||
case "backward-delete-char/eof":
|
||||
appendAction(actBackwardDeleteCharEOF)
|
||||
case "backward-word":
|
||||
appendAction(actBackwardWord)
|
||||
case "clear-screen":
|
||||
@@ -734,6 +774,10 @@ func parseKeymap(keymap map[int][]action, str string) {
|
||||
appendAction(actEndOfLine)
|
||||
case "cancel":
|
||||
appendAction(actCancel)
|
||||
case "clear-query":
|
||||
appendAction(actClearQuery)
|
||||
case "clear-selection":
|
||||
appendAction(actClearSelection)
|
||||
case "forward-char":
|
||||
appendAction(actForwardChar)
|
||||
case "forward-word":
|
||||
@@ -805,7 +849,11 @@ func parseKeymap(keymap map[int][]action, str string) {
|
||||
default:
|
||||
t := isExecuteAction(specLower)
|
||||
if t == actIgnore {
|
||||
errorExit("unknown action: " + spec)
|
||||
if specIndex == 0 && specLower == "" {
|
||||
actions = append(keymap[key], actions...)
|
||||
} else {
|
||||
errorExit("unknown action: " + spec)
|
||||
}
|
||||
} else {
|
||||
var offset int
|
||||
switch t {
|
||||
@@ -1025,6 +1073,8 @@ func parseOptions(opts *Options, allArgs []string) {
|
||||
}
|
||||
}
|
||||
validateJumpLabels := false
|
||||
validatePointer := false
|
||||
validateMarker := false
|
||||
for i := 0; i < len(allArgs); i++ {
|
||||
arg := allArgs[i]
|
||||
switch arg {
|
||||
@@ -1068,7 +1118,7 @@ func parseOptions(opts *Options, allArgs []string) {
|
||||
case "--bind":
|
||||
parseKeymap(opts.Keymap, nextString(allArgs, &i, "bind expression required"))
|
||||
case "--color":
|
||||
spec := optionalNextString(allArgs, &i)
|
||||
_, spec := optionalNextString(allArgs, &i)
|
||||
if len(spec) == 0 {
|
||||
opts.Theme = tui.EmptyTheme()
|
||||
} else {
|
||||
@@ -1127,6 +1177,10 @@ func parseOptions(opts *Options, allArgs []string) {
|
||||
opts.Cycle = true
|
||||
case "--no-cycle":
|
||||
opts.Cycle = false
|
||||
case "--keep-right":
|
||||
opts.KeepRight = true
|
||||
case "--no-keep-right":
|
||||
opts.KeepRight = false
|
||||
case "--hscroll":
|
||||
opts.Hscroll = true
|
||||
case "--no-hscroll":
|
||||
@@ -1173,6 +1227,12 @@ func parseOptions(opts *Options, allArgs []string) {
|
||||
opts.PrintQuery = false
|
||||
case "--prompt":
|
||||
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":
|
||||
opts.Sync = true
|
||||
case "--no-sync":
|
||||
@@ -1210,9 +1270,10 @@ func parseOptions(opts *Options, allArgs []string) {
|
||||
case "--no-margin":
|
||||
opts.Margin = defaultMargin()
|
||||
case "--no-border":
|
||||
opts.Bordered = false
|
||||
opts.BorderShape = tui.BorderNone
|
||||
case "--border":
|
||||
opts.Bordered = true
|
||||
hasArg, arg := optionalNextString(allArgs, &i)
|
||||
opts.BorderShape = parseBorder(arg, !hasArg)
|
||||
case "--no-unicode":
|
||||
opts.Unicode = false
|
||||
case "--unicode":
|
||||
@@ -1237,8 +1298,16 @@ func parseOptions(opts *Options, allArgs []string) {
|
||||
opts.Filter = &value
|
||||
} else if match, value := optString(arg, "-d", "--delimiter="); match {
|
||||
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 {
|
||||
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 {
|
||||
opts.Nth = splitNth(value)
|
||||
} else if match, value := optString(arg, "--with-nth="); match {
|
||||
@@ -1287,6 +1356,7 @@ func parseOptions(opts *Options, allArgs []string) {
|
||||
opts.HscrollOff = atoi(value)
|
||||
} else if match, value := optString(arg, "--jump-labels="); match {
|
||||
opts.JumpLabels = value
|
||||
validateJumpLabels = true
|
||||
} else {
|
||||
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) {
|
||||
if util.IsWindows() && opts.Height.size > 0 {
|
||||
errorExit("--height option is currently not supported on Windows")
|
||||
if !tui.IsLightRendererSupported() && opts.Height.size > 0 {
|
||||
errorExit("--height option is currently not supported on this platform")
|
||||
}
|
||||
// Default actions for CTRL-N / CTRL-P when --history is set
|
||||
if opts.History != nil {
|
||||
|
@@ -243,9 +243,10 @@ func TestBind(t *testing.T) {
|
||||
check(tui.CtrlA, "", actBeginningOfLine)
|
||||
parseKeymap(keymap,
|
||||
"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 (,),[,],/,:,@,%,{};,"+
|
||||
"x:Execute(foo+bar),X:execute/bar+baz/"+
|
||||
",f1:+top,f1:+top"+
|
||||
",,:abort,::accept,+:execute:++\nfoobar,Y:execute(baz)+up")
|
||||
check(tui.CtrlA, "", actKillLine)
|
||||
check(tui.CtrlB, "", actToggleSort, actUp, actDown)
|
||||
@@ -253,7 +254,7 @@ func TestBind(t *testing.T) {
|
||||
check(tui.AltZ+',', "", actAbort)
|
||||
check(tui.AltZ+':', "", actAccept)
|
||||
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.F3, "echo '({})'", actExecute)
|
||||
check(tui.F4, "less {}", actExecute)
|
||||
@@ -421,3 +422,29 @@ func TestAdditiveExpect(t *testing.T) {
|
||||
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", 0, 3)
|
||||
match("AbC ", 0, 3)
|
||||
match(" AbC ", 1, 4)
|
||||
match(" AbC", 2, 5)
|
||||
}
|
||||
|
||||
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 (
|
||||
"bufio"
|
||||
"context"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/junegunn/fzf/src/util"
|
||||
"github.com/saracen/walker"
|
||||
)
|
||||
|
||||
// Reader reads from command or standard input
|
||||
@@ -78,7 +81,7 @@ func (r *Reader) terminate() {
|
||||
r.killed = true
|
||||
if r.exec != nil && r.exec.Process != nil {
|
||||
util.KillCommand(r.exec)
|
||||
} else {
|
||||
} else if defaultCommand != "" {
|
||||
os.Stdin.Close()
|
||||
}
|
||||
}
|
||||
@@ -99,7 +102,11 @@ func (r *Reader) ReadSource() {
|
||||
shell := "bash"
|
||||
cmd := os.Getenv("FZF_DEFAULT_COMMAND")
|
||||
if len(cmd) == 0 {
|
||||
success = r.readFromCommand(&shell, defaultCommand)
|
||||
if defaultCommand != "" {
|
||||
success = r.readFromCommand(&shell, defaultCommand)
|
||||
} else {
|
||||
success = r.readFiles()
|
||||
}
|
||||
} else {
|
||||
success = r.readFromCommand(nil, cmd)
|
||||
}
|
||||
@@ -144,6 +151,31 @@ func (r *Reader) readFromStdin() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (r *Reader) readFiles() bool {
|
||||
r.killed = false
|
||||
fn := func(path string, mode os.FileInfo) error {
|
||||
path = filepath.Clean(path)
|
||||
if path != "." {
|
||||
if mode.Mode().IsDir() && filepath.Base(path)[0] == '.' {
|
||||
return filepath.SkipDir
|
||||
}
|
||||
if 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 {
|
||||
r.mutex.Lock()
|
||||
r.killed = false
|
||||
|
389
src/terminal.go
389
src/terminal.go
@@ -25,6 +25,8 @@ import (
|
||||
var placeholder *regexp.Regexp
|
||||
var activeTempFiles []string
|
||||
|
||||
const ellipsis string = ".."
|
||||
|
||||
func init() {
|
||||
placeholder = regexp.MustCompile(`\\?(?:{[+sf]*[0-9,-.]*}|{q}|{\+?f?nf?})`)
|
||||
activeTempFiles = []string{}
|
||||
@@ -59,71 +61,79 @@ var emptyLine = itemLine{}
|
||||
|
||||
// Terminal represents terminal input/output
|
||||
type Terminal struct {
|
||||
initDelay time.Duration
|
||||
infoStyle infoStyle
|
||||
prompt string
|
||||
promptLen int
|
||||
queryLen [2]int
|
||||
layout layoutType
|
||||
fullscreen bool
|
||||
hscroll bool
|
||||
hscrollOff int
|
||||
wordRubout string
|
||||
wordNext string
|
||||
cx int
|
||||
cy int
|
||||
offset int
|
||||
xoffset int
|
||||
yanked []rune
|
||||
input []rune
|
||||
multi int
|
||||
sort bool
|
||||
toggleSort bool
|
||||
delimiter Delimiter
|
||||
expect map[int]string
|
||||
keymap map[int][]action
|
||||
pressed string
|
||||
printQuery bool
|
||||
history *History
|
||||
cycle bool
|
||||
header []string
|
||||
header0 []string
|
||||
ansi bool
|
||||
tabstop int
|
||||
margin [4]sizeSpec
|
||||
strong tui.Attr
|
||||
unicode bool
|
||||
bordered bool
|
||||
cleanExit bool
|
||||
border tui.Window
|
||||
window tui.Window
|
||||
pborder tui.Window
|
||||
pwindow tui.Window
|
||||
count int
|
||||
progress int
|
||||
reading bool
|
||||
failed *string
|
||||
jumping jumpMode
|
||||
jumpLabels string
|
||||
printer func(string)
|
||||
printsep string
|
||||
merger *Merger
|
||||
selected map[int32]selectedItem
|
||||
version int64
|
||||
reqBox *util.EventBox
|
||||
preview previewOpts
|
||||
previewer previewer
|
||||
previewBox *util.EventBox
|
||||
eventBox *util.EventBox
|
||||
mutex sync.Mutex
|
||||
initFunc func()
|
||||
prevLines []itemLine
|
||||
suppress bool
|
||||
startChan chan bool
|
||||
killChan chan int
|
||||
slab *util.Slab
|
||||
theme *tui.ColorTheme
|
||||
tui tui.Renderer
|
||||
initDelay time.Duration
|
||||
infoStyle infoStyle
|
||||
spinner []string
|
||||
prompt string
|
||||
promptLen int
|
||||
pointer string
|
||||
pointerLen int
|
||||
pointerEmpty string
|
||||
marker string
|
||||
markerLen int
|
||||
markerEmpty string
|
||||
queryLen [2]int
|
||||
layout layoutType
|
||||
fullscreen bool
|
||||
keepRight bool
|
||||
hscroll bool
|
||||
hscrollOff int
|
||||
wordRubout string
|
||||
wordNext string
|
||||
cx int
|
||||
cy int
|
||||
offset int
|
||||
xoffset int
|
||||
yanked []rune
|
||||
input []rune
|
||||
multi int
|
||||
sort bool
|
||||
toggleSort bool
|
||||
delimiter Delimiter
|
||||
expect map[int]string
|
||||
keymap map[int][]action
|
||||
pressed string
|
||||
printQuery bool
|
||||
history *History
|
||||
cycle bool
|
||||
header []string
|
||||
header0 []string
|
||||
ansi bool
|
||||
tabstop int
|
||||
margin [4]sizeSpec
|
||||
strong tui.Attr
|
||||
unicode bool
|
||||
borderShape tui.BorderShape
|
||||
cleanExit bool
|
||||
border tui.Window
|
||||
window tui.Window
|
||||
pborder tui.Window
|
||||
pwindow tui.Window
|
||||
count int
|
||||
progress int
|
||||
reading bool
|
||||
failed *string
|
||||
jumping jumpMode
|
||||
jumpLabels string
|
||||
printer func(string)
|
||||
printsep string
|
||||
merger *Merger
|
||||
selected map[int32]selectedItem
|
||||
version int64
|
||||
reqBox *util.EventBox
|
||||
preview previewOpts
|
||||
previewer previewer
|
||||
previewBox *util.EventBox
|
||||
eventBox *util.EventBox
|
||||
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 {
|
||||
@@ -145,8 +155,6 @@ func (a byTimeOrder) Less(i, j int) bool {
|
||||
return a[i].at.Before(a[j].at)
|
||||
}
|
||||
|
||||
var _spinner = []string{`-`, `\`, `|`, `/`, `-`, `\`, `|`, `/`}
|
||||
|
||||
const (
|
||||
reqPrompt util.EventType = iota
|
||||
reqInfo
|
||||
@@ -182,9 +190,12 @@ const (
|
||||
actAcceptNonEmpty
|
||||
actBackwardChar
|
||||
actBackwardDeleteChar
|
||||
actBackwardDeleteCharEOF
|
||||
actBackwardWord
|
||||
actCancel
|
||||
actClearScreen
|
||||
actClearQuery
|
||||
actClearSelection
|
||||
actDeleteChar
|
||||
actDeleteCharEOF
|
||||
actEndOfLine
|
||||
@@ -362,9 +373,9 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
|
||||
effectiveMinHeight *= 2
|
||||
}
|
||||
if opts.InfoStyle != infoDefault {
|
||||
effectiveMinHeight -= 1
|
||||
effectiveMinHeight--
|
||||
}
|
||||
if opts.Bordered {
|
||||
if opts.BorderShape != tui.BorderNone {
|
||||
effectiveMinHeight += 2
|
||||
}
|
||||
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)
|
||||
wordNext = fmt.Sprintf("[^%s]%s|(.$)", sep, sep)
|
||||
}
|
||||
spinner := []string{`⠋`, `⠙`, `⠹`, `⠸`, `⠼`, `⠴`, `⠦`, `⠧`, `⠇`, `⠏`}
|
||||
if !opts.Unicode {
|
||||
spinner = []string{`-`, `\`, `|`, `/`, `-`, `\`, `|`, `/`}
|
||||
}
|
||||
t := Terminal{
|
||||
initDelay: delay,
|
||||
infoStyle: opts.InfoStyle,
|
||||
queryLen: [2]int{0, 0},
|
||||
layout: opts.Layout,
|
||||
fullscreen: fullscreen,
|
||||
hscroll: opts.Hscroll,
|
||||
hscrollOff: opts.HscrollOff,
|
||||
wordRubout: wordRubout,
|
||||
wordNext: wordNext,
|
||||
cx: len(input),
|
||||
cy: 0,
|
||||
offset: 0,
|
||||
xoffset: 0,
|
||||
yanked: []rune{},
|
||||
input: input,
|
||||
multi: opts.Multi,
|
||||
sort: opts.Sort > 0,
|
||||
toggleSort: opts.ToggleSort,
|
||||
delimiter: opts.Delimiter,
|
||||
expect: opts.Expect,
|
||||
keymap: opts.Keymap,
|
||||
pressed: "",
|
||||
printQuery: opts.PrintQuery,
|
||||
history: opts.History,
|
||||
margin: opts.Margin,
|
||||
unicode: opts.Unicode,
|
||||
bordered: opts.Bordered,
|
||||
cleanExit: opts.ClearOnExit,
|
||||
strong: strongAttr,
|
||||
cycle: opts.Cycle,
|
||||
header: header,
|
||||
header0: header,
|
||||
ansi: opts.Ansi,
|
||||
tabstop: opts.Tabstop,
|
||||
reading: true,
|
||||
failed: nil,
|
||||
jumping: jumpDisabled,
|
||||
jumpLabels: opts.JumpLabels,
|
||||
printer: opts.Printer,
|
||||
printsep: opts.PrintSep,
|
||||
merger: EmptyMerger,
|
||||
selected: make(map[int32]selectedItem),
|
||||
reqBox: util.NewEventBox(),
|
||||
preview: opts.Preview,
|
||||
previewer: previewer{"", 0, 0, previewBox != nil && !opts.Preview.hidden, false},
|
||||
previewBox: previewBox,
|
||||
eventBox: eventBox,
|
||||
mutex: sync.Mutex{},
|
||||
suppress: true,
|
||||
slab: util.MakeSlab(slab16Size, slab32Size),
|
||||
theme: opts.Theme,
|
||||
startChan: make(chan bool, 1),
|
||||
killChan: make(chan int),
|
||||
tui: renderer,
|
||||
initFunc: func() { renderer.Init() }}
|
||||
initDelay: delay,
|
||||
infoStyle: opts.InfoStyle,
|
||||
spinner: spinner,
|
||||
queryLen: [2]int{0, 0},
|
||||
layout: opts.Layout,
|
||||
fullscreen: fullscreen,
|
||||
keepRight: opts.KeepRight,
|
||||
hscroll: opts.Hscroll,
|
||||
hscrollOff: opts.HscrollOff,
|
||||
wordRubout: wordRubout,
|
||||
wordNext: wordNext,
|
||||
cx: len(input),
|
||||
cy: 0,
|
||||
offset: 0,
|
||||
xoffset: 0,
|
||||
yanked: []rune{},
|
||||
input: input,
|
||||
multi: opts.Multi,
|
||||
sort: opts.Sort > 0,
|
||||
toggleSort: opts.ToggleSort,
|
||||
delimiter: opts.Delimiter,
|
||||
expect: opts.Expect,
|
||||
keymap: opts.Keymap,
|
||||
pressed: "",
|
||||
printQuery: opts.PrintQuery,
|
||||
history: opts.History,
|
||||
margin: opts.Margin,
|
||||
unicode: opts.Unicode,
|
||||
borderShape: opts.BorderShape,
|
||||
cleanExit: opts.ClearOnExit,
|
||||
strong: strongAttr,
|
||||
cycle: opts.Cycle,
|
||||
header: header,
|
||||
header0: header,
|
||||
ansi: opts.Ansi,
|
||||
tabstop: opts.Tabstop,
|
||||
reading: true,
|
||||
failed: nil,
|
||||
jumping: jumpDisabled,
|
||||
jumpLabels: opts.JumpLabels,
|
||||
printer: opts.Printer,
|
||||
printsep: opts.PrintSep,
|
||||
merger: EmptyMerger,
|
||||
selected: make(map[int32]selectedItem),
|
||||
reqBox: util.NewEventBox(),
|
||||
preview: opts.Preview,
|
||||
previewer: previewer{"", 0, 0, previewBox != nil && !opts.Preview.hidden, false},
|
||||
previewBox: previewBox,
|
||||
eventBox: eventBox,
|
||||
mutex: sync.Mutex{},
|
||||
suppress: true,
|
||||
slab: util.MakeSlab(slab16Size, slab32Size),
|
||||
theme: opts.Theme,
|
||||
startChan: make(chan bool, 1),
|
||||
killChan: make(chan int),
|
||||
tui: renderer,
|
||||
initFunc: func() { renderer.Init() }}
|
||||
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
|
||||
}
|
||||
|
||||
@@ -493,10 +516,13 @@ func (t *Terminal) UpdateProgress(progress float32) {
|
||||
}
|
||||
|
||||
// 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.progress = 100
|
||||
t.merger = merger
|
||||
if reset {
|
||||
t.selected = make(map[int32]selectedItem)
|
||||
}
|
||||
t.mutex.Unlock()
|
||||
t.reqBox.Set(reqInfo, nil)
|
||||
t.reqBox.Set(reqList, nil)
|
||||
@@ -573,8 +599,11 @@ func (t *Terminal) resizeWindows() {
|
||||
} else {
|
||||
marginInt[idx] = int(sizeSpec.size)
|
||||
}
|
||||
if t.bordered && idx%2 == 0 {
|
||||
marginInt[idx] += 1
|
||||
switch t.borderShape {
|
||||
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) {
|
||||
@@ -614,21 +643,30 @@ func (t *Terminal) resizeWindows() {
|
||||
|
||||
width := screenWidth - marginInt[1] - marginInt[3]
|
||||
height := screenHeight - marginInt[0] - marginInt[2]
|
||||
if t.bordered {
|
||||
switch t.borderShape {
|
||||
case tui.BorderHorizontal:
|
||||
t.border = t.tui.NewWindow(
|
||||
marginInt[0]-1,
|
||||
marginInt[3],
|
||||
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)
|
||||
if previewVisible {
|
||||
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 {
|
||||
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
|
||||
// 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
|
||||
@@ -636,28 +674,28 @@ func (t *Terminal) resizeWindows() {
|
||||
if !t.preview.wrap && t.tui.DoesAutoWrap() {
|
||||
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 {
|
||||
case posUp:
|
||||
pheight := calculateSize(height, t.preview.size, minHeight, 3)
|
||||
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)
|
||||
case posDown:
|
||||
pheight := calculateSize(height, t.preview.size, minHeight, 3)
|
||||
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)
|
||||
case posLeft:
|
||||
pwidth := calculateSize(width, t.preview.size, minWidth, 5)
|
||||
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)
|
||||
case posRight:
|
||||
pwidth := calculateSize(width, t.preview.size, minWidth, 5)
|
||||
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)
|
||||
}
|
||||
} else {
|
||||
@@ -665,7 +703,7 @@ func (t *Terminal) resizeWindows() {
|
||||
marginInt[0],
|
||||
marginInt[3],
|
||||
width,
|
||||
height, noBorder)
|
||||
height, false, noBorder)
|
||||
}
|
||||
for i := 0; i < t.window.Height(); i++ {
|
||||
t.window.MoveAndClear(i, 0)
|
||||
@@ -738,8 +776,8 @@ func (t *Terminal) printInfo() {
|
||||
t.move(1, 0, true)
|
||||
if t.reading {
|
||||
duration := int64(spinnerDuration)
|
||||
idx := (time.Now().UnixNano() % (duration * int64(len(_spinner)))) / duration
|
||||
t.window.CPrint(tui.ColSpinner, t.strong, _spinner[idx])
|
||||
idx := (time.Now().UnixNano() % (duration * int64(len(t.spinner)))) / duration
|
||||
t.window.CPrint(tui.ColSpinner, t.strong, t.spinner[idx])
|
||||
}
|
||||
t.move(1, 2, false)
|
||||
pos = 2
|
||||
@@ -785,7 +823,7 @@ func (t *Terminal) printInfo() {
|
||||
maxWidth := t.window.Width() - pos
|
||||
if len(output) > maxWidth {
|
||||
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)
|
||||
}
|
||||
@@ -842,15 +880,15 @@ func (t *Terminal) printList() {
|
||||
func (t *Terminal) printItem(result Result, line int, i int, current bool) {
|
||||
item := result.item
|
||||
_, selected := t.selected[item.Index()]
|
||||
label := " "
|
||||
label := t.pointerEmpty
|
||||
if t.jumping != jumpDisabled {
|
||||
if i < len(t.jumpLabels) {
|
||||
// Striped
|
||||
current = i%2 == 0
|
||||
label = t.jumpLabels[i : i+1]
|
||||
label = t.jumpLabels[i:i+1] + strings.Repeat(" ", t.pointerLen-1)
|
||||
}
|
||||
} else if current {
|
||||
label = ">"
|
||||
label = t.pointer
|
||||
}
|
||||
|
||||
// Avoid unnecessary redraw
|
||||
@@ -869,17 +907,17 @@ func (t *Terminal) printItem(result Result, line int, i int, current bool) {
|
||||
if current {
|
||||
t.window.CPrint(tui.ColCurrentCursor, t.strong, label)
|
||||
if selected {
|
||||
t.window.CPrint(tui.ColCurrentSelected, t.strong, ">")
|
||||
t.window.CPrint(tui.ColCurrentSelected, t.strong, t.marker)
|
||||
} 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)
|
||||
} else {
|
||||
t.window.CPrint(tui.ColCursor, t.strong, label)
|
||||
if selected {
|
||||
t.window.CPrint(tui.ColSelected, t.strong, ">")
|
||||
t.window.CPrint(tui.ColSelected, t.strong, t.marker)
|
||||
} else {
|
||||
t.window.Print(" ")
|
||||
t.window.Print(t.markerEmpty)
|
||||
}
|
||||
newLine.width = t.printHighlighted(result, 0, tui.ColNormal, tui.ColMatch, false, true)
|
||||
}
|
||||
@@ -966,14 +1004,17 @@ func (t *Terminal) printHighlighted(result Result, attr tui.Attr, col1 tui.Color
|
||||
displayWidth := t.displayWidthWithLimit(text, 0, maxWidth)
|
||||
if displayWidth > maxWidth {
|
||||
if t.hscroll {
|
||||
// Stri..
|
||||
if !t.overflow(text[:maxe], maxWidth-2) {
|
||||
if t.keepRight && pos == nil {
|
||||
text, _ = t.trimLeft(text, maxWidth-2)
|
||||
text = append([]rune(ellipsis), text...)
|
||||
} else if !t.overflow(text[:maxe], maxWidth-2) {
|
||||
// Stri..
|
||||
text, _ = t.trimRight(text, maxWidth-2)
|
||||
text = append(text, []rune("..")...)
|
||||
text = append(text, []rune(ellipsis)...)
|
||||
} else {
|
||||
// Stri..
|
||||
if t.overflow(text[maxe:], 2) {
|
||||
text = append(text[:maxe], []rune("..")...)
|
||||
text = append(text[:maxe], []rune(ellipsis)...)
|
||||
}
|
||||
// ..ri..
|
||||
var diff int32
|
||||
@@ -988,11 +1029,11 @@ func (t *Terminal) printHighlighted(result Result, attr tui.Attr, col1 tui.Color
|
||||
offsets[idx].offset[0] = b
|
||||
offsets[idx].offset[1] = util.Max32(b, e)
|
||||
}
|
||||
text = append([]rune(".."), text...)
|
||||
text = append([]rune(ellipsis), text...)
|
||||
}
|
||||
} else {
|
||||
text, _ = t.trimRight(text, maxWidth-2)
|
||||
text = append(text, []rune("..")...)
|
||||
text = append(text, []rune(ellipsis)...)
|
||||
|
||||
for idx, offset := range offsets {
|
||||
offsets[idx].offset[0] = util.Min32(offset.offset[0], int32(maxWidth-2))
|
||||
@@ -1066,7 +1107,7 @@ func (t *Terminal) printPreview() {
|
||||
if t.theme != nil && ansi != nil && ansi.colored() {
|
||||
fillRet = t.pwindow.CFill(ansi.fg, ansi.bg, ansi.attr, str)
|
||||
} 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
|
||||
})
|
||||
@@ -1123,7 +1164,7 @@ func (t *Terminal) refresh() {
|
||||
t.placeCursor()
|
||||
if !t.suppress {
|
||||
windows := make([]tui.Window, 0, 4)
|
||||
if t.bordered {
|
||||
if t.borderShape != tui.BorderNone {
|
||||
windows = append(windows, t.border)
|
||||
}
|
||||
if t.hasPreviewWindow() {
|
||||
@@ -1237,7 +1278,7 @@ func parsePlaceholder(match string) (bool, string, placeholderFlags) {
|
||||
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) {
|
||||
_, _, flags := parsePlaceholder(match)
|
||||
if flags.plus {
|
||||
@@ -1246,6 +1287,7 @@ func hasPreviewFlags(template string) (plus bool, query bool) {
|
||||
if flags.query {
|
||||
query = true
|
||||
}
|
||||
slot = true
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -1409,7 +1451,7 @@ func (t *Terminal) currentItem() *Item {
|
||||
|
||||
func (t *Terminal) buildPlusList(template string, forcePlus bool) (bool, []*Item) {
|
||||
current := t.currentItem()
|
||||
plus, query := hasPreviewFlags(template)
|
||||
_, plus, query := hasPreviewFlags(template)
|
||||
if !(query && len(t.input) > 0 || (forcePlus || plus) && len(t.selected) > 0) {
|
||||
return current != nil, []*Item{current, current}
|
||||
}
|
||||
@@ -1565,7 +1607,10 @@ func (t *Terminal) Loop() {
|
||||
var out bytes.Buffer
|
||||
cmd.Stdout = &out
|
||||
cmd.Stderr = &out
|
||||
cmd.Start()
|
||||
err := cmd.Start()
|
||||
if err != nil {
|
||||
out.Write([]byte(err.Error()))
|
||||
}
|
||||
finishChan := make(chan bool, 1)
|
||||
updateChan := make(chan bool)
|
||||
go func() {
|
||||
@@ -1820,6 +1865,13 @@ func (t *Terminal) Loop() {
|
||||
t.input = []rune{}
|
||||
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:
|
||||
if t.cx < len(t.input) {
|
||||
t.cx++
|
||||
@@ -1904,6 +1956,15 @@ func (t *Terminal) Loop() {
|
||||
}
|
||||
case actClearScreen:
|
||||
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:
|
||||
t.vset(0)
|
||||
req(reqList)
|
||||
@@ -2045,17 +2106,17 @@ func (t *Terminal) Loop() {
|
||||
t.failed = nil
|
||||
|
||||
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 {
|
||||
_, query := hasPreviewFlags(a.a)
|
||||
valid = query
|
||||
// We run the command even when there's no match
|
||||
// 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 {
|
||||
command := replacePlaceholder(a.a,
|
||||
t.ansi, t.delimiter, t.printsep, false, string(t.input), list)
|
||||
newCommand = &command
|
||||
t.selected = make(map[int32]selectedItem)
|
||||
}
|
||||
}
|
||||
return true
|
||||
@@ -2095,7 +2156,7 @@ func (t *Terminal) Loop() {
|
||||
|
||||
if queryChanged {
|
||||
if t.isPreviewEnabled() {
|
||||
_, q := hasPreviewFlags(t.preview.command)
|
||||
_, _, q := hasPreviewFlags(t.preview.command)
|
||||
if q {
|
||||
t.version++
|
||||
}
|
||||
|
@@ -39,6 +39,6 @@ func (r *FullscreenRenderer) MaxY() int { return 0 }
|
||||
|
||||
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
|
||||
}
|
||||
|
132
src/tui/light.go
132
src/tui/light.go
@@ -7,7 +7,6 @@ import (
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
|
||||
@@ -23,27 +22,13 @@ const (
|
||||
defaultEscDelay = 100
|
||||
escPollInterval = 5
|
||||
offsetPollTries = 10
|
||||
maxInputBuffer = 10 * 1024
|
||||
)
|
||||
|
||||
const consoleDevice string = "/dev/tty"
|
||||
|
||||
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) {
|
||||
r.stderrInternal(str, true)
|
||||
}
|
||||
@@ -100,11 +85,19 @@ type LightRenderer struct {
|
||||
y int
|
||||
x int
|
||||
maxHeightFunc func(int) int
|
||||
|
||||
// Windows only
|
||||
ttyinChannel chan byte
|
||||
inHandle uintptr
|
||||
outHandle uintptr
|
||||
origStateInput uint32
|
||||
origStateOutput uint32
|
||||
}
|
||||
|
||||
type LightWindow struct {
|
||||
renderer *LightRenderer
|
||||
colored bool
|
||||
preview bool
|
||||
border BorderStyle
|
||||
top int
|
||||
left int
|
||||
@@ -132,10 +125,6 @@ func NewLightRenderer(theme *ColorTheme, forceBlack bool, mouse bool, tabstop in
|
||||
return &r
|
||||
}
|
||||
|
||||
func (r *LightRenderer) fd() int {
|
||||
return int(r.ttyin.Fd())
|
||||
}
|
||||
|
||||
func (r *LightRenderer) defaultTheme() *ColorTheme {
|
||||
if strings.Contains(os.Getenv("TERM"), "256") {
|
||||
return Dark256
|
||||
@@ -147,22 +136,6 @@ func (r *LightRenderer) defaultTheme() *ColorTheme {
|
||||
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 {
|
||||
if times > 0 {
|
||||
return strings.Repeat(string(r), times)
|
||||
@@ -181,13 +154,9 @@ func atoi(s string, defaultValue int) int {
|
||||
func (r *LightRenderer) Init() {
|
||||
r.escDelay = atoi(os.Getenv("ESCDELAY"), defaultEscDelay)
|
||||
|
||||
fd := r.fd()
|
||||
origState, err := terminal.GetState(fd)
|
||||
if err != nil {
|
||||
if err := r.initPlatform(); err != nil {
|
||||
errorExit(err.Error())
|
||||
}
|
||||
r.origState = origState
|
||||
terminal.MakeRaw(fd)
|
||||
r.updateTerminalSize()
|
||||
initTheme(r.theme, r.defaultTheme(), r.forceBlack)
|
||||
|
||||
@@ -260,28 +229,6 @@ func getEnv(name string, defaultValue int) int {
|
||||
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 {
|
||||
return r.getBytesInternal(r.buffer, false)
|
||||
}
|
||||
@@ -316,6 +263,13 @@ func (r *LightRenderer) getBytesInternal(buffer []byte, nonblock bool) []byte {
|
||||
}
|
||||
buffer = append(buffer, byte(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
|
||||
@@ -453,7 +407,10 @@ func (r *LightRenderer) escSequence(sz *int) Event {
|
||||
*sz = 4
|
||||
switch r.buffer[2] {
|
||||
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
|
||||
switch r.buffer[3] {
|
||||
case 48:
|
||||
@@ -467,7 +424,7 @@ func (r *LightRenderer) escSequence(sz *int) Event {
|
||||
}
|
||||
}
|
||||
// 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
|
||||
r.buffer = r.buffer[6:]
|
||||
*sz = 0
|
||||
@@ -486,10 +443,18 @@ func (r *LightRenderer) escSequence(sz *int) Event {
|
||||
switch r.buffer[3] {
|
||||
case 126:
|
||||
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 {
|
||||
*sz = 5
|
||||
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:
|
||||
return Event{F5, 0, nil}
|
||||
case 55:
|
||||
@@ -584,7 +549,7 @@ func (r *LightRenderer) rmcup() {
|
||||
}
|
||||
|
||||
func (r *LightRenderer) Pause(clear bool) {
|
||||
terminal.Restore(r.fd(), r.origState)
|
||||
r.restoreTerminal()
|
||||
if clear {
|
||||
if r.fullscreen {
|
||||
r.rmcup()
|
||||
@@ -597,7 +562,7 @@ func (r *LightRenderer) Pause(clear bool) {
|
||||
}
|
||||
|
||||
func (r *LightRenderer) Resume(clear bool) {
|
||||
terminal.MakeRaw(r.fd())
|
||||
r.setupTerminal()
|
||||
if clear {
|
||||
if r.fullscreen {
|
||||
r.smcup()
|
||||
@@ -651,7 +616,8 @@ func (r *LightRenderer) Close() {
|
||||
r.csi("?1000l")
|
||||
}
|
||||
r.flush()
|
||||
terminal.Restore(r.fd(), r.origState)
|
||||
r.closePlatform()
|
||||
r.restoreTerminal()
|
||||
}
|
||||
|
||||
func (r *LightRenderer) MaxX() int {
|
||||
@@ -666,10 +632,11 @@ func (r *LightRenderer) DoesAutoWrap() bool {
|
||||
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{
|
||||
renderer: r,
|
||||
colored: r.theme != nil,
|
||||
preview: preview,
|
||||
border: borderStyle,
|
||||
top: top,
|
||||
left: left,
|
||||
@@ -679,8 +646,13 @@ func (r *LightRenderer) NewWindow(top int, left int, width int, height int, bord
|
||||
fg: colDefault,
|
||||
bg: colDefault}
|
||||
if r.theme != nil {
|
||||
w.fg = r.theme.Fg
|
||||
w.bg = r.theme.Bg
|
||||
if preview {
|
||||
w.fg = r.theme.PreviewFg
|
||||
w.bg = r.theme.PreviewBg
|
||||
} else {
|
||||
w.fg = r.theme.Fg
|
||||
w.bg = r.theme.Bg
|
||||
}
|
||||
}
|
||||
w.drawBorder()
|
||||
return w
|
||||
@@ -688,7 +660,7 @@ func (r *LightRenderer) NewWindow(top int, left int, width int, height int, bord
|
||||
|
||||
func (w *LightWindow) drawBorder() {
|
||||
switch w.border.shape {
|
||||
case BorderAround:
|
||||
case BorderRounded, BorderSharp:
|
||||
w.drawBorderAround()
|
||||
case BorderHorizontal:
|
||||
w.drawBorderHorizontal()
|
||||
@@ -704,16 +676,20 @@ func (w *LightWindow) drawBorderHorizontal() {
|
||||
|
||||
func (w *LightWindow) drawBorderAround() {
|
||||
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))
|
||||
for y := 1; y < w.height-1; y++ {
|
||||
w.Move(y, 0)
|
||||
w.CPrint(ColBorder, AttrRegular, string(w.border.vertical))
|
||||
w.cprint2(colDefault, w.bg, AttrRegular, repeat(' ', w.width-2))
|
||||
w.CPrint(ColBorder, AttrRegular, string(w.border.vertical))
|
||||
w.CPrint(color, AttrRegular, string(w.border.vertical))
|
||||
w.CPrint(color, AttrRegular, repeat(' ', w.width-2))
|
||||
w.CPrint(color, AttrRegular, string(w.border.vertical))
|
||||
}
|
||||
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))
|
||||
}
|
||||
|
||||
|
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 {
|
||||
color bool
|
||||
preview bool
|
||||
top int
|
||||
left int
|
||||
width int
|
||||
height int
|
||||
normal ColorPair
|
||||
lastX int
|
||||
lastY int
|
||||
moveCursor bool
|
||||
@@ -317,6 +319,8 @@ func (r *FullscreenRenderer) GetChar() Event {
|
||||
}
|
||||
return Event{Right, 0, nil}
|
||||
|
||||
case tcell.KeyInsert:
|
||||
return Event{Insert, 0, nil}
|
||||
case tcell.KeyHome:
|
||||
return Event{Home, 0, nil}
|
||||
case tcell.KeyDelete:
|
||||
@@ -408,14 +412,19 @@ func (r *FullscreenRenderer) RefreshWindows(windows []Window) {
|
||||
_screen.Show()
|
||||
}
|
||||
|
||||
func (r *FullscreenRenderer) NewWindow(top int, left int, width int, height int, borderStyle BorderStyle) Window {
|
||||
// TODO
|
||||
func (r *FullscreenRenderer) NewWindow(top int, left int, width int, height int, preview bool, borderStyle BorderStyle) Window {
|
||||
normal := ColNormal
|
||||
if preview {
|
||||
normal = ColPreview
|
||||
}
|
||||
return &TcellWindow{
|
||||
color: r.theme != nil,
|
||||
preview: preview,
|
||||
top: top,
|
||||
left: left,
|
||||
width: width,
|
||||
height: height,
|
||||
normal: normal,
|
||||
borderStyle: borderStyle}
|
||||
}
|
||||
|
||||
@@ -423,16 +432,16 @@ func (w *TcellWindow) Close() {
|
||||
// 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 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() {
|
||||
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 {
|
||||
@@ -449,13 +458,13 @@ func (w *TcellWindow) Move(y int, x int) {
|
||||
func (w *TcellWindow) MoveAndClear(y int, x int) {
|
||||
w.Move(y, x)
|
||||
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
|
||||
}
|
||||
|
||||
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) {
|
||||
@@ -468,7 +477,7 @@ func (w *TcellWindow) printString(text string, pair ColorPair, a Attr) {
|
||||
Reverse(a&Attr(tcell.AttrReverse) != 0).
|
||||
Underline(a&Attr(tcell.AttrUnderline) != 0)
|
||||
} else {
|
||||
style = ColNormal.style().
|
||||
style = w.normal.style().
|
||||
Reverse(a&Attr(tcell.AttrReverse) != 0 || pair == ColCurrent || 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 {
|
||||
style = pair.style()
|
||||
} else {
|
||||
style = ColNormal.style()
|
||||
style = w.normal.style()
|
||||
}
|
||||
style = style.
|
||||
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 {
|
||||
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 {
|
||||
if fg == colDefault {
|
||||
fg = ColNormal.Fg()
|
||||
fg = w.normal.Fg()
|
||||
}
|
||||
if bg == colDefault {
|
||||
bg = ColNormal.Bg()
|
||||
bg = w.normal.Bg()
|
||||
}
|
||||
return w.fillString(str, NewColorPair(fg, bg), a)
|
||||
}
|
||||
@@ -584,9 +593,13 @@ func (w *TcellWindow) drawBorder() {
|
||||
|
||||
var style tcell.Style
|
||||
if w.color {
|
||||
style = ColBorder.style()
|
||||
if w.preview {
|
||||
style = ColPreviewBorder.style()
|
||||
} else {
|
||||
style = ColBorder.style()
|
||||
}
|
||||
} else {
|
||||
style = ColNormal.style()
|
||||
style = w.normal.style()
|
||||
}
|
||||
|
||||
for x := left; x < right; x++ {
|
||||
@@ -594,7 +607,7 @@ func (w *TcellWindow) drawBorder() {
|
||||
_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++ {
|
||||
_screen.SetContent(left, y, w.borderStyle.vertical, nil, style)
|
||||
_screen.SetContent(right-1, y, w.borderStyle.vertical, nil, style)
|
||||
|
@@ -66,6 +66,7 @@ const (
|
||||
Right
|
||||
Home
|
||||
End
|
||||
Insert
|
||||
|
||||
SUp
|
||||
SDown
|
||||
@@ -173,6 +174,8 @@ func (p ColorPair) Bg() Color {
|
||||
type ColorTheme struct {
|
||||
Fg Color
|
||||
Bg Color
|
||||
PreviewFg Color
|
||||
PreviewBg Color
|
||||
DarkBg Color
|
||||
Gutter Color
|
||||
Prompt Color
|
||||
@@ -207,7 +210,8 @@ type BorderShape int
|
||||
|
||||
const (
|
||||
BorderNone BorderShape = iota
|
||||
BorderAround
|
||||
BorderRounded
|
||||
BorderSharp
|
||||
BorderHorizontal
|
||||
)
|
||||
|
||||
@@ -225,6 +229,17 @@ type BorderCharacter int
|
||||
|
||||
func MakeBorderStyle(shape BorderShape, unicode bool) BorderStyle {
|
||||
if unicode {
|
||||
if shape == BorderRounded {
|
||||
return BorderStyle{
|
||||
shape: shape,
|
||||
horizontal: '─',
|
||||
vertical: '│',
|
||||
topLeft: '╭',
|
||||
topRight: '╮',
|
||||
bottomLeft: '╰',
|
||||
bottomRight: '╯',
|
||||
}
|
||||
}
|
||||
return BorderStyle{
|
||||
shape: shape,
|
||||
horizontal: '─',
|
||||
@@ -248,7 +263,7 @@ func MakeBorderStyle(shape BorderShape, unicode bool) BorderStyle {
|
||||
|
||||
func MakeTransparentBorder() BorderStyle {
|
||||
return BorderStyle{
|
||||
shape: BorderAround,
|
||||
shape: BorderRounded,
|
||||
horizontal: ' ',
|
||||
vertical: ' ',
|
||||
topLeft: ' ',
|
||||
@@ -272,7 +287,7 @@ type Renderer interface {
|
||||
MaxY() int
|
||||
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 {
|
||||
@@ -334,12 +349,16 @@ var (
|
||||
ColInfo ColorPair
|
||||
ColHeader ColorPair
|
||||
ColBorder ColorPair
|
||||
ColPreview ColorPair
|
||||
ColPreviewBorder ColorPair
|
||||
)
|
||||
|
||||
func EmptyTheme() *ColorTheme {
|
||||
return &ColorTheme{
|
||||
Fg: colUndefined,
|
||||
Bg: colUndefined,
|
||||
PreviewFg: colUndefined,
|
||||
PreviewBg: colUndefined,
|
||||
DarkBg: colUndefined,
|
||||
Gutter: colUndefined,
|
||||
Prompt: colUndefined,
|
||||
@@ -363,8 +382,10 @@ func init() {
|
||||
Default16 = &ColorTheme{
|
||||
Fg: colDefault,
|
||||
Bg: colDefault,
|
||||
PreviewFg: colUndefined,
|
||||
PreviewBg: colUndefined,
|
||||
DarkBg: colBlack,
|
||||
Gutter: colBlack,
|
||||
Gutter: colUndefined,
|
||||
Prompt: colBlue,
|
||||
Match: colGreen,
|
||||
Current: colYellow,
|
||||
@@ -378,6 +399,8 @@ func init() {
|
||||
Dark256 = &ColorTheme{
|
||||
Fg: colDefault,
|
||||
Bg: colDefault,
|
||||
PreviewFg: colUndefined,
|
||||
PreviewBg: colUndefined,
|
||||
DarkBg: 236,
|
||||
Gutter: colUndefined,
|
||||
Prompt: 110,
|
||||
@@ -393,6 +416,8 @@ func init() {
|
||||
Light256 = &ColorTheme{
|
||||
Fg: colDefault,
|
||||
Bg: colDefault,
|
||||
PreviewFg: colUndefined,
|
||||
PreviewBg: colUndefined,
|
||||
DarkBg: 251,
|
||||
Gutter: colUndefined,
|
||||
Prompt: 25,
|
||||
@@ -425,6 +450,8 @@ func initTheme(theme *ColorTheme, baseTheme *ColorTheme, forceBlack bool) {
|
||||
}
|
||||
theme.Fg = o(baseTheme.Fg, theme.Fg)
|
||||
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.Gutter = o(theme.DarkBg, o(baseTheme.Gutter, theme.Gutter))
|
||||
theme.Prompt = o(baseTheme.Prompt, theme.Prompt)
|
||||
@@ -461,6 +488,8 @@ func initPalette(theme *ColorTheme) {
|
||||
ColInfo = pair(theme.Info, theme.Bg)
|
||||
ColHeader = pair(theme.Header, theme.Bg)
|
||||
ColBorder = pair(theme.Border, theme.Bg)
|
||||
ColPreview = pair(theme.PreviewFg, theme.PreviewBg)
|
||||
ColPreviewBorder = pair(theme.Border, theme.PreviewBg)
|
||||
} else {
|
||||
ColPrompt = pair(colDefault, colDefault)
|
||||
ColNormal = pair(colDefault, colDefault)
|
||||
@@ -475,6 +504,8 @@ func initPalette(theme *ColorTheme) {
|
||||
ColInfo = pair(colDefault, colDefault)
|
||||
ColHeader = pair(colDefault, colDefault)
|
||||
ColBorder = pair(colDefault, colDefault)
|
||||
ColPreview = pair(colDefault, colDefault)
|
||||
ColPreviewBorder = pair(colDefault, colDefault)
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -130,6 +130,18 @@ func (chars *Chars) TrimLength() uint16 {
|
||||
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 {
|
||||
whitespaces := 0
|
||||
for i := chars.Length() - 1; i >= 0; i-- {
|
||||
|
@@ -112,3 +112,13 @@ func DurWithin(
|
||||
func IsTty() bool {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
}
|
||||
|
365
test/test_go.rb
365
test/test_go.rb
@@ -9,13 +9,23 @@ require 'minitest/autorun'
|
||||
require 'fileutils'
|
||||
require 'English'
|
||||
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
|
||||
|
||||
FILE = File.expand_path(__FILE__)
|
||||
base = File.expand_path('../../', __FILE__)
|
||||
Dir.chdir base
|
||||
FZF = "FZF_DEFAULT_OPTS= FZF_DEFAULT_COMMAND= #{base}/bin/fzf"
|
||||
BASE = File.expand_path('..', __dir__)
|
||||
Dir.chdir(BASE)
|
||||
FZF = "FZF_DEFAULT_OPTS= FZF_DEFAULT_COMMAND= #{BASE}/bin/fzf"
|
||||
|
||||
class NilClass
|
||||
def include?(_str)
|
||||
@@ -42,89 +52,71 @@ end
|
||||
|
||||
class Shell
|
||||
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
|
||||
'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
|
||||
|
||||
def zsh
|
||||
FileUtils.mkdir_p '/tmp/fzf-zsh'
|
||||
FileUtils.cp File.expand_path('~/.fzf.zsh'), '/tmp/fzf-zsh/.zshrc'
|
||||
'PS1= PROMPT_COMMAND= HISTSIZE=100 ZDOTDIR=/tmp/fzf-zsh zsh'
|
||||
@zsh ||=
|
||||
begin
|
||||
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
|
||||
|
||||
def fish
|
||||
'fish'
|
||||
UNSETS.map { |v| v + '= ' }.join + 'fish'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class Tmux
|
||||
TEMPNAME = '/tmp/fzf-test.txt'
|
||||
|
||||
attr_reader :win
|
||||
|
||||
def initialize(shell = :bash)
|
||||
@win =
|
||||
case shell
|
||||
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
|
||||
|
||||
@win = go(%W[new-window -d -P -F #I #{Shell.send(shell)}]).first
|
||||
go(%W[set-window-option -t #{@win} pane-base-index 0])
|
||||
return unless shell == :fish
|
||||
|
||||
send_keys('function fish_prompt; end; clear', :Enter)
|
||||
self.until(&:empty?)
|
||||
end
|
||||
|
||||
def kill
|
||||
go("kill-window -t #{win} 2> /dev/null")
|
||||
go(%W[kill-window -t #{win}])
|
||||
end
|
||||
|
||||
def send_keys(*args)
|
||||
target =
|
||||
if args.last.is_a?(Hash)
|
||||
hash = args.pop
|
||||
go("select-window -t #{win}")
|
||||
go(%W[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
|
||||
go(%W[send-keys -t #{target}] + args.map(&:to_s))
|
||||
end
|
||||
|
||||
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
|
||||
|
||||
def capture(pane = 0)
|
||||
File.unlink TEMPNAME while File.exist? TEMPNAME
|
||||
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
|
||||
go(%W[capture-pane -p -t #{win}.#{pane}]).reverse.drop_while(&:empty?).reverse
|
||||
end
|
||||
|
||||
def until(refresh = false, pane = 0)
|
||||
@@ -175,7 +167,7 @@ class Tmux
|
||||
tries = 0
|
||||
begin
|
||||
self.until do |lines|
|
||||
send_keys 'C-u', 'hello'
|
||||
send_keys ' ', 'C-u', 'hello', :Left, :Right
|
||||
lines[-1].end_with?('hello')
|
||||
end
|
||||
rescue StandardError
|
||||
@@ -186,8 +178,8 @@ class Tmux
|
||||
|
||||
private
|
||||
|
||||
def go(*args)
|
||||
`tmux #{args.join ' '}`.split($INPUT_RECORD_SEPARATOR)
|
||||
def go(args)
|
||||
IO.popen(['tmux'] + args) { |io| io.readlines(chomp: true) }
|
||||
end
|
||||
end
|
||||
|
||||
@@ -491,14 +483,14 @@ class TestGoFZF < TestBase
|
||||
end
|
||||
|
||||
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.send_keys :Enter
|
||||
assert_equal ['가나다'], readonce.split($INPUT_RECORD_SEPARATOR)
|
||||
end
|
||||
|
||||
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.send_keys 9
|
||||
tmux.until { |lines| lines[-2] == ' 19/100' }
|
||||
@@ -779,7 +771,7 @@ class TestGoFZF < TestBase
|
||||
end
|
||||
|
||||
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
|
||||
tmux.send_keys command, :Enter
|
||||
@@ -938,7 +930,7 @@ class TestGoFZF < TestBase
|
||||
|
||||
def 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 } }
|
||||
writelines tempname, %w[foo'bar foo"bar foo$bar]
|
||||
tmux.send_keys "cat #{tempname} | #{fzf opts}; sync", :Enter
|
||||
@@ -977,7 +969,7 @@ class TestGoFZF < TestBase
|
||||
|
||||
def 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]
|
||||
tmux.send_keys "cat #{tempname} | #{fzf opts}", :Enter
|
||||
tmux.until { |lines| lines[-2].include? '4/4' }
|
||||
@@ -1163,7 +1155,7 @@ class TestGoFZF < TestBase
|
||||
end
|
||||
|
||||
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)
|
||||
tmux.until do |lines|
|
||||
lines[-2].include?('100/100') &&
|
||||
@@ -1173,7 +1165,7 @@ class TestGoFZF < TestBase
|
||||
end
|
||||
|
||||
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)
|
||||
tmux.until do |lines|
|
||||
lines[1].include?('100/100') &&
|
||||
@@ -1183,7 +1175,7 @@ class TestGoFZF < TestBase
|
||||
end
|
||||
|
||||
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)
|
||||
tmux.until do |lines|
|
||||
lines[-2].include?('100/100') &&
|
||||
@@ -1193,7 +1185,7 @@ class TestGoFZF < TestBase
|
||||
end
|
||||
|
||||
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)
|
||||
tmux.until do |lines|
|
||||
lines[-2].include?('90/90') &&
|
||||
@@ -1203,7 +1195,7 @@ class TestGoFZF < TestBase
|
||||
end
|
||||
|
||||
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)
|
||||
tmux.until do |lines|
|
||||
lines[1].include?('90/90') &&
|
||||
@@ -1213,7 +1205,7 @@ class TestGoFZF < TestBase
|
||||
end
|
||||
|
||||
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)
|
||||
tmux.until do |lines|
|
||||
lines[-2].include?('90/90') &&
|
||||
@@ -1324,7 +1316,7 @@ class TestGoFZF < TestBase
|
||||
|
||||
def test_exitstatus_empty
|
||||
{ '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.send_keys :Enter
|
||||
tmux.until { |lines| lines.last.include? "--#{status}--" }
|
||||
@@ -1407,6 +1399,39 @@ class TestGoFZF < TestBase
|
||||
assert_equal '3', readonce.chomp
|
||||
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
|
||||
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}') }
|
||||
@@ -1436,7 +1461,7 @@ class TestGoFZF < TestBase
|
||||
end
|
||||
|
||||
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.send_keys '?'
|
||||
tmux.until { |lines| lines[-2] =~ / {1-1-1-[0-9]+}/ }
|
||||
@@ -1640,11 +1665,80 @@ class TestGoFZF < TestBase
|
||||
tmux.until { |lines| lines.item_count == 553 && lines.match_count == 1 }
|
||||
tmux.until { |lines| !lines[-2].include?('(1/2)') }
|
||||
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
|
||||
|
||||
module TestShell
|
||||
def setup
|
||||
super
|
||||
@tmux = Tmux.new shell
|
||||
tmux.prepare
|
||||
end
|
||||
|
||||
def teardown
|
||||
@@ -1704,7 +1798,7 @@ module TestShell
|
||||
tmux.until { |lines| lines.select_count == 2 }
|
||||
|
||||
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.until { |lines| lines.any_include?(/^fzf-unicode.*1.*fzf-unicode.*2/) }
|
||||
end
|
||||
@@ -1761,6 +1855,37 @@ module TestShell
|
||||
tmux.until { |lines| lines[-1] == '3rd' }
|
||||
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)
|
||||
(times - 1).times do
|
||||
begin
|
||||
@@ -1890,7 +2015,7 @@ module CompletionTest
|
||||
end
|
||||
|
||||
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.send_keys 'ls /tmp/**', :Tab
|
||||
tmux.until { |lines| lines.match_count == 11 }
|
||||
@@ -1921,7 +2046,7 @@ module CompletionTest
|
||||
|
||||
def test_file_completion_unicode
|
||||
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.send_keys 'cat fzf-unicode**', :Tab
|
||||
tmux.until { |lines| lines.match_count == 2 }
|
||||
@@ -1944,23 +2069,41 @@ module CompletionTest
|
||||
tmux.send_keys :Enter
|
||||
tmux.until { |lines| lines[-1].include? 'test3test4' }
|
||||
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
|
||||
|
||||
class TestBash < TestBase
|
||||
include TestShell
|
||||
include CompletionTest
|
||||
|
||||
def shell
|
||||
:bash
|
||||
end
|
||||
|
||||
def new_shell
|
||||
tmux.prepare
|
||||
tmux.send_keys "FZF_TMUX=1 #{Shell.bash}", :Enter
|
||||
tmux.prepare
|
||||
end
|
||||
|
||||
def setup
|
||||
super
|
||||
@tmux = Tmux.new :bash
|
||||
end
|
||||
|
||||
def test_dynamic_completion_loader
|
||||
tmux.paste 'touch /tmp/foo; _fzf_completion_loader=1'
|
||||
tmux.paste '_completion_loader() { complete -o default fake; }'
|
||||
@@ -1983,20 +2126,23 @@ class TestZsh < TestBase
|
||||
include TestShell
|
||||
include CompletionTest
|
||||
|
||||
def shell
|
||||
:zsh
|
||||
end
|
||||
|
||||
def new_shell
|
||||
tmux.send_keys "FZF_TMUX=1 #{Shell.zsh}", :Enter
|
||||
tmux.prepare
|
||||
end
|
||||
|
||||
def setup
|
||||
super
|
||||
@tmux = Tmux.new :zsh
|
||||
end
|
||||
end
|
||||
|
||||
class TestFish < TestBase
|
||||
include TestShell
|
||||
|
||||
def shell
|
||||
:fish
|
||||
end
|
||||
|
||||
def new_shell
|
||||
tmux.send_keys 'env FZF_TMUX=1 fish', :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.prepare
|
||||
end
|
||||
|
||||
def setup
|
||||
super
|
||||
@tmux = Tmux.new :fish
|
||||
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 "--multi --prompt \"prompt-f> \"" "$@" < <(
|
||||
echo foo
|
||||
echo bar
|
||||
)
|
||||
}
|
||||
|
||||
# New API
|
||||
_fzf_complete_g() {
|
||||
_fzf_complete --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