mirror of
https://github.com/junegunn/fzf.git
synced 2025-11-15 14:53:47 -05:00
Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dc975e8974 | ||
|
|
4311ade535 | ||
|
|
cd23401411 | ||
|
|
176ee6910f | ||
|
|
13c8f3d3aa | ||
|
|
ce9af687bc | ||
|
|
43f0d0cacd | ||
|
|
20b4e6953e | ||
|
|
7da287e3aa | ||
|
|
205f885d69 | ||
|
|
3715cd349d |
38
ADVANCED.md
38
ADVANCED.md
@@ -429,30 +429,34 @@ Admittedly, that was a silly example. Here's a practical one for browsing
|
||||
Kubernetes pods.
|
||||
|
||||
```bash
|
||||
#!/usr/bin/env bash
|
||||
|
||||
read -ra tokens < <(
|
||||
kubectl get pods --all-namespaces |
|
||||
fzf --info=inline --layout=reverse --header-lines=1 --border \
|
||||
pods() {
|
||||
FZF_DEFAULT_COMMAND="kubectl get pods --all-namespaces" \
|
||||
fzf --info=inline --layout=reverse --header-lines=1 \
|
||||
--prompt "$(kubectl config current-context | sed 's/-context$//')> " \
|
||||
--header $'Press CTRL-O to open log in editor\n\n' \
|
||||
--bind ctrl-/:toggle-preview \
|
||||
--bind 'ctrl-o:execute:${EDITOR:-vim} <(kubectl logs --namespace {1} {2}) > /dev/tty' \
|
||||
--preview-window up,follow \
|
||||
--preview 'kubectl logs --follow --tail=100000 --namespace {1} {2}' "$@"
|
||||
)
|
||||
[ ${#tokens} -gt 1 ] &&
|
||||
kubectl exec -it --namespace "${tokens[0]}" "${tokens[1]}" -- bash
|
||||
--header $'╱ Enter (kubectl exec) ╱ CTRL-O (open log in editor) ╱ CTRL-R (reload) ╱\n\n' \
|
||||
--bind 'ctrl-/:change-preview-window(80%,border-bottom|hidden|)' \
|
||||
--bind 'enter:execute:kubectl exec -it --namespace {1} {2} -- bash > /dev/tty' \
|
||||
--bind 'ctrl-o:execute:${EDITOR:-vim} <(kubectl logs --all-containers --namespace {1} {2}) > /dev/tty' \
|
||||
--bind 'ctrl-r:reload:$FZF_DEFAULT_COMMAND' \
|
||||
--preview-window up:follow \
|
||||
--preview 'kubectl logs --follow --all-containers --tail=10000 --namespace {1} {2}' "$@"
|
||||
}
|
||||
```
|
||||
|
||||

