mirror of
https://github.com/junegunn/fzf.git
synced 2025-11-13 13:53:47 -05:00
Compare commits
65 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b0673c3563 | ||
|
|
373c6d8d55 | ||
|
|
b8fc828955 | ||
|
|
b43b040512 | ||
|
|
50b7608f9d | ||
|
|
7085e5b629 | ||
|
|
7d5985baf9 | ||
|
|
7c40a424c0 | ||
|
|
baf882ace7 | ||
|
|
ba82f0bef9 | ||
|
|
d9c6a0305b | ||
|
|
d9b1211191 | ||
|
|
99f1e02766 | ||
|
|
242c0db26b | ||
|
|
dd49e41c42 | ||
|
|
6db15e8693 | ||
|
|
4c9cab3f8a | ||
|
|
b2c0413a98 | ||
|
|
e34c7c00b1 | ||
|
|
7c447bbdc7 | ||
|
|
7bf1f2cc84 | ||
|
|
afa2c4e0af | ||
|
|
2ff7db1b36 | ||
|
|
9f0626da64 | ||
|
|
d8cb5c1cf5 | ||
|
|
dca56da0ef | ||
|
|
ec75d16ea8 | ||
|
|
5cae8ea733 | ||
|
|
1ccd8f6a64 | ||
|
|
9c293bb82b | ||
|
|
9897ee9591 | ||
|
|
5215415315 | ||
|
|
54891d11e0 | ||
|
|
567c8303bf | ||
|
|
2a60edcd52 | ||
|
|
d61ac32d7b | ||
|
|
b57e6cff7e | ||
|
|
5b99f19dac | ||
|
|
6c03571887 | ||
|
|
4fb410a93c | ||
|
|
5e1db9fdd3 | ||
|
|
9d7480ae3c | ||
|
|
f39cf6d855 | ||
|
|
001d116884 | ||
|
|
02c5e62efe | ||
|
|
446df07b62 | ||
|
|
8583b150c9 | ||
|
|
a859aa72ee | ||
|
|
0896036266 | ||
|
|
311b78ae82 | ||
|
|
f5cf4fc8fb | ||
|
|
7ceb58b2aa | ||
|
|
293dd76af1 | ||
|
|
3918c45ced | ||
|
|
4ec403347c | ||
|
|
e01266ffcb | ||
|
|
f246fb2fc2 | ||
|
|
f7b26b34cb | ||
|
|
a1bcdc225e | ||
|
|
7771241cc0 | ||
|
|
6e3af646b2 | ||
|
|
82bf8c138d | ||
|
|
e21b001116 | ||
|
|
577024f1e9 | ||
|
|
d4ad4a25db |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -8,3 +8,4 @@ doc/tags
|
|||||||
vendor
|
vendor
|
||||||
gopath
|
gopath
|
||||||
*.zwc
|
*.zwc
|
||||||
|
fzf
|
||||||
|
|||||||
41
.travis.yml
41
.travis.yml
@@ -1,27 +1,22 @@
|
|||||||
language: go
|
language: go
|
||||||
dist: xenial
|
env: GO111MODULE=on
|
||||||
|
os:
|
||||||
|
- linux
|
||||||
|
- osx
|
||||||
|
dist: bionic # For fish >= 2.3.0 string builtin
|
||||||
addons:
|
addons:
|
||||||
apt:
|
apt:
|
||||||
sources:
|
|
||||||
- sourceline: "ppa:pi-rho/dev"
|
|
||||||
- sourceline: "ppa:fish-shell/release-2"
|
|
||||||
packages:
|
packages:
|
||||||
- tmux
|
- fish
|
||||||
- zsh
|
- zsh
|
||||||
- fish
|
homebrew:
|
||||||
|
packages:
|
||||||
env:
|
- fish
|
||||||
- GO111MODULE=on
|
- tmux
|
||||||
|
update: true
|
||||||
jobs:
|
script:
|
||||||
include:
|
- make test
|
||||||
- stage: unittest
|
# LC_ALL=C to avoid escape codes in
|
||||||
go: "1.13.x"
|
# printf %q $'\355\205\214\354\212\244\355\212\270' on macOS. Bash on
|
||||||
script: make && make test
|
# macOS is built without HANDLE_MULTIBYTE?
|
||||||
|
- make install && ./install --all && LC_ALL=C tmux new-session -d && ruby test/test_go.rb --verbose
|
||||||
- stage: cli
|
|
||||||
go: "1.13.x"
|
|
||||||
rvm: "2.5"
|
|
||||||
script: |
|
|
||||||
make install && ./install --all && tmux new "ruby test/test_go.rb > out && touch ok" && cat out && [ -e ok ]
|
|
||||||
|
|
||||||
|
|||||||
36
CHANGELOG.md
36
CHANGELOG.md
@@ -1,6 +1,42 @@
|
|||||||
CHANGELOG
|
CHANGELOG
|
||||||
=========
|
=========
|
||||||
|
|
||||||
|
0.21.0
|
||||||
|
------
|
||||||
|
- `--height` option is now available on Windows as well (@kelleyma49)
|
||||||
|
- Added `--pointer` and `--marker` options
|
||||||
|
- Added `--keep-right` option that keeps the right end of the line visible
|
||||||
|
when it's too long
|
||||||
|
- Style changes
|
||||||
|
- `--border` will now print border with rounded corners around the
|
||||||
|
finder instead of printing horizontal lines above and below it.
|
||||||
|
The previous style is available via `--border=horizontal`
|
||||||
|
- Unicode spinner
|
||||||
|
- More keys and actions for `--bind`
|
||||||
|
- Added PowerShell script for downloading Windows binary
|
||||||
|
- Vim plugin: Built-in floating windows support
|
||||||
|
```vim
|
||||||
|
let g:fzf_layout = { 'window': { 'width': 0.9, 'height': 0.6 } }
|
||||||
|
```
|
||||||
|
- bash: Various improvements in key bindings (CTRL-T, CTRL-R, ALT-C)
|
||||||
|
- CTRL-R will start with the current command-line as the initial query
|
||||||
|
- CTRL-R properly supports multi-line commands
|
||||||
|
- Fuzzy completion API changed
|
||||||
|
```sh
|
||||||
|
# Previous: fzf arguments given as a single string argument
|
||||||
|
# - This style is still supported, but it's deprecated
|
||||||
|
_fzf_complete "--multi --reverse --prompt=\"doge> \"" "$@" < <(
|
||||||
|
echo foo
|
||||||
|
)
|
||||||
|
|
||||||
|
# New API: multiple fzf arguments before "--"
|
||||||
|
# - Easier to write multiple options
|
||||||
|
_fzf_complete --multi --reverse --prompt="doge> " -- "$@" < <(
|
||||||
|
echo foo
|
||||||
|
)
|
||||||
|
```
|
||||||
|
- Bug fixes and improvements
|
||||||
|
|
||||||
0.20.0
|
0.20.0
|
||||||
------
|
------
|
||||||
- Customizable preview window color (`preview-fg` and `preview-bg` for `--color`)
|
- Customizable preview window color (`preview-fg` and `preview-bg` for `--color`)
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
FROM archlinux/base:latest
|
FROM archlinux/base:latest
|
||||||
RUN pacman -Sy && pacman --noconfirm -S awk git tmux zsh fish ruby procps go make
|
RUN pacman -Sy && pacman --noconfirm -S awk git tmux zsh fish ruby procps go make gcc
|
||||||
RUN gem install --no-document minitest
|
RUN gem install --no-document minitest
|
||||||
RUN echo '. /usr/share/bash-completion/completions/git' >> ~/.bashrc
|
RUN echo '. /usr/share/bash-completion/completions/git' >> ~/.bashrc
|
||||||
RUN echo '. ~/.bashrc' >> ~/.bash_profile
|
RUN echo '. ~/.bashrc' >> ~/.bash_profile
|
||||||
|
|||||||
2
LICENSE
2
LICENSE
@@ -1,6 +1,6 @@
|
|||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2016 Junegunn Choi
|
Copyright (c) 2013-2020 Junegunn Choi
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
|||||||
140
README-VIM.md
140
README-VIM.md
@@ -1,6 +1,50 @@
|
|||||||
FZF Vim integration
|
FZF Vim integration
|
||||||
===================
|
===================
|
||||||
|
|
||||||
|
Installation
|
||||||
|
------------
|
||||||
|
|
||||||
|
Once you have fzf installed, you can enable it inside Vim simply by adding the
|
||||||
|
directory to `&runtimepath` in your Vim configuration file. The path may
|
||||||
|
differ depending on the package manager.
|
||||||
|
|
||||||
|
```vim
|
||||||
|
" If installed using Homebrew
|
||||||
|
set rtp+=/usr/local/opt/fzf
|
||||||
|
|
||||||
|
" If installed using git
|
||||||
|
set rtp+=~/.fzf
|
||||||
|
```
|
||||||
|
|
||||||
|
If you use [vim-plug](https://github.com/junegunn/vim-plug), the same can be
|
||||||
|
written as:
|
||||||
|
|
||||||
|
```vim
|
||||||
|
" If installed using Homebrew
|
||||||
|
Plug '/usr/local/opt/fzf'
|
||||||
|
|
||||||
|
" If installed using git
|
||||||
|
Plug '~/.fzf'
|
||||||
|
```
|
||||||
|
|
||||||
|
But if you want the latest Vim plugin file from GitHub rather than the one
|
||||||
|
included in the package, write:
|
||||||
|
|
||||||
|
```vim
|
||||||
|
Plug 'junegunn/fzf'
|
||||||
|
```
|
||||||
|
|
||||||
|
The Vim plugin will pick up fzf binary available on the system. If fzf is not
|
||||||
|
found on `$PATH`, it will ask you if it should download the latest binary for
|
||||||
|
you.
|
||||||
|
|
||||||
|
To make sure that you have the latest version of the binary, set up
|
||||||
|
post-update hook like so:
|
||||||
|
|
||||||
|
```vim
|
||||||
|
Plug 'junegunn/fzf', { 'do': { -> fzf#install() } }
|
||||||
|
```
|
||||||
|
|
||||||
Summary
|
Summary
|
||||||
-------
|
-------
|
||||||
|
|
||||||
@@ -115,6 +159,53 @@ let g:fzf_colors =
|
|||||||
let g:fzf_history_dir = '~/.local/share/fzf-history'
|
let g:fzf_history_dir = '~/.local/share/fzf-history'
|
||||||
```
|
```
|
||||||
|
|
||||||
|
##### Explanation of `g:fzf_colors`
|
||||||
|
|
||||||
|
`g:fzf_colors` is a dictionary mapping fzf elements to a color specification
|
||||||
|
list:
|
||||||
|
|
||||||
|
element: [ component, group1 [, group2, ...] ]
|
||||||
|
|
||||||
|
- `element` is an fzf element to apply a color to:
|
||||||
|
|
||||||
|
| Element | Description |
|
||||||
|
| --- | --- |
|
||||||
|
| `fg` / `bg` / `hl` | Item (foreground / background / highlight) |
|
||||||
|
| `fg+` / `bg+` / `hl+` | Current item (foreground / background / highlight) |
|
||||||
|
| `hl` / `hl+` | Highlighted substrings (normal / current) |
|
||||||
|
| `gutter` | Background of the gutter on the left |
|
||||||
|
| `pointer` | Pointer to the current line (`>`) |
|
||||||
|
| `marker` | Multi-select marker (`>`) |
|
||||||
|
| `border` | Border around the window (`--border` and `--preview`) |
|
||||||
|
| `header` | Header (`--header` or `--header-lines`) |
|
||||||
|
| `info` | Info line (match counters) |
|
||||||
|
| `spinner` | Streaming input indicator |
|
||||||
|
| `prompt` | Prompt before query (`> `) |
|
||||||
|
|
||||||
|
- `component` specifies the component (`fg` / `bg`) from which to extract the
|
||||||
|
color when considering each of the following highlight groups
|
||||||
|
|
||||||
|
- `group1 [, group2, ...]` is a list of highlight groups that are searched (in
|
||||||
|
order) for a matching color definition
|
||||||
|
|
||||||
|
For example, consider the following specification:
|
||||||
|
|
||||||
|
```vim
|
||||||
|
'prompt': ['fg', 'Conditional', 'Comment'],
|
||||||
|
```
|
||||||
|
|
||||||
|
This means we color the **prompt**
|
||||||
|
- using the `fg` attribute of the `Conditional` if it exists,
|
||||||
|
- otherwise use the `fg` attribute of the `Comment` highlight group if it exists,
|
||||||
|
- otherwise fall back to the default color settings for the **prompt**.
|
||||||
|
|
||||||
|
You can examine the color option generated according the setting by printing
|
||||||
|
the result of `fzf#wrap()` function like so:
|
||||||
|
|
||||||
|
```vim
|
||||||
|
:echo fzf#wrap()
|
||||||
|
```
|
||||||
|
|
||||||
`fzf#run`
|
`fzf#run`
|
||||||
---------
|
---------
|
||||||
|
|
||||||
@@ -184,6 +275,7 @@ The following table summarizes the available options.
|
|||||||
| `dir` | string | Working directory |
|
| `dir` | string | Working directory |
|
||||||
| `up`/`down`/`left`/`right` | number/string | (Layout) Window position and size (e.g. `20`, `50%`) |
|
| `up`/`down`/`left`/`right` | number/string | (Layout) Window position and size (e.g. `20`, `50%`) |
|
||||||
| `window` (Vim 8 / Neovim) | string | (Layout) Command to open fzf window (e.g. `vertical aboveleft 30new`) |
|
| `window` (Vim 8 / Neovim) | string | (Layout) Command to open fzf window (e.g. `vertical aboveleft 30new`) |
|
||||||
|
| `window` (Vim 8 / Neovim) | dict | (Layout) Popup window settings (e.g. `{'width': 0.9, 'height': 0.6}`) |
|
||||||
|
|
||||||
`options` entry can be either a string or a list. For simple cases, string
|
`options` entry can be either a string or a list. For simple cases, string
|
||||||
should suffice, but prefer to use list type to avoid escaping issues.
|
should suffice, but prefer to use list type to avoid escaping issues.
|
||||||
@@ -193,6 +285,19 @@ call fzf#run({'options': '--reverse --prompt "C:\\Program Files\\"'})
|
|||||||
call fzf#run({'options': ['--reverse', '--prompt', 'C:\Program Files\']})
|
call fzf#run({'options': ['--reverse', '--prompt', 'C:\Program Files\']})
|
||||||
```
|
```
|
||||||
|
|
||||||
|
When `window` entry is a dictionary, fzf will start in a popup window. The
|
||||||
|
following options are allowed:
|
||||||
|
|
||||||
|
- Required:
|
||||||
|
- `width` [float range [0 ~ 1]]
|
||||||
|
- `height` [float range [0 ~ 1]]
|
||||||
|
- Optional:
|
||||||
|
- `yoffset` [float default 0.5 range [0 ~ 1]]
|
||||||
|
- `xoffset` [float default 0.5 range [0 ~ 1]]
|
||||||
|
- `highlight` [string default `'Comment'`]: Highlight group for border
|
||||||
|
- `border` [string default `rounded`]: Border style
|
||||||
|
- `rounded` / `sharp` / `horizontal` / `vertical` / `top` / `bottom` / `left` / `right`
|
||||||
|
|
||||||
`fzf#wrap`
|
`fzf#wrap`
|
||||||
----------
|
----------
|
||||||
|
|
||||||
@@ -276,29 +381,20 @@ The latest versions of Vim and Neovim include builtin terminal emulator
|
|||||||
- On Terminal Vim with a non-default layout
|
- On Terminal Vim with a non-default layout
|
||||||
- `call fzf#run({'left': '30%'})` or `let g:fzf_layout = {'left': '30%'}`
|
- `call fzf#run({'left': '30%'})` or `let g:fzf_layout = {'left': '30%'}`
|
||||||
|
|
||||||
#### Starting fzf in Neovim floating window
|
#### Starting fzf in a popup window
|
||||||
|
|
||||||
```vim
|
```vim
|
||||||
" Using floating windows of Neovim to start fzf
|
" Required:
|
||||||
if has('nvim')
|
" - width [float range [0 ~ 1]]
|
||||||
let $FZF_DEFAULT_OPTS .= ' --border --margin=0,2'
|
" - height [float range [0 ~ 1]]
|
||||||
|
"
|
||||||
function! FloatingFZF()
|
" Optional:
|
||||||
let width = float2nr(&columns * 0.9)
|
" - xoffset [float default 0.5 range [0 ~ 1]]
|
||||||
let height = float2nr(&lines * 0.6)
|
" - yoffset [float default 0.5 range [0 ~ 1]]
|
||||||
let opts = { 'relative': 'editor',
|
" - highlight [string default 'Comment']: Highlight group for border
|
||||||
\ 'row': (&lines - height) / 2,
|
" - border [string default 'rounded']: Border style
|
||||||
\ 'col': (&columns - width) / 2,
|
" - 'rounded' / 'sharp' / 'horizontal' / 'vertical' / 'top' / 'bottom' / 'left' / 'right'
|
||||||
\ 'width': width,
|
let g:fzf_layout = { 'window': { 'width': 0.9, 'height': 0.6 } }
|
||||||
\ 'height': height }
|
|
||||||
|
|
||||||
let win = nvim_open_win(nvim_create_buf(v:false, v:true), v:true, opts)
|
|
||||||
call setwinvar(win, '&winhighlight', 'NormalFloat:Normal')
|
|
||||||
endfunction
|
|
||||||
|
|
||||||
let g:fzf_layout = { 'window': 'call FloatingFZF()' }
|
|
||||||
endif
|
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Hide statusline
|
#### Hide statusline
|
||||||
@@ -323,4 +419,4 @@ endif
|
|||||||
|
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2019 Junegunn Choi
|
Copyright (c) 2013-2020 Junegunn Choi
|
||||||
|
|||||||
114
README.md
114
README.md
@@ -47,6 +47,7 @@ Table of Contents
|
|||||||
* [Environment variables / Aliases](#environment-variables--aliases)
|
* [Environment variables / Aliases](#environment-variables--aliases)
|
||||||
* [Settings](#settings)
|
* [Settings](#settings)
|
||||||
* [Supported commands](#supported-commands)
|
* [Supported commands](#supported-commands)
|
||||||
|
* [Custom fuzzy completion](#custom-fuzzy-completion)
|
||||||
* [Vim plugin](#vim-plugin)
|
* [Vim plugin](#vim-plugin)
|
||||||
* [Advanced topics](#advanced-topics)
|
* [Advanced topics](#advanced-topics)
|
||||||
* [Performance](#performance)
|
* [Performance](#performance)
|
||||||
@@ -112,6 +113,7 @@ git clone --depth 1 https://github.com/junegunn/fzf.git ~/.fzf
|
|||||||
| FreeBSD | `pkg install fzf` |
|
| FreeBSD | `pkg install fzf` |
|
||||||
| NixOS | `nix-env -iA nixpkgs.fzf` |
|
| NixOS | `nix-env -iA nixpkgs.fzf` |
|
||||||
| openSUSE | `sudo zypper install fzf` |
|
| openSUSE | `sudo zypper install fzf` |
|
||||||
|
| OpenBSD | `pkg_add fzf` |
|
||||||
|
|
||||||
Shell extensions (key bindings and fuzzy auto-completion) and Vim/Neovim
|
Shell extensions (key bindings and fuzzy auto-completion) and Vim/Neovim
|
||||||
plugin may or may not be enabled by default depending on the package manager.
|
plugin may or may not be enabled by default depending on the package manager.
|
||||||
@@ -137,39 +139,18 @@ page][windows-wiki].
|
|||||||
|
|
||||||
### As Vim plugin
|
### As Vim plugin
|
||||||
|
|
||||||
Once you have fzf installed, you can enable it inside Vim simply by adding the
|
If you use
|
||||||
directory to `&runtimepath` in your Vim configuration file. The path may
|
[vim-plug](https://github.com/junegunn/vim-plug), add this line to your Vim
|
||||||
differ depending on the package manager.
|
configuration file:
|
||||||
|
|
||||||
```vim
|
```vim
|
||||||
" If installed using Homebrew
|
Plug 'junegunn/fzf', { 'do': { -> fzf#install() } }
|
||||||
set rtp+=/usr/local/opt/fzf
|
|
||||||
|
|
||||||
" If installed using git
|
|
||||||
set rtp+=~/.fzf
|
|
||||||
```
|
```
|
||||||
|
|
||||||
If you use [vim-plug](https://github.com/junegunn/vim-plug), the same can be
|
`fzf#install()` makes sure that you have the latest binary, but it's optional,
|
||||||
written as:
|
so you can omit it if you use a plugin manager that doesn't support hooks.
|
||||||
|
|
||||||
```vim
|
For more installation options, see [README-VIM.md](README-VIM.md).
|
||||||
" If installed using Homebrew
|
|
||||||
Plug '/usr/local/opt/fzf'
|
|
||||||
|
|
||||||
" If installed using git
|
|
||||||
Plug '~/.fzf'
|
|
||||||
```
|
|
||||||
|
|
||||||
But instead of separately installing fzf on your system (using Homebrew or
|
|
||||||
"git clone") and enabling it on Vim (adding it to `&runtimepath`), you can use
|
|
||||||
vim-plug to do both.
|
|
||||||
|
|
||||||
```vim
|
|
||||||
" PlugInstall and PlugUpdate will clone fzf in ~/.fzf and run the install script
|
|
||||||
Plug 'junegunn/fzf', { 'dir': '~/.fzf', 'do': './install --all' }
|
|
||||||
" Both options are optional. You don't have to install fzf in ~/.fzf
|
|
||||||
" and you don't have to run the install script if you use fzf only in Vim.
|
|
||||||
```
|
|
||||||
|
|
||||||
Upgrading fzf
|
Upgrading fzf
|
||||||
-------------
|
-------------
|
||||||
@@ -180,6 +161,7 @@ method used.
|
|||||||
|
|
||||||
- git: `cd ~/.fzf && git pull && ./install`
|
- git: `cd ~/.fzf && git pull && ./install`
|
||||||
- brew: `brew update; brew reinstall fzf`
|
- brew: `brew update; brew reinstall fzf`
|
||||||
|
- macports: `sudo port upgrade fzf`
|
||||||
- chocolatey: `choco upgrade fzf`
|
- chocolatey: `choco upgrade fzf`
|
||||||
- vim-plug: `:PlugUpdate fzf`
|
- vim-plug: `:PlugUpdate fzf`
|
||||||
|
|
||||||
@@ -340,10 +322,6 @@ If you're on a tmux session, you can start fzf in a split pane by setting
|
|||||||
`FZF_TMUX` to 1, and change the height of the pane with `FZF_TMUX_HEIGHT`
|
`FZF_TMUX` to 1, and change the height of the pane with `FZF_TMUX_HEIGHT`
|
||||||
(e.g. `20`, `50%`).
|
(e.g. `20`, `50%`).
|
||||||
|
|
||||||
If you use vi mode on bash, you need to add `set -o vi` *before* `source
|
|
||||||
~/.fzf.bash` in your .bashrc, so that it correctly sets up key bindings for vi
|
|
||||||
mode.
|
|
||||||
|
|
||||||
More tips can be found on [the wiki page](https://github.com/junegunn/fzf/wiki/Configuring-shell-key-bindings).
|
More tips can be found on [the wiki page](https://github.com/junegunn/fzf/wiki/Configuring-shell-key-bindings).
|
||||||
|
|
||||||
Fuzzy completion for bash and zsh
|
Fuzzy completion for bash and zsh
|
||||||
@@ -427,6 +405,21 @@ _fzf_compgen_path() {
|
|||||||
_fzf_compgen_dir() {
|
_fzf_compgen_dir() {
|
||||||
fd --type d --hidden --follow --exclude ".git" . "$1"
|
fd --type d --hidden --follow --exclude ".git" . "$1"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# (EXPERIMENTAL) Advanced customization of fzf options via _fzf_comprun function
|
||||||
|
# - The first argument to the function is the name of the command.
|
||||||
|
# - You should make sure to pass the rest of the arguments to fzf.
|
||||||
|
_fzf_comprun() {
|
||||||
|
local command=$1
|
||||||
|
shift
|
||||||
|
|
||||||
|
case "$command" in
|
||||||
|
cd) fzf "$@" --preview 'tree -C {} | head -200' ;;
|
||||||
|
export|unset) fzf "$@" --preview "eval 'echo \$'{}" ;;
|
||||||
|
ssh) fzf "$@" --preview 'dig {}' ;;
|
||||||
|
*) fzf "$@" ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Supported commands
|
#### Supported commands
|
||||||
@@ -436,11 +429,61 @@ On bash, fuzzy completion is enabled only for a predefined set of commands
|
|||||||
commands as well by using `_fzf_setup_completion` helper function.
|
commands as well by using `_fzf_setup_completion` helper function.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
# usage: _fzf_setup_completion path|dir COMMANDS...
|
# usage: _fzf_setup_completion path|dir|var|alias|host COMMANDS...
|
||||||
_fzf_setup_completion path ag git kubectl
|
_fzf_setup_completion path ag git kubectl
|
||||||
_fzf_setup_completion dir tree
|
_fzf_setup_completion dir tree
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### Custom fuzzy completion
|
||||||
|
|
||||||
|
_**(Custom completion API is experimental and subject to change)**_
|
||||||
|
|
||||||
|
For a command named _"COMMAND"_, define `_fzf_complete_COMMAND` function using
|
||||||
|
`_fzf_complete` helper.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# Custom fuzzy completion for "doge" command
|
||||||
|
# e.g. doge **<TAB>
|
||||||
|
_fzf_complete_doge() {
|
||||||
|
_fzf_complete --multi --reverse --prompt="doge> " -- "$@" < <(
|
||||||
|
echo very
|
||||||
|
echo wow
|
||||||
|
echo such
|
||||||
|
echo doge
|
||||||
|
)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- The arguments before `--` are the options to fzf.
|
||||||
|
- After `--`, simply pass the original completion arguments unchanged (`"$@"`).
|
||||||
|
- Then write a set of commands that generates the completion candidates and
|
||||||
|
feed its output to the function using process substitution (`< <(...)`).
|
||||||
|
|
||||||
|
zsh will automatically pick up the function using the naming convention but in
|
||||||
|
bash you have to manually associate the function with the command using
|
||||||
|
`complete` command.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
[ -n "$BASH" ] && complete -F _fzf_complete_doge -o default -o bashdefault doge
|
||||||
|
```
|
||||||
|
|
||||||
|
If you need to post-process the output from fzf, define
|
||||||
|
`_fzf_complete_COMMAND_post` as follows.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
_fzf_complete_foo() {
|
||||||
|
_fzf_complete --multi --reverse --header-lines=3 -- "$@" < <(
|
||||||
|
ls -al
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
_fzf_complete_foo_post() {
|
||||||
|
awk '{print $NF}'
|
||||||
|
}
|
||||||
|
|
||||||
|
[ -n "$BASH" ] && complete -F _fzf_complete_foo -o default -o bashdefault foo
|
||||||
|
```
|
||||||
|
|
||||||
Vim plugin
|
Vim plugin
|
||||||
----------
|
----------
|
||||||
|
|
||||||
@@ -484,7 +527,8 @@ See *KEY BINDINGS* section of the man page for details.
|
|||||||
### Preview window
|
### Preview window
|
||||||
|
|
||||||
When `--preview` option is set, fzf automatically starts an external process with
|
When `--preview` option is set, fzf automatically starts an external process with
|
||||||
the current line as the argument and shows the result in the split window.
|
the current line as the argument and shows the result in the split window. Your
|
||||||
|
`$SHELL` is used to execute the command with `$SHELL -c COMMAND`.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# {} is replaced to the single-quoted string of the focused line
|
# {} is replaced to the single-quoted string of the focused line
|
||||||
@@ -605,4 +649,4 @@ https://github.com/junegunn/fzf/wiki/Related-projects
|
|||||||
|
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2019 Junegunn Choi
|
Copyright (c) 2013-2020 Junegunn Choi
|
||||||
|
|||||||
55
doc/fzf.txt
55
doc/fzf.txt
@@ -1,4 +1,4 @@
|
|||||||
fzf.txt fzf Last change: November 23 2019
|
fzf.txt fzf Last change: February 14 2020
|
||||||
FZF - TABLE OF CONTENTS *fzf* *fzf-toc*
|
FZF - TABLE OF CONTENTS *fzf* *fzf-toc*
|
||||||
==============================================================================
|
==============================================================================
|
||||||
|
|
||||||
@@ -11,7 +11,7 @@ FZF - TABLE OF CONTENTS *fzf* *fzf-to
|
|||||||
fzf#wrap
|
fzf#wrap
|
||||||
Tips
|
Tips
|
||||||
fzf inside terminal buffer
|
fzf inside terminal buffer
|
||||||
Starting fzf in Neovim floating window
|
Starting fzf in a popup window
|
||||||
Hide statusline
|
Hide statusline
|
||||||
License
|
License
|
||||||
|
|
||||||
@@ -204,6 +204,7 @@ The following table summarizes the available options.
|
|||||||
`dir` | string | Working directory
|
`dir` | string | Working directory
|
||||||
`up` / `down` / `left` / `right` | number/string | (Layout) Window position and size (e.g. `20` , `50%` )
|
`up` / `down` / `left` / `right` | number/string | (Layout) Window position and size (e.g. `20` , `50%` )
|
||||||
`window` (Vim 8 / Neovim) | string | (Layout) Command to open fzf window (e.g. `vertical aboveleft 30new` )
|
`window` (Vim 8 / Neovim) | string | (Layout) Command to open fzf window (e.g. `vertical aboveleft 30new` )
|
||||||
|
`window` (Vim 8 / Neovim) | dict | (Layout) Popup window settings (e.g. `{'width': 0.9, 'height': 0.6}` )
|
||||||
---------------------------+---------------+----------------------------------------------------------------------
|
---------------------------+---------------+----------------------------------------------------------------------
|
||||||
|
|
||||||
`options` entry can be either a string or a list. For simple cases, string
|
`options` entry can be either a string or a list. For simple cases, string
|
||||||
@@ -212,6 +213,19 @@ should suffice, but prefer to use list type to avoid escaping issues.
|
|||||||
call fzf#run({'options': '--reverse --prompt "C:\\Program Files\\"'})
|
call fzf#run({'options': '--reverse --prompt "C:\\Program Files\\"'})
|
||||||
call fzf#run({'options': ['--reverse', '--prompt', 'C:\Program Files\']})
|
call fzf#run({'options': ['--reverse', '--prompt', 'C:\Program Files\']})
|
||||||
<
|
<
|
||||||
|
When `window` entry is a dictionary, fzf will start in a popup window. The
|
||||||
|
following options are allowed:
|
||||||
|
|
||||||
|
- Required:
|
||||||
|
- `width` [float range [0 ~ 1]]
|
||||||
|
- `height` [float range [0 ~ 1]]
|
||||||
|
- Optional:
|
||||||
|
- `yoffset` [float default 0.5 range [0 ~ 1]]
|
||||||
|
- `xoffset` [float default 0.5 range [0 ~ 1]]
|
||||||
|
- `highlight` [string default `'Comment'`]: Highlight group for border
|
||||||
|
- `border` [string default `rounded`]: Border style
|
||||||
|
- `rounded` / `sharp` / `horizontal` / `vertical` / `top` / `bottom` / `left` / `right`
|
||||||
|
|
||||||
|
|
||||||
FZF#WRAP
|
FZF#WRAP
|
||||||
==============================================================================
|
==============================================================================
|
||||||
@@ -291,29 +305,20 @@ The latest versions of Vim and Neovim include builtin terminal emulator
|
|||||||
- `call fzf#run({'left': '30%'})` or `let g:fzf_layout = {'left': '30%'}`
|
- `call fzf#run({'left': '30%'})` or `let g:fzf_layout = {'left': '30%'}`
|
||||||
|
|
||||||
|
|
||||||
Starting fzf in Neovim floating window~
|
Starting fzf in a popup window~
|
||||||
*fzf-starting-fzf-in-neovim-floating-window*
|
*fzf-starting-fzf-in-a-popup-window*
|
||||||
>
|
>
|
||||||
" Using floating windows of Neovim to start fzf
|
" Required:
|
||||||
if has('nvim')
|
" - width [float range [0 ~ 1]]
|
||||||
let $FZF_DEFAULT_OPTS .= ' --border --margin=0,2'
|
" - height [float range [0 ~ 1]]
|
||||||
|
"
|
||||||
function! FloatingFZF()
|
" Optional:
|
||||||
let width = float2nr(&columns * 0.9)
|
" - xoffset [float default 0.5 range [0 ~ 1]]
|
||||||
let height = float2nr(&lines * 0.6)
|
" - yoffset [float default 0.5 range [0 ~ 1]]
|
||||||
let opts = { 'relative': 'editor',
|
" - highlight [string default 'Comment']: Highlight group for border
|
||||||
\ 'row': (&lines - height) / 2,
|
" - border [string default 'rounded']: Border style
|
||||||
\ 'col': (&columns - width) / 2,
|
" - 'rounded' / 'sharp' / 'horizontal' / 'vertical' / 'top' / 'bottom' / 'left' / 'right'
|
||||||
\ 'width': width,
|
let g:fzf_layout = { 'window': { 'width': 0.9, 'height': 0.6 } }
|
||||||
\ 'height': height }
|
|
||||||
|
|
||||||
let win = nvim_open_win(nvim_create_buf(v:false, v:true), v:true, opts)
|
|
||||||
call setwinvar(win, '&winhighlight', 'NormalFloat:Normal')
|
|
||||||
endfunction
|
|
||||||
|
|
||||||
let g:fzf_layout = { 'window': 'call FloatingFZF()' }
|
|
||||||
endif
|
|
||||||
|
|
||||||
<
|
<
|
||||||
|
|
||||||
Hide statusline~
|
Hide statusline~
|
||||||
@@ -338,7 +343,7 @@ LICENSE *fzf-license*
|
|||||||
|
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2019 Junegunn Choi
|
Copyright (c) 2013-2020 Junegunn Choi
|
||||||
|
|
||||||
==============================================================================
|
==============================================================================
|
||||||
vim:tw=78:sw=2:ts=2:ft=help:norl:nowrap:
|
vim:tw=78:sw=2:ts=2:ft=help:norl:nowrap:
|
||||||
|
|||||||
22
go.mod
22
go.mod
@@ -1,19 +1,15 @@
|
|||||||
module github.com/junegunn/fzf
|
module github.com/junegunn/fzf
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/gdamore/encoding v0.0.0-20151215212835-b23993cbb635 // indirect
|
github.com/gdamore/tcell v1.3.0
|
||||||
github.com/gdamore/tcell v0.0.0-20170915061752-0a0db94084df
|
github.com/lucasb-eyer/go-colorful v1.0.3 // indirect
|
||||||
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e // indirect
|
github.com/mattn/go-isatty v0.0.12
|
||||||
github.com/jtolds/gls v4.2.1+incompatible // indirect
|
github.com/mattn/go-runewidth v0.0.8
|
||||||
github.com/lucasb-eyer/go-colorful v0.0.0-20170223221042-c900de9dbbc7 // indirect
|
github.com/mattn/go-shellwords v1.0.9
|
||||||
github.com/mattn/go-isatty v0.0.0-20160806122752-66b8e73f3f5c
|
github.com/saracen/walker v0.0.0-20191201085201-324a081bae7e
|
||||||
github.com/mattn/go-runewidth v0.0.0-20170201023540-14207d285c6c
|
golang.org/x/crypto v0.0.0-20200128174031-69ecbb4d6d5d
|
||||||
github.com/mattn/go-shellwords v1.0.3
|
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5
|
||||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d // indirect
|
golang.org/x/text v0.3.2 // indirect
|
||||||
github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c // indirect
|
|
||||||
golang.org/x/crypto v0.0.0-20170728183002-558b6879de74
|
|
||||||
golang.org/x/sys v0.0.0-20190509141414-a5b02f93d862 // indirect
|
|
||||||
golang.org/x/text v0.0.0-20170530162606-4ee4af566555 // indirect
|
|
||||||
)
|
)
|
||||||
|
|
||||||
go 1.13
|
go 1.13
|
||||||
|
|||||||
70
go.sum
70
go.sum
@@ -1,26 +1,44 @@
|
|||||||
github.com/gdamore/encoding v0.0.0-20151215212835-b23993cbb635 h1:hheUEMzaOie/wKeIc1WPa7CDVuIO5hqQxjS+dwTQEnI=
|
github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
|
||||||
github.com/gdamore/encoding v0.0.0-20151215212835-b23993cbb635/go.mod h1:yrQYJKKDTrHmbYxI7CYi+/hbdiDT2m4Hj+t0ikCjsrQ=
|
github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko=
|
||||||
github.com/gdamore/tcell v0.0.0-20170915061752-0a0db94084df h1:tLS1QD2puA1USLvkEnGfOt+Zp2ijtNIK3z2YFaIZZR4=
|
github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
|
||||||
github.com/gdamore/tcell v0.0.0-20170915061752-0a0db94084df/go.mod h1:tqyG50u7+Ctv1w5VX67kLzKcj9YXR/JSBZQq/+mLl1A=
|
github.com/gdamore/tcell v1.3.0 h1:r35w0JBADPZCVQijYebl6YMWWtHRqVEGt7kL2eBADRM=
|
||||||
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e h1:JKmoR8x90Iww1ks85zJ1lfDGgIiMDuIptTOhJq+zKyg=
|
github.com/gdamore/tcell v1.3.0/go.mod h1:Hjvr+Ofd+gLglo7RYKxxnzCBmev3BzsS67MebKS4zMM=
|
||||||
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
github.com/lucasb-eyer/go-colorful v1.0.2 h1:mCMFu6PgSozg9tDNMMK3g18oJBX7oYGrC09mS6CXfO4=
|
||||||
github.com/jtolds/gls v4.2.1+incompatible h1:fSuqC+Gmlu6l/ZYAoZzx2pyucC8Xza35fpRVWLVmUEE=
|
github.com/lucasb-eyer/go-colorful v1.0.2/go.mod h1:0MS4r+7BZKSJ5mw4/S5MPN+qHFF1fYclkSPilDOKW0s=
|
||||||
github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
github.com/lucasb-eyer/go-colorful v1.0.3 h1:QIbQXiugsb+q10B+MI+7DI1oQLdmnep86tWFlaaUAac=
|
||||||
github.com/lucasb-eyer/go-colorful v0.0.0-20170223221042-c900de9dbbc7 h1:G52I+Gk/wPD4HKvKT0Vxxp9OUPxqKs3OK6rffSPtNkA=
|
github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||||
github.com/lucasb-eyer/go-colorful v0.0.0-20170223221042-c900de9dbbc7/go.mod h1:NXg0ArsFk0Y01623LgUqoqcouGDB+PwCCQlrwrG6xJ4=
|
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
|
||||||
github.com/mattn/go-isatty v0.0.0-20160806122752-66b8e73f3f5c h1:3nKFouDdpgGUV/uerJcYWH45ZbJzX0SiVWfTgmUeTzc=
|
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||||
github.com/mattn/go-isatty v0.0.0-20160806122752-66b8e73f3f5c/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/p7Y=
|
||||||
github.com/mattn/go-runewidth v0.0.0-20170201023540-14207d285c6c h1:eFzthqtg3W6Pihj3DMTXLAF4f+ge5r5Ie5g6HLIZAF0=
|
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||||
github.com/mattn/go-runewidth v0.0.0-20170201023540-14207d285c6c/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
github.com/mattn/go-runewidth v0.0.8 h1:3tS41NlGYSmhhe/8fhGRzc+z3AYCw1Fe1WAyLuujKs0=
|
||||||
github.com/mattn/go-shellwords v1.0.3 h1:K/VxK7SZ+cvuPgFSLKi5QPI9Vr/ipOf4C1gN+ntueUk=
|
github.com/mattn/go-runewidth v0.0.8/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||||
github.com/mattn/go-shellwords v1.0.3/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o=
|
github.com/mattn/go-shellwords v1.0.9 h1:eaB5JspOwiKKcHdqcjbfe5lA9cNn/4NRRtddXJCimqk=
|
||||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
|
github.com/mattn/go-shellwords v1.0.9/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
|
||||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
github.com/saracen/walker v0.0.0-20191201085201-324a081bae7e h1:NO86zOn5ScSKW8wRbMaSIcjDZUFpWdCQQnexRqZ9h9A=
|
||||||
github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c h1:Ho+uVpkel/udgjbwB5Lktg9BtvJSh2DT0Hi6LPSyI2w=
|
github.com/saracen/walker v0.0.0-20191201085201-324a081bae7e/go.mod h1:G0Z6yVPru183i2MuRJx1DcR4dgIZtLcTdaaE/pC1BJU=
|
||||||
github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
|
||||||
golang.org/x/crypto v0.0.0-20170728183002-558b6879de74 h1:/0jf0Cx3u07Ma4EzUA6NIGuvk9hb3Br6i9V8atthkwk=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20170728183002-558b6879de74/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
golang.org/x/crypto v0.0.0-20200128174031-69ecbb4d6d5d h1:9FCpayM9Egr1baVnV1SX0H87m+XB0B8S0hAMi99X/3U=
|
||||||
golang.org/x/sys v0.0.0-20190509141414-a5b02f93d862 h1:rM0ROo5vb9AdYJi1110yjWGMej9ITfKddS89P3Fkhug=
|
golang.org/x/crypto v0.0.0-20200128174031-69ecbb4d6d5d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/sys v0.0.0-20190509141414-a5b02f93d862/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/text v0.0.0-20170530162606-4ee4af566555 h1:pjwO9HxObpgZBurDvTLFbSinaf3+cKpTAjVfiGaHwrI=
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI=
|
||||||
golang.org/x/text v0.0.0-20170530162606-4ee4af566555/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY=
|
||||||
|
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756 h1:9nuHUbU8dRnRRfj9KjWUVrJeoexdbeMjttk6Oh1rD10=
|
||||||
|
golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5 h1:LfCXLvNmTYH9kEmVgqbnsWfruoXZIrh4YBgqVHtDvw0=
|
||||||
|
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
||||||
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
||||||
|
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20191011211836-4c025a95b26e h1:1o2bDs9pCd2xFhdwqJTrCIswAeEsn4h/PCNelWpfcsI=
|
||||||
|
golang.org/x/tools v0.0.0-20191011211836-4c025a95b26e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
|||||||
2
install
2
install
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
set -u
|
set -u
|
||||||
|
|
||||||
version=0.20.0
|
version=0.21.0
|
||||||
auto_completion=
|
auto_completion=
|
||||||
key_bindings=
|
key_bindings=
|
||||||
update_config=2
|
update_config=2
|
||||||
|
|||||||
73
install.ps1
Normal file
73
install.ps1
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
$version="0.21.0"
|
||||||
|
|
||||||
|
if ([Environment]::Is64BitProcess) {
|
||||||
|
$binary_arch="amd64"
|
||||||
|
} else {
|
||||||
|
$binary_arch="386"
|
||||||
|
}
|
||||||
|
|
||||||
|
$fzf_base=Split-Path -Parent $MyInvocation.MyCommand.Definition
|
||||||
|
|
||||||
|
function check_binary () {
|
||||||
|
Write-Host " - Checking fzf executable ... " -NoNewline
|
||||||
|
$output=cmd /c $fzf_base\bin\fzf.exe --version 2>&1
|
||||||
|
if (-not $?) {
|
||||||
|
Write-Host "Error: $output"
|
||||||
|
$binary_error="Invalid binary"
|
||||||
|
} else {
|
||||||
|
$output=(-Split $output)[0]
|
||||||
|
if ($version -ne $output) {
|
||||||
|
Write-Host "$output != $version"
|
||||||
|
$binary_error="Invalid version"
|
||||||
|
} else {
|
||||||
|
Write-Host "$output"
|
||||||
|
$binary_error=""
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Remove-Item "$fzf_base\bin\fzf.exe"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
function download {
|
||||||
|
param($file)
|
||||||
|
Write-Host "Downloading bin/fzf ..."
|
||||||
|
if ("$version" -ne "alpha") {
|
||||||
|
if (Test-Path "$fzf_base\bin\fzf.exe") {
|
||||||
|
Write-Host " - Already exists"
|
||||||
|
if (check_binary) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (-not (Test-Path "$fzf_base\bin")) {
|
||||||
|
md "$fzf_base\bin"
|
||||||
|
}
|
||||||
|
if (-not $?) {
|
||||||
|
$binary_error="Failed to create bin directory"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
cd "$fzf_base\bin"
|
||||||
|
if ("$version" -eq "alpha") {
|
||||||
|
$url="https://github.com/junegunn/fzf-bin/releases/download/alpha/$file"
|
||||||
|
} else {
|
||||||
|
$url="https://github.com/junegunn/fzf-bin/releases/download/$version/$file"
|
||||||
|
}
|
||||||
|
$temp=$env:TMP + "\fzf.zip"
|
||||||
|
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
|
||||||
|
(New-Object Net.WebClient).DownloadFile($url, $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath("$temp"))
|
||||||
|
if ($?) {
|
||||||
|
(Expand-Archive -Path $temp -DestinationPath .); (Remove-Item $temp)
|
||||||
|
} else {
|
||||||
|
$binary_error="Failed to download with powershell"
|
||||||
|
}
|
||||||
|
if (-not (Test-Path fzf.exe)) {
|
||||||
|
$binary_error="Failed to download $file"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
check_binary >$null
|
||||||
|
}
|
||||||
|
|
||||||
|
download "fzf-$version-windows_$binary_arch.zip"
|
||||||
|
|
||||||
|
Write-Host 'For more information, see: https://github.com/junegunn/fzf'
|
||||||
6
main.go
6
main.go
@@ -1,9 +1,13 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import "github.com/junegunn/fzf/src"
|
import (
|
||||||
|
"github.com/junegunn/fzf/src"
|
||||||
|
"github.com/junegunn/fzf/src/protector"
|
||||||
|
)
|
||||||
|
|
||||||
var revision string
|
var revision string
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
protector.Protect()
|
||||||
fzf.Run(fzf.ParseOptions(), revision)
|
fzf.Run(fzf.ParseOptions(), revision)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
.ig
|
.ig
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2019 Junegunn Choi
|
Copyright (c) 2013-2020 Junegunn Choi
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
@@ -21,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 "Dec 2019" "fzf 0.20.0" "fzf-tmux - open fzf in tmux split pane"
|
.TH fzf-tmux 1 "Mar 2020" "fzf 0.21.0" "fzf-tmux - open fzf in tmux split pane"
|
||||||
|
|
||||||
.SH NAME
|
.SH NAME
|
||||||
fzf-tmux - open fzf in tmux split pane
|
fzf-tmux - open fzf in tmux split pane
|
||||||
|
|||||||
127
man/man1/fzf.1
127
man/man1/fzf.1
@@ -1,7 +1,7 @@
|
|||||||
.ig
|
.ig
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2019 Junegunn Choi
|
Copyright (c) 2013-2020 Junegunn Choi
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
@@ -21,7 +21,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
THE SOFTWARE.
|
THE SOFTWARE.
|
||||||
..
|
..
|
||||||
.TH fzf 1 "Dec 2019" "fzf 0.20.0" "fzf - a command-line fuzzy finder"
|
.TH fzf 1 "Mar 2020" "fzf 0.21.0" "fzf - a command-line fuzzy finder"
|
||||||
|
|
||||||
.SH NAME
|
.SH NAME
|
||||||
fzf - a command-line fuzzy finder
|
fzf - a command-line fuzzy finder
|
||||||
@@ -130,6 +130,10 @@ the details.
|
|||||||
.B "--cycle"
|
.B "--cycle"
|
||||||
Enable cyclic scroll
|
Enable cyclic scroll
|
||||||
.TP
|
.TP
|
||||||
|
.B "--keep-right"
|
||||||
|
Keep the right end of the line visible when it's too long. Effective only when
|
||||||
|
the query string is empty.
|
||||||
|
.TP
|
||||||
.B "--no-hscroll"
|
.B "--no-hscroll"
|
||||||
Disable horizontal scroll
|
Disable horizontal scroll
|
||||||
.TP
|
.TP
|
||||||
@@ -178,8 +182,16 @@ Choose the layout (default: default)
|
|||||||
A synonym for \fB--layout=reverse\fB
|
A synonym for \fB--layout=reverse\fB
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.B "--border"
|
.BI "--border" [=STYLE]
|
||||||
Draw border above and below the finder
|
Draw border around the finder
|
||||||
|
|
||||||
|
.br
|
||||||
|
.BR rounded " Border with rounded corners (default)"
|
||||||
|
.br
|
||||||
|
.BR sharp " Border with sharp corners"
|
||||||
|
.br
|
||||||
|
.BR horizontal " Horizontal lines above and below the finder"
|
||||||
|
.br
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.B "--no-unicode"
|
.B "--no-unicode"
|
||||||
@@ -231,6 +243,12 @@ A synonym for \fB--info=hidden\fB
|
|||||||
.BI "--prompt=" "STR"
|
.BI "--prompt=" "STR"
|
||||||
Input prompt (default: '> ')
|
Input prompt (default: '> ')
|
||||||
.TP
|
.TP
|
||||||
|
.BI "--pointer=" "STR"
|
||||||
|
Pointer to the current line (default: '>')
|
||||||
|
.TP
|
||||||
|
.BI "--marker=" "STR"
|
||||||
|
Multi-select marker (default: '>')
|
||||||
|
.TP
|
||||||
.BI "--header=" "STR"
|
.BI "--header=" "STR"
|
||||||
The given string will be printed as the sticky header. The lines are displayed
|
The given string will be printed as the sticky header. The lines are displayed
|
||||||
in the given order from top to bottom regardless of \fB--layout\fR option, and
|
in the given order from top to bottom regardless of \fB--layout\fR option, and
|
||||||
@@ -274,8 +292,8 @@ format.
|
|||||||
\fBbg+ \fRBackground (current line)
|
\fBbg+ \fRBackground (current line)
|
||||||
\fBgutter \fRGutter on the left (defaults to \fBbg+\fR)
|
\fBgutter \fRGutter on the left (defaults to \fBbg+\fR)
|
||||||
\fBhl+ \fRHighlighted substrings (current line)
|
\fBhl+ \fRHighlighted substrings (current line)
|
||||||
\fBinfo \fRInfo
|
\fBinfo \fRInfo line (match counters)
|
||||||
\fBborder \fRBorder of the preview window and horizontal separators (\fB--border\fR)
|
\fBborder \fRBorder around the window (\fB--border\fR and \fB--preview\fR)
|
||||||
\fBprompt \fRPrompt
|
\fBprompt \fRPrompt
|
||||||
\fBpointer \fRPointer to the current line
|
\fBpointer \fRPointer to the current line
|
||||||
\fBmarker \fRMulti-select marker
|
\fBmarker \fRMulti-select marker
|
||||||
@@ -594,6 +612,8 @@ e.g.
|
|||||||
.br
|
.br
|
||||||
\fIend\fR
|
\fIend\fR
|
||||||
.br
|
.br
|
||||||
|
\fIinsert\fR
|
||||||
|
.br
|
||||||
\fIpgup\fR (\fIpage-up\fR)
|
\fIpgup\fR (\fIpage-up\fR)
|
||||||
.br
|
.br
|
||||||
\fIpgdn\fR (\fIpage-down\fR)
|
\fIpgdn\fR (\fIpage-down\fR)
|
||||||
@@ -625,62 +645,63 @@ or any single character
|
|||||||
.SS AVAILABLE ACTIONS:
|
.SS AVAILABLE ACTIONS:
|
||||||
A key or an event can be bound to one or more of the following actions.
|
A key or an event can be bound to one or more of the following actions.
|
||||||
|
|
||||||
\fBACTION: DEFAULT BINDINGS (NOTES):
|
\fBACTION: DEFAULT BINDINGS (NOTES):
|
||||||
\fBabort\fR \fIctrl-c ctrl-g ctrl-q esc\fR
|
\fBabort\fR \fIctrl-c ctrl-g ctrl-q esc\fR
|
||||||
\fBaccept\fR \fIenter double-click\fR
|
\fBaccept\fR \fIenter double-click\fR
|
||||||
\fBaccept-non-empty\fR (same as \fBaccept\fR except that it prevents fzf from exiting without selection)
|
\fBaccept-non-empty\fR (same as \fBaccept\fR except that it prevents fzf from exiting without selection)
|
||||||
\fBbackward-char\fR \fIctrl-b left\fR
|
\fBbackward-char\fR \fIctrl-b left\fR
|
||||||
\fBbackward-delete-char\fR \fIctrl-h bspace\fR
|
\fBbackward-delete-char\fR \fIctrl-h bspace\fR
|
||||||
\fBbackward-kill-word\fR \fIalt-bs\fR
|
\fBbackward-delete-char/eof\fR (same as \fBbackward-delete-char\fR except aborts fzf if query is empty)
|
||||||
\fBbackward-word\fR \fIalt-b shift-left\fR
|
\fBbackward-kill-word\fR \fIalt-bs\fR
|
||||||
\fBbeginning-of-line\fR \fIctrl-a home\fR
|
\fBbackward-word\fR \fIalt-b shift-left\fR
|
||||||
\fBcancel\fR (clear query string if not empty, abort fzf otherwise)
|
\fBbeginning-of-line\fR \fIctrl-a home\fR
|
||||||
\fBclear-screen\fR \fIctrl-l\fR
|
\fBcancel\fR (clear query string if not empty, abort fzf otherwise)
|
||||||
\fBclear-selection\fR (clear multi-selection)
|
\fBclear-screen\fR \fIctrl-l\fR
|
||||||
\fBclear-query\fR (clear query string)
|
\fBclear-selection\fR (clear multi-selection)
|
||||||
\fBdelete-char\fR \fIdel\fR
|
\fBclear-query\fR (clear query string)
|
||||||
\fBdelete-char/eof\fR \fIctrl-d\fR
|
\fBdelete-char\fR \fIdel\fR
|
||||||
\fBdeselect-all\fR (deselect all matches)
|
\fBdelete-char/eof\fR \fIctrl-d\fR (same as \fBdelete-char\fR except aborts fzf if query is empty)
|
||||||
\fBdown\fR \fIctrl-j ctrl-n down\fR
|
\fBdeselect-all\fR (deselect all matches)
|
||||||
\fBend-of-line\fR \fIctrl-e end\fR
|
\fBdown\fR \fIctrl-j ctrl-n down\fR
|
||||||
\fBexecute(...)\fR (see below for the details)
|
\fBend-of-line\fR \fIctrl-e end\fR
|
||||||
\fBexecute-silent(...)\fR (see below for the details)
|
\fBexecute(...)\fR (see below for the details)
|
||||||
\fRexecute-multi(...)\fR (deprecated in favor of \fB{+}\fR expression)
|
\fBexecute-silent(...)\fR (see below for the details)
|
||||||
\fBforward-char\fR \fIctrl-f right\fR
|
\fRexecute-multi(...)\fR (deprecated in favor of \fB{+}\fR expression)
|
||||||
\fBforward-word\fR \fIalt-f shift-right\fR
|
\fBforward-char\fR \fIctrl-f right\fR
|
||||||
|
\fBforward-word\fR \fIalt-f shift-right\fR
|
||||||
\fBignore\fR
|
\fBignore\fR
|
||||||
\fBjump\fR (EasyMotion-like 2-keystroke movement)
|
\fBjump\fR (EasyMotion-like 2-keystroke movement)
|
||||||
\fBjump-accept\fR (jump and accept)
|
\fBjump-accept\fR (jump and accept)
|
||||||
\fBkill-line\fR
|
\fBkill-line\fR
|
||||||
\fBkill-word\fR \fIalt-d\fR
|
\fBkill-word\fR \fIalt-d\fR
|
||||||
\fBnext-history\fR (\fIctrl-n\fR on \fB--history\fR)
|
\fBnext-history\fR (\fIctrl-n\fR on \fB--history\fR)
|
||||||
\fBpage-down\fR \fIpgdn\fR
|
\fBpage-down\fR \fIpgdn\fR
|
||||||
\fBpage-up\fR \fIpgup\fR
|
\fBpage-up\fR \fIpgup\fR
|
||||||
\fBhalf-page-down\fR
|
\fBhalf-page-down\fR
|
||||||
\fBhalf-page-up\fR
|
\fBhalf-page-up\fR
|
||||||
\fBpreview-down\fR \fIshift-down\fR
|
\fBpreview-down\fR \fIshift-down\fR
|
||||||
\fBpreview-up\fR \fIshift-up\fR
|
\fBpreview-up\fR \fIshift-up\fR
|
||||||
\fBpreview-page-down\fR
|
\fBpreview-page-down\fR
|
||||||
\fBpreview-page-up\fR
|
\fBpreview-page-up\fR
|
||||||
\fBprevious-history\fR (\fIctrl-p\fR on \fB--history\fR)
|
\fBprevious-history\fR (\fIctrl-p\fR on \fB--history\fR)
|
||||||
\fBprint-query\fR (print query and exit)
|
\fBprint-query\fR (print query and exit)
|
||||||
\fBreload(...)\fR (see below for the details)
|
\fBreload(...)\fR (see below for the details)
|
||||||
\fBreplace-query\fR (replace query string with the current selection)
|
\fBreplace-query\fR (replace query string with the current selection)
|
||||||
\fBselect-all\fR (select all matches)
|
\fBselect-all\fR (select all matches)
|
||||||
\fBtoggle\fR (\fIright-click\fR)
|
\fBtoggle\fR (\fIright-click\fR)
|
||||||
\fBtoggle-all\fR (toggle all matches)
|
\fBtoggle-all\fR (toggle all matches)
|
||||||
\fBtoggle+down\fR \fIctrl-i (tab)\fR
|
\fBtoggle+down\fR \fIctrl-i (tab)\fR
|
||||||
\fBtoggle-in\fR (\fB--layout=reverse*\fR ? \fBtoggle+up\fR : \fBtoggle+down\fR)
|
\fBtoggle-in\fR (\fB--layout=reverse*\fR ? \fBtoggle+up\fR : \fBtoggle+down\fR)
|
||||||
\fBtoggle-out\fR (\fB--layout=reverse*\fR ? \fBtoggle+down\fR : \fBtoggle+up\fR)
|
\fBtoggle-out\fR (\fB--layout=reverse*\fR ? \fBtoggle+down\fR : \fBtoggle+up\fR)
|
||||||
\fBtoggle-preview\fR
|
\fBtoggle-preview\fR
|
||||||
\fBtoggle-preview-wrap\fR
|
\fBtoggle-preview-wrap\fR
|
||||||
\fBtoggle-sort\fR
|
\fBtoggle-sort\fR
|
||||||
\fBtoggle+up\fR \fIbtab (shift-tab)\fR
|
\fBtoggle+up\fR \fIbtab (shift-tab)\fR
|
||||||
\fBtop\fR (move to the top result)
|
\fBtop\fR (move to the top result)
|
||||||
\fBunix-line-discard\fR \fIctrl-u\fR
|
\fBunix-line-discard\fR \fIctrl-u\fR
|
||||||
\fBunix-word-rubout\fR \fIctrl-w\fR
|
\fBunix-word-rubout\fR \fIctrl-w\fR
|
||||||
\fBup\fR \fIctrl-k ctrl-p up\fR
|
\fBup\fR \fIctrl-k ctrl-p up\fR
|
||||||
\fByank\fR \fIctrl-y\fR
|
\fByank\fR \fIctrl-y\fR
|
||||||
|
|
||||||
.SS ACTION COMPOSITION
|
.SS ACTION COMPOSITION
|
||||||
|
|
||||||
|
|||||||
176
plugin/fzf.vim
176
plugin/fzf.vim
@@ -49,8 +49,13 @@ if s:is_win
|
|||||||
|
|
||||||
" Use utf-8 for fzf.vim commands
|
" Use utf-8 for fzf.vim commands
|
||||||
" Return array of shell commands for cmd.exe
|
" Return array of shell commands for cmd.exe
|
||||||
let s:codepage = libcallnr('kernel32.dll', 'GetACP', 0)
|
|
||||||
function! s:enc_to_cp(str)
|
function! s:enc_to_cp(str)
|
||||||
|
if !has('iconv')
|
||||||
|
return a:str
|
||||||
|
endif
|
||||||
|
if !exists('s:codepage')
|
||||||
|
let s:codepage = libcallnr('kernel32.dll', 'GetACP', 0)
|
||||||
|
endif
|
||||||
return iconv(a:str, &encoding, 'cp'.s:codepage)
|
return iconv(a:str, &encoding, 'cp'.s:codepage)
|
||||||
endfunction
|
endfunction
|
||||||
function! s:wrap_cmds(cmds)
|
function! s:wrap_cmds(cmds)
|
||||||
@@ -114,30 +119,41 @@ let s:default_layout = { 'down': '~40%' }
|
|||||||
let s:layout_keys = ['window', 'up', 'down', 'left', 'right']
|
let s:layout_keys = ['window', 'up', 'down', 'left', 'right']
|
||||||
let s:fzf_go = s:base_dir.'/bin/fzf'
|
let s:fzf_go = s:base_dir.'/bin/fzf'
|
||||||
let s:fzf_tmux = s:base_dir.'/bin/fzf-tmux'
|
let s:fzf_tmux = s:base_dir.'/bin/fzf-tmux'
|
||||||
let s:install = s:base_dir.'/install'
|
|
||||||
let s:installed = 0
|
|
||||||
|
|
||||||
let s:cpo_save = &cpo
|
let s:cpo_save = &cpo
|
||||||
set cpo&vim
|
set cpo&vim
|
||||||
|
|
||||||
|
function! fzf#install()
|
||||||
|
if s:is_win && !has('win32unix')
|
||||||
|
let script = s:base_dir.'/install.ps1'
|
||||||
|
if !filereadable(script)
|
||||||
|
throw script.' not found'
|
||||||
|
endif
|
||||||
|
let script = 'powershell -ExecutionPolicy Bypass -file ' . script
|
||||||
|
else
|
||||||
|
let script = s:base_dir.'/install'
|
||||||
|
if !executable(script)
|
||||||
|
throw script.' not found'
|
||||||
|
endif
|
||||||
|
let script .= ' --bin'
|
||||||
|
endif
|
||||||
|
|
||||||
|
call s:warn('Running fzf installer ...')
|
||||||
|
call system(script)
|
||||||
|
if v:shell_error
|
||||||
|
throw 'Failed to download fzf: '.script
|
||||||
|
endif
|
||||||
|
endfunction
|
||||||
|
|
||||||
function! s:fzf_exec()
|
function! s:fzf_exec()
|
||||||
if !exists('s:exec')
|
if !exists('s:exec')
|
||||||
if executable(s:fzf_go)
|
if executable(s:fzf_go)
|
||||||
let s:exec = s:fzf_go
|
let s:exec = s:fzf_go
|
||||||
elseif executable('fzf')
|
elseif executable('fzf')
|
||||||
let s:exec = 'fzf'
|
let s:exec = 'fzf'
|
||||||
elseif s:is_win && !has('win32unix')
|
elseif input('fzf executable not found. Download binary? (y/n) ') =~? '^y'
|
||||||
call s:warn('fzf executable not found.')
|
|
||||||
call s:warn('Download fzf binary for Windows from https://github.com/junegunn/fzf-bin/releases/')
|
|
||||||
call s:warn('and place it as '.s:base_dir.'\bin\fzf.exe')
|
|
||||||
throw 'fzf executable not found'
|
|
||||||
elseif !s:installed && executable(s:install) &&
|
|
||||||
\ input('fzf executable not found. Download binary? (y/n) ') =~? '^y'
|
|
||||||
redraw
|
redraw
|
||||||
echo
|
call fzf#install()
|
||||||
call s:warn('Downloading fzf binary. Please wait ...')
|
|
||||||
let s:installed = 1
|
|
||||||
call system(s:install.' --bin')
|
|
||||||
return s:fzf_exec()
|
return s:fzf_exec()
|
||||||
else
|
else
|
||||||
redraw
|
redraw
|
||||||
@@ -148,7 +164,7 @@ function! s:fzf_exec()
|
|||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
function! s:tmux_enabled()
|
function! s:tmux_enabled()
|
||||||
if has('gui_running')
|
if has('gui_running') || !exists('$TMUX')
|
||||||
return 0
|
return 0
|
||||||
endif
|
endif
|
||||||
|
|
||||||
@@ -157,10 +173,16 @@ function! s:tmux_enabled()
|
|||||||
endif
|
endif
|
||||||
|
|
||||||
let s:tmux = 0
|
let s:tmux = 0
|
||||||
if exists('$TMUX') && executable(s:fzf_tmux)
|
if !executable(s:fzf_tmux)
|
||||||
let output = system('tmux -V')
|
if executable('fzf-tmux')
|
||||||
let s:tmux = !v:shell_error && output >= 'tmux 1.7'
|
let s:fzf_tmux = 'fzf-tmux'
|
||||||
|
else
|
||||||
|
return 0
|
||||||
|
endif
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
let output = system('tmux -V')
|
||||||
|
let s:tmux = !v:shell_error && output >= 'tmux 1.7'
|
||||||
return s:tmux
|
return s:tmux
|
||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
@@ -378,12 +400,6 @@ try
|
|||||||
let dict.dir = fnamemodify(dict.dir, ':p')
|
let dict.dir = fnamemodify(dict.dir, ':p')
|
||||||
endif
|
endif
|
||||||
|
|
||||||
if !has_key(dict, 'source') && !empty($FZF_DEFAULT_COMMAND) && !s:is_win
|
|
||||||
let temps.source = s:fzf_tempname()
|
|
||||||
call writefile(s:wrap_cmds(split($FZF_DEFAULT_COMMAND, "\n")), temps.source)
|
|
||||||
let dict.source = (empty($SHELL) ? &shell : $SHELL).' '.fzf#shellescape(temps.source)
|
|
||||||
endif
|
|
||||||
|
|
||||||
if has_key(dict, 'source')
|
if has_key(dict, 'source')
|
||||||
let source = dict.source
|
let source = dict.source
|
||||||
let type = type(source)
|
let type = type(source)
|
||||||
@@ -626,9 +642,11 @@ function! s:calc_size(max, val, dict)
|
|||||||
endif
|
endif
|
||||||
|
|
||||||
let opts = $FZF_DEFAULT_OPTS.' '.s:evaluate_opts(get(a:dict, 'options', ''))
|
let opts = $FZF_DEFAULT_OPTS.' '.s:evaluate_opts(get(a:dict, 'options', ''))
|
||||||
let margin = stridx(opts, '--inline-info') > stridx(opts, '--no-inline-info') ? 1 : 2
|
let margin = match(opts, '--inline-info\|--info[^-]\{-}inline') > match(opts, '--no-inline-info\|--info[^-]\{-}\(default\|hidden\)') ? 1 : 2
|
||||||
let margin += stridx(opts, '--border') > stridx(opts, '--no-border') ? 2 : 0
|
let margin += stridx(opts, '--border') > stridx(opts, '--no-border') ? 2 : 0
|
||||||
let margin += stridx(opts, '--header') > stridx(opts, '--no-header')
|
if stridx(opts, '--header') > stridx(opts, '--no-header')
|
||||||
|
let margin += len(split(opts, "\n"))
|
||||||
|
endif
|
||||||
return srcsz >= 0 ? min([srcsz + margin, size]) : size
|
return srcsz >= 0 ? min([srcsz + margin, size]) : size
|
||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
@@ -645,7 +663,14 @@ function! s:split(dict)
|
|||||||
let ppos = s:getpos()
|
let ppos = s:getpos()
|
||||||
try
|
try
|
||||||
if s:present(a:dict, 'window')
|
if s:present(a:dict, 'window')
|
||||||
execute 'keepalt' a:dict.window
|
if type(a:dict.window) == type({})
|
||||||
|
if !has('nvim') && !has('patch-8.2.191')
|
||||||
|
throw 'Vim 8.2.191 or later is required for pop-up window'
|
||||||
|
end
|
||||||
|
call s:popup(a:dict.window)
|
||||||
|
else
|
||||||
|
execute 'keepalt' a:dict.window
|
||||||
|
endif
|
||||||
elseif !s:splittable(a:dict)
|
elseif !s:splittable(a:dict)
|
||||||
execute (tabpagenr()-1).'tabnew'
|
execute (tabpagenr()-1).'tabnew'
|
||||||
else
|
else
|
||||||
@@ -737,6 +762,9 @@ function! s:execute_term(dict, command, temps) abort
|
|||||||
if has('nvim')
|
if has('nvim')
|
||||||
call termopen(command, fzf)
|
call termopen(command, fzf)
|
||||||
else
|
else
|
||||||
|
if !len(&bufhidden)
|
||||||
|
setlocal bufhidden=hide
|
||||||
|
endif
|
||||||
let fzf.buf = term_start([&shell, &shellcmdflag, command], {'curwin': 1, 'exit_cb': function(fzf.on_exit)})
|
let fzf.buf = term_start([&shell, &shellcmdflag, command], {'curwin': 1, 'exit_cb': function(fzf.on_exit)})
|
||||||
if !has('patch-8.0.1261') && !has('nvim') && !s:is_win
|
if !has('patch-8.0.1261') && !has('nvim') && !s:is_win
|
||||||
call term_wait(fzf.buf, 20)
|
call term_wait(fzf.buf, 20)
|
||||||
@@ -793,6 +821,100 @@ function! s:callback(dict, lines) abort
|
|||||||
endif
|
endif
|
||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
|
if has('nvim')
|
||||||
|
function s:create_popup(hl, opts) abort
|
||||||
|
let buf = nvim_create_buf(v:false, v:true)
|
||||||
|
let opts = extend({'relative': 'editor', 'style': 'minimal'}, a:opts)
|
||||||
|
let border = has_key(opts, 'border') ? remove(opts, 'border') : []
|
||||||
|
let win = nvim_open_win(buf, v:true, opts)
|
||||||
|
call setwinvar(win, '&winhighlight', 'NormalFloat:'..a:hl)
|
||||||
|
call setwinvar(win, '&colorcolumn', '')
|
||||||
|
if !empty(border)
|
||||||
|
call nvim_buf_set_lines(buf, 0, -1, v:true, border)
|
||||||
|
endif
|
||||||
|
return buf
|
||||||
|
endfunction
|
||||||
|
else
|
||||||
|
function! s:create_popup(hl, opts) abort
|
||||||
|
let is_frame = has_key(a:opts, 'border')
|
||||||
|
let buf = is_frame ? '' : term_start(&shell, #{hidden: 1})
|
||||||
|
let id = popup_create(buf, #{
|
||||||
|
\ line: a:opts.row,
|
||||||
|
\ col: a:opts.col,
|
||||||
|
\ minwidth: a:opts.width,
|
||||||
|
\ minheight: a:opts.height,
|
||||||
|
\ zindex: 50 - is_frame,
|
||||||
|
\ })
|
||||||
|
|
||||||
|
if is_frame
|
||||||
|
call setwinvar(id, '&wincolor', a:hl)
|
||||||
|
call setbufline(winbufnr(id), 1, a:opts.border)
|
||||||
|
execute 'autocmd BufWipeout * ++once call popup_close('..id..')'
|
||||||
|
else
|
||||||
|
execute 'autocmd BufWipeout * ++once bwipeout! '..buf
|
||||||
|
endif
|
||||||
|
return winbufnr(id)
|
||||||
|
endfunction
|
||||||
|
endif
|
||||||
|
|
||||||
|
function! s:popup(opts) abort
|
||||||
|
" Support ambiwidth == 'double'
|
||||||
|
let ambidouble = &ambiwidth == 'double' ? 2 : 1
|
||||||
|
|
||||||
|
" Size and position
|
||||||
|
let width = min([max([0, float2nr(&columns * a:opts.width)]), &columns])
|
||||||
|
let width += width % ambidouble
|
||||||
|
let height = min([max([0, float2nr(&lines * a:opts.height)]), &lines - has('nvim')])
|
||||||
|
let row = float2nr(get(a:opts, 'yoffset', 0.5) * (&lines - height))
|
||||||
|
let col = float2nr(get(a:opts, 'xoffset', 0.5) * (&columns - width))
|
||||||
|
|
||||||
|
" Managing the differences
|
||||||
|
let row = min([max([0, row]), &lines - has('nvim') - height])
|
||||||
|
let col = min([max([0, col]), &columns - width])
|
||||||
|
let row += !has('nvim')
|
||||||
|
let col += !has('nvim')
|
||||||
|
|
||||||
|
" Border style
|
||||||
|
let style = tolower(get(a:opts, 'border', 'rounded'))
|
||||||
|
if !has_key(a:opts, 'border') && !get(a:opts, 'rounded', 1)
|
||||||
|
let style = 'sharp'
|
||||||
|
endif
|
||||||
|
|
||||||
|
if style =~ 'vertical\|left\|right'
|
||||||
|
let mid = style == 'vertical' ? '│' .. repeat(' ', width - 2 * ambidouble) .. '│' :
|
||||||
|
\ style == 'left' ? '│' .. repeat(' ', width - 1 * ambidouble)
|
||||||
|
\ : repeat(' ', width - 1 * ambidouble) .. '│'
|
||||||
|
let border = repeat([mid], height)
|
||||||
|
let shift = { 'row': 0, 'col': style == 'right' ? 0 : 2, 'width': style == 'vertical' ? -4 : -2, 'height': 0 }
|
||||||
|
elseif style =~ 'horizontal\|top\|bottom'
|
||||||
|
let hor = repeat('─', width / ambidouble)
|
||||||
|
let mid = repeat(' ', width)
|
||||||
|
let border = style == 'horizontal' ? [hor] + repeat([mid], height - 2) + [hor] :
|
||||||
|
\ style == 'top' ? [hor] + repeat([mid], height - 1)
|
||||||
|
\ : repeat([mid], height - 1) + [hor]
|
||||||
|
let shift = { 'row': style == 'bottom' ? 0 : 1, 'col': 0, 'width': 0, 'height': style == 'horizontal' ? -2 : -1 }
|
||||||
|
else
|
||||||
|
let edges = style == 'sharp' ? ['┌', '┐', '└', '┘'] : ['╭', '╮', '╰', '╯']
|
||||||
|
let bar = repeat('─', width / ambidouble - 2)
|
||||||
|
let top = edges[0] .. bar .. edges[1]
|
||||||
|
let mid = '│' .. repeat(' ', width - 2 * ambidouble) .. '│'
|
||||||
|
let bot = edges[2] .. bar .. edges[3]
|
||||||
|
let border = [top] + repeat([mid], height - 2) + [bot]
|
||||||
|
let shift = { 'row': 1, 'col': 2, 'width': -4, 'height': -2 }
|
||||||
|
endif
|
||||||
|
|
||||||
|
let highlight = get(a:opts, 'highlight', 'Comment')
|
||||||
|
let frame = s:create_popup(highlight, {
|
||||||
|
\ 'row': row, 'col': col, 'width': width, 'height': height, 'border': border
|
||||||
|
\ })
|
||||||
|
call s:create_popup('Normal', {
|
||||||
|
\ 'row': row + shift.row, 'col': col + shift.col, 'width': width + shift.width, 'height': height + shift.height
|
||||||
|
\ })
|
||||||
|
if has('nvim')
|
||||||
|
execute 'autocmd BufWipeout <buffer> bwipeout '..frame
|
||||||
|
endif
|
||||||
|
endfunction
|
||||||
|
|
||||||
let s:default_action = {
|
let s:default_action = {
|
||||||
\ 'ctrl-t': 'tab split',
|
\ 'ctrl-t': 'tab split',
|
||||||
\ 'ctrl-x': 'split',
|
\ 'ctrl-x': 'split',
|
||||||
|
|||||||
@@ -34,9 +34,16 @@ fi
|
|||||||
# To redraw line after fzf closes (printf '\e[5n')
|
# To redraw line after fzf closes (printf '\e[5n')
|
||||||
bind '"\e[0n": redraw-current-line'
|
bind '"\e[0n": redraw-current-line'
|
||||||
|
|
||||||
__fzfcmd_complete() {
|
__fzf_comprun() {
|
||||||
[ -n "$TMUX_PANE" ] && [ "${FZF_TMUX:-0}" != 0 ] && [ ${LINES:-40} -gt 15 ] &&
|
if [ "$(type -t _fzf_comprun 2>&1)" = function ]; then
|
||||||
echo "fzf-tmux -d${FZF_TMUX_HEIGHT:-40%}" || echo "fzf"
|
_fzf_comprun "$@"
|
||||||
|
elif [ -n "$TMUX_PANE" ] && [ "${FZF_TMUX:-0}" != 0 ] && [ ${LINES:-40} -gt 15 ]; then
|
||||||
|
shift
|
||||||
|
fzf-tmux -d "${FZF_TMUX_HEIGHT:-40%}" "$@"
|
||||||
|
else
|
||||||
|
shift
|
||||||
|
fzf "$@"
|
||||||
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
__fzf_orig_completion_filter() {
|
__fzf_orig_completion_filter() {
|
||||||
@@ -72,6 +79,8 @@ _fzf_opts_completion() {
|
|||||||
--margin
|
--margin
|
||||||
--inline-info
|
--inline-info
|
||||||
--prompt
|
--prompt
|
||||||
|
--pointer
|
||||||
|
--marker
|
||||||
--header
|
--header
|
||||||
--header-lines
|
--header-lines
|
||||||
--ansi
|
--ansi
|
||||||
@@ -140,8 +149,7 @@ _fzf_handle_dynamic_completion() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
__fzf_generic_path_completion() {
|
__fzf_generic_path_completion() {
|
||||||
local cur base dir leftover matches trigger cmd fzf
|
local cur base dir leftover matches trigger cmd
|
||||||
fzf="$(__fzfcmd_complete)"
|
|
||||||
cmd="${COMP_WORDS[0]//[^A-Za-z0-9_=]/_}"
|
cmd="${COMP_WORDS[0]//[^A-Za-z0-9_=]/_}"
|
||||||
COMPREPLY=()
|
COMPREPLY=()
|
||||||
trigger=${FZF_COMPLETION_TRIGGER-'**'}
|
trigger=${FZF_COMPLETION_TRIGGER-'**'}
|
||||||
@@ -157,7 +165,7 @@ __fzf_generic_path_completion() {
|
|||||||
leftover=${leftover/#\/}
|
leftover=${leftover/#\/}
|
||||||
[ -z "$dir" ] && dir='.'
|
[ -z "$dir" ] && dir='.'
|
||||||
[ "$dir" != "/" ] && dir="${dir/%\//}"
|
[ "$dir" != "/" ] && dir="${dir/%\//}"
|
||||||
matches=$(eval "$1 $(printf %q "$dir")" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse $FZF_DEFAULT_OPTS $FZF_COMPLETION_OPTS" $fzf $2 -q "$leftover" | while read -r item; do
|
matches=$(eval "$1 $(printf %q "$dir")" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse $FZF_DEFAULT_OPTS $FZF_COMPLETION_OPTS $2" __fzf_comprun "$4" -q "$leftover" | while read -r item; do
|
||||||
printf "%q$3 " "$item"
|
printf "%q$3 " "$item"
|
||||||
done)
|
done)
|
||||||
matches=${matches% }
|
matches=${matches% }
|
||||||
@@ -182,10 +190,30 @@ __fzf_generic_path_completion() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_fzf_complete() {
|
_fzf_complete() {
|
||||||
local cur selected trigger cmd fzf post
|
# Split arguments around --
|
||||||
|
local args rest str_arg i sep
|
||||||
|
args=("$@")
|
||||||
|
sep=
|
||||||
|
for i in "${!args[@]}"; do
|
||||||
|
if [[ "${args[$i]}" = -- ]]; then
|
||||||
|
sep=$i
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
if [[ -n "$sep" ]]; then
|
||||||
|
str_arg=
|
||||||
|
rest=("${args[@]:$((sep + 1)):${#args[@]}}")
|
||||||
|
args=("${args[@]:0:$sep}")
|
||||||
|
else
|
||||||
|
str_arg=$1
|
||||||
|
args=()
|
||||||
|
shift
|
||||||
|
rest=("$@")
|
||||||
|
fi
|
||||||
|
|
||||||
|
local cur selected trigger cmd post
|
||||||
post="$(caller 0 | awk '{print $2}')_post"
|
post="$(caller 0 | awk '{print $2}')_post"
|
||||||
type -t "$post" > /dev/null 2>&1 || post=cat
|
type -t "$post" > /dev/null 2>&1 || post=cat
|
||||||
fzf="$(__fzfcmd_complete)"
|
|
||||||
|
|
||||||
cmd="${COMP_WORDS[0]//[^A-Za-z0-9_=]/_}"
|
cmd="${COMP_WORDS[0]//[^A-Za-z0-9_=]/_}"
|
||||||
trigger=${FZF_COMPLETION_TRIGGER-'**'}
|
trigger=${FZF_COMPLETION_TRIGGER-'**'}
|
||||||
@@ -193,7 +221,7 @@ _fzf_complete() {
|
|||||||
if [[ "$cur" == *"$trigger" ]]; then
|
if [[ "$cur" == *"$trigger" ]]; then
|
||||||
cur=${cur:0:${#cur}-${#trigger}}
|
cur=${cur:0:${#cur}-${#trigger}}
|
||||||
|
|
||||||
selected=$(cat | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse $FZF_DEFAULT_OPTS $FZF_COMPLETION_OPTS" $fzf $1 -q "$cur" | $post | tr '\n' ' ')
|
selected=$(FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse $FZF_DEFAULT_OPTS $FZF_COMPLETION_OPTS $str_arg" __fzf_comprun "${rest[0]}" "${args[@]}" -q "$cur" | $post | tr '\n' ' ')
|
||||||
selected=${selected% } # Strip trailing space not to repeat "-o nospace"
|
selected=${selected% } # Strip trailing space not to repeat "-o nospace"
|
||||||
if [ -n "$selected" ]; then
|
if [ -n "$selected" ]; then
|
||||||
COMPREPLY=("$selected")
|
COMPREPLY=("$selected")
|
||||||
@@ -203,8 +231,7 @@ _fzf_complete() {
|
|||||||
printf '\e[5n'
|
printf '\e[5n'
|
||||||
return 0
|
return 0
|
||||||
else
|
else
|
||||||
shift
|
_fzf_handle_dynamic_completion "$cmd" "${rest[@]}"
|
||||||
_fzf_handle_dynamic_completion "$cmd" "$@"
|
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -224,9 +251,9 @@ _fzf_dir_completion() {
|
|||||||
_fzf_complete_kill() {
|
_fzf_complete_kill() {
|
||||||
[ -n "${COMP_WORDS[COMP_CWORD]}" ] && return 1
|
[ -n "${COMP_WORDS[COMP_CWORD]}" ] && return 1
|
||||||
|
|
||||||
local selected fzf
|
local selected
|
||||||
fzf="$(__fzfcmd_complete)"
|
selected=$(command ps -ef | sed 1d | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-50%} --min-height 15 --reverse $FZF_DEFAULT_OPTS $FZF_COMPLETION_OPTS --preview 'echo {}' --preview-window down:3:wrap" __fzf_comprun "kill" -m | awk '{print $2}' | tr '\n' ' ')
|
||||||
selected=$(command ps -ef | sed 1d | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-50%} --min-height 15 --reverse $FZF_DEFAULT_OPTS --preview 'echo {}' --preview-window down:3:wrap $FZF_COMPLETION_OPTS" $fzf -m | awk '{print $2}' | tr '\n' ' ')
|
selected=${selected% }
|
||||||
printf '\e[5n'
|
printf '\e[5n'
|
||||||
|
|
||||||
if [ -n "$selected" ]; then
|
if [ -n "$selected" ]; then
|
||||||
@@ -235,15 +262,8 @@ _fzf_complete_kill() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
_fzf_complete_telnet() {
|
_fzf_host_completion() {
|
||||||
_fzf_complete '+m' "$@" < <(
|
_fzf_complete +m -- "$@" < <(
|
||||||
command grep -v '^\s*\(#\|$\)' /etc/hosts | command grep -Fv '0.0.0.0' |
|
|
||||||
awk '{if (length($2) > 0) {print $2}}' | sort -u
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
_fzf_complete_ssh() {
|
|
||||||
_fzf_complete '+m' "$@" < <(
|
|
||||||
cat <(cat ~/.ssh/config ~/.ssh/config.d/* /etc/ssh/ssh_config 2> /dev/null | command grep -i '^\s*host\(name\)\? ' | awk '{for (i = 2; i <= NF; i++) print $1 " " $i}' | command grep -v '[*?]') \
|
cat <(cat ~/.ssh/config ~/.ssh/config.d/* /etc/ssh/ssh_config 2> /dev/null | command grep -i '^\s*host\(name\)\? ' | awk '{for (i = 2; i <= NF; i++) print $1 " " $i}' | command grep -v '[*?]') \
|
||||||
<(command grep -oE '^[[a-z0-9.,:-]+' ~/.ssh/known_hosts | tr ',' '\n' | tr -d '[' | awk '{ print $1 " " $1 }') \
|
<(command grep -oE '^[[a-z0-9.,:-]+' ~/.ssh/known_hosts | tr ',' '\n' | tr -d '[' | awk '{ print $1 " " $1 }') \
|
||||||
<(command grep -v '^\s*\(#\|$\)' /etc/hosts | command grep -Fv '0.0.0.0') |
|
<(command grep -v '^\s*\(#\|$\)' /etc/hosts | command grep -Fv '0.0.0.0') |
|
||||||
@@ -251,20 +271,14 @@ _fzf_complete_ssh() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
_fzf_complete_unset() {
|
_fzf_var_completion() {
|
||||||
_fzf_complete '-m' "$@" < <(
|
_fzf_complete -m -- "$@" < <(
|
||||||
declare -xp | sed 's/=.*//' | sed 's/.* //'
|
declare -xp | sed 's/=.*//' | sed 's/.* //'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
_fzf_complete_export() {
|
_fzf_alias_completion() {
|
||||||
_fzf_complete '-m' "$@" < <(
|
_fzf_complete -m -- "$@" < <(
|
||||||
declare -xp | sed 's/=.*//' | sed 's/.* //'
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
_fzf_complete_unalias() {
|
|
||||||
_fzf_complete '-m' "$@" < <(
|
|
||||||
alias | sed 's/=.*//' | sed 's/.* //'
|
alias | sed 's/=.*//' | sed 's/.* //'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -282,7 +296,7 @@ a_cmds="
|
|||||||
find git grep gunzip gzip hg jar
|
find git grep gunzip gzip hg jar
|
||||||
ln ls mv open rm rsync scp
|
ln ls mv open rm rsync scp
|
||||||
svn tar unzip zip"
|
svn tar unzip zip"
|
||||||
x_cmds="kill ssh telnet unset unalias export"
|
x_cmds="kill"
|
||||||
|
|
||||||
# Preserve existing completion
|
# Preserve existing completion
|
||||||
eval "$(complete |
|
eval "$(complete |
|
||||||
@@ -319,16 +333,7 @@ for cmd in $d_cmds; do
|
|||||||
done
|
done
|
||||||
|
|
||||||
# Kill completion
|
# Kill completion
|
||||||
complete -F _fzf_complete_kill -o nospace -o default -o bashdefault kill
|
complete -F _fzf_complete_kill -o default -o bashdefault kill
|
||||||
|
|
||||||
# Host completion
|
|
||||||
complete -F _fzf_complete_ssh -o default -o bashdefault ssh
|
|
||||||
complete -F _fzf_complete_telnet -o default -o bashdefault telnet
|
|
||||||
|
|
||||||
# Environment variables / Aliases
|
|
||||||
complete -F _fzf_complete_unset -o default -o bashdefault unset
|
|
||||||
complete -F _fzf_complete_export -o default -o bashdefault export
|
|
||||||
complete -F _fzf_complete_unalias -o default -o bashdefault unalias
|
|
||||||
|
|
||||||
unset cmd d_cmds a_cmds x_cmds
|
unset cmd d_cmds a_cmds x_cmds
|
||||||
|
|
||||||
@@ -337,17 +342,24 @@ _fzf_setup_completion() {
|
|||||||
kind=$1
|
kind=$1
|
||||||
fn=_fzf_${1}_completion
|
fn=_fzf_${1}_completion
|
||||||
if [[ $# -lt 2 ]] || ! type -t "$fn" > /dev/null; then
|
if [[ $# -lt 2 ]] || ! type -t "$fn" > /dev/null; then
|
||||||
echo "usage: ${FUNCNAME[0]} path|dir COMMANDS..."
|
echo "usage: ${FUNCNAME[0]} path|dir|var|alias|host COMMANDS..."
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
shift
|
shift
|
||||||
|
eval "$(complete -p "$@" 2> /dev/null | grep -v "$fn" | __fzf_orig_completion_filter)"
|
||||||
for cmd in "$@"; do
|
for cmd in "$@"; do
|
||||||
eval "$(complete -p "$cmd" 2> /dev/null | grep -v "$fn" | __fzf_orig_completion_filter)"
|
|
||||||
case "$kind" in
|
case "$kind" in
|
||||||
dir) __fzf_defc "$cmd" "$fn" "-o nospace -o dirnames" ;;
|
dir) __fzf_defc "$cmd" "$fn" "-o nospace -o dirnames" ;;
|
||||||
*) __fzf_defc "$cmd" "$fn" "-o default -o bashdefault" ;;
|
var) __fzf_defc "$cmd" "$fn" "-o default -o nospace -v" ;;
|
||||||
|
alias) __fzf_defc "$cmd" "$fn" "-a" ;;
|
||||||
|
*) __fzf_defc "$cmd" "$fn" "-o default -o bashdefault" ;;
|
||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Environment variables / Aliases / Hosts
|
||||||
|
_fzf_setup_completion 'var' export unset
|
||||||
|
_fzf_setup_completion 'alias' unalias
|
||||||
|
_fzf_setup_completion 'host' ssh telnet
|
||||||
|
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -31,20 +31,40 @@ fi
|
|||||||
|
|
||||||
###########################################################
|
###########################################################
|
||||||
|
|
||||||
__fzfcmd_complete() {
|
__fzf_comprun() {
|
||||||
[ -n "$TMUX_PANE" ] && [ "${FZF_TMUX:-0}" != 0 ] && [ ${LINES:-40} -gt 15 ] &&
|
if [[ "$(type _fzf_comprun 2>&1)" =~ function ]]; then
|
||||||
echo "fzf-tmux -d${FZF_TMUX_HEIGHT:-40%}" || echo "fzf"
|
_fzf_comprun "$@"
|
||||||
|
elif [ -n "$TMUX_PANE" ] && [ "${FZF_TMUX:-0}" != 0 ] && [ ${LINES:-40} -gt 15 ]; then
|
||||||
|
shift
|
||||||
|
fzf-tmux -d "${FZF_TMUX_HEIGHT:-40%}" "$@"
|
||||||
|
else
|
||||||
|
shift
|
||||||
|
fzf "$@"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Extract the name of the command. e.g. foo=1 bar baz**<tab>
|
||||||
|
__fzf_extract_command() {
|
||||||
|
local token tokens
|
||||||
|
tokens=(${(z)1})
|
||||||
|
for token in $tokens; do
|
||||||
|
if [[ "$token" =~ [[:alnum:]] && ! "$token" =~ "=" ]]; then
|
||||||
|
echo "$token"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
echo "${tokens[1]}"
|
||||||
}
|
}
|
||||||
|
|
||||||
__fzf_generic_path_completion() {
|
__fzf_generic_path_completion() {
|
||||||
local base lbuf compgen fzf_opts suffix tail fzf dir leftover matches
|
local base lbuf cmd compgen fzf_opts suffix tail dir leftover matches
|
||||||
base=$1
|
base=$1
|
||||||
lbuf=$2
|
lbuf=$2
|
||||||
|
cmd=$(__fzf_extract_command "$lbuf")
|
||||||
compgen=$3
|
compgen=$3
|
||||||
fzf_opts=$4
|
fzf_opts=$4
|
||||||
suffix=$5
|
suffix=$5
|
||||||
tail=$6
|
tail=$6
|
||||||
fzf="$(__fzfcmd_complete)"
|
|
||||||
|
|
||||||
setopt localoptions nonomatch
|
setopt localoptions nonomatch
|
||||||
eval "base=$base"
|
eval "base=$base"
|
||||||
@@ -55,7 +75,7 @@ __fzf_generic_path_completion() {
|
|||||||
leftover=${leftover/#\/}
|
leftover=${leftover/#\/}
|
||||||
[ -z "$dir" ] && dir='.'
|
[ -z "$dir" ] && dir='.'
|
||||||
[ "$dir" != "/" ] && dir="${dir/%\//}"
|
[ "$dir" != "/" ] && dir="${dir/%\//}"
|
||||||
matches=$(eval "$compgen $(printf %q "$dir")" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse $FZF_DEFAULT_OPTS $FZF_COMPLETION_OPTS" ${=fzf} ${=fzf_opts} -q "$leftover" | while read item; do
|
matches=$(eval "$compgen $(printf %q "$dir")" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse $FZF_DEFAULT_OPTS $FZF_COMPLETION_OPTS" __fzf_comprun "$cmd" ${(Q)${(Z+n+)fzf_opts}} -q "$leftover" | while read item; do
|
||||||
echo -n "${(q)item}$suffix "
|
echo -n "${(q)item}$suffix "
|
||||||
done)
|
done)
|
||||||
matches=${matches% }
|
matches=${matches% }
|
||||||
@@ -87,17 +107,37 @@ _fzf_feed_fifo() (
|
|||||||
)
|
)
|
||||||
|
|
||||||
_fzf_complete() {
|
_fzf_complete() {
|
||||||
local fifo fzf_opts lbuf fzf matches post
|
setopt localoptions ksh_arrays
|
||||||
|
# Split arguments around --
|
||||||
|
local args rest str_arg i sep
|
||||||
|
args=("$@")
|
||||||
|
sep=
|
||||||
|
for i in {0..$#args}; do
|
||||||
|
if [[ "${args[$i]}" = -- ]]; then
|
||||||
|
sep=$i
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
if [[ -n "$sep" ]]; then
|
||||||
|
str_arg=
|
||||||
|
rest=("${args[@]:$((sep + 1)):${#args[@]}}")
|
||||||
|
args=("${args[@]:0:$sep}")
|
||||||
|
else
|
||||||
|
str_arg=$1
|
||||||
|
args=()
|
||||||
|
shift
|
||||||
|
rest=("$@")
|
||||||
|
fi
|
||||||
|
|
||||||
|
local fifo lbuf cmd matches post
|
||||||
fifo="${TMPDIR:-/tmp}/fzf-complete-fifo-$$"
|
fifo="${TMPDIR:-/tmp}/fzf-complete-fifo-$$"
|
||||||
fzf_opts=$1
|
lbuf=${rest[0]}
|
||||||
lbuf=$2
|
cmd=$(__fzf_extract_command "$lbuf")
|
||||||
post="${funcstack[2]}_post"
|
post="${funcstack[1]}_post"
|
||||||
type $post > /dev/null 2>&1 || post=cat
|
type $post > /dev/null 2>&1 || post=cat
|
||||||
|
|
||||||
fzf="$(__fzfcmd_complete)"
|
|
||||||
|
|
||||||
_fzf_feed_fifo "$fifo"
|
_fzf_feed_fifo "$fifo"
|
||||||
matches=$(cat "$fifo" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse $FZF_DEFAULT_OPTS $FZF_COMPLETION_OPTS" ${=fzf} ${=fzf_opts} -q "${(Q)prefix}" | $post | tr '\n' ' ')
|
matches=$(FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse $FZF_DEFAULT_OPTS $FZF_COMPLETION_OPTS $str_arg" __fzf_comprun "$cmd" "${args[@]}" -q "${(Q)prefix}" < "$fifo" | $post | tr '\n' ' ')
|
||||||
if [ -n "$matches" ]; then
|
if [ -n "$matches" ]; then
|
||||||
LBUFFER="$lbuf$matches"
|
LBUFFER="$lbuf$matches"
|
||||||
fi
|
fi
|
||||||
@@ -106,14 +146,14 @@ _fzf_complete() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_fzf_complete_telnet() {
|
_fzf_complete_telnet() {
|
||||||
_fzf_complete '+m' "$@" < <(
|
_fzf_complete +m -- "$@" < <(
|
||||||
command grep -v '^\s*\(#\|$\)' /etc/hosts | command grep -Fv '0.0.0.0' |
|
command grep -v '^\s*\(#\|$\)' /etc/hosts | command grep -Fv '0.0.0.0' |
|
||||||
awk '{if (length($2) > 0) {print $2}}' | sort -u
|
awk '{if (length($2) > 0) {print $2}}' | sort -u
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
_fzf_complete_ssh() {
|
_fzf_complete_ssh() {
|
||||||
_fzf_complete '+m' "$@" < <(
|
_fzf_complete +m -- "$@" < <(
|
||||||
setopt localoptions nonomatch
|
setopt localoptions nonomatch
|
||||||
command cat <(cat ~/.ssh/config ~/.ssh/config.d/* /etc/ssh/ssh_config 2> /dev/null | command grep -i '^\s*host\(name\)\? ' | awk '{for (i = 2; i <= NF; i++) print $1 " " $i}' | command grep -v '[*?]') \
|
command cat <(cat ~/.ssh/config ~/.ssh/config.d/* /etc/ssh/ssh_config 2> /dev/null | command grep -i '^\s*host\(name\)\? ' | awk '{for (i = 2; i <= NF; i++) print $1 " " $i}' | command grep -v '[*?]') \
|
||||||
<(command grep -oE '^[[a-z0-9.,:-]+' ~/.ssh/known_hosts | tr ',' '\n' | tr -d '[' | awk '{ print $1 " " $1 }') \
|
<(command grep -oE '^[[a-z0-9.,:-]+' ~/.ssh/known_hosts | tr ',' '\n' | tr -d '[' | awk '{ print $1 " " $1 }') \
|
||||||
@@ -123,25 +163,25 @@ _fzf_complete_ssh() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_fzf_complete_export() {
|
_fzf_complete_export() {
|
||||||
_fzf_complete '-m' "$@" < <(
|
_fzf_complete -m -- "$@" < <(
|
||||||
declare -xp | sed 's/=.*//' | sed 's/.* //'
|
declare -xp | sed 's/=.*//' | sed 's/.* //'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
_fzf_complete_unset() {
|
_fzf_complete_unset() {
|
||||||
_fzf_complete '-m' "$@" < <(
|
_fzf_complete -m -- "$@" < <(
|
||||||
declare -xp | sed 's/=.*//' | sed 's/.* //'
|
declare -xp | sed 's/=.*//' | sed 's/.* //'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
_fzf_complete_unalias() {
|
_fzf_complete_unalias() {
|
||||||
_fzf_complete '+m' "$@" < <(
|
_fzf_complete +m -- "$@" < <(
|
||||||
alias | sed 's/=.*//'
|
alias | sed 's/=.*//'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fzf-completion() {
|
fzf-completion() {
|
||||||
local tokens cmd prefix trigger tail fzf matches lbuf d_cmds
|
local tokens cmd prefix trigger tail matches lbuf d_cmds
|
||||||
setopt localoptions noshwordsplit noksh_arrays noposixbuiltins
|
setopt localoptions noshwordsplit noksh_arrays noposixbuiltins
|
||||||
|
|
||||||
# http://zsh.sourceforge.net/FAQ/zshfaq03.html
|
# http://zsh.sourceforge.net/FAQ/zshfaq03.html
|
||||||
@@ -152,7 +192,7 @@ fzf-completion() {
|
|||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
|
|
||||||
cmd=${tokens[1]}
|
cmd=$(__fzf_extract_command "$LBUFFER")
|
||||||
|
|
||||||
# Explicitly allow for empty trigger.
|
# Explicitly allow for empty trigger.
|
||||||
trigger=${FZF_COMPLETION_TRIGGER-'**'}
|
trigger=${FZF_COMPLETION_TRIGGER-'**'}
|
||||||
@@ -167,8 +207,7 @@ fzf-completion() {
|
|||||||
tail=${LBUFFER:$(( ${#LBUFFER} - ${#trigger} ))}
|
tail=${LBUFFER:$(( ${#LBUFFER} - ${#trigger} ))}
|
||||||
# Kill completion (do not require trigger sequence)
|
# Kill completion (do not require trigger sequence)
|
||||||
if [ $cmd = kill -a ${LBUFFER[-1]} = ' ' ]; then
|
if [ $cmd = kill -a ${LBUFFER[-1]} = ' ' ]; then
|
||||||
fzf="$(__fzfcmd_complete)"
|
matches=$(command ps -ef | sed 1d | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-50%} --min-height 15 --reverse $FZF_DEFAULT_OPTS $FZF_COMPLETION_OPTS --preview 'echo {}' --preview-window down:3:wrap" __fzf_comprun "$cmd" -m | awk '{print $2}' | tr '\n' ' ')
|
||||||
matches=$(command ps -ef | sed 1d | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-50%} --min-height 15 --reverse $FZF_DEFAULT_OPTS --preview 'echo {}' --preview-window down:3:wrap $FZF_COMPLETION_OPTS" ${=fzf} -m | awk '{print $2}' | tr '\n' ' ')
|
|
||||||
if [ -n "$matches" ]; then
|
if [ -n "$matches" ]; then
|
||||||
LBUFFER="$LBUFFER$matches"
|
LBUFFER="$LBUFFER$matches"
|
||||||
fi
|
fi
|
||||||
@@ -181,7 +220,7 @@ fzf-completion() {
|
|||||||
[ -z "${tokens[-1]}" ] && lbuf=$LBUFFER || lbuf=${LBUFFER:0:-${#tokens[-1]}}
|
[ -z "${tokens[-1]}" ] && lbuf=$LBUFFER || lbuf=${LBUFFER:0:-${#tokens[-1]}}
|
||||||
|
|
||||||
if eval "type _fzf_complete_${cmd} > /dev/null"; then
|
if eval "type _fzf_complete_${cmd} > /dev/null"; then
|
||||||
eval "prefix=\"$prefix\" _fzf_complete_${cmd} \"$lbuf\""
|
prefix="$prefix" eval _fzf_complete_${cmd} ${(q)lbuf}
|
||||||
elif [ ${d_cmds[(i)$cmd]} -le ${#d_cmds} ]; then
|
elif [ ${d_cmds[(i)$cmd]} -le ${#d_cmds} ]; then
|
||||||
_fzf_dir_completion "$prefix" "$lbuf"
|
_fzf_dir_completion "$prefix" "$lbuf"
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -51,71 +51,57 @@ __fzf_cd__() {
|
|||||||
dir=$(eval "$cmd" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse $FZF_DEFAULT_OPTS $FZF_ALT_C_OPTS" $(__fzfcmd) +m) && printf 'cd %q' "$dir"
|
dir=$(eval "$cmd" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse $FZF_DEFAULT_OPTS $FZF_ALT_C_OPTS" $(__fzfcmd) +m) && printf 'cd %q' "$dir"
|
||||||
}
|
}
|
||||||
|
|
||||||
__fzf_history__() (
|
__fzf_history__() {
|
||||||
local line
|
local output
|
||||||
shopt -u nocaseglob nocasematch
|
output=$(
|
||||||
line=$(
|
builtin fc -lnr -2147483648 |
|
||||||
HISTTIMEFORMAT= builtin history |
|
last_hist=$(HISTTIMEFORMAT='' builtin history 1) perl -p -l0 -e 'BEGIN { getc; $/ = "\n\t"; $HISTCMD = $ENV{last_hist} + 1 } s/^[ *]//; $_ = $HISTCMD - $. . "\t$_"' |
|
||||||
FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} $FZF_DEFAULT_OPTS --tac --sync -n2..,.. --tiebreak=index --bind=ctrl-r:toggle-sort $FZF_CTRL_R_OPTS +m" $(__fzfcmd) |
|
FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} $FZF_DEFAULT_OPTS --tiebreak=index --bind=ctrl-r:toggle-sort $FZF_CTRL_R_OPTS +m --read0" $(__fzfcmd) --query "$READLINE_LINE"
|
||||||
command grep '^ *[0-9]') &&
|
) || return
|
||||||
if [[ $- =~ H ]]; then
|
READLINE_LINE=${output#*$'\t'}
|
||||||
sed 's/^ *\([0-9]*\)\** .*/!\1/' <<< "$line"
|
if [ -z "$READLINE_POINT" ]; then
|
||||||
else
|
echo "$READLINE_LINE"
|
||||||
sed 's/^ *\([0-9]*\)\** *//' <<< "$line"
|
|
||||||
fi
|
|
||||||
)
|
|
||||||
|
|
||||||
if [[ ! -o vi ]]; then
|
|
||||||
# Required to refresh the prompt after fzf
|
|
||||||
bind '"\er": redraw-current-line'
|
|
||||||
bind '"\e^": history-expand-line'
|
|
||||||
|
|
||||||
# CTRL-T - Paste the selected file path into the command line
|
|
||||||
if [ $BASH_VERSINFO -gt 3 ]; then
|
|
||||||
bind -x '"\C-t": "fzf-file-widget"'
|
|
||||||
elif __fzf_use_tmux__; then
|
|
||||||
bind '"\C-t": " \C-u \C-a\C-k`__fzf_select_tmux__`\e\C-e\C-y\C-a\C-d\C-y\ey\C-h"'
|
|
||||||
else
|
else
|
||||||
bind '"\C-t": " \C-u \C-a\C-k`__fzf_select__`\e\C-e\C-y\C-a\C-y\ey\C-h\C-e\er \C-h"'
|
READLINE_POINT=0x7fffffff
|
||||||
fi
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Required to refresh the prompt after fzf
|
||||||
|
bind -m emacs-standard '"\er": redraw-current-line'
|
||||||
|
|
||||||
|
bind -m vi-command '"\C-z": emacs-editing-mode'
|
||||||
|
bind -m vi-insert '"\C-z": emacs-editing-mode'
|
||||||
|
bind -m emacs-standard '"\C-z": vi-editing-mode'
|
||||||
|
|
||||||
|
if [ "${BASH_VERSINFO[0]}" -lt 4 ]; then
|
||||||
|
# CTRL-T - Paste the selected file path into the command line
|
||||||
|
if __fzf_use_tmux__; then
|
||||||
|
bind -m emacs-standard '"\C-t": " \C-b\C-k \C-u`__fzf_select_tmux__`\e\C-e\C-a\C-y\C-h\C-e\e \C-y\ey\C-x\C-x\C-f"'
|
||||||
|
else
|
||||||
|
bind -m emacs-standard '"\C-t": " \C-b\C-k \C-u`__fzf_select__`\e\C-e\er\C-a\C-y\C-h\C-e\e \C-y\ey\C-x\C-x\C-f"'
|
||||||
|
fi
|
||||||
|
bind -m vi-command '"\C-t": "\C-z\C-t\C-z"'
|
||||||
|
bind -m vi-insert '"\C-t": "\C-z\C-t\C-z"'
|
||||||
|
|
||||||
# CTRL-R - Paste the selected command from history into the command line
|
# CTRL-R - Paste the selected command from history into the command line
|
||||||
bind '"\C-r": " \C-e\C-u\C-y\ey\C-u`__fzf_history__`\e\C-e\er\e^"'
|
bind -m emacs-standard '"\C-r": "\C-e \C-u\C-y\ey\C-u"$(__fzf_history__)"\e\C-e\er"'
|
||||||
|
bind -m vi-command '"\C-r": "\C-z\C-r\C-z"'
|
||||||
# ALT-C - cd into the selected directory
|
bind -m vi-insert '"\C-r": "\C-z\C-r\C-z"'
|
||||||
bind '"\ec": " \C-e\C-u`__fzf_cd__`\e\C-e\er\C-m"'
|
|
||||||
else
|
else
|
||||||
# We'd usually use "\e" to enter vi-movement-mode so we can do our magic,
|
|
||||||
# but this incurs a very noticeable delay of a half second or so,
|
|
||||||
# because many other commands start with "\e".
|
|
||||||
# Instead, we bind an unused key, "\C-x\C-a",
|
|
||||||
# to also enter vi-movement-mode,
|
|
||||||
# and then use that thereafter.
|
|
||||||
# (We imagine that "\C-x\C-a" is relatively unlikely to be in use.)
|
|
||||||
bind '"\C-x\C-a": vi-movement-mode'
|
|
||||||
|
|
||||||
bind '"\C-x\C-e": shell-expand-line'
|
|
||||||
bind '"\C-x\C-r": redraw-current-line'
|
|
||||||
bind '"\C-x^": history-expand-line'
|
|
||||||
|
|
||||||
# CTRL-T - Paste the selected file path into the command line
|
# CTRL-T - Paste the selected file path into the command line
|
||||||
# - FIXME: Selected items are attached to the end regardless of cursor position
|
bind -m emacs-standard -x '"\C-t": fzf-file-widget'
|
||||||
if [ $BASH_VERSINFO -gt 3 ]; then
|
bind -m vi-command -x '"\C-t": fzf-file-widget'
|
||||||
bind -x '"\C-t": "fzf-file-widget"'
|
bind -m vi-insert -x '"\C-t": fzf-file-widget'
|
||||||
elif __fzf_use_tmux__; then
|
|
||||||
bind '"\C-t": "\C-x\C-a$a \C-x\C-addi`__fzf_select_tmux__`\C-x\C-e\C-x\C-a0P$xa"'
|
|
||||||
else
|
|
||||||
bind '"\C-t": "\C-x\C-a$a \C-x\C-addi`__fzf_select__`\C-x\C-e\C-x\C-a0Px$a \C-x\C-r\C-x\C-axa "'
|
|
||||||
fi
|
|
||||||
bind -m vi-command '"\C-t": "i\C-t"'
|
|
||||||
|
|
||||||
# CTRL-R - Paste the selected command from history into the command line
|
# CTRL-R - Paste the selected command from history into the command line
|
||||||
bind '"\C-r": "\C-x\C-addi`__fzf_history__`\C-x\C-e\C-x\C-r\C-x^\C-x\C-a$a"'
|
bind -m emacs-standard -x '"\C-r": __fzf_history__'
|
||||||
bind -m vi-command '"\C-r": "i\C-r"'
|
bind -m vi-command -x '"\C-r": __fzf_history__'
|
||||||
|
bind -m vi-insert -x '"\C-r": __fzf_history__'
|
||||||
# ALT-C - cd into the selected directory
|
|
||||||
bind '"\ec": "\C-x\C-addi`__fzf_cd__`\C-x\C-e\C-x\C-r\C-m"'
|
|
||||||
bind -m vi-command '"\ec": "ddi`__fzf_cd__`\C-x\C-e\C-x\C-r\C-m"'
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# ALT-C - cd into the selected directory
|
||||||
|
bind -m emacs-standard '"\ec": " \C-b\C-k \C-u`__fzf_cd__`\e\C-e\er\C-m\C-y\C-h\e \C-y\ey\C-x\C-x\C-d"'
|
||||||
|
bind -m vi-command '"\ec": "\C-z\ec\C-z"'
|
||||||
|
bind -m vi-insert '"\ec": "\C-z\ec\C-z"'
|
||||||
|
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -10,13 +10,13 @@ function fzf_key_bindings
|
|||||||
|
|
||||||
# "-path \$dir'*/\\.*'" matches hidden files/folders inside $dir but not
|
# "-path \$dir'*/\\.*'" matches hidden files/folders inside $dir but not
|
||||||
# $dir itself, even if hidden.
|
# $dir itself, even if hidden.
|
||||||
set -q FZF_CTRL_T_COMMAND; or set -l FZF_CTRL_T_COMMAND "
|
test -n "$FZF_CTRL_T_COMMAND"; or set -l FZF_CTRL_T_COMMAND "
|
||||||
command find -L \$dir -mindepth 1 \\( -path \$dir'*/\\.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' \\) -prune \
|
command find -L \$dir -mindepth 1 \\( -path \$dir'*/\\.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' \\) -prune \
|
||||||
-o -type f -print \
|
-o -type f -print \
|
||||||
-o -type d -print \
|
-o -type d -print \
|
||||||
-o -type l -print 2> /dev/null | sed 's@^\./@@'"
|
-o -type l -print 2> /dev/null | sed 's@^\./@@'"
|
||||||
|
|
||||||
set -q FZF_TMUX_HEIGHT; or set FZF_TMUX_HEIGHT 40%
|
test -n "$FZF_TMUX_HEIGHT"; or set FZF_TMUX_HEIGHT 40%
|
||||||
begin
|
begin
|
||||||
set -lx FZF_DEFAULT_OPTS "--height $FZF_TMUX_HEIGHT --reverse $FZF_DEFAULT_OPTS $FZF_CTRL_T_OPTS"
|
set -lx FZF_DEFAULT_OPTS "--height $FZF_TMUX_HEIGHT --reverse $FZF_DEFAULT_OPTS $FZF_CTRL_T_OPTS"
|
||||||
eval "$FZF_CTRL_T_COMMAND | "(__fzfcmd)' -m --query "'$fzf_query'"' | while read -l r; set result $result $r; end
|
eval "$FZF_CTRL_T_COMMAND | "(__fzfcmd)' -m --query "'$fzf_query'"' | while read -l r; set result $result $r; end
|
||||||
@@ -36,7 +36,7 @@ function fzf_key_bindings
|
|||||||
end
|
end
|
||||||
|
|
||||||
function fzf-history-widget -d "Show command history"
|
function fzf-history-widget -d "Show command history"
|
||||||
set -q FZF_TMUX_HEIGHT; or set FZF_TMUX_HEIGHT 40%
|
test -n "$FZF_TMUX_HEIGHT"; or set FZF_TMUX_HEIGHT 40%
|
||||||
begin
|
begin
|
||||||
set -lx FZF_DEFAULT_OPTS "--height $FZF_TMUX_HEIGHT $FZF_DEFAULT_OPTS --tiebreak=index --bind=ctrl-r:toggle-sort $FZF_CTRL_R_OPTS +m"
|
set -lx FZF_DEFAULT_OPTS "--height $FZF_TMUX_HEIGHT $FZF_DEFAULT_OPTS --tiebreak=index --bind=ctrl-r:toggle-sort $FZF_CTRL_R_OPTS +m"
|
||||||
|
|
||||||
@@ -62,10 +62,10 @@ function fzf_key_bindings
|
|||||||
set -l dir $commandline[1]
|
set -l dir $commandline[1]
|
||||||
set -l fzf_query $commandline[2]
|
set -l fzf_query $commandline[2]
|
||||||
|
|
||||||
set -q FZF_ALT_C_COMMAND; or set -l FZF_ALT_C_COMMAND "
|
test -n "$FZF_ALT_C_COMMAND"; or set -l FZF_ALT_C_COMMAND "
|
||||||
command find -L \$dir -mindepth 1 \\( -path \$dir'*/\\.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' \\) -prune \
|
command find -L \$dir -mindepth 1 \\( -path \$dir'*/\\.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' \\) -prune \
|
||||||
-o -type d -print 2> /dev/null | sed 's@^\./@@'"
|
-o -type d -print 2> /dev/null | sed 's@^\./@@'"
|
||||||
set -q FZF_TMUX_HEIGHT; or set FZF_TMUX_HEIGHT 40%
|
test -n "$FZF_TMUX_HEIGHT"; or set FZF_TMUX_HEIGHT 40%
|
||||||
begin
|
begin
|
||||||
set -lx FZF_DEFAULT_OPTS "--height $FZF_TMUX_HEIGHT --reverse $FZF_DEFAULT_OPTS $FZF_ALT_C_OPTS"
|
set -lx FZF_DEFAULT_OPTS "--height $FZF_TMUX_HEIGHT --reverse $FZF_DEFAULT_OPTS $FZF_ALT_C_OPTS"
|
||||||
eval "$FZF_ALT_C_COMMAND | "(__fzfcmd)' +m --query "'$fzf_query'"' | read -l result
|
eval "$FZF_ALT_C_COMMAND | "(__fzfcmd)' +m --query "'$fzf_query'"' | read -l result
|
||||||
@@ -82,8 +82,8 @@ function fzf_key_bindings
|
|||||||
end
|
end
|
||||||
|
|
||||||
function __fzfcmd
|
function __fzfcmd
|
||||||
set -q FZF_TMUX; or set FZF_TMUX 0
|
test -n "$FZF_TMUX"; or set FZF_TMUX 0
|
||||||
set -q FZF_TMUX_HEIGHT; or set FZF_TMUX_HEIGHT 40%
|
test -n "$FZF_TMUX_HEIGHT"; or set FZF_TMUX_HEIGHT 40%
|
||||||
if [ $FZF_TMUX -eq 1 ]
|
if [ $FZF_TMUX -eq 1 ]
|
||||||
echo "fzf-tmux -d$FZF_TMUX_HEIGHT"
|
echo "fzf-tmux -d$FZF_TMUX_HEIGHT"
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2017 Junegunn Choi
|
Copyright (c) 2013-2020 Junegunn Choi
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
|||||||
@@ -773,12 +773,17 @@ func PrefixMatch(caseSensitive bool, normalize bool, forward bool, text *util.Ch
|
|||||||
return Result{0, 0, 0}, nil
|
return Result{0, 0, 0}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if text.Length() < len(pattern) {
|
trimmedLen := 0
|
||||||
|
if !unicode.IsSpace(pattern[0]) {
|
||||||
|
trimmedLen = text.LeadingWhitespaces()
|
||||||
|
}
|
||||||
|
|
||||||
|
if text.Length()-trimmedLen < len(pattern) {
|
||||||
return Result{-1, -1, 0}, nil
|
return Result{-1, -1, 0}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
for index, r := range pattern {
|
for index, r := range pattern {
|
||||||
char := text.Get(index)
|
char := text.Get(trimmedLen + index)
|
||||||
if !caseSensitive {
|
if !caseSensitive {
|
||||||
char = unicode.ToLower(char)
|
char = unicode.ToLower(char)
|
||||||
}
|
}
|
||||||
@@ -790,14 +795,17 @@ func PrefixMatch(caseSensitive bool, normalize bool, forward bool, text *util.Ch
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
lenPattern := len(pattern)
|
lenPattern := len(pattern)
|
||||||
score, _ := calculateScore(caseSensitive, normalize, text, pattern, 0, lenPattern, false)
|
score, _ := calculateScore(caseSensitive, normalize, text, pattern, trimmedLen, trimmedLen+lenPattern, false)
|
||||||
return Result{0, lenPattern, score}, nil
|
return Result{trimmedLen, trimmedLen + lenPattern, score}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// SuffixMatch performs suffix-match
|
// SuffixMatch performs suffix-match
|
||||||
func SuffixMatch(caseSensitive bool, normalize bool, forward bool, text *util.Chars, pattern []rune, withPos bool, slab *util.Slab) (Result, *[]int) {
|
func SuffixMatch(caseSensitive bool, normalize bool, forward bool, text *util.Chars, pattern []rune, withPos bool, slab *util.Slab) (Result, *[]int) {
|
||||||
lenRunes := text.Length()
|
lenRunes := text.Length()
|
||||||
trimmedLen := lenRunes - text.TrailingWhitespaces()
|
trimmedLen := lenRunes
|
||||||
|
if len(pattern) == 0 || !unicode.IsSpace(pattern[len(pattern)-1]) {
|
||||||
|
trimmedLen -= text.TrailingWhitespaces()
|
||||||
|
}
|
||||||
if len(pattern) == 0 {
|
if len(pattern) == 0 {
|
||||||
return Result{trimmedLen, trimmedLen, 0}, nil
|
return Result{trimmedLen, trimmedLen, 0}, nil
|
||||||
}
|
}
|
||||||
@@ -828,14 +836,30 @@ func SuffixMatch(caseSensitive bool, normalize bool, forward bool, text *util.Ch
|
|||||||
// EqualMatch performs equal-match
|
// EqualMatch performs equal-match
|
||||||
func EqualMatch(caseSensitive bool, normalize bool, forward bool, text *util.Chars, pattern []rune, withPos bool, slab *util.Slab) (Result, *[]int) {
|
func EqualMatch(caseSensitive bool, normalize bool, forward bool, text *util.Chars, pattern []rune, withPos bool, slab *util.Slab) (Result, *[]int) {
|
||||||
lenPattern := len(pattern)
|
lenPattern := len(pattern)
|
||||||
if text.Length() != lenPattern {
|
if lenPattern == 0 {
|
||||||
|
return Result{-1, -1, 0}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Strip leading whitespaces
|
||||||
|
trimmedLen := 0
|
||||||
|
if !unicode.IsSpace(pattern[0]) {
|
||||||
|
trimmedLen = text.LeadingWhitespaces()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Strip trailing whitespaces
|
||||||
|
trimmedEndLen := 0
|
||||||
|
if !unicode.IsSpace(pattern[lenPattern-1]) {
|
||||||
|
trimmedEndLen = text.TrailingWhitespaces()
|
||||||
|
}
|
||||||
|
|
||||||
|
if text.Length()-trimmedLen-trimmedEndLen != lenPattern {
|
||||||
return Result{-1, -1, 0}, nil
|
return Result{-1, -1, 0}, nil
|
||||||
}
|
}
|
||||||
match := true
|
match := true
|
||||||
if normalize {
|
if normalize {
|
||||||
runes := text.ToRunes()
|
runes := text.ToRunes()
|
||||||
for idx, pchar := range pattern {
|
for idx, pchar := range pattern {
|
||||||
char := runes[idx]
|
char := runes[trimmedLen+idx]
|
||||||
if !caseSensitive {
|
if !caseSensitive {
|
||||||
char = unicode.To(unicode.LowerCase, char)
|
char = unicode.To(unicode.LowerCase, char)
|
||||||
}
|
}
|
||||||
@@ -845,14 +869,15 @@ func EqualMatch(caseSensitive bool, normalize bool, forward bool, text *util.Cha
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
runesStr := text.ToString()
|
runes := text.ToRunes()
|
||||||
|
runesStr := string(runes[trimmedLen : len(runes)-trimmedEndLen])
|
||||||
if !caseSensitive {
|
if !caseSensitive {
|
||||||
runesStr = strings.ToLower(runesStr)
|
runesStr = strings.ToLower(runesStr)
|
||||||
}
|
}
|
||||||
match = runesStr == string(pattern)
|
match = runesStr == string(pattern)
|
||||||
}
|
}
|
||||||
if match {
|
if match {
|
||||||
return Result{0, lenPattern, (scoreMatch+bonusBoundary)*lenPattern +
|
return Result{trimmedLen, trimmedLen + lenPattern, (scoreMatch+bonusBoundary)*lenPattern +
|
||||||
(bonusFirstCharMultiplier-1)*bonusBoundary}, nil
|
(bonusFirstCharMultiplier-1)*bonusBoundary}, nil
|
||||||
}
|
}
|
||||||
return Result{-1, -1, 0}, nil
|
return Result{-1, -1, 0}, nil
|
||||||
|
|||||||
@@ -136,6 +136,10 @@ func TestPrefixMatch(t *testing.T) {
|
|||||||
assertMatch(t, PrefixMatch, false, dir, "fooBarbaz", "Foo", 0, 3, score)
|
assertMatch(t, PrefixMatch, false, dir, "fooBarbaz", "Foo", 0, 3, score)
|
||||||
assertMatch(t, PrefixMatch, false, dir, "foOBarBaZ", "foo", 0, 3, score)
|
assertMatch(t, PrefixMatch, false, dir, "foOBarBaZ", "foo", 0, 3, score)
|
||||||
assertMatch(t, PrefixMatch, false, dir, "f-oBarbaz", "f-o", 0, 3, score)
|
assertMatch(t, PrefixMatch, false, dir, "f-oBarbaz", "f-o", 0, 3, score)
|
||||||
|
|
||||||
|
assertMatch(t, PrefixMatch, false, dir, " fooBar", "foo", 1, 4, score)
|
||||||
|
assertMatch(t, PrefixMatch, false, dir, " fooBar", " fo", 0, 3, score)
|
||||||
|
assertMatch(t, PrefixMatch, false, dir, " fo", "foo", -1, -1, 0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -148,6 +152,13 @@ func TestSuffixMatch(t *testing.T) {
|
|||||||
scoreMatch*3+bonusConsecutive*2)
|
scoreMatch*3+bonusConsecutive*2)
|
||||||
assertMatch(t, SuffixMatch, false, dir, "fooBarBaZ", "baz", 6, 9,
|
assertMatch(t, SuffixMatch, false, dir, "fooBarBaZ", "baz", 6, 9,
|
||||||
(scoreMatch+bonusCamel123)*3+bonusCamel123*(bonusFirstCharMultiplier-1))
|
(scoreMatch+bonusCamel123)*3+bonusCamel123*(bonusFirstCharMultiplier-1))
|
||||||
|
|
||||||
|
// Strip trailing white space from the string
|
||||||
|
assertMatch(t, SuffixMatch, false, dir, "fooBarbaz ", "baz", 6, 9,
|
||||||
|
scoreMatch*3+bonusConsecutive*2)
|
||||||
|
// Only when the pattern doesn't end with a space
|
||||||
|
assertMatch(t, SuffixMatch, false, dir, "fooBarbaz ", "baz ", 6, 10,
|
||||||
|
scoreMatch*4+bonusConsecutive*2+bonusNonWord)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -90,10 +90,11 @@ func init() {
|
|||||||
- http://ascii-table.com/ansi-escape-sequences.php
|
- http://ascii-table.com/ansi-escape-sequences.php
|
||||||
- http://ascii-table.com/ansi-escape-sequences-vt-100.php
|
- http://ascii-table.com/ansi-escape-sequences-vt-100.php
|
||||||
- http://tldp.org/HOWTO/Bash-Prompt-HOWTO/x405.html
|
- http://tldp.org/HOWTO/Bash-Prompt-HOWTO/x405.html
|
||||||
|
- https://invisible-island.net/xterm/ctlseqs/ctlseqs.html
|
||||||
*/
|
*/
|
||||||
// The following regular expression will include not all but most of the
|
// The following regular expression will include not all but most of the
|
||||||
// frequently used ANSI sequences
|
// frequently used ANSI sequences
|
||||||
ansiRegex = regexp.MustCompile("(?:\x1b[\\[()][0-9;]*[a-zA-Z@]|\x1b.|[\x0e\x0f]|.\x08)")
|
ansiRegex = regexp.MustCompile("(?:\x1b[\\[()][0-9;]*[a-zA-Z@]|\x1b][0-9];[[:print:]]+(?:\x1b\\\\|\x07)|\x1b.|[\x0e\x0f]|.\x08)")
|
||||||
}
|
}
|
||||||
|
|
||||||
func findAnsiStart(str string) int {
|
func findAnsiStart(str string) int {
|
||||||
@@ -243,6 +244,10 @@ func interpretCode(ansiCode string, prevState *ansiState) *ansiState {
|
|||||||
state.attr = state.attr | tui.Blink
|
state.attr = state.attr | tui.Blink
|
||||||
case 7:
|
case 7:
|
||||||
state.attr = state.attr | tui.Reverse
|
state.attr = state.attr | tui.Reverse
|
||||||
|
case 23: // tput rmso
|
||||||
|
state.attr = state.attr &^ tui.Italic
|
||||||
|
case 24: // tput rmul
|
||||||
|
state.attr = state.attr &^ tui.Underline
|
||||||
case 0:
|
case 0:
|
||||||
init()
|
init()
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import (
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
// Current version
|
// Current version
|
||||||
version = "0.20.0"
|
version = "0.21.0"
|
||||||
|
|
||||||
// Core
|
// Core
|
||||||
coordinatorDelayMax time.Duration = 100 * time.Millisecond
|
coordinatorDelayMax time.Duration = 100 * time.Millisecond
|
||||||
@@ -25,7 +25,7 @@ const (
|
|||||||
// Terminal
|
// Terminal
|
||||||
initialDelay = 20 * time.Millisecond
|
initialDelay = 20 * time.Millisecond
|
||||||
initialDelayTac = 100 * time.Millisecond
|
initialDelayTac = 100 * time.Millisecond
|
||||||
spinnerDuration = 200 * time.Millisecond
|
spinnerDuration = 100 * time.Millisecond
|
||||||
previewCancelWait = 500 * time.Millisecond
|
previewCancelWait = 500 * time.Millisecond
|
||||||
maxPatternLength = 300
|
maxPatternLength = 300
|
||||||
maxMulti = math.MaxInt32
|
maxMulti = math.MaxInt32
|
||||||
@@ -62,8 +62,6 @@ func init() {
|
|||||||
defaultCommand = `set -o pipefail; command find -L . -mindepth 1 \( -path '*/\.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' -o -fstype 'proc' \) -prune -o -type f -print -o -type l -print 2> /dev/null | cut -b3-`
|
defaultCommand = `set -o pipefail; command find -L . -mindepth 1 \( -path '*/\.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' -o -fstype 'proc' \) -prune -o -type f -print -o -type l -print 2> /dev/null | cut -b3-`
|
||||||
} else if os.Getenv("TERM") == "cygwin" {
|
} else if os.Getenv("TERM") == "cygwin" {
|
||||||
defaultCommand = `sh -c "command find -L . -mindepth 1 -path '*/\.*' -prune -o -type f -print -o -type l -print 2> /dev/null | cut -b3-"`
|
defaultCommand = `sh -c "command find -L . -mindepth 1 -path '*/\.*' -prune -o -type f -print -o -type l -print 2> /dev/null | cut -b3-"`
|
||||||
} else {
|
|
||||||
defaultCommand = `for /r %P in (*) do @(set "_curfile=%P" & set "_curfile=!_curfile:%__CD__%=!" & echo !_curfile!)`
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
107
src/options.go
107
src/options.go
@@ -6,12 +6,13 @@ import (
|
|||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"unicode"
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
|
|
||||||
"github.com/junegunn/fzf/src/algo"
|
"github.com/junegunn/fzf/src/algo"
|
||||||
"github.com/junegunn/fzf/src/tui"
|
"github.com/junegunn/fzf/src/tui"
|
||||||
"github.com/junegunn/fzf/src/util"
|
|
||||||
|
|
||||||
|
"github.com/mattn/go-runewidth"
|
||||||
"github.com/mattn/go-shellwords"
|
"github.com/mattn/go-shellwords"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -43,6 +44,7 @@ const usage = `usage: fzf [options]
|
|||||||
--no-mouse Disable mouse
|
--no-mouse Disable mouse
|
||||||
--bind=KEYBINDS Custom key bindings. Refer to the man page.
|
--bind=KEYBINDS Custom key bindings. Refer to the man page.
|
||||||
--cycle Enable cyclic scroll
|
--cycle Enable cyclic scroll
|
||||||
|
--keep-right Keep the right end of the line visible on overflow
|
||||||
--no-hscroll Disable horizontal scroll
|
--no-hscroll Disable horizontal scroll
|
||||||
--hscroll-off=COL Number of screen columns to keep to the right of the
|
--hscroll-off=COL Number of screen columns to keep to the right of the
|
||||||
highlighted substring (default: 10)
|
highlighted substring (default: 10)
|
||||||
@@ -55,10 +57,13 @@ const usage = `usage: fzf [options]
|
|||||||
--min-height=HEIGHT Minimum height when --height is given in percent
|
--min-height=HEIGHT Minimum height when --height is given in percent
|
||||||
(default: 10)
|
(default: 10)
|
||||||
--layout=LAYOUT Choose layout: [default|reverse|reverse-list]
|
--layout=LAYOUT Choose layout: [default|reverse|reverse-list]
|
||||||
--border Draw border above and below the finder
|
--border[=STYLE] Draw border around the finder
|
||||||
|
[rounded|sharp|horizontal] (default: rounded)
|
||||||
--margin=MARGIN Screen margin (TRBL / TB,RL / T,RL,B / T,R,B,L)
|
--margin=MARGIN Screen margin (TRBL / TB,RL / T,RL,B / T,R,B,L)
|
||||||
--info=STYLE Finder info style [default|inline|hidden]
|
--info=STYLE Finder info style [default|inline|hidden]
|
||||||
--prompt=STR Input prompt (default: '> ')
|
--prompt=STR Input prompt (default: '> ')
|
||||||
|
--pointer=STR Pointer to the current line (default: '>')
|
||||||
|
--marker=STR Multi-select marker (default: '>')
|
||||||
--header=STR String to print as header
|
--header=STR String to print as header
|
||||||
--header-lines=N The first N lines of the input are treated as header
|
--header-lines=N The first N lines of the input are treated as header
|
||||||
|
|
||||||
@@ -183,12 +188,15 @@ type Options struct {
|
|||||||
MinHeight int
|
MinHeight int
|
||||||
Layout layoutType
|
Layout layoutType
|
||||||
Cycle bool
|
Cycle bool
|
||||||
|
KeepRight bool
|
||||||
Hscroll bool
|
Hscroll bool
|
||||||
HscrollOff int
|
HscrollOff int
|
||||||
FileWord bool
|
FileWord bool
|
||||||
InfoStyle infoStyle
|
InfoStyle infoStyle
|
||||||
JumpLabels string
|
JumpLabels string
|
||||||
Prompt string
|
Prompt string
|
||||||
|
Pointer string
|
||||||
|
Marker string
|
||||||
Query string
|
Query string
|
||||||
Select1 bool
|
Select1 bool
|
||||||
Exit0 bool
|
Exit0 bool
|
||||||
@@ -206,7 +214,7 @@ type Options struct {
|
|||||||
Header []string
|
Header []string
|
||||||
HeaderLines int
|
HeaderLines int
|
||||||
Margin [4]sizeSpec
|
Margin [4]sizeSpec
|
||||||
Bordered bool
|
BorderShape tui.BorderShape
|
||||||
Unicode bool
|
Unicode bool
|
||||||
Tabstop int
|
Tabstop int
|
||||||
ClearOnExit bool
|
ClearOnExit bool
|
||||||
@@ -236,12 +244,15 @@ func defaultOptions() *Options {
|
|||||||
MinHeight: 10,
|
MinHeight: 10,
|
||||||
Layout: layoutDefault,
|
Layout: layoutDefault,
|
||||||
Cycle: false,
|
Cycle: false,
|
||||||
|
KeepRight: false,
|
||||||
Hscroll: true,
|
Hscroll: true,
|
||||||
HscrollOff: 10,
|
HscrollOff: 10,
|
||||||
FileWord: false,
|
FileWord: false,
|
||||||
InfoStyle: infoDefault,
|
InfoStyle: infoDefault,
|
||||||
JumpLabels: defaultJumpLabels,
|
JumpLabels: defaultJumpLabels,
|
||||||
Prompt: "> ",
|
Prompt: "> ",
|
||||||
|
Pointer: ">",
|
||||||
|
Marker: ">",
|
||||||
Query: "",
|
Query: "",
|
||||||
Select1: false,
|
Select1: false,
|
||||||
Exit0: false,
|
Exit0: false,
|
||||||
@@ -293,12 +304,12 @@ func nextString(args []string, i *int, message string) string {
|
|||||||
return args[*i]
|
return args[*i]
|
||||||
}
|
}
|
||||||
|
|
||||||
func optionalNextString(args []string, i *int) string {
|
func optionalNextString(args []string, i *int) (bool, string) {
|
||||||
if len(args) > *i+1 && !strings.HasPrefix(args[*i+1], "-") {
|
if len(args) > *i+1 && !strings.HasPrefix(args[*i+1], "-") && !strings.HasPrefix(args[*i+1], "+") {
|
||||||
*i++
|
*i++
|
||||||
return args[*i]
|
return true, args[*i]
|
||||||
}
|
}
|
||||||
return ""
|
return false, ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func atoi(str string) int {
|
func atoi(str string) int {
|
||||||
@@ -392,6 +403,23 @@ func parseAlgo(str string) algo.Algo {
|
|||||||
return algo.FuzzyMatchV2
|
return algo.FuzzyMatchV2
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func parseBorder(str string, optional bool) tui.BorderShape {
|
||||||
|
switch str {
|
||||||
|
case "rounded":
|
||||||
|
return tui.BorderRounded
|
||||||
|
case "sharp":
|
||||||
|
return tui.BorderSharp
|
||||||
|
case "horizontal":
|
||||||
|
return tui.BorderHorizontal
|
||||||
|
default:
|
||||||
|
if optional && str == "" {
|
||||||
|
return tui.BorderRounded
|
||||||
|
}
|
||||||
|
errorExit("invalid border style (expected: rounded|sharp|horizontal)")
|
||||||
|
}
|
||||||
|
return tui.BorderNone
|
||||||
|
}
|
||||||
|
|
||||||
func parseKeyChords(str string, message string) map[int]string {
|
func parseKeyChords(str string, message string) map[int]string {
|
||||||
if len(str) == 0 {
|
if len(str) == 0 {
|
||||||
errorExit(message)
|
errorExit(message)
|
||||||
@@ -464,6 +492,8 @@ func parseKeyChords(str string, message string) map[int]string {
|
|||||||
chord = tui.Home
|
chord = tui.Home
|
||||||
case "end":
|
case "end":
|
||||||
chord = tui.End
|
chord = tui.End
|
||||||
|
case "insert":
|
||||||
|
chord = tui.Insert
|
||||||
case "pgup", "page-up":
|
case "pgup", "page-up":
|
||||||
chord = tui.PgUp
|
chord = tui.PgUp
|
||||||
case "pgdn", "page-down":
|
case "pgdn", "page-down":
|
||||||
@@ -730,6 +760,8 @@ func parseKeymap(keymap map[int][]action, str string) {
|
|||||||
appendAction(actBackwardChar)
|
appendAction(actBackwardChar)
|
||||||
case "backward-delete-char":
|
case "backward-delete-char":
|
||||||
appendAction(actBackwardDeleteChar)
|
appendAction(actBackwardDeleteChar)
|
||||||
|
case "backward-delete-char/eof":
|
||||||
|
appendAction(actBackwardDeleteCharEOF)
|
||||||
case "backward-word":
|
case "backward-word":
|
||||||
appendAction(actBackwardWord)
|
appendAction(actBackwardWord)
|
||||||
case "clear-screen":
|
case "clear-screen":
|
||||||
@@ -1041,6 +1073,8 @@ func parseOptions(opts *Options, allArgs []string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
validateJumpLabels := false
|
validateJumpLabels := false
|
||||||
|
validatePointer := false
|
||||||
|
validateMarker := false
|
||||||
for i := 0; i < len(allArgs); i++ {
|
for i := 0; i < len(allArgs); i++ {
|
||||||
arg := allArgs[i]
|
arg := allArgs[i]
|
||||||
switch arg {
|
switch arg {
|
||||||
@@ -1084,7 +1118,7 @@ func parseOptions(opts *Options, allArgs []string) {
|
|||||||
case "--bind":
|
case "--bind":
|
||||||
parseKeymap(opts.Keymap, nextString(allArgs, &i, "bind expression required"))
|
parseKeymap(opts.Keymap, nextString(allArgs, &i, "bind expression required"))
|
||||||
case "--color":
|
case "--color":
|
||||||
spec := optionalNextString(allArgs, &i)
|
_, spec := optionalNextString(allArgs, &i)
|
||||||
if len(spec) == 0 {
|
if len(spec) == 0 {
|
||||||
opts.Theme = tui.EmptyTheme()
|
opts.Theme = tui.EmptyTheme()
|
||||||
} else {
|
} else {
|
||||||
@@ -1143,6 +1177,10 @@ func parseOptions(opts *Options, allArgs []string) {
|
|||||||
opts.Cycle = true
|
opts.Cycle = true
|
||||||
case "--no-cycle":
|
case "--no-cycle":
|
||||||
opts.Cycle = false
|
opts.Cycle = false
|
||||||
|
case "--keep-right":
|
||||||
|
opts.KeepRight = true
|
||||||
|
case "--no-keep-right":
|
||||||
|
opts.KeepRight = false
|
||||||
case "--hscroll":
|
case "--hscroll":
|
||||||
opts.Hscroll = true
|
opts.Hscroll = true
|
||||||
case "--no-hscroll":
|
case "--no-hscroll":
|
||||||
@@ -1189,6 +1227,12 @@ func parseOptions(opts *Options, allArgs []string) {
|
|||||||
opts.PrintQuery = false
|
opts.PrintQuery = false
|
||||||
case "--prompt":
|
case "--prompt":
|
||||||
opts.Prompt = nextString(allArgs, &i, "prompt string required")
|
opts.Prompt = nextString(allArgs, &i, "prompt string required")
|
||||||
|
case "--pointer":
|
||||||
|
opts.Pointer = nextString(allArgs, &i, "pointer sign string required")
|
||||||
|
validatePointer = true
|
||||||
|
case "--marker":
|
||||||
|
opts.Marker = nextString(allArgs, &i, "selected sign string required")
|
||||||
|
validateMarker = true
|
||||||
case "--sync":
|
case "--sync":
|
||||||
opts.Sync = true
|
opts.Sync = true
|
||||||
case "--no-sync":
|
case "--no-sync":
|
||||||
@@ -1226,9 +1270,10 @@ func parseOptions(opts *Options, allArgs []string) {
|
|||||||
case "--no-margin":
|
case "--no-margin":
|
||||||
opts.Margin = defaultMargin()
|
opts.Margin = defaultMargin()
|
||||||
case "--no-border":
|
case "--no-border":
|
||||||
opts.Bordered = false
|
opts.BorderShape = tui.BorderNone
|
||||||
case "--border":
|
case "--border":
|
||||||
opts.Bordered = true
|
hasArg, arg := optionalNextString(allArgs, &i)
|
||||||
|
opts.BorderShape = parseBorder(arg, !hasArg)
|
||||||
case "--no-unicode":
|
case "--no-unicode":
|
||||||
opts.Unicode = false
|
opts.Unicode = false
|
||||||
case "--unicode":
|
case "--unicode":
|
||||||
@@ -1253,8 +1298,16 @@ func parseOptions(opts *Options, allArgs []string) {
|
|||||||
opts.Filter = &value
|
opts.Filter = &value
|
||||||
} else if match, value := optString(arg, "-d", "--delimiter="); match {
|
} else if match, value := optString(arg, "-d", "--delimiter="); match {
|
||||||
opts.Delimiter = delimiterRegexp(value)
|
opts.Delimiter = delimiterRegexp(value)
|
||||||
|
} else if match, value := optString(arg, "--border="); match {
|
||||||
|
opts.BorderShape = parseBorder(value, false)
|
||||||
} else if match, value := optString(arg, "--prompt="); match {
|
} else if match, value := optString(arg, "--prompt="); match {
|
||||||
opts.Prompt = value
|
opts.Prompt = value
|
||||||
|
} else if match, value := optString(arg, "--pointer="); match {
|
||||||
|
opts.Pointer = value
|
||||||
|
validatePointer = true
|
||||||
|
} else if match, value := optString(arg, "--marker="); match {
|
||||||
|
opts.Marker = value
|
||||||
|
validateMarker = true
|
||||||
} else if match, value := optString(arg, "-n", "--nth="); match {
|
} else if match, value := optString(arg, "-n", "--nth="); match {
|
||||||
opts.Nth = splitNth(value)
|
opts.Nth = splitNth(value)
|
||||||
} else if match, value := optString(arg, "--with-nth="); match {
|
} else if match, value := optString(arg, "--with-nth="); match {
|
||||||
@@ -1303,6 +1356,7 @@ func parseOptions(opts *Options, allArgs []string) {
|
|||||||
opts.HscrollOff = atoi(value)
|
opts.HscrollOff = atoi(value)
|
||||||
} else if match, value := optString(arg, "--jump-labels="); match {
|
} else if match, value := optString(arg, "--jump-labels="); match {
|
||||||
opts.JumpLabels = value
|
opts.JumpLabels = value
|
||||||
|
validateJumpLabels = true
|
||||||
} else {
|
} else {
|
||||||
errorExit("unknown option: " + arg)
|
errorExit("unknown option: " + arg)
|
||||||
}
|
}
|
||||||
@@ -1332,11 +1386,40 @@ func parseOptions(opts *Options, allArgs []string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if validatePointer {
|
||||||
|
if err := validateSign(opts.Pointer, "pointer"); err != nil {
|
||||||
|
errorExit(err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if validateMarker {
|
||||||
|
if err := validateSign(opts.Marker, "marker"); err != nil {
|
||||||
|
errorExit(err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateSign(sign string, signOptName string) error {
|
||||||
|
if sign == "" {
|
||||||
|
return fmt.Errorf("%v cannot be empty", signOptName)
|
||||||
|
}
|
||||||
|
widthSum := 0
|
||||||
|
for _, r := range sign {
|
||||||
|
if !unicode.IsGraphic(r) {
|
||||||
|
return fmt.Errorf("invalid character in %v", signOptName)
|
||||||
|
}
|
||||||
|
widthSum += runewidth.RuneWidth(r)
|
||||||
|
if widthSum > 2 {
|
||||||
|
return fmt.Errorf("%v display width should be up to 2", signOptName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func postProcessOptions(opts *Options) {
|
func postProcessOptions(opts *Options) {
|
||||||
if util.IsWindows() && opts.Height.size > 0 {
|
if !tui.IsLightRendererSupported() && opts.Height.size > 0 {
|
||||||
errorExit("--height option is currently not supported on Windows")
|
errorExit("--height option is currently not supported on this platform")
|
||||||
}
|
}
|
||||||
// Default actions for CTRL-N / CTRL-P when --history is set
|
// Default actions for CTRL-N / CTRL-P when --history is set
|
||||||
if opts.History != nil {
|
if opts.History != nil {
|
||||||
|
|||||||
@@ -422,3 +422,29 @@ func TestAdditiveExpect(t *testing.T) {
|
|||||||
t.Error(opts.Expect)
|
t.Error(opts.Expect)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestValidateSign(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
inputSign string
|
||||||
|
isValid bool
|
||||||
|
}{
|
||||||
|
{"> ", true},
|
||||||
|
{"아", true},
|
||||||
|
{"😀", true},
|
||||||
|
{"", false},
|
||||||
|
{">>>", false},
|
||||||
|
{"\n", false},
|
||||||
|
{"\t", false},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, testCase := range testCases {
|
||||||
|
err := validateSign(testCase.inputSign, "")
|
||||||
|
if testCase.isValid && err != nil {
|
||||||
|
t.Errorf("Input sign `%s` caused error", testCase.inputSign)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !testCase.isValid && err == nil {
|
||||||
|
t.Errorf("Input sign `%s` did not cause error", testCase.inputSign)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -98,6 +98,9 @@ func TestEqual(t *testing.T) {
|
|||||||
}
|
}
|
||||||
match("ABC", -1, -1)
|
match("ABC", -1, -1)
|
||||||
match("AbC", 0, 3)
|
match("AbC", 0, 3)
|
||||||
|
match("AbC ", 0, 3)
|
||||||
|
match(" AbC ", 1, 4)
|
||||||
|
match(" AbC", 2, 5)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCaseSensitivity(t *testing.T) {
|
func TestCaseSensitivity(t *testing.T) {
|
||||||
|
|||||||
8
src/protector/protector.go
Normal file
8
src/protector/protector.go
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
// +build !openbsd
|
||||||
|
|
||||||
|
package protector
|
||||||
|
|
||||||
|
// Protect calls OS specific protections like pledge on OpenBSD
|
||||||
|
func Protect() {
|
||||||
|
return
|
||||||
|
}
|
||||||
10
src/protector/protector_openbsd.go
Normal file
10
src/protector/protector_openbsd.go
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
// +build openbsd
|
||||||
|
|
||||||
|
package protector
|
||||||
|
|
||||||
|
import "golang.org/x/sys/unix"
|
||||||
|
|
||||||
|
// Protect calls OS specific protections like pledge on OpenBSD
|
||||||
|
func Protect() {
|
||||||
|
unix.PledgePromises("stdio rpath tty proc exec")
|
||||||
|
}
|
||||||
@@ -2,14 +2,17 @@ package fzf
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
|
"context"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/junegunn/fzf/src/util"
|
"github.com/junegunn/fzf/src/util"
|
||||||
|
"github.com/saracen/walker"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Reader reads from command or standard input
|
// Reader reads from command or standard input
|
||||||
@@ -78,7 +81,7 @@ func (r *Reader) terminate() {
|
|||||||
r.killed = true
|
r.killed = true
|
||||||
if r.exec != nil && r.exec.Process != nil {
|
if r.exec != nil && r.exec.Process != nil {
|
||||||
util.KillCommand(r.exec)
|
util.KillCommand(r.exec)
|
||||||
} else {
|
} else if defaultCommand != "" {
|
||||||
os.Stdin.Close()
|
os.Stdin.Close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -99,7 +102,11 @@ func (r *Reader) ReadSource() {
|
|||||||
shell := "bash"
|
shell := "bash"
|
||||||
cmd := os.Getenv("FZF_DEFAULT_COMMAND")
|
cmd := os.Getenv("FZF_DEFAULT_COMMAND")
|
||||||
if len(cmd) == 0 {
|
if len(cmd) == 0 {
|
||||||
success = r.readFromCommand(&shell, defaultCommand)
|
if defaultCommand != "" {
|
||||||
|
success = r.readFromCommand(&shell, defaultCommand)
|
||||||
|
} else {
|
||||||
|
success = r.readFiles()
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
success = r.readFromCommand(nil, cmd)
|
success = r.readFromCommand(nil, cmd)
|
||||||
}
|
}
|
||||||
@@ -144,6 +151,31 @@ func (r *Reader) readFromStdin() bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *Reader) readFiles() bool {
|
||||||
|
r.killed = false
|
||||||
|
fn := func(path string, mode os.FileInfo) error {
|
||||||
|
path = filepath.Clean(path)
|
||||||
|
if path != "." {
|
||||||
|
if mode.Mode().IsDir() && filepath.Base(path)[0] == '.' {
|
||||||
|
return filepath.SkipDir
|
||||||
|
}
|
||||||
|
if r.pusher([]byte(path)) {
|
||||||
|
atomic.StoreInt32(&r.event, int32(EvtReadNew))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
r.mutex.Lock()
|
||||||
|
defer r.mutex.Unlock()
|
||||||
|
if r.killed {
|
||||||
|
return context.Canceled
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
cb := walker.WithErrorCallback(func(pathname string, err error) error {
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
return walker.Walk(".", fn, cb) == nil
|
||||||
|
}
|
||||||
|
|
||||||
func (r *Reader) readFromCommand(shell *string, command string) bool {
|
func (r *Reader) readFromCommand(shell *string, command string) bool {
|
||||||
r.mutex.Lock()
|
r.mutex.Lock()
|
||||||
r.killed = false
|
r.killed = false
|
||||||
|
|||||||
337
src/terminal.go
337
src/terminal.go
@@ -25,6 +25,8 @@ import (
|
|||||||
var placeholder *regexp.Regexp
|
var placeholder *regexp.Regexp
|
||||||
var activeTempFiles []string
|
var activeTempFiles []string
|
||||||
|
|
||||||
|
const ellipsis string = ".."
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
placeholder = regexp.MustCompile(`\\?(?:{[+sf]*[0-9,-.]*}|{q}|{\+?f?nf?})`)
|
placeholder = regexp.MustCompile(`\\?(?:{[+sf]*[0-9,-.]*}|{q}|{\+?f?nf?})`)
|
||||||
activeTempFiles = []string{}
|
activeTempFiles = []string{}
|
||||||
@@ -59,71 +61,79 @@ var emptyLine = itemLine{}
|
|||||||
|
|
||||||
// Terminal represents terminal input/output
|
// Terminal represents terminal input/output
|
||||||
type Terminal struct {
|
type Terminal struct {
|
||||||
initDelay time.Duration
|
initDelay time.Duration
|
||||||
infoStyle infoStyle
|
infoStyle infoStyle
|
||||||
prompt string
|
spinner []string
|
||||||
promptLen int
|
prompt string
|
||||||
queryLen [2]int
|
promptLen int
|
||||||
layout layoutType
|
pointer string
|
||||||
fullscreen bool
|
pointerLen int
|
||||||
hscroll bool
|
pointerEmpty string
|
||||||
hscrollOff int
|
marker string
|
||||||
wordRubout string
|
markerLen int
|
||||||
wordNext string
|
markerEmpty string
|
||||||
cx int
|
queryLen [2]int
|
||||||
cy int
|
layout layoutType
|
||||||
offset int
|
fullscreen bool
|
||||||
xoffset int
|
keepRight bool
|
||||||
yanked []rune
|
hscroll bool
|
||||||
input []rune
|
hscrollOff int
|
||||||
multi int
|
wordRubout string
|
||||||
sort bool
|
wordNext string
|
||||||
toggleSort bool
|
cx int
|
||||||
delimiter Delimiter
|
cy int
|
||||||
expect map[int]string
|
offset int
|
||||||
keymap map[int][]action
|
xoffset int
|
||||||
pressed string
|
yanked []rune
|
||||||
printQuery bool
|
input []rune
|
||||||
history *History
|
multi int
|
||||||
cycle bool
|
sort bool
|
||||||
header []string
|
toggleSort bool
|
||||||
header0 []string
|
delimiter Delimiter
|
||||||
ansi bool
|
expect map[int]string
|
||||||
tabstop int
|
keymap map[int][]action
|
||||||
margin [4]sizeSpec
|
pressed string
|
||||||
strong tui.Attr
|
printQuery bool
|
||||||
unicode bool
|
history *History
|
||||||
bordered bool
|
cycle bool
|
||||||
cleanExit bool
|
header []string
|
||||||
border tui.Window
|
header0 []string
|
||||||
window tui.Window
|
ansi bool
|
||||||
pborder tui.Window
|
tabstop int
|
||||||
pwindow tui.Window
|
margin [4]sizeSpec
|
||||||
count int
|
strong tui.Attr
|
||||||
progress int
|
unicode bool
|
||||||
reading bool
|
borderShape tui.BorderShape
|
||||||
failed *string
|
cleanExit bool
|
||||||
jumping jumpMode
|
border tui.Window
|
||||||
jumpLabels string
|
window tui.Window
|
||||||
printer func(string)
|
pborder tui.Window
|
||||||
printsep string
|
pwindow tui.Window
|
||||||
merger *Merger
|
count int
|
||||||
selected map[int32]selectedItem
|
progress int
|
||||||
version int64
|
reading bool
|
||||||
reqBox *util.EventBox
|
failed *string
|
||||||
preview previewOpts
|
jumping jumpMode
|
||||||
previewer previewer
|
jumpLabels string
|
||||||
previewBox *util.EventBox
|
printer func(string)
|
||||||
eventBox *util.EventBox
|
printsep string
|
||||||
mutex sync.Mutex
|
merger *Merger
|
||||||
initFunc func()
|
selected map[int32]selectedItem
|
||||||
prevLines []itemLine
|
version int64
|
||||||
suppress bool
|
reqBox *util.EventBox
|
||||||
startChan chan bool
|
preview previewOpts
|
||||||
killChan chan int
|
previewer previewer
|
||||||
slab *util.Slab
|
previewBox *util.EventBox
|
||||||
theme *tui.ColorTheme
|
eventBox *util.EventBox
|
||||||
tui tui.Renderer
|
mutex sync.Mutex
|
||||||
|
initFunc func()
|
||||||
|
prevLines []itemLine
|
||||||
|
suppress bool
|
||||||
|
startChan chan bool
|
||||||
|
killChan chan int
|
||||||
|
slab *util.Slab
|
||||||
|
theme *tui.ColorTheme
|
||||||
|
tui tui.Renderer
|
||||||
}
|
}
|
||||||
|
|
||||||
type selectedItem struct {
|
type selectedItem struct {
|
||||||
@@ -145,8 +155,6 @@ func (a byTimeOrder) Less(i, j int) bool {
|
|||||||
return a[i].at.Before(a[j].at)
|
return a[i].at.Before(a[j].at)
|
||||||
}
|
}
|
||||||
|
|
||||||
var _spinner = []string{`-`, `\`, `|`, `/`, `-`, `\`, `|`, `/`}
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
reqPrompt util.EventType = iota
|
reqPrompt util.EventType = iota
|
||||||
reqInfo
|
reqInfo
|
||||||
@@ -182,6 +190,7 @@ const (
|
|||||||
actAcceptNonEmpty
|
actAcceptNonEmpty
|
||||||
actBackwardChar
|
actBackwardChar
|
||||||
actBackwardDeleteChar
|
actBackwardDeleteChar
|
||||||
|
actBackwardDeleteCharEOF
|
||||||
actBackwardWord
|
actBackwardWord
|
||||||
actCancel
|
actCancel
|
||||||
actClearScreen
|
actClearScreen
|
||||||
@@ -364,9 +373,9 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
|
|||||||
effectiveMinHeight *= 2
|
effectiveMinHeight *= 2
|
||||||
}
|
}
|
||||||
if opts.InfoStyle != infoDefault {
|
if opts.InfoStyle != infoDefault {
|
||||||
effectiveMinHeight -= 1
|
effectiveMinHeight--
|
||||||
}
|
}
|
||||||
if opts.Bordered {
|
if opts.BorderShape != tui.BorderNone {
|
||||||
effectiveMinHeight += 2
|
effectiveMinHeight += 2
|
||||||
}
|
}
|
||||||
return util.Min(termHeight, util.Max(maxHeight, effectiveMinHeight))
|
return util.Min(termHeight, util.Max(maxHeight, effectiveMinHeight))
|
||||||
@@ -380,63 +389,75 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
|
|||||||
wordRubout = fmt.Sprintf("%s[^%s]", sep, sep)
|
wordRubout = fmt.Sprintf("%s[^%s]", sep, sep)
|
||||||
wordNext = fmt.Sprintf("[^%s]%s|(.$)", sep, sep)
|
wordNext = fmt.Sprintf("[^%s]%s|(.$)", sep, sep)
|
||||||
}
|
}
|
||||||
|
spinner := []string{`⠋`, `⠙`, `⠹`, `⠸`, `⠼`, `⠴`, `⠦`, `⠧`, `⠇`, `⠏`}
|
||||||
|
if !opts.Unicode {
|
||||||
|
spinner = []string{`-`, `\`, `|`, `/`, `-`, `\`, `|`, `/`}
|
||||||
|
}
|
||||||
t := Terminal{
|
t := Terminal{
|
||||||
initDelay: delay,
|
initDelay: delay,
|
||||||
infoStyle: opts.InfoStyle,
|
infoStyle: opts.InfoStyle,
|
||||||
queryLen: [2]int{0, 0},
|
spinner: spinner,
|
||||||
layout: opts.Layout,
|
queryLen: [2]int{0, 0},
|
||||||
fullscreen: fullscreen,
|
layout: opts.Layout,
|
||||||
hscroll: opts.Hscroll,
|
fullscreen: fullscreen,
|
||||||
hscrollOff: opts.HscrollOff,
|
keepRight: opts.KeepRight,
|
||||||
wordRubout: wordRubout,
|
hscroll: opts.Hscroll,
|
||||||
wordNext: wordNext,
|
hscrollOff: opts.HscrollOff,
|
||||||
cx: len(input),
|
wordRubout: wordRubout,
|
||||||
cy: 0,
|
wordNext: wordNext,
|
||||||
offset: 0,
|
cx: len(input),
|
||||||
xoffset: 0,
|
cy: 0,
|
||||||
yanked: []rune{},
|
offset: 0,
|
||||||
input: input,
|
xoffset: 0,
|
||||||
multi: opts.Multi,
|
yanked: []rune{},
|
||||||
sort: opts.Sort > 0,
|
input: input,
|
||||||
toggleSort: opts.ToggleSort,
|
multi: opts.Multi,
|
||||||
delimiter: opts.Delimiter,
|
sort: opts.Sort > 0,
|
||||||
expect: opts.Expect,
|
toggleSort: opts.ToggleSort,
|
||||||
keymap: opts.Keymap,
|
delimiter: opts.Delimiter,
|
||||||
pressed: "",
|
expect: opts.Expect,
|
||||||
printQuery: opts.PrintQuery,
|
keymap: opts.Keymap,
|
||||||
history: opts.History,
|
pressed: "",
|
||||||
margin: opts.Margin,
|
printQuery: opts.PrintQuery,
|
||||||
unicode: opts.Unicode,
|
history: opts.History,
|
||||||
bordered: opts.Bordered,
|
margin: opts.Margin,
|
||||||
cleanExit: opts.ClearOnExit,
|
unicode: opts.Unicode,
|
||||||
strong: strongAttr,
|
borderShape: opts.BorderShape,
|
||||||
cycle: opts.Cycle,
|
cleanExit: opts.ClearOnExit,
|
||||||
header: header,
|
strong: strongAttr,
|
||||||
header0: header,
|
cycle: opts.Cycle,
|
||||||
ansi: opts.Ansi,
|
header: header,
|
||||||
tabstop: opts.Tabstop,
|
header0: header,
|
||||||
reading: true,
|
ansi: opts.Ansi,
|
||||||
failed: nil,
|
tabstop: opts.Tabstop,
|
||||||
jumping: jumpDisabled,
|
reading: true,
|
||||||
jumpLabels: opts.JumpLabels,
|
failed: nil,
|
||||||
printer: opts.Printer,
|
jumping: jumpDisabled,
|
||||||
printsep: opts.PrintSep,
|
jumpLabels: opts.JumpLabels,
|
||||||
merger: EmptyMerger,
|
printer: opts.Printer,
|
||||||
selected: make(map[int32]selectedItem),
|
printsep: opts.PrintSep,
|
||||||
reqBox: util.NewEventBox(),
|
merger: EmptyMerger,
|
||||||
preview: opts.Preview,
|
selected: make(map[int32]selectedItem),
|
||||||
previewer: previewer{"", 0, 0, previewBox != nil && !opts.Preview.hidden, false},
|
reqBox: util.NewEventBox(),
|
||||||
previewBox: previewBox,
|
preview: opts.Preview,
|
||||||
eventBox: eventBox,
|
previewer: previewer{"", 0, 0, previewBox != nil && !opts.Preview.hidden, false},
|
||||||
mutex: sync.Mutex{},
|
previewBox: previewBox,
|
||||||
suppress: true,
|
eventBox: eventBox,
|
||||||
slab: util.MakeSlab(slab16Size, slab32Size),
|
mutex: sync.Mutex{},
|
||||||
theme: opts.Theme,
|
suppress: true,
|
||||||
startChan: make(chan bool, 1),
|
slab: util.MakeSlab(slab16Size, slab32Size),
|
||||||
killChan: make(chan int),
|
theme: opts.Theme,
|
||||||
tui: renderer,
|
startChan: make(chan bool, 1),
|
||||||
initFunc: func() { renderer.Init() }}
|
killChan: make(chan int),
|
||||||
|
tui: renderer,
|
||||||
|
initFunc: func() { renderer.Init() }}
|
||||||
t.prompt, t.promptLen = t.processTabs([]rune(opts.Prompt), 0)
|
t.prompt, t.promptLen = t.processTabs([]rune(opts.Prompt), 0)
|
||||||
|
t.pointer, t.pointerLen = t.processTabs([]rune(opts.Pointer), 0)
|
||||||
|
t.marker, t.markerLen = t.processTabs([]rune(opts.Marker), 0)
|
||||||
|
// Pre-calculated empty pointer and marker signs
|
||||||
|
t.pointerEmpty = strings.Repeat(" ", t.pointerLen)
|
||||||
|
t.markerEmpty = strings.Repeat(" ", t.markerLen)
|
||||||
|
|
||||||
return &t
|
return &t
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -578,8 +599,11 @@ func (t *Terminal) resizeWindows() {
|
|||||||
} else {
|
} else {
|
||||||
marginInt[idx] = int(sizeSpec.size)
|
marginInt[idx] = int(sizeSpec.size)
|
||||||
}
|
}
|
||||||
if t.bordered && idx%2 == 0 {
|
switch t.borderShape {
|
||||||
marginInt[idx] += 1
|
case tui.BorderHorizontal:
|
||||||
|
marginInt[idx] += 1 - idx%2
|
||||||
|
case tui.BorderRounded, tui.BorderSharp:
|
||||||
|
marginInt[idx] += 1 + idx%2
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
adjust := func(idx1 int, idx2 int, max int, min int) {
|
adjust := func(idx1 int, idx2 int, max int, min int) {
|
||||||
@@ -619,18 +643,26 @@ func (t *Terminal) resizeWindows() {
|
|||||||
|
|
||||||
width := screenWidth - marginInt[1] - marginInt[3]
|
width := screenWidth - marginInt[1] - marginInt[3]
|
||||||
height := screenHeight - marginInt[0] - marginInt[2]
|
height := screenHeight - marginInt[0] - marginInt[2]
|
||||||
if t.bordered {
|
switch t.borderShape {
|
||||||
|
case tui.BorderHorizontal:
|
||||||
t.border = t.tui.NewWindow(
|
t.border = t.tui.NewWindow(
|
||||||
marginInt[0]-1,
|
marginInt[0]-1,
|
||||||
marginInt[3],
|
marginInt[3],
|
||||||
width,
|
width,
|
||||||
height+2,
|
height+2,
|
||||||
false, tui.MakeBorderStyle(tui.BorderHorizontal, t.unicode))
|
false, tui.MakeBorderStyle(tui.BorderHorizontal, t.unicode))
|
||||||
|
case tui.BorderRounded, tui.BorderSharp:
|
||||||
|
t.border = t.tui.NewWindow(
|
||||||
|
marginInt[0]-1,
|
||||||
|
marginInt[3]-2,
|
||||||
|
width+4,
|
||||||
|
height+2,
|
||||||
|
false, tui.MakeBorderStyle(t.borderShape, t.unicode))
|
||||||
}
|
}
|
||||||
noBorder := tui.MakeBorderStyle(tui.BorderNone, t.unicode)
|
noBorder := tui.MakeBorderStyle(tui.BorderNone, t.unicode)
|
||||||
if previewVisible {
|
if previewVisible {
|
||||||
createPreviewWindow := func(y int, x int, w int, h int) {
|
createPreviewWindow := func(y int, x int, w int, h int) {
|
||||||
previewBorder := tui.MakeBorderStyle(tui.BorderAround, t.unicode)
|
previewBorder := tui.MakeBorderStyle(tui.BorderRounded, t.unicode)
|
||||||
if !t.preview.border {
|
if !t.preview.border {
|
||||||
previewBorder = tui.MakeTransparentBorder()
|
previewBorder = tui.MakeTransparentBorder()
|
||||||
}
|
}
|
||||||
@@ -744,8 +776,8 @@ func (t *Terminal) printInfo() {
|
|||||||
t.move(1, 0, true)
|
t.move(1, 0, true)
|
||||||
if t.reading {
|
if t.reading {
|
||||||
duration := int64(spinnerDuration)
|
duration := int64(spinnerDuration)
|
||||||
idx := (time.Now().UnixNano() % (duration * int64(len(_spinner)))) / duration
|
idx := (time.Now().UnixNano() % (duration * int64(len(t.spinner)))) / duration
|
||||||
t.window.CPrint(tui.ColSpinner, t.strong, _spinner[idx])
|
t.window.CPrint(tui.ColSpinner, t.strong, t.spinner[idx])
|
||||||
}
|
}
|
||||||
t.move(1, 2, false)
|
t.move(1, 2, false)
|
||||||
pos = 2
|
pos = 2
|
||||||
@@ -791,7 +823,7 @@ func (t *Terminal) printInfo() {
|
|||||||
maxWidth := t.window.Width() - pos
|
maxWidth := t.window.Width() - pos
|
||||||
if len(output) > maxWidth {
|
if len(output) > maxWidth {
|
||||||
outputRunes, _ := t.trimRight([]rune(output), maxWidth-2)
|
outputRunes, _ := t.trimRight([]rune(output), maxWidth-2)
|
||||||
output = string(outputRunes) + ".."
|
output = string(outputRunes) + strings.Repeat(".", util.Constrain(maxWidth, 0, 2))
|
||||||
}
|
}
|
||||||
t.window.CPrint(tui.ColInfo, 0, output)
|
t.window.CPrint(tui.ColInfo, 0, output)
|
||||||
}
|
}
|
||||||
@@ -848,15 +880,15 @@ func (t *Terminal) printList() {
|
|||||||
func (t *Terminal) printItem(result Result, line int, i int, current bool) {
|
func (t *Terminal) printItem(result Result, line int, i int, current bool) {
|
||||||
item := result.item
|
item := result.item
|
||||||
_, selected := t.selected[item.Index()]
|
_, selected := t.selected[item.Index()]
|
||||||
label := " "
|
label := t.pointerEmpty
|
||||||
if t.jumping != jumpDisabled {
|
if t.jumping != jumpDisabled {
|
||||||
if i < len(t.jumpLabels) {
|
if i < len(t.jumpLabels) {
|
||||||
// Striped
|
// Striped
|
||||||
current = i%2 == 0
|
current = i%2 == 0
|
||||||
label = t.jumpLabels[i : i+1]
|
label = t.jumpLabels[i:i+1] + strings.Repeat(" ", t.pointerLen-1)
|
||||||
}
|
}
|
||||||
} else if current {
|
} else if current {
|
||||||
label = ">"
|
label = t.pointer
|
||||||
}
|
}
|
||||||
|
|
||||||
// Avoid unnecessary redraw
|
// Avoid unnecessary redraw
|
||||||
@@ -875,17 +907,17 @@ func (t *Terminal) printItem(result Result, line int, i int, current bool) {
|
|||||||
if current {
|
if current {
|
||||||
t.window.CPrint(tui.ColCurrentCursor, t.strong, label)
|
t.window.CPrint(tui.ColCurrentCursor, t.strong, label)
|
||||||
if selected {
|
if selected {
|
||||||
t.window.CPrint(tui.ColCurrentSelected, t.strong, ">")
|
t.window.CPrint(tui.ColCurrentSelected, t.strong, t.marker)
|
||||||
} else {
|
} else {
|
||||||
t.window.CPrint(tui.ColCurrentSelected, t.strong, " ")
|
t.window.CPrint(tui.ColCurrentSelected, t.strong, t.markerEmpty)
|
||||||
}
|
}
|
||||||
newLine.width = t.printHighlighted(result, t.strong, tui.ColCurrent, tui.ColCurrentMatch, true, true)
|
newLine.width = t.printHighlighted(result, t.strong, tui.ColCurrent, tui.ColCurrentMatch, true, true)
|
||||||
} else {
|
} else {
|
||||||
t.window.CPrint(tui.ColCursor, t.strong, label)
|
t.window.CPrint(tui.ColCursor, t.strong, label)
|
||||||
if selected {
|
if selected {
|
||||||
t.window.CPrint(tui.ColSelected, t.strong, ">")
|
t.window.CPrint(tui.ColSelected, t.strong, t.marker)
|
||||||
} else {
|
} else {
|
||||||
t.window.Print(" ")
|
t.window.Print(t.markerEmpty)
|
||||||
}
|
}
|
||||||
newLine.width = t.printHighlighted(result, 0, tui.ColNormal, tui.ColMatch, false, true)
|
newLine.width = t.printHighlighted(result, 0, tui.ColNormal, tui.ColMatch, false, true)
|
||||||
}
|
}
|
||||||
@@ -972,14 +1004,17 @@ func (t *Terminal) printHighlighted(result Result, attr tui.Attr, col1 tui.Color
|
|||||||
displayWidth := t.displayWidthWithLimit(text, 0, maxWidth)
|
displayWidth := t.displayWidthWithLimit(text, 0, maxWidth)
|
||||||
if displayWidth > maxWidth {
|
if displayWidth > maxWidth {
|
||||||
if t.hscroll {
|
if t.hscroll {
|
||||||
// Stri..
|
if t.keepRight && pos == nil {
|
||||||
if !t.overflow(text[:maxe], maxWidth-2) {
|
text, _ = t.trimLeft(text, maxWidth-2)
|
||||||
|
text = append([]rune(ellipsis), text...)
|
||||||
|
} else if !t.overflow(text[:maxe], maxWidth-2) {
|
||||||
|
// Stri..
|
||||||
text, _ = t.trimRight(text, maxWidth-2)
|
text, _ = t.trimRight(text, maxWidth-2)
|
||||||
text = append(text, []rune("..")...)
|
text = append(text, []rune(ellipsis)...)
|
||||||
} else {
|
} else {
|
||||||
// Stri..
|
// Stri..
|
||||||
if t.overflow(text[maxe:], 2) {
|
if t.overflow(text[maxe:], 2) {
|
||||||
text = append(text[:maxe], []rune("..")...)
|
text = append(text[:maxe], []rune(ellipsis)...)
|
||||||
}
|
}
|
||||||
// ..ri..
|
// ..ri..
|
||||||
var diff int32
|
var diff int32
|
||||||
@@ -994,11 +1029,11 @@ func (t *Terminal) printHighlighted(result Result, attr tui.Attr, col1 tui.Color
|
|||||||
offsets[idx].offset[0] = b
|
offsets[idx].offset[0] = b
|
||||||
offsets[idx].offset[1] = util.Max32(b, e)
|
offsets[idx].offset[1] = util.Max32(b, e)
|
||||||
}
|
}
|
||||||
text = append([]rune(".."), text...)
|
text = append([]rune(ellipsis), text...)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
text, _ = t.trimRight(text, maxWidth-2)
|
text, _ = t.trimRight(text, maxWidth-2)
|
||||||
text = append(text, []rune("..")...)
|
text = append(text, []rune(ellipsis)...)
|
||||||
|
|
||||||
for idx, offset := range offsets {
|
for idx, offset := range offsets {
|
||||||
offsets[idx].offset[0] = util.Min32(offset.offset[0], int32(maxWidth-2))
|
offsets[idx].offset[0] = util.Min32(offset.offset[0], int32(maxWidth-2))
|
||||||
@@ -1129,7 +1164,7 @@ func (t *Terminal) refresh() {
|
|||||||
t.placeCursor()
|
t.placeCursor()
|
||||||
if !t.suppress {
|
if !t.suppress {
|
||||||
windows := make([]tui.Window, 0, 4)
|
windows := make([]tui.Window, 0, 4)
|
||||||
if t.bordered {
|
if t.borderShape != tui.BorderNone {
|
||||||
windows = append(windows, t.border)
|
windows = append(windows, t.border)
|
||||||
}
|
}
|
||||||
if t.hasPreviewWindow() {
|
if t.hasPreviewWindow() {
|
||||||
@@ -1572,7 +1607,10 @@ func (t *Terminal) Loop() {
|
|||||||
var out bytes.Buffer
|
var out bytes.Buffer
|
||||||
cmd.Stdout = &out
|
cmd.Stdout = &out
|
||||||
cmd.Stderr = &out
|
cmd.Stderr = &out
|
||||||
cmd.Start()
|
err := cmd.Start()
|
||||||
|
if err != nil {
|
||||||
|
out.Write([]byte(err.Error()))
|
||||||
|
}
|
||||||
finishChan := make(chan bool, 1)
|
finishChan := make(chan bool, 1)
|
||||||
updateChan := make(chan bool)
|
updateChan := make(chan bool)
|
||||||
go func() {
|
go func() {
|
||||||
@@ -1827,6 +1865,13 @@ func (t *Terminal) Loop() {
|
|||||||
t.input = []rune{}
|
t.input = []rune{}
|
||||||
t.cx = 0
|
t.cx = 0
|
||||||
}
|
}
|
||||||
|
case actBackwardDeleteCharEOF:
|
||||||
|
if len(t.input) == 0 {
|
||||||
|
req(reqQuit)
|
||||||
|
} else if t.cx > 0 {
|
||||||
|
t.input = append(t.input[:t.cx-1], t.input[t.cx:]...)
|
||||||
|
t.cx--
|
||||||
|
}
|
||||||
case actForwardChar:
|
case actForwardChar:
|
||||||
if t.cx < len(t.input) {
|
if t.cx < len(t.input) {
|
||||||
t.cx++
|
t.cx++
|
||||||
|
|||||||
121
src/tui/light.go
121
src/tui/light.go
@@ -7,7 +7,6 @@ import (
|
|||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
|
||||||
"time"
|
"time"
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
|
|
||||||
@@ -23,27 +22,13 @@ const (
|
|||||||
defaultEscDelay = 100
|
defaultEscDelay = 100
|
||||||
escPollInterval = 5
|
escPollInterval = 5
|
||||||
offsetPollTries = 10
|
offsetPollTries = 10
|
||||||
|
maxInputBuffer = 10 * 1024
|
||||||
)
|
)
|
||||||
|
|
||||||
const consoleDevice string = "/dev/tty"
|
const consoleDevice string = "/dev/tty"
|
||||||
|
|
||||||
var offsetRegexp *regexp.Regexp = regexp.MustCompile("(.*)\x1b\\[([0-9]+);([0-9]+)R")
|
var offsetRegexp *regexp.Regexp = regexp.MustCompile("(.*)\x1b\\[([0-9]+);([0-9]+)R")
|
||||||
|
|
||||||
func openTtyIn() *os.File {
|
|
||||||
in, err := os.OpenFile(consoleDevice, syscall.O_RDONLY, 0)
|
|
||||||
if err != nil {
|
|
||||||
tty := ttyname()
|
|
||||||
if len(tty) > 0 {
|
|
||||||
if in, err := os.OpenFile(tty, syscall.O_RDONLY, 0); err == nil {
|
|
||||||
return in
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fmt.Fprintln(os.Stderr, "Failed to open "+consoleDevice)
|
|
||||||
os.Exit(2)
|
|
||||||
}
|
|
||||||
return in
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *LightRenderer) stderr(str string) {
|
func (r *LightRenderer) stderr(str string) {
|
||||||
r.stderrInternal(str, true)
|
r.stderrInternal(str, true)
|
||||||
}
|
}
|
||||||
@@ -100,11 +85,19 @@ type LightRenderer struct {
|
|||||||
y int
|
y int
|
||||||
x int
|
x int
|
||||||
maxHeightFunc func(int) int
|
maxHeightFunc func(int) int
|
||||||
|
|
||||||
|
// Windows only
|
||||||
|
ttyinChannel chan byte
|
||||||
|
inHandle uintptr
|
||||||
|
outHandle uintptr
|
||||||
|
origStateInput uint32
|
||||||
|
origStateOutput uint32
|
||||||
}
|
}
|
||||||
|
|
||||||
type LightWindow struct {
|
type LightWindow struct {
|
||||||
renderer *LightRenderer
|
renderer *LightRenderer
|
||||||
colored bool
|
colored bool
|
||||||
|
preview bool
|
||||||
border BorderStyle
|
border BorderStyle
|
||||||
top int
|
top int
|
||||||
left int
|
left int
|
||||||
@@ -132,10 +125,6 @@ func NewLightRenderer(theme *ColorTheme, forceBlack bool, mouse bool, tabstop in
|
|||||||
return &r
|
return &r
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *LightRenderer) fd() int {
|
|
||||||
return int(r.ttyin.Fd())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *LightRenderer) defaultTheme() *ColorTheme {
|
func (r *LightRenderer) defaultTheme() *ColorTheme {
|
||||||
if strings.Contains(os.Getenv("TERM"), "256") {
|
if strings.Contains(os.Getenv("TERM"), "256") {
|
||||||
return Dark256
|
return Dark256
|
||||||
@@ -147,22 +136,6 @@ func (r *LightRenderer) defaultTheme() *ColorTheme {
|
|||||||
return Default16
|
return Default16
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *LightRenderer) findOffset() (row int, col int) {
|
|
||||||
r.csi("6n")
|
|
||||||
r.flush()
|
|
||||||
bytes := []byte{}
|
|
||||||
for tries := 0; tries < offsetPollTries; tries++ {
|
|
||||||
bytes = r.getBytesInternal(bytes, tries > 0)
|
|
||||||
offsets := offsetRegexp.FindSubmatch(bytes)
|
|
||||||
if len(offsets) > 3 {
|
|
||||||
// add anything we skipped over to the input buffer
|
|
||||||
r.buffer = append(r.buffer, offsets[1]...)
|
|
||||||
return atoi(string(offsets[2]), 0) - 1, atoi(string(offsets[3]), 0) - 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return -1, -1
|
|
||||||
}
|
|
||||||
|
|
||||||
func repeat(r rune, times int) string {
|
func repeat(r rune, times int) string {
|
||||||
if times > 0 {
|
if times > 0 {
|
||||||
return strings.Repeat(string(r), times)
|
return strings.Repeat(string(r), times)
|
||||||
@@ -181,13 +154,9 @@ func atoi(s string, defaultValue int) int {
|
|||||||
func (r *LightRenderer) Init() {
|
func (r *LightRenderer) Init() {
|
||||||
r.escDelay = atoi(os.Getenv("ESCDELAY"), defaultEscDelay)
|
r.escDelay = atoi(os.Getenv("ESCDELAY"), defaultEscDelay)
|
||||||
|
|
||||||
fd := r.fd()
|
if err := r.initPlatform(); err != nil {
|
||||||
origState, err := terminal.GetState(fd)
|
|
||||||
if err != nil {
|
|
||||||
errorExit(err.Error())
|
errorExit(err.Error())
|
||||||
}
|
}
|
||||||
r.origState = origState
|
|
||||||
terminal.MakeRaw(fd)
|
|
||||||
r.updateTerminalSize()
|
r.updateTerminalSize()
|
||||||
initTheme(r.theme, r.defaultTheme(), r.forceBlack)
|
initTheme(r.theme, r.defaultTheme(), r.forceBlack)
|
||||||
|
|
||||||
@@ -260,28 +229,6 @@ func getEnv(name string, defaultValue int) int {
|
|||||||
return atoi(env, defaultValue)
|
return atoi(env, defaultValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *LightRenderer) updateTerminalSize() {
|
|
||||||
width, height, err := terminal.GetSize(r.fd())
|
|
||||||
if err == nil {
|
|
||||||
r.width = width
|
|
||||||
r.height = r.maxHeightFunc(height)
|
|
||||||
} else {
|
|
||||||
r.width = getEnv("COLUMNS", defaultWidth)
|
|
||||||
r.height = r.maxHeightFunc(getEnv("LINES", defaultHeight))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *LightRenderer) getch(nonblock bool) (int, bool) {
|
|
||||||
b := make([]byte, 1)
|
|
||||||
fd := r.fd()
|
|
||||||
util.SetNonblock(r.ttyin, nonblock)
|
|
||||||
_, err := util.Read(fd, b)
|
|
||||||
if err != nil {
|
|
||||||
return 0, false
|
|
||||||
}
|
|
||||||
return int(b[0]), true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *LightRenderer) getBytes() []byte {
|
func (r *LightRenderer) getBytes() []byte {
|
||||||
return r.getBytesInternal(r.buffer, false)
|
return r.getBytesInternal(r.buffer, false)
|
||||||
}
|
}
|
||||||
@@ -316,6 +263,13 @@ func (r *LightRenderer) getBytesInternal(buffer []byte, nonblock bool) []byte {
|
|||||||
}
|
}
|
||||||
buffer = append(buffer, byte(c))
|
buffer = append(buffer, byte(c))
|
||||||
pc = c
|
pc = c
|
||||||
|
|
||||||
|
// This should never happen under normal conditions,
|
||||||
|
// so terminate fzf immediately.
|
||||||
|
if len(buffer) > maxInputBuffer {
|
||||||
|
r.Close()
|
||||||
|
panic(fmt.Sprintf("Input buffer overflow (%d): %v", len(buffer), buffer))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return buffer
|
return buffer
|
||||||
@@ -453,7 +407,10 @@ func (r *LightRenderer) escSequence(sz *int) Event {
|
|||||||
*sz = 4
|
*sz = 4
|
||||||
switch r.buffer[2] {
|
switch r.buffer[2] {
|
||||||
case 50:
|
case 50:
|
||||||
if len(r.buffer) == 5 && r.buffer[4] == 126 {
|
if r.buffer[3] == 126 {
|
||||||
|
return Event{Insert, 0, nil}
|
||||||
|
}
|
||||||
|
if len(r.buffer) > 4 && r.buffer[4] == 126 {
|
||||||
*sz = 5
|
*sz = 5
|
||||||
switch r.buffer[3] {
|
switch r.buffer[3] {
|
||||||
case 48:
|
case 48:
|
||||||
@@ -467,7 +424,7 @@ func (r *LightRenderer) escSequence(sz *int) Event {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Bracketed paste mode: \e[200~ ... \e[201~
|
// Bracketed paste mode: \e[200~ ... \e[201~
|
||||||
if r.buffer[3] == '0' && (r.buffer[4] == '0' || r.buffer[4] == '1') && r.buffer[5] == '~' {
|
if len(r.buffer) > 5 && r.buffer[3] == '0' && (r.buffer[4] == '0' || r.buffer[4] == '1') && r.buffer[5] == '~' {
|
||||||
// Immediately discard the sequence from the buffer and reread input
|
// Immediately discard the sequence from the buffer and reread input
|
||||||
r.buffer = r.buffer[6:]
|
r.buffer = r.buffer[6:]
|
||||||
*sz = 0
|
*sz = 0
|
||||||
@@ -486,10 +443,18 @@ func (r *LightRenderer) escSequence(sz *int) Event {
|
|||||||
switch r.buffer[3] {
|
switch r.buffer[3] {
|
||||||
case 126:
|
case 126:
|
||||||
return Event{Home, 0, nil}
|
return Event{Home, 0, nil}
|
||||||
case 53, 55, 56, 57:
|
case 49, 50, 51, 52, 53, 55, 56, 57:
|
||||||
if len(r.buffer) == 5 && r.buffer[4] == 126 {
|
if len(r.buffer) == 5 && r.buffer[4] == 126 {
|
||||||
*sz = 5
|
*sz = 5
|
||||||
switch r.buffer[3] {
|
switch r.buffer[3] {
|
||||||
|
case 49:
|
||||||
|
return Event{F1, 0, nil}
|
||||||
|
case 50:
|
||||||
|
return Event{F2, 0, nil}
|
||||||
|
case 51:
|
||||||
|
return Event{F3, 0, nil}
|
||||||
|
case 52:
|
||||||
|
return Event{F4, 0, nil}
|
||||||
case 53:
|
case 53:
|
||||||
return Event{F5, 0, nil}
|
return Event{F5, 0, nil}
|
||||||
case 55:
|
case 55:
|
||||||
@@ -584,7 +549,7 @@ func (r *LightRenderer) rmcup() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *LightRenderer) Pause(clear bool) {
|
func (r *LightRenderer) Pause(clear bool) {
|
||||||
terminal.Restore(r.fd(), r.origState)
|
r.restoreTerminal()
|
||||||
if clear {
|
if clear {
|
||||||
if r.fullscreen {
|
if r.fullscreen {
|
||||||
r.rmcup()
|
r.rmcup()
|
||||||
@@ -597,7 +562,7 @@ func (r *LightRenderer) Pause(clear bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *LightRenderer) Resume(clear bool) {
|
func (r *LightRenderer) Resume(clear bool) {
|
||||||
terminal.MakeRaw(r.fd())
|
r.setupTerminal()
|
||||||
if clear {
|
if clear {
|
||||||
if r.fullscreen {
|
if r.fullscreen {
|
||||||
r.smcup()
|
r.smcup()
|
||||||
@@ -651,7 +616,8 @@ func (r *LightRenderer) Close() {
|
|||||||
r.csi("?1000l")
|
r.csi("?1000l")
|
||||||
}
|
}
|
||||||
r.flush()
|
r.flush()
|
||||||
terminal.Restore(r.fd(), r.origState)
|
r.closePlatform()
|
||||||
|
r.restoreTerminal()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *LightRenderer) MaxX() int {
|
func (r *LightRenderer) MaxX() int {
|
||||||
@@ -670,6 +636,7 @@ func (r *LightRenderer) NewWindow(top int, left int, width int, height int, prev
|
|||||||
w := &LightWindow{
|
w := &LightWindow{
|
||||||
renderer: r,
|
renderer: r,
|
||||||
colored: r.theme != nil,
|
colored: r.theme != nil,
|
||||||
|
preview: preview,
|
||||||
border: borderStyle,
|
border: borderStyle,
|
||||||
top: top,
|
top: top,
|
||||||
left: left,
|
left: left,
|
||||||
@@ -693,7 +660,7 @@ func (r *LightRenderer) NewWindow(top int, left int, width int, height int, prev
|
|||||||
|
|
||||||
func (w *LightWindow) drawBorder() {
|
func (w *LightWindow) drawBorder() {
|
||||||
switch w.border.shape {
|
switch w.border.shape {
|
||||||
case BorderAround:
|
case BorderRounded, BorderSharp:
|
||||||
w.drawBorderAround()
|
w.drawBorderAround()
|
||||||
case BorderHorizontal:
|
case BorderHorizontal:
|
||||||
w.drawBorderHorizontal()
|
w.drawBorderHorizontal()
|
||||||
@@ -709,16 +676,20 @@ func (w *LightWindow) drawBorderHorizontal() {
|
|||||||
|
|
||||||
func (w *LightWindow) drawBorderAround() {
|
func (w *LightWindow) drawBorderAround() {
|
||||||
w.Move(0, 0)
|
w.Move(0, 0)
|
||||||
w.CPrint(ColPreviewBorder, AttrRegular,
|
color := ColBorder
|
||||||
|
if w.preview {
|
||||||
|
color = ColPreviewBorder
|
||||||
|
}
|
||||||
|
w.CPrint(color, AttrRegular,
|
||||||
string(w.border.topLeft)+repeat(w.border.horizontal, w.width-2)+string(w.border.topRight))
|
string(w.border.topLeft)+repeat(w.border.horizontal, w.width-2)+string(w.border.topRight))
|
||||||
for y := 1; y < w.height-1; y++ {
|
for y := 1; y < w.height-1; y++ {
|
||||||
w.Move(y, 0)
|
w.Move(y, 0)
|
||||||
w.CPrint(ColPreviewBorder, AttrRegular, string(w.border.vertical))
|
w.CPrint(color, AttrRegular, string(w.border.vertical))
|
||||||
w.CPrint(ColPreviewBorder, AttrRegular, repeat(' ', w.width-2))
|
w.CPrint(color, AttrRegular, repeat(' ', w.width-2))
|
||||||
w.CPrint(ColPreviewBorder, AttrRegular, string(w.border.vertical))
|
w.CPrint(color, AttrRegular, string(w.border.vertical))
|
||||||
}
|
}
|
||||||
w.Move(w.height-1, 0)
|
w.Move(w.height-1, 0)
|
||||||
w.CPrint(ColPreviewBorder, AttrRegular,
|
w.CPrint(color, AttrRegular,
|
||||||
string(w.border.bottomLeft)+repeat(w.border.horizontal, w.width-2)+string(w.border.bottomRight))
|
string(w.border.bottomLeft)+repeat(w.border.horizontal, w.width-2)+string(w.border.bottomRight))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
97
src/tui/light_unix.go
Normal file
97
src/tui/light_unix.go
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
// +build !windows
|
||||||
|
|
||||||
|
package tui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"github.com/junegunn/fzf/src/util"
|
||||||
|
"golang.org/x/crypto/ssh/terminal"
|
||||||
|
)
|
||||||
|
|
||||||
|
func IsLightRendererSupported() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *LightRenderer) fd() int {
|
||||||
|
return int(r.ttyin.Fd())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *LightRenderer) initPlatform() error {
|
||||||
|
fd := r.fd()
|
||||||
|
origState, err := terminal.GetState(fd)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
r.origState = origState
|
||||||
|
terminal.MakeRaw(fd)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *LightRenderer) closePlatform() {
|
||||||
|
// NOOP
|
||||||
|
}
|
||||||
|
|
||||||
|
func openTtyIn() *os.File {
|
||||||
|
in, err := os.OpenFile(consoleDevice, syscall.O_RDONLY, 0)
|
||||||
|
if err != nil {
|
||||||
|
tty := ttyname()
|
||||||
|
if len(tty) > 0 {
|
||||||
|
if in, err := os.OpenFile(tty, syscall.O_RDONLY, 0); err == nil {
|
||||||
|
return in
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fmt.Fprintln(os.Stderr, "Failed to open "+consoleDevice)
|
||||||
|
os.Exit(2)
|
||||||
|
}
|
||||||
|
return in
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *LightRenderer) setupTerminal() {
|
||||||
|
terminal.MakeRaw(r.fd())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *LightRenderer) restoreTerminal() {
|
||||||
|
terminal.Restore(r.fd(), r.origState)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *LightRenderer) updateTerminalSize() {
|
||||||
|
width, height, err := terminal.GetSize(r.fd())
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
r.width = width
|
||||||
|
r.height = r.maxHeightFunc(height)
|
||||||
|
} else {
|
||||||
|
r.width = getEnv("COLUMNS", defaultWidth)
|
||||||
|
r.height = r.maxHeightFunc(getEnv("LINES", defaultHeight))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *LightRenderer) findOffset() (row int, col int) {
|
||||||
|
r.csi("6n")
|
||||||
|
r.flush()
|
||||||
|
bytes := []byte{}
|
||||||
|
for tries := 0; tries < offsetPollTries; tries++ {
|
||||||
|
bytes = r.getBytesInternal(bytes, tries > 0)
|
||||||
|
offsets := offsetRegexp.FindSubmatch(bytes)
|
||||||
|
if len(offsets) > 3 {
|
||||||
|
// Add anything we skipped over to the input buffer
|
||||||
|
r.buffer = append(r.buffer, offsets[1]...)
|
||||||
|
return atoi(string(offsets[2]), 0) - 1, atoi(string(offsets[3]), 0) - 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1, -1
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *LightRenderer) getch(nonblock bool) (int, bool) {
|
||||||
|
b := make([]byte, 1)
|
||||||
|
fd := r.fd()
|
||||||
|
util.SetNonblock(r.ttyin, nonblock)
|
||||||
|
_, err := util.Read(fd, b)
|
||||||
|
if err != nil {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
return int(b[0]), true
|
||||||
|
}
|
||||||
132
src/tui/light_windows.go
Normal file
132
src/tui/light_windows.go
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
//+build windows
|
||||||
|
|
||||||
|
package tui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"github.com/junegunn/fzf/src/util"
|
||||||
|
"golang.org/x/sys/windows"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
consoleFlagsInput = uint32(windows.ENABLE_VIRTUAL_TERMINAL_INPUT | windows.ENABLE_PROCESSED_INPUT | windows.ENABLE_EXTENDED_FLAGS)
|
||||||
|
consoleFlagsOutput = uint32(windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING | windows.ENABLE_PROCESSED_OUTPUT | windows.DISABLE_NEWLINE_AUTO_RETURN)
|
||||||
|
)
|
||||||
|
|
||||||
|
// IsLightRendererSupported checks to see if the Light renderer is supported
|
||||||
|
func IsLightRendererSupported() bool {
|
||||||
|
var oldState uint32
|
||||||
|
// enable vt100 emulation (https://docs.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences)
|
||||||
|
if windows.GetConsoleMode(windows.Stderr, &oldState) != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
// attempt to set mode to determine if we support VT 100 codes. This will work on newer Windows 10
|
||||||
|
// version:
|
||||||
|
canSetVt100 := windows.SetConsoleMode(windows.Stderr, oldState|windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING) == nil
|
||||||
|
var checkState uint32
|
||||||
|
if windows.GetConsoleMode(windows.Stderr, &checkState) != nil ||
|
||||||
|
(checkState&windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING) != windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
windows.SetConsoleMode(windows.Stderr, oldState)
|
||||||
|
return canSetVt100
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *LightRenderer) initPlatform() error {
|
||||||
|
//outHandle := windows.Stdout
|
||||||
|
outHandle, _ := syscall.Open("CONOUT$", syscall.O_RDWR, 0)
|
||||||
|
// enable vt100 emulation (https://docs.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences)
|
||||||
|
if err := windows.GetConsoleMode(windows.Handle(outHandle), &r.origStateOutput); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
r.outHandle = uintptr(outHandle)
|
||||||
|
inHandle, _ := syscall.Open("CONIN$", syscall.O_RDWR, 0)
|
||||||
|
if err := windows.GetConsoleMode(windows.Handle(inHandle), &r.origStateInput); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
r.inHandle = uintptr(inHandle)
|
||||||
|
|
||||||
|
r.setupTerminal()
|
||||||
|
|
||||||
|
// channel for non-blocking reads. Buffer to make sure
|
||||||
|
// we get the ESC sets:
|
||||||
|
r.ttyinChannel = make(chan byte, 12)
|
||||||
|
|
||||||
|
// the following allows for non-blocking IO.
|
||||||
|
// syscall.SetNonblock() is a NOOP under Windows.
|
||||||
|
go func() {
|
||||||
|
fd := int(r.inHandle)
|
||||||
|
b := make([]byte, 1)
|
||||||
|
for {
|
||||||
|
// HACK: if run from PSReadline, something resets ConsoleMode to remove ENABLE_VIRTUAL_TERMINAL_INPUT.
|
||||||
|
_ = windows.SetConsoleMode(windows.Handle(r.inHandle), consoleFlagsInput)
|
||||||
|
|
||||||
|
_, err := util.Read(fd, b)
|
||||||
|
if err == nil {
|
||||||
|
r.ttyinChannel <- b[0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *LightRenderer) closePlatform() {
|
||||||
|
windows.SetConsoleMode(windows.Handle(r.outHandle), r.origStateOutput)
|
||||||
|
windows.SetConsoleMode(windows.Handle(r.inHandle), r.origStateInput)
|
||||||
|
}
|
||||||
|
|
||||||
|
func openTtyIn() *os.File {
|
||||||
|
// not used
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *LightRenderer) setupTerminal() error {
|
||||||
|
if err := windows.SetConsoleMode(windows.Handle(r.outHandle), consoleFlagsOutput); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return windows.SetConsoleMode(windows.Handle(r.inHandle), consoleFlagsInput)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *LightRenderer) restoreTerminal() error {
|
||||||
|
if err := windows.SetConsoleMode(windows.Handle(r.inHandle), r.origStateInput); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return windows.SetConsoleMode(windows.Handle(r.outHandle), r.origStateOutput)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *LightRenderer) updateTerminalSize() {
|
||||||
|
var bufferInfo windows.ConsoleScreenBufferInfo
|
||||||
|
if err := windows.GetConsoleScreenBufferInfo(windows.Handle(r.outHandle), &bufferInfo); err != nil {
|
||||||
|
r.width = getEnv("COLUMNS", defaultWidth)
|
||||||
|
r.height = r.maxHeightFunc(getEnv("LINES", defaultHeight))
|
||||||
|
|
||||||
|
} else {
|
||||||
|
r.width = int(bufferInfo.Window.Right - bufferInfo.Window.Left)
|
||||||
|
r.height = r.maxHeightFunc(int(bufferInfo.Window.Bottom - bufferInfo.Window.Top))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *LightRenderer) findOffset() (row int, col int) {
|
||||||
|
var bufferInfo windows.ConsoleScreenBufferInfo
|
||||||
|
if err := windows.GetConsoleScreenBufferInfo(windows.Handle(r.outHandle), &bufferInfo); err != nil {
|
||||||
|
return -1, -1
|
||||||
|
}
|
||||||
|
return int(bufferInfo.CursorPosition.X), int(bufferInfo.CursorPosition.Y)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *LightRenderer) getch(nonblock bool) (int, bool) {
|
||||||
|
if nonblock {
|
||||||
|
select {
|
||||||
|
case bc := <-r.ttyinChannel:
|
||||||
|
return int(bc), true
|
||||||
|
default:
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
bc := <-r.ttyinChannel
|
||||||
|
return int(bc), true
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -28,6 +28,7 @@ type Attr tcell.Style
|
|||||||
|
|
||||||
type TcellWindow struct {
|
type TcellWindow struct {
|
||||||
color bool
|
color bool
|
||||||
|
preview bool
|
||||||
top int
|
top int
|
||||||
left int
|
left int
|
||||||
width int
|
width int
|
||||||
@@ -318,6 +319,8 @@ func (r *FullscreenRenderer) GetChar() Event {
|
|||||||
}
|
}
|
||||||
return Event{Right, 0, nil}
|
return Event{Right, 0, nil}
|
||||||
|
|
||||||
|
case tcell.KeyInsert:
|
||||||
|
return Event{Insert, 0, nil}
|
||||||
case tcell.KeyHome:
|
case tcell.KeyHome:
|
||||||
return Event{Home, 0, nil}
|
return Event{Home, 0, nil}
|
||||||
case tcell.KeyDelete:
|
case tcell.KeyDelete:
|
||||||
@@ -416,6 +419,7 @@ func (r *FullscreenRenderer) NewWindow(top int, left int, width int, height int,
|
|||||||
}
|
}
|
||||||
return &TcellWindow{
|
return &TcellWindow{
|
||||||
color: r.theme != nil,
|
color: r.theme != nil,
|
||||||
|
preview: preview,
|
||||||
top: top,
|
top: top,
|
||||||
left: left,
|
left: left,
|
||||||
width: width,
|
width: width,
|
||||||
@@ -589,7 +593,7 @@ func (w *TcellWindow) drawBorder() {
|
|||||||
|
|
||||||
var style tcell.Style
|
var style tcell.Style
|
||||||
if w.color {
|
if w.color {
|
||||||
if w.borderStyle.shape == BorderAround {
|
if w.preview {
|
||||||
style = ColPreviewBorder.style()
|
style = ColPreviewBorder.style()
|
||||||
} else {
|
} else {
|
||||||
style = ColBorder.style()
|
style = ColBorder.style()
|
||||||
@@ -603,7 +607,7 @@ func (w *TcellWindow) drawBorder() {
|
|||||||
_screen.SetContent(x, bot-1, w.borderStyle.horizontal, nil, style)
|
_screen.SetContent(x, bot-1, w.borderStyle.horizontal, nil, style)
|
||||||
}
|
}
|
||||||
|
|
||||||
if w.borderStyle.shape == BorderAround {
|
if w.borderStyle.shape != BorderHorizontal {
|
||||||
for y := top; y < bot; y++ {
|
for y := top; y < bot; y++ {
|
||||||
_screen.SetContent(left, y, w.borderStyle.vertical, nil, style)
|
_screen.SetContent(left, y, w.borderStyle.vertical, nil, style)
|
||||||
_screen.SetContent(right-1, y, w.borderStyle.vertical, nil, style)
|
_screen.SetContent(right-1, y, w.borderStyle.vertical, nil, style)
|
||||||
|
|||||||
@@ -66,6 +66,7 @@ const (
|
|||||||
Right
|
Right
|
||||||
Home
|
Home
|
||||||
End
|
End
|
||||||
|
Insert
|
||||||
|
|
||||||
SUp
|
SUp
|
||||||
SDown
|
SDown
|
||||||
@@ -209,7 +210,8 @@ type BorderShape int
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
BorderNone BorderShape = iota
|
BorderNone BorderShape = iota
|
||||||
BorderAround
|
BorderRounded
|
||||||
|
BorderSharp
|
||||||
BorderHorizontal
|
BorderHorizontal
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -227,6 +229,17 @@ type BorderCharacter int
|
|||||||
|
|
||||||
func MakeBorderStyle(shape BorderShape, unicode bool) BorderStyle {
|
func MakeBorderStyle(shape BorderShape, unicode bool) BorderStyle {
|
||||||
if unicode {
|
if unicode {
|
||||||
|
if shape == BorderRounded {
|
||||||
|
return BorderStyle{
|
||||||
|
shape: shape,
|
||||||
|
horizontal: '─',
|
||||||
|
vertical: '│',
|
||||||
|
topLeft: '╭',
|
||||||
|
topRight: '╮',
|
||||||
|
bottomLeft: '╰',
|
||||||
|
bottomRight: '╯',
|
||||||
|
}
|
||||||
|
}
|
||||||
return BorderStyle{
|
return BorderStyle{
|
||||||
shape: shape,
|
shape: shape,
|
||||||
horizontal: '─',
|
horizontal: '─',
|
||||||
@@ -250,7 +263,7 @@ func MakeBorderStyle(shape BorderShape, unicode bool) BorderStyle {
|
|||||||
|
|
||||||
func MakeTransparentBorder() BorderStyle {
|
func MakeTransparentBorder() BorderStyle {
|
||||||
return BorderStyle{
|
return BorderStyle{
|
||||||
shape: BorderAround,
|
shape: BorderRounded,
|
||||||
horizontal: ' ',
|
horizontal: ' ',
|
||||||
vertical: ' ',
|
vertical: ' ',
|
||||||
topLeft: ' ',
|
topLeft: ' ',
|
||||||
|
|||||||
@@ -130,6 +130,18 @@ func (chars *Chars) TrimLength() uint16 {
|
|||||||
return chars.trimLength
|
return chars.trimLength
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (chars *Chars) LeadingWhitespaces() int {
|
||||||
|
whitespaces := 0
|
||||||
|
for i := 0; i < chars.Length(); i++ {
|
||||||
|
char := chars.Get(i)
|
||||||
|
if !unicode.IsSpace(char) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
whitespaces++
|
||||||
|
}
|
||||||
|
return whitespaces
|
||||||
|
}
|
||||||
|
|
||||||
func (chars *Chars) TrailingWhitespaces() int {
|
func (chars *Chars) TrailingWhitespaces() int {
|
||||||
whitespaces := 0
|
whitespaces := 0
|
||||||
for i := chars.Length() - 1; i >= 0; i-- {
|
for i := chars.Length() - 1; i >= 0; i-- {
|
||||||
|
|||||||
328
test/test_go.rb
328
test/test_go.rb
@@ -9,13 +9,23 @@ require 'minitest/autorun'
|
|||||||
require 'fileutils'
|
require 'fileutils'
|
||||||
require 'English'
|
require 'English'
|
||||||
require 'shellwords'
|
require 'shellwords'
|
||||||
|
require 'erb'
|
||||||
|
require 'tempfile'
|
||||||
|
|
||||||
|
TEMPLATE = DATA.read
|
||||||
|
UNSETS = %w[
|
||||||
|
FZF_DEFAULT_COMMAND FZF_DEFAULT_OPTS
|
||||||
|
FZF_CTRL_T_COMMAND FZF_CTRL_T_OPTS
|
||||||
|
FZF_ALT_C_COMMAND
|
||||||
|
FZF_ALT_C_OPTS FZF_CTRL_R_OPTS
|
||||||
|
fish_history
|
||||||
|
].freeze
|
||||||
DEFAULT_TIMEOUT = 20
|
DEFAULT_TIMEOUT = 20
|
||||||
|
|
||||||
FILE = File.expand_path(__FILE__)
|
FILE = File.expand_path(__FILE__)
|
||||||
base = File.expand_path('../../', __FILE__)
|
BASE = File.expand_path('..', __dir__)
|
||||||
Dir.chdir base
|
Dir.chdir(BASE)
|
||||||
FZF = "FZF_DEFAULT_OPTS= FZF_DEFAULT_COMMAND= #{base}/bin/fzf"
|
FZF = "FZF_DEFAULT_OPTS= FZF_DEFAULT_COMMAND= #{BASE}/bin/fzf"
|
||||||
|
|
||||||
class NilClass
|
class NilClass
|
||||||
def include?(_str)
|
def include?(_str)
|
||||||
@@ -42,89 +52,71 @@ end
|
|||||||
|
|
||||||
class Shell
|
class Shell
|
||||||
class << self
|
class << self
|
||||||
def unsets
|
|
||||||
'unset FZF_DEFAULT_COMMAND FZF_DEFAULT_OPTS FZF_CTRL_T_COMMAND FZF_CTRL_T_OPTS FZF_ALT_C_COMMAND FZF_ALT_C_OPTS FZF_CTRL_R_OPTS;'
|
|
||||||
end
|
|
||||||
|
|
||||||
def bash
|
def bash
|
||||||
'PS1= PROMPT_COMMAND= bash --rcfile ~/.fzf.bash'
|
@bash ||=
|
||||||
|
begin
|
||||||
|
bashrc = '/tmp/fzf.bash'
|
||||||
|
File.open(bashrc, 'w') do |f|
|
||||||
|
f.puts(ERB.new(TEMPLATE).result(binding))
|
||||||
|
end
|
||||||
|
|
||||||
|
"bash --rcfile #{bashrc}"
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def zsh
|
def zsh
|
||||||
FileUtils.mkdir_p '/tmp/fzf-zsh'
|
@zsh ||=
|
||||||
FileUtils.cp File.expand_path('~/.fzf.zsh'), '/tmp/fzf-zsh/.zshrc'
|
begin
|
||||||
'PS1= PROMPT_COMMAND= HISTSIZE=100 ZDOTDIR=/tmp/fzf-zsh zsh'
|
zdotdir = '/tmp/fzf-zsh'
|
||||||
|
FileUtils.rm_rf(zdotdir)
|
||||||
|
FileUtils.mkdir_p(zdotdir)
|
||||||
|
File.open("#{zdotdir}/.zshrc", 'w') do |f|
|
||||||
|
f.puts(ERB.new(TEMPLATE).result(binding))
|
||||||
|
end
|
||||||
|
"ZDOTDIR=#{zdotdir} zsh"
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def fish
|
def fish
|
||||||
'fish'
|
UNSETS.map { |v| v + '= ' }.join + 'fish'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
class Tmux
|
class Tmux
|
||||||
TEMPNAME = '/tmp/fzf-test.txt'
|
|
||||||
|
|
||||||
attr_reader :win
|
attr_reader :win
|
||||||
|
|
||||||
def initialize(shell = :bash)
|
def initialize(shell = :bash)
|
||||||
@win =
|
@win = go(%W[new-window -d -P -F #I #{Shell.send(shell)}]).first
|
||||||
case shell
|
go(%W[set-window-option -t #{@win} pane-base-index 0])
|
||||||
when :bash
|
|
||||||
go("new-window -d -P -F '#I' '#{Shell.unsets + Shell.bash}'").first
|
|
||||||
when :zsh
|
|
||||||
go("new-window -d -P -F '#I' '#{Shell.unsets + Shell.zsh}'").first
|
|
||||||
when :fish
|
|
||||||
go("new-window -d -P -F '#I' '#{Shell.unsets + Shell.fish}'").first
|
|
||||||
else
|
|
||||||
raise "Unknown shell: #{shell}"
|
|
||||||
end
|
|
||||||
go("set-window-option -t #{@win} pane-base-index 0")
|
|
||||||
@lines = `tput lines`.chomp.to_i
|
|
||||||
|
|
||||||
return unless shell == :fish
|
return unless shell == :fish
|
||||||
|
|
||||||
send_keys('function fish_prompt; end; clear', :Enter)
|
send_keys('function fish_prompt; end; clear', :Enter)
|
||||||
self.until(&:empty?)
|
self.until(&:empty?)
|
||||||
end
|
end
|
||||||
|
|
||||||
def kill
|
def kill
|
||||||
go("kill-window -t #{win} 2> /dev/null")
|
go(%W[kill-window -t #{win}])
|
||||||
end
|
end
|
||||||
|
|
||||||
def send_keys(*args)
|
def send_keys(*args)
|
||||||
target =
|
target =
|
||||||
if args.last.is_a?(Hash)
|
if args.last.is_a?(Hash)
|
||||||
hash = args.pop
|
hash = args.pop
|
||||||
go("select-window -t #{win}")
|
go(%W[select-window -t #{win}])
|
||||||
"#{win}.#{hash[:pane]}"
|
"#{win}.#{hash[:pane]}"
|
||||||
else
|
else
|
||||||
win
|
win
|
||||||
end
|
end
|
||||||
enum = (args + [nil]).each_cons(2)
|
go(%W[send-keys -t #{target}] + args.map(&:to_s))
|
||||||
loop do
|
|
||||||
pair = enum.next
|
|
||||||
if pair.first == :Escape
|
|
||||||
arg = pair.compact.map { |key| %("#{key}") }.join(' ')
|
|
||||||
go(%(send-keys -t #{target} #{arg}))
|
|
||||||
enum.next if pair.last
|
|
||||||
else
|
|
||||||
go(%(send-keys -t #{target} "#{pair.first}"))
|
|
||||||
end
|
|
||||||
break unless pair.last
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def paste(str)
|
def paste(str)
|
||||||
`tmux setb '#{str.gsub("'", "'\\''")}' \\; pasteb -t #{win} \\; send-keys -t #{win} Enter`
|
system('tmux', 'setb', str, ';', 'pasteb', '-t', win, ';', 'send-keys', '-t', win, 'Enter')
|
||||||
end
|
end
|
||||||
|
|
||||||
def capture(pane = 0)
|
def capture(pane = 0)
|
||||||
File.unlink TEMPNAME while File.exist? TEMPNAME
|
go(%W[capture-pane -p -t #{win}.#{pane}]).reverse.drop_while(&:empty?).reverse
|
||||||
wait do
|
|
||||||
go("capture-pane -t #{win}.#{pane} \\; save-buffer #{TEMPNAME} 2> /dev/null")
|
|
||||||
$CHILD_STATUS.exitstatus.zero?
|
|
||||||
end
|
|
||||||
File.read(TEMPNAME).split($INPUT_RECORD_SEPARATOR)[0, @lines].reverse.drop_while(&:empty?).reverse
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def until(refresh = false, pane = 0)
|
def until(refresh = false, pane = 0)
|
||||||
@@ -175,7 +167,7 @@ class Tmux
|
|||||||
tries = 0
|
tries = 0
|
||||||
begin
|
begin
|
||||||
self.until do |lines|
|
self.until do |lines|
|
||||||
send_keys 'C-u', 'hello'
|
send_keys ' ', 'C-u', 'hello', :Left, :Right
|
||||||
lines[-1].end_with?('hello')
|
lines[-1].end_with?('hello')
|
||||||
end
|
end
|
||||||
rescue StandardError
|
rescue StandardError
|
||||||
@@ -186,8 +178,8 @@ class Tmux
|
|||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def go(*args)
|
def go(args)
|
||||||
`tmux #{args.join ' '}`.split($INPUT_RECORD_SEPARATOR)
|
IO.popen(['tmux'] + args) { |io| io.readlines(chomp: true) }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -491,14 +483,14 @@ class TestGoFZF < TestBase
|
|||||||
end
|
end
|
||||||
|
|
||||||
def test_query_unicode
|
def test_query_unicode
|
||||||
tmux.paste "(echo abc; echo 가나다) | #{fzf :query, '가다'}"
|
tmux.paste "(echo abc; echo $'\\352\\260\\200\\353\\202\\230\\353\\213\\244') | #{fzf :query, "$'\\352\\260\\200\\353\\213\\244'"}"
|
||||||
tmux.until { |lines| lines[-2].include? '1/2' }
|
tmux.until { |lines| lines[-2].include? '1/2' }
|
||||||
tmux.send_keys :Enter
|
tmux.send_keys :Enter
|
||||||
assert_equal ['가나다'], readonce.split($INPUT_RECORD_SEPARATOR)
|
assert_equal ['가나다'], readonce.split($INPUT_RECORD_SEPARATOR)
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_sync
|
def test_sync
|
||||||
tmux.send_keys "seq 1 100 | #{fzf! :multi} | awk '{print \\$1 \\$1}' | #{fzf :sync}", :Enter
|
tmux.send_keys "seq 1 100 | #{fzf! :multi} | awk '{print $1 $1}' | #{fzf :sync}", :Enter
|
||||||
tmux.until { |lines| lines[-1] == '>' }
|
tmux.until { |lines| lines[-1] == '>' }
|
||||||
tmux.send_keys 9
|
tmux.send_keys 9
|
||||||
tmux.until { |lines| lines[-2] == ' 19/100' }
|
tmux.until { |lines| lines[-2] == ' 19/100' }
|
||||||
@@ -779,7 +771,7 @@ class TestGoFZF < TestBase
|
|||||||
end
|
end
|
||||||
|
|
||||||
def test_invalid_cache_query_type
|
def test_invalid_cache_query_type
|
||||||
command = %[(echo 'foo\\$bar'; echo 'barfoo'; echo 'foo^bar'; echo \\"foo'1-2\\"; seq 100) | #{fzf}]
|
command = %[(echo 'foo$bar'; echo 'barfoo'; echo 'foo^bar'; echo "foo'1-2"; seq 100) | #{fzf}]
|
||||||
|
|
||||||
# Suffix match
|
# Suffix match
|
||||||
tmux.send_keys command, :Enter
|
tmux.send_keys command, :Enter
|
||||||
@@ -938,7 +930,7 @@ class TestGoFZF < TestBase
|
|||||||
|
|
||||||
def test_execute
|
def test_execute
|
||||||
output = '/tmp/fzf-test-execute'
|
output = '/tmp/fzf-test-execute'
|
||||||
opts = %[--bind \\"alt-a:execute(echo /{}/ >> #{output}),alt-b:execute[echo /{}{}/ >> #{output}],C:execute:echo /{}{}{}/ >> #{output}\\"]
|
opts = %[--bind "alt-a:execute(echo /{}/ >> #{output}),alt-b:execute[echo /{}{}/ >> #{output}],C:execute:echo /{}{}{}/ >> #{output}"]
|
||||||
wait = ->(exp) { tmux.until { |lines| lines[-2].include? exp } }
|
wait = ->(exp) { tmux.until { |lines| lines[-2].include? exp } }
|
||||||
writelines tempname, %w[foo'bar foo"bar foo$bar]
|
writelines tempname, %w[foo'bar foo"bar foo$bar]
|
||||||
tmux.send_keys "cat #{tempname} | #{fzf opts}; sync", :Enter
|
tmux.send_keys "cat #{tempname} | #{fzf opts}; sync", :Enter
|
||||||
@@ -977,7 +969,7 @@ class TestGoFZF < TestBase
|
|||||||
|
|
||||||
def test_execute_multi
|
def test_execute_multi
|
||||||
output = '/tmp/fzf-test-execute-multi'
|
output = '/tmp/fzf-test-execute-multi'
|
||||||
opts = %[--multi --bind \\"alt-a:execute-multi(echo {}/{+} >> #{output}; sync)\\"]
|
opts = %[--multi --bind "alt-a:execute-multi(echo {}/{+} >> #{output}; sync)"]
|
||||||
writelines tempname, %w[foo'bar foo"bar foo$bar foobar]
|
writelines tempname, %w[foo'bar foo"bar foo$bar foobar]
|
||||||
tmux.send_keys "cat #{tempname} | #{fzf opts}", :Enter
|
tmux.send_keys "cat #{tempname} | #{fzf opts}", :Enter
|
||||||
tmux.until { |lines| lines[-2].include? '4/4' }
|
tmux.until { |lines| lines[-2].include? '4/4' }
|
||||||
@@ -1163,7 +1155,7 @@ class TestGoFZF < TestBase
|
|||||||
end
|
end
|
||||||
|
|
||||||
def test_header
|
def test_header
|
||||||
tmux.send_keys "seq 100 | #{fzf "--header \\\"\\$(head -5 #{FILE})\\\""}", :Enter
|
tmux.send_keys "seq 100 | #{fzf "--header \"$(head -5 #{FILE})\""}", :Enter
|
||||||
header = File.readlines(FILE).take(5).map(&:strip)
|
header = File.readlines(FILE).take(5).map(&:strip)
|
||||||
tmux.until do |lines|
|
tmux.until do |lines|
|
||||||
lines[-2].include?('100/100') &&
|
lines[-2].include?('100/100') &&
|
||||||
@@ -1173,7 +1165,7 @@ class TestGoFZF < TestBase
|
|||||||
end
|
end
|
||||||
|
|
||||||
def test_header_reverse
|
def test_header_reverse
|
||||||
tmux.send_keys "seq 100 | #{fzf "--header=\\\"\\$(head -5 #{FILE})\\\" --reverse"}", :Enter
|
tmux.send_keys "seq 100 | #{fzf "--header \"$(head -5 #{FILE})\" --reverse"}", :Enter
|
||||||
header = File.readlines(FILE).take(5).map(&:strip)
|
header = File.readlines(FILE).take(5).map(&:strip)
|
||||||
tmux.until do |lines|
|
tmux.until do |lines|
|
||||||
lines[1].include?('100/100') &&
|
lines[1].include?('100/100') &&
|
||||||
@@ -1183,7 +1175,7 @@ class TestGoFZF < TestBase
|
|||||||
end
|
end
|
||||||
|
|
||||||
def test_header_reverse_list
|
def test_header_reverse_list
|
||||||
tmux.send_keys "seq 100 | #{fzf "--header=\\\"\\$(head -5 #{FILE})\\\" --layout=reverse-list"}", :Enter
|
tmux.send_keys "seq 100 | #{fzf "--header \"$(head -5 #{FILE})\" --layout=reverse-list"}", :Enter
|
||||||
header = File.readlines(FILE).take(5).map(&:strip)
|
header = File.readlines(FILE).take(5).map(&:strip)
|
||||||
tmux.until do |lines|
|
tmux.until do |lines|
|
||||||
lines[-2].include?('100/100') &&
|
lines[-2].include?('100/100') &&
|
||||||
@@ -1193,7 +1185,7 @@ class TestGoFZF < TestBase
|
|||||||
end
|
end
|
||||||
|
|
||||||
def test_header_and_header_lines
|
def test_header_and_header_lines
|
||||||
tmux.send_keys "seq 100 | #{fzf "--header-lines 10 --header \\\"\\$(head -5 #{FILE})\\\""}", :Enter
|
tmux.send_keys "seq 100 | #{fzf "--header-lines 10 --header \"$(head -5 #{FILE})\""}", :Enter
|
||||||
header = File.readlines(FILE).take(5).map(&:strip)
|
header = File.readlines(FILE).take(5).map(&:strip)
|
||||||
tmux.until do |lines|
|
tmux.until do |lines|
|
||||||
lines[-2].include?('90/90') &&
|
lines[-2].include?('90/90') &&
|
||||||
@@ -1203,7 +1195,7 @@ class TestGoFZF < TestBase
|
|||||||
end
|
end
|
||||||
|
|
||||||
def test_header_and_header_lines_reverse
|
def test_header_and_header_lines_reverse
|
||||||
tmux.send_keys "seq 100 | #{fzf "--reverse --header-lines 10 --header \\\"\\$(head -5 #{FILE})\\\""}", :Enter
|
tmux.send_keys "seq 100 | #{fzf "--reverse --header-lines 10 --header \"$(head -5 #{FILE})\""}", :Enter
|
||||||
header = File.readlines(FILE).take(5).map(&:strip)
|
header = File.readlines(FILE).take(5).map(&:strip)
|
||||||
tmux.until do |lines|
|
tmux.until do |lines|
|
||||||
lines[1].include?('90/90') &&
|
lines[1].include?('90/90') &&
|
||||||
@@ -1213,7 +1205,7 @@ class TestGoFZF < TestBase
|
|||||||
end
|
end
|
||||||
|
|
||||||
def test_header_and_header_lines_reverse_list
|
def test_header_and_header_lines_reverse_list
|
||||||
tmux.send_keys "seq 100 | #{fzf "--layout=reverse-list --header-lines 10 --header \\\"\\$(head -5 #{FILE})\\\""}", :Enter
|
tmux.send_keys "seq 100 | #{fzf "--layout=reverse-list --header-lines 10 --header \"$(head -5 #{FILE})\""}", :Enter
|
||||||
header = File.readlines(FILE).take(5).map(&:strip)
|
header = File.readlines(FILE).take(5).map(&:strip)
|
||||||
tmux.until do |lines|
|
tmux.until do |lines|
|
||||||
lines[-2].include?('90/90') &&
|
lines[-2].include?('90/90') &&
|
||||||
@@ -1324,7 +1316,7 @@ class TestGoFZF < TestBase
|
|||||||
|
|
||||||
def test_exitstatus_empty
|
def test_exitstatus_empty
|
||||||
{ '99' => '0', '999' => '1' }.each do |query, status|
|
{ '99' => '0', '999' => '1' }.each do |query, status|
|
||||||
tmux.send_keys "seq 100 | #{FZF} -q #{query}; echo --\\$?--", :Enter
|
tmux.send_keys "seq 100 | #{FZF} -q #{query}; echo --$?--", :Enter
|
||||||
tmux.until { |lines| lines[-2] =~ %r{ [10]/100} }
|
tmux.until { |lines| lines[-2] =~ %r{ [10]/100} }
|
||||||
tmux.send_keys :Enter
|
tmux.send_keys :Enter
|
||||||
tmux.until { |lines| lines.last.include? "--#{status}--" }
|
tmux.until { |lines| lines.last.include? "--#{status}--" }
|
||||||
@@ -1407,6 +1399,39 @@ class TestGoFZF < TestBase
|
|||||||
assert_equal '3', readonce.chomp
|
assert_equal '3', readonce.chomp
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_pointer
|
||||||
|
pointer = '>>'
|
||||||
|
tmux.send_keys "seq 10 | #{fzf "--pointer '#{pointer}'"}", :Enter
|
||||||
|
tmux.until { |lines| lines[-2] == ' 10/10' }
|
||||||
|
lines = tmux.capture
|
||||||
|
# Assert that specified pointer is displayed
|
||||||
|
assert_equal "#{pointer} 1", lines[-3]
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_pointer_with_jump
|
||||||
|
pointer = '>>'
|
||||||
|
tmux.send_keys "seq 10 | #{fzf "--multi --jump-labels 12345 --bind 'ctrl-j:jump' --pointer '#{pointer}'"}", :Enter
|
||||||
|
tmux.until { |lines| lines[-2] == ' 10/10' }
|
||||||
|
tmux.send_keys 'C-j'
|
||||||
|
# Correctly padded jump label should appear
|
||||||
|
tmux.until { |lines| lines[-7] == '5 5' }
|
||||||
|
tmux.until { |lines| lines[-8] == ' 6' }
|
||||||
|
tmux.send_keys '5'
|
||||||
|
lines = tmux.capture
|
||||||
|
# Assert that specified pointer is displayed
|
||||||
|
assert_equal "#{pointer} 5", lines[-7]
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_marker
|
||||||
|
marker = '>>'
|
||||||
|
tmux.send_keys "seq 10 | #{fzf "--multi --marker '#{marker}'"}", :Enter
|
||||||
|
tmux.until { |lines| lines[-2] == ' 10/10' }
|
||||||
|
tmux.send_keys :BTab
|
||||||
|
lines = tmux.capture
|
||||||
|
# Assert that specified marker is displayed
|
||||||
|
assert_equal " #{marker}1", lines[-3]
|
||||||
|
end
|
||||||
|
|
||||||
def test_preview
|
def test_preview
|
||||||
tmux.send_keys %(seq 1000 | sed s/^2$// | #{FZF} -m --preview 'sleep 0.2; echo {{}-{+}}' --bind ?:toggle-preview), :Enter
|
tmux.send_keys %(seq 1000 | sed s/^2$// | #{FZF} -m --preview 'sleep 0.2; echo {{}-{+}}' --bind ?:toggle-preview), :Enter
|
||||||
tmux.until { |lines| lines[1].include?(' {1-1}') }
|
tmux.until { |lines| lines[1].include?(' {1-1}') }
|
||||||
@@ -1436,7 +1461,7 @@ class TestGoFZF < TestBase
|
|||||||
end
|
end
|
||||||
|
|
||||||
def test_preview_hidden
|
def test_preview_hidden
|
||||||
tmux.send_keys %(seq 1000 | #{FZF} --preview 'echo {{}-{}-\\$FZF_PREVIEW_LINES-\\$FZF_PREVIEW_COLUMNS}' --preview-window down:1:hidden --bind ?:toggle-preview), :Enter
|
tmux.send_keys %(seq 1000 | #{FZF} --preview 'echo {{}-{}-$FZF_PREVIEW_LINES-$FZF_PREVIEW_COLUMNS}' --preview-window down:1:hidden --bind ?:toggle-preview), :Enter
|
||||||
tmux.until { |lines| lines[-1] == '>' }
|
tmux.until { |lines| lines[-1] == '>' }
|
||||||
tmux.send_keys '?'
|
tmux.send_keys '?'
|
||||||
tmux.until { |lines| lines[-2] =~ / {1-1-1-[0-9]+}/ }
|
tmux.until { |lines| lines[-2] =~ / {1-1-1-[0-9]+}/ }
|
||||||
@@ -1677,11 +1702,43 @@ class TestGoFZF < TestBase
|
|||||||
tmux.until { |lines| lines.match_count.zero? }
|
tmux.until { |lines| lines.match_count.zero? }
|
||||||
tmux.until { |lines| !lines[-2].include?('(1)') }
|
tmux.until { |lines| !lines[-2].include?('(1)') }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_backward_delete_char_eof
|
||||||
|
tmux.send_keys "seq 1000 | #{fzf "--bind 'bs:backward-delete-char/eof'"}", :Enter
|
||||||
|
tmux.until { |lines| lines[-2] == ' 1000/1000' }
|
||||||
|
tmux.send_keys '11'
|
||||||
|
tmux.until { |lines| lines[-1] == '> 11' }
|
||||||
|
tmux.send_keys :BSpace
|
||||||
|
tmux.until { |lines| lines[-1] == '> 1' }
|
||||||
|
tmux.send_keys :BSpace
|
||||||
|
tmux.until { |lines| lines[-1] == '>' }
|
||||||
|
tmux.send_keys :BSpace
|
||||||
|
tmux.prepare
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_strip_xterm_osc_sequence
|
||||||
|
%W[\x07 \x1b\\].each do |esc|
|
||||||
|
writelines tempname, [%(printf $1"\e]4;3;rgb:aa/bb/cc#{esc} "$2)]
|
||||||
|
File.chmod(0o755, tempname)
|
||||||
|
tmux.prepare
|
||||||
|
tmux.send_keys(
|
||||||
|
%(echo foo bar | #{FZF} --preview '#{tempname} {2} {1}'), :Enter
|
||||||
|
)
|
||||||
|
tmux.until { |lines| lines.any_include?('bar foo') }
|
||||||
|
tmux.send_keys :Enter
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_keep_right
|
||||||
|
tmux.send_keys("seq 10000 | #{FZF} --read0 --keep-right", :Enter)
|
||||||
|
tmux.until { |lines| lines.any_include?('9999 10000') }
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
module TestShell
|
module TestShell
|
||||||
def setup
|
def setup
|
||||||
super
|
@tmux = Tmux.new shell
|
||||||
|
tmux.prepare
|
||||||
end
|
end
|
||||||
|
|
||||||
def teardown
|
def teardown
|
||||||
@@ -1741,7 +1798,7 @@ module TestShell
|
|||||||
tmux.until { |lines| lines.select_count == 2 }
|
tmux.until { |lines| lines.select_count == 2 }
|
||||||
|
|
||||||
tmux.send_keys :Enter
|
tmux.send_keys :Enter
|
||||||
tmux.until { |lines| lines.any_include?(/echo.*fzf-unicode.*1.*fzf-unicode.*2/) }
|
tmux.until { |lines| lines.join.match(/echo.*fzf-unicode.*1.*fzf-unicode.*2/) }
|
||||||
tmux.send_keys :Enter
|
tmux.send_keys :Enter
|
||||||
tmux.until { |lines| lines.any_include?(/^fzf-unicode.*1.*fzf-unicode.*2/) }
|
tmux.until { |lines| lines.any_include?(/^fzf-unicode.*1.*fzf-unicode.*2/) }
|
||||||
end
|
end
|
||||||
@@ -1798,6 +1855,37 @@ module TestShell
|
|||||||
tmux.until { |lines| lines[-1] == '3rd' }
|
tmux.until { |lines| lines[-1] == '3rd' }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_ctrl_r_multiline
|
||||||
|
tmux.send_keys 'echo "foo', :Enter, 'bar"', :Enter
|
||||||
|
tmux.until { |lines| lines[-2..-1] == ['foo', 'bar'] }
|
||||||
|
retries do
|
||||||
|
tmux.prepare
|
||||||
|
tmux.send_keys 'C-r'
|
||||||
|
tmux.until { |lines| lines[-1] == '>' }
|
||||||
|
end
|
||||||
|
tmux.send_keys 'foo bar'
|
||||||
|
tmux.until { |lines| lines[-3].end_with? 'bar"' }
|
||||||
|
tmux.send_keys :Enter
|
||||||
|
tmux.until { |lines| lines[-1].end_with? 'bar"' }
|
||||||
|
tmux.send_keys :Enter
|
||||||
|
tmux.until { |lines| lines[-2..-1] == ['foo', 'bar'] }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_ctrl_r_abort
|
||||||
|
skip "doesn't restore the original line when search is aborted pre Bash 4" if shell == :bash && /(?<= version )\d+/.match(`#{Shell.bash} --version`).to_s.to_i < 4
|
||||||
|
%w[foo ' "].each do |query|
|
||||||
|
retries do
|
||||||
|
tmux.prepare
|
||||||
|
tmux.send_keys(:Space, 'C-e', 'C-u', query)
|
||||||
|
tmux.until { |lines| lines[-1].start_with? query }
|
||||||
|
tmux.send_keys 'C-r'
|
||||||
|
tmux.until { |lines| lines[-1] == "> #{query}" }
|
||||||
|
end
|
||||||
|
tmux.send_keys 'C-g'
|
||||||
|
tmux.until { |lines| lines[-1].start_with? query }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def retries(times = 3)
|
def retries(times = 3)
|
||||||
(times - 1).times do
|
(times - 1).times do
|
||||||
begin
|
begin
|
||||||
@@ -1927,7 +2015,7 @@ module CompletionTest
|
|||||||
end
|
end
|
||||||
|
|
||||||
def test_custom_completion
|
def test_custom_completion
|
||||||
tmux.send_keys '_fzf_compgen_path() { echo "\$1"; seq 10; }', :Enter
|
tmux.send_keys '_fzf_compgen_path() { echo "$1"; seq 10; }', :Enter
|
||||||
tmux.prepare
|
tmux.prepare
|
||||||
tmux.send_keys 'ls /tmp/**', :Tab
|
tmux.send_keys 'ls /tmp/**', :Tab
|
||||||
tmux.until { |lines| lines.match_count == 11 }
|
tmux.until { |lines| lines.match_count == 11 }
|
||||||
@@ -1958,7 +2046,7 @@ module CompletionTest
|
|||||||
|
|
||||||
def test_file_completion_unicode
|
def test_file_completion_unicode
|
||||||
FileUtils.mkdir_p '/tmp/fzf-test'
|
FileUtils.mkdir_p '/tmp/fzf-test'
|
||||||
tmux.paste 'cd /tmp/fzf-test; echo -n test3 > "fzf-unicode 테스트1"; echo -n test4 > "fzf-unicode 테스트2"'
|
tmux.paste "cd /tmp/fzf-test; echo -n test3 > $'fzf-unicode \\355\\205\\214\\354\\212\\244\\355\\212\\2701'; echo -n test4 > $'fzf-unicode \\355\\205\\214\\354\\212\\244\\355\\212\\2702'"
|
||||||
tmux.prepare
|
tmux.prepare
|
||||||
tmux.send_keys 'cat fzf-unicode**', :Tab
|
tmux.send_keys 'cat fzf-unicode**', :Tab
|
||||||
tmux.until { |lines| lines.match_count == 2 }
|
tmux.until { |lines| lines.match_count == 2 }
|
||||||
@@ -1981,23 +2069,41 @@ module CompletionTest
|
|||||||
tmux.send_keys :Enter
|
tmux.send_keys :Enter
|
||||||
tmux.until { |lines| lines[-1].include? 'test3test4' }
|
tmux.until { |lines| lines[-1].include? 'test3test4' }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_custom_completion_api
|
||||||
|
tmux.send_keys 'eval "_fzf$(declare -f _comprun)"', :Enter
|
||||||
|
%w[f g].each do |command|
|
||||||
|
tmux.prepare
|
||||||
|
tmux.send_keys "#{command} b**", :Tab
|
||||||
|
tmux.until do |lines|
|
||||||
|
lines.item_count == 2 && lines.match_count == 1 &&
|
||||||
|
lines.any_include?("prompt-#{command}") &&
|
||||||
|
lines.any_include?("preview-#{command}-bar")
|
||||||
|
end
|
||||||
|
tmux.send_keys :Enter
|
||||||
|
tmux.until { |lines| lines[-1].include?("#{command} #{command}barbar") }
|
||||||
|
tmux.send_keys 'C-u'
|
||||||
|
end
|
||||||
|
ensure
|
||||||
|
tmux.prepare
|
||||||
|
tmux.send_keys 'unset -f _fzf_comprun', :Enter
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
class TestBash < TestBase
|
class TestBash < TestBase
|
||||||
include TestShell
|
include TestShell
|
||||||
include CompletionTest
|
include CompletionTest
|
||||||
|
|
||||||
|
def shell
|
||||||
|
:bash
|
||||||
|
end
|
||||||
|
|
||||||
def new_shell
|
def new_shell
|
||||||
tmux.prepare
|
tmux.prepare
|
||||||
tmux.send_keys "FZF_TMUX=1 #{Shell.bash}", :Enter
|
tmux.send_keys "FZF_TMUX=1 #{Shell.bash}", :Enter
|
||||||
tmux.prepare
|
tmux.prepare
|
||||||
end
|
end
|
||||||
|
|
||||||
def setup
|
|
||||||
super
|
|
||||||
@tmux = Tmux.new :bash
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_dynamic_completion_loader
|
def test_dynamic_completion_loader
|
||||||
tmux.paste 'touch /tmp/foo; _fzf_completion_loader=1'
|
tmux.paste 'touch /tmp/foo; _fzf_completion_loader=1'
|
||||||
tmux.paste '_completion_loader() { complete -o default fake; }'
|
tmux.paste '_completion_loader() { complete -o default fake; }'
|
||||||
@@ -2020,20 +2126,23 @@ class TestZsh < TestBase
|
|||||||
include TestShell
|
include TestShell
|
||||||
include CompletionTest
|
include CompletionTest
|
||||||
|
|
||||||
|
def shell
|
||||||
|
:zsh
|
||||||
|
end
|
||||||
|
|
||||||
def new_shell
|
def new_shell
|
||||||
tmux.send_keys "FZF_TMUX=1 #{Shell.zsh}", :Enter
|
tmux.send_keys "FZF_TMUX=1 #{Shell.zsh}", :Enter
|
||||||
tmux.prepare
|
tmux.prepare
|
||||||
end
|
end
|
||||||
|
|
||||||
def setup
|
|
||||||
super
|
|
||||||
@tmux = Tmux.new :zsh
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
class TestFish < TestBase
|
class TestFish < TestBase
|
||||||
include TestShell
|
include TestShell
|
||||||
|
|
||||||
|
def shell
|
||||||
|
:fish
|
||||||
|
end
|
||||||
|
|
||||||
def new_shell
|
def new_shell
|
||||||
tmux.send_keys 'env FZF_TMUX=1 fish', :Enter
|
tmux.send_keys 'env FZF_TMUX=1 fish', :Enter
|
||||||
tmux.send_keys 'function fish_prompt; end; clear', :Enter
|
tmux.send_keys 'function fish_prompt; end; clear', :Enter
|
||||||
@@ -2045,9 +2154,60 @@ class TestFish < TestBase
|
|||||||
tmux.send_keys "set -g #{name} '#{val}'", :Enter
|
tmux.send_keys "set -g #{name} '#{val}'", :Enter
|
||||||
tmux.prepare
|
tmux.prepare
|
||||||
end
|
end
|
||||||
|
|
||||||
def setup
|
|
||||||
super
|
|
||||||
@tmux = Tmux.new :fish
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
__END__
|
||||||
|
# Setup fzf
|
||||||
|
# ---------
|
||||||
|
if [[ ! "$PATH" == *<%= BASE %>/bin* ]]; then
|
||||||
|
export PATH="${PATH:+${PATH}:}<%= BASE %>/bin"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Auto-completion
|
||||||
|
# ---------------
|
||||||
|
[[ $- == *i* ]] && source "<%= BASE %>/shell/completion.<%= __method__ %>" 2> /dev/null
|
||||||
|
|
||||||
|
# Key bindings
|
||||||
|
# ------------
|
||||||
|
source "<%= BASE %>/shell/key-bindings.<%= __method__ %>"
|
||||||
|
|
||||||
|
PS1= PROMPT_COMMAND= HISTFILE= HISTSIZE=100
|
||||||
|
unset <%= UNSETS.join(' ') %>
|
||||||
|
|
||||||
|
# Old API
|
||||||
|
_fzf_complete_f() {
|
||||||
|
_fzf_complete "--multi --prompt \"prompt-f> \"" "$@" < <(
|
||||||
|
echo foo
|
||||||
|
echo bar
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
# New API
|
||||||
|
_fzf_complete_g() {
|
||||||
|
_fzf_complete --multi --prompt "prompt-g> " -- "$@" < <(
|
||||||
|
echo foo
|
||||||
|
echo bar
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
_fzf_complete_f_post() {
|
||||||
|
awk '{print "f" $0 $0}'
|
||||||
|
}
|
||||||
|
|
||||||
|
_fzf_complete_g_post() {
|
||||||
|
awk '{print "g" $0 $0}'
|
||||||
|
}
|
||||||
|
|
||||||
|
[ -n "$BASH" ] && complete -F _fzf_complete_f -o default -o bashdefault f
|
||||||
|
[ -n "$BASH" ] && complete -F _fzf_complete_g -o default -o bashdefault g
|
||||||
|
|
||||||
|
_comprun() {
|
||||||
|
local command=$1
|
||||||
|
shift
|
||||||
|
|
||||||
|
case "$command" in
|
||||||
|
f) fzf "$@" --preview 'echo preview-f-{}' ;;
|
||||||
|
g) fzf "$@" --preview 'echo preview-g-{}' ;;
|
||||||
|
*) fzf "$@" ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user