mirror of
https://github.com/junegunn/fzf.git
synced 2025-12-07 05:14:25 +08:00
Compare commits
133 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
390b49653b | ||
|
|
b877c385f0 | ||
|
|
9c47739c0e | ||
|
|
04aa2992e7 | ||
|
|
2f1edeff78 | ||
|
|
306d51cdcf | ||
|
|
54a026525a | ||
|
|
d6588fc835 | ||
|
|
5a7b41a2cf | ||
|
|
338a73d764 | ||
|
|
c20954f020 | ||
|
|
1e8e1d3c9d | ||
|
|
f6b1962056 | ||
|
|
b3b101a89c | ||
|
|
9615c4edf1 | ||
|
|
85a75ee035 | ||
|
|
1e5bd55672 | ||
|
|
37d4015d56 | ||
|
|
6b27554cdb | ||
|
|
fc1b119159 | ||
|
|
2cd0d4a9f7 | ||
|
|
fd03aabeb2 | ||
|
|
8068c975c2 | ||
|
|
a6d2ab3360 | ||
|
|
fe7b91dfd9 | ||
|
|
5784101bea | ||
|
|
eaf6eb8978 | ||
|
|
3af63bcf1f | ||
|
|
80a21f7a75 | ||
|
|
0b33dc6ce1 | ||
|
|
64a6ced62e | ||
|
|
438f6c96cd | ||
|
|
6ae085f974 | ||
|
|
cb8e97274e | ||
|
|
c4185e81e8 | ||
|
|
0580fe9046 | ||
|
|
1b1bc9ea36 | ||
|
|
c2614467cf | ||
|
|
077ae51f05 | ||
|
|
ee40212e97 | ||
|
|
7f5f6efbac | ||
|
|
45d4c57d91 | ||
|
|
41e0208335 | ||
|
|
2f8238342b | ||
|
|
e1582b8323 | ||
|
|
7cfa6f0265 | ||
|
|
e3973c74e7 | ||
|
|
a8deca2dd9 | ||
|
|
a78ade1771 | ||
|
|
79d2ef4616 | ||
|
|
5edc3f755c | ||
|
|
288976310b | ||
|
|
58b5be8ab6 | ||
|
|
26d7896877 | ||
|
|
fd6bc7308f | ||
|
|
6c41c95f28 | ||
|
|
446e04469d | ||
|
|
5097e563df | ||
|
|
c7ad97c641 | ||
|
|
9516fe3324 | ||
|
|
20cdbac8c3 | ||
|
|
e3e7b3360c | ||
|
|
655dfb8328 | ||
|
|
9b9c67b768 | ||
|
|
5b7457ff08 | ||
|
|
48adad5454 | ||
|
|
b27dc3eb17 | ||
|
|
e89eebb7ba | ||
|
|
fee404399a | ||
|
|
6b4805ca1a | ||
|
|
159699b5d7 | ||
|
|
af809c9661 | ||
|
|
329de8f416 | ||
|
|
e825b07e85 | ||
|
|
71fdb99a07 | ||
|
|
55ee4186aa | ||
|
|
941b0a0ff7 | ||
|
|
6aae12288e | ||
|
|
302cc552ef | ||
|
|
a2a4df0886 | ||
|
|
3399e39968 | ||
|
|
87874bba88 | ||
|
|
c304fc4333 | ||
|
|
6977cf268f | ||
|
|
931c78a70c | ||
|
|
8d23646fe6 | ||
|
|
656963e018 | ||
|
|
644277faf1 | ||
|
|
0558dfee79 | ||
|
|
487c8fe88f | ||
|
|
0d171ba1d8 | ||
|
|
2069bbc8b5 | ||
|
|
053d628b53 | ||
|
|
6bc592e6c9 | ||
|
|
6c76d8cd1c | ||
|
|
a09e411936 | ||
|
|
02a7b96f33 | ||
|
|
e55e029ae8 | ||
|
|
6b18b144cf | ||
|
|
6d53089cc1 | ||
|
|
e85a8a68d0 | ||
|
|
dc55e68524 | ||
|
|
462c68b625 | ||
|
|
999d374f0c | ||
|
|
b208aa675e | ||
|
|
2b98fee136 | ||
|
|
e5e75efebc | ||
|
|
4a4fef2daf | ||
|
|
ecb6b234cc | ||
|
|
39dbc8acdb | ||
|
|
a56489bc7f | ||
|
|
99927c7071 | ||
|
|
3e28403978 | ||
|
|
37370f057f | ||
|
|
f4b46fad27 | ||
|
|
9d2c6a95f4 | ||
|
|
376a76d1d3 | ||
|
|
1fcc07e54e | ||
|
|
8db3345c2f | ||
|
|
69aa2fea68 | ||
|
|
298749bfcd | ||
|
|
f1f31baae1 | ||
|
|
e1c8f19e8f | ||
|
|
5e302c70e9 | ||
|
|
4c5a679066 | ||
|
|
41f0b2c354 | ||
|
|
a0a3c349c9 | ||
|
|
bc3983181d | ||
|
|
980b58ef5a | ||
|
|
a2604c0963 | ||
|
|
6dbc108da2 | ||
|
|
bd98f988f0 | ||
|
|
06301c7847 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,4 +1,5 @@
|
|||||||
bin/fzf
|
bin/fzf
|
||||||
|
bin/fzf.exe
|
||||||
target
|
target
|
||||||
pkg
|
pkg
|
||||||
Gemfile.lock
|
Gemfile.lock
|
||||||
|
|||||||
@@ -1,19 +1,18 @@
|
|||||||
language: ruby
|
language: ruby
|
||||||
|
dist: trusty
|
||||||
|
sudo: required
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- env: TAGS=
|
- env: TAGS=
|
||||||
rvm: 2.3.3
|
rvm: 2.3.3
|
||||||
# - env: TAGS=tcell
|
# - env: TAGS=tcell
|
||||||
# rvm: 2.2.0
|
# rvm: 2.3.3
|
||||||
|
|
||||||
install:
|
install:
|
||||||
- sudo apt-get update
|
|
||||||
- sudo apt-get install -y libncurses-dev lib32ncurses5-dev libgpm-dev
|
|
||||||
- sudo add-apt-repository -y ppa:pi-rho/dev
|
- sudo add-apt-repository -y ppa:pi-rho/dev
|
||||||
- sudo apt-add-repository -y ppa:fish-shell/release-2
|
- sudo apt-add-repository -y ppa:fish-shell/release-2
|
||||||
- sudo apt-get update
|
- sudo apt-get update
|
||||||
- sudo apt-get install -y tmux=1.9a-1~ppa1~p
|
- sudo apt-get install -y tmux zsh fish
|
||||||
- sudo apt-get install -y zsh fish
|
|
||||||
|
|
||||||
script: |
|
script: |
|
||||||
make test install &&
|
make test install &&
|
||||||
|
|||||||
55
CHANGELOG.md
55
CHANGELOG.md
@@ -1,6 +1,61 @@
|
|||||||
CHANGELOG
|
CHANGELOG
|
||||||
=========
|
=========
|
||||||
|
|
||||||
|
0.17.3
|
||||||
|
------
|
||||||
|
- `$LINES` and `$COLUMNS` are exported to preview command so that the command
|
||||||
|
knows the exact size of the preview window.
|
||||||
|
- Better error messages when the default command or `$FZF_DEFAULT_COMMAND`
|
||||||
|
fails.
|
||||||
|
- Reverted #1061 to avoid having duplicate entries in the list when find
|
||||||
|
command detected a file system loop (#1120). The default command now
|
||||||
|
requires that find supports `-fstype` option.
|
||||||
|
- fzf now distinguishes mouse left click and right click (#1130)
|
||||||
|
- Right click is now bound to `toggle` action by default
|
||||||
|
- `--bind` understands `left-click` and `right-click`
|
||||||
|
- Added `replace-query` action (#1137)
|
||||||
|
- Replaces query string with the current selection
|
||||||
|
- Added `accept-non-empty` action (#1162)
|
||||||
|
- Same as accept, except that it prevents fzf from exiting without any
|
||||||
|
selection
|
||||||
|
|
||||||
|
0.17.1
|
||||||
|
------
|
||||||
|
|
||||||
|
- Fixed custom background color of preview window (#1046)
|
||||||
|
- Fixed background color issues of Windows binary
|
||||||
|
- Fixed Windows binary to execute command using cmd.exe with no parsing and
|
||||||
|
escaping (#1072)
|
||||||
|
- Added support for `window` layout on Vim 8 using Vim 8 terminal (#1055)
|
||||||
|
|
||||||
|
0.17.0-2
|
||||||
|
--------
|
||||||
|
|
||||||
|
A maintenance release for auxiliary scripts. fzf binaries are not updated.
|
||||||
|
|
||||||
|
- Experimental support for the builtin terminal of Vim 8
|
||||||
|
- fzf can now run inside GVim
|
||||||
|
- Updated Vim plugin to better handle `&shell` issue on fish
|
||||||
|
- Fixed a bug of fzf-tmux where invalid output is generated
|
||||||
|
- Fixed fzf-tmux to work even when `tput` does not work
|
||||||
|
|
||||||
|
0.17.0
|
||||||
|
------
|
||||||
|
- Performance optimization
|
||||||
|
- One can match literal spaces in extended-search mode with a space prepended
|
||||||
|
by a backslash.
|
||||||
|
- `--expect` is now additive and can be specified multiple times.
|
||||||
|
|
||||||
|
0.16.11
|
||||||
|
-------
|
||||||
|
- Performance optimization
|
||||||
|
- Fixed missing preview update
|
||||||
|
|
||||||
|
0.16.10
|
||||||
|
-------
|
||||||
|
- Fixed invalid handling of ANSI colors in preview window
|
||||||
|
- Further improved `--ansi` performance
|
||||||
|
|
||||||
0.16.9
|
0.16.9
|
||||||
------
|
------
|
||||||
- Memory and performance optimization
|
- Memory and performance optimization
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ Note that the environment variables `FZF_DEFAULT_COMMAND` and
|
|||||||
- `g:fzf_action`
|
- `g:fzf_action`
|
||||||
- Customizable extra key bindings for opening selected files in different ways
|
- Customizable extra key bindings for opening selected files in different ways
|
||||||
- `g:fzf_layout`
|
- `g:fzf_layout`
|
||||||
- Determines the size and position of fzf window (tmux pane or Neovim split)
|
- Determines the size and position of fzf window
|
||||||
- `g:fzf_colors`
|
- `g:fzf_colors`
|
||||||
- Customizes fzf colors to match the current color scheme
|
- Customizes fzf colors to match the current color scheme
|
||||||
- `g:fzf_history_dir`
|
- `g:fzf_history_dir`
|
||||||
@@ -55,11 +55,24 @@ let g:fzf_action = {
|
|||||||
\ 'ctrl-x': 'split',
|
\ 'ctrl-x': 'split',
|
||||||
\ 'ctrl-v': 'vsplit' }
|
\ 'ctrl-v': 'vsplit' }
|
||||||
|
|
||||||
|
" An action can be a reference to a function that processes selected lines
|
||||||
|
function! s:build_quickfix_list(lines)
|
||||||
|
call setqflist(map(copy(a:lines), '{ "filename": v:val }'))
|
||||||
|
copen
|
||||||
|
cc
|
||||||
|
endfunction
|
||||||
|
|
||||||
|
let g:fzf_action = {
|
||||||
|
\ 'ctrl-q': function('s:build_quickfix_list'),
|
||||||
|
\ 'ctrl-t': 'tab split',
|
||||||
|
\ 'ctrl-x': 'split',
|
||||||
|
\ 'ctrl-v': 'vsplit' }
|
||||||
|
|
||||||
" Default fzf layout
|
" Default fzf layout
|
||||||
" - down / up / left / right
|
" - down / up / left / right
|
||||||
let g:fzf_layout = { 'down': '~40%' }
|
let g:fzf_layout = { 'down': '~40%' }
|
||||||
|
|
||||||
" In Neovim, you can set up fzf window using a Vim command
|
" You can set up fzf window using a Vim command (Neovim or latest Vim 8 required)
|
||||||
let g:fzf_layout = { 'window': 'enew' }
|
let g:fzf_layout = { 'window': 'enew' }
|
||||||
let g:fzf_layout = { 'window': '-tabnew' }
|
let g:fzf_layout = { 'window': '-tabnew' }
|
||||||
let g:fzf_layout = { 'window': '10split enew' }
|
let g:fzf_layout = { 'window': '10split enew' }
|
||||||
@@ -73,6 +86,7 @@ let g:fzf_colors =
|
|||||||
\ 'bg+': ['bg', 'CursorLine', 'CursorColumn'],
|
\ 'bg+': ['bg', 'CursorLine', 'CursorColumn'],
|
||||||
\ 'hl+': ['fg', 'Statement'],
|
\ 'hl+': ['fg', 'Statement'],
|
||||||
\ 'info': ['fg', 'PreProc'],
|
\ 'info': ['fg', 'PreProc'],
|
||||||
|
\ 'border': ['fg', 'Ignore'],
|
||||||
\ 'prompt': ['fg', 'Conditional'],
|
\ 'prompt': ['fg', 'Conditional'],
|
||||||
\ 'pointer': ['fg', 'Exception'],
|
\ 'pointer': ['fg', 'Exception'],
|
||||||
\ 'marker': ['fg', 'Keyword'],
|
\ 'marker': ['fg', 'Keyword'],
|
||||||
@@ -102,7 +116,7 @@ following options.
|
|||||||
| `options` | string/list | Options to fzf |
|
| `options` | string/list | Options to fzf |
|
||||||
| `dir` | string | Working directory |
|
| `dir` | string | Working directory |
|
||||||
| `up`/`down`/`left`/`right` | number/string | Use tmux pane with the given size (e.g. `20`, `50%`) |
|
| `up`/`down`/`left`/`right` | number/string | Use tmux pane with the given size (e.g. `20`, `50%`) |
|
||||||
| `window` (*Neovim only*) | string | Command to open fzf window (e.g. `vertical aboveleft 30new`) |
|
| `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` | string | External terminal emulator to start fzf with (GVim only) |
|
||||||
| `launcher` | funcref | Function for generating `launcher` string (GVim only) |
|
| `launcher` | funcref | Function for generating `launcher` string (GVim only) |
|
||||||
|
|
||||||
@@ -128,11 +142,38 @@ command! -bang MyStuff
|
|||||||
\ call fzf#run(fzf#wrap('my-stuff', {'dir': '~/my-stuff'}, <bang>0))
|
\ call fzf#run(fzf#wrap('my-stuff', {'dir': '~/my-stuff'}, <bang>0))
|
||||||
```
|
```
|
||||||
|
|
||||||
|
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
|
||||||
|
- `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.
|
||||||
|
|
||||||
|
```vim
|
||||||
|
autocmd! FileType fzf
|
||||||
|
autocmd FileType fzf set laststatus=0 noshowmode noruler
|
||||||
|
\| autocmd BufLeave <buffer> set laststatus=2 showmode ruler
|
||||||
|
```
|
||||||
|
|
||||||
GVim
|
GVim
|
||||||
----
|
----
|
||||||
|
|
||||||
In GVim, you need an external terminal emulator to start fzf with. `xterm`
|
With the latest version of GVim, fzf will start inside the builtin terminal
|
||||||
command is used by default, but you can customize it with `g:fzf_launcher`.
|
emulator of Vim. Please note that this terminal feature of Vim is still young
|
||||||
|
and unstable and you may run into some issues.
|
||||||
|
|
||||||
|
If you have an older version of GVim, you need an external terminal emulator
|
||||||
|
to start fzf with. `xterm` command is used by default, but you can customize
|
||||||
|
it with `g:fzf_launcher`.
|
||||||
|
|
||||||
```vim
|
```vim
|
||||||
" This is the default. %s is replaced with fzf command
|
" This is the default. %s is replaced with fzf command
|
||||||
|
|||||||
223
README.md
223
README.md
@@ -3,15 +3,19 @@
|
|||||||
|
|
||||||
fzf is a general-purpose command-line fuzzy finder.
|
fzf is a general-purpose command-line fuzzy finder.
|
||||||
|
|
||||||

|
<img src="https://raw.githubusercontent.com/junegunn/i/master/fzf-preview.png" width=640>
|
||||||
|
|
||||||
|
It's an interactive Unix filter for command-line that can be used with any
|
||||||
|
list; files, command history, processes, hostnames, bookmarks, git commits,
|
||||||
|
etc.
|
||||||
|
|
||||||
Pros
|
Pros
|
||||||
----
|
----
|
||||||
|
|
||||||
- No dependencies
|
- Portable, no dependencies
|
||||||
- Blazingly fast
|
- Blazingly fast
|
||||||
- The most comprehensive feature set
|
- The most comprehensive feature set
|
||||||
- Flexible layout using tmux panes
|
- Flexible layout
|
||||||
- Batteries included
|
- Batteries included
|
||||||
- Vim/Neovim plugin, key bindings and fuzzy auto-completion
|
- Vim/Neovim plugin, key bindings and fuzzy auto-completion
|
||||||
|
|
||||||
@@ -19,9 +23,10 @@ Table of Contents
|
|||||||
-----------------
|
-----------------
|
||||||
|
|
||||||
* [Installation](#installation)
|
* [Installation](#installation)
|
||||||
|
* [Using Homebrew or Linuxbrew](#using-homebrew-or-linuxbrew)
|
||||||
* [Using git](#using-git)
|
* [Using git](#using-git)
|
||||||
* [Using Homebrew](#using-homebrew)
|
|
||||||
* [As Vim plugin](#as-vim-plugin)
|
* [As Vim plugin](#as-vim-plugin)
|
||||||
|
* [Fedora](#fedora)
|
||||||
* [Windows](#windows)
|
* [Windows](#windows)
|
||||||
* [Upgrading fzf](#upgrading-fzf)
|
* [Upgrading fzf](#upgrading-fzf)
|
||||||
* [Building fzf](#building-fzf)
|
* [Building fzf](#building-fzf)
|
||||||
@@ -42,8 +47,12 @@ Table of Contents
|
|||||||
* [Settings](#settings)
|
* [Settings](#settings)
|
||||||
* [Supported commands](#supported-commands)
|
* [Supported commands](#supported-commands)
|
||||||
* [Vim plugin](#vim-plugin)
|
* [Vim plugin](#vim-plugin)
|
||||||
|
* [Advanced topics](#advanced-topics)
|
||||||
|
* [Performance](#performance)
|
||||||
|
* [Executing external programs](#executing-external-programs)
|
||||||
|
* [Preview window](#preview-window)
|
||||||
* [Tips](#tips)
|
* [Tips](#tips)
|
||||||
* [Respecting .gitignore, <code>.hgignore</code>, and <code>svn:ignore</code>](#respecting-gitignore-hgignore-and-svnignore)
|
* [Respecting .gitignore](#respecting-gitignore)
|
||||||
* [git ls-tree for fast traversal](#git-ls-tree-for-fast-traversal)
|
* [git ls-tree for fast traversal](#git-ls-tree-for-fast-traversal)
|
||||||
* [Fish shell](#fish-shell)
|
* [Fish shell](#fish-shell)
|
||||||
* [<a href="LICENSE">License</a>](#license)
|
* [<a href="LICENSE">License</a>](#license)
|
||||||
@@ -65,9 +74,21 @@ stuff.
|
|||||||
|
|
||||||
[bin]: https://github.com/junegunn/fzf-bin/releases
|
[bin]: https://github.com/junegunn/fzf-bin/releases
|
||||||
|
|
||||||
|
### Using Homebrew or Linuxbrew
|
||||||
|
|
||||||
|
You can use [Homebrew](http://brew.sh/) or [Linuxbrew](http://linuxbrew.sh/)
|
||||||
|
to install fzf.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
brew install fzf
|
||||||
|
|
||||||
|
# To install useful key bindings and fuzzy completion:
|
||||||
|
$(brew --prefix)/opt/fzf/install
|
||||||
|
```
|
||||||
|
|
||||||
### Using git
|
### Using git
|
||||||
|
|
||||||
Clone this repository and run
|
Alternatively, you can "git clone" this repository to any directory and run
|
||||||
[install](https://github.com/junegunn/fzf/blob/master/install) script.
|
[install](https://github.com/junegunn/fzf/blob/master/install) script.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
@@ -75,36 +96,55 @@ git clone --depth 1 https://github.com/junegunn/fzf.git ~/.fzf
|
|||||||
~/.fzf/install
|
~/.fzf/install
|
||||||
```
|
```
|
||||||
|
|
||||||
### Using Homebrew
|
|
||||||
|
|
||||||
On OS X, you can use [Homebrew](http://brew.sh/) to install fzf.
|
|
||||||
|
|
||||||
```sh
|
|
||||||
brew install fzf
|
|
||||||
|
|
||||||
# Install shell extensions
|
|
||||||
/usr/local/opt/fzf/install
|
|
||||||
```
|
|
||||||
|
|
||||||
### As Vim plugin
|
### As Vim plugin
|
||||||
|
|
||||||
You can manually add the directory to `&runtimepath` as follows,
|
Once you have fzf installed, you can enable it inside Vim simply by adding the
|
||||||
|
directory to `&runtimepath` as follows:
|
||||||
|
|
||||||
```vim
|
```vim
|
||||||
" If installed using git
|
|
||||||
set rtp+=~/.fzf
|
|
||||||
|
|
||||||
" If installed using Homebrew
|
" If installed using Homebrew
|
||||||
set rtp+=/usr/local/opt/fzf
|
set rtp+=/usr/local/opt/fzf
|
||||||
|
|
||||||
|
" If installed using git
|
||||||
|
set rtp+=~/.fzf
|
||||||
```
|
```
|
||||||
|
|
||||||
But it's recommended that you use a plugin manager like
|
If you use [vim-plug](https://github.com/junegunn/vim-plug), the same can be
|
||||||
[vim-plug](https://github.com/junegunn/vim-plug).
|
written as:
|
||||||
|
|
||||||
```vim
|
```vim
|
||||||
Plug 'junegunn/fzf', { 'dir': '~/.fzf', 'do': './install --all' }
|
" 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 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 install script if you use fzf only in Vim.
|
||||||
|
```
|
||||||
|
|
||||||
|
### 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 for more information.
|
||||||
|
|
||||||
|
|
||||||
### Windows
|
### Windows
|
||||||
|
|
||||||
Pre-built binaries for Windows can be downloaded [here][bin]. fzf is also
|
Pre-built binaries for Windows can be downloaded [here][bin]. fzf is also
|
||||||
@@ -116,10 +156,12 @@ available as a [Chocolatey package][choco].
|
|||||||
choco install fzf
|
choco install fzf
|
||||||
```
|
```
|
||||||
|
|
||||||
However, other components of the project may not work on Windows. You might
|
However, other components of the project may not work on Windows. Known issues
|
||||||
want to consider installing fzf on [Windows Subsystem for Linux][wsl] where
|
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.
|
everything runs flawlessly.
|
||||||
|
|
||||||
|
[windows-wiki]: https://github.com/junegunn/fzf/wiki/Windows
|
||||||
[wsl]: https://blogs.msdn.microsoft.com/wsl/
|
[wsl]: https://blogs.msdn.microsoft.com/wsl/
|
||||||
|
|
||||||
Upgrading fzf
|
Upgrading fzf
|
||||||
@@ -220,7 +262,7 @@ or `py`.
|
|||||||
|
|
||||||
- `FZF_DEFAULT_COMMAND`
|
- `FZF_DEFAULT_COMMAND`
|
||||||
- Default command to use when input is tty
|
- Default command to use when input is tty
|
||||||
- e.g. `export FZF_DEFAULT_COMMAND='ag -g ""'`
|
- e.g. `export FZF_DEFAULT_COMMAND='fd --type f'`
|
||||||
- `FZF_DEFAULT_OPTS`
|
- `FZF_DEFAULT_OPTS`
|
||||||
- Default options
|
- Default options
|
||||||
- e.g. `export FZF_DEFAULT_OPTS="--reverse --inline-info"`
|
- e.g. `export FZF_DEFAULT_OPTS="--reverse --inline-info"`
|
||||||
@@ -358,12 +400,17 @@ export FZF_COMPLETION_TRIGGER='~~'
|
|||||||
# Options to fzf command
|
# Options to fzf command
|
||||||
export FZF_COMPLETION_OPTS='+c -x'
|
export FZF_COMPLETION_OPTS='+c -x'
|
||||||
|
|
||||||
# Use ag instead of the default find command for listing candidates.
|
# Use fd (https://github.com/sharkdp/fd) instead of the default find
|
||||||
# - The first argument to the function is the base path to start traversal
|
# command for listing path candidates.
|
||||||
# - Note that ag only lists files not directories
|
# - The first argument to the function ($1) is the base path to start traversal
|
||||||
# - See the source code (completion.{bash,zsh}) for the details.
|
# - See the source code (completion.{bash,zsh}) for the details.
|
||||||
_fzf_compgen_path() {
|
_fzf_compgen_path() {
|
||||||
ag -g "" "$1"
|
fd --hidden --follow --exclude ".git" . "$1"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Use fd to generate the list for directory completion
|
||||||
|
_fzf_compgen_dir() {
|
||||||
|
fd --type d --hidden --follow --exclude ".git" . "$1"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -383,33 +430,117 @@ Vim plugin
|
|||||||
|
|
||||||
See [README-VIM.md](README-VIM.md).
|
See [README-VIM.md](README-VIM.md).
|
||||||
|
|
||||||
|
Advanced topics
|
||||||
|
---------------
|
||||||
|
|
||||||
|
### Performance
|
||||||
|
|
||||||
|
fzf is fast, and is [getting even faster][perf]. Performance should not be
|
||||||
|
a problem in most use cases. However, you might want to be aware of the
|
||||||
|
options that affect the performance.
|
||||||
|
|
||||||
|
- `--ansi` tells fzf to extract and parse ANSI color codes in the input and it
|
||||||
|
makes the initial scanning slower. So it's not recommended that you add it
|
||||||
|
to your `$FZF_DEFAULT_OPTS`.
|
||||||
|
- `--nth` makes fzf slower as fzf has to tokenize each line.
|
||||||
|
- `--with-nth` makes fzf slower as fzf has to tokenize and reassemble each
|
||||||
|
line.
|
||||||
|
- If you absolutely need better performance, you can consider using
|
||||||
|
`--algo=v1` (the default being `v2`) to make fzf use faster greedy
|
||||||
|
algorithm. However, this algorithm is not guaranteed to find the optimal
|
||||||
|
ordering of the matches and is not recommended.
|
||||||
|
|
||||||
|
[perf]: https://junegunn.kr/images/fzf-0.17.0.png
|
||||||
|
|
||||||
|
### Executing external programs
|
||||||
|
|
||||||
|
You can set up key bindings for starting external processes without leaving
|
||||||
|
fzf (`execute`, `execute-silent`).
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Press F1 to open the file with less without leaving fzf
|
||||||
|
# Press CTRL-Y to copy the line to clipboard and aborts fzf (requires pbcopy)
|
||||||
|
fzf --bind 'f1:execute(less -f {}),ctrl-y:execute-silent(echo {} | pbcopy)+abort'
|
||||||
|
```
|
||||||
|
|
||||||
|
See *KEY BINDINGS* section of the man page for details.
|
||||||
|
|
||||||
|
### Preview window
|
||||||
|
|
||||||
|
When `--preview` option is set, fzf automatically starts external process with
|
||||||
|
the current line as the argument and shows the result in the split window.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# {} is replaced to the single-quoted string of the focused line
|
||||||
|
fzf --preview 'cat {}'
|
||||||
|
```
|
||||||
|
|
||||||
|
Since preview window is updated only after the process is complete, it's
|
||||||
|
important that the command finishes quickly.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Use head instead of cat so that the command doesn't take too long to finish
|
||||||
|
fzf --preview 'head -100 {}'
|
||||||
|
```
|
||||||
|
|
||||||
|
Preview window supports ANSI colors, so you can use programs that
|
||||||
|
syntax-highlights the content of a file.
|
||||||
|
|
||||||
|
- Highlight: http://www.andre-simon.de/doku/highlight/en/highlight.php
|
||||||
|
- CodeRay: http://coderay.rubychan.de/
|
||||||
|
- Rouge: https://github.com/jneen/rouge
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Try highlight, coderay, rougify in turn, then fall back to cat
|
||||||
|
fzf --preview '[[ $(file --mime {}) =~ binary ]] &&
|
||||||
|
echo {} is a binary file ||
|
||||||
|
(highlight -O ansi -l {} ||
|
||||||
|
coderay {} ||
|
||||||
|
rougify {} ||
|
||||||
|
cat {}) 2> /dev/null | head -500'
|
||||||
|
```
|
||||||
|
|
||||||
|
You can customize the size and position of the preview window using
|
||||||
|
`--preview-window` option. For example,
|
||||||
|
|
||||||
|
```bash
|
||||||
|
fzf --height 40% --reverse --preview 'file {}' --preview-window down:1
|
||||||
|
```
|
||||||
|
|
||||||
|
For more advanced examples, see [Key bindings for git with fzf][fzf-git].
|
||||||
|
|
||||||
|
[fzf-git]: https://junegunn.kr/2016/07/fzf-git/
|
||||||
|
|
||||||
Tips
|
Tips
|
||||||
----
|
----
|
||||||
|
|
||||||
#### Respecting `.gitignore`, `.hgignore`, and `svn:ignore`
|
#### Respecting `.gitignore`
|
||||||
|
|
||||||
[ag](https://github.com/ggreer/the_silver_searcher) or
|
You can use [fd](https://github.com/sharkdp/fd),
|
||||||
[pt](https://github.com/monochromegane/the_platinum_searcher) will do the
|
[ripgrep](https://github.com/BurntSushi/ripgrep), or [the silver
|
||||||
filtering:
|
searcher](https://github.com/ggreer/the_silver_searcher) instead of the
|
||||||
|
default find command to traverse the file system while respecting
|
||||||
|
`.gitignore`.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
# Feed the output of ag into fzf
|
# Feed the output of fd into fzf
|
||||||
ag -g "" | fzf
|
fd --type f | fzf
|
||||||
|
|
||||||
# Setting ag as the default source for fzf
|
# Setting fd as the default source for fzf
|
||||||
export FZF_DEFAULT_COMMAND='ag -g ""'
|
export FZF_DEFAULT_COMMAND='fd --type f'
|
||||||
|
|
||||||
# Now fzf (w/o pipe) will use ag instead of find
|
# Now fzf (w/o pipe) will use fd instead of find
|
||||||
fzf
|
fzf
|
||||||
|
|
||||||
# To apply the command to CTRL-T as well
|
# To apply the command to CTRL-T as well
|
||||||
export FZF_CTRL_T_COMMAND="$FZF_DEFAULT_COMMAND"
|
export FZF_CTRL_T_COMMAND="$FZF_DEFAULT_COMMAND"
|
||||||
```
|
```
|
||||||
|
|
||||||
If you don't want to exclude hidden files, use the following command:
|
If you want the command to follow symbolic links, and don't want it to exclude
|
||||||
|
hidden files, use the following command:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
export FZF_DEFAULT_COMMAND='ag --hidden --ignore .git -g ""'
|
export FZF_DEFAULT_COMMAND='fd --type f --hidden --follow --exclude .git'
|
||||||
```
|
```
|
||||||
|
|
||||||
#### `git ls-tree` for fast traversal
|
#### `git ls-tree` for fast traversal
|
||||||
@@ -426,10 +557,10 @@ export FZF_DEFAULT_COMMAND='
|
|||||||
|
|
||||||
#### Fish shell
|
#### Fish shell
|
||||||
|
|
||||||
It's [a known bug of fish](https://github.com/fish-shell/fish-shell/issues/1362)
|
Fish shell before version 2.6.0 [doesn't allow](https://github.com/fish-shell/fish-shell/issues/1362)
|
||||||
(will be fixed in 2.6.0) that it doesn't allow reading from STDIN in command
|
reading from STDIN in command substitution, which means simple `vim (fzf)`
|
||||||
substitution, which means simple `vim (fzf)` won't work as expected. The
|
doesn't work as expected. The workaround for fish 2.5.0 and earlier is to use
|
||||||
workaround is to use the `read` fish command:
|
the `read` fish command:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
fzf | read -l result; and vim $result
|
fzf | read -l result; and vim $result
|
||||||
|
|||||||
17
bin/fzf-tmux
17
bin/fzf-tmux
@@ -16,8 +16,8 @@ skip=""
|
|||||||
swap=""
|
swap=""
|
||||||
close=""
|
close=""
|
||||||
term=""
|
term=""
|
||||||
[[ -n "$LINES" ]] && lines=$LINES || lines=$(tput lines)
|
[[ -n "$LINES" ]] && lines=$LINES || lines=$(tput lines) || lines=$(tmux display-message -p "#{pane_height}")
|
||||||
[[ -n "$COLUMNS" ]] && columns=$COLUMNS || columns=$(tput cols)
|
[[ -n "$COLUMNS" ]] && columns=$COLUMNS || columns=$(tput cols) || columns=$(tmux display-message -p "#{pane_width}")
|
||||||
|
|
||||||
help() {
|
help() {
|
||||||
>&2 echo 'usage: fzf-tmux [-u|-d [HEIGHT[%]]] [-l|-r [WIDTH[%]]] [--] [FZF OPTIONS]
|
>&2 echo 'usage: fzf-tmux [-u|-d [HEIGHT[%]]] [-l|-r [WIDTH[%]]] [--] [FZF OPTIONS]
|
||||||
@@ -146,6 +146,7 @@ cleanup() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
if [ $# -gt 0 ]; then
|
if [ $# -gt 0 ]; then
|
||||||
|
trap - EXIT
|
||||||
exit 130
|
exit 130
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
@@ -170,21 +171,21 @@ for arg in "${args[@]}"; do
|
|||||||
done
|
done
|
||||||
|
|
||||||
pppid=$$
|
pppid=$$
|
||||||
trap_set="trap 'kill -SIGUSR1 -$pppid' EXIT SIGINT SIGTERM"
|
echo -n "trap 'kill -SIGUSR1 -$pppid' EXIT SIGINT SIGTERM;" > $argsf
|
||||||
trap_unset="trap - EXIT SIGINT SIGTERM"
|
close="; trap - EXIT SIGINT SIGTERM $close"
|
||||||
|
|
||||||
if [[ -n "$term" ]] || [[ -t 0 ]]; then
|
if [[ -n "$term" ]] || [[ -t 0 ]]; then
|
||||||
cat <<< "\"$fzf\" $opts > $fifo2; echo \$? > $fifo3 $close" > $argsf
|
cat <<< "\"$fzf\" $opts > $fifo2; echo \$? > $fifo3 $close" >> $argsf
|
||||||
TMUX=$(echo $TMUX | cut -d , -f 1,2) tmux set-window-option synchronize-panes off \;\
|
TMUX=$(echo $TMUX | cut -d , -f 1,2) tmux set-window-option synchronize-panes off \;\
|
||||||
set-window-option remain-on-exit off \;\
|
set-window-option remain-on-exit off \;\
|
||||||
split-window $opt "$trap_set;cd $(printf %q "$PWD");$envs bash $argsf;$trap_unset" $swap \
|
split-window $opt "$envs bash -c 'cd $(printf %q "$PWD"); exec -a fzf bash $argsf'" $swap \
|
||||||
> /dev/null 2>&1
|
> /dev/null 2>&1
|
||||||
else
|
else
|
||||||
mkfifo $fifo1
|
mkfifo $fifo1
|
||||||
cat <<< "\"$fzf\" $opts < $fifo1 > $fifo2; echo \$? > $fifo3 $close" > $argsf
|
cat <<< "\"$fzf\" $opts < $fifo1 > $fifo2; echo \$? > $fifo3 $close" >> $argsf
|
||||||
TMUX=$(echo $TMUX | cut -d , -f 1,2) tmux set-window-option synchronize-panes off \;\
|
TMUX=$(echo $TMUX | cut -d , -f 1,2) tmux set-window-option synchronize-panes off \;\
|
||||||
set-window-option remain-on-exit off \;\
|
set-window-option remain-on-exit off \;\
|
||||||
split-window $opt "$trap_set;$envs bash $argsf;$trap_unset" $swap \
|
split-window $opt "$envs bash -c 'exec -a fzf bash $argsf'" $swap \
|
||||||
> /dev/null 2>&1
|
> /dev/null 2>&1
|
||||||
cat <&0 > $fifo1 &
|
cat <&0 > $fifo1 &
|
||||||
fi
|
fi
|
||||||
|
|||||||
56
doc/fzf.txt
56
doc/fzf.txt
@@ -1,4 +1,4 @@
|
|||||||
fzf.txt fzf Last change: April 28 2017
|
fzf.txt fzf Last change: November 19 2017
|
||||||
FZF - TABLE OF CONTENTS *fzf* *fzf-toc*
|
FZF - TABLE OF CONTENTS *fzf* *fzf-toc*
|
||||||
==============================================================================
|
==============================================================================
|
||||||
|
|
||||||
@@ -8,6 +8,8 @@ FZF - TABLE OF CONTENTS *fzf* *fzf-to
|
|||||||
Examples
|
Examples
|
||||||
fzf#run
|
fzf#run
|
||||||
fzf#wrap
|
fzf#wrap
|
||||||
|
fzf inside terminal buffer
|
||||||
|
Hide statusline
|
||||||
GVim
|
GVim
|
||||||
License
|
License
|
||||||
|
|
||||||
@@ -61,7 +63,7 @@ Note that the environment variables `FZF_DEFAULT_COMMAND` and
|
|||||||
- Customizable extra key bindings for opening selected files in different
|
- Customizable extra key bindings for opening selected files in different
|
||||||
ways
|
ways
|
||||||
- `g:fzf_layout`
|
- `g:fzf_layout`
|
||||||
- Determines the size and position of fzf window (tmux pane or Neovim split)
|
- Determines the size and position of fzf window
|
||||||
- `g:fzf_colors`
|
- `g:fzf_colors`
|
||||||
- Customizes fzf colors to match the current color scheme
|
- Customizes fzf colors to match the current color scheme
|
||||||
- `g:fzf_history_dir`
|
- `g:fzf_history_dir`
|
||||||
@@ -80,11 +82,24 @@ Examples~
|
|||||||
\ 'ctrl-x': 'split',
|
\ 'ctrl-x': 'split',
|
||||||
\ 'ctrl-v': 'vsplit' }
|
\ 'ctrl-v': 'vsplit' }
|
||||||
|
|
||||||
|
" An action can be a reference to a function that processes selected lines
|
||||||
|
function! s:build_quickfix_list(lines)
|
||||||
|
call setqflist(map(copy(a:lines), '{ "filename": v:val }'))
|
||||||
|
copen
|
||||||
|
cc
|
||||||
|
endfunction
|
||||||
|
|
||||||
|
let g:fzf_action = {
|
||||||
|
\ 'ctrl-q': function('s:build_quickfix_list'),
|
||||||
|
\ 'ctrl-t': 'tab split',
|
||||||
|
\ 'ctrl-x': 'split',
|
||||||
|
\ 'ctrl-v': 'vsplit' }
|
||||||
|
|
||||||
" Default fzf layout
|
" Default fzf layout
|
||||||
" - down / up / left / right
|
" - down / up / left / right
|
||||||
let g:fzf_layout = { 'down': '~40%' }
|
let g:fzf_layout = { 'down': '~40%' }
|
||||||
|
|
||||||
" In Neovim, you can set up fzf window using a Vim command
|
" You can set up fzf window using a Vim command (Neovim or latest Vim 8 required)
|
||||||
let g:fzf_layout = { 'window': 'enew' }
|
let g:fzf_layout = { 'window': 'enew' }
|
||||||
let g:fzf_layout = { 'window': '-tabnew' }
|
let g:fzf_layout = { 'window': '-tabnew' }
|
||||||
let g:fzf_layout = { 'window': '10split enew' }
|
let g:fzf_layout = { 'window': '10split enew' }
|
||||||
@@ -98,6 +113,7 @@ Examples~
|
|||||||
\ 'bg+': ['bg', 'CursorLine', 'CursorColumn'],
|
\ 'bg+': ['bg', 'CursorLine', 'CursorColumn'],
|
||||||
\ 'hl+': ['fg', 'Statement'],
|
\ 'hl+': ['fg', 'Statement'],
|
||||||
\ 'info': ['fg', 'PreProc'],
|
\ 'info': ['fg', 'PreProc'],
|
||||||
|
\ 'border': ['fg', 'Ignore'],
|
||||||
\ 'prompt': ['fg', 'Conditional'],
|
\ 'prompt': ['fg', 'Conditional'],
|
||||||
\ 'pointer': ['fg', 'Exception'],
|
\ 'pointer': ['fg', 'Exception'],
|
||||||
\ 'marker': ['fg', 'Keyword'],
|
\ 'marker': ['fg', 'Keyword'],
|
||||||
@@ -128,7 +144,7 @@ following options.
|
|||||||
`options` | string/list | Options to fzf
|
`options` | string/list | Options to fzf
|
||||||
`dir` | string | Working directory
|
`dir` | string | Working directory
|
||||||
`up` / `down` / `left` / `right` | number/string | Use tmux pane with the given size (e.g. `20` , `50%` )
|
`up` / `down` / `left` / `right` | number/string | Use tmux pane with the given size (e.g. `20` , `50%` )
|
||||||
`window` (Neovim only) | string | Command to open fzf window (e.g. `vertical aboveleft 30new` )
|
`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` | string | External terminal emulator to start fzf with (GVim only)
|
||||||
`launcher` | funcref | Function for generating `launcher` string (GVim only)
|
`launcher` | funcref | Function for generating `launcher` string (GVim only)
|
||||||
---------------------------+---------------+--------------------------------------------------------------
|
---------------------------+---------------+--------------------------------------------------------------
|
||||||
@@ -153,11 +169,39 @@ function that decorates the options dictionary so that it understands
|
|||||||
\ call fzf#run(fzf#wrap('my-stuff', {'dir': '~/my-stuff'}, <bang>0))
|
\ call fzf#run(fzf#wrap('my-stuff', {'dir': '~/my-stuff'}, <bang>0))
|
||||||
<
|
<
|
||||||
|
|
||||||
|
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
|
||||||
|
- `call fzf#run({'left': '30%'})` or `let g:fzf_layout = {'left': '30%'}`
|
||||||
|
|
||||||
|
|
||||||
|
< Hide statusline >___________________________________________________________~
|
||||||
|
*fzf-hide-statusline*
|
||||||
|
|
||||||
|
When fzf starts in a terminal buffer, you may want to hide the statusline of
|
||||||
|
the containing buffer.
|
||||||
|
>
|
||||||
|
autocmd! FileType fzf
|
||||||
|
autocmd FileType fzf set laststatus=0 noshowmode noruler
|
||||||
|
\| autocmd BufLeave <buffer> set laststatus=2 showmode ruler
|
||||||
|
<
|
||||||
|
|
||||||
GVIM *fzf-gvim*
|
GVIM *fzf-gvim*
|
||||||
==============================================================================
|
==============================================================================
|
||||||
|
|
||||||
In GVim, you need an external terminal emulator to start fzf with. `xterm`
|
With the latest version of GVim, fzf will start inside the builtin terminal
|
||||||
command is used by default, but you can customize it with `g:fzf_launcher`.
|
emulator of Vim. Please note that this terminal feature of Vim is still young
|
||||||
|
and unstable and you may run into some issues.
|
||||||
|
|
||||||
|
If you have an older version of GVim, you need an external terminal emulator
|
||||||
|
to start fzf with. `xterm` command is used by default, but you can customize
|
||||||
|
it with `g:fzf_launcher`.
|
||||||
>
|
>
|
||||||
" This is the default. %s is replaced with fzf command
|
" This is the default. %s is replaced with fzf command
|
||||||
let g:fzf_launcher = 'xterm -e bash -ic %s'
|
let g:fzf_launcher = 'xterm -e bash -ic %s'
|
||||||
|
|||||||
104
glide.lock
generated
104
glide.lock
generated
@@ -1,24 +1,68 @@
|
|||||||
hash: d68dd0bd779ac4ffca1e0c49ca38d85f90d5d68fa8e2d5d7db70a8ce8c662ec1
|
hash: 92a208bfbaecdf8d1ccaf99a465884c49f9cd91f44f1756d7bbf3290795c781b
|
||||||
updated: 2017-06-01T15:48:41.653745249-07:00
|
updated: 2017-12-03T13:37:23.420874333+09:00
|
||||||
imports:
|
imports:
|
||||||
|
- name: github.com/bjwbell/gensimd
|
||||||
|
version: 06eb18285485c0d572cc7f024050fc6cb652ed4c
|
||||||
|
subpackages:
|
||||||
|
- simd
|
||||||
|
- name: github.com/codegangsta/cli
|
||||||
|
version: c6af8847eb2b7b297d07c3ede98903e95e680ef9
|
||||||
- name: github.com/gdamore/encoding
|
- name: github.com/gdamore/encoding
|
||||||
version: b23993cbb6353f0e6aa98d0ee318a34728f628b9
|
version: b23993cbb6353f0e6aa98d0ee318a34728f628b9
|
||||||
- name: github.com/gdamore/tcell
|
- name: github.com/gdamore/tcell
|
||||||
version: 44772c121bb7838819d3ba4a7e84c0c2d617328e
|
version: 0a0db94084dfe181108c18508ebd312f12d331fb
|
||||||
subpackages:
|
subpackages:
|
||||||
- encoding
|
- encoding
|
||||||
- name: github.com/lucasb-eyer/go-colorful
|
- name: github.com/lucasb-eyer/go-colorful
|
||||||
version: c900de9dbbc73129068f5af6a823068fc5f2308c
|
version: c900de9dbbc73129068f5af6a823068fc5f2308c
|
||||||
|
- name: github.com/Masterminds/semver
|
||||||
|
version: 15d8430ab86497c5c0da827b748823945e1cf1e1
|
||||||
|
- name: github.com/Masterminds/vcs
|
||||||
|
version: 6f1c6d150500e452704e9863f68c2559f58616bf
|
||||||
- name: github.com/mattn/go-isatty
|
- name: github.com/mattn/go-isatty
|
||||||
version: 66b8e73f3f5cda9f96b69efd03dd3d7fc4a5cdb8
|
version: 66b8e73f3f5cda9f96b69efd03dd3d7fc4a5cdb8
|
||||||
- name: github.com/mattn/go-runewidth
|
- name: github.com/mattn/go-runewidth
|
||||||
version: 14207d285c6c197daabb5c9793d63e7af9ab2d50
|
version: 14207d285c6c197daabb5c9793d63e7af9ab2d50
|
||||||
- name: github.com/mattn/go-shellwords
|
- name: github.com/mattn/go-shellwords
|
||||||
version: 02e3cf038dcea8290e44424da473dd12be796a8a
|
version: 02e3cf038dcea8290e44424da473dd12be796a8a
|
||||||
|
- name: github.com/mengzhuo/intrinsic
|
||||||
|
version: 34b800838e0bcd9c5b6abd414e3ad03dc1a686b8
|
||||||
|
subpackages:
|
||||||
|
- sse2
|
||||||
|
- name: github.com/mitchellh/go-homedir
|
||||||
|
version: b8bc1bf767474819792c23f32d8286a45736f1c6
|
||||||
- name: golang.org/x/crypto
|
- name: golang.org/x/crypto
|
||||||
version: e1a4589e7d3ea14a3352255d04b6f1a418845e5e
|
version: e1a4589e7d3ea14a3352255d04b6f1a418845e5e
|
||||||
subpackages:
|
subpackages:
|
||||||
|
- acme
|
||||||
|
- blowfish
|
||||||
|
- cast5
|
||||||
|
- chacha20poly1305/internal/chacha20
|
||||||
|
- curve25519
|
||||||
|
- ed25519
|
||||||
|
- ed25519/internal/edwards25519
|
||||||
|
- hkdf
|
||||||
|
- nacl/secretbox
|
||||||
|
- openpgp
|
||||||
|
- openpgp/armor
|
||||||
|
- openpgp/elgamal
|
||||||
|
- openpgp/errors
|
||||||
|
- openpgp/packet
|
||||||
|
- openpgp/s2k
|
||||||
|
- pbkdf2
|
||||||
|
- pkcs12/internal/rc2
|
||||||
|
- poly1305
|
||||||
|
- ripemd160
|
||||||
|
- salsa20/salsa
|
||||||
|
- ssh
|
||||||
|
- ssh/agent
|
||||||
- ssh/terminal
|
- ssh/terminal
|
||||||
|
- ssh/testdata
|
||||||
|
- name: golang.org/x/net
|
||||||
|
version: a8b9294777976932365dabb6640cf1468d95c70f
|
||||||
|
subpackages:
|
||||||
|
- context
|
||||||
|
- context/ctxhttp
|
||||||
- name: golang.org/x/sys
|
- name: golang.org/x/sys
|
||||||
version: b90f89a1e7a9c1f6b918820b3daa7f08488c8594
|
version: b90f89a1e7a9c1f6b918820b3daa7f08488c8594
|
||||||
subpackages:
|
subpackages:
|
||||||
@@ -26,13 +70,65 @@ imports:
|
|||||||
- name: golang.org/x/text
|
- name: golang.org/x/text
|
||||||
version: 4ee4af566555f5fbe026368b75596286a312663a
|
version: 4ee4af566555f5fbe026368b75596286a312663a
|
||||||
subpackages:
|
subpackages:
|
||||||
|
- cases
|
||||||
|
- collate
|
||||||
|
- collate/build
|
||||||
|
- currency
|
||||||
- encoding
|
- encoding
|
||||||
- encoding/charmap
|
- encoding/charmap
|
||||||
|
- encoding/ianaindex
|
||||||
- encoding/internal
|
- encoding/internal
|
||||||
- encoding/internal/identifier
|
- encoding/internal/identifier
|
||||||
- encoding/japanese
|
- encoding/japanese
|
||||||
- encoding/korean
|
- encoding/korean
|
||||||
- encoding/simplifiedchinese
|
- encoding/simplifiedchinese
|
||||||
- encoding/traditionalchinese
|
- encoding/traditionalchinese
|
||||||
|
- encoding/unicode
|
||||||
|
- encoding/unicode/utf32
|
||||||
|
- internal
|
||||||
|
- internal/colltab
|
||||||
|
- internal/format
|
||||||
|
- internal/gen
|
||||||
|
- internal/stringset
|
||||||
|
- internal/tag
|
||||||
|
- internal/testtext
|
||||||
|
- internal/triegen
|
||||||
|
- internal/ucd
|
||||||
|
- internal/utf8internal
|
||||||
|
- language
|
||||||
|
- language/display
|
||||||
|
- message
|
||||||
|
- runes
|
||||||
|
- secure/bidirule
|
||||||
- transform
|
- transform
|
||||||
testImports: []
|
- unicode/bidi
|
||||||
|
- unicode/cldr
|
||||||
|
- unicode/norm
|
||||||
|
- unicode/rangetable
|
||||||
|
- width
|
||||||
|
- name: golang.org/x/tools
|
||||||
|
version: 04447353bc504b9a5c02eb227b9ecd252e64ea20
|
||||||
|
subpackages:
|
||||||
|
- go/ast/astutil
|
||||||
|
- go/buildutil
|
||||||
|
- go/loader
|
||||||
|
- name: gopkg.in/yaml.v2
|
||||||
|
version: 287cf08546ab5e7e37d55a84f7ed3fd1db036de5
|
||||||
|
testImports:
|
||||||
|
- name: github.com/gopherjs/gopherjs
|
||||||
|
version: 444abdf920945de5d4a977b572bcc6c674d1e4eb
|
||||||
|
subpackages:
|
||||||
|
- js
|
||||||
|
- name: github.com/jtolds/gls
|
||||||
|
version: 77f18212c9c7edc9bd6a33d383a7b545ce62f064
|
||||||
|
- name: github.com/smartystreets/assertions
|
||||||
|
version: 0b37b35ec7434b77e77a4bb29b79677cced992ea
|
||||||
|
subpackages:
|
||||||
|
- internal/go-render/render
|
||||||
|
- internal/oglematchers
|
||||||
|
- name: github.com/smartystreets/goconvey
|
||||||
|
version: e5b2b7c9111590d019a696c7800593f666e1a7f4
|
||||||
|
subpackages:
|
||||||
|
- convey
|
||||||
|
- convey/gotest
|
||||||
|
- convey/reporting
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import:
|
|||||||
- package: github.com/mattn/go-shellwords
|
- package: github.com/mattn/go-shellwords
|
||||||
version: 02e3cf038dcea8290e44424da473dd12be796a8a
|
version: 02e3cf038dcea8290e44424da473dd12be796a8a
|
||||||
- package: github.com/gdamore/tcell
|
- package: github.com/gdamore/tcell
|
||||||
version: 44772c121bb7838819d3ba4a7e84c0c2d617328e
|
version: 0a0db94084dfe181108c18508ebd312f12d331fb
|
||||||
subpackages:
|
subpackages:
|
||||||
- encoding
|
- encoding
|
||||||
- package: golang.org/x/crypto
|
- package: golang.org/x/crypto
|
||||||
|
|||||||
75
install
75
install
@@ -2,12 +2,13 @@
|
|||||||
|
|
||||||
set -u
|
set -u
|
||||||
|
|
||||||
version=0.16.9
|
version=0.17.3
|
||||||
auto_completion=
|
auto_completion=
|
||||||
key_bindings=
|
key_bindings=
|
||||||
update_config=2
|
update_config=2
|
||||||
binary_arch=
|
binary_arch=
|
||||||
allow_legacy=
|
allow_legacy=
|
||||||
|
shells="bash zsh fish"
|
||||||
|
|
||||||
help() {
|
help() {
|
||||||
cat << EOF
|
cat << EOF
|
||||||
@@ -21,6 +22,10 @@ usage: $0 [OPTIONS]
|
|||||||
--[no-]completion Enable/disable fuzzy completion (bash & zsh)
|
--[no-]completion Enable/disable fuzzy completion (bash & zsh)
|
||||||
--[no-]update-rc Whether or not to update shell configuration files
|
--[no-]update-rc Whether or not to update shell configuration files
|
||||||
|
|
||||||
|
--no-bash Do not set up bash configuration
|
||||||
|
--no-zsh Do not set up zsh configuration
|
||||||
|
--no-fish Do not set up fish configuration
|
||||||
|
|
||||||
--32 Download 32-bit binary
|
--32 Download 32-bit binary
|
||||||
--64 Download 64-bit binary
|
--64 Download 64-bit binary
|
||||||
EOF
|
EOF
|
||||||
@@ -47,6 +52,9 @@ for opt in "$@"; do
|
|||||||
--32) binary_arch=386 ;;
|
--32) binary_arch=386 ;;
|
||||||
--64) binary_arch=amd64 ;;
|
--64) binary_arch=amd64 ;;
|
||||||
--bin) ;;
|
--bin) ;;
|
||||||
|
--no-bash) shells=${shells/bash/} ;;
|
||||||
|
--no-zsh) shells=${shells/zsh/} ;;
|
||||||
|
--no-fish) shells=${shells/fish/} ;;
|
||||||
*)
|
*)
|
||||||
echo "unknown option: $opt"
|
echo "unknown option: $opt"
|
||||||
help
|
help
|
||||||
@@ -59,14 +67,14 @@ cd "$(dirname "${BASH_SOURCE[0]}")"
|
|||||||
fzf_base="$(pwd)"
|
fzf_base="$(pwd)"
|
||||||
|
|
||||||
ask() {
|
ask() {
|
||||||
# If stdin is a tty, we are "interactive".
|
while true; do
|
||||||
# non-interactive shell: wait for a linefeed
|
read -p "$1 ([y]/n) " -r
|
||||||
# interactive shell: continue after a single keypress
|
if [[ $REPLY =~ ^[Yy]$ ]]; then
|
||||||
read_n=$([ -t 0 ] && echo "-n 1")
|
return 1
|
||||||
|
elif [[ $REPLY =~ ^[Nn]$ ]]; then
|
||||||
read -p "$1 ([y]/n) " $read_n -r
|
return 0
|
||||||
echo
|
fi
|
||||||
[[ $REPLY =~ ^[Nn]$ ]]
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
check_binary() {
|
check_binary() {
|
||||||
@@ -160,17 +168,20 @@ binary_error=""
|
|||||||
case "$archi" in
|
case "$archi" in
|
||||||
Darwin\ *64) download fzf-$version-darwin_${binary_arch:-amd64}.tgz ;;
|
Darwin\ *64) download fzf-$version-darwin_${binary_arch:-amd64}.tgz ;;
|
||||||
Darwin\ *86) download fzf-$version-darwin_${binary_arch:-386}.tgz ;;
|
Darwin\ *86) download fzf-$version-darwin_${binary_arch:-386}.tgz ;;
|
||||||
Linux\ *64) download fzf-$version-linux_${binary_arch:-amd64}.tgz ;;
|
|
||||||
Linux\ *86) download fzf-$version-linux_${binary_arch:-386}.tgz ;;
|
|
||||||
Linux\ armv5*) download fzf-$version-linux_${binary_arch:-arm5}.tgz ;;
|
Linux\ armv5*) download fzf-$version-linux_${binary_arch:-arm5}.tgz ;;
|
||||||
Linux\ armv6*) download fzf-$version-linux_${binary_arch:-arm6}.tgz ;;
|
Linux\ armv6*) download fzf-$version-linux_${binary_arch:-arm6}.tgz ;;
|
||||||
Linux\ armv7*) download fzf-$version-linux_${binary_arch:-arm7}.tgz ;;
|
Linux\ armv7*) download fzf-$version-linux_${binary_arch:-arm7}.tgz ;;
|
||||||
Linux\ armv8*) download fzf-$version-linux_${binary_arch:-arm8}.tgz ;;
|
Linux\ armv8*) download fzf-$version-linux_${binary_arch:-arm8}.tgz ;;
|
||||||
|
Linux\ aarch64*) download fzf-$version-linux_${binary_arch:-arm8}.tgz ;;
|
||||||
|
Linux\ *64) download fzf-$version-linux_${binary_arch:-amd64}.tgz ;;
|
||||||
|
Linux\ *86) download fzf-$version-linux_${binary_arch:-386}.tgz ;;
|
||||||
FreeBSD\ *64) download fzf-$version-freebsd_${binary_arch:-amd64}.tgz ;;
|
FreeBSD\ *64) download fzf-$version-freebsd_${binary_arch:-amd64}.tgz ;;
|
||||||
FreeBSD\ *86) download fzf-$version-freebsd_${binary_arch:-386}.tgz ;;
|
FreeBSD\ *86) download fzf-$version-freebsd_${binary_arch:-386}.tgz ;;
|
||||||
OpenBSD\ *64) download fzf-$version-openbsd_${binary_arch:-amd64}.tgz ;;
|
OpenBSD\ *64) download fzf-$version-openbsd_${binary_arch:-amd64}.tgz ;;
|
||||||
OpenBSD\ *86) download fzf-$version-openbsd_${binary_arch:-386}.tgz ;;
|
OpenBSD\ *86) download fzf-$version-openbsd_${binary_arch:-386}.tgz ;;
|
||||||
CYGWIN*\ *64) download fzf-$version-windows_${binary_arch:-amd64}.zip ;;
|
CYGWIN*\ *64) download fzf-$version-windows_${binary_arch:-amd64}.zip ;;
|
||||||
|
MINGW*\ *86) download fzf-$version-windows_${binary_arch:-386}.zip ;;
|
||||||
|
MINGW*\ *64) download fzf-$version-windows_${binary_arch:-amd64}.zip ;;
|
||||||
*) binary_available=0 binary_error=1 ;;
|
*) binary_available=0 binary_error=1 ;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
@@ -202,6 +213,17 @@ fi
|
|||||||
|
|
||||||
[[ "$*" =~ "--bin" ]] && exit 0
|
[[ "$*" =~ "--bin" ]] && exit 0
|
||||||
|
|
||||||
|
for s in $shells; do
|
||||||
|
if ! command -v "$s" > /dev/null; then
|
||||||
|
shells=${shells/$s/}
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ ${#shells} -lt 3 ]]; then
|
||||||
|
echo "No shell configuration to be updated."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
# Auto-completion
|
# Auto-completion
|
||||||
if [ -z "$auto_completion" ]; then
|
if [ -z "$auto_completion" ]; then
|
||||||
ask "Do you want to enable fuzzy auto-completion?"
|
ask "Do you want to enable fuzzy auto-completion?"
|
||||||
@@ -215,9 +237,8 @@ if [ -z "$key_bindings" ]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
echo
|
echo
|
||||||
has_zsh=$(command -v zsh > /dev/null && echo 1 || echo 0)
|
|
||||||
shells=$([ $has_zsh -eq 1 ] && echo "bash zsh" || echo "bash")
|
|
||||||
for shell in $shells; do
|
for shell in $shells; do
|
||||||
|
[[ "$shell" = fish ]] && continue
|
||||||
echo -n "Generate ~/.fzf.$shell ... "
|
echo -n "Generate ~/.fzf.$shell ... "
|
||||||
src=~/.fzf.${shell}
|
src=~/.fzf.${shell}
|
||||||
|
|
||||||
@@ -251,11 +272,10 @@ EOF
|
|||||||
done
|
done
|
||||||
|
|
||||||
# fish
|
# fish
|
||||||
has_fish=$(command -v fish > /dev/null && echo 1 || echo 0)
|
if [[ "$shells" =~ fish ]]; then
|
||||||
if [ $has_fish -eq 1 ]; then
|
|
||||||
echo -n "Update fish_user_paths ... "
|
echo -n "Update fish_user_paths ... "
|
||||||
fish << EOF
|
fish << EOF
|
||||||
echo \$fish_user_paths | grep $fzf_base/bin > /dev/null
|
echo \$fish_user_paths | \grep $fzf_base/bin > /dev/null
|
||||||
or set --universal fish_user_paths \$fish_user_paths $fzf_base/bin
|
or set --universal fish_user_paths \$fish_user_paths $fzf_base/bin
|
||||||
EOF
|
EOF
|
||||||
[ $? -eq 0 ] && echo "OK" || echo "Failed"
|
[ $? -eq 0 ] && echo "OK" || echo "Failed"
|
||||||
@@ -286,20 +306,22 @@ append_line() {
|
|||||||
line="$2"
|
line="$2"
|
||||||
file="$3"
|
file="$3"
|
||||||
pat="${4:-}"
|
pat="${4:-}"
|
||||||
|
lno=""
|
||||||
|
|
||||||
echo "Update $file:"
|
echo "Update $file:"
|
||||||
echo " - $line"
|
echo " - $line"
|
||||||
[ -f "$file" ] || touch "$file"
|
if [ -f "$file" ]; then
|
||||||
if [ $# -lt 4 ]; then
|
if [ $# -lt 4 ]; then
|
||||||
lno=$(\grep -nF "$line" "$file" | sed 's/:.*//' | tr '\n' ' ')
|
lno=$(\grep -nF "$line" "$file" | sed 's/:.*//' | tr '\n' ' ')
|
||||||
else
|
else
|
||||||
lno=$(\grep -nF "$pat" "$file" | sed 's/:.*//' | tr '\n' ' ')
|
lno=$(\grep -nF "$pat" "$file" | sed 's/:.*//' | tr '\n' ' ')
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
if [ -n "$lno" ]; then
|
if [ -n "$lno" ]; then
|
||||||
echo " - Already exists: line #$lno"
|
echo " - Already exists: line #$lno"
|
||||||
else
|
else
|
||||||
if [ $update -eq 1 ]; then
|
if [ $update -eq 1 ]; then
|
||||||
echo >> "$file"
|
[ -f "$file" ] && echo >> "$file"
|
||||||
echo "$line" >> "$file"
|
echo "$line" >> "$file"
|
||||||
echo " + Added"
|
echo " + Added"
|
||||||
else
|
else
|
||||||
@@ -328,11 +350,12 @@ if [ $update_config -eq 2 ]; then
|
|||||||
fi
|
fi
|
||||||
echo
|
echo
|
||||||
for shell in $shells; do
|
for shell in $shells; do
|
||||||
|
[[ "$shell" = fish ]] && continue
|
||||||
[ $shell = zsh ] && dest=${ZDOTDIR:-~}/.zshrc || dest=~/.bashrc
|
[ $shell = zsh ] && dest=${ZDOTDIR:-~}/.zshrc || dest=~/.bashrc
|
||||||
append_line $update_config "[ -f ~/.fzf.${shell} ] && source ~/.fzf.${shell}" "$dest" "~/.fzf.${shell}"
|
append_line $update_config "[ -f ~/.fzf.${shell} ] && source ~/.fzf.${shell}" "$dest" "~/.fzf.${shell}"
|
||||||
done
|
done
|
||||||
|
|
||||||
if [ $key_bindings -eq 1 ] && [ $has_fish -eq 1 ]; then
|
if [ $key_bindings -eq 1 ] && [[ "$shells" =~ fish ]]; then
|
||||||
bind_file=~/.config/fish/functions/fish_user_key_bindings.fish
|
bind_file=~/.config/fish/functions/fish_user_key_bindings.fish
|
||||||
if [ ! -e "$bind_file" ]; then
|
if [ ! -e "$bind_file" ]; then
|
||||||
create_file "$bind_file" \
|
create_file "$bind_file" \
|
||||||
@@ -346,9 +369,9 @@ fi
|
|||||||
|
|
||||||
if [ $update_config -eq 1 ]; then
|
if [ $update_config -eq 1 ]; then
|
||||||
echo 'Finished. Restart your shell or reload config file.'
|
echo 'Finished. Restart your shell or reload config file.'
|
||||||
echo ' source ~/.bashrc # bash'
|
[[ "$shells" =~ bash ]] && echo ' source ~/.bashrc # bash'
|
||||||
[ $has_zsh -eq 1 ] && echo " source ${ZDOTDIR:-~}/.zshrc # zsh"
|
[[ "$shells" =~ zsh ]] && echo " source ${ZDOTDIR:-~}/.zshrc # zsh"
|
||||||
[ $has_fish -eq 1 ] && [ $key_bindings -eq 1 ] && echo ' fzf_key_bindings # fish'
|
[[ "$shells" =~ fish ]] && [ $key_bindings -eq 1 ] && echo ' fzf_key_bindings # fish'
|
||||||
echo
|
echo
|
||||||
echo 'Use uninstall script to remove fzf.'
|
echo 'Use uninstall script to remove fzf.'
|
||||||
echo
|
echo
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
THE SOFTWARE.
|
THE SOFTWARE.
|
||||||
..
|
..
|
||||||
.TH fzf-tmux 1 "Jul 2017" "fzf 0.16.9" "fzf-tmux - open fzf in tmux split pane"
|
.TH fzf-tmux 1 "Dec 2017" "fzf 0.17.3" "fzf-tmux - open fzf in tmux split pane"
|
||||||
|
|
||||||
.SH NAME
|
.SH NAME
|
||||||
fzf-tmux - open fzf in tmux split pane
|
fzf-tmux - open fzf in tmux split pane
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
THE SOFTWARE.
|
THE SOFTWARE.
|
||||||
..
|
..
|
||||||
.TH fzf 1 "Jul 2017" "fzf 0.16.9" "fzf - a command-line fuzzy finder"
|
.TH fzf 1 "Dec 2017" "fzf 0.17.3" "fzf - a command-line fuzzy finder"
|
||||||
|
|
||||||
.SH NAME
|
.SH NAME
|
||||||
fzf - a command-line fuzzy finder
|
fzf - a command-line fuzzy finder
|
||||||
@@ -111,6 +111,9 @@ Comma-separated list of sort criteria to apply when the scores are tied.
|
|||||||
.B "-m, --multi"
|
.B "-m, --multi"
|
||||||
Enable multi-select with tab/shift-tab
|
Enable multi-select with tab/shift-tab
|
||||||
.TP
|
.TP
|
||||||
|
.B "+m, --no-multi"
|
||||||
|
Disable multi-select
|
||||||
|
.TP
|
||||||
.B "--no-mouse"
|
.B "--no-mouse"
|
||||||
Disable mouse
|
Disable mouse
|
||||||
.TP
|
.TP
|
||||||
@@ -269,14 +272,17 @@ string, specify field index expressions between the braces (See \fBFIELD INDEX
|
|||||||
EXPRESSION\fR for the details).
|
EXPRESSION\fR for the details).
|
||||||
|
|
||||||
.RS
|
.RS
|
||||||
e.g. \fBfzf --preview="head -$LINES {}"\fR
|
e.g. \fBfzf --preview='head -$LINES {}'\fR
|
||||||
\fBls -l | fzf --preview="echo user={3} when={-4..-2}; cat {-1}" --header-lines=1\fR
|
\fBls -l | fzf --preview="echo user={3} when={-4..-2}; cat {-1}" --header-lines=1\fR
|
||||||
|
|
||||||
|
fzf overrides \fB$LINES\fR and \fB$COLUMNS\fR so that they represent the exact
|
||||||
|
size of the preview window.
|
||||||
|
|
||||||
A placeholder expression starting with \fB+\fR flag will be replaced to the
|
A placeholder expression starting with \fB+\fR flag will be replaced to the
|
||||||
space-separated list of the selected lines (or the current line if no selection
|
space-separated list of the selected lines (or the current line if no selection
|
||||||
was made) individually quoted.
|
was made) individually quoted.
|
||||||
|
|
||||||
e.g. \fBfzf --multi --preview="head -10 {+}"\fR
|
e.g. \fBfzf --multi --preview='head -10 {+}'\fR
|
||||||
\fBgit log --oneline | fzf --multi --preview 'git show {+1}'\fR
|
\fBgit log --oneline | fzf --multi --preview 'git show {+1}'\fR
|
||||||
|
|
||||||
Also, \fB{q}\fR is replaced to the current query string.
|
Also, \fB{q}\fR is replaced to the current query string.
|
||||||
@@ -328,10 +334,12 @@ Comma-separated list of keys that can be used to complete fzf in addition to
|
|||||||
the default enter key. When this option is set, fzf will print the name of the
|
the default enter key. When this option is set, fzf will print the name of the
|
||||||
key pressed as the first line of its output (or as the second line if
|
key pressed as the first line of its output (or as the second line if
|
||||||
\fB--print-query\fR is also used). The line will be empty if fzf is completed
|
\fB--print-query\fR is also used). The line will be empty if fzf is completed
|
||||||
with the default enter key.
|
with the default enter key. If \fB--expect\fR option is specified multiple
|
||||||
|
times, fzf will expect the union of the keys. \fB--no-expect\fR will clear the
|
||||||
|
list.
|
||||||
|
|
||||||
.RS
|
.RS
|
||||||
e.g. \fBfzf --expect=ctrl-v,ctrl-t,alt-s,f1,f2,~,@\fR
|
e.g. \fBfzf --expect=ctrl-v,ctrl-t,alt-s --expect=f1,f2,~,@\fR
|
||||||
.RE
|
.RE
|
||||||
.TP
|
.TP
|
||||||
.B "--read0"
|
.B "--read0"
|
||||||
@@ -357,6 +365,9 @@ e.g. \fBfzf --multi | fzf --sync\fR
|
|||||||
.B "--version"
|
.B "--version"
|
||||||
Display version information and exit
|
Display version information and exit
|
||||||
|
|
||||||
|
.TP
|
||||||
|
Note that most options have the opposite versions with \fB--no-\fR prefix.
|
||||||
|
|
||||||
.SH ENVIRONMENT VARIABLES
|
.SH ENVIRONMENT VARIABLES
|
||||||
.TP
|
.TP
|
||||||
.B FZF_DEFAULT_COMMAND
|
.B FZF_DEFAULT_COMMAND
|
||||||
@@ -404,6 +415,9 @@ Unless specified otherwise, fzf will start in "extended-search mode". In this
|
|||||||
mode, you can specify multiple patterns delimited by spaces, such as: \fB'wild
|
mode, you can specify multiple patterns delimited by spaces, such as: \fB'wild
|
||||||
^music .mp3$ sbtrkt !rmx\fR
|
^music .mp3$ sbtrkt !rmx\fR
|
||||||
|
|
||||||
|
You can prepend a backslash to a space (\fB\\ \fR) to match a literal space
|
||||||
|
character.
|
||||||
|
|
||||||
.SS Exact-match (quoted)
|
.SS Exact-match (quoted)
|
||||||
A term that is prefixed by a single-quote character (\fB'\fR) is interpreted as
|
A term that is prefixed by a single-quote character (\fB'\fR) is interpreted as
|
||||||
an "exact-match" (or "non-fuzzy") term. fzf will search for the exact
|
an "exact-match" (or "non-fuzzy") term. fzf will search for the exact
|
||||||
@@ -465,6 +479,8 @@ e.g. \fBfzf --bind=ctrl-j:accept,ctrl-k:kill-line\fR
|
|||||||
\fIpgdn\fR (\fIpage-down\fR)
|
\fIpgdn\fR (\fIpage-down\fR)
|
||||||
\fIshift-left\fR
|
\fIshift-left\fR
|
||||||
\fIshift-right\fR
|
\fIshift-right\fR
|
||||||
|
\fIleft-click\fR
|
||||||
|
\fIright-click\fR
|
||||||
\fIdouble-click\fR
|
\fIdouble-click\fR
|
||||||
or any single character
|
or any single character
|
||||||
|
|
||||||
@@ -476,12 +492,13 @@ triggered whenever the query string is changed.
|
|||||||
\fBACTION: DEFAULT BINDINGS (NOTES):
|
\fBACTION: DEFAULT BINDINGS (NOTES):
|
||||||
\fBabort\fR \fIctrl-c ctrl-g ctrl-q esc\fR
|
\fBabort\fR \fIctrl-c ctrl-g ctrl-q esc\fR
|
||||||
\fBaccept\fR \fIenter double-click\fR
|
\fBaccept\fR \fIenter double-click\fR
|
||||||
|
\fBaccept-non-empty\fR (same as \fBaccept\fR except that it prevents fzf from exiting without selection)
|
||||||
\fBbackward-char\fR \fIctrl-b left\fR
|
\fBbackward-char\fR \fIctrl-b left\fR
|
||||||
\fBbackward-delete-char\fR \fIctrl-h bspace\fR
|
\fBbackward-delete-char\fR \fIctrl-h bspace\fR
|
||||||
\fBbackward-kill-word\fR \fIalt-bs\fR
|
\fBbackward-kill-word\fR \fIalt-bs\fR
|
||||||
\fBbackward-word\fR \fIalt-b shift-left\fR
|
\fBbackward-word\fR \fIalt-b shift-left\fR
|
||||||
\fBbeginning-of-line\fR \fIctrl-a home\fR
|
\fBbeginning-of-line\fR \fIctrl-a home\fR
|
||||||
\fBcancel\fR
|
\fBcancel\fR (clears query string if not empty, aborts fzf otherwise)
|
||||||
\fBclear-screen\fR \fIctrl-l\fR
|
\fBclear-screen\fR \fIctrl-l\fR
|
||||||
\fBdelete-char\fR \fIdel\fR
|
\fBdelete-char\fR \fIdel\fR
|
||||||
\fBdelete-char/eof\fR \fIctrl-d\fR
|
\fBdelete-char/eof\fR \fIctrl-d\fR
|
||||||
@@ -509,8 +526,9 @@ triggered whenever the query string is changed.
|
|||||||
\fBpreview-page-up\fR
|
\fBpreview-page-up\fR
|
||||||
\fBprevious-history\fR (\fIctrl-p\fR on \fB--history\fR)
|
\fBprevious-history\fR (\fIctrl-p\fR on \fB--history\fR)
|
||||||
\fBprint-query\fR (print query and exit)
|
\fBprint-query\fR (print query and exit)
|
||||||
|
\fBreplace-query\fR (replace query string with the current selection)
|
||||||
\fBselect-all\fR
|
\fBselect-all\fR
|
||||||
\fBtoggle\fR
|
\fBtoggle\fR (\fIright-click\fR)
|
||||||
\fBtoggle-all\fR
|
\fBtoggle-all\fR
|
||||||
\fBtoggle+down\fR \fIctrl-i (tab)\fR
|
\fBtoggle+down\fR \fIctrl-i (tab)\fR
|
||||||
\fBtoggle-in\fR (\fB--reverse\fR ? \fBtoggle+up\fR : \fBtoggle+down\fR)
|
\fBtoggle-in\fR (\fB--reverse\fR ? \fBtoggle+up\fR : \fBtoggle+down\fR)
|
||||||
|
|||||||
@@ -35,6 +35,8 @@ else
|
|||||||
let s:base_dir = expand('<sfile>:h:h')
|
let s:base_dir = expand('<sfile>:h:h')
|
||||||
endif
|
endif
|
||||||
if s:is_win
|
if s:is_win
|
||||||
|
let s:term_marker = '&::FZF'
|
||||||
|
|
||||||
function! s:fzf_call(fn, ...)
|
function! s:fzf_call(fn, ...)
|
||||||
let shellslash = &shellslash
|
let shellslash = &shellslash
|
||||||
try
|
try
|
||||||
@@ -53,6 +55,8 @@ if s:is_win
|
|||||||
\ ['chcp %origchcp% > nul']
|
\ ['chcp %origchcp% > nul']
|
||||||
endfunction
|
endfunction
|
||||||
else
|
else
|
||||||
|
let s:term_marker = ";#FZF"
|
||||||
|
|
||||||
function! s:fzf_call(fn, ...)
|
function! s:fzf_call(fn, ...)
|
||||||
return call(a:fn, a:000)
|
return call(a:fn, a:000)
|
||||||
endfunction
|
endfunction
|
||||||
@@ -66,8 +70,8 @@ function! s:shellesc_cmd(arg)
|
|||||||
let escaped = substitute(a:arg, '[&|<>()@^]', '^&', 'g')
|
let escaped = substitute(a:arg, '[&|<>()@^]', '^&', 'g')
|
||||||
let escaped = substitute(escaped, '%', '%%', 'g')
|
let escaped = substitute(escaped, '%', '%%', 'g')
|
||||||
let escaped = substitute(escaped, '"', '\\^&', 'g')
|
let escaped = substitute(escaped, '"', '\\^&', 'g')
|
||||||
let escaped = substitute(escaped, '\\\+\(\\^\)', '\\\\\1', 'g')
|
let escaped = substitute(escaped, '\(\\\+\)\(\\^\)', '\1\1\2', 'g')
|
||||||
return '^"'.substitute(escaped, '[^\\]\zs\\$', '\\\\', '').'^"'
|
return '^"'.substitute(escaped, '\(\\\+\)$', '\1\1', '').'^"'
|
||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
function! fzf#shellescape(arg, ...)
|
function! fzf#shellescape(arg, ...)
|
||||||
@@ -201,7 +205,10 @@ function! s:common_sink(action, lines) abort
|
|||||||
return
|
return
|
||||||
endif
|
endif
|
||||||
let key = remove(a:lines, 0)
|
let key = remove(a:lines, 0)
|
||||||
let cmd = get(a:action, key, 'e')
|
let Cmd = get(a:action, key, 'e')
|
||||||
|
if type(Cmd) == type(function('call'))
|
||||||
|
return Cmd(a:lines)
|
||||||
|
endif
|
||||||
if len(a:lines) > 1
|
if len(a:lines) > 1
|
||||||
augroup fzf_swap
|
augroup fzf_swap
|
||||||
autocmd SwapExists * let v:swapchoice='o'
|
autocmd SwapExists * let v:swapchoice='o'
|
||||||
@@ -217,7 +224,7 @@ function! s:common_sink(action, lines) abort
|
|||||||
execute 'e' s:escape(item)
|
execute 'e' s:escape(item)
|
||||||
let empty = 0
|
let empty = 0
|
||||||
else
|
else
|
||||||
call s:open(cmd, item)
|
call s:open(Cmd, item)
|
||||||
endif
|
endif
|
||||||
if !has('patch-8.0.0177') && !has('nvim-0.2') && exists('#BufEnter')
|
if !has('patch-8.0.0177') && !has('nvim-0.2') && exists('#BufEnter')
|
||||||
\ && isdirectory(item)
|
\ && isdirectory(item)
|
||||||
@@ -325,25 +332,21 @@ function! fzf#wrap(...)
|
|||||||
return opts
|
return opts
|
||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
function! fzf#run(...) abort
|
function! s:use_sh()
|
||||||
try
|
let [shell, shellslash] = [&shell, &shellslash]
|
||||||
let oshell = &shell
|
|
||||||
let useshellslash = &shellslash
|
|
||||||
|
|
||||||
if s:is_win
|
if s:is_win
|
||||||
set shell=cmd.exe
|
set shell=cmd.exe
|
||||||
set noshellslash
|
set noshellslash
|
||||||
else
|
else
|
||||||
set shell=sh
|
set shell=sh
|
||||||
endif
|
endif
|
||||||
|
return [shell, shellslash]
|
||||||
|
endfunction
|
||||||
|
|
||||||
|
function! fzf#run(...) abort
|
||||||
|
try
|
||||||
|
let [shell, shellslash] = s:use_sh()
|
||||||
|
|
||||||
if has('nvim')
|
|
||||||
let running = filter(range(1, bufnr('$')), "bufname(v:val) =~# ';#FZF'")
|
|
||||||
if len(running)
|
|
||||||
call s:warn('FZF is already running (in buffer '.join(running, ', ').')!')
|
|
||||||
return []
|
|
||||||
endif
|
|
||||||
endif
|
|
||||||
let dict = exists('a:1') ? s:upgrade(a:1) : {}
|
let dict = exists('a:1') ? s:upgrade(a:1) : {}
|
||||||
let temps = { 'result': s:fzf_tempname() }
|
let temps = { 'result': s:fzf_tempname() }
|
||||||
let optstr = s:evaluate_opts(get(dict, 'options', ''))
|
let optstr = s:evaluate_opts(get(dict, 'options', ''))
|
||||||
@@ -370,7 +373,7 @@ try
|
|||||||
let source = dict.source
|
let source = dict.source
|
||||||
let type = type(source)
|
let type = type(source)
|
||||||
if type == 1
|
if type == 1
|
||||||
let prefix = source.'|'
|
let prefix = '( '.source.' )|'
|
||||||
elseif type == 3
|
elseif type == 3
|
||||||
let temps.input = s:fzf_tempname()
|
let temps.input = s:fzf_tempname()
|
||||||
call writefile(source, temps.input)
|
call writefile(source, temps.input)
|
||||||
@@ -383,10 +386,13 @@ try
|
|||||||
endif
|
endif
|
||||||
|
|
||||||
let prefer_tmux = get(g:, 'fzf_prefer_tmux', 0)
|
let prefer_tmux = get(g:, 'fzf_prefer_tmux', 0)
|
||||||
let use_height = has_key(dict, 'down') &&
|
let use_height = has_key(dict, 'down') && !has('gui_running') &&
|
||||||
\ !(has('nvim') || s:is_win || has('win32unix') || s:present(dict, 'up', 'left', 'right')) &&
|
\ !(has('nvim') || s:is_win || has('win32unix') || s:present(dict, 'up', 'left', 'right', 'window')) &&
|
||||||
\ executable('tput') && filereadable('/dev/tty')
|
\ executable('tput') && filereadable('/dev/tty')
|
||||||
let use_term = has('nvim') && !s:is_win
|
let has_vim8_term = has('terminal') && has('patch-8.0.995')
|
||||||
|
let has_nvim_term = has('nvim-0.2.1') || has('nvim') && !s:is_win
|
||||||
|
let use_term = has_nvim_term ||
|
||||||
|
\ has_vim8_term && !has('win32unix') && (has('gui_running') || s:is_win || !use_height && s:present(dict, 'down', 'up', 'left', 'right', 'window'))
|
||||||
let use_tmux = (!use_height && !use_term || prefer_tmux) && !has('win32unix') && s:tmux_enabled() && s:splittable(dict)
|
let use_tmux = (!use_height && !use_term || prefer_tmux) && !has('win32unix') && s:tmux_enabled() && s:splittable(dict)
|
||||||
if prefer_tmux && use_tmux
|
if prefer_tmux && use_tmux
|
||||||
let use_height = 0
|
let use_height = 0
|
||||||
@@ -409,8 +415,7 @@ try
|
|||||||
call s:callback(dict, lines)
|
call s:callback(dict, lines)
|
||||||
return lines
|
return lines
|
||||||
finally
|
finally
|
||||||
let &shell = oshell
|
let [&shell, &shellslash] = [shell, shellslash]
|
||||||
let &shellslash = useshellslash
|
|
||||||
endtry
|
endtry
|
||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
@@ -466,11 +471,11 @@ augroup fzf_popd
|
|||||||
augroup END
|
augroup END
|
||||||
|
|
||||||
function! s:dopopd()
|
function! s:dopopd()
|
||||||
if !exists('w:fzf_prev_dir') || exists('*haslocaldir') && !haslocaldir()
|
if !exists('w:fzf_dir') || s:fzf_getcwd() != w:fzf_dir[1]
|
||||||
return
|
return
|
||||||
endif
|
endif
|
||||||
execute 'lcd' s:escape(w:fzf_prev_dir)
|
execute 'lcd' s:escape(w:fzf_dir[0])
|
||||||
unlet w:fzf_prev_dir
|
unlet w:fzf_dir
|
||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
function! s:xterm_launcher()
|
function! s:xterm_launcher()
|
||||||
@@ -629,6 +634,7 @@ function! s:execute_term(dict, command, temps) abort
|
|||||||
let winrest = winrestcmd()
|
let winrest = winrestcmd()
|
||||||
let pbuf = bufnr('')
|
let pbuf = bufnr('')
|
||||||
let [ppos, winopts] = s:split(a:dict)
|
let [ppos, winopts] = s:split(a:dict)
|
||||||
|
call s:use_sh()
|
||||||
let b:fzf = a:dict
|
let b:fzf = a:dict
|
||||||
let fzf = { 'buf': bufnr(''), 'pbuf': pbuf, 'ppos': ppos, 'dict': a:dict, 'temps': a:temps,
|
let fzf = { 'buf': bufnr(''), 'pbuf': pbuf, 'ppos': ppos, 'dict': a:dict, 'temps': a:temps,
|
||||||
\ 'winopts': winopts, 'winrest': winrest, 'lines': &lines,
|
\ 'winopts': winopts, 'winrest': winrest, 'lines': &lines,
|
||||||
@@ -644,7 +650,7 @@ function! s:execute_term(dict, command, temps) abort
|
|||||||
endif
|
endif
|
||||||
endif
|
endif
|
||||||
endfunction
|
endfunction
|
||||||
function! fzf.on_exit(id, code, _event)
|
function! fzf.on_exit(id, code, ...)
|
||||||
if s:getpos() == self.ppos " {'window': 'enew'}
|
if s:getpos() == self.ppos " {'window': 'enew'}
|
||||||
for [opt, val] in items(self.winopts)
|
for [opt, val] in items(self.winopts)
|
||||||
execute 'let' opt '=' val
|
execute 'let' opt '=' val
|
||||||
@@ -682,13 +688,28 @@ function! s:execute_term(dict, command, temps) abort
|
|||||||
if s:present(a:dict, 'dir')
|
if s:present(a:dict, 'dir')
|
||||||
execute 'lcd' s:escape(a:dict.dir)
|
execute 'lcd' s:escape(a:dict.dir)
|
||||||
endif
|
endif
|
||||||
call termopen(a:command . ';#FZF', fzf)
|
if s:is_win
|
||||||
|
let fzf.temps.batchfile = s:fzf_tempname().'.bat'
|
||||||
|
call writefile(s:wrap_cmds(a:command), fzf.temps.batchfile)
|
||||||
|
let command = fzf.temps.batchfile
|
||||||
|
else
|
||||||
|
let command = a:command
|
||||||
|
endif
|
||||||
|
let command .= s:term_marker
|
||||||
|
if has('nvim')
|
||||||
|
call termopen(command, fzf)
|
||||||
|
else
|
||||||
|
let t = term_start([&shell, &shellcmdflag, command], {'curwin': fzf.buf, 'exit_cb': function(fzf.on_exit)})
|
||||||
|
if !has('patch-8.0.1261') && !has('nvim') && !s:is_win
|
||||||
|
call term_wait(t, 20)
|
||||||
|
endif
|
||||||
|
endif
|
||||||
finally
|
finally
|
||||||
if s:present(a:dict, 'dir')
|
if s:present(a:dict, 'dir')
|
||||||
lcd -
|
lcd -
|
||||||
endif
|
endif
|
||||||
endtry
|
endtry
|
||||||
setlocal nospell bufhidden=wipe nobuflisted
|
setlocal nospell bufhidden=wipe nobuflisted nonumber
|
||||||
setf fzf
|
setf fzf
|
||||||
startinsert
|
startinsert
|
||||||
return []
|
return []
|
||||||
@@ -719,7 +740,7 @@ function! s:callback(dict, lines) abort
|
|||||||
let popd = has_key(a:dict, 'prev_dir') &&
|
let popd = has_key(a:dict, 'prev_dir') &&
|
||||||
\ (!&autochdir || (empty(a:lines) || len(a:lines) == 1 && empty(a:lines[0])))
|
\ (!&autochdir || (empty(a:lines) || len(a:lines) == 1 && empty(a:lines[0])))
|
||||||
if popd
|
if popd
|
||||||
let w:fzf_prev_dir = a:dict.prev_dir
|
let w:fzf_dir = [a:dict.prev_dir, a:dict.dir]
|
||||||
endif
|
endif
|
||||||
|
|
||||||
try
|
try
|
||||||
@@ -743,7 +764,7 @@ function! s:callback(dict, lines) abort
|
|||||||
|
|
||||||
" We may have opened a new window or tab
|
" We may have opened a new window or tab
|
||||||
if popd
|
if popd
|
||||||
let w:fzf_prev_dir = a:dict.prev_dir
|
let w:fzf_dir = [a:dict.prev_dir, a:dict.dir]
|
||||||
call s:dopopd()
|
call s:dopopd()
|
||||||
endif
|
endif
|
||||||
endfunction
|
endfunction
|
||||||
@@ -754,9 +775,12 @@ let s:default_action = {
|
|||||||
\ 'ctrl-v': 'vsplit' }
|
\ 'ctrl-v': 'vsplit' }
|
||||||
|
|
||||||
function! s:shortpath()
|
function! s:shortpath()
|
||||||
let short = pathshorten(fnamemodify(getcwd(), ':~:.'))
|
let short = fnamemodify(getcwd(), ':~:.')
|
||||||
|
if !has('win32unix')
|
||||||
|
let short = pathshorten(short)
|
||||||
|
endif
|
||||||
let slash = (s:is_win && !&shellslash) ? '\' : '/'
|
let slash = (s:is_win && !&shellslash) ? '\' : '/'
|
||||||
return empty(short) ? '~'.slash : short . (short =~ slash.'$' ? '' : slash)
|
return empty(short) ? '~'.slash : short . (short =~ escape(slash, '\').'$' ? '' : slash)
|
||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
function! s:cmd(bang, ...) abort
|
function! s:cmd(bang, ...) abort
|
||||||
@@ -771,6 +795,7 @@ function! s:cmd(bang, ...) abort
|
|||||||
else
|
else
|
||||||
let prompt = s:shortpath()
|
let prompt = s:shortpath()
|
||||||
endif
|
endif
|
||||||
|
let prompt = strwidth(prompt) < &columns - 20 ? prompt : '> '
|
||||||
call extend(opts.options, ['--prompt', prompt])
|
call extend(opts.options, ['--prompt', prompt])
|
||||||
call extend(opts.options, args)
|
call extend(opts.options, args)
|
||||||
call fzf#run(fzf#wrap('FZF', opts, a:bang))
|
call fzf#run(fzf#wrap('FZF', opts, a:bang))
|
||||||
|
|||||||
@@ -113,7 +113,7 @@ _fzf_opts_completion() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_fzf_handle_dynamic_completion() {
|
_fzf_handle_dynamic_completion() {
|
||||||
local cmd orig_var orig ret orig_cmd
|
local cmd orig_var orig ret orig_cmd orig_complete
|
||||||
cmd="$1"
|
cmd="$1"
|
||||||
shift
|
shift
|
||||||
orig_cmd="$1"
|
orig_cmd="$1"
|
||||||
@@ -122,10 +122,14 @@ _fzf_handle_dynamic_completion() {
|
|||||||
if [ -n "$orig" ] && type "$orig" > /dev/null 2>&1; then
|
if [ -n "$orig" ] && type "$orig" > /dev/null 2>&1; then
|
||||||
$orig "$@"
|
$orig "$@"
|
||||||
elif [ -n "$_fzf_completion_loader" ]; then
|
elif [ -n "$_fzf_completion_loader" ]; then
|
||||||
|
orig_complete=$(complete -p "$cmd")
|
||||||
_completion_loader "$@"
|
_completion_loader "$@"
|
||||||
ret=$?
|
ret=$?
|
||||||
eval "$(complete | command grep "\-F.* $orig_cmd$" | _fzf_orig_completion_filter)"
|
# _completion_loader may not have updated completion for the command
|
||||||
source "${BASH_SOURCE[0]}"
|
if [ "$(complete -p "$cmd")" != "$orig_complete" ]; then
|
||||||
|
eval "$(complete | command grep "\-F.* $orig_cmd$" | _fzf_orig_completion_filter)"
|
||||||
|
eval "$orig_complete"
|
||||||
|
fi
|
||||||
return $ret
|
return $ret
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
@@ -215,7 +219,7 @@ _fzf_complete_kill() {
|
|||||||
|
|
||||||
local selected fzf
|
local selected fzf
|
||||||
fzf="$(__fzfcmd_complete)"
|
fzf="$(__fzfcmd_complete)"
|
||||||
selected=$(ps -ef | sed 1d | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-50%} --min-height 15 --reverse $FZF_DEFAULT_OPTS --preview 'echo {}' --preview-window down:3:wrap $FZF_COMPLETION_OPTS" $fzf -m | awk '{print $2}' | tr '\n' ' ')
|
selected=$(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' ' ')
|
||||||
printf '\e[5n'
|
printf '\e[5n'
|
||||||
|
|
||||||
if [ -n "$selected" ]; then
|
if [ -n "$selected" ]; then
|
||||||
@@ -233,8 +237,8 @@ _fzf_complete_telnet() {
|
|||||||
|
|
||||||
_fzf_complete_ssh() {
|
_fzf_complete_ssh() {
|
||||||
_fzf_complete '+m' "$@" < <(
|
_fzf_complete '+m' "$@" < <(
|
||||||
cat <(cat ~/.ssh/config /etc/ssh/ssh_config 2> /dev/null | command grep -i '^host' | command grep -v '*') \
|
cat <(cat ~/.ssh/config /etc/ssh/ssh_config 2> /dev/null | command grep -i '^host' | command grep -v '*' | awk '{for (i = 2; i <= NF; i++) print $1 " " $i}') \
|
||||||
<(command grep -oE '^[a-z0-9.,:-]+' ~/.ssh/known_hosts | tr ',' '\n' | awk '{ print $1 " " $1 }') \
|
<(command grep -oE '^[[a-z0-9.,:-]+' ~/.ssh/known_hosts | tr ',' '\n' | tr -d '[' | awk '{ print $1 " " $1 }') \
|
||||||
<(command grep -v '^\s*\(#\|$\)' /etc/hosts | command grep -Fv '0.0.0.0') |
|
<(command grep -v '^\s*\(#\|$\)' /etc/hosts | command grep -Fv '0.0.0.0') |
|
||||||
awk '{if (length($2) > 0) {print $2}}' | sort -u
|
awk '{if (length($2) > 0) {print $2}}' | sort -u
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -116,8 +116,8 @@ _fzf_complete_telnet() {
|
|||||||
|
|
||||||
_fzf_complete_ssh() {
|
_fzf_complete_ssh() {
|
||||||
_fzf_complete '+m' "$@" < <(
|
_fzf_complete '+m' "$@" < <(
|
||||||
command cat <(cat ~/.ssh/config /etc/ssh/ssh_config 2> /dev/null | command grep -i '^host' | command grep -v '*') \
|
command cat <(cat ~/.ssh/config /etc/ssh/ssh_config 2> /dev/null | command grep -i '^host' | command grep -v '*' | awk '{for (i = 2; i <= NF; i++) print $1 " " $i}') \
|
||||||
<(command grep -oE '^[a-z0-9.,:-]+' ~/.ssh/known_hosts | tr ',' '\n' | awk '{ print $1 " " $1 }') \
|
<(command grep -oE '^[[a-z0-9.,:-]+' ~/.ssh/known_hosts | tr ',' '\n' | tr -d '[' | awk '{ print $1 " " $1 }') \
|
||||||
<(command grep -v '^\s*\(#\|$\)' /etc/hosts | command grep -Fv '0.0.0.0') |
|
<(command grep -v '^\s*\(#\|$\)' /etc/hosts | command grep -Fv '0.0.0.0') |
|
||||||
awk '{if (length($2) > 0) {print $2}}' | sort -u
|
awk '{if (length($2) > 0) {print $2}}' | sort -u
|
||||||
)
|
)
|
||||||
@@ -163,7 +163,7 @@ fzf-completion() {
|
|||||||
# Kill completion (do not require trigger sequence)
|
# Kill completion (do not require trigger sequence)
|
||||||
if [ $cmd = kill -a ${LBUFFER[-1]} = ' ' ]; then
|
if [ $cmd = kill -a ${LBUFFER[-1]} = ' ' ]; then
|
||||||
fzf="$(__fzfcmd_complete)"
|
fzf="$(__fzfcmd_complete)"
|
||||||
matches=$(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 --preview 'echo {}' --preview-window down:3:wrap $FZF_COMPLETION_OPTS" ${=fzf} -m | awk '{print $2}' | tr '\n' ' ')
|
||||||
if [ -n "$matches" ]; then
|
if [ -n "$matches" ]; then
|
||||||
LBUFFER="$LBUFFER$matches"
|
LBUFFER="$LBUFFER$matches"
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -36,6 +36,16 @@ fzf-file-widget() {
|
|||||||
zle -N fzf-file-widget
|
zle -N fzf-file-widget
|
||||||
bindkey '^T' fzf-file-widget
|
bindkey '^T' fzf-file-widget
|
||||||
|
|
||||||
|
# Ensure precmds are run after cd
|
||||||
|
fzf-redraw-prompt() {
|
||||||
|
local precmd
|
||||||
|
for precmd in $precmd_functions; do
|
||||||
|
$precmd
|
||||||
|
done
|
||||||
|
zle reset-prompt
|
||||||
|
}
|
||||||
|
zle -N fzf-redraw-prompt
|
||||||
|
|
||||||
# ALT-C - cd into the selected directory
|
# ALT-C - cd into the selected directory
|
||||||
fzf-cd-widget() {
|
fzf-cd-widget() {
|
||||||
local cmd="${FZF_ALT_C_COMMAND:-"command find -L . -mindepth 1 \\( -path '*/\\.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' -o -fstype 'proc' \\) -prune \
|
local cmd="${FZF_ALT_C_COMMAND:-"command find -L . -mindepth 1 \\( -path '*/\\.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' -o -fstype 'proc' \\) -prune \
|
||||||
@@ -48,7 +58,7 @@ fzf-cd-widget() {
|
|||||||
fi
|
fi
|
||||||
cd "$dir"
|
cd "$dir"
|
||||||
local ret=$?
|
local ret=$?
|
||||||
zle reset-prompt
|
zle fzf-redraw-prompt
|
||||||
typeset -f zle-line-init >/dev/null && zle zle-line-init
|
typeset -f zle-line-init >/dev/null && zle zle-line-init
|
||||||
return $ret
|
return $ret
|
||||||
}
|
}
|
||||||
@@ -59,8 +69,8 @@ bindkey '\ec' fzf-cd-widget
|
|||||||
fzf-history-widget() {
|
fzf-history-widget() {
|
||||||
local selected num
|
local selected num
|
||||||
setopt localoptions noglobsubst noposixbuiltins pipefail 2> /dev/null
|
setopt localoptions noglobsubst noposixbuiltins pipefail 2> /dev/null
|
||||||
selected=( $(fc -l 1 |
|
selected=( $(fc -rl 1 |
|
||||||
FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} $FZF_DEFAULT_OPTS --tac -n2..,.. --tiebreak=index --bind=ctrl-r:toggle-sort $FZF_CTRL_R_OPTS --query=${(q)LBUFFER} +m" $(__fzfcmd)) )
|
FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} $FZF_DEFAULT_OPTS -n2..,.. --tiebreak=index --bind=ctrl-r:toggle-sort $FZF_CTRL_R_OPTS --query=${(qqq)LBUFFER} +m" $(__fzfcmd)) )
|
||||||
local ret=$?
|
local ret=$?
|
||||||
if [ -n "$selected" ]; then
|
if [ -n "$selected" ]; then
|
||||||
num=$selected[1]
|
num=$selected[1]
|
||||||
|
|||||||
351
src/algo/algo.go
351
src/algo/algo.go
@@ -78,9 +78,11 @@ Scoring criteria
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
"unicode"
|
"unicode"
|
||||||
|
"unicode/utf8"
|
||||||
|
|
||||||
"github.com/junegunn/fzf/src/util"
|
"github.com/junegunn/fzf/src/util"
|
||||||
)
|
)
|
||||||
@@ -156,27 +158,17 @@ func posArray(withPos bool, len int) *[]int {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func alloc16(offset int, slab *util.Slab, size int, clear bool) (int, []int16) {
|
func alloc16(offset int, slab *util.Slab, size int) (int, []int16) {
|
||||||
if slab != nil && cap(slab.I16) > offset+size {
|
if slab != nil && cap(slab.I16) > offset+size {
|
||||||
slice := slab.I16[offset : offset+size]
|
slice := slab.I16[offset : offset+size]
|
||||||
if clear {
|
|
||||||
for idx := range slice {
|
|
||||||
slice[idx] = 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return offset + size, slice
|
return offset + size, slice
|
||||||
}
|
}
|
||||||
return offset, make([]int16, size)
|
return offset, make([]int16, size)
|
||||||
}
|
}
|
||||||
|
|
||||||
func alloc32(offset int, slab *util.Slab, size int, clear bool) (int, []int32) {
|
func alloc32(offset int, slab *util.Slab, size int) (int, []int32) {
|
||||||
if slab != nil && cap(slab.I32) > offset+size {
|
if slab != nil && cap(slab.I32) > offset+size {
|
||||||
slice := slab.I32[offset : offset+size]
|
slice := slab.I32[offset : offset+size]
|
||||||
if clear {
|
|
||||||
for idx := range slice {
|
|
||||||
slice[idx] = 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return offset + size, slice
|
return offset + size, slice
|
||||||
}
|
}
|
||||||
return offset, make([]int32, size)
|
return offset, make([]int32, size)
|
||||||
@@ -227,7 +219,7 @@ func bonusFor(prevClass charClass, class charClass) int16 {
|
|||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func bonusAt(input util.Chars, idx int) int16 {
|
func bonusAt(input *util.Chars, idx int) int16 {
|
||||||
if idx == 0 {
|
if idx == 0 {
|
||||||
return bonusBoundary
|
return bonusBoundary
|
||||||
}
|
}
|
||||||
@@ -249,21 +241,113 @@ func normalizeRune(r rune) rune {
|
|||||||
// Algo functions make two assumptions
|
// Algo functions make two assumptions
|
||||||
// 1. "pattern" is given in lowercase if "caseSensitive" is false
|
// 1. "pattern" is given in lowercase if "caseSensitive" is false
|
||||||
// 2. "pattern" is already normalized if "normalize" is true
|
// 2. "pattern" is already normalized if "normalize" is true
|
||||||
type Algo func(caseSensitive bool, normalize bool, forward bool, input util.Chars, pattern []rune, withPos bool, slab *util.Slab) (Result, *[]int)
|
type Algo func(caseSensitive bool, normalize bool, forward bool, input *util.Chars, pattern []rune, withPos bool, slab *util.Slab) (Result, *[]int)
|
||||||
|
|
||||||
func FuzzyMatchV2(caseSensitive bool, normalize bool, forward bool, input util.Chars, pattern []rune, withPos bool, slab *util.Slab) (Result, *[]int) {
|
func trySkip(input *util.Chars, caseSensitive bool, b byte, from int) int {
|
||||||
|
byteArray := input.Bytes()[from:]
|
||||||
|
idx := bytes.IndexByte(byteArray, b)
|
||||||
|
if idx == 0 {
|
||||||
|
// Can't skip any further
|
||||||
|
return from
|
||||||
|
}
|
||||||
|
// We may need to search for the uppercase letter again. We don't have to
|
||||||
|
// consider normalization as we can be sure that this is an ASCII string.
|
||||||
|
if !caseSensitive && b >= 'a' && b <= 'z' {
|
||||||
|
if idx > 0 {
|
||||||
|
byteArray = byteArray[:idx]
|
||||||
|
}
|
||||||
|
uidx := bytes.IndexByte(byteArray, b-32)
|
||||||
|
if uidx >= 0 {
|
||||||
|
idx = uidx
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if idx < 0 {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
return from + idx
|
||||||
|
}
|
||||||
|
|
||||||
|
func isAscii(runes []rune) bool {
|
||||||
|
for _, r := range runes {
|
||||||
|
if r >= utf8.RuneSelf {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func asciiFuzzyIndex(input *util.Chars, pattern []rune, caseSensitive bool) int {
|
||||||
|
// Can't determine
|
||||||
|
if !input.IsBytes() {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Not possible
|
||||||
|
if !isAscii(pattern) {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
firstIdx, idx := 0, 0
|
||||||
|
for pidx := 0; pidx < len(pattern); pidx++ {
|
||||||
|
idx = trySkip(input, caseSensitive, byte(pattern[pidx]), idx)
|
||||||
|
if idx < 0 {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
if pidx == 0 && idx > 0 {
|
||||||
|
// Step back to find the right bonus point
|
||||||
|
firstIdx = idx - 1
|
||||||
|
}
|
||||||
|
idx++
|
||||||
|
}
|
||||||
|
return firstIdx
|
||||||
|
}
|
||||||
|
|
||||||
|
func debugV2(T []rune, pattern []rune, F []int32, lastIdx int, H []int16, C []int16) {
|
||||||
|
width := lastIdx - int(F[0]) + 1
|
||||||
|
|
||||||
|
for i, f := range F {
|
||||||
|
I := i * width
|
||||||
|
if i == 0 {
|
||||||
|
fmt.Print(" ")
|
||||||
|
for j := int(f); j <= lastIdx; j++ {
|
||||||
|
fmt.Printf(" " + string(T[j]) + " ")
|
||||||
|
}
|
||||||
|
fmt.Println()
|
||||||
|
}
|
||||||
|
fmt.Print(string(pattern[i]) + " ")
|
||||||
|
for idx := int(F[0]); idx < int(f); idx++ {
|
||||||
|
fmt.Print(" 0 ")
|
||||||
|
}
|
||||||
|
for idx := int(f); idx <= lastIdx; idx++ {
|
||||||
|
fmt.Printf("%2d ", H[i*width+idx-int(F[0])])
|
||||||
|
}
|
||||||
|
fmt.Println()
|
||||||
|
|
||||||
|
fmt.Print(" ")
|
||||||
|
for idx, p := range C[I : I+width] {
|
||||||
|
if idx+int(F[0]) < int(F[i]) {
|
||||||
|
p = 0
|
||||||
|
}
|
||||||
|
if p > 0 {
|
||||||
|
fmt.Printf("%2d ", p)
|
||||||
|
} else {
|
||||||
|
fmt.Print(" ")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fmt.Println()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func FuzzyMatchV2(caseSensitive bool, normalize bool, forward bool, input *util.Chars, pattern []rune, withPos bool, slab *util.Slab) (Result, *[]int) {
|
||||||
// Assume that pattern is given in lowercase if case-insensitive.
|
// Assume that pattern is given in lowercase if case-insensitive.
|
||||||
// First check if there's a match and calculate bonus for each position.
|
// First check if there's a match and calculate bonus for each position.
|
||||||
// If the input string is too long, consider finding the matching chars in
|
// If the input string is too long, consider finding the matching chars in
|
||||||
// this phase as well (non-optimal alignment).
|
// this phase as well (non-optimal alignment).
|
||||||
N := input.Length()
|
|
||||||
M := len(pattern)
|
M := len(pattern)
|
||||||
switch M {
|
if M == 0 {
|
||||||
case 0:
|
|
||||||
return Result{0, 0, 0}, posArray(withPos, M)
|
return Result{0, 0, 0}, posArray(withPos, M)
|
||||||
case 1:
|
|
||||||
return ExactMatchNaive(caseSensitive, normalize, forward, input, pattern[0:1], withPos, slab)
|
|
||||||
}
|
}
|
||||||
|
N := input.Length()
|
||||||
|
|
||||||
// Since O(nm) algorithm can be prohibitively expensive for large input,
|
// Since O(nm) algorithm can be prohibitively expensive for large input,
|
||||||
// we fall back to the greedy algorithm.
|
// we fall back to the greedy algorithm.
|
||||||
@@ -271,159 +355,175 @@ func FuzzyMatchV2(caseSensitive bool, normalize bool, forward bool, input util.C
|
|||||||
return FuzzyMatchV1(caseSensitive, normalize, forward, input, pattern, withPos, slab)
|
return FuzzyMatchV1(caseSensitive, normalize, forward, input, pattern, withPos, slab)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Phase 1. Optimized search for ASCII string
|
||||||
|
idx := asciiFuzzyIndex(input, pattern, caseSensitive)
|
||||||
|
if idx < 0 {
|
||||||
|
return Result{-1, -1, 0}, nil
|
||||||
|
}
|
||||||
|
|
||||||
// Reuse pre-allocated integer slice to avoid unnecessary sweeping of garbages
|
// Reuse pre-allocated integer slice to avoid unnecessary sweeping of garbages
|
||||||
offset16 := 0
|
offset16 := 0
|
||||||
offset32 := 0
|
offset32 := 0
|
||||||
|
offset16, H0 := alloc16(offset16, slab, N)
|
||||||
|
offset16, C0 := alloc16(offset16, slab, N)
|
||||||
// Bonus point for each position
|
// Bonus point for each position
|
||||||
offset16, B := alloc16(offset16, slab, N, false)
|
offset16, B := alloc16(offset16, slab, N)
|
||||||
// The first occurrence of each character in the pattern
|
// The first occurrence of each character in the pattern
|
||||||
offset32, F := alloc32(offset32, slab, M, false)
|
offset32, F := alloc32(offset32, slab, M)
|
||||||
// Rune array
|
// Rune array
|
||||||
offset32, T := alloc32(offset32, slab, N, false)
|
offset32, T := alloc32(offset32, slab, N)
|
||||||
|
|
||||||
// Phase 1. Check if there's a match and calculate bonus for each point
|
|
||||||
pidx, lastIdx, prevClass := 0, 0, charNonWord
|
|
||||||
input.CopyRunes(T)
|
input.CopyRunes(T)
|
||||||
for idx := 0; idx < N; idx++ {
|
|
||||||
char := T[idx]
|
// Phase 2. Calculate bonus for each point
|
||||||
|
maxScore, maxScorePos := int16(0), 0
|
||||||
|
pidx, lastIdx := 0, 0
|
||||||
|
pchar0, pchar, prevH0, prevClass, inGap := pattern[0], pattern[0], int16(0), charNonWord, false
|
||||||
|
Tsub := T[idx:]
|
||||||
|
H0sub, C0sub, Bsub := H0[idx:][:len(Tsub)], C0[idx:][:len(Tsub)], B[idx:][:len(Tsub)]
|
||||||
|
for off, char := range Tsub {
|
||||||
var class charClass
|
var class charClass
|
||||||
if char <= unicode.MaxASCII {
|
if char <= unicode.MaxASCII {
|
||||||
class = charClassOfAscii(char)
|
class = charClassOfAscii(char)
|
||||||
|
if !caseSensitive && class == charUpper {
|
||||||
|
char += 32
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
class = charClassOfNonAscii(char)
|
class = charClassOfNonAscii(char)
|
||||||
}
|
if !caseSensitive && class == charUpper {
|
||||||
|
|
||||||
if !caseSensitive && class == charUpper {
|
|
||||||
if char <= unicode.MaxASCII {
|
|
||||||
char += 32
|
|
||||||
} else {
|
|
||||||
char = unicode.To(unicode.LowerCase, char)
|
char = unicode.To(unicode.LowerCase, char)
|
||||||
}
|
}
|
||||||
|
if normalize {
|
||||||
|
char = normalizeRune(char)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if normalize {
|
Tsub[off] = char
|
||||||
char = normalizeRune(char)
|
bonus := bonusFor(prevClass, class)
|
||||||
}
|
Bsub[off] = bonus
|
||||||
|
|
||||||
T[idx] = char
|
|
||||||
B[idx] = bonusFor(prevClass, class)
|
|
||||||
prevClass = class
|
prevClass = class
|
||||||
|
|
||||||
if pidx < M {
|
if char == pchar {
|
||||||
if char == pattern[pidx] {
|
if pidx < M {
|
||||||
lastIdx = idx
|
F[pidx] = int32(idx + off)
|
||||||
F[pidx] = int32(idx)
|
|
||||||
pidx++
|
pidx++
|
||||||
|
pchar = pattern[util.Min(pidx, M-1)]
|
||||||
}
|
}
|
||||||
} else {
|
lastIdx = idx + off
|
||||||
if char == pattern[M-1] {
|
|
||||||
lastIdx = idx
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if char == pchar0 {
|
||||||
|
score := scoreMatch + bonus*bonusFirstCharMultiplier
|
||||||
|
H0sub[off] = score
|
||||||
|
C0sub[off] = 1
|
||||||
|
if M == 1 && (forward && score > maxScore || !forward && score >= maxScore) {
|
||||||
|
maxScore, maxScorePos = score, idx+off
|
||||||
|
if forward && bonus == bonusBoundary {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
inGap = false
|
||||||
|
} else {
|
||||||
|
if inGap {
|
||||||
|
H0sub[off] = util.Max16(prevH0+scoreGapExtention, 0)
|
||||||
|
} else {
|
||||||
|
H0sub[off] = util.Max16(prevH0+scoreGapStart, 0)
|
||||||
|
}
|
||||||
|
C0sub[off] = 0
|
||||||
|
inGap = true
|
||||||
|
}
|
||||||
|
prevH0 = H0sub[off]
|
||||||
}
|
}
|
||||||
if pidx != M {
|
if pidx != M {
|
||||||
return Result{-1, -1, 0}, nil
|
return Result{-1, -1, 0}, nil
|
||||||
}
|
}
|
||||||
|
if M == 1 {
|
||||||
|
result := Result{maxScorePos, maxScorePos + 1, int(maxScore)}
|
||||||
|
if !withPos {
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
pos := []int{maxScorePos}
|
||||||
|
return result, &pos
|
||||||
|
}
|
||||||
|
|
||||||
// Phase 2. Fill in score matrix (H)
|
// Phase 3. Fill in score matrix (H)
|
||||||
// Unlike the original algorithm, we do not allow omission.
|
// Unlike the original algorithm, we do not allow omission.
|
||||||
width := lastIdx - int(F[0]) + 1
|
f0 := int(F[0])
|
||||||
offset16, H := alloc16(offset16, slab, width*M, false)
|
width := lastIdx - f0 + 1
|
||||||
|
offset16, H := alloc16(offset16, slab, width*M)
|
||||||
|
copy(H, H0[f0:lastIdx+1])
|
||||||
|
|
||||||
// Possible length of consecutive chunk at each position.
|
// Possible length of consecutive chunk at each position.
|
||||||
offset16, C := alloc16(offset16, slab, width*M, false)
|
offset16, C := alloc16(offset16, slab, width*M)
|
||||||
|
copy(C, C0[f0:lastIdx+1])
|
||||||
|
|
||||||
maxScore, maxScorePos := int16(0), 0
|
Fsub := F[1:]
|
||||||
for i := 0; i < M; i++ {
|
Psub := pattern[1:][:len(Fsub)]
|
||||||
I := i * width
|
for off, f := range Fsub {
|
||||||
|
f := int(f)
|
||||||
|
pchar := Psub[off]
|
||||||
|
pidx := off + 1
|
||||||
|
row := pidx * width
|
||||||
inGap := false
|
inGap := false
|
||||||
for j := int(F[i]); j <= lastIdx; j++ {
|
Tsub := T[f : lastIdx+1]
|
||||||
j0 := j - int(F[0])
|
Bsub := B[f:][:len(Tsub)]
|
||||||
|
Csub := C[row+f-f0:][:len(Tsub)]
|
||||||
|
Cdiag := C[row+f-f0-1-width:][:len(Tsub)]
|
||||||
|
Hsub := H[row+f-f0:][:len(Tsub)]
|
||||||
|
Hdiag := H[row+f-f0-1-width:][:len(Tsub)]
|
||||||
|
Hleft := H[row+f-f0-1:][:len(Tsub)]
|
||||||
|
Hleft[0] = 0
|
||||||
|
for off, char := range Tsub {
|
||||||
|
col := off + f
|
||||||
var s1, s2, consecutive int16
|
var s1, s2, consecutive int16
|
||||||
|
|
||||||
if j > int(F[i]) {
|
if inGap {
|
||||||
if inGap {
|
s2 = Hleft[off] + scoreGapExtention
|
||||||
s2 = H[I+j0-1] + scoreGapExtention
|
} else {
|
||||||
} else {
|
s2 = Hleft[off] + scoreGapStart
|
||||||
s2 = H[I+j0-1] + scoreGapStart
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if pattern[i] == T[j] {
|
if pchar == char {
|
||||||
var diag int16
|
s1 = Hdiag[off] + scoreMatch
|
||||||
if i > 0 && j0 > 0 {
|
b := Bsub[off]
|
||||||
diag = H[I-width+j0-1]
|
consecutive = Cdiag[off] + 1
|
||||||
}
|
// Break consecutive chunk
|
||||||
s1 = diag + scoreMatch
|
if b == bonusBoundary {
|
||||||
b := B[j]
|
|
||||||
if i > 0 {
|
|
||||||
// j > 0 if i > 0
|
|
||||||
consecutive = C[I-width+j0-1] + 1
|
|
||||||
// Break consecutive chunk
|
|
||||||
if b == bonusBoundary {
|
|
||||||
consecutive = 1
|
|
||||||
} else if consecutive > 1 {
|
|
||||||
b = util.Max16(b, util.Max16(bonusConsecutive, B[j-int(consecutive)+1]))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
consecutive = 1
|
consecutive = 1
|
||||||
b *= bonusFirstCharMultiplier
|
} else if consecutive > 1 {
|
||||||
|
b = util.Max16(b, util.Max16(bonusConsecutive, B[col-int(consecutive)+1]))
|
||||||
}
|
}
|
||||||
if s1+b < s2 {
|
if s1+b < s2 {
|
||||||
s1 += B[j]
|
s1 += Bsub[off]
|
||||||
consecutive = 0
|
consecutive = 0
|
||||||
} else {
|
} else {
|
||||||
s1 += b
|
s1 += b
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
C[I+j0] = consecutive
|
Csub[off] = consecutive
|
||||||
|
|
||||||
inGap = s1 < s2
|
inGap = s1 < s2
|
||||||
score := util.Max16(util.Max16(s1, s2), 0)
|
score := util.Max16(util.Max16(s1, s2), 0)
|
||||||
if i == M-1 && (forward && score > maxScore || !forward && score >= maxScore) {
|
if pidx == M-1 && (forward && score > maxScore || !forward && score >= maxScore) {
|
||||||
maxScore, maxScorePos = score, j
|
maxScore, maxScorePos = score, col
|
||||||
}
|
}
|
||||||
H[I+j0] = score
|
Hsub[off] = score
|
||||||
}
|
|
||||||
|
|
||||||
if DEBUG {
|
|
||||||
if i == 0 {
|
|
||||||
fmt.Print(" ")
|
|
||||||
for j := int(F[i]); j <= lastIdx; j++ {
|
|
||||||
fmt.Printf(" " + string(T[j]) + " ")
|
|
||||||
}
|
|
||||||
fmt.Println()
|
|
||||||
}
|
|
||||||
fmt.Print(string(pattern[i]) + " ")
|
|
||||||
for idx := int(F[0]); idx < int(F[i]); idx++ {
|
|
||||||
fmt.Print(" 0 ")
|
|
||||||
}
|
|
||||||
for idx := int(F[i]); idx <= lastIdx; idx++ {
|
|
||||||
fmt.Printf("%2d ", H[i*width+idx-int(F[0])])
|
|
||||||
}
|
|
||||||
fmt.Println()
|
|
||||||
|
|
||||||
fmt.Print(" ")
|
|
||||||
for idx, p := range C[I : I+width] {
|
|
||||||
if idx+int(F[0]) < int(F[i]) {
|
|
||||||
p = 0
|
|
||||||
}
|
|
||||||
fmt.Printf("%2d ", p)
|
|
||||||
}
|
|
||||||
fmt.Println()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Phase 3. (Optional) Backtrace to find character positions
|
if DEBUG {
|
||||||
|
debugV2(T, pattern, F, lastIdx, H, C)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Phase 4. (Optional) Backtrace to find character positions
|
||||||
pos := posArray(withPos, M)
|
pos := posArray(withPos, M)
|
||||||
j := int(F[0])
|
j := f0
|
||||||
if withPos {
|
if withPos {
|
||||||
i := M - 1
|
i := M - 1
|
||||||
j = maxScorePos
|
j = maxScorePos
|
||||||
preferMatch := true
|
preferMatch := true
|
||||||
for {
|
for {
|
||||||
I := i * width
|
I := i * width
|
||||||
j0 := j - int(F[0])
|
j0 := j - f0
|
||||||
s := H[I+j0]
|
s := H[I+j0]
|
||||||
|
|
||||||
var s1, s2 int16
|
var s1, s2 int16
|
||||||
@@ -452,7 +552,7 @@ func FuzzyMatchV2(caseSensitive bool, normalize bool, forward bool, input util.C
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Implement the same sorting criteria as V2
|
// Implement the same sorting criteria as V2
|
||||||
func calculateScore(caseSensitive bool, normalize bool, text util.Chars, pattern []rune, sidx int, eidx int, withPos bool) (int, *[]int) {
|
func calculateScore(caseSensitive bool, normalize bool, text *util.Chars, pattern []rune, sidx int, eidx int, withPos bool) (int, *[]int) {
|
||||||
pidx, score, inGap, consecutive, firstBonus := 0, 0, false, 0, int16(0)
|
pidx, score, inGap, consecutive, firstBonus := 0, 0, false, 0, int16(0)
|
||||||
pos := posArray(withPos, len(pattern))
|
pos := posArray(withPos, len(pattern))
|
||||||
prevClass := charNonWord
|
prevClass := charNonWord
|
||||||
@@ -512,10 +612,13 @@ func calculateScore(caseSensitive bool, normalize bool, text util.Chars, pattern
|
|||||||
}
|
}
|
||||||
|
|
||||||
// FuzzyMatchV1 performs fuzzy-match
|
// FuzzyMatchV1 performs fuzzy-match
|
||||||
func FuzzyMatchV1(caseSensitive bool, normalize bool, forward bool, text util.Chars, pattern []rune, withPos bool, slab *util.Slab) (Result, *[]int) {
|
func FuzzyMatchV1(caseSensitive bool, normalize bool, forward bool, text *util.Chars, pattern []rune, withPos bool, slab *util.Slab) (Result, *[]int) {
|
||||||
if len(pattern) == 0 {
|
if len(pattern) == 0 {
|
||||||
return Result{0, 0, 0}, nil
|
return Result{0, 0, 0}, nil
|
||||||
}
|
}
|
||||||
|
if asciiFuzzyIndex(text, pattern, caseSensitive) < 0 {
|
||||||
|
return Result{-1, -1, 0}, nil
|
||||||
|
}
|
||||||
|
|
||||||
pidx := 0
|
pidx := 0
|
||||||
sidx := -1
|
sidx := -1
|
||||||
@@ -595,7 +698,7 @@ func FuzzyMatchV1(caseSensitive bool, normalize bool, forward bool, text util.Ch
|
|||||||
// bonus point, instead of stopping immediately after finding the first match.
|
// bonus point, instead of stopping immediately after finding the first match.
|
||||||
// The solution is much cheaper since there is only one possible alignment of
|
// The solution is much cheaper since there is only one possible alignment of
|
||||||
// the pattern.
|
// the pattern.
|
||||||
func ExactMatchNaive(caseSensitive bool, normalize bool, forward bool, text util.Chars, pattern []rune, withPos bool, slab *util.Slab) (Result, *[]int) {
|
func ExactMatchNaive(caseSensitive bool, normalize bool, forward bool, text *util.Chars, pattern []rune, withPos bool, slab *util.Slab) (Result, *[]int) {
|
||||||
if len(pattern) == 0 {
|
if len(pattern) == 0 {
|
||||||
return Result{0, 0, 0}, nil
|
return Result{0, 0, 0}, nil
|
||||||
}
|
}
|
||||||
@@ -607,6 +710,10 @@ func ExactMatchNaive(caseSensitive bool, normalize bool, forward bool, text util
|
|||||||
return Result{-1, -1, 0}, nil
|
return Result{-1, -1, 0}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if asciiFuzzyIndex(text, pattern, caseSensitive) < 0 {
|
||||||
|
return Result{-1, -1, 0}, nil
|
||||||
|
}
|
||||||
|
|
||||||
// For simplicity, only look at the bonus at the first character position
|
// For simplicity, only look at the bonus at the first character position
|
||||||
pidx := 0
|
pidx := 0
|
||||||
bestPos, bonus, bestBonus := -1, int16(0), int16(-1)
|
bestPos, bonus, bestBonus := -1, int16(0), int16(-1)
|
||||||
@@ -661,7 +768,7 @@ func ExactMatchNaive(caseSensitive bool, normalize bool, forward bool, text util
|
|||||||
}
|
}
|
||||||
|
|
||||||
// PrefixMatch performs prefix-match
|
// PrefixMatch performs prefix-match
|
||||||
func PrefixMatch(caseSensitive bool, normalize bool, forward bool, text util.Chars, pattern []rune, withPos bool, slab *util.Slab) (Result, *[]int) {
|
func PrefixMatch(caseSensitive bool, normalize bool, forward bool, text *util.Chars, pattern []rune, withPos bool, slab *util.Slab) (Result, *[]int) {
|
||||||
if len(pattern) == 0 {
|
if len(pattern) == 0 {
|
||||||
return Result{0, 0, 0}, nil
|
return Result{0, 0, 0}, nil
|
||||||
}
|
}
|
||||||
@@ -688,7 +795,7 @@ func PrefixMatch(caseSensitive bool, normalize bool, forward bool, text util.Cha
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SuffixMatch performs suffix-match
|
// SuffixMatch performs suffix-match
|
||||||
func SuffixMatch(caseSensitive bool, normalize bool, forward bool, text util.Chars, pattern []rune, withPos bool, slab *util.Slab) (Result, *[]int) {
|
func SuffixMatch(caseSensitive bool, normalize bool, forward bool, text *util.Chars, pattern []rune, withPos bool, slab *util.Slab) (Result, *[]int) {
|
||||||
lenRunes := text.Length()
|
lenRunes := text.Length()
|
||||||
trimmedLen := lenRunes - text.TrailingWhitespaces()
|
trimmedLen := lenRunes - text.TrailingWhitespaces()
|
||||||
if len(pattern) == 0 {
|
if len(pattern) == 0 {
|
||||||
@@ -719,7 +826,7 @@ func SuffixMatch(caseSensitive bool, normalize bool, forward bool, text util.Cha
|
|||||||
}
|
}
|
||||||
|
|
||||||
// EqualMatch performs equal-match
|
// EqualMatch performs equal-match
|
||||||
func EqualMatch(caseSensitive bool, normalize bool, forward bool, text util.Chars, pattern []rune, withPos bool, slab *util.Slab) (Result, *[]int) {
|
func EqualMatch(caseSensitive bool, normalize bool, forward bool, text *util.Chars, pattern []rune, withPos bool, slab *util.Slab) (Result, *[]int) {
|
||||||
lenPattern := len(pattern)
|
lenPattern := len(pattern)
|
||||||
if text.Length() != lenPattern {
|
if text.Length() != lenPattern {
|
||||||
return Result{-1, -1, 0}, nil
|
return Result{-1, -1, 0}, nil
|
||||||
|
|||||||
@@ -17,7 +17,8 @@ func assertMatch2(t *testing.T, fun Algo, caseSensitive, normalize, forward bool
|
|||||||
if !caseSensitive {
|
if !caseSensitive {
|
||||||
pattern = strings.ToLower(pattern)
|
pattern = strings.ToLower(pattern)
|
||||||
}
|
}
|
||||||
res, pos := fun(caseSensitive, normalize, forward, util.RunesToChars([]rune(input)), []rune(pattern), true, nil)
|
chars := util.ToChars([]byte(input))
|
||||||
|
res, pos := fun(caseSensitive, normalize, forward, &chars, []rune(pattern), true, nil)
|
||||||
var start, end int
|
var start, end int
|
||||||
if pos == nil || len(*pos) == 0 {
|
if pos == nil || len(*pos) == 0 {
|
||||||
start = res.Start
|
start = res.Start
|
||||||
|
|||||||
30
src/ansi.go
30
src/ansi.go
@@ -73,15 +73,13 @@ func extractColor(str string, state *ansiState, proc func(string, *ansiState) bo
|
|||||||
runeCount := 0
|
runeCount := 0
|
||||||
for idx := 0; idx < len(str); {
|
for idx := 0; idx < len(str); {
|
||||||
idx += findAnsiStart(str[idx:])
|
idx += findAnsiStart(str[idx:])
|
||||||
|
|
||||||
// No sign of ANSI code
|
|
||||||
if idx == len(str) {
|
if idx == len(str) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make sure that we found an ANSI code
|
// Make sure that we found an ANSI code
|
||||||
offset := ansiRegex.FindStringIndex(str[idx:])
|
offset := ansiRegex.FindStringIndex(str[idx:])
|
||||||
if offset == nil {
|
if len(offset) < 2 {
|
||||||
idx++
|
idx++
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -117,22 +115,30 @@ func extractColor(str string, state *ansiState, proc func(string, *ansiState) bo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
rest := str[prevIdx:]
|
var rest string
|
||||||
if len(rest) > 0 {
|
var trimmed string
|
||||||
|
|
||||||
|
if prevIdx == 0 {
|
||||||
|
// No ANSI code found
|
||||||
|
rest = str
|
||||||
|
trimmed = str
|
||||||
|
} else {
|
||||||
|
rest = str[prevIdx:]
|
||||||
output.WriteString(rest)
|
output.WriteString(rest)
|
||||||
if state != nil {
|
trimmed = output.String()
|
||||||
// Update last offset
|
}
|
||||||
runeCount += utf8.RuneCountInString(rest)
|
if len(rest) > 0 && state != nil {
|
||||||
(&offsets[len(offsets)-1]).offset[1] = int32(runeCount)
|
// Update last offset
|
||||||
}
|
runeCount += utf8.RuneCountInString(rest)
|
||||||
|
(&offsets[len(offsets)-1]).offset[1] = int32(runeCount)
|
||||||
}
|
}
|
||||||
if proc != nil {
|
if proc != nil {
|
||||||
proc(rest, state)
|
proc(rest, state)
|
||||||
}
|
}
|
||||||
if len(offsets) == 0 {
|
if len(offsets) == 0 {
|
||||||
return output.String(), nil, state
|
return trimmed, nil, state
|
||||||
}
|
}
|
||||||
return output.String(), &offsets, state
|
return trimmed, &offsets, state
|
||||||
}
|
}
|
||||||
|
|
||||||
func interpretCode(ansiCode string, prevState *ansiState) *ansiState {
|
func interpretCode(ansiCode string, prevState *ansiState) *ansiState {
|
||||||
|
|||||||
@@ -4,9 +4,8 @@ import "testing"
|
|||||||
|
|
||||||
func TestChunkCache(t *testing.T) {
|
func TestChunkCache(t *testing.T) {
|
||||||
cache := NewChunkCache()
|
cache := NewChunkCache()
|
||||||
chunk2 := make(Chunk, chunkSize)
|
|
||||||
chunk1p := &Chunk{}
|
chunk1p := &Chunk{}
|
||||||
chunk2p := &chunk2
|
chunk2p := &Chunk{count: chunkSize}
|
||||||
items1 := []Result{Result{}}
|
items1 := []Result{Result{}}
|
||||||
items2 := []Result{Result{}, Result{}}
|
items2 := []Result{Result{}, Result{}}
|
||||||
cache.Add(chunk1p, "foo", items1)
|
cache.Add(chunk1p, "foo", items1)
|
||||||
|
|||||||
@@ -3,16 +3,17 @@ package fzf
|
|||||||
import "sync"
|
import "sync"
|
||||||
|
|
||||||
// Chunk is a list of Items whose size has the upper limit of chunkSize
|
// Chunk is a list of Items whose size has the upper limit of chunkSize
|
||||||
type Chunk []Item
|
type Chunk struct {
|
||||||
|
items [chunkSize]Item
|
||||||
|
count int
|
||||||
|
}
|
||||||
|
|
||||||
// ItemBuilder is a closure type that builds Item object from a pointer to a
|
// ItemBuilder is a closure type that builds Item object from byte array
|
||||||
// string and an integer
|
type ItemBuilder func(*Item, []byte) bool
|
||||||
type ItemBuilder func([]byte, int) Item
|
|
||||||
|
|
||||||
// ChunkList is a list of Chunks
|
// ChunkList is a list of Chunks
|
||||||
type ChunkList struct {
|
type ChunkList struct {
|
||||||
chunks []*Chunk
|
chunks []*Chunk
|
||||||
count int
|
|
||||||
mutex sync.Mutex
|
mutex sync.Mutex
|
||||||
trans ItemBuilder
|
trans ItemBuilder
|
||||||
}
|
}
|
||||||
@@ -21,23 +22,21 @@ type ChunkList struct {
|
|||||||
func NewChunkList(trans ItemBuilder) *ChunkList {
|
func NewChunkList(trans ItemBuilder) *ChunkList {
|
||||||
return &ChunkList{
|
return &ChunkList{
|
||||||
chunks: []*Chunk{},
|
chunks: []*Chunk{},
|
||||||
count: 0,
|
|
||||||
mutex: sync.Mutex{},
|
mutex: sync.Mutex{},
|
||||||
trans: trans}
|
trans: trans}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Chunk) push(trans ItemBuilder, data []byte, index int) bool {
|
func (c *Chunk) push(trans ItemBuilder, data []byte) bool {
|
||||||
item := trans(data, index)
|
if trans(&c.items[c.count], data) {
|
||||||
if item.Nil() {
|
c.count++
|
||||||
return false
|
return true
|
||||||
}
|
}
|
||||||
*c = append(*c, item)
|
return false
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsFull returns true if the Chunk is full
|
// IsFull returns true if the Chunk is full
|
||||||
func (c *Chunk) IsFull() bool {
|
func (c *Chunk) IsFull() bool {
|
||||||
return len(*c) == chunkSize
|
return c.count == chunkSize
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cl *ChunkList) lastChunk() *Chunk {
|
func (cl *ChunkList) lastChunk() *Chunk {
|
||||||
@@ -49,30 +48,25 @@ func CountItems(cs []*Chunk) int {
|
|||||||
if len(cs) == 0 {
|
if len(cs) == 0 {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
return chunkSize*(len(cs)-1) + len(*(cs[len(cs)-1]))
|
return chunkSize*(len(cs)-1) + cs[len(cs)-1].count
|
||||||
}
|
}
|
||||||
|
|
||||||
// Push adds the item to the list
|
// Push adds the item to the list
|
||||||
func (cl *ChunkList) Push(data []byte) bool {
|
func (cl *ChunkList) Push(data []byte) bool {
|
||||||
cl.mutex.Lock()
|
cl.mutex.Lock()
|
||||||
defer cl.mutex.Unlock()
|
|
||||||
|
|
||||||
if len(cl.chunks) == 0 || cl.lastChunk().IsFull() {
|
if len(cl.chunks) == 0 || cl.lastChunk().IsFull() {
|
||||||
newChunk := Chunk(make([]Item, 0, chunkSize))
|
cl.chunks = append(cl.chunks, &Chunk{})
|
||||||
cl.chunks = append(cl.chunks, &newChunk)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if cl.lastChunk().push(cl.trans, data, cl.count) {
|
ret := cl.lastChunk().push(cl.trans, data)
|
||||||
cl.count++
|
cl.mutex.Unlock()
|
||||||
return true
|
return ret
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Snapshot returns immutable snapshot of the ChunkList
|
// Snapshot returns immutable snapshot of the ChunkList
|
||||||
func (cl *ChunkList) Snapshot() ([]*Chunk, int) {
|
func (cl *ChunkList) Snapshot() ([]*Chunk, int) {
|
||||||
cl.mutex.Lock()
|
cl.mutex.Lock()
|
||||||
defer cl.mutex.Unlock()
|
|
||||||
|
|
||||||
ret := make([]*Chunk, len(cl.chunks))
|
ret := make([]*Chunk, len(cl.chunks))
|
||||||
copy(ret, cl.chunks)
|
copy(ret, cl.chunks)
|
||||||
@@ -82,5 +76,7 @@ func (cl *ChunkList) Snapshot() ([]*Chunk, int) {
|
|||||||
newChunk := *ret[cnt-1]
|
newChunk := *ret[cnt-1]
|
||||||
ret[cnt-1] = &newChunk
|
ret[cnt-1] = &newChunk
|
||||||
}
|
}
|
||||||
return ret, cl.count
|
|
||||||
|
cl.mutex.Unlock()
|
||||||
|
return ret, CountItems(ret)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,10 +11,9 @@ func TestChunkList(t *testing.T) {
|
|||||||
// FIXME global
|
// FIXME global
|
||||||
sortCriteria = []criterion{byScore, byLength}
|
sortCriteria = []criterion{byScore, byLength}
|
||||||
|
|
||||||
cl := NewChunkList(func(s []byte, i int) Item {
|
cl := NewChunkList(func(item *Item, s []byte) bool {
|
||||||
chars := util.ToChars(s)
|
item.text = util.ToChars(s)
|
||||||
chars.Index = int32(i * 2)
|
return true
|
||||||
return Item{text: chars}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// Snapshot
|
// Snapshot
|
||||||
@@ -40,11 +39,11 @@ func TestChunkList(t *testing.T) {
|
|||||||
|
|
||||||
// Check the content of the ChunkList
|
// Check the content of the ChunkList
|
||||||
chunk1 := snapshot[0]
|
chunk1 := snapshot[0]
|
||||||
if len(*chunk1) != 2 {
|
if chunk1.count != 2 {
|
||||||
t.Error("Snapshot should contain only two items")
|
t.Error("Snapshot should contain only two items")
|
||||||
}
|
}
|
||||||
if (*chunk1)[0].text.ToString() != "hello" || (*chunk1)[0].Index() != 0 ||
|
if chunk1.items[0].text.ToString() != "hello" ||
|
||||||
(*chunk1)[1].text.ToString() != "world" || (*chunk1)[1].Index() != 2 {
|
chunk1.items[1].text.ToString() != "world" {
|
||||||
t.Error("Invalid data")
|
t.Error("Invalid data")
|
||||||
}
|
}
|
||||||
if chunk1.IsFull() {
|
if chunk1.IsFull() {
|
||||||
@@ -67,14 +66,14 @@ func TestChunkList(t *testing.T) {
|
|||||||
!snapshot[1].IsFull() || snapshot[2].IsFull() || count != chunkSize*2+2 {
|
!snapshot[1].IsFull() || snapshot[2].IsFull() || count != chunkSize*2+2 {
|
||||||
t.Error("Expected two full chunks and one more chunk")
|
t.Error("Expected two full chunks and one more chunk")
|
||||||
}
|
}
|
||||||
if len(*snapshot[2]) != 2 {
|
if snapshot[2].count != 2 {
|
||||||
t.Error("Unexpected number of items")
|
t.Error("Unexpected number of items")
|
||||||
}
|
}
|
||||||
|
|
||||||
cl.Push([]byte("hello"))
|
cl.Push([]byte("hello"))
|
||||||
cl.Push([]byte("world"))
|
cl.Push([]byte("world"))
|
||||||
|
|
||||||
lastChunkCount := len(*snapshot[len(snapshot)-1])
|
lastChunkCount := snapshot[len(snapshot)-1].count
|
||||||
if lastChunkCount != 2 {
|
if lastChunkCount != 2 {
|
||||||
t.Error("Unexpected number of items:", lastChunkCount)
|
t.Error("Unexpected number of items:", lastChunkCount)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,14 +9,17 @@ import (
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
// Current version
|
// Current version
|
||||||
version = "0.16.9"
|
version = "0.17.3"
|
||||||
|
|
||||||
// Core
|
// Core
|
||||||
coordinatorDelayMax time.Duration = 100 * time.Millisecond
|
coordinatorDelayMax time.Duration = 100 * time.Millisecond
|
||||||
coordinatorDelayStep time.Duration = 10 * time.Millisecond
|
coordinatorDelayStep time.Duration = 10 * time.Millisecond
|
||||||
|
|
||||||
// Reader
|
// Reader
|
||||||
readerBufferSize = 64 * 1024
|
readerBufferSize = 64 * 1024
|
||||||
|
readerPollIntervalMin = 10 * time.Millisecond
|
||||||
|
readerPollIntervalStep = 5 * time.Millisecond
|
||||||
|
readerPollIntervalMax = 50 * time.Millisecond
|
||||||
|
|
||||||
// Terminal
|
// Terminal
|
||||||
initialDelay = 20 * time.Millisecond
|
initialDelay = 20 * time.Millisecond
|
||||||
@@ -52,7 +55,7 @@ var defaultCommand string
|
|||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
if !util.IsWindows() {
|
if !util.IsWindows() {
|
||||||
defaultCommand = `command find -L . -mindepth 1 \( -path '*/\.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' -o -fstype 'proc' \) -prune -o -type f -print -o -type l -print 2> /dev/null | cut -b3-`
|
defaultCommand = `set -o pipefail; command find -L . -mindepth 1 \( -path '*/\.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' -o -fstype 'proc' \) -prune -o -type f -print -o -type l -print 2> /dev/null | cut -b3-`
|
||||||
} else if os.Getenv("TERM") == "cygwin" {
|
} else if os.Getenv("TERM") == "cygwin" {
|
||||||
defaultCommand = `sh -c "command find -L . -mindepth 1 -path '*/\.*' -prune -o -type f -print -o -type l -print 2> /dev/null | cut -b3-"`
|
defaultCommand = `sh -c "command find -L . -mindepth 1 -path '*/\.*' -prune -o -type f -print -o -type l -print 2> /dev/null | cut -b3-"`
|
||||||
} else {
|
} else {
|
||||||
@@ -68,7 +71,7 @@ const (
|
|||||||
EvtSearchProgress
|
EvtSearchProgress
|
||||||
EvtSearchFin
|
EvtSearchFin
|
||||||
EvtHeader
|
EvtHeader
|
||||||
EvtClose
|
EvtReady
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|||||||
48
src/core.go
48
src/core.go
@@ -69,54 +69,58 @@ func Run(opts *Options, revision string) {
|
|||||||
ansiProcessor = func(data []byte) (util.Chars, *[]ansiOffset) {
|
ansiProcessor = func(data []byte) (util.Chars, *[]ansiOffset) {
|
||||||
trimmed, offsets, newState := extractColor(string(data), state, nil)
|
trimmed, offsets, newState := extractColor(string(data), state, nil)
|
||||||
state = newState
|
state = newState
|
||||||
return util.RunesToChars([]rune(trimmed)), offsets
|
return util.ToChars([]byte(trimmed)), offsets
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// When color is disabled but ansi option is given,
|
// When color is disabled but ansi option is given,
|
||||||
// we simply strip out ANSI codes from the input
|
// we simply strip out ANSI codes from the input
|
||||||
ansiProcessor = func(data []byte) (util.Chars, *[]ansiOffset) {
|
ansiProcessor = func(data []byte) (util.Chars, *[]ansiOffset) {
|
||||||
trimmed, _, _ := extractColor(string(data), nil, nil)
|
trimmed, _, _ := extractColor(string(data), nil, nil)
|
||||||
return util.RunesToChars([]rune(trimmed)), nil
|
return util.ToChars([]byte(trimmed)), nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Chunk list
|
// Chunk list
|
||||||
var chunkList *ChunkList
|
var chunkList *ChunkList
|
||||||
|
var itemIndex int32
|
||||||
header := make([]string, 0, opts.HeaderLines)
|
header := make([]string, 0, opts.HeaderLines)
|
||||||
if len(opts.WithNth) == 0 {
|
if len(opts.WithNth) == 0 {
|
||||||
chunkList = NewChunkList(func(data []byte, index int) Item {
|
chunkList = NewChunkList(func(item *Item, data []byte) bool {
|
||||||
if len(header) < opts.HeaderLines {
|
if len(header) < opts.HeaderLines {
|
||||||
header = append(header, string(data))
|
header = append(header, string(data))
|
||||||
eventBox.Set(EvtHeader, header)
|
eventBox.Set(EvtHeader, header)
|
||||||
return nilItem
|
return false
|
||||||
}
|
}
|
||||||
chars, colors := ansiProcessor(data)
|
item.text, item.colors = ansiProcessor(data)
|
||||||
chars.Index = int32(index)
|
item.text.Index = itemIndex
|
||||||
return Item{text: chars, colors: colors}
|
itemIndex++
|
||||||
|
return true
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
chunkList = NewChunkList(func(data []byte, index int) Item {
|
chunkList = NewChunkList(func(item *Item, data []byte) bool {
|
||||||
tokens := Tokenize(string(data), opts.Delimiter)
|
tokens := Tokenize(string(data), opts.Delimiter)
|
||||||
trans := Transform(tokens, opts.WithNth)
|
trans := Transform(tokens, opts.WithNth)
|
||||||
transformed := joinTokens(trans)
|
transformed := joinTokens(trans)
|
||||||
if len(header) < opts.HeaderLines {
|
if len(header) < opts.HeaderLines {
|
||||||
header = append(header, transformed)
|
header = append(header, transformed)
|
||||||
eventBox.Set(EvtHeader, header)
|
eventBox.Set(EvtHeader, header)
|
||||||
return nilItem
|
return false
|
||||||
}
|
}
|
||||||
trimmed, colors := ansiProcessor([]byte(transformed))
|
item.text, item.colors = ansiProcessor([]byte(transformed))
|
||||||
trimmed.Index = int32(index)
|
item.text.Index = itemIndex
|
||||||
return Item{text: trimmed, colors: colors, origText: &data}
|
item.origText = &data
|
||||||
|
itemIndex++
|
||||||
|
return true
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reader
|
// Reader
|
||||||
streamingFilter := opts.Filter != nil && !sort && !opts.Tac && !opts.Sync
|
streamingFilter := opts.Filter != nil && !sort && !opts.Tac && !opts.Sync
|
||||||
if !streamingFilter {
|
if !streamingFilter {
|
||||||
reader := Reader{func(data []byte) bool {
|
reader := NewReader(func(data []byte) bool {
|
||||||
return chunkList.Push(data)
|
return chunkList.Push(data)
|
||||||
}, eventBox, opts.ReadZero}
|
}, eventBox, opts.ReadZero)
|
||||||
go reader.ReadSource()
|
go reader.ReadSource()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -149,17 +153,17 @@ func Run(opts *Options, revision string) {
|
|||||||
found := false
|
found := false
|
||||||
if streamingFilter {
|
if streamingFilter {
|
||||||
slab := util.MakeSlab(slab16Size, slab32Size)
|
slab := util.MakeSlab(slab16Size, slab32Size)
|
||||||
reader := Reader{
|
reader := NewReader(
|
||||||
func(runes []byte) bool {
|
func(runes []byte) bool {
|
||||||
item := chunkList.trans(runes, 0)
|
item := Item{}
|
||||||
if !item.Nil() {
|
if chunkList.trans(&item, runes) {
|
||||||
if result, _, _ := pattern.MatchItem(&item, false, slab); result != nil {
|
if result, _, _ := pattern.MatchItem(&item, false, slab); result != nil {
|
||||||
opts.Printer(item.text.ToString())
|
opts.Printer(item.text.ToString())
|
||||||
found = true
|
found = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}, eventBox, opts.ReadZero}
|
}, eventBox, opts.ReadZero)
|
||||||
reader.ReadSource()
|
reader.ReadSource()
|
||||||
} else {
|
} else {
|
||||||
eventBox.Unwatch(EvtReadNew)
|
eventBox.Unwatch(EvtReadNew)
|
||||||
@@ -205,7 +209,9 @@ func Run(opts *Options, revision string) {
|
|||||||
delay := true
|
delay := true
|
||||||
ticks++
|
ticks++
|
||||||
eventBox.Wait(func(events *util.Events) {
|
eventBox.Wait(func(events *util.Events) {
|
||||||
defer events.Clear()
|
if _, fin := (*events)[EvtReadFin]; fin {
|
||||||
|
delete(*events, EvtReadNew)
|
||||||
|
}
|
||||||
for evt, value := range *events {
|
for evt, value := range *events {
|
||||||
switch evt {
|
switch evt {
|
||||||
|
|
||||||
@@ -213,6 +219,9 @@ func Run(opts *Options, revision string) {
|
|||||||
reading = reading && evt == EvtReadNew
|
reading = reading && evt == EvtReadNew
|
||||||
snapshot, count := chunkList.Snapshot()
|
snapshot, count := chunkList.Snapshot()
|
||||||
terminal.UpdateCount(count, !reading, value.(bool))
|
terminal.UpdateCount(count, !reading, value.(bool))
|
||||||
|
if opts.Sync {
|
||||||
|
terminal.UpdateList(PassMerger(&snapshot, opts.Tac))
|
||||||
|
}
|
||||||
matcher.Reset(snapshot, terminal.Input(), false, !reading, sort)
|
matcher.Reset(snapshot, terminal.Input(), false, !reading, sort)
|
||||||
|
|
||||||
case EvtSearchNew:
|
case EvtSearchNew:
|
||||||
@@ -265,6 +274,7 @@ func Run(opts *Options, revision string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
events.Clear()
|
||||||
})
|
})
|
||||||
if delay && reading {
|
if delay && reading {
|
||||||
dur := util.DurWithin(
|
dur := util.DurWithin(
|
||||||
|
|||||||
@@ -17,11 +17,7 @@ func (item *Item) Index() int32 {
|
|||||||
return item.text.Index
|
return item.text.Index
|
||||||
}
|
}
|
||||||
|
|
||||||
var nilItem = Item{text: util.Chars{Index: -1}}
|
var minItem = Item{text: util.Chars{Index: -1}}
|
||||||
|
|
||||||
func (item *Item) Nil() bool {
|
|
||||||
return item.Index() < 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (item *Item) TrimLength() uint16 {
|
func (item *Item) TrimLength() uint16 {
|
||||||
return item.text.TrimLength()
|
return item.text.TrimLength()
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ func PassMerger(chunks *[]*Chunk, tac bool) *Merger {
|
|||||||
count: 0}
|
count: 0}
|
||||||
|
|
||||||
for _, chunk := range *mg.chunks {
|
for _, chunk := range *mg.chunks {
|
||||||
mg.count += len(*chunk)
|
mg.count += chunk.count
|
||||||
}
|
}
|
||||||
return &mg
|
return &mg
|
||||||
}
|
}
|
||||||
@@ -65,7 +65,7 @@ func (mg *Merger) Get(idx int) Result {
|
|||||||
idx = mg.count - idx - 1
|
idx = mg.count - idx - 1
|
||||||
}
|
}
|
||||||
chunk := (*mg.chunks)[idx/chunkSize]
|
chunk := (*mg.chunks)[idx/chunkSize]
|
||||||
return Result{item: &(*chunk)[idx%chunkSize]}
|
return Result{item: &chunk.items[idx%chunkSize]}
|
||||||
}
|
}
|
||||||
|
|
||||||
if mg.sorted {
|
if mg.sorted {
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ func assert(t *testing.T, cond bool, msg ...string) {
|
|||||||
|
|
||||||
func randResult() Result {
|
func randResult() Result {
|
||||||
str := fmt.Sprintf("%d", rand.Uint32())
|
str := fmt.Sprintf("%d", rand.Uint32())
|
||||||
chars := util.RunesToChars([]rune(str))
|
chars := util.ToChars([]byte(str))
|
||||||
chars.Index = rand.Int31()
|
chars.Index = rand.Int31()
|
||||||
return Result{item: &Item{text: chars}}
|
return Result{item: &Item{text: chars}}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -430,6 +430,10 @@ func parseKeyChords(str string, message string) map[int]string {
|
|||||||
chord = tui.SLeft
|
chord = tui.SLeft
|
||||||
case "shift-right":
|
case "shift-right":
|
||||||
chord = tui.SRight
|
chord = tui.SRight
|
||||||
|
case "left-click":
|
||||||
|
chord = tui.LeftClick
|
||||||
|
case "right-click":
|
||||||
|
chord = tui.RightClick
|
||||||
case "double-click":
|
case "double-click":
|
||||||
chord = tui.DoubleClick
|
chord = tui.DoubleClick
|
||||||
case "f10":
|
case "f10":
|
||||||
@@ -658,8 +662,12 @@ func parseKeymap(keymap map[int][]action, str string) {
|
|||||||
appendAction(actAbort)
|
appendAction(actAbort)
|
||||||
case "accept":
|
case "accept":
|
||||||
appendAction(actAccept)
|
appendAction(actAccept)
|
||||||
|
case "accept-non-empty":
|
||||||
|
appendAction(actAcceptNonEmpty)
|
||||||
case "print-query":
|
case "print-query":
|
||||||
appendAction(actPrintQuery)
|
appendAction(actPrintQuery)
|
||||||
|
case "replace-query":
|
||||||
|
appendAction(actReplaceQuery)
|
||||||
case "backward-char":
|
case "backward-char":
|
||||||
appendAction(actBackwardChar)
|
appendAction(actBackwardChar)
|
||||||
case "backward-delete-char":
|
case "backward-delete-char":
|
||||||
@@ -833,9 +841,6 @@ func parseSize(str string, maxPercent float64, label string) sizeSpec {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func parseHeight(str string) sizeSpec {
|
func parseHeight(str string) sizeSpec {
|
||||||
if util.IsWindows() {
|
|
||||||
errorExit("--height options is currently not supported on Windows")
|
|
||||||
}
|
|
||||||
size := parseSize(str, 100, "height")
|
size := parseSize(str, 100, "height")
|
||||||
return size
|
return size
|
||||||
}
|
}
|
||||||
@@ -962,7 +967,9 @@ func parseOptions(opts *Options, allArgs []string) {
|
|||||||
case "--algo":
|
case "--algo":
|
||||||
opts.FuzzyAlgo = parseAlgo(nextString(allArgs, &i, "algorithm required (v1|v2)"))
|
opts.FuzzyAlgo = parseAlgo(nextString(allArgs, &i, "algorithm required (v1|v2)"))
|
||||||
case "--expect":
|
case "--expect":
|
||||||
opts.Expect = parseKeyChords(nextString(allArgs, &i, "key names required"), "key names required")
|
for k, v := range parseKeyChords(nextString(allArgs, &i, "key names required"), "key names required") {
|
||||||
|
opts.Expect[k] = v
|
||||||
|
}
|
||||||
case "--no-expect":
|
case "--no-expect":
|
||||||
opts.Expect = make(map[int]string)
|
opts.Expect = make(map[int]string)
|
||||||
case "--tiebreak":
|
case "--tiebreak":
|
||||||
@@ -1140,7 +1147,9 @@ func parseOptions(opts *Options, allArgs []string) {
|
|||||||
} else if match, value := optString(arg, "--toggle-sort="); match {
|
} else if match, value := optString(arg, "--toggle-sort="); match {
|
||||||
parseToggleSort(opts.Keymap, value)
|
parseToggleSort(opts.Keymap, value)
|
||||||
} else if match, value := optString(arg, "--expect="); match {
|
} else if match, value := optString(arg, "--expect="); match {
|
||||||
opts.Expect = parseKeyChords(value, "key names required")
|
for k, v := range parseKeyChords(value, "key names required") {
|
||||||
|
opts.Expect[k] = v
|
||||||
|
}
|
||||||
} else if match, value := optString(arg, "--tiebreak="); match {
|
} else if match, value := optString(arg, "--tiebreak="); match {
|
||||||
opts.Criteria = parseTiebreak(value)
|
opts.Criteria = parseTiebreak(value)
|
||||||
} else if match, value := optString(arg, "--color="); match {
|
} else if match, value := optString(arg, "--color="); match {
|
||||||
@@ -1199,6 +1208,9 @@ func parseOptions(opts *Options, allArgs []string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func postProcessOptions(opts *Options) {
|
func postProcessOptions(opts *Options) {
|
||||||
|
if util.IsWindows() && opts.Height.size > 0 {
|
||||||
|
errorExit("--height option is currently not supported on Windows")
|
||||||
|
}
|
||||||
// Default actions for CTRL-N / CTRL-P when --history is set
|
// Default actions for CTRL-N / CTRL-P when --history is set
|
||||||
if opts.History != nil {
|
if opts.History != nil {
|
||||||
if _, prs := opts.Keymap[tui.CtrlP]; !prs {
|
if _, prs := opts.Keymap[tui.CtrlP]; !prs {
|
||||||
|
|||||||
@@ -414,3 +414,10 @@ func TestPreviewOpts(t *testing.T) {
|
|||||||
t.Error(opts.Preview)
|
t.Error(opts.Preview)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAdditiveExpect(t *testing.T) {
|
||||||
|
opts := optsFor("--expect=a", "--expect", "b", "--expect=c")
|
||||||
|
if len(opts.Expect) != 3 {
|
||||||
|
t.Error(opts.Expect)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -10,12 +10,12 @@ import (
|
|||||||
|
|
||||||
// fuzzy
|
// fuzzy
|
||||||
// 'exact
|
// 'exact
|
||||||
// ^exact-prefix
|
// ^prefix-exact
|
||||||
// exact-suffix$
|
// suffix-exact$
|
||||||
// !not-fuzzy
|
// !inverse-exact
|
||||||
// !'not-exact
|
// !'inverse-fuzzy
|
||||||
// !^not-exact-prefix
|
// !^inverse-prefix-exact
|
||||||
// !not-exact-suffix$
|
// !inverse-suffix-exact$
|
||||||
|
|
||||||
type termType int
|
type termType int
|
||||||
|
|
||||||
@@ -32,7 +32,6 @@ type term struct {
|
|||||||
inv bool
|
inv bool
|
||||||
text []rune
|
text []rune
|
||||||
caseSensitive bool
|
caseSensitive bool
|
||||||
origText []rune
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type termSet []term
|
type termSet []term
|
||||||
@@ -48,6 +47,7 @@ type Pattern struct {
|
|||||||
text []rune
|
text []rune
|
||||||
termSets []termSet
|
termSets []termSet
|
||||||
cacheable bool
|
cacheable bool
|
||||||
|
cacheKey string
|
||||||
delimiter Delimiter
|
delimiter Delimiter
|
||||||
nth []Range
|
nth []Range
|
||||||
procFun map[termType]algo.Algo
|
procFun map[termType]algo.Algo
|
||||||
@@ -60,7 +60,7 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
_splitRegex = regexp.MustCompile("\\s+")
|
_splitRegex = regexp.MustCompile(" +")
|
||||||
clearPatternCache()
|
clearPatternCache()
|
||||||
clearChunkCache()
|
clearChunkCache()
|
||||||
}
|
}
|
||||||
@@ -81,7 +81,10 @@ func BuildPattern(fuzzy bool, fuzzyAlgo algo.Algo, extended bool, caseMode Case,
|
|||||||
|
|
||||||
var asString string
|
var asString string
|
||||||
if extended {
|
if extended {
|
||||||
asString = strings.Trim(string(runes), " ")
|
asString = strings.TrimLeft(string(runes), " ")
|
||||||
|
for strings.HasSuffix(asString, " ") && !strings.HasSuffix(asString, "\\ ") {
|
||||||
|
asString = asString[:len(asString)-1]
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
asString = string(runes)
|
asString = string(runes)
|
||||||
}
|
}
|
||||||
@@ -101,7 +104,7 @@ func BuildPattern(fuzzy bool, fuzzyAlgo algo.Algo, extended bool, caseMode Case,
|
|||||||
for idx, term := range termSet {
|
for idx, term := range termSet {
|
||||||
// If the query contains inverse search terms or OR operators,
|
// If the query contains inverse search terms or OR operators,
|
||||||
// we cannot cache the search scope
|
// we cannot cache the search scope
|
||||||
if !cacheable || idx > 0 || term.inv || !fuzzy && term.typ != termExact {
|
if !cacheable || idx > 0 || term.inv || fuzzy && term.typ != termFuzzy || !fuzzy && term.typ != termExact {
|
||||||
cacheable = false
|
cacheable = false
|
||||||
break Loop
|
break Loop
|
||||||
}
|
}
|
||||||
@@ -130,6 +133,7 @@ func BuildPattern(fuzzy bool, fuzzyAlgo algo.Algo, extended bool, caseMode Case,
|
|||||||
delimiter: delimiter,
|
delimiter: delimiter,
|
||||||
procFun: make(map[termType]algo.Algo)}
|
procFun: make(map[termType]algo.Algo)}
|
||||||
|
|
||||||
|
ptr.cacheKey = ptr.buildCacheKey()
|
||||||
ptr.procFun[termFuzzy] = fuzzyAlgo
|
ptr.procFun[termFuzzy] = fuzzyAlgo
|
||||||
ptr.procFun[termEqual] = algo.EqualMatch
|
ptr.procFun[termEqual] = algo.EqualMatch
|
||||||
ptr.procFun[termExact] = algo.ExactMatchNaive
|
ptr.procFun[termExact] = algo.ExactMatchNaive
|
||||||
@@ -141,27 +145,30 @@ func BuildPattern(fuzzy bool, fuzzyAlgo algo.Algo, extended bool, caseMode Case,
|
|||||||
}
|
}
|
||||||
|
|
||||||
func parseTerms(fuzzy bool, caseMode Case, normalize bool, str string) []termSet {
|
func parseTerms(fuzzy bool, caseMode Case, normalize bool, str string) []termSet {
|
||||||
|
str = strings.Replace(str, "\\ ", "\t", -1)
|
||||||
tokens := _splitRegex.Split(str, -1)
|
tokens := _splitRegex.Split(str, -1)
|
||||||
sets := []termSet{}
|
sets := []termSet{}
|
||||||
set := termSet{}
|
set := termSet{}
|
||||||
switchSet := false
|
switchSet := false
|
||||||
|
afterBar := false
|
||||||
for _, token := range tokens {
|
for _, token := range tokens {
|
||||||
typ, inv, text := termFuzzy, false, token
|
typ, inv, text := termFuzzy, false, strings.Replace(token, "\t", " ", -1)
|
||||||
lowerText := strings.ToLower(text)
|
lowerText := strings.ToLower(text)
|
||||||
caseSensitive := caseMode == CaseRespect ||
|
caseSensitive := caseMode == CaseRespect ||
|
||||||
caseMode == CaseSmart && text != lowerText
|
caseMode == CaseSmart && text != lowerText
|
||||||
if !caseSensitive {
|
if !caseSensitive {
|
||||||
text = lowerText
|
text = lowerText
|
||||||
}
|
}
|
||||||
origText := []rune(text)
|
|
||||||
if !fuzzy {
|
if !fuzzy {
|
||||||
typ = termExact
|
typ = termExact
|
||||||
}
|
}
|
||||||
|
|
||||||
if text == "|" {
|
if len(set) > 0 && !afterBar && text == "|" {
|
||||||
switchSet = false
|
switchSet = false
|
||||||
|
afterBar = true
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
afterBar = false
|
||||||
|
|
||||||
if strings.HasPrefix(text, "!") {
|
if strings.HasPrefix(text, "!") {
|
||||||
inv = true
|
inv = true
|
||||||
@@ -169,6 +176,11 @@ func parseTerms(fuzzy bool, caseMode Case, normalize bool, str string) []termSet
|
|||||||
text = text[1:]
|
text = text[1:]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if text != "$" && strings.HasSuffix(text, "$") {
|
||||||
|
typ = termSuffix
|
||||||
|
text = text[:len(text)-1]
|
||||||
|
}
|
||||||
|
|
||||||
if strings.HasPrefix(text, "'") {
|
if strings.HasPrefix(text, "'") {
|
||||||
// Flip exactness
|
// Flip exactness
|
||||||
if fuzzy && !inv {
|
if fuzzy && !inv {
|
||||||
@@ -179,16 +191,12 @@ func parseTerms(fuzzy bool, caseMode Case, normalize bool, str string) []termSet
|
|||||||
text = text[1:]
|
text = text[1:]
|
||||||
}
|
}
|
||||||
} else if strings.HasPrefix(text, "^") {
|
} else if strings.HasPrefix(text, "^") {
|
||||||
if strings.HasSuffix(text, "$") {
|
if typ == termSuffix {
|
||||||
typ = termEqual
|
typ = termEqual
|
||||||
text = text[1 : len(text)-1]
|
|
||||||
} else {
|
} else {
|
||||||
typ = termPrefix
|
typ = termPrefix
|
||||||
text = text[1:]
|
|
||||||
}
|
}
|
||||||
} else if strings.HasSuffix(text, "$") {
|
text = text[1:]
|
||||||
typ = termSuffix
|
|
||||||
text = text[:len(text)-1]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(text) > 0 {
|
if len(text) > 0 {
|
||||||
@@ -204,8 +212,7 @@ func parseTerms(fuzzy bool, caseMode Case, normalize bool, str string) []termSet
|
|||||||
typ: typ,
|
typ: typ,
|
||||||
inv: inv,
|
inv: inv,
|
||||||
text: textRunes,
|
text: textRunes,
|
||||||
caseSensitive: caseSensitive,
|
caseSensitive: caseSensitive})
|
||||||
origText: origText})
|
|
||||||
switchSet = true
|
switchSet = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -228,18 +235,22 @@ func (p *Pattern) AsString() string {
|
|||||||
return string(p.text)
|
return string(p.text)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CacheKey is used to build string to be used as the key of result cache
|
func (p *Pattern) buildCacheKey() string {
|
||||||
func (p *Pattern) CacheKey() string {
|
|
||||||
if !p.extended {
|
if !p.extended {
|
||||||
return p.AsString()
|
return p.AsString()
|
||||||
}
|
}
|
||||||
cacheableTerms := []string{}
|
cacheableTerms := []string{}
|
||||||
for _, termSet := range p.termSets {
|
for _, termSet := range p.termSets {
|
||||||
if len(termSet) == 1 && !termSet[0].inv && (p.fuzzy || termSet[0].typ == termExact) {
|
if len(termSet) == 1 && !termSet[0].inv && (p.fuzzy || termSet[0].typ == termExact) {
|
||||||
cacheableTerms = append(cacheableTerms, string(termSet[0].origText))
|
cacheableTerms = append(cacheableTerms, string(termSet[0].text))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return strings.Join(cacheableTerms, " ")
|
return strings.Join(cacheableTerms, "\t")
|
||||||
|
}
|
||||||
|
|
||||||
|
// CacheKey is used to build string to be used as the key of result cache
|
||||||
|
func (p *Pattern) CacheKey() string {
|
||||||
|
return p.cacheKey
|
||||||
}
|
}
|
||||||
|
|
||||||
// Match returns the list of matches Items in the given Chunk
|
// Match returns the list of matches Items in the given Chunk
|
||||||
@@ -267,8 +278,8 @@ func (p *Pattern) matchChunk(chunk *Chunk, space []Result, slab *util.Slab) []Re
|
|||||||
matches := []Result{}
|
matches := []Result{}
|
||||||
|
|
||||||
if space == nil {
|
if space == nil {
|
||||||
for idx := range *chunk {
|
for idx := 0; idx < chunk.count; idx++ {
|
||||||
if match, _, _ := p.MatchItem(&(*chunk)[idx], false, slab); match != nil {
|
if match, _, _ := p.MatchItem(&chunk.items[idx], false, slab); match != nil {
|
||||||
matches = append(matches, *match)
|
matches = append(matches, *match)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -301,7 +312,12 @@ func (p *Pattern) MatchItem(item *Item, withPos bool, slab *util.Slab) (*Result,
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *Pattern) basicMatch(item *Item, withPos bool, slab *util.Slab) (Offset, int, *[]int) {
|
func (p *Pattern) basicMatch(item *Item, withPos bool, slab *util.Slab) (Offset, int, *[]int) {
|
||||||
input := p.prepareInput(item)
|
var input []Token
|
||||||
|
if len(p.nth) == 0 {
|
||||||
|
input = []Token{Token{text: &item.text, prefixLength: 0}}
|
||||||
|
} else {
|
||||||
|
input = p.transformInput(item)
|
||||||
|
}
|
||||||
if p.fuzzy {
|
if p.fuzzy {
|
||||||
return p.iter(p.fuzzyAlgo, input, p.caseSensitive, p.normalize, p.forward, p.text, withPos, slab)
|
return p.iter(p.fuzzyAlgo, input, p.caseSensitive, p.normalize, p.forward, p.text, withPos, slab)
|
||||||
}
|
}
|
||||||
@@ -309,7 +325,12 @@ func (p *Pattern) basicMatch(item *Item, withPos bool, slab *util.Slab) (Offset,
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *Pattern) extendedMatch(item *Item, withPos bool, slab *util.Slab) ([]Offset, int, *[]int) {
|
func (p *Pattern) extendedMatch(item *Item, withPos bool, slab *util.Slab) ([]Offset, int, *[]int) {
|
||||||
input := p.prepareInput(item)
|
var input []Token
|
||||||
|
if len(p.nth) == 0 {
|
||||||
|
input = []Token{Token{text: &item.text, prefixLength: 0}}
|
||||||
|
} else {
|
||||||
|
input = p.transformInput(item)
|
||||||
|
}
|
||||||
offsets := []Offset{}
|
offsets := []Offset{}
|
||||||
var totalScore int
|
var totalScore int
|
||||||
var allPos *[]int
|
var allPos *[]int
|
||||||
@@ -353,11 +374,7 @@ func (p *Pattern) extendedMatch(item *Item, withPos bool, slab *util.Slab) ([]Of
|
|||||||
return offsets, totalScore, allPos
|
return offsets, totalScore, allPos
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Pattern) prepareInput(item *Item) []Token {
|
func (p *Pattern) transformInput(item *Item) []Token {
|
||||||
if len(p.nth) == 0 {
|
|
||||||
return []Token{Token{text: &item.text, prefixLength: 0}}
|
|
||||||
}
|
|
||||||
|
|
||||||
if item.transformed != nil {
|
if item.transformed != nil {
|
||||||
return *item.transformed
|
return *item.transformed
|
||||||
}
|
}
|
||||||
@@ -370,7 +387,7 @@ func (p *Pattern) prepareInput(item *Item) []Token {
|
|||||||
|
|
||||||
func (p *Pattern) iter(pfun algo.Algo, tokens []Token, caseSensitive bool, normalize bool, forward bool, pattern []rune, withPos bool, slab *util.Slab) (Offset, int, *[]int) {
|
func (p *Pattern) iter(pfun algo.Algo, tokens []Token, caseSensitive bool, normalize bool, forward bool, pattern []rune, withPos bool, slab *util.Slab) (Offset, int, *[]int) {
|
||||||
for _, part := range tokens {
|
for _, part := range tokens {
|
||||||
if res, pos := pfun(caseSensitive, normalize, forward, *part.text, pattern, withPos, slab); res.Start >= 0 {
|
if res, pos := pfun(caseSensitive, normalize, forward, part.text, pattern, withPos, slab); res.Start >= 0 {
|
||||||
sidx := int32(res.Start) + part.prefixLength
|
sidx := int32(res.Start) + part.prefixLength
|
||||||
eidx := int32(res.End) + part.prefixLength
|
eidx := int32(res.End) + part.prefixLength
|
||||||
if pos != nil {
|
if pos != nil {
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ func init() {
|
|||||||
|
|
||||||
func TestParseTermsExtended(t *testing.T) {
|
func TestParseTermsExtended(t *testing.T) {
|
||||||
terms := parseTerms(true, CaseSmart, false,
|
terms := parseTerms(true, CaseSmart, false,
|
||||||
"| aaa 'bbb ^ccc ddd$ !eee !'fff !^ggg !hhh$ | ^iii$ ^xxx | 'yyy | | zzz$ | !ZZZ |")
|
"aaa 'bbb ^ccc ddd$ !eee !'fff !^ggg !hhh$ | ^iii$ ^xxx | 'yyy | zzz$ | !ZZZ |")
|
||||||
if len(terms) != 9 ||
|
if len(terms) != 9 ||
|
||||||
terms[0][0].typ != termFuzzy || terms[0][0].inv ||
|
terms[0][0].typ != termFuzzy || terms[0][0].inv ||
|
||||||
terms[1][0].typ != termExact || terms[1][0].inv ||
|
terms[1][0].typ != termExact || terms[1][0].inv ||
|
||||||
@@ -33,19 +33,11 @@ func TestParseTermsExtended(t *testing.T) {
|
|||||||
terms[8][3].typ != termExact || !terms[8][3].inv {
|
terms[8][3].typ != termExact || !terms[8][3].inv {
|
||||||
t.Errorf("%s", terms)
|
t.Errorf("%s", terms)
|
||||||
}
|
}
|
||||||
for idx, termSet := range terms[:8] {
|
for _, termSet := range terms[:8] {
|
||||||
term := termSet[0]
|
term := termSet[0]
|
||||||
if len(term.text) != 3 {
|
if len(term.text) != 3 {
|
||||||
t.Errorf("%s", term)
|
t.Errorf("%s", term)
|
||||||
}
|
}
|
||||||
if idx > 0 && len(term.origText) != 4+idx/5 {
|
|
||||||
t.Errorf("%s", term)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, term := range terms[8] {
|
|
||||||
if len(term.origText) != 4 {
|
|
||||||
t.Errorf("%s", term)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -66,7 +58,7 @@ func TestParseTermsExtendedExact(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestParseTermsEmpty(t *testing.T) {
|
func TestParseTermsEmpty(t *testing.T) {
|
||||||
terms := parseTerms(true, CaseSmart, false, "' $ ^ !' !^ !$")
|
terms := parseTerms(true, CaseSmart, false, "' ^ !' !^")
|
||||||
if len(terms) != 0 {
|
if len(terms) != 0 {
|
||||||
t.Errorf("%s", terms)
|
t.Errorf("%s", terms)
|
||||||
}
|
}
|
||||||
@@ -77,8 +69,9 @@ func TestExact(t *testing.T) {
|
|||||||
clearPatternCache()
|
clearPatternCache()
|
||||||
pattern := BuildPattern(true, algo.FuzzyMatchV2, true, CaseSmart, false, true, true,
|
pattern := BuildPattern(true, algo.FuzzyMatchV2, true, CaseSmart, false, true, true,
|
||||||
[]Range{}, Delimiter{}, []rune("'abc"))
|
[]Range{}, Delimiter{}, []rune("'abc"))
|
||||||
|
chars := util.ToChars([]byte("aabbcc abc"))
|
||||||
res, pos := algo.ExactMatchNaive(
|
res, pos := algo.ExactMatchNaive(
|
||||||
pattern.caseSensitive, pattern.normalize, pattern.forward, util.RunesToChars([]rune("aabbcc abc")), pattern.termSets[0][0].text, true, nil)
|
pattern.caseSensitive, pattern.normalize, pattern.forward, &chars, pattern.termSets[0][0].text, true, nil)
|
||||||
if res.Start != 7 || res.End != 10 {
|
if res.Start != 7 || res.End != 10 {
|
||||||
t.Errorf("%s / %d / %d", pattern.termSets, res.Start, res.End)
|
t.Errorf("%s / %d / %d", pattern.termSets, res.Start, res.End)
|
||||||
}
|
}
|
||||||
@@ -93,8 +86,9 @@ func TestEqual(t *testing.T) {
|
|||||||
pattern := BuildPattern(true, algo.FuzzyMatchV2, true, CaseSmart, false, true, true, []Range{}, Delimiter{}, []rune("^AbC$"))
|
pattern := BuildPattern(true, algo.FuzzyMatchV2, true, CaseSmart, false, true, true, []Range{}, Delimiter{}, []rune("^AbC$"))
|
||||||
|
|
||||||
match := func(str string, sidxExpected int, eidxExpected int) {
|
match := func(str string, sidxExpected int, eidxExpected int) {
|
||||||
|
chars := util.ToChars([]byte(str))
|
||||||
res, pos := algo.EqualMatch(
|
res, pos := algo.EqualMatch(
|
||||||
pattern.caseSensitive, pattern.normalize, pattern.forward, util.RunesToChars([]rune(str)), pattern.termSets[0][0].text, true, nil)
|
pattern.caseSensitive, pattern.normalize, pattern.forward, &chars, pattern.termSets[0][0].text, true, nil)
|
||||||
if res.Start != sidxExpected || res.End != eidxExpected {
|
if res.Start != sidxExpected || res.End != eidxExpected {
|
||||||
t.Errorf("%s / %d / %d", pattern.termSets, res.Start, res.End)
|
t.Errorf("%s / %d / %d", pattern.termSets, res.Start, res.End)
|
||||||
}
|
}
|
||||||
@@ -138,12 +132,11 @@ func TestOrigTextAndTransformed(t *testing.T) {
|
|||||||
|
|
||||||
origBytes := []byte("junegunn.choi")
|
origBytes := []byte("junegunn.choi")
|
||||||
for _, extended := range []bool{false, true} {
|
for _, extended := range []bool{false, true} {
|
||||||
chunk := Chunk{
|
chunk := Chunk{count: 1}
|
||||||
Item{
|
chunk.items[0] = Item{
|
||||||
text: util.RunesToChars([]rune("junegunn")),
|
text: util.ToChars([]byte("junegunn")),
|
||||||
origText: &origBytes,
|
origText: &origBytes,
|
||||||
transformed: &trans},
|
transformed: &trans}
|
||||||
}
|
|
||||||
pattern.extended = extended
|
pattern.extended = extended
|
||||||
matches := pattern.matchChunk(&chunk, nil, slab) // No cache
|
matches := pattern.matchChunk(&chunk, nil, slab) // No cache
|
||||||
if !(matches[0].item.text.ToString() == "junegunn" &&
|
if !(matches[0].item.text.ToString() == "junegunn" &&
|
||||||
@@ -152,7 +145,7 @@ func TestOrigTextAndTransformed(t *testing.T) {
|
|||||||
t.Error("Invalid match result", matches)
|
t.Error("Invalid match result", matches)
|
||||||
}
|
}
|
||||||
|
|
||||||
match, offsets, pos := pattern.MatchItem(&chunk[0], true, slab)
|
match, offsets, pos := pattern.MatchItem(&chunk.items[0], true, slab)
|
||||||
if !(match.item.text.ToString() == "junegunn" &&
|
if !(match.item.text.ToString() == "junegunn" &&
|
||||||
string(*match.item.origText) == "junegunn.choi" &&
|
string(*match.item.origText) == "junegunn.choi" &&
|
||||||
offsets[0][0] == 0 && offsets[0][1] == 5 &&
|
offsets[0][0] == 0 && offsets[0][1] == 5 &&
|
||||||
@@ -167,40 +160,47 @@ func TestOrigTextAndTransformed(t *testing.T) {
|
|||||||
|
|
||||||
func TestCacheKey(t *testing.T) {
|
func TestCacheKey(t *testing.T) {
|
||||||
test := func(extended bool, patStr string, expected string, cacheable bool) {
|
test := func(extended bool, patStr string, expected string, cacheable bool) {
|
||||||
|
clearPatternCache()
|
||||||
pat := BuildPattern(true, algo.FuzzyMatchV2, extended, CaseSmart, false, true, true, []Range{}, Delimiter{}, []rune(patStr))
|
pat := BuildPattern(true, algo.FuzzyMatchV2, extended, CaseSmart, false, true, true, []Range{}, Delimiter{}, []rune(patStr))
|
||||||
if pat.CacheKey() != expected {
|
if pat.CacheKey() != expected {
|
||||||
t.Errorf("Expected: %s, actual: %s", expected, pat.CacheKey())
|
t.Errorf("Expected: %s, actual: %s", expected, pat.CacheKey())
|
||||||
}
|
}
|
||||||
if pat.cacheable != cacheable {
|
if pat.cacheable != cacheable {
|
||||||
t.Errorf("Expected: %s, actual: %s (%s)", cacheable, pat.cacheable, patStr)
|
t.Errorf("Expected: %t, actual: %t (%s)", cacheable, pat.cacheable, patStr)
|
||||||
}
|
}
|
||||||
clearPatternCache()
|
clearPatternCache()
|
||||||
}
|
}
|
||||||
test(false, "foo !bar", "foo !bar", true)
|
test(false, "foo !bar", "foo !bar", true)
|
||||||
test(false, "foo | bar !baz", "foo | bar !baz", true)
|
test(false, "foo | bar !baz", "foo | bar !baz", true)
|
||||||
test(true, "foo bar baz", "foo bar baz", true)
|
test(true, "foo bar baz", "foo\tbar\tbaz", true)
|
||||||
test(true, "foo !bar", "foo", false)
|
test(true, "foo !bar", "foo", false)
|
||||||
test(true, "foo !bar baz", "foo baz", false)
|
test(true, "foo !bar baz", "foo\tbaz", false)
|
||||||
test(true, "foo | bar baz", "baz", false)
|
test(true, "foo | bar baz", "baz", false)
|
||||||
test(true, "foo | bar | baz", "", false)
|
test(true, "foo | bar | baz", "", false)
|
||||||
test(true, "foo | bar !baz", "", false)
|
test(true, "foo | bar !baz", "", false)
|
||||||
test(true, "| | | foo", "foo", true)
|
test(true, "| | foo", "", false)
|
||||||
|
test(true, "| | | foo", "foo", false)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCacheable(t *testing.T) {
|
func TestCacheable(t *testing.T) {
|
||||||
test := func(fuzzy bool, str string, cacheable bool) {
|
test := func(fuzzy bool, str string, expected string, cacheable bool) {
|
||||||
clearPatternCache()
|
clearPatternCache()
|
||||||
pat := BuildPattern(fuzzy, algo.FuzzyMatchV2, true, CaseSmart, true, true, true, []Range{}, Delimiter{}, []rune(str))
|
pat := BuildPattern(fuzzy, algo.FuzzyMatchV2, true, CaseSmart, true, true, true, []Range{}, Delimiter{}, []rune(str))
|
||||||
|
if pat.CacheKey() != expected {
|
||||||
|
t.Errorf("Expected: %s, actual: %s", expected, pat.CacheKey())
|
||||||
|
}
|
||||||
if cacheable != pat.cacheable {
|
if cacheable != pat.cacheable {
|
||||||
t.Errorf("Invalid Pattern.cacheable for \"%s\": %v (expected: %v)", str, pat.cacheable, cacheable)
|
t.Errorf("Invalid Pattern.cacheable for \"%s\": %v (expected: %v)", str, pat.cacheable, cacheable)
|
||||||
}
|
}
|
||||||
|
clearPatternCache()
|
||||||
}
|
}
|
||||||
test(true, "foo bar", true)
|
test(true, "foo bar", "foo\tbar", true)
|
||||||
test(true, "foo 'bar", true)
|
test(true, "foo 'bar", "foo\tbar", false)
|
||||||
test(true, "foo !bar", false)
|
test(true, "foo !bar", "foo", false)
|
||||||
|
|
||||||
test(false, "foo bar", true)
|
test(false, "foo bar", "foo\tbar", true)
|
||||||
test(false, "foo '", true)
|
test(false, "foo 'bar", "foo", false)
|
||||||
test(false, "foo 'bar", false)
|
test(false, "foo '", "foo", true)
|
||||||
test(false, "foo !bar", false)
|
test(false, "foo 'bar", "foo", false)
|
||||||
|
test(false, "foo !bar", "foo", false)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import (
|
|||||||
"bufio"
|
"bufio"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/junegunn/fzf/src/util"
|
"github.com/junegunn/fzf/src/util"
|
||||||
)
|
)
|
||||||
@@ -13,21 +15,56 @@ type Reader struct {
|
|||||||
pusher func([]byte) bool
|
pusher func([]byte) bool
|
||||||
eventBox *util.EventBox
|
eventBox *util.EventBox
|
||||||
delimNil bool
|
delimNil bool
|
||||||
|
event int32
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewReader returns new Reader object
|
||||||
|
func NewReader(pusher func([]byte) bool, eventBox *util.EventBox, delimNil bool) *Reader {
|
||||||
|
return &Reader{pusher, eventBox, delimNil, int32(EvtReady)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Reader) startEventPoller() {
|
||||||
|
go func() {
|
||||||
|
ptr := &r.event
|
||||||
|
pollInterval := readerPollIntervalMin
|
||||||
|
for {
|
||||||
|
if atomic.CompareAndSwapInt32(ptr, int32(EvtReadNew), int32(EvtReady)) {
|
||||||
|
r.eventBox.Set(EvtReadNew, true)
|
||||||
|
pollInterval = readerPollIntervalMin
|
||||||
|
} else if atomic.LoadInt32(ptr) == int32(EvtReadFin) {
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
pollInterval += readerPollIntervalStep
|
||||||
|
if pollInterval > readerPollIntervalMax {
|
||||||
|
pollInterval = readerPollIntervalMax
|
||||||
|
}
|
||||||
|
}
|
||||||
|
time.Sleep(pollInterval)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Reader) fin(success bool) {
|
||||||
|
atomic.StoreInt32(&r.event, int32(EvtReadFin))
|
||||||
|
r.eventBox.Set(EvtReadFin, success)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReadSource reads data from the default command or from standard input
|
// ReadSource reads data from the default command or from standard input
|
||||||
func (r *Reader) ReadSource() {
|
func (r *Reader) ReadSource() {
|
||||||
|
r.startEventPoller()
|
||||||
var success bool
|
var success bool
|
||||||
if util.IsTty() {
|
if util.IsTty() {
|
||||||
cmd := os.Getenv("FZF_DEFAULT_COMMAND")
|
cmd := os.Getenv("FZF_DEFAULT_COMMAND")
|
||||||
if len(cmd) == 0 {
|
if len(cmd) == 0 {
|
||||||
cmd = defaultCommand
|
// The default command for *nix requires bash
|
||||||
|
success = r.readFromCommand("bash", defaultCommand)
|
||||||
|
} else {
|
||||||
|
success = r.readFromCommand("sh", cmd)
|
||||||
}
|
}
|
||||||
success = r.readFromCommand(cmd)
|
|
||||||
} else {
|
} else {
|
||||||
success = r.readFromStdin()
|
success = r.readFromStdin()
|
||||||
}
|
}
|
||||||
r.eventBox.Set(EvtReadFin, success)
|
r.fin(success)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Reader) feed(src io.Reader) {
|
func (r *Reader) feed(src io.Reader) {
|
||||||
@@ -41,7 +78,7 @@ func (r *Reader) feed(src io.Reader) {
|
|||||||
// end in delim.
|
// end in delim.
|
||||||
bytea, err := reader.ReadBytes(delim)
|
bytea, err := reader.ReadBytes(delim)
|
||||||
byteaLen := len(bytea)
|
byteaLen := len(bytea)
|
||||||
if len(bytea) > 0 {
|
if byteaLen > 0 {
|
||||||
if err == nil {
|
if err == nil {
|
||||||
// get rid of carriage return if under Windows:
|
// get rid of carriage return if under Windows:
|
||||||
if util.IsWindows() && byteaLen >= 2 && bytea[byteaLen-2] == byte('\r') {
|
if util.IsWindows() && byteaLen >= 2 && bytea[byteaLen-2] == byte('\r') {
|
||||||
@@ -51,7 +88,7 @@ func (r *Reader) feed(src io.Reader) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if r.pusher(bytea) {
|
if r.pusher(bytea) {
|
||||||
r.eventBox.Set(EvtReadNew, true)
|
atomic.StoreInt32(&r.event, int32(EvtReadNew))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -65,8 +102,8 @@ func (r *Reader) readFromStdin() bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Reader) readFromCommand(cmd string) bool {
|
func (r *Reader) readFromCommand(shell string, cmd string) bool {
|
||||||
listCommand := util.ExecCommand(cmd)
|
listCommand := util.ExecCommandWith(shell, cmd)
|
||||||
out, err := listCommand.StdoutPipe()
|
out, err := listCommand.StdoutPipe()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false
|
return false
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package fzf
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/junegunn/fzf/src/util"
|
"github.com/junegunn/fzf/src/util"
|
||||||
)
|
)
|
||||||
@@ -11,7 +12,10 @@ func TestReadFromCommand(t *testing.T) {
|
|||||||
eb := util.NewEventBox()
|
eb := util.NewEventBox()
|
||||||
reader := Reader{
|
reader := Reader{
|
||||||
pusher: func(s []byte) bool { strs = append(strs, string(s)); return true },
|
pusher: func(s []byte) bool { strs = append(strs, string(s)); return true },
|
||||||
eventBox: eb}
|
eventBox: eb,
|
||||||
|
event: int32(EvtReady)}
|
||||||
|
|
||||||
|
reader.startEventPoller()
|
||||||
|
|
||||||
// Check EventBox
|
// Check EventBox
|
||||||
if eb.Peek(EvtReadNew) {
|
if eb.Peek(EvtReadNew) {
|
||||||
@@ -19,21 +23,16 @@ func TestReadFromCommand(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Normal command
|
// Normal command
|
||||||
reader.readFromCommand(`echo abc && echo def`)
|
reader.fin(reader.readFromCommand("sh", `echo abc && echo def`))
|
||||||
if len(strs) != 2 || strs[0] != "abc" || strs[1] != "def" {
|
if len(strs) != 2 || strs[0] != "abc" || strs[1] != "def" {
|
||||||
t.Errorf("%s", strs)
|
t.Errorf("%s", strs)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check EventBox again
|
// Check EventBox again
|
||||||
if !eb.Peek(EvtReadNew) {
|
eb.WaitFor(EvtReadFin)
|
||||||
t.Error("EvtReadNew should be set yet")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait should return immediately
|
// Wait should return immediately
|
||||||
eb.Wait(func(events *util.Events) {
|
eb.Wait(func(events *util.Events) {
|
||||||
if _, found := (*events)[EvtReadNew]; !found {
|
|
||||||
t.Errorf("%s", events)
|
|
||||||
}
|
|
||||||
events.Clear()
|
events.Clear()
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -42,8 +41,14 @@ func TestReadFromCommand(t *testing.T) {
|
|||||||
t.Error("EvtReadNew should not be set yet")
|
t.Error("EvtReadNew should not be set yet")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Make sure that event poller is finished
|
||||||
|
time.Sleep(readerPollIntervalMax)
|
||||||
|
|
||||||
|
// Restart event poller
|
||||||
|
reader.startEventPoller()
|
||||||
|
|
||||||
// Failing command
|
// Failing command
|
||||||
reader.readFromCommand(`no-such-command`)
|
reader.fin(reader.readFromCommand("sh", `no-such-command`))
|
||||||
strs = []string{}
|
strs = []string{}
|
||||||
if len(strs) > 0 {
|
if len(strs) > 0 {
|
||||||
t.Errorf("%s", strs)
|
t.Errorf("%s", strs)
|
||||||
@@ -51,6 +56,9 @@ func TestReadFromCommand(t *testing.T) {
|
|||||||
|
|
||||||
// Check EventBox again
|
// Check EventBox again
|
||||||
if eb.Peek(EvtReadNew) {
|
if eb.Peek(EvtReadNew) {
|
||||||
t.Error("Command failed. EvtReadNew should be set")
|
t.Error("Command failed. EvtReadNew should not be set")
|
||||||
|
}
|
||||||
|
if !eb.Peek(EvtReadFin) {
|
||||||
|
t.Error("EvtReadFin should be set")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ func buildResult(item *Item, offsets []Offset, score int) Result {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
result.points[idx] = val
|
result.points[3-idx] = val
|
||||||
}
|
}
|
||||||
|
|
||||||
return result
|
return result
|
||||||
@@ -85,7 +85,7 @@ func (result *Result) Index() int32 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func minRank() Result {
|
func minRank() Result {
|
||||||
return Result{item: &nilItem, points: [4]uint16{math.MaxUint16, 0, 0, 0}}
|
return Result{item: &minItem, points: [4]uint16{math.MaxUint16, 0, 0, 0}}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (result *Result) colorOffsets(matchOffsets []Offset, theme *tui.ColorTheme, color tui.ColorPair, attr tui.Attr, current bool) []colorOffset {
|
func (result *Result) colorOffsets(matchOffsets []Offset, theme *tui.ColorTheme, color tui.ColorPair, attr tui.Attr, current bool) []colorOffset {
|
||||||
@@ -224,16 +224,3 @@ func (a ByRelevanceTac) Swap(i, j int) {
|
|||||||
func (a ByRelevanceTac) Less(i, j int) bool {
|
func (a ByRelevanceTac) Less(i, j int) bool {
|
||||||
return compareRanks(a[i], a[j], true)
|
return compareRanks(a[i], a[j], true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func compareRanks(irank Result, jrank Result, tac bool) bool {
|
|
||||||
for idx := 0; idx < 4; idx++ {
|
|
||||||
left := irank.points[idx]
|
|
||||||
right := jrank.points[idx]
|
|
||||||
if left < right {
|
|
||||||
return true
|
|
||||||
} else if left > right {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return (irank.item.Index() <= jrank.item.Index()) != tac
|
|
||||||
}
|
|
||||||
|
|||||||
16
src/result_others.go
Normal file
16
src/result_others.go
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
// +build !386,!amd64
|
||||||
|
|
||||||
|
package fzf
|
||||||
|
|
||||||
|
func compareRanks(irank Result, jrank Result, tac bool) bool {
|
||||||
|
for idx := 3; idx >= 0; idx-- {
|
||||||
|
left := irank.points[idx]
|
||||||
|
right := jrank.points[idx]
|
||||||
|
if left < right {
|
||||||
|
return true
|
||||||
|
} else if left > right {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (irank.item.Index() <= jrank.item.Index()) != tac
|
||||||
|
}
|
||||||
@@ -59,10 +59,10 @@ func TestResultRank(t *testing.T) {
|
|||||||
strs := [][]rune{[]rune("foo"), []rune("foobar"), []rune("bar"), []rune("baz")}
|
strs := [][]rune{[]rune("foo"), []rune("foobar"), []rune("bar"), []rune("baz")}
|
||||||
item1 := buildResult(
|
item1 := buildResult(
|
||||||
withIndex(&Item{text: util.RunesToChars(strs[0])}, 1), []Offset{}, 2)
|
withIndex(&Item{text: util.RunesToChars(strs[0])}, 1), []Offset{}, 2)
|
||||||
if item1.points[0] != math.MaxUint16-2 || // Bonus
|
if item1.points[3] != math.MaxUint16-2 || // Bonus
|
||||||
item1.points[1] != 3 || // Length
|
item1.points[2] != 3 || // Length
|
||||||
item1.points[2] != 0 || // Unused
|
item1.points[1] != 0 || // Unused
|
||||||
item1.points[3] != 0 || // Unused
|
item1.points[0] != 0 || // Unused
|
||||||
item1.item.Index() != 1 {
|
item1.item.Index() != 1 {
|
||||||
t.Error(item1)
|
t.Error(item1)
|
||||||
}
|
}
|
||||||
|
|||||||
16
src/result_x86.go
Normal file
16
src/result_x86.go
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
// +build 386 amd64
|
||||||
|
|
||||||
|
package fzf
|
||||||
|
|
||||||
|
import "unsafe"
|
||||||
|
|
||||||
|
func compareRanks(irank Result, jrank Result, tac bool) bool {
|
||||||
|
left := *(*uint64)(unsafe.Pointer(&irank.points[0]))
|
||||||
|
right := *(*uint64)(unsafe.Pointer(&jrank.points[0]))
|
||||||
|
if left < right {
|
||||||
|
return true
|
||||||
|
} else if left > right {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return (irank.item.Index() <= jrank.item.Index()) != tac
|
||||||
|
}
|
||||||
125
src/terminal.go
125
src/terminal.go
@@ -101,6 +101,7 @@ type Terminal struct {
|
|||||||
printer func(string)
|
printer func(string)
|
||||||
merger *Merger
|
merger *Merger
|
||||||
selected map[int32]selectedItem
|
selected map[int32]selectedItem
|
||||||
|
version int64
|
||||||
reqBox *util.EventBox
|
reqBox *util.EventBox
|
||||||
preview previewOpts
|
preview previewOpts
|
||||||
previewer previewer
|
previewer previewer
|
||||||
@@ -169,6 +170,7 @@ const (
|
|||||||
actBeginningOfLine
|
actBeginningOfLine
|
||||||
actAbort
|
actAbort
|
||||||
actAccept
|
actAccept
|
||||||
|
actAcceptNonEmpty
|
||||||
actBackwardChar
|
actBackwardChar
|
||||||
actBackwardDeleteChar
|
actBackwardDeleteChar
|
||||||
actBackwardWord
|
actBackwardWord
|
||||||
@@ -202,6 +204,7 @@ const (
|
|||||||
actJump
|
actJump
|
||||||
actJumpAccept
|
actJumpAccept
|
||||||
actPrintQuery
|
actPrintQuery
|
||||||
|
actReplaceQuery
|
||||||
actToggleSort
|
actToggleSort
|
||||||
actTogglePreview
|
actTogglePreview
|
||||||
actTogglePreviewWrap
|
actTogglePreviewWrap
|
||||||
@@ -277,12 +280,18 @@ func defaultKeymap() map[int][]action {
|
|||||||
keymap[tui.Rune] = toActions(actRune)
|
keymap[tui.Rune] = toActions(actRune)
|
||||||
keymap[tui.Mouse] = toActions(actMouse)
|
keymap[tui.Mouse] = toActions(actMouse)
|
||||||
keymap[tui.DoubleClick] = toActions(actAccept)
|
keymap[tui.DoubleClick] = toActions(actAccept)
|
||||||
|
keymap[tui.LeftClick] = toActions(actIgnore)
|
||||||
|
keymap[tui.RightClick] = toActions(actToggle)
|
||||||
return keymap
|
return keymap
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func trimQuery(query string) []rune {
|
||||||
|
return []rune(strings.Replace(query, "\t", " ", -1))
|
||||||
|
}
|
||||||
|
|
||||||
// NewTerminal returns new Terminal object
|
// NewTerminal returns new Terminal object
|
||||||
func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
|
func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
|
||||||
input := []rune(opts.Query)
|
input := trimQuery(opts.Query)
|
||||||
var header []string
|
var header []string
|
||||||
if opts.Reverse {
|
if opts.Reverse {
|
||||||
header = opts.Header
|
header = opts.Header
|
||||||
@@ -618,10 +627,8 @@ func (t *Terminal) resizeWindows() {
|
|||||||
width,
|
width,
|
||||||
height, tui.BorderNone)
|
height, tui.BorderNone)
|
||||||
}
|
}
|
||||||
if !t.tui.IsOptimized() {
|
for i := 0; i < t.window.Height(); i++ {
|
||||||
for i := 0; i < t.window.Height(); i++ {
|
t.window.MoveAndClear(i, 0)
|
||||||
t.window.MoveAndClear(i, 0)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
t.truncateQuery()
|
t.truncateQuery()
|
||||||
}
|
}
|
||||||
@@ -688,7 +695,11 @@ func (t *Terminal) printInfo() {
|
|||||||
output += fmt.Sprintf(" (%d%%)", t.progress)
|
output += fmt.Sprintf(" (%d%%)", t.progress)
|
||||||
}
|
}
|
||||||
if !t.success && t.count == 0 {
|
if !t.success && t.count == 0 {
|
||||||
output += " [ERROR]"
|
if len(os.Getenv("FZF_DEFAULT_COMMAND")) > 0 {
|
||||||
|
output = "[$FZF_DEFAULT_COMMAND failed]"
|
||||||
|
} else {
|
||||||
|
output = "[default command failed - $FZF_DEFAULT_COMMAND required]"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if pos+len(output) <= t.window.Width() {
|
if pos+len(output) <= t.window.Width() {
|
||||||
t.window.CPrint(tui.ColInfo, 0, output)
|
t.window.CPrint(tui.ColInfo, 0, output)
|
||||||
@@ -712,12 +723,12 @@ func (t *Terminal) printHeader() {
|
|||||||
trimmed, colors, newState := extractColor(lineStr, state, nil)
|
trimmed, colors, newState := extractColor(lineStr, state, nil)
|
||||||
state = newState
|
state = newState
|
||||||
item := &Item{
|
item := &Item{
|
||||||
text: util.RunesToChars([]rune(trimmed)),
|
text: util.ToChars([]byte(trimmed)),
|
||||||
colors: colors}
|
colors: colors}
|
||||||
|
|
||||||
t.move(line, 2, true)
|
t.move(line, 2, true)
|
||||||
t.printHighlighted(Result{item: item},
|
t.printHighlighted(Result{item: item},
|
||||||
tui.AttrRegular, tui.ColHeader, tui.ColDefault, false, false)
|
tui.AttrRegular, tui.ColHeader, tui.ColHeader, false, false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -770,8 +781,7 @@ func (t *Terminal) printItem(result Result, line int, i int, current bool) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Optimized renderer can simply erase to the end of the window
|
t.move(line, 0, false)
|
||||||
t.move(line, 0, t.tui.IsOptimized())
|
|
||||||
t.window.CPrint(tui.ColCursor, t.strong, label)
|
t.window.CPrint(tui.ColCursor, t.strong, label)
|
||||||
if current {
|
if current {
|
||||||
if selected {
|
if selected {
|
||||||
@@ -788,11 +798,9 @@ func (t *Terminal) printItem(result Result, line int, i int, current bool) {
|
|||||||
}
|
}
|
||||||
newLine.width = t.printHighlighted(result, 0, tui.ColNormal, tui.ColMatch, false, true)
|
newLine.width = t.printHighlighted(result, 0, tui.ColNormal, tui.ColMatch, false, true)
|
||||||
}
|
}
|
||||||
if !t.tui.IsOptimized() {
|
fillSpaces := prevLine.width - newLine.width
|
||||||
fillSpaces := prevLine.width - newLine.width
|
if fillSpaces > 0 {
|
||||||
if fillSpaces > 0 {
|
t.window.Print(strings.Repeat(" ", fillSpaces))
|
||||||
t.window.Print(strings.Repeat(" ", fillSpaces))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
t.prevLines[i] = newLine
|
t.prevLines[i] = newLine
|
||||||
}
|
}
|
||||||
@@ -985,7 +993,7 @@ func (t *Terminal) printPreview() {
|
|||||||
if t.theme != nil && ansi != nil && ansi.colored() {
|
if t.theme != nil && ansi != nil && ansi.colored() {
|
||||||
fillRet = t.pwindow.CFill(ansi.fg, ansi.bg, ansi.attr, str)
|
fillRet = t.pwindow.CFill(ansi.fg, ansi.bg, ansi.attr, str)
|
||||||
} else {
|
} else {
|
||||||
fillRet = t.pwindow.Fill(str)
|
fillRet = t.pwindow.CFill(tui.ColNormal.Fg(), tui.ColNormal.Bg(), tui.AttrRegular, str)
|
||||||
}
|
}
|
||||||
return fillRet == tui.FillContinue
|
return fillRet == tui.FillContinue
|
||||||
})
|
})
|
||||||
@@ -1103,9 +1111,18 @@ func keyMatch(key int, event tui.Event) bool {
|
|||||||
event.Type == tui.Mouse && key == tui.DoubleClick && event.MouseEvent.Double
|
event.Type == tui.Mouse && key == tui.DoubleClick && event.MouseEvent.Double
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func quoteEntryCmd(entry string) string {
|
||||||
|
escaped := strings.Replace(entry, `\`, `\\`, -1)
|
||||||
|
escaped = `"` + strings.Replace(escaped, `"`, `\"`, -1) + `"`
|
||||||
|
r, _ := regexp.Compile(`[&|<>()@^%!"]`)
|
||||||
|
return r.ReplaceAllStringFunc(escaped, func(match string) string {
|
||||||
|
return "^" + match
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func quoteEntry(entry string) string {
|
func quoteEntry(entry string) string {
|
||||||
if util.IsWindows() {
|
if util.IsWindows() {
|
||||||
return strconv.Quote(strings.Replace(entry, "\"", "\\\"", -1))
|
return quoteEntryCmd(entry)
|
||||||
}
|
}
|
||||||
return "'" + strings.Replace(entry, "'", "'\\''", -1) + "'"
|
return "'" + strings.Replace(entry, "'", "'\\''", -1) + "'"
|
||||||
}
|
}
|
||||||
@@ -1173,8 +1190,7 @@ func replacePlaceholder(template string, stripAnsi bool, delimiter Delimiter, fo
|
|||||||
}
|
}
|
||||||
|
|
||||||
for idx, item := range items {
|
for idx, item := range items {
|
||||||
chars := util.RunesToChars([]rune(item.AsString(stripAnsi)))
|
tokens := Tokenize(item.AsString(stripAnsi), delimiter)
|
||||||
tokens := Tokenize(chars.ToString(), delimiter)
|
|
||||||
trans := Transform(tokens, ranges)
|
trans := Transform(tokens, ranges)
|
||||||
str := string(joinTokens(trans))
|
str := string(joinTokens(trans))
|
||||||
if delimiter.str != nil {
|
if delimiter.str != nil {
|
||||||
@@ -1258,6 +1274,24 @@ func (t *Terminal) truncateQuery() {
|
|||||||
t.cx = util.Constrain(t.cx, 0, len(t.input))
|
t.cx = util.Constrain(t.cx, 0, len(t.input))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *Terminal) selectItem(item *Item) {
|
||||||
|
t.selected[item.Index()] = selectedItem{time.Now(), item}
|
||||||
|
t.version++
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Terminal) deselectItem(item *Item) {
|
||||||
|
delete(t.selected, item.Index())
|
||||||
|
t.version++
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Terminal) toggleItem(item *Item) {
|
||||||
|
if _, found := t.selected[item.Index()]; !found {
|
||||||
|
t.selectItem(item)
|
||||||
|
} else {
|
||||||
|
t.deselectItem(item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Loop is called to start Terminal I/O
|
// Loop is called to start Terminal I/O
|
||||||
func (t *Terminal) Loop() {
|
func (t *Terminal) Loop() {
|
||||||
// prof := profile.Start(profile.ProfilePath("/tmp/"))
|
// prof := profile.Start(profile.ProfilePath("/tmp/"))
|
||||||
@@ -1336,6 +1370,12 @@ func (t *Terminal) Loop() {
|
|||||||
command := replacePlaceholder(t.preview.command,
|
command := replacePlaceholder(t.preview.command,
|
||||||
t.ansi, t.delimiter, false, string(t.input), request)
|
t.ansi, t.delimiter, false, string(t.input), request)
|
||||||
cmd := util.ExecCommand(command)
|
cmd := util.ExecCommand(command)
|
||||||
|
if t.pwindow != nil {
|
||||||
|
env := os.Environ()
|
||||||
|
env = append(env, fmt.Sprintf("LINES=%d", t.pwindow.Height()))
|
||||||
|
env = append(env, fmt.Sprintf("COLUMNS=%d", t.pwindow.Width()))
|
||||||
|
cmd.Env = env
|
||||||
|
}
|
||||||
out, _ := cmd.CombinedOutput()
|
out, _ := cmd.CombinedOutput()
|
||||||
t.reqBox.Set(reqPreviewDisplay, string(out))
|
t.reqBox.Set(reqPreviewDisplay, string(out))
|
||||||
} else {
|
} else {
|
||||||
@@ -1360,6 +1400,7 @@ func (t *Terminal) Loop() {
|
|||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
var focused *Item
|
var focused *Item
|
||||||
|
var version int64
|
||||||
for {
|
for {
|
||||||
t.reqBox.Wait(func(events *util.Events) {
|
t.reqBox.Wait(func(events *util.Events) {
|
||||||
defer events.Clear()
|
defer events.Clear()
|
||||||
@@ -1376,7 +1417,8 @@ func (t *Terminal) Loop() {
|
|||||||
case reqList:
|
case reqList:
|
||||||
t.printList()
|
t.printList()
|
||||||
currentFocus := t.currentItem()
|
currentFocus := t.currentItem()
|
||||||
if currentFocus != focused {
|
if currentFocus != focused || version != t.version {
|
||||||
|
version = t.version
|
||||||
focused = currentFocus
|
focused = currentFocus
|
||||||
if t.isPreviewEnabled() {
|
if t.isPreviewEnabled() {
|
||||||
_, list := t.buildPlusList(t.preview.command, false)
|
_, list := t.buildPlusList(t.preview.command, false)
|
||||||
@@ -1442,22 +1484,9 @@ func (t *Terminal) Loop() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
selectItem := func(item *Item) bool {
|
|
||||||
if _, found := t.selected[item.Index()]; !found {
|
|
||||||
t.selected[item.Index()] = selectedItem{time.Now(), item}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
toggleY := func(y int) {
|
|
||||||
item := t.merger.Get(y).item
|
|
||||||
if !selectItem(item) {
|
|
||||||
delete(t.selected, item.Index())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
toggle := func() {
|
toggle := func() {
|
||||||
if t.cy < t.merger.Length() {
|
if t.cy < t.merger.Length() {
|
||||||
toggleY(t.cy)
|
t.toggleItem(t.merger.Get(t.cy).item)
|
||||||
req(reqInfo)
|
req(reqInfo)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1541,6 +1570,11 @@ func (t *Terminal) Loop() {
|
|||||||
}
|
}
|
||||||
case actPrintQuery:
|
case actPrintQuery:
|
||||||
req(reqPrintQuery)
|
req(reqPrintQuery)
|
||||||
|
case actReplaceQuery:
|
||||||
|
if t.cy >= 0 && t.cy < t.merger.Length() {
|
||||||
|
t.input = t.merger.Get(t.cy).item.text.ToRunes()
|
||||||
|
t.cx = len(t.input)
|
||||||
|
}
|
||||||
case actAbort:
|
case actAbort:
|
||||||
req(reqQuit)
|
req(reqQuit)
|
||||||
case actDeleteChar:
|
case actDeleteChar:
|
||||||
@@ -1571,17 +1605,14 @@ func (t *Terminal) Loop() {
|
|||||||
case actSelectAll:
|
case actSelectAll:
|
||||||
if t.multi {
|
if t.multi {
|
||||||
for i := 0; i < t.merger.Length(); i++ {
|
for i := 0; i < t.merger.Length(); i++ {
|
||||||
item := t.merger.Get(i).item
|
t.selectItem(t.merger.Get(i).item)
|
||||||
selectItem(item)
|
|
||||||
}
|
}
|
||||||
req(reqList, reqInfo)
|
req(reqList, reqInfo)
|
||||||
}
|
}
|
||||||
case actDeselectAll:
|
case actDeselectAll:
|
||||||
if t.multi {
|
if t.multi {
|
||||||
for i := 0; i < t.merger.Length(); i++ {
|
t.selected = make(map[int32]selectedItem)
|
||||||
item := t.merger.Get(i)
|
t.version++
|
||||||
delete(t.selected, item.Index())
|
|
||||||
}
|
|
||||||
req(reqList, reqInfo)
|
req(reqList, reqInfo)
|
||||||
}
|
}
|
||||||
case actToggle:
|
case actToggle:
|
||||||
@@ -1592,7 +1623,7 @@ func (t *Terminal) Loop() {
|
|||||||
case actToggleAll:
|
case actToggleAll:
|
||||||
if t.multi {
|
if t.multi {
|
||||||
for i := 0; i < t.merger.Length(); i++ {
|
for i := 0; i < t.merger.Length(); i++ {
|
||||||
toggleY(i)
|
t.toggleItem(t.merger.Get(i).item)
|
||||||
}
|
}
|
||||||
req(reqList, reqInfo)
|
req(reqList, reqInfo)
|
||||||
}
|
}
|
||||||
@@ -1626,6 +1657,10 @@ func (t *Terminal) Loop() {
|
|||||||
req(reqList)
|
req(reqList)
|
||||||
case actAccept:
|
case actAccept:
|
||||||
req(reqClose)
|
req(reqClose)
|
||||||
|
case actAcceptNonEmpty:
|
||||||
|
if len(t.selected) > 0 || t.merger.Length() > 0 || !t.reading && t.count == 0 {
|
||||||
|
req(reqClose)
|
||||||
|
}
|
||||||
case actClearScreen:
|
case actClearScreen:
|
||||||
req(reqRedraw)
|
req(reqRedraw)
|
||||||
case actTop:
|
case actTop:
|
||||||
@@ -1690,13 +1725,13 @@ func (t *Terminal) Loop() {
|
|||||||
case actPreviousHistory:
|
case actPreviousHistory:
|
||||||
if t.history != nil {
|
if t.history != nil {
|
||||||
t.history.override(string(t.input))
|
t.history.override(string(t.input))
|
||||||
t.input = []rune(t.history.previous())
|
t.input = trimQuery(t.history.previous())
|
||||||
t.cx = len(t.input)
|
t.cx = len(t.input)
|
||||||
}
|
}
|
||||||
case actNextHistory:
|
case actNextHistory:
|
||||||
if t.history != nil {
|
if t.history != nil {
|
||||||
t.history.override(string(t.input))
|
t.history.override(string(t.input))
|
||||||
t.input = []rune(t.history.next())
|
t.input = trimQuery(t.history.next())
|
||||||
t.cx = len(t.input)
|
t.cx = len(t.input)
|
||||||
}
|
}
|
||||||
case actSigStop:
|
case actSigStop:
|
||||||
@@ -1750,6 +1785,10 @@ func (t *Terminal) Loop() {
|
|||||||
toggle()
|
toggle()
|
||||||
}
|
}
|
||||||
req(reqList)
|
req(reqList)
|
||||||
|
if me.Left {
|
||||||
|
return doActions(t.keymap[tui.LeftClick], tui.LeftClick)
|
||||||
|
}
|
||||||
|
return doActions(t.keymap[tui.RightClick], tui.RightClick)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import (
|
|||||||
func newItem(str string) *Item {
|
func newItem(str string) *Item {
|
||||||
bytes := []byte(str)
|
bytes := []byte(str)
|
||||||
trimmed, _, _ := extractColor(str, nil, nil)
|
trimmed, _, _ := extractColor(str, nil, nil)
|
||||||
return &Item{origText: &bytes, text: util.RunesToChars([]rune(trimmed))}
|
return &Item{origText: &bytes, text: util.ToChars([]byte(trimmed))}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestReplacePlaceholder(t *testing.T) {
|
func TestReplacePlaceholder(t *testing.T) {
|
||||||
@@ -91,3 +91,22 @@ func TestReplacePlaceholder(t *testing.T) {
|
|||||||
result = replacePlaceholder("echo {}/{1}/{3}/{2..3}", true, Delimiter{regex: regex}, false, "query", items1)
|
result = replacePlaceholder("echo {}/{1}/{3}/{2..3}", true, Delimiter{regex: regex}, false, "query", items1)
|
||||||
check("echo ' foo'\\''bar baz'/'f'/'r b'/''\\''bar b'")
|
check("echo ' foo'\\''bar baz'/'f'/'r b'/''\\''bar b'")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestQuoteEntryCmd(t *testing.T) {
|
||||||
|
tests := map[string]string{
|
||||||
|
`"`: `^"\^"^"`,
|
||||||
|
`\`: `^"\\^"`,
|
||||||
|
`\"`: `^"\\\^"^"`,
|
||||||
|
`"\\\"`: `^"\^"\\\\\\\^"^"`,
|
||||||
|
`&|<>()@^%!`: `^"^&^|^<^>^(^)^@^^^%^!^"`,
|
||||||
|
`%USERPROFILE%`: `^"^%USERPROFILE^%^"`,
|
||||||
|
`C:\Program Files (x86)\`: `^"C:\\Program Files ^(x86^)\\^"`,
|
||||||
|
}
|
||||||
|
|
||||||
|
for input, expected := range tests {
|
||||||
|
escaped := quoteEntryCmd(input)
|
||||||
|
if escaped != expected {
|
||||||
|
t.Errorf("Input: %s, expected: %s, actual %s", input, expected, escaped)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -147,7 +147,7 @@ func Tokenize(text string, delimiter Delimiter) []Token {
|
|||||||
if delimiter.regex != nil {
|
if delimiter.regex != nil {
|
||||||
for len(text) > 0 {
|
for len(text) > 0 {
|
||||||
loc := delimiter.regex.FindStringIndex(text)
|
loc := delimiter.regex.FindStringIndex(text)
|
||||||
if loc == nil {
|
if len(loc) < 2 {
|
||||||
loc = []int{0, len(text)}
|
loc = []int{0, len(text)}
|
||||||
}
|
}
|
||||||
last := util.Max(loc[1], 1)
|
last := util.Max(loc[1], 1)
|
||||||
|
|||||||
@@ -33,7 +33,6 @@ func (r *FullscreenRenderer) Refresh() {}
|
|||||||
func (r *FullscreenRenderer) Close() {}
|
func (r *FullscreenRenderer) Close() {}
|
||||||
|
|
||||||
func (r *FullscreenRenderer) DoesAutoWrap() bool { return false }
|
func (r *FullscreenRenderer) DoesAutoWrap() bool { return false }
|
||||||
func (r *FullscreenRenderer) IsOptimized() bool { return false }
|
|
||||||
func (r *FullscreenRenderer) GetChar() Event { return Event{} }
|
func (r *FullscreenRenderer) GetChar() Event { return Event{} }
|
||||||
func (r *FullscreenRenderer) MaxX() int { return 0 }
|
func (r *FullscreenRenderer) MaxX() int { return 0 }
|
||||||
func (r *FullscreenRenderer) MaxY() int { return 0 }
|
func (r *FullscreenRenderer) MaxY() int { return 0 }
|
||||||
|
|||||||
@@ -32,7 +32,8 @@ var offsetRegexp *regexp.Regexp = regexp.MustCompile("\x1b\\[([0-9]+);([0-9]+)R"
|
|||||||
func openTtyIn() *os.File {
|
func openTtyIn() *os.File {
|
||||||
in, err := os.OpenFile(consoleDevice, syscall.O_RDONLY, 0)
|
in, err := os.OpenFile(consoleDevice, syscall.O_RDONLY, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic("Failed to open " + consoleDevice)
|
fmt.Fprintln(os.Stderr, "Failed to open "+consoleDevice)
|
||||||
|
os.Exit(2)
|
||||||
}
|
}
|
||||||
return in
|
return in
|
||||||
}
|
}
|
||||||
@@ -104,6 +105,7 @@ type LightWindow struct {
|
|||||||
posx int
|
posx int
|
||||||
posy int
|
posy int
|
||||||
tabstop int
|
tabstop int
|
||||||
|
fg Color
|
||||||
bg Color
|
bg Color
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -208,7 +210,9 @@ func (r *LightRenderer) Init() {
|
|||||||
r.csi(fmt.Sprintf("%dA", r.MaxY()-1))
|
r.csi(fmt.Sprintf("%dA", r.MaxY()-1))
|
||||||
r.csi("G")
|
r.csi("G")
|
||||||
r.csi("K")
|
r.csi("K")
|
||||||
r.csi("s")
|
if !r.clearOnExit && !r.fullscreen {
|
||||||
|
r.csi("s")
|
||||||
|
}
|
||||||
if !r.fullscreen && r.mouse {
|
if !r.fullscreen && r.mouse {
|
||||||
r.yoffset, _ = r.findOffset()
|
r.yoffset, _ = r.findOffset()
|
||||||
}
|
}
|
||||||
@@ -491,16 +495,19 @@ func (r *LightRenderer) mouseSequence(sz *int) Event {
|
|||||||
}
|
}
|
||||||
*sz = 6
|
*sz = 6
|
||||||
switch r.buffer[3] {
|
switch r.buffer[3] {
|
||||||
case 32, 36, 40, 48, // mouse-down / shift / cmd / ctrl
|
case 32, 34, 36, 40, 48, // mouse-down / shift / cmd / ctrl
|
||||||
35, 39, 43, 51: // mouse-up / shift / cmd / ctrl
|
35, 39, 43, 51: // mouse-up / shift / cmd / ctrl
|
||||||
mod := r.buffer[3] >= 36
|
mod := r.buffer[3] >= 36
|
||||||
|
left := r.buffer[3] == 32
|
||||||
down := r.buffer[3]%2 == 0
|
down := r.buffer[3]%2 == 0
|
||||||
x := int(r.buffer[4] - 33)
|
x := int(r.buffer[4] - 33)
|
||||||
y := int(r.buffer[5]-33) - r.yoffset
|
y := int(r.buffer[5]-33) - r.yoffset
|
||||||
double := false
|
double := false
|
||||||
if down {
|
if down {
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
if now.Sub(r.prevDownTime) < doubleClickDuration {
|
if !left { // Right double click is not allowed
|
||||||
|
r.clickY = []int{}
|
||||||
|
} else if now.Sub(r.prevDownTime) < doubleClickDuration {
|
||||||
r.clickY = append(r.clickY, y)
|
r.clickY = append(r.clickY, y)
|
||||||
} else {
|
} else {
|
||||||
r.clickY = []int{y}
|
r.clickY = []int{y}
|
||||||
@@ -513,14 +520,14 @@ func (r *LightRenderer) mouseSequence(sz *int) Event {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Event{Mouse, 0, &MouseEvent{y, x, 0, down, double, mod}}
|
return Event{Mouse, 0, &MouseEvent{y, x, 0, left, down, double, mod}}
|
||||||
case 96, 100, 104, 112, // scroll-up / shift / cmd / ctrl
|
case 96, 100, 104, 112, // scroll-up / shift / cmd / ctrl
|
||||||
97, 101, 105, 113: // scroll-down / shift / cmd / ctrl
|
97, 101, 105, 113: // scroll-down / shift / cmd / ctrl
|
||||||
mod := r.buffer[3] >= 100
|
mod := r.buffer[3] >= 100
|
||||||
s := 1 - int(r.buffer[3]%2)*2
|
s := 1 - int(r.buffer[3]%2)*2
|
||||||
x := int(r.buffer[4] - 33)
|
x := int(r.buffer[4] - 33)
|
||||||
y := int(r.buffer[5]-33) - r.yoffset
|
y := int(r.buffer[5]-33) - r.yoffset
|
||||||
return Event{Mouse, 0, &MouseEvent{y, x, s, false, false, mod}}
|
return Event{Mouse, 0, &MouseEvent{y, x, s, false, false, false, mod}}
|
||||||
}
|
}
|
||||||
return Event{Invalid, 0, nil}
|
return Event{Invalid, 0, nil}
|
||||||
}
|
}
|
||||||
@@ -616,10 +623,6 @@ func (r *LightRenderer) DoesAutoWrap() bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *LightRenderer) IsOptimized() 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, borderStyle BorderStyle) Window {
|
||||||
w := &LightWindow{
|
w := &LightWindow{
|
||||||
renderer: r,
|
renderer: r,
|
||||||
@@ -630,8 +633,10 @@ func (r *LightRenderer) NewWindow(top int, left int, width int, height int, bord
|
|||||||
width: width,
|
width: width,
|
||||||
height: height,
|
height: height,
|
||||||
tabstop: r.tabstop,
|
tabstop: r.tabstop,
|
||||||
|
fg: colDefault,
|
||||||
bg: colDefault}
|
bg: colDefault}
|
||||||
if r.theme != nil {
|
if r.theme != nil {
|
||||||
|
w.fg = r.theme.Fg
|
||||||
w.bg = r.theme.Bg
|
w.bg = r.theme.Bg
|
||||||
}
|
}
|
||||||
w.drawBorder()
|
w.drawBorder()
|
||||||
@@ -878,12 +883,15 @@ func (w *LightWindow) Fill(text string) FillReturn {
|
|||||||
|
|
||||||
func (w *LightWindow) CFill(fg Color, bg Color, attr Attr, text string) FillReturn {
|
func (w *LightWindow) CFill(fg Color, bg Color, attr Attr, text string) FillReturn {
|
||||||
w.Move(w.posy, w.posx)
|
w.Move(w.posy, w.posx)
|
||||||
|
if fg == colDefault {
|
||||||
|
fg = w.fg
|
||||||
|
}
|
||||||
if bg == colDefault {
|
if bg == colDefault {
|
||||||
bg = w.bg
|
bg = w.bg
|
||||||
}
|
}
|
||||||
if w.csiColor(fg, bg, attr) {
|
if w.csiColor(fg, bg, attr) {
|
||||||
return w.fill(text, func() { w.csiColor(fg, bg, attr) })
|
|
||||||
defer w.csi("m")
|
defer w.csi("m")
|
||||||
|
return w.fill(text, func() { w.csiColor(fg, bg, attr) })
|
||||||
}
|
}
|
||||||
return w.fill(text, w.setBg)
|
return w.fill(text, w.setBg)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,505 +0,0 @@
|
|||||||
// +build ncurses
|
|
||||||
// +build !windows
|
|
||||||
// +build !tcell
|
|
||||||
|
|
||||||
package tui
|
|
||||||
|
|
||||||
/*
|
|
||||||
#include <ncurses.h>
|
|
||||||
#include <locale.h>
|
|
||||||
#cgo !static LDFLAGS: -lncurses
|
|
||||||
#cgo static LDFLAGS: -l:libncursesw.a -l:libtinfo.a -l:libgpm.a -ldl
|
|
||||||
#cgo android static LDFLAGS: -l:libncurses.a -fPIE -march=armv7-a -mfpu=neon -mhard-float -Wl,--no-warn-mismatch
|
|
||||||
|
|
||||||
FILE* c_tty() {
|
|
||||||
return fopen("/dev/tty", "r");
|
|
||||||
}
|
|
||||||
|
|
||||||
SCREEN* c_newterm(FILE* tty) {
|
|
||||||
return newterm(NULL, stderr, tty);
|
|
||||||
}
|
|
||||||
|
|
||||||
int c_getcurx(WINDOW* win) {
|
|
||||||
return getcurx(win);
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
import "C"
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
"unicode/utf8"
|
|
||||||
)
|
|
||||||
|
|
||||||
func HasFullscreenRenderer() bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
type Attr C.uint
|
|
||||||
|
|
||||||
type CursesWindow struct {
|
|
||||||
impl *C.WINDOW
|
|
||||||
top int
|
|
||||||
left int
|
|
||||||
width int
|
|
||||||
height int
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *CursesWindow) Top() int {
|
|
||||||
return w.top
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *CursesWindow) Left() int {
|
|
||||||
return w.left
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *CursesWindow) Width() int {
|
|
||||||
return w.width
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *CursesWindow) Height() int {
|
|
||||||
return w.height
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *CursesWindow) Refresh() {
|
|
||||||
C.wnoutrefresh(w.impl)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *CursesWindow) FinishFill() {
|
|
||||||
// NO-OP
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
Bold Attr = C.A_BOLD
|
|
||||||
Dim = C.A_DIM
|
|
||||||
Blink = C.A_BLINK
|
|
||||||
Reverse = C.A_REVERSE
|
|
||||||
Underline = C.A_UNDERLINE
|
|
||||||
)
|
|
||||||
|
|
||||||
var Italic Attr = C.A_VERTICAL << 1 // FIXME
|
|
||||||
|
|
||||||
const (
|
|
||||||
AttrRegular Attr = 0
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
_screen *C.SCREEN
|
|
||||||
_colorMap map[int]int16
|
|
||||||
_colorFn func(ColorPair, Attr) (C.short, C.int)
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
_colorMap = make(map[int]int16)
|
|
||||||
if strings.HasPrefix(C.GoString(C.curses_version()), "ncurses 5") {
|
|
||||||
Italic = C.A_NORMAL
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a Attr) Merge(b Attr) Attr {
|
|
||||||
return a | b
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *FullscreenRenderer) defaultTheme() *ColorTheme {
|
|
||||||
if C.tigetnum(C.CString("colors")) >= 256 {
|
|
||||||
return Dark256
|
|
||||||
}
|
|
||||||
return Default16
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *FullscreenRenderer) Init() {
|
|
||||||
C.setlocale(C.LC_ALL, C.CString(""))
|
|
||||||
tty := C.c_tty()
|
|
||||||
if tty == nil {
|
|
||||||
errorExit("Failed to open /dev/tty")
|
|
||||||
}
|
|
||||||
_screen = C.c_newterm(tty)
|
|
||||||
if _screen == nil {
|
|
||||||
errorExit("Invalid $TERM: " + os.Getenv("TERM"))
|
|
||||||
}
|
|
||||||
C.set_term(_screen)
|
|
||||||
if r.mouse {
|
|
||||||
C.mousemask(C.ALL_MOUSE_EVENTS, nil)
|
|
||||||
C.mouseinterval(0)
|
|
||||||
}
|
|
||||||
C.noecho()
|
|
||||||
C.raw() // stty dsusp undef
|
|
||||||
C.nonl()
|
|
||||||
C.keypad(C.stdscr, true)
|
|
||||||
|
|
||||||
delay := 50
|
|
||||||
delayEnv := os.Getenv("ESCDELAY")
|
|
||||||
if len(delayEnv) > 0 {
|
|
||||||
num, err := strconv.Atoi(delayEnv)
|
|
||||||
if err == nil && num >= 0 {
|
|
||||||
delay = num
|
|
||||||
}
|
|
||||||
}
|
|
||||||
C.set_escdelay(C.int(delay))
|
|
||||||
|
|
||||||
if r.theme != nil {
|
|
||||||
C.start_color()
|
|
||||||
initTheme(r.theme, r.defaultTheme(), r.forceBlack)
|
|
||||||
initPairs(r.theme)
|
|
||||||
C.bkgd(C.chtype(C.COLOR_PAIR(C.int(ColNormal.index()))))
|
|
||||||
_colorFn = attrColored
|
|
||||||
} else {
|
|
||||||
initTheme(r.theme, nil, r.forceBlack)
|
|
||||||
_colorFn = attrMono
|
|
||||||
}
|
|
||||||
|
|
||||||
C.nodelay(C.stdscr, true)
|
|
||||||
ch := C.getch()
|
|
||||||
if ch != C.ERR {
|
|
||||||
C.ungetch(ch)
|
|
||||||
}
|
|
||||||
C.nodelay(C.stdscr, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
func initPairs(theme *ColorTheme) {
|
|
||||||
C.assume_default_colors(C.int(theme.Fg), C.int(theme.Bg))
|
|
||||||
for _, pair := range []ColorPair{
|
|
||||||
ColNormal,
|
|
||||||
ColPrompt,
|
|
||||||
ColMatch,
|
|
||||||
ColCurrent,
|
|
||||||
ColCurrentMatch,
|
|
||||||
ColSpinner,
|
|
||||||
ColInfo,
|
|
||||||
ColCursor,
|
|
||||||
ColSelected,
|
|
||||||
ColHeader,
|
|
||||||
ColBorder} {
|
|
||||||
C.init_pair(C.short(pair.index()), C.short(pair.Fg()), C.short(pair.Bg()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *FullscreenRenderer) Pause(bool) {
|
|
||||||
C.endwin()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *FullscreenRenderer) Resume(bool) {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *FullscreenRenderer) Close() {
|
|
||||||
C.endwin()
|
|
||||||
C.delscreen(_screen)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *FullscreenRenderer) NewWindow(top int, left int, width int, height int, borderStyle BorderStyle) Window {
|
|
||||||
win := C.newwin(C.int(height), C.int(width), C.int(top), C.int(left))
|
|
||||||
if r.theme != nil {
|
|
||||||
C.wbkgd(win, C.chtype(C.COLOR_PAIR(C.int(ColNormal.index()))))
|
|
||||||
}
|
|
||||||
// FIXME Does not implement BorderHorizontal
|
|
||||||
if borderStyle != BorderNone {
|
|
||||||
pair, attr := _colorFn(ColBorder, 0)
|
|
||||||
C.wcolor_set(win, pair, nil)
|
|
||||||
C.wattron(win, attr)
|
|
||||||
C.box(win, 0, 0)
|
|
||||||
C.wattroff(win, attr)
|
|
||||||
C.wcolor_set(win, 0, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &CursesWindow{
|
|
||||||
impl: win,
|
|
||||||
top: top,
|
|
||||||
left: left,
|
|
||||||
width: width,
|
|
||||||
height: height,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func attrColored(color ColorPair, a Attr) (C.short, C.int) {
|
|
||||||
return C.short(color.index()), C.int(a)
|
|
||||||
}
|
|
||||||
|
|
||||||
func attrMono(color ColorPair, a Attr) (C.short, C.int) {
|
|
||||||
return 0, C.int(attrFor(color, a))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *FullscreenRenderer) MaxX() int {
|
|
||||||
return int(C.COLS)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *FullscreenRenderer) MaxY() int {
|
|
||||||
return int(C.LINES)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *CursesWindow) Close() {
|
|
||||||
C.delwin(w.impl)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *CursesWindow) Enclose(y int, x int) bool {
|
|
||||||
return bool(C.wenclose(w.impl, C.int(y), C.int(x)))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *CursesWindow) Move(y int, x int) {
|
|
||||||
C.wmove(w.impl, C.int(y), C.int(x))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *CursesWindow) MoveAndClear(y int, x int) {
|
|
||||||
w.Move(y, x)
|
|
||||||
C.wclrtoeol(w.impl)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *CursesWindow) Print(text string) {
|
|
||||||
C.waddstr(w.impl, C.CString(strings.Map(func(r rune) rune {
|
|
||||||
if r < 32 {
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
return r
|
|
||||||
}, text)))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *CursesWindow) CPrint(color ColorPair, attr Attr, text string) {
|
|
||||||
p, a := _colorFn(color, attr)
|
|
||||||
C.wcolor_set(w.impl, p, nil)
|
|
||||||
C.wattron(w.impl, a)
|
|
||||||
w.Print(text)
|
|
||||||
C.wattroff(w.impl, a)
|
|
||||||
C.wcolor_set(w.impl, 0, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *FullscreenRenderer) Clear() {
|
|
||||||
C.clear()
|
|
||||||
C.endwin()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *FullscreenRenderer) Refresh() {
|
|
||||||
C.refresh()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *CursesWindow) Erase() {
|
|
||||||
C.werase(w.impl)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *CursesWindow) X() int {
|
|
||||||
return int(C.c_getcurx(w.impl))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *FullscreenRenderer) DoesAutoWrap() bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *FullscreenRenderer) IsOptimized() bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *CursesWindow) Fill(str string) FillReturn {
|
|
||||||
if C.waddstr(w.impl, C.CString(str)) == C.OK {
|
|
||||||
return FillContinue
|
|
||||||
}
|
|
||||||
return FillSuspend
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *CursesWindow) CFill(fg Color, bg Color, attr Attr, str string) FillReturn {
|
|
||||||
index := ColorPair{fg, bg, -1}.index()
|
|
||||||
C.wcolor_set(w.impl, C.short(index), nil)
|
|
||||||
C.wattron(w.impl, C.int(attr))
|
|
||||||
ret := w.Fill(str)
|
|
||||||
C.wattroff(w.impl, C.int(attr))
|
|
||||||
C.wcolor_set(w.impl, 0, nil)
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *FullscreenRenderer) RefreshWindows(windows []Window) {
|
|
||||||
for _, w := range windows {
|
|
||||||
w.Refresh()
|
|
||||||
}
|
|
||||||
C.doupdate()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p ColorPair) index() int16 {
|
|
||||||
if p.id >= 0 {
|
|
||||||
return p.id
|
|
||||||
}
|
|
||||||
|
|
||||||
// ncurses does not support 24-bit colors
|
|
||||||
if p.is24() {
|
|
||||||
return ColDefault.index()
|
|
||||||
}
|
|
||||||
|
|
||||||
key := p.key()
|
|
||||||
if found, prs := _colorMap[key]; prs {
|
|
||||||
return found
|
|
||||||
}
|
|
||||||
|
|
||||||
id := int16(len(_colorMap)) + ColUser.id
|
|
||||||
C.init_pair(C.short(id), C.short(p.Fg()), C.short(p.Bg()))
|
|
||||||
_colorMap[key] = id
|
|
||||||
return id
|
|
||||||
}
|
|
||||||
|
|
||||||
func consume(expects ...rune) bool {
|
|
||||||
for _, r := range expects {
|
|
||||||
if int(C.getch()) != int(r) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func escSequence() Event {
|
|
||||||
C.nodelay(C.stdscr, true)
|
|
||||||
defer func() {
|
|
||||||
C.nodelay(C.stdscr, false)
|
|
||||||
}()
|
|
||||||
c := C.getch()
|
|
||||||
switch c {
|
|
||||||
case C.ERR:
|
|
||||||
return Event{ESC, 0, nil}
|
|
||||||
case CtrlM:
|
|
||||||
return Event{CtrlAltM, 0, nil}
|
|
||||||
case '/':
|
|
||||||
return Event{AltSlash, 0, nil}
|
|
||||||
case ' ':
|
|
||||||
return Event{AltSpace, 0, nil}
|
|
||||||
case 127, C.KEY_BACKSPACE:
|
|
||||||
return Event{AltBS, 0, nil}
|
|
||||||
case '[':
|
|
||||||
// Bracketed paste mode (printf "\e[?2004h")
|
|
||||||
// \e[200~ TEXT \e[201~
|
|
||||||
if consume('2', '0', '0', '~') {
|
|
||||||
return Event{Invalid, 0, nil}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if c >= 'a' && c <= 'z' {
|
|
||||||
return Event{AltA + int(c) - 'a', 0, nil}
|
|
||||||
}
|
|
||||||
|
|
||||||
if c >= '0' && c <= '9' {
|
|
||||||
return Event{Alt0 + int(c) - '0', 0, nil}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Don't care. Ignore the rest.
|
|
||||||
for ; c != C.ERR; c = C.getch() {
|
|
||||||
}
|
|
||||||
return Event{Invalid, 0, nil}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *FullscreenRenderer) GetChar() Event {
|
|
||||||
c := C.getch()
|
|
||||||
switch c {
|
|
||||||
case C.ERR:
|
|
||||||
// Unexpected error from blocking read
|
|
||||||
r.Close()
|
|
||||||
errorExit("Failed to read /dev/tty")
|
|
||||||
case C.KEY_UP:
|
|
||||||
return Event{Up, 0, nil}
|
|
||||||
case C.KEY_DOWN:
|
|
||||||
return Event{Down, 0, nil}
|
|
||||||
case C.KEY_LEFT:
|
|
||||||
return Event{Left, 0, nil}
|
|
||||||
case C.KEY_RIGHT:
|
|
||||||
return Event{Right, 0, nil}
|
|
||||||
case C.KEY_HOME:
|
|
||||||
return Event{Home, 0, nil}
|
|
||||||
case C.KEY_END:
|
|
||||||
return Event{End, 0, nil}
|
|
||||||
case C.KEY_BACKSPACE:
|
|
||||||
return Event{BSpace, 0, nil}
|
|
||||||
case C.KEY_F0 + 1:
|
|
||||||
return Event{F1, 0, nil}
|
|
||||||
case C.KEY_F0 + 2:
|
|
||||||
return Event{F2, 0, nil}
|
|
||||||
case C.KEY_F0 + 3:
|
|
||||||
return Event{F3, 0, nil}
|
|
||||||
case C.KEY_F0 + 4:
|
|
||||||
return Event{F4, 0, nil}
|
|
||||||
case C.KEY_F0 + 5:
|
|
||||||
return Event{F5, 0, nil}
|
|
||||||
case C.KEY_F0 + 6:
|
|
||||||
return Event{F6, 0, nil}
|
|
||||||
case C.KEY_F0 + 7:
|
|
||||||
return Event{F7, 0, nil}
|
|
||||||
case C.KEY_F0 + 8:
|
|
||||||
return Event{F8, 0, nil}
|
|
||||||
case C.KEY_F0 + 9:
|
|
||||||
return Event{F9, 0, nil}
|
|
||||||
case C.KEY_F0 + 10:
|
|
||||||
return Event{F10, 0, nil}
|
|
||||||
case C.KEY_F0 + 11:
|
|
||||||
return Event{F11, 0, nil}
|
|
||||||
case C.KEY_F0 + 12:
|
|
||||||
return Event{F12, 0, nil}
|
|
||||||
case C.KEY_DC:
|
|
||||||
return Event{Del, 0, nil}
|
|
||||||
case C.KEY_PPAGE:
|
|
||||||
return Event{PgUp, 0, nil}
|
|
||||||
case C.KEY_NPAGE:
|
|
||||||
return Event{PgDn, 0, nil}
|
|
||||||
case C.KEY_BTAB:
|
|
||||||
return Event{BTab, 0, nil}
|
|
||||||
case C.KEY_ENTER:
|
|
||||||
return Event{CtrlM, 0, nil}
|
|
||||||
case C.KEY_SLEFT:
|
|
||||||
return Event{SLeft, 0, nil}
|
|
||||||
case C.KEY_SRIGHT:
|
|
||||||
return Event{SRight, 0, nil}
|
|
||||||
case C.KEY_MOUSE:
|
|
||||||
var me C.MEVENT
|
|
||||||
if C.getmouse(&me) != C.ERR {
|
|
||||||
mod := ((me.bstate & C.BUTTON_SHIFT) | (me.bstate & C.BUTTON_CTRL) | (me.bstate & C.BUTTON_ALT)) > 0
|
|
||||||
x := int(me.x)
|
|
||||||
y := int(me.y)
|
|
||||||
/* Cannot use BUTTON1_DOUBLE_CLICKED due to mouseinterval(0) */
|
|
||||||
if (me.bstate & C.BUTTON1_PRESSED) > 0 {
|
|
||||||
now := time.Now()
|
|
||||||
if now.Sub(r.prevDownTime) < doubleClickDuration {
|
|
||||||
r.clickY = append(r.clickY, y)
|
|
||||||
} else {
|
|
||||||
r.clickY = []int{y}
|
|
||||||
r.prevDownTime = now
|
|
||||||
}
|
|
||||||
return Event{Mouse, 0, &MouseEvent{y, x, 0, true, false, mod}}
|
|
||||||
} else if (me.bstate & C.BUTTON1_RELEASED) > 0 {
|
|
||||||
double := false
|
|
||||||
if len(r.clickY) > 1 && r.clickY[0] == r.clickY[1] &&
|
|
||||||
time.Now().Sub(r.prevDownTime) < doubleClickDuration {
|
|
||||||
double = true
|
|
||||||
}
|
|
||||||
return Event{Mouse, 0, &MouseEvent{y, x, 0, false, double, mod}}
|
|
||||||
} else if (me.bstate&0x8000000) > 0 || (me.bstate&0x80) > 0 {
|
|
||||||
return Event{Mouse, 0, &MouseEvent{y, x, -1, false, false, mod}}
|
|
||||||
} else if (me.bstate & C.BUTTON4_PRESSED) > 0 {
|
|
||||||
return Event{Mouse, 0, &MouseEvent{y, x, 1, false, false, mod}}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Event{Invalid, 0, nil}
|
|
||||||
case C.KEY_RESIZE:
|
|
||||||
return Event{Resize, 0, nil}
|
|
||||||
case ESC:
|
|
||||||
return escSequence()
|
|
||||||
case 127:
|
|
||||||
return Event{BSpace, 0, nil}
|
|
||||||
case 0:
|
|
||||||
return Event{CtrlSpace, 0, nil}
|
|
||||||
}
|
|
||||||
// CTRL-A ~ CTRL-Z
|
|
||||||
if c >= CtrlA && c <= CtrlZ {
|
|
||||||
return Event{int(c), 0, nil}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Multi-byte character
|
|
||||||
buffer := []byte{byte(c)}
|
|
||||||
for {
|
|
||||||
r, _ := utf8.DecodeRune(buffer)
|
|
||||||
if r != utf8.RuneError {
|
|
||||||
return Event{Rune, r, nil}
|
|
||||||
}
|
|
||||||
|
|
||||||
c := C.getch()
|
|
||||||
if c == C.ERR {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if c >= C.KEY_CODE_YES {
|
|
||||||
C.ungetch(c)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
buffer = append(buffer, byte(c))
|
|
||||||
}
|
|
||||||
return Event{Invalid, 0, nil}
|
|
||||||
}
|
|
||||||
@@ -172,10 +172,6 @@ func (r *FullscreenRenderer) DoesAutoWrap() bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *FullscreenRenderer) IsOptimized() bool {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *FullscreenRenderer) Clear() {
|
func (r *FullscreenRenderer) Clear() {
|
||||||
_screen.Sync()
|
_screen.Sync()
|
||||||
_screen.Clear()
|
_screen.Clear()
|
||||||
@@ -197,19 +193,22 @@ func (r *FullscreenRenderer) GetChar() Event {
|
|||||||
button := ev.Buttons()
|
button := ev.Buttons()
|
||||||
mod := ev.Modifiers() != 0
|
mod := ev.Modifiers() != 0
|
||||||
if button&tcell.WheelDown != 0 {
|
if button&tcell.WheelDown != 0 {
|
||||||
return Event{Mouse, 0, &MouseEvent{y, x, -1, false, false, mod}}
|
return Event{Mouse, 0, &MouseEvent{y, x, -1, false, false, false, mod}}
|
||||||
} else if button&tcell.WheelUp != 0 {
|
} else if button&tcell.WheelUp != 0 {
|
||||||
return Event{Mouse, 0, &MouseEvent{y, x, +1, false, false, mod}}
|
return Event{Mouse, 0, &MouseEvent{y, x, +1, false, false, false, mod}}
|
||||||
} else if runtime.GOOS != "windows" {
|
} else if runtime.GOOS != "windows" {
|
||||||
// double and single taps on Windows don't quite work due to
|
// double and single taps on Windows don't quite work due to
|
||||||
// the console acting on the events and not allowing us
|
// the console acting on the events and not allowing us
|
||||||
// to consume them.
|
// to consume them.
|
||||||
|
|
||||||
down := button&tcell.Button1 != 0 // left
|
left := button&tcell.Button1 != 0
|
||||||
|
down := left || button&tcell.Button3 != 0
|
||||||
double := false
|
double := false
|
||||||
if down {
|
if down {
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
if now.Sub(r.prevDownTime) < doubleClickDuration {
|
if !left {
|
||||||
|
r.clickY = []int{}
|
||||||
|
} else if now.Sub(r.prevDownTime) < doubleClickDuration {
|
||||||
r.clickY = append(r.clickY, x)
|
r.clickY = append(r.clickY, x)
|
||||||
} else {
|
} else {
|
||||||
r.clickY = []int{x}
|
r.clickY = []int{x}
|
||||||
@@ -222,7 +221,7 @@ func (r *FullscreenRenderer) GetChar() Event {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Event{Mouse, 0, &MouseEvent{y, x, 0, down, double, mod}}
|
return Event{Mouse, 0, &MouseEvent{y, x, 0, left, down, double, mod}}
|
||||||
}
|
}
|
||||||
|
|
||||||
// process keyboard:
|
// process keyboard:
|
||||||
@@ -409,14 +408,13 @@ func (w *TcellWindow) Close() {
|
|||||||
func fill(x, y, w, h int, r rune) {
|
func fill(x, y, w, h int, r rune) {
|
||||||
for ly := 0; ly <= h; ly++ {
|
for ly := 0; ly <= h; ly++ {
|
||||||
for lx := 0; lx <= w; lx++ {
|
for lx := 0; lx <= w; lx++ {
|
||||||
_screen.SetContent(x+lx, y+ly, r, nil, ColDefault.style())
|
_screen.SetContent(x+lx, y+ly, r, nil, ColNormal.style())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *TcellWindow) Erase() {
|
func (w *TcellWindow) Erase() {
|
||||||
// TODO
|
fill(w.left-1, w.top, w.width+1, w.height, ' ')
|
||||||
fill(w.left, w.top, w.width, w.height, ' ')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *TcellWindow) Enclose(y int, x int) bool {
|
func (w *TcellWindow) Enclose(y int, x int) bool {
|
||||||
@@ -433,13 +431,13 @@ func (w *TcellWindow) Move(y int, x int) {
|
|||||||
func (w *TcellWindow) MoveAndClear(y int, x int) {
|
func (w *TcellWindow) MoveAndClear(y int, x int) {
|
||||||
w.Move(y, x)
|
w.Move(y, x)
|
||||||
for i := w.lastX; i < w.width; i++ {
|
for i := w.lastX; i < w.width; i++ {
|
||||||
_screen.SetContent(i+w.left, w.lastY+w.top, rune(' '), nil, ColDefault.style())
|
_screen.SetContent(i+w.left, w.lastY+w.top, rune(' '), nil, ColNormal.style())
|
||||||
}
|
}
|
||||||
w.lastX = x
|
w.lastX = x
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *TcellWindow) Print(text string) {
|
func (w *TcellWindow) Print(text string) {
|
||||||
w.printString(text, ColDefault, 0)
|
w.printString(text, ColNormal, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *TcellWindow) printString(text string, pair ColorPair, a Attr) {
|
func (w *TcellWindow) printString(text string, pair ColorPair, a Attr) {
|
||||||
@@ -452,7 +450,7 @@ func (w *TcellWindow) printString(text string, pair ColorPair, a Attr) {
|
|||||||
Reverse(a&Attr(tcell.AttrReverse) != 0).
|
Reverse(a&Attr(tcell.AttrReverse) != 0).
|
||||||
Underline(a&Attr(tcell.AttrUnderline) != 0)
|
Underline(a&Attr(tcell.AttrUnderline) != 0)
|
||||||
} else {
|
} else {
|
||||||
style = ColDefault.style().
|
style = ColNormal.style().
|
||||||
Reverse(a&Attr(tcell.AttrReverse) != 0 || pair == ColCurrent || pair == ColCurrentMatch).
|
Reverse(a&Attr(tcell.AttrReverse) != 0 || pair == ColCurrent || pair == ColCurrentMatch).
|
||||||
Underline(a&Attr(tcell.AttrUnderline) != 0 || pair == ColMatch || pair == ColCurrentMatch)
|
Underline(a&Attr(tcell.AttrUnderline) != 0 || pair == ColMatch || pair == ColCurrentMatch)
|
||||||
}
|
}
|
||||||
@@ -503,7 +501,7 @@ func (w *TcellWindow) fillString(text string, pair ColorPair, a Attr) FillReturn
|
|||||||
if w.color {
|
if w.color {
|
||||||
style = pair.style()
|
style = pair.style()
|
||||||
} else {
|
} else {
|
||||||
style = ColDefault.style()
|
style = ColNormal.style()
|
||||||
}
|
}
|
||||||
style = style.
|
style = style.
|
||||||
Blink(a&Attr(tcell.AttrBlink) != 0).
|
Blink(a&Attr(tcell.AttrBlink) != 0).
|
||||||
@@ -543,11 +541,17 @@ func (w *TcellWindow) fillString(text string, pair ColorPair, a Attr) FillReturn
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (w *TcellWindow) Fill(str string) FillReturn {
|
func (w *TcellWindow) Fill(str string) FillReturn {
|
||||||
return w.fillString(str, ColDefault, 0)
|
return w.fillString(str, ColNormal, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *TcellWindow) CFill(fg Color, bg Color, a Attr, str string) FillReturn {
|
func (w *TcellWindow) CFill(fg Color, bg Color, a Attr, str string) FillReturn {
|
||||||
return w.fillString(str, ColorPair{fg, bg, -1}, a)
|
if fg == colDefault {
|
||||||
|
fg = ColNormal.Fg()
|
||||||
|
}
|
||||||
|
if bg == colDefault {
|
||||||
|
bg = ColNormal.Bg()
|
||||||
|
}
|
||||||
|
return w.fillString(str, NewColorPair(fg, bg), a)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *TcellWindow) drawBorder(around bool) {
|
func (w *TcellWindow) drawBorder(around bool) {
|
||||||
@@ -560,7 +564,7 @@ func (w *TcellWindow) drawBorder(around bool) {
|
|||||||
if w.color {
|
if w.color {
|
||||||
style = ColBorder.style()
|
style = ColBorder.style()
|
||||||
} else {
|
} else {
|
||||||
style = ColDefault.style()
|
style = ColNormal.style()
|
||||||
}
|
}
|
||||||
|
|
||||||
for x := left; x < right; x++ {
|
for x := left; x < right; x++ {
|
||||||
|
|||||||
@@ -44,6 +44,8 @@ const (
|
|||||||
Resize
|
Resize
|
||||||
Mouse
|
Mouse
|
||||||
DoubleClick
|
DoubleClick
|
||||||
|
LeftClick
|
||||||
|
RightClick
|
||||||
|
|
||||||
BTab
|
BTab
|
||||||
BSpace
|
BSpace
|
||||||
@@ -133,7 +135,7 @@ const (
|
|||||||
type ColorPair struct {
|
type ColorPair struct {
|
||||||
fg Color
|
fg Color
|
||||||
bg Color
|
bg Color
|
||||||
id int16
|
id int
|
||||||
}
|
}
|
||||||
|
|
||||||
func HexToColor(rrggbb string) Color {
|
func HexToColor(rrggbb string) Color {
|
||||||
@@ -155,12 +157,8 @@ func (p ColorPair) Bg() Color {
|
|||||||
return p.bg
|
return p.bg
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p ColorPair) key() int {
|
|
||||||
return (int(p.Fg()) << 8) + int(p.Bg())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p ColorPair) is24() bool {
|
func (p ColorPair) is24() bool {
|
||||||
return p.Fg().is24() || p.Bg().is24()
|
return p.fg.is24() || p.bg.is24()
|
||||||
}
|
}
|
||||||
|
|
||||||
type ColorTheme struct {
|
type ColorTheme struct {
|
||||||
@@ -179,10 +177,6 @@ type ColorTheme struct {
|
|||||||
Border Color
|
Border Color
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *ColorTheme) HasBg() bool {
|
|
||||||
return t.Bg != colDefault
|
|
||||||
}
|
|
||||||
|
|
||||||
type Event struct {
|
type Event struct {
|
||||||
Type int
|
Type int
|
||||||
Char rune
|
Char rune
|
||||||
@@ -193,6 +187,7 @@ type MouseEvent struct {
|
|||||||
Y int
|
Y int
|
||||||
X int
|
X int
|
||||||
S int
|
S int
|
||||||
|
Left bool
|
||||||
Down bool
|
Down bool
|
||||||
Double bool
|
Double bool
|
||||||
Mod bool
|
Mod bool
|
||||||
@@ -220,7 +215,6 @@ type Renderer interface {
|
|||||||
MaxX() int
|
MaxX() int
|
||||||
MaxY() int
|
MaxY() int
|
||||||
DoesAutoWrap() bool
|
DoesAutoWrap() bool
|
||||||
IsOptimized() bool
|
|
||||||
|
|
||||||
NewWindow(top int, left int, width int, height int, borderStyle BorderStyle) Window
|
NewWindow(top int, left int, width int, height int, borderStyle BorderStyle) Window
|
||||||
}
|
}
|
||||||
@@ -271,7 +265,6 @@ var (
|
|||||||
Dark256 *ColorTheme
|
Dark256 *ColorTheme
|
||||||
Light256 *ColorTheme
|
Light256 *ColorTheme
|
||||||
|
|
||||||
ColDefault ColorPair
|
|
||||||
ColNormal ColorPair
|
ColNormal ColorPair
|
||||||
ColPrompt ColorPair
|
ColPrompt ColorPair
|
||||||
ColMatch ColorPair
|
ColMatch ColorPair
|
||||||
@@ -283,7 +276,6 @@ var (
|
|||||||
ColSelected ColorPair
|
ColSelected ColorPair
|
||||||
ColHeader ColorPair
|
ColHeader ColorPair
|
||||||
ColBorder ColorPair
|
ColBorder ColorPair
|
||||||
ColUser ColorPair
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func EmptyTheme() *ColorTheme {
|
func EmptyTheme() *ColorTheme {
|
||||||
@@ -387,33 +379,36 @@ func initTheme(theme *ColorTheme, baseTheme *ColorTheme, forceBlack bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func initPalette(theme *ColorTheme) {
|
func initPalette(theme *ColorTheme) {
|
||||||
ColDefault = ColorPair{colDefault, colDefault, 0}
|
idx := 0
|
||||||
if theme != nil {
|
pair := func(fg, bg Color) ColorPair {
|
||||||
ColNormal = ColorPair{theme.Fg, theme.Bg, 1}
|
idx++
|
||||||
ColPrompt = ColorPair{theme.Prompt, theme.Bg, 2}
|
return ColorPair{fg, bg, idx}
|
||||||
ColMatch = ColorPair{theme.Match, theme.Bg, 3}
|
}
|
||||||
ColCurrent = ColorPair{theme.Current, theme.DarkBg, 4}
|
if theme != nil {
|
||||||
ColCurrentMatch = ColorPair{theme.CurrentMatch, theme.DarkBg, 5}
|
ColNormal = pair(theme.Fg, theme.Bg)
|
||||||
ColSpinner = ColorPair{theme.Spinner, theme.Bg, 6}
|
ColPrompt = pair(theme.Prompt, theme.Bg)
|
||||||
ColInfo = ColorPair{theme.Info, theme.Bg, 7}
|
ColMatch = pair(theme.Match, theme.Bg)
|
||||||
ColCursor = ColorPair{theme.Cursor, theme.DarkBg, 8}
|
ColCurrent = pair(theme.Current, theme.DarkBg)
|
||||||
ColSelected = ColorPair{theme.Selected, theme.DarkBg, 9}
|
ColCurrentMatch = pair(theme.CurrentMatch, theme.DarkBg)
|
||||||
ColHeader = ColorPair{theme.Header, theme.Bg, 10}
|
ColSpinner = pair(theme.Spinner, theme.Bg)
|
||||||
ColBorder = ColorPair{theme.Border, theme.Bg, 11}
|
ColInfo = pair(theme.Info, theme.Bg)
|
||||||
} else {
|
ColCursor = pair(theme.Cursor, theme.DarkBg)
|
||||||
ColNormal = ColorPair{colDefault, colDefault, 1}
|
ColSelected = pair(theme.Selected, theme.DarkBg)
|
||||||
ColPrompt = ColorPair{colDefault, colDefault, 2}
|
ColHeader = pair(theme.Header, theme.Bg)
|
||||||
ColMatch = ColorPair{colDefault, colDefault, 3}
|
ColBorder = pair(theme.Border, theme.Bg)
|
||||||
ColCurrent = ColorPair{colDefault, colDefault, 4}
|
} else {
|
||||||
ColCurrentMatch = ColorPair{colDefault, colDefault, 5}
|
ColNormal = pair(colDefault, colDefault)
|
||||||
ColSpinner = ColorPair{colDefault, colDefault, 6}
|
ColPrompt = pair(colDefault, colDefault)
|
||||||
ColInfo = ColorPair{colDefault, colDefault, 7}
|
ColMatch = pair(colDefault, colDefault)
|
||||||
ColCursor = ColorPair{colDefault, colDefault, 8}
|
ColCurrent = pair(colDefault, colDefault)
|
||||||
ColSelected = ColorPair{colDefault, colDefault, 9}
|
ColCurrentMatch = pair(colDefault, colDefault)
|
||||||
ColHeader = ColorPair{colDefault, colDefault, 10}
|
ColSpinner = pair(colDefault, colDefault)
|
||||||
ColBorder = ColorPair{colDefault, colDefault, 11}
|
ColInfo = pair(colDefault, colDefault)
|
||||||
|
ColCursor = pair(colDefault, colDefault)
|
||||||
|
ColSelected = pair(colDefault, colDefault)
|
||||||
|
ColHeader = pair(colDefault, colDefault)
|
||||||
|
ColBorder = pair(colDefault, colDefault)
|
||||||
}
|
}
|
||||||
ColUser = ColorPair{colDefault, colDefault, 12}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func attrFor(color ColorPair, attr Attr) Attr {
|
func attrFor(color ColorPair, attr Attr) Attr {
|
||||||
|
|||||||
@@ -24,12 +24,12 @@ type Chars struct {
|
|||||||
|
|
||||||
func checkAscii(bytes []byte) (bool, int) {
|
func checkAscii(bytes []byte) (bool, int) {
|
||||||
i := 0
|
i := 0
|
||||||
for ; i < len(bytes)-8; i += 8 {
|
for ; i <= len(bytes)-8; i += 8 {
|
||||||
if (overflow64 & *(*uint64)(unsafe.Pointer(&bytes[i]))) > 0 {
|
if (overflow64 & *(*uint64)(unsafe.Pointer(&bytes[i]))) > 0 {
|
||||||
return false, i
|
return false, i
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for ; i < len(bytes)-4; i += 4 {
|
for ; i <= len(bytes)-4; i += 4 {
|
||||||
if (overflow32 & *(*uint32)(unsafe.Pointer(&bytes[i]))) > 0 {
|
if (overflow32 & *(*uint32)(unsafe.Pointer(&bytes[i]))) > 0 {
|
||||||
return false, i
|
return false, i
|
||||||
}
|
}
|
||||||
@@ -65,6 +65,14 @@ func RunesToChars(runes []rune) Chars {
|
|||||||
return Chars{slice: *(*[]byte)(unsafe.Pointer(&runes)), inBytes: false}
|
return Chars{slice: *(*[]byte)(unsafe.Pointer(&runes)), inBytes: false}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (chars *Chars) IsBytes() bool {
|
||||||
|
return chars.inBytes
|
||||||
|
}
|
||||||
|
|
||||||
|
func (chars *Chars) Bytes() []byte {
|
||||||
|
return chars.slice
|
||||||
|
}
|
||||||
|
|
||||||
func (chars *Chars) optionalRunes() []rune {
|
func (chars *Chars) optionalRunes() []rune {
|
||||||
if chars.inBytes {
|
if chars.inBytes {
|
||||||
return nil
|
return nil
|
||||||
@@ -152,7 +160,7 @@ func (chars *Chars) CopyRunes(dest []rune) {
|
|||||||
copy(dest, runes)
|
copy(dest, runes)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
for idx, b := range chars.slice {
|
for idx, b := range chars.slice[:len(dest)] {
|
||||||
dest[idx] = rune(b)
|
dest[idx] = rune(b)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -26,23 +26,23 @@ func NewEventBox() *EventBox {
|
|||||||
// Wait blocks the goroutine until signaled
|
// Wait blocks the goroutine until signaled
|
||||||
func (b *EventBox) Wait(callback func(*Events)) {
|
func (b *EventBox) Wait(callback func(*Events)) {
|
||||||
b.cond.L.Lock()
|
b.cond.L.Lock()
|
||||||
defer b.cond.L.Unlock()
|
|
||||||
|
|
||||||
if len(b.events) == 0 {
|
if len(b.events) == 0 {
|
||||||
b.cond.Wait()
|
b.cond.Wait()
|
||||||
}
|
}
|
||||||
|
|
||||||
callback(&b.events)
|
callback(&b.events)
|
||||||
|
b.cond.L.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set turns on the event type on the box
|
// Set turns on the event type on the box
|
||||||
func (b *EventBox) Set(event EventType, value interface{}) {
|
func (b *EventBox) Set(event EventType, value interface{}) {
|
||||||
b.cond.L.Lock()
|
b.cond.L.Lock()
|
||||||
defer b.cond.L.Unlock()
|
|
||||||
b.events[event] = value
|
b.events[event] = value
|
||||||
if _, found := b.ignore[event]; !found {
|
if _, found := b.ignore[event]; !found {
|
||||||
b.cond.Broadcast()
|
b.cond.Broadcast()
|
||||||
}
|
}
|
||||||
|
b.cond.L.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear clears the events
|
// Clear clears the events
|
||||||
@@ -56,27 +56,27 @@ func (events *Events) Clear() {
|
|||||||
// Peek peeks at the event box if the given event is set
|
// Peek peeks at the event box if the given event is set
|
||||||
func (b *EventBox) Peek(event EventType) bool {
|
func (b *EventBox) Peek(event EventType) bool {
|
||||||
b.cond.L.Lock()
|
b.cond.L.Lock()
|
||||||
defer b.cond.L.Unlock()
|
|
||||||
_, ok := b.events[event]
|
_, ok := b.events[event]
|
||||||
|
b.cond.L.Unlock()
|
||||||
return ok
|
return ok
|
||||||
}
|
}
|
||||||
|
|
||||||
// Watch deletes the events from the ignore list
|
// Watch deletes the events from the ignore list
|
||||||
func (b *EventBox) Watch(events ...EventType) {
|
func (b *EventBox) Watch(events ...EventType) {
|
||||||
b.cond.L.Lock()
|
b.cond.L.Lock()
|
||||||
defer b.cond.L.Unlock()
|
|
||||||
for _, event := range events {
|
for _, event := range events {
|
||||||
delete(b.ignore, event)
|
delete(b.ignore, event)
|
||||||
}
|
}
|
||||||
|
b.cond.L.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unwatch adds the events to the ignore list
|
// Unwatch adds the events to the ignore list
|
||||||
func (b *EventBox) Unwatch(events ...EventType) {
|
func (b *EventBox) Unwatch(events ...EventType) {
|
||||||
b.cond.L.Lock()
|
b.cond.L.Lock()
|
||||||
defer b.cond.L.Unlock()
|
|
||||||
for _, event := range events {
|
for _, event := range events {
|
||||||
b.ignore[event] = true
|
b.ignore[event] = true
|
||||||
}
|
}
|
||||||
|
b.cond.L.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
// WaitFor blocks the execution until the event is received
|
// WaitFor blocks the execution until the event is received
|
||||||
|
|||||||
@@ -14,6 +14,11 @@ func ExecCommand(command string) *exec.Cmd {
|
|||||||
if len(shell) == 0 {
|
if len(shell) == 0 {
|
||||||
shell = "sh"
|
shell = "sh"
|
||||||
}
|
}
|
||||||
|
return ExecCommandWith(shell, command)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExecCommandWith executes the given command with the specified shell
|
||||||
|
func ExecCommandWith(shell string, command string) *exec.Cmd {
|
||||||
return exec.Command(shell, "-c", command)
|
return exec.Command(shell, "-c", command)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,20 +3,27 @@
|
|||||||
package util
|
package util
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"github.com/mattn/go-shellwords"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// ExecCommand executes the given command with $SHELL
|
// ExecCommand executes the given command with cmd
|
||||||
func ExecCommand(command string) *exec.Cmd {
|
func ExecCommand(command string) *exec.Cmd {
|
||||||
args, _ := shellwords.Parse(command)
|
return ExecCommandWith("cmd", command)
|
||||||
allArgs := make([]string, len(args)+1)
|
}
|
||||||
allArgs[0] = "/c"
|
|
||||||
copy(allArgs[1:], args)
|
// ExecCommandWith executes the given command with cmd. _shell parameter is
|
||||||
return exec.Command("cmd", allArgs...)
|
// ignored on Windows.
|
||||||
|
func ExecCommandWith(_shell string, command string) *exec.Cmd {
|
||||||
|
cmd := exec.Command("cmd")
|
||||||
|
cmd.SysProcAttr = &syscall.SysProcAttr{
|
||||||
|
HideWindow: false,
|
||||||
|
CmdLine: fmt.Sprintf(` /s /c "%s"`, command),
|
||||||
|
CreationFlags: 0,
|
||||||
|
}
|
||||||
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsWindows returns true on Windows
|
// IsWindows returns true on Windows
|
||||||
|
|||||||
@@ -6,11 +6,11 @@ Execute (Setup):
|
|||||||
|
|
||||||
Execute (fzf#run with dir option):
|
Execute (fzf#run with dir option):
|
||||||
let cwd = getcwd()
|
let cwd = getcwd()
|
||||||
let result = fzf#run({ 'options': '--filter=vdr', 'dir': g:dir })
|
let result = fzf#run({ 'source': 'git ls-files', 'options': '--filter=vdr', 'dir': g:dir })
|
||||||
AssertEqual ['fzf.vader'], result
|
AssertEqual ['fzf.vader'], result
|
||||||
AssertEqual getcwd(), cwd
|
AssertEqual getcwd(), cwd
|
||||||
|
|
||||||
let result = sort(fzf#run({ 'options': '--filter e', 'dir': g:dir }))
|
let result = sort(fzf#run({ 'source': 'git ls-files', 'options': '--filter e', 'dir': g:dir }))
|
||||||
AssertEqual ['fzf.vader', 'test_go.rb'], result
|
AssertEqual ['fzf.vader', 'test_go.rb'], result
|
||||||
AssertEqual getcwd(), cwd
|
AssertEqual getcwd(), cwd
|
||||||
|
|
||||||
@@ -19,7 +19,7 @@ Execute (fzf#run with Funcref command):
|
|||||||
function! g:FzfTest(e)
|
function! g:FzfTest(e)
|
||||||
call add(g:ret, a:e)
|
call add(g:ret, a:e)
|
||||||
endfunction
|
endfunction
|
||||||
let result = sort(fzf#run({ 'sink': function('g:FzfTest'), 'options': '--filter e', 'dir': g:dir }))
|
let result = sort(fzf#run({ 'source': 'git ls-files', 'sink': function('g:FzfTest'), 'options': '--filter e', 'dir': g:dir }))
|
||||||
AssertEqual ['fzf.vader', 'test_go.rb'], result
|
AssertEqual ['fzf.vader', 'test_go.rb'], result
|
||||||
AssertEqual ['fzf.vader', 'test_go.rb'], sort(g:ret)
|
AssertEqual ['fzf.vader', 'test_go.rb'], sort(g:ret)
|
||||||
|
|
||||||
@@ -140,7 +140,7 @@ Execute (fzf#wrap):
|
|||||||
let g:fzf_history_dir = '/tmp'
|
let g:fzf_history_dir = '/tmp'
|
||||||
let opts = fzf#wrap('foobar', {'options': '--color light'})
|
let opts = fzf#wrap('foobar', {'options': '--color light'})
|
||||||
Log opts
|
Log opts
|
||||||
Assert opts.options =~ '--history /tmp/foobar'
|
Assert opts.options =~ "--history '/tmp/foobar'"
|
||||||
Assert opts.options =~ '--color light'
|
Assert opts.options =~ '--color light'
|
||||||
|
|
||||||
let g:fzf_colors = { 'fg': ['fg', 'Error'] }
|
let g:fzf_colors = { 'fg': ['fg', 'Error'] }
|
||||||
@@ -149,16 +149,18 @@ Execute (fzf#wrap):
|
|||||||
|
|
||||||
Execute (fzf#shellescape with sh):
|
Execute (fzf#shellescape with sh):
|
||||||
AssertEqual '''''', fzf#shellescape('', 'sh')
|
AssertEqual '''''', fzf#shellescape('', 'sh')
|
||||||
|
AssertEqual '''\''', fzf#shellescape('\', 'sh')
|
||||||
AssertEqual '''""''', fzf#shellescape('""', 'sh')
|
AssertEqual '''""''', fzf#shellescape('""', 'sh')
|
||||||
AssertEqual '''foobar>''', fzf#shellescape('foobar>', 'sh')
|
AssertEqual '''foobar>''', fzf#shellescape('foobar>', 'sh')
|
||||||
AssertEqual '''\"''', fzf#shellescape('\"', 'sh')
|
AssertEqual '''\\\"\\\''', fzf#shellescape('\\\"\\\', 'sh')
|
||||||
AssertEqual '''echo ''\''''a''\'''' && echo ''\''''b''\''''''', fzf#shellescape('echo ''a'' && echo ''b''', 'sh')
|
AssertEqual '''echo ''\''''a''\'''' && echo ''\''''b''\''''''', fzf#shellescape('echo ''a'' && echo ''b''', 'sh')
|
||||||
|
|
||||||
Execute (fzf#shellescape with cmd.exe):
|
Execute (fzf#shellescape with cmd.exe):
|
||||||
AssertEqual '^"^"', fzf#shellescape('', 'cmd.exe')
|
AssertEqual '^"^"', fzf#shellescape('', 'cmd.exe')
|
||||||
|
AssertEqual '^"\\^"', fzf#shellescape('\', 'cmd.exe')
|
||||||
AssertEqual '^"\^"\^"^"', fzf#shellescape('""', 'cmd.exe')
|
AssertEqual '^"\^"\^"^"', fzf#shellescape('""', 'cmd.exe')
|
||||||
AssertEqual '^"foobar^>^"', fzf#shellescape('foobar>', 'cmd.exe')
|
AssertEqual '^"foobar^>^"', fzf#shellescape('foobar>', 'cmd.exe')
|
||||||
AssertEqual '^"\\\^"\\^"', fzf#shellescape('\\\\\\\\"\', 'cmd.exe')
|
AssertEqual '^"\\\\\\\^"\\\\\\^"', fzf#shellescape('\\\"\\\', 'cmd.exe')
|
||||||
AssertEqual '^"echo ''a'' ^&^& echo ''b''^"', fzf#shellescape('echo ''a'' && echo ''b''', 'cmd.exe')
|
AssertEqual '^"echo ''a'' ^&^& echo ''b''^"', fzf#shellescape('echo ''a'' && echo ''b''', 'cmd.exe')
|
||||||
|
|
||||||
AssertEqual '^"C:\Program Files ^(x86^)\\^"', fzf#shellescape('C:\Program Files (x86)\', 'cmd.exe')
|
AssertEqual '^"C:\Program Files ^(x86^)\\^"', fzf#shellescape('C:\Program Files (x86)\', 'cmd.exe')
|
||||||
|
|||||||
603
test/test_go.rb
603
test/test_go.rb
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user