|
||||
|
||||
- The preview window will *"log tail"* the pod
|
||||
- Holding on to a large amount of log will consume a lot of memory. So we
|
||||
limited the initial log amount with `--tail=100000`.
|
||||
- With `execute` binding, you can press CTRL-O to open the log in your editor
|
||||
without leaving fzf
|
||||
- Select a pod (with an enter key) to `kubectl exec` into it
|
||||
limited the initial log amount with `--tail=10000`.
|
||||
- `execute` bindings allow you to run any command without leaving fzf
|
||||
- Press enter key on a pod to `kubectl exec` into it
|
||||
- Press CTRL-O to open the log in your editor
|
||||
- Press CTRL-R to reload the pod list
|
||||
- Press CTRL-/ repeatedly to to rotate through a different sets of preview
|
||||
window options
|
||||
1. `80%,border-bottom`
|
||||
1. `hidden`
|
||||
1. Empty string after `|` translates to the default options from `--preview-window`
|
||||
|
||||
Key bindings for git objects
|
||||
----------------------------
|
||||
|
||||
13
CHANGELOG.md
13
CHANGELOG.md
@@ -1,6 +1,19 @@
|
||||
CHANGELOG
|
||||
=========
|
||||
|
||||
0.29.0
|
||||
------
|
||||
- Added `change-preview(...)` action to change the `--preview` command
|
||||
- cf. `preview(...)` is a one-off action that doesn't change the default
|
||||
preview command
|
||||
- Added `change-preview-window(...)` action
|
||||
- You can rotate through the different options separated by `|`
|
||||
```sh
|
||||
fzf --preview 'cat {}' --preview-window right:40% \
|
||||
--bind 'ctrl-/:change-preview-window(right,70%|down,40%,border-top|hidden|)'
|
||||
```
|
||||
- Fixed rendering of the prompt line when overflow occurs with `--info=inline`
|
||||
|
||||
0.28.0
|
||||
------
|
||||
- Added `--header-first` option to print header before the prompt line
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM archlinux/base:latest
|
||||
FROM archlinux
|
||||
RUN pacman -Sy && pacman --noconfirm -S awk git tmux zsh fish ruby procps go make gcc
|
||||
RUN gem install --no-document -v 5.14.2 minitest
|
||||
RUN echo '. /usr/share/bash-completion/completions/git' >> ~/.bashrc
|
||||
|
||||
@@ -86,7 +86,7 @@ stuff.
|
||||
|
||||
### Using Homebrew
|
||||
|
||||
You can use [Homebrew](http://brew.sh/) (on macOS or Linux)
|
||||
You can use [Homebrew](https://brew.sh/) (on macOS or Linux)
|
||||
to install fzf.
|
||||
|
||||
```sh
|
||||
@@ -131,6 +131,8 @@ git clone --depth 1 https://github.com/junegunn/fzf.git ~/.fzf
|
||||
>
|
||||
> Refer to the package documentation for more information. (e.g. `apt-cache show fzf`)
|
||||
|
||||
[](https://repology.org/project/fzf/versions)
|
||||
|
||||
### Windows
|
||||
|
||||
Pre-built binaries for Windows can be downloaded [here][bin]. fzf is also
|
||||
|
||||
2
install
2
install
@@ -2,7 +2,7 @@
|
||||
|
||||
set -u
|
||||
|
||||
version=0.28.0
|
||||
version=0.29.0
|
||||
auto_completion=
|
||||
key_bindings=
|
||||
update_config=2
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
$version="0.28.0"
|
||||
$version="0.29.0"
|
||||
|
||||
$fzf_base=Split-Path -Parent $MyInvocation.MyCommand.Definition
|
||||
|
||||
|
||||
2
main.go
2
main.go
@@ -5,7 +5,7 @@ import (
|
||||
"github.com/junegunn/fzf/src/protector"
|
||||
)
|
||||
|
||||
var version string = "0.28"
|
||||
var version string = "0.29"
|
||||
var revision string = "devel"
|
||||
|
||||
func main() {
|
||||
|
||||
@@ -21,7 +21,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
..
|
||||
.TH fzf-tmux 1 "Nov 2021" "fzf 0.28.0" "fzf-tmux - open fzf in tmux split pane"
|
||||
.TH fzf-tmux 1 "Dec 2021" "fzf 0.29.0" "fzf-tmux - open fzf in tmux split pane"
|
||||
|
||||
.SH NAME
|
||||
fzf-tmux - open fzf in tmux split pane
|
||||
|
||||
131
man/man1/fzf.1
131
man/man1/fzf.1
@@ -21,7 +21,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
..
|
||||
.TH fzf 1 "Nov 2021" "fzf 0.28.0" "fzf - a command-line fuzzy finder"
|
||||
.TH fzf 1 "Dec 2021" "fzf 0.29.0" "fzf - a command-line fuzzy finder"
|
||||
|
||||
.SH NAME
|
||||
fzf - a command-line fuzzy finder
|
||||
@@ -810,77 +810,79 @@ e.g.
|
||||
A key or an event can be bound to one or more of the following actions.
|
||||
|
||||
\fBACTION: DEFAULT BINDINGS (NOTES):
|
||||
\fBabort\fR \fIctrl-c ctrl-g ctrl-q esc\fR
|
||||
\fBaccept\fR \fIenter double-click\fR
|
||||
\fBaccept-non-empty\fR (same as \fBaccept\fR except that it prevents fzf from exiting without selection)
|
||||
\fBbackward-char\fR \fIctrl-b left\fR
|
||||
\fBbackward-delete-char\fR \fIctrl-h bspace\fR
|
||||
\fBbackward-delete-char/eof\fR (same as \fBbackward-delete-char\fR except aborts fzf if query is empty)
|
||||
\fBbackward-kill-word\fR \fIalt-bs\fR
|
||||
\fBbackward-word\fR \fIalt-b shift-left\fR
|
||||
\fBbeginning-of-line\fR \fIctrl-a home\fR
|
||||
\fBcancel\fR (clear query string if not empty, abort fzf otherwise)
|
||||
\fBchange-prompt(...)\fR (change prompt to the given string)
|
||||
\fBclear-screen\fR \fIctrl-l\fR
|
||||
\fBclear-selection\fR (clear multi-selection)
|
||||
\fBclose\fR (close preview window if open, abort fzf otherwise)
|
||||
\fBclear-query\fR (clear query string)
|
||||
\fBdelete-char\fR \fIdel\fR
|
||||
\fBdelete-char/eof\fR \fIctrl-d\fR (same as \fBdelete-char\fR except aborts fzf if query is empty)
|
||||
\fBabort\fR \fIctrl-c ctrl-g ctrl-q esc\fR
|
||||
\fBaccept\fR \fIenter double-click\fR
|
||||
\fBaccept-non-empty\fR (same as \fBaccept\fR except that it prevents fzf from exiting without selection)
|
||||
\fBbackward-char\fR \fIctrl-b left\fR
|
||||
\fBbackward-delete-char\fR \fIctrl-h bspace\fR
|
||||
\fBbackward-delete-char/eof\fR (same as \fBbackward-delete-char\fR except aborts fzf if query is empty)
|
||||
\fBbackward-kill-word\fR \fIalt-bs\fR
|
||||
\fBbackward-word\fR \fIalt-b shift-left\fR
|
||||
\fBbeginning-of-line\fR \fIctrl-a home\fR
|
||||
\fBcancel\fR (clear query string if not empty, abort fzf otherwise)
|
||||
\fBchange-preview(...)\fR (change \fB--preview\fR option)
|
||||
\fBchange-preview-window(...)\fR (change \fB--preview-window\fR option; rotate through the multiple option sets separated by '|')
|
||||
\fBchange-prompt(...)\fR (change prompt to the given string)
|
||||
\fBclear-screen\fR \fIctrl-l\fR
|
||||
\fBclear-selection\fR (clear multi-selection)
|
||||
\fBclose\fR (close preview window if open, abort fzf otherwise)
|
||||
\fBclear-query\fR (clear query string)
|
||||
\fBdelete-char\fR \fIdel\fR
|
||||
\fBdelete-char/eof\fR \fIctrl-d\fR (same as \fBdelete-char\fR except aborts fzf if query is empty)
|
||||
\fBdeselect\fR
|
||||
\fBdeselect-all\fR (deselect all matches)
|
||||
\fBdisable-search\fR (disable search functionality)
|
||||
\fBdown\fR \fIctrl-j ctrl-n down\fR
|
||||
\fBenable-search\fR (enable search functionality)
|
||||
\fBend-of-line\fR \fIctrl-e end\fR
|
||||
\fBexecute(...)\fR (see below for the details)
|
||||
\fBexecute-silent(...)\fR (see below for the details)
|
||||
\fBfirst\fR (move to the first match)
|
||||
\fBforward-char\fR \fIctrl-f right\fR
|
||||
\fBforward-word\fR \fIalt-f shift-right\fR
|
||||
\fBdeselect-all\fR (deselect all matches)
|
||||
\fBdisable-search\fR (disable search functionality)
|
||||
\fBdown\fR \fIctrl-j ctrl-n down\fR
|
||||
\fBenable-search\fR (enable search functionality)
|
||||
\fBend-of-line\fR \fIctrl-e end\fR
|
||||
\fBexecute(...)\fR (see below for the details)
|
||||
\fBexecute-silent(...)\fR (see below for the details)
|
||||
\fBfirst\fR (move to the first match)
|
||||
\fBforward-char\fR \fIctrl-f right\fR
|
||||
\fBforward-word\fR \fIalt-f shift-right\fR
|
||||
\fBignore\fR
|
||||
\fBjump\fR (EasyMotion-like 2-keystroke movement)
|
||||
\fBjump-accept\fR (jump and accept)
|
||||
\fBjump\fR (EasyMotion-like 2-keystroke movement)
|
||||
\fBjump-accept\fR (jump and accept)
|
||||
\fBkill-line\fR
|
||||
\fBkill-word\fR \fIalt-d\fR
|
||||
\fBlast\fR (move to the last match)
|
||||
\fBnext-history\fR (\fIctrl-n\fR on \fB--history\fR)
|
||||
\fBpage-down\fR \fIpgdn\fR
|
||||
\fBpage-up\fR \fIpgup\fR
|
||||
\fBkill-word\fR \fIalt-d\fR
|
||||
\fBlast\fR (move to the last match)
|
||||
\fBnext-history\fR (\fIctrl-n\fR on \fB--history\fR)
|
||||
\fBpage-down\fR \fIpgdn\fR
|
||||
\fBpage-up\fR \fIpgup\fR
|
||||
\fBhalf-page-down\fR
|
||||
\fBhalf-page-up\fR
|
||||
\fBpreview(...)\fR (see below for the details)
|
||||
\fBpreview-down\fR \fIshift-down\fR
|
||||
\fBpreview-up\fR \fIshift-up\fR
|
||||
\fBpreview(...)\fR (see below for the details)
|
||||
\fBpreview-down\fR \fIshift-down\fR
|
||||
\fBpreview-up\fR \fIshift-up\fR
|
||||
\fBpreview-page-down\fR
|
||||
\fBpreview-page-up\fR
|
||||
\fBpreview-half-page-down\fR
|
||||
\fBpreview-half-page-up\fR
|
||||
\fBpreview-bottom\fR
|
||||
\fBpreview-top\fR
|
||||
\fBprevious-history\fR (\fIctrl-p\fR on \fB--history\fR)
|
||||
\fBprint-query\fR (print query and exit)
|
||||
\fBput\fR (put the character to the prompt)
|
||||
\fBprevious-history\fR (\fIctrl-p\fR on \fB--history\fR)
|
||||
\fBprint-query\fR (print query and exit)
|
||||
\fBput\fR (put the character to the prompt)
|
||||
\fBrefresh-preview\fR
|
||||
\fBreload(...)\fR (see below for the details)
|
||||
\fBreplace-query\fR (replace query string with the current selection)
|
||||
\fBreload(...)\fR (see below for the details)
|
||||
\fBreplace-query\fR (replace query string with the current selection)
|
||||
\fBselect\fR
|
||||
\fBselect-all\fR (select all matches)
|
||||
\fBtoggle\fR (\fIright-click\fR)
|
||||
\fBtoggle-all\fR (toggle all matches)
|
||||
\fBtoggle+down\fR \fIctrl-i (tab)\fR
|
||||
\fBtoggle-in\fR (\fB--layout=reverse*\fR ? \fBtoggle+up\fR : \fBtoggle+down\fR)
|
||||
\fBtoggle-out\fR (\fB--layout=reverse*\fR ? \fBtoggle+down\fR : \fBtoggle+up\fR)
|
||||
\fBselect-all\fR (select all matches)
|
||||
\fBtoggle\fR (\fIright-click\fR)
|
||||
\fBtoggle-all\fR (toggle all matches)
|
||||
\fBtoggle+down\fR \fIctrl-i (tab)\fR
|
||||
\fBtoggle-in\fR (\fB--layout=reverse*\fR ? \fBtoggle+up\fR : \fBtoggle+down\fR)
|
||||
\fBtoggle-out\fR (\fB--layout=reverse*\fR ? \fBtoggle+down\fR : \fBtoggle+up\fR)
|
||||
\fBtoggle-preview\fR
|
||||
\fBtoggle-preview-wrap\fR
|
||||
\fBtoggle-search\fR (toggle search functionality)
|
||||
\fBtoggle-search\fR (toggle search functionality)
|
||||
\fBtoggle-sort\fR
|
||||
\fBtoggle+up\fR \fIbtab (shift-tab)\fR
|
||||
\fBunbind(...)\fR (unbind bindings)
|
||||
\fBunix-line-discard\fR \fIctrl-u\fR
|
||||
\fBunix-word-rubout\fR \fIctrl-w\fR
|
||||
\fBup\fR \fIctrl-k ctrl-p up\fR
|
||||
\fByank\fR \fIctrl-y\fR
|
||||
\fBtoggle+up\fR \fIbtab (shift-tab)\fR
|
||||
\fBunbind(...)\fR (unbind bindings)
|
||||
\fBunix-line-discard\fR \fIctrl-u\fR
|
||||
\fBunix-word-rubout\fR \fIctrl-w\fR
|
||||
\fBup\fR \fIctrl-k ctrl-p up\fR
|
||||
\fByank\fR \fIctrl-y\fR
|
||||
|
||||
.SS ACTION COMPOSITION
|
||||
|
||||
@@ -970,7 +972,6 @@ commands in addition to the default preview command given by \fB--preview\fR
|
||||
option.
|
||||
|
||||
e.g.
|
||||
|
||||
# Default preview command with an extra preview binding
|
||||
fzf --preview 'file {}' --bind '?:preview:cat {}'
|
||||
|
||||
@@ -981,6 +982,22 @@ e.g.
|
||||
# Preview window hidden by default, it appears when you first hit '?'
|
||||
fzf --bind '?:preview:cat {}' --preview-window hidden
|
||||
|
||||
.SS CHANGE PREVIEW WINDOW ATTRIBUTES
|
||||
|
||||
\fBchange-preview-window\fR action can be used to change the properties of the
|
||||
preview window. Unlike the \fB--preview-window\fR option, you can specify
|
||||
multiple sets of options separated by '|' characters.
|
||||
|
||||
e.g.
|
||||
# Rotate through the options using CTRL-/
|
||||
fzf --preview 'cat {}' --bind 'ctrl-/:change-preview-window(right,70%|down,40%,border-horizontal|hidden|right)'
|
||||
|
||||
# The default properties given by `--preview-window` are inherited, so an empty string in the list is interpreted as the default
|
||||
fzf --preview 'cat {}' --preview-window 'right,40%,border-left' --bind 'ctrl-/:change-preview-window(70%|down,border-top|hidden|)'
|
||||
|
||||
# This is equivalent to toggle-preview action
|
||||
fzf --preview 'cat {}' --bind 'ctrl-/:change-preview-window(hidden|)'
|
||||
|
||||
.SH AUTHOR
|
||||
Junegunn Choi (\fIjunegunn.c@gmail.com\fR)
|
||||
|
||||
|
||||
@@ -444,6 +444,12 @@ function! s:use_sh()
|
||||
return [shell, shellslash, shellcmdflag, shellxquote]
|
||||
endfunction
|
||||
|
||||
function! s:writefile(...)
|
||||
if call('writefile', a:000) == -1
|
||||
throw 'Failed to write temporary file. Check if you can write to the path tempname() returns.'
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! fzf#run(...) abort
|
||||
try
|
||||
let [shell, shellslash, shellcmdflag, shellxquote] = s:use_sh()
|
||||
@@ -471,7 +477,7 @@ try
|
||||
let source_command = source
|
||||
elseif type == 3
|
||||
let temps.input = s:fzf_tempname()
|
||||
call writefile(source, temps.input)
|
||||
call s:writefile(source, temps.input)
|
||||
let source_command = (s:is_win ? 'type ' : 'cat ').fzf#shellescape(temps.input)
|
||||
else
|
||||
throw 'Invalid source type'
|
||||
@@ -515,7 +521,7 @@ try
|
||||
call s:callback(dict, lines)
|
||||
return lines
|
||||
finally
|
||||
if len(source_command)
|
||||
if exists('source_command') && len(source_command)
|
||||
if len(prev_default_command)
|
||||
let $FZF_DEFAULT_COMMAND = prev_default_command
|
||||
else
|
||||
@@ -660,7 +666,7 @@ function! s:execute(dict, command, use_height, temps) abort
|
||||
endif
|
||||
if s:is_win
|
||||
let batchfile = s:fzf_tempname().'.bat'
|
||||
call writefile(s:wrap_cmds(command), batchfile)
|
||||
call s:writefile(s:wrap_cmds(command), batchfile)
|
||||
let command = batchfile
|
||||
let a:temps.batchfile = batchfile
|
||||
if has('nvim')
|
||||
@@ -678,7 +684,7 @@ function! s:execute(dict, command, use_height, temps) abort
|
||||
endif
|
||||
elseif has('win32unix') && $TERM !=# 'cygwin'
|
||||
let shellscript = s:fzf_tempname()
|
||||
call writefile([command], shellscript)
|
||||
call s:writefile([command], shellscript)
|
||||
let command = 'cmd.exe /C '.fzf#shellescape('set "TERM=" & start /WAIT sh -c '.shellscript)
|
||||
let a:temps.shellscript = shellscript
|
||||
endif
|
||||
@@ -877,7 +883,7 @@ function! s:execute_term(dict, command, temps) abort
|
||||
call s:pushd(a:dict)
|
||||
if s:is_win
|
||||
let fzf.temps.batchfile = s:fzf_tempname().'.bat'
|
||||
call writefile(s:wrap_cmds(a:command), fzf.temps.batchfile)
|
||||
call s:writefile(s:wrap_cmds(a:command), fzf.temps.batchfile)
|
||||
let command = fzf.temps.batchfile
|
||||
else
|
||||
let command = a:command
|
||||
|
||||
@@ -41,7 +41,7 @@ __fzf_cd__() {
|
||||
local cmd dir
|
||||
cmd="${FZF_ALT_C_COMMAND:-"command find -L . -mindepth 1 \\( -path '*/\\.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' -o -fstype 'proc' \\) -prune \
|
||||
-o -type d -print 2> /dev/null | cut -b3-"}"
|
||||
dir=$(eval "$cmd" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --bind=ctrl-z:ignore $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 --bind=ctrl-z:ignore $FZF_DEFAULT_OPTS $FZF_ALT_C_OPTS" $(__fzfcmd) +m) && printf 'cd -- %q' "$dir"
|
||||
}
|
||||
|
||||
__fzf_history__() {
|
||||
|
||||
@@ -87,7 +87,7 @@ function fzf_key_bindings
|
||||
eval "$FZF_ALT_C_COMMAND | "(__fzfcmd)' +m --query "'$fzf_query'"' | read -l result
|
||||
|
||||
if [ -n "$result" ]
|
||||
cd $result
|
||||
cd -- $result
|
||||
|
||||
# Remove last token from commandline.
|
||||
commandline -t ""
|
||||
|
||||
@@ -79,7 +79,7 @@ fzf-cd-widget() {
|
||||
return 0
|
||||
fi
|
||||
zle push-line # Clear buffer. Auto-restored on next prompt.
|
||||
BUFFER="cd ${(q)dir}"
|
||||
BUFFER="cd -- ${(q)dir}"
|
||||
zle accept-line
|
||||
local ret=$?
|
||||
unset dir # ensure this doesn't end up appearing in prompt expansion
|
||||
|
||||
@@ -176,6 +176,14 @@ type previewOpts struct {
|
||||
headerLines int
|
||||
}
|
||||
|
||||
func (a previewOpts) sameLayout(b previewOpts) bool {
|
||||
return a.size == b.size && a.position == b.position && a.border == b.border && a.hidden == b.hidden
|
||||
}
|
||||
|
||||
func (a previewOpts) sameContentLayout(b previewOpts) bool {
|
||||
return a.wrap == b.wrap && a.headerLines == b.headerLines
|
||||
}
|
||||
|
||||
// Options stores the values of command-line options
|
||||
type Options struct {
|
||||
Fuzzy bool
|
||||
@@ -216,7 +224,7 @@ type Options struct {
|
||||
Filter *string
|
||||
ToggleSort bool
|
||||
Expect map[tui.Event]string
|
||||
Keymap map[tui.Event][]action
|
||||
Keymap map[tui.Event][]*action
|
||||
Preview previewOpts
|
||||
PrintQuery bool
|
||||
ReadZero bool
|
||||
@@ -279,7 +287,7 @@ func defaultOptions() *Options {
|
||||
Filter: nil,
|
||||
ToggleSort: false,
|
||||
Expect: make(map[tui.Event]string),
|
||||
Keymap: make(map[tui.Event][]action),
|
||||
Keymap: make(map[tui.Event][]*action),
|
||||
Preview: defaultPreviewOpts(""),
|
||||
PrintQuery: false,
|
||||
ReadZero: false,
|
||||
@@ -787,10 +795,10 @@ func init() {
|
||||
// Backreferences are not supported.
|
||||
// "~!@#$%^&*;/|".each_char.map { |c| Regexp.escape(c) }.map { |c| "#{c}[^#{c}]*#{c}" }.join('|')
|
||||
executeRegexp = regexp.MustCompile(
|
||||
`(?si)[:+](execute(?:-multi|-silent)?|reload|preview|change-prompt|unbind):.+|[:+](execute(?:-multi|-silent)?|reload|preview|change-prompt|unbind)(\([^)]*\)|\[[^\]]*\]|~[^~]*~|![^!]*!|@[^@]*@|\#[^\#]*\#|\$[^\$]*\$|%[^%]*%|\^[^\^]*\^|&[^&]*&|\*[^\*]*\*|;[^;]*;|/[^/]*/|\|[^\|]*\|)`)
|
||||
`(?si)[:+](execute(?:-multi|-silent)?|reload|preview|change-prompt|change-preview-window|change-preview|unbind):.+|[:+](execute(?:-multi|-silent)?|reload|preview|change-prompt|change-preview-window|change-preview|unbind)(\([^)]*\)|\[[^\]]*\]|~[^~]*~|![^!]*!|@[^@]*@|\#[^\#]*\#|\$[^\$]*\$|%[^%]*%|\^[^\^]*\^|&[^&]*&|\*[^\*]*\*|;[^;]*;|/[^/]*/|\|[^\|]*\|)`)
|
||||
}
|
||||
|
||||
func parseKeymap(keymap map[tui.Event][]action, str string) {
|
||||
func parseKeymap(keymap map[tui.Event][]*action, str string) {
|
||||
masked := executeRegexp.ReplaceAllStringFunc(str, func(src string) string {
|
||||
symbol := ":"
|
||||
if strings.HasPrefix(src, "+") {
|
||||
@@ -799,6 +807,10 @@ func parseKeymap(keymap map[tui.Event][]action, str string) {
|
||||
prefix := symbol + "execute"
|
||||
if strings.HasPrefix(src[1:], "reload") {
|
||||
prefix = symbol + "reload"
|
||||
} else if strings.HasPrefix(src[1:], "change-preview-window") {
|
||||
prefix = symbol + "change-preview-window"
|
||||
} else if strings.HasPrefix(src[1:], "change-preview") {
|
||||
prefix = symbol + "change-preview"
|
||||
} else if strings.HasPrefix(src[1:], "preview") {
|
||||
prefix = symbol + "preview"
|
||||
} else if strings.HasPrefix(src[1:], "unbind") {
|
||||
@@ -842,7 +854,7 @@ func parseKeymap(keymap map[tui.Event][]action, str string) {
|
||||
|
||||
idx2 := len(pair[0]) + 1
|
||||
specs := strings.Split(pair[1], "+")
|
||||
actions := make([]action, 0, len(specs))
|
||||
actions := make([]*action, 0, len(specs))
|
||||
appendAction := func(types ...actionType) {
|
||||
actions = append(actions, toActions(types...)...)
|
||||
}
|
||||
@@ -1002,6 +1014,10 @@ func parseKeymap(keymap map[tui.Event][]action, str string) {
|
||||
offset = len("reload")
|
||||
case actPreview:
|
||||
offset = len("preview")
|
||||
case actChangePreviewWindow:
|
||||
offset = len("change-preview-window")
|
||||
case actChangePreview:
|
||||
offset = len("change-preview")
|
||||
case actChangePrompt:
|
||||
offset = len("change-prompt")
|
||||
case actUnbind:
|
||||
@@ -1017,17 +1033,22 @@ func parseKeymap(keymap map[tui.Event][]action, str string) {
|
||||
if spec[offset] == ':' {
|
||||
if specIndex == len(specs)-1 {
|
||||
actionArg = spec[offset+1:]
|
||||
actions = append(actions, action{t: t, a: actionArg})
|
||||
actions = append(actions, &action{t: t, a: actionArg})
|
||||
} else {
|
||||
prevSpec = spec + "+"
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
actionArg = spec[offset+1 : len(spec)-1]
|
||||
actions = append(actions, action{t: t, a: actionArg})
|
||||
actions = append(actions, &action{t: t, a: actionArg})
|
||||
}
|
||||
if t == actUnbind {
|
||||
parseKeyChords(actionArg, "unbind target required")
|
||||
} else if t == actChangePreviewWindow {
|
||||
opts := previewOpts{}
|
||||
for _, arg := range strings.Split(actionArg, "|") {
|
||||
parsePreviewWindow(&opts, arg)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1053,6 +1074,10 @@ func isExecuteAction(str string) actionType {
|
||||
return actUnbind
|
||||
case "preview":
|
||||
return actPreview
|
||||
case "change-preview-window":
|
||||
return actChangePreviewWindow
|
||||
case "change-preview":
|
||||
return actChangePreview
|
||||
case "change-prompt":
|
||||
return actChangePrompt
|
||||
case "execute":
|
||||
@@ -1065,7 +1090,7 @@ func isExecuteAction(str string) actionType {
|
||||
return actIgnore
|
||||
}
|
||||
|
||||
func parseToggleSort(keymap map[tui.Event][]action, str string) {
|
||||
func parseToggleSort(keymap map[tui.Event][]*action, str string) {
|
||||
keys := parseKeyChords(str, "key name required")
|
||||
if len(keys) != 1 {
|
||||
errorExit("multiple keys specified")
|
||||
@@ -1633,11 +1658,29 @@ func postProcessOptions(opts *Options) {
|
||||
// Extend the default key map
|
||||
keymap := defaultKeymap()
|
||||
for key, actions := range opts.Keymap {
|
||||
var lastChangePreviewWindow *action
|
||||
for _, act := range actions {
|
||||
if act.t == actToggleSort {
|
||||
switch act.t {
|
||||
case actToggleSort:
|
||||
// To display "+S"/"-S" on info line
|
||||
opts.ToggleSort = true
|
||||
case actChangePreviewWindow:
|
||||
lastChangePreviewWindow = act
|
||||
}
|
||||
}
|
||||
// Re-organize actions so that we only keep the last change-preview-window
|
||||
// and it comes first in the list.
|
||||
// * change-preview-window(up,+10)+preview(sleep 3; cat {})+change-preview-window(up,+20)
|
||||
// -> change-preview-window(up,+20)+preview(sleep 3; cat {})
|
||||
if lastChangePreviewWindow != nil {
|
||||
reordered := []*action{lastChangePreviewWindow}
|
||||
for _, act := range actions {
|
||||
if act.t != actChangePreviewWindow {
|
||||
reordered = append(reordered, act)
|
||||
}
|
||||
}
|
||||
actions = reordered
|
||||
}
|
||||
keymap[key] = actions
|
||||
}
|
||||
opts.Keymap = keymap
|
||||
|
||||
432
src/terminal.go
432
src/terminal.go
@@ -4,6 +4,7 @@ import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math"
|
||||
"os"
|
||||
"os/signal"
|
||||
"regexp"
|
||||
@@ -104,88 +105,89 @@ var emptyLine = itemLine{}
|
||||
|
||||
// Terminal represents terminal input/output
|
||||
type Terminal struct {
|
||||
initDelay time.Duration
|
||||
infoStyle infoStyle
|
||||
spinner []string
|
||||
prompt func()
|
||||
promptLen int
|
||||
pointer string
|
||||
pointerLen int
|
||||
pointerEmpty string
|
||||
marker string
|
||||
markerLen int
|
||||
markerEmpty string
|
||||
queryLen [2]int
|
||||
layout layoutType
|
||||
fullscreen bool
|
||||
keepRight bool
|
||||
hscroll bool
|
||||
hscrollOff int
|
||||
scrollOff int
|
||||
wordRubout string
|
||||
wordNext string
|
||||
cx int
|
||||
cy int
|
||||
offset int
|
||||
xoffset int
|
||||
yanked []rune
|
||||
input []rune
|
||||
multi int
|
||||
sort bool
|
||||
toggleSort bool
|
||||
delimiter Delimiter
|
||||
expect map[tui.Event]string
|
||||
keymap map[tui.Event][]action
|
||||
pressed string
|
||||
printQuery bool
|
||||
history *History
|
||||
cycle bool
|
||||
headerFirst bool
|
||||
headerLines int
|
||||
header []string
|
||||
header0 []string
|
||||
ansi bool
|
||||
tabstop int
|
||||
margin [4]sizeSpec
|
||||
padding [4]sizeSpec
|
||||
strong tui.Attr
|
||||
unicode bool
|
||||
borderShape tui.BorderShape
|
||||
cleanExit bool
|
||||
paused bool
|
||||
border tui.Window
|
||||
window tui.Window
|
||||
pborder tui.Window
|
||||
pwindow tui.Window
|
||||
count int
|
||||
progress int
|
||||
reading bool
|
||||
running bool
|
||||
failed *string
|
||||
jumping jumpMode
|
||||
jumpLabels string
|
||||
printer func(string)
|
||||
printsep string
|
||||
merger *Merger
|
||||
selected map[int32]selectedItem
|
||||
version int64
|
||||
reqBox *util.EventBox
|
||||
previewOpts previewOpts
|
||||
previewer previewer
|
||||
previewed previewed
|
||||
previewBox *util.EventBox
|
||||
eventBox *util.EventBox
|
||||
mutex sync.Mutex
|
||||
initFunc func()
|
||||
prevLines []itemLine
|
||||
suppress bool
|
||||
sigstop bool
|
||||
startChan chan bool
|
||||
killChan chan int
|
||||
slab *util.Slab
|
||||
theme *tui.ColorTheme
|
||||
tui tui.Renderer
|
||||
executing *util.AtomicBool
|
||||
initDelay time.Duration
|
||||
infoStyle infoStyle
|
||||
spinner []string
|
||||
prompt func()
|
||||
promptLen int
|
||||
pointer string
|
||||
pointerLen int
|
||||
pointerEmpty string
|
||||
marker string
|
||||
markerLen int
|
||||
markerEmpty string
|
||||
queryLen [2]int
|
||||
layout layoutType
|
||||
fullscreen bool
|
||||
keepRight bool
|
||||
hscroll bool
|
||||
hscrollOff int
|
||||
scrollOff int
|
||||
wordRubout string
|
||||
wordNext string
|
||||
cx int
|
||||
cy int
|
||||
offset int
|
||||
xoffset int
|
||||
yanked []rune
|
||||
input []rune
|
||||
multi int
|
||||
sort bool
|
||||
toggleSort bool
|
||||
delimiter Delimiter
|
||||
expect map[tui.Event]string
|
||||
keymap map[tui.Event][]*action
|
||||
pressed string
|
||||
printQuery bool
|
||||
history *History
|
||||
cycle bool
|
||||
headerFirst bool
|
||||
headerLines int
|
||||
header []string
|
||||
header0 []string
|
||||
ansi bool
|
||||
tabstop int
|
||||
margin [4]sizeSpec
|
||||
padding [4]sizeSpec
|
||||
strong tui.Attr
|
||||
unicode bool
|
||||
borderShape tui.BorderShape
|
||||
cleanExit bool
|
||||
paused bool
|
||||
border tui.Window
|
||||
window tui.Window
|
||||
pborder tui.Window
|
||||
pwindow tui.Window
|
||||
count int
|
||||
progress int
|
||||
reading bool
|
||||
running bool
|
||||
failed *string
|
||||
jumping jumpMode
|
||||
jumpLabels string
|
||||
printer func(string)
|
||||
printsep string
|
||||
merger *Merger
|
||||
selected map[int32]selectedItem
|
||||
version int64
|
||||
reqBox *util.EventBox
|
||||
initialPreviewOpts previewOpts
|
||||
previewOpts previewOpts
|
||||
previewer previewer
|
||||
previewed previewed
|
||||
previewBox *util.EventBox
|
||||
eventBox *util.EventBox
|
||||
mutex sync.Mutex
|
||||
initFunc func()
|
||||
prevLines []itemLine
|
||||
suppress bool
|
||||
sigstop bool
|
||||
startChan chan bool
|
||||
killChan chan int
|
||||
slab *util.Slab
|
||||
theme *tui.ColorTheme
|
||||
tui tui.Renderer
|
||||
executing *util.AtomicBool
|
||||
}
|
||||
|
||||
type selectedItem struct {
|
||||
@@ -216,6 +218,7 @@ const (
|
||||
reqRefresh
|
||||
reqReinit
|
||||
reqRedraw
|
||||
reqFullRedraw
|
||||
reqClose
|
||||
reqPrintQuery
|
||||
reqPreviewEnqueue
|
||||
@@ -286,6 +289,8 @@ const (
|
||||
actTogglePreview
|
||||
actTogglePreviewWrap
|
||||
actPreview
|
||||
actChangePreview
|
||||
actChangePreviewWindow
|
||||
actPreviewTop
|
||||
actPreviewBottom
|
||||
actPreviewUp
|
||||
@@ -324,9 +329,10 @@ type searchRequest struct {
|
||||
}
|
||||
|
||||
type previewRequest struct {
|
||||
template string
|
||||
pwindow tui.Window
|
||||
list []*Item
|
||||
template string
|
||||
pwindow tui.Window
|
||||
scrollOffset int
|
||||
list []*Item
|
||||
}
|
||||
|
||||
type previewResult struct {
|
||||
@@ -336,16 +342,16 @@ type previewResult struct {
|
||||
spinner string
|
||||
}
|
||||
|
||||
func toActions(types ...actionType) []action {
|
||||
actions := make([]action, len(types))
|
||||
func toActions(types ...actionType) []*action {
|
||||
actions := make([]*action, len(types))
|
||||
for idx, t := range types {
|
||||
actions[idx] = action{t: t, a: ""}
|
||||
actions[idx] = &action{t: t, a: ""}
|
||||
}
|
||||
return actions
|
||||
}
|
||||
|
||||
func defaultKeymap() map[tui.Event][]action {
|
||||
keymap := make(map[tui.Event][]action)
|
||||
func defaultKeymap() map[tui.Event][]*action {
|
||||
keymap := make(map[tui.Event][]*action)
|
||||
add := func(e tui.EventType, a actionType) {
|
||||
keymap[e.AsEvent()] = toActions(a)
|
||||
}
|
||||
@@ -416,7 +422,7 @@ func trimQuery(query string) []rune {
|
||||
func hasPreviewAction(opts *Options) bool {
|
||||
for _, actions := range opts.Keymap {
|
||||
for _, action := range actions {
|
||||
if action.t == actPreview {
|
||||
if action.t == actPreview || action.t == actChangePreview {
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -496,72 +502,73 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
|
||||
wordNext = fmt.Sprintf("[^%s]%s|(.$)", sep, sep)
|
||||
}
|
||||
t := Terminal{
|
||||
initDelay: delay,
|
||||
infoStyle: opts.InfoStyle,
|
||||
spinner: makeSpinner(opts.Unicode),
|
||||
queryLen: [2]int{0, 0},
|
||||
layout: opts.Layout,
|
||||
fullscreen: fullscreen,
|
||||
keepRight: opts.KeepRight,
|
||||
hscroll: opts.Hscroll,
|
||||
hscrollOff: opts.HscrollOff,
|
||||
scrollOff: opts.ScrollOff,
|
||||
wordRubout: wordRubout,
|
||||
wordNext: wordNext,
|
||||
cx: len(input),
|
||||
cy: 0,
|
||||
offset: 0,
|
||||
xoffset: 0,
|
||||
yanked: []rune{},
|
||||
input: input,
|
||||
multi: opts.Multi,
|
||||
sort: opts.Sort > 0,
|
||||
toggleSort: opts.ToggleSort,
|
||||
delimiter: opts.Delimiter,
|
||||
expect: opts.Expect,
|
||||
keymap: opts.Keymap,
|
||||
pressed: "",
|
||||
printQuery: opts.PrintQuery,
|
||||
history: opts.History,
|
||||
margin: opts.Margin,
|
||||
padding: opts.Padding,
|
||||
unicode: opts.Unicode,
|
||||
borderShape: opts.BorderShape,
|
||||
cleanExit: opts.ClearOnExit,
|
||||
paused: opts.Phony,
|
||||
strong: strongAttr,
|
||||
cycle: opts.Cycle,
|
||||
headerFirst: opts.HeaderFirst,
|
||||
headerLines: opts.HeaderLines,
|
||||
header: header,
|
||||
header0: header,
|
||||
ansi: opts.Ansi,
|
||||
tabstop: opts.Tabstop,
|
||||
reading: true,
|
||||
running: true,
|
||||
failed: nil,
|
||||
jumping: jumpDisabled,
|
||||
jumpLabels: opts.JumpLabels,
|
||||
printer: opts.Printer,
|
||||
printsep: opts.PrintSep,
|
||||
merger: EmptyMerger,
|
||||
selected: make(map[int32]selectedItem),
|
||||
reqBox: util.NewEventBox(),
|
||||
previewOpts: opts.Preview,
|
||||
previewer: previewer{0, []string{}, 0, showPreviewWindow, false, true, false, ""},
|
||||
previewed: previewed{0, 0, 0, false},
|
||||
previewBox: previewBox,
|
||||
eventBox: eventBox,
|
||||
mutex: sync.Mutex{},
|
||||
suppress: true,
|
||||
sigstop: false,
|
||||
slab: util.MakeSlab(slab16Size, slab32Size),
|
||||
theme: opts.Theme,
|
||||
startChan: make(chan bool, 1),
|
||||
killChan: make(chan int),
|
||||
tui: renderer,
|
||||
initFunc: func() { renderer.Init() },
|
||||
executing: util.NewAtomicBool(false)}
|
||||
initDelay: delay,
|
||||
infoStyle: opts.InfoStyle,
|
||||
spinner: makeSpinner(opts.Unicode),
|
||||
queryLen: [2]int{0, 0},
|
||||
layout: opts.Layout,
|
||||
fullscreen: fullscreen,
|
||||
keepRight: opts.KeepRight,
|
||||
hscroll: opts.Hscroll,
|
||||
hscrollOff: opts.HscrollOff,
|
||||
scrollOff: opts.ScrollOff,
|
||||
wordRubout: wordRubout,
|
||||
wordNext: wordNext,
|
||||
cx: len(input),
|
||||
cy: 0,
|
||||
offset: 0,
|
||||
xoffset: 0,
|
||||
yanked: []rune{},
|
||||
input: input,
|
||||
multi: opts.Multi,
|
||||
sort: opts.Sort > 0,
|
||||
toggleSort: opts.ToggleSort,
|
||||
delimiter: opts.Delimiter,
|
||||
expect: opts.Expect,
|
||||
keymap: opts.Keymap,
|
||||
pressed: "",
|
||||
printQuery: opts.PrintQuery,
|
||||
history: opts.History,
|
||||
margin: opts.Margin,
|
||||
padding: opts.Padding,
|
||||
unicode: opts.Unicode,
|
||||
borderShape: opts.BorderShape,
|
||||
cleanExit: opts.ClearOnExit,
|
||||
paused: opts.Phony,
|
||||
strong: strongAttr,
|
||||
cycle: opts.Cycle,
|
||||
headerFirst: opts.HeaderFirst,
|
||||
headerLines: opts.HeaderLines,
|
||||
header: header,
|
||||
header0: header,
|
||||
ansi: opts.Ansi,
|
||||
tabstop: opts.Tabstop,
|
||||
reading: true,
|
||||
running: true,
|
||||
failed: nil,
|
||||
jumping: jumpDisabled,
|
||||
jumpLabels: opts.JumpLabels,
|
||||
printer: opts.Printer,
|
||||
printsep: opts.PrintSep,
|
||||
merger: EmptyMerger,
|
||||
selected: make(map[int32]selectedItem),
|
||||
reqBox: util.NewEventBox(),
|
||||
initialPreviewOpts: opts.Preview,
|
||||
previewOpts: opts.Preview,
|
||||
previewer: previewer{0, []string{}, 0, showPreviewWindow, false, true, false, ""},
|
||||
previewed: previewed{0, 0, 0, false},
|
||||
previewBox: previewBox,
|
||||
eventBox: eventBox,
|
||||
mutex: sync.Mutex{},
|
||||
suppress: true,
|
||||
sigstop: false,
|
||||
slab: util.MakeSlab(slab16Size, slab32Size),
|
||||
theme: opts.Theme,
|
||||
startChan: make(chan bool, 1),
|
||||
killChan: make(chan int),
|
||||
tui: renderer,
|
||||
initFunc: func() { renderer.Init() },
|
||||
executing: util.NewAtomicBool(false)}
|
||||
t.prompt, t.promptLen = t.parsePrompt(opts.Prompt)
|
||||
t.pointer, t.pointerLen = t.processTabs([]rune(opts.Pointer), 0)
|
||||
t.marker, t.markerLen = t.processTabs([]rune(opts.Marker), 0)
|
||||
@@ -703,7 +710,7 @@ func (t *Terminal) sortSelected() []selectedItem {
|
||||
}
|
||||
|
||||
func (t *Terminal) displayWidth(runes []rune) int {
|
||||
width, _ := util.RunesWidth(runes, 0, t.tabstop, 0)
|
||||
width, _ := util.RunesWidth(runes, 0, t.tabstop, math.MaxInt32)
|
||||
return width
|
||||
}
|
||||
|
||||
@@ -1191,7 +1198,7 @@ func (t *Terminal) printItem(result Result, line int, i int, current bool) {
|
||||
|
||||
func (t *Terminal) trimRight(runes []rune, width int) ([]rune, bool) {
|
||||
// We start from the beginning to handle tab characters
|
||||
width, overflowIdx := util.RunesWidth(runes, 0, t.tabstop, width)
|
||||
_, overflowIdx := util.RunesWidth(runes, 0, t.tabstop, width)
|
||||
if overflowIdx >= 0 {
|
||||
return runes[:overflowIdx], true
|
||||
}
|
||||
@@ -1642,9 +1649,14 @@ func (t *Terminal) replacePlaceholder(template string, forcePlus bool, input str
|
||||
template, t.ansi, t.delimiter, t.printsep, forcePlus, input, list)
|
||||
}
|
||||
|
||||
func (t *Terminal) evaluateScrollOffset(list []*Item, height int) int {
|
||||
func (t *Terminal) evaluateScrollOffset() int {
|
||||
if t.pwindow == nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
// We only need the current item to calculate the scroll offset
|
||||
offsetExpr := offsetTrimCharsRegex.ReplaceAllString(
|
||||
t.replacePlaceholder(t.previewOpts.scroll, false, "", list), "")
|
||||
t.replacePlaceholder(t.previewOpts.scroll, false, "", []*Item{t.currentItem(), nil}), "")
|
||||
|
||||
atoi := func(s string) int {
|
||||
n, e := strconv.Atoi(s)
|
||||
@@ -1655,20 +1667,21 @@ func (t *Terminal) evaluateScrollOffset(list []*Item, height int) int {
|
||||
}
|
||||
|
||||
base := -1
|
||||
height := util.Max(0, t.pwindow.Height()-t.previewOpts.headerLines)
|
||||
for _, component := range offsetComponentRegex.FindAllString(offsetExpr, -1) {
|
||||
if strings.HasPrefix(component, "-/") {
|
||||
component = component[1:]
|
||||
}
|
||||
if component[0] == '/' {
|
||||
denom := atoi(component[1:])
|
||||
if denom == 0 {
|
||||
return base
|
||||
if denom != 0 {
|
||||
base -= height / denom
|
||||
}
|
||||
return base - height/denom
|
||||
break
|
||||
}
|
||||
base += atoi(component)
|
||||
}
|
||||
return base
|
||||
return util.Max(0, base)
|
||||
}
|
||||
|
||||
func replacePlaceholder(template string, stripAnsi bool, delimiter Delimiter, printsep string, forcePlus bool, query string, allItems []*Item) string {
|
||||
@@ -1767,8 +1780,10 @@ func replacePlaceholder(template string, stripAnsi bool, delimiter Delimiter, pr
|
||||
})
|
||||
}
|
||||
|
||||
func (t *Terminal) redraw() {
|
||||
t.tui.Clear()
|
||||
func (t *Terminal) redraw(clear bool) {
|
||||
if clear {
|
||||
t.tui.Clear()
|
||||
}
|
||||
t.tui.Refresh()
|
||||
t.printAll()
|
||||
}
|
||||
@@ -1788,7 +1803,7 @@ func (t *Terminal) executeCommand(template string, forcePlus bool, background bo
|
||||
t.tui.Pause(true)
|
||||
cmd.Run()
|
||||
t.tui.Resume(true, false)
|
||||
t.redraw()
|
||||
t.redraw(true)
|
||||
t.refresh()
|
||||
} else {
|
||||
t.tui.Pause(false)
|
||||
@@ -1933,7 +1948,7 @@ func (t *Terminal) Loop() {
|
||||
go func() {
|
||||
for {
|
||||
<-resizeChan
|
||||
t.reqBox.Set(reqRedraw, nil)
|
||||
t.reqBox.Set(reqFullRedraw, nil)
|
||||
}
|
||||
}()
|
||||
|
||||
@@ -1972,12 +1987,14 @@ func (t *Terminal) Loop() {
|
||||
var items []*Item
|
||||
var commandTemplate string
|
||||
var pwindow tui.Window
|
||||
initialOffset := 0
|
||||
t.previewBox.Wait(func(events *util.Events) {
|
||||
for req, value := range *events {
|
||||
switch req {
|
||||
case reqPreviewEnqueue:
|
||||
request := value.(previewRequest)
|
||||
commandTemplate = request.template
|
||||
initialOffset = request.scrollOffset
|
||||
items = request.list
|
||||
pwindow = request.pwindow
|
||||
}
|
||||
@@ -1989,11 +2006,9 @@ func (t *Terminal) Loop() {
|
||||
if items[0] != nil {
|
||||
_, query := t.Input()
|
||||
command := t.replacePlaceholder(commandTemplate, false, string(query), items)
|
||||
initialOffset := 0
|
||||
cmd := util.ExecCommand(command, true)
|
||||
if pwindow != nil {
|
||||
height := pwindow.Height()
|
||||
initialOffset = util.Max(0, t.evaluateScrollOffset(items, util.Max(0, height-t.previewOpts.headerLines)))
|
||||
env := os.Environ()
|
||||
lines := fmt.Sprintf("LINES=%d", height)
|
||||
columns := fmt.Sprintf("COLUMNS=%d", pwindow.Width())
|
||||
@@ -2128,7 +2143,7 @@ func (t *Terminal) Loop() {
|
||||
if len(command) > 0 && t.isPreviewEnabled() {
|
||||
_, list := t.buildPlusList(command, false)
|
||||
t.cancelPreview()
|
||||
t.previewBox.Set(reqPreviewEnqueue, previewRequest{command, t.pwindow, list})
|
||||
t.previewBox.Set(reqPreviewEnqueue, previewRequest{command, t.pwindow, t.evaluateScrollOffset(), list})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2183,9 +2198,11 @@ func (t *Terminal) Loop() {
|
||||
t.suppress = false
|
||||
case reqReinit:
|
||||
t.tui.Resume(t.fullscreen, t.sigstop)
|
||||
t.redraw()
|
||||
t.redraw(true)
|
||||
case reqRedraw:
|
||||
t.redraw()
|
||||
t.redraw(false)
|
||||
case reqFullRedraw:
|
||||
t.redraw(true)
|
||||
case reqClose:
|
||||
exit(func() int {
|
||||
if t.output() {
|
||||
@@ -2253,13 +2270,15 @@ func (t *Terminal) Loop() {
|
||||
}
|
||||
}
|
||||
}
|
||||
togglePreview := func(enabled bool) {
|
||||
togglePreview := func(enabled bool) bool {
|
||||
if t.previewer.enabled != enabled {
|
||||
t.previewer.enabled = enabled
|
||||
t.tui.Clear()
|
||||
// We need to immediately update t.pwindow so we don't use reqRedraw
|
||||
t.resizeWindows()
|
||||
req(reqPrompt, reqList, reqInfo, reqHeader)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
toggle := func() bool {
|
||||
current := t.currentItem()
|
||||
@@ -2296,12 +2315,12 @@ func (t *Terminal) Loop() {
|
||||
}
|
||||
}
|
||||
|
||||
actionsFor := func(eventType tui.EventType) []action {
|
||||
actionsFor := func(eventType tui.EventType) []*action {
|
||||
return t.keymap[eventType.AsEvent()]
|
||||
}
|
||||
|
||||
var doAction func(action) bool
|
||||
doActions := func(actions []action) bool {
|
||||
var doAction func(*action) bool
|
||||
doActions := func(actions []*action) bool {
|
||||
for _, action := range actions {
|
||||
if !doAction(action) {
|
||||
return false
|
||||
@@ -2309,7 +2328,7 @@ func (t *Terminal) Loop() {
|
||||
}
|
||||
return true
|
||||
}
|
||||
doAction = func(a action) bool {
|
||||
doAction = func(a *action) bool {
|
||||
switch a.t {
|
||||
case actIgnore:
|
||||
case actExecute, actExecuteSilent:
|
||||
@@ -2327,7 +2346,7 @@ func (t *Terminal) Loop() {
|
||||
if valid {
|
||||
t.cancelPreview()
|
||||
t.previewBox.Set(reqPreviewEnqueue,
|
||||
previewRequest{t.previewOpts.command, t.pwindow, list})
|
||||
previewRequest{t.previewOpts.command, t.pwindow, t.evaluateScrollOffset(), list})
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2489,14 +2508,14 @@ func (t *Terminal) Loop() {
|
||||
}
|
||||
case actToggleIn:
|
||||
if t.layout != layoutDefault {
|
||||
return doAction(action{t: actToggleUp})
|
||||
return doAction(&action{t: actToggleUp})
|
||||
}
|
||||
return doAction(action{t: actToggleDown})
|
||||
return doAction(&action{t: actToggleDown})
|
||||
case actToggleOut:
|
||||
if t.layout != layoutDefault {
|
||||
return doAction(action{t: actToggleDown})
|
||||
return doAction(&action{t: actToggleDown})
|
||||
}
|
||||
return doAction(action{t: actToggleUp})
|
||||
return doAction(&action{t: actToggleUp})
|
||||
case actToggleDown:
|
||||
if t.multi > 0 && t.merger.Length() > 0 && toggle() {
|
||||
t.vmove(-1, true)
|
||||
@@ -2520,7 +2539,7 @@ func (t *Terminal) Loop() {
|
||||
req(reqClose)
|
||||
}
|
||||
case actClearScreen:
|
||||
req(reqRedraw)
|
||||
req(reqFullRedraw)
|
||||
case actClearQuery:
|
||||
t.input = []rune{}
|
||||
t.cx = 0
|
||||
@@ -2707,6 +2726,45 @@ func (t *Terminal) Loop() {
|
||||
for key := range keys {
|
||||
delete(t.keymap, key)
|
||||
}
|
||||
case actChangePreview:
|
||||
if t.previewOpts.command != a.a {
|
||||
togglePreview(len(a.a) > 0)
|
||||
t.previewOpts.command = a.a
|
||||
refreshPreview(t.previewOpts.command)
|
||||
}
|
||||
case actChangePreviewWindow:
|
||||
currentPreviewOpts := t.previewOpts
|
||||
|
||||
// Reset preview options and apply the additional options
|
||||
t.previewOpts = t.initialPreviewOpts
|
||||
|
||||
// Split window options
|
||||
tokens := strings.Split(a.a, "|")
|
||||
parsePreviewWindow(&t.previewOpts, tokens[0])
|
||||
if len(tokens) > 1 {
|
||||
a.a = strings.Join(append(tokens[1:], tokens[0]), "|")
|
||||
}
|
||||
|
||||
if t.previewOpts.hidden {
|
||||
togglePreview(false)
|
||||
} else {
|
||||
// Full redraw
|
||||
if !currentPreviewOpts.sameLayout(t.previewOpts) {
|
||||
if togglePreview(true) {
|
||||
refreshPreview(t.previewOpts.command)
|
||||
} else {
|
||||
req(reqRedraw)
|
||||
}
|
||||
} else if !currentPreviewOpts.sameContentLayout(t.previewOpts) {
|
||||
t.previewed.version = 0
|
||||
req(reqPreviewRefresh)
|
||||
}
|
||||
|
||||
// Adjust scroll offset
|
||||
if t.hasPreviewWindow() && currentPreviewOpts.scroll != t.previewOpts.scroll {
|
||||
scrollPreviewTo(t.evaluateScrollOffset())
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
@@ -2714,7 +2772,7 @@ func (t *Terminal) Loop() {
|
||||
if t.jumping == jumpDisabled {
|
||||
actions := t.keymap[event.Comparable()]
|
||||
if len(actions) == 0 && event.Type == tui.Rune {
|
||||
doAction(action{t: actRune})
|
||||
doAction(&action{t: actRune})
|
||||
} else if !doActions(actions) {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ func RunesWidth(runes []rune, prefixWidth int, tabstop int, limit int) (int, int
|
||||
w = runewidth.StringWidth(s) + strings.Count(s, "\n")
|
||||
}
|
||||
width += w
|
||||
if limit > 0 && width > limit {
|
||||
if width > limit {
|
||||
return width, idx
|
||||
}
|
||||
idx += len(rs)
|
||||
|
||||
@@ -38,3 +38,19 @@ func TestOnce(t *testing.T) {
|
||||
t.Error("Expected: false")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunesWidth(t *testing.T) {
|
||||
for _, args := range [][]int{
|
||||
{100, 5, -1},
|
||||
{3, 4, 3},
|
||||
{0, 1, 0},
|
||||
} {
|
||||
width, overflowIdx := RunesWidth([]rune("hello"), 0, 0, args[0])
|
||||
if width != args[1] {
|
||||
t.Errorf("Expected width: %d, actual: %d", args[1], width)
|
||||
}
|
||||
if overflowIdx != args[2] {
|
||||
t.Errorf("Expected overflow index: %d, actual: %d", args[2], overflowIdx)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2142,6 +2142,72 @@ class TestGoFZF < TestBase
|
||||
assert_equal expected.chomp, lines.take(6).join("\n")
|
||||
end
|
||||
end
|
||||
|
||||
def test_change_preview_window
|
||||
tmux.send_keys "seq 1000 | #{FZF} --preview 'echo [[{}]]' --preview-window border-none --bind '" \
|
||||
'a:change-preview(echo __{}__),' \
|
||||
'b:change-preview-window(down)+change-preview(echo =={}==)+change-preview-window(up),' \
|
||||
'c:change-preview(),d:change-preview-window(hidden),' \
|
||||
"e:preview(printf ::%${FZF_PREVIEW_COLUMNS}s{})+change-preview-window(up),f:change-preview-window(up,wrap)'", :Enter
|
||||
tmux.until { |lines| assert_equal 1000, lines.item_count }
|
||||
tmux.until { |lines| assert_includes lines[0], '[[1]]' }
|
||||
|
||||
# change-preview action permanently changes the preview command set by --preview
|
||||
tmux.send_keys 'a'
|
||||
tmux.until { |lines| assert_includes lines[0], '__1__' }
|
||||
tmux.send_keys :Up
|
||||
tmux.until { |lines| assert_includes lines[0], '__2__' }
|
||||
|
||||
# When multiple change-preview-window actions are bound to a single key,
|
||||
# the last one wins and the updated options are immediately applied to the new preview
|
||||
tmux.send_keys 'b'
|
||||
tmux.until { |lines| assert_equal '==2==', lines[0] }
|
||||
tmux.send_keys :Up
|
||||
tmux.until { |lines| assert_equal '==3==', lines[0] }
|
||||
|
||||
# change-preview with an empty preview command closes the preview window
|
||||
tmux.send_keys 'c'
|
||||
tmux.until { |lines| refute_includes lines[0], '==' }
|
||||
|
||||
# change-preview again to re-open the preview window
|
||||
tmux.send_keys 'a'
|
||||
tmux.until { |lines| assert_equal '__3__', lines[0] }
|
||||
|
||||
# Hide the preview window with hidden flag
|
||||
tmux.send_keys 'd'
|
||||
tmux.until { |lines| refute_includes lines[0], '__3__' }
|
||||
|
||||
# One-off preview
|
||||
tmux.send_keys 'e'
|
||||
tmux.until do |lines|
|
||||
assert_equal '::', lines[0]
|
||||
refute_includes lines[1], '3'
|
||||
end
|
||||
|
||||
# Wrapped
|
||||
tmux.send_keys 'f'
|
||||
tmux.until do |lines|
|
||||
assert_equal '::', lines[0]
|
||||
assert_equal ' 3', lines[1]
|
||||
end
|
||||
end
|
||||
|
||||
def test_change_preview_window_rotate
|
||||
tmux.send_keys "seq 100 | #{FZF} --preview-window left,border-none --preview 'echo hello' --bind '" \
|
||||
"a:change-preview-window(right|down|up|hidden|)'", :Enter
|
||||
3.times do
|
||||
tmux.until { |lines| lines[0].start_with?('hello') }
|
||||
tmux.send_keys 'a'
|
||||
tmux.until { |lines| lines[0].end_with?('hello') }
|
||||
tmux.send_keys 'a'
|
||||
tmux.until { |lines| lines[-1].start_with?('hello') }
|
||||
tmux.send_keys 'a'
|
||||
tmux.until { |lines| assert_equal 'hello', lines[0] }
|
||||
tmux.send_keys 'a'
|
||||
tmux.until { |lines| refute_includes lines[0], 'hello' }
|
||||
tmux.send_keys 'a'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
module TestShell
|
||||
|
||||
Reference in New Issue
Block a user