summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJunegunn Choi <junegunn.c@gmail.com>2017-01-08 01:30:31 +0900
committerJunegunn Choi <junegunn.c@gmail.com>2017-01-08 02:09:56 +0900
commit1448d631a7c72905f62dbb343a8f231a1c3cc52c (patch)
tree05abfedd2a0777c2640c8259267d3ad855879dfe
parentfd137a9e875ba1fd9feed4903e102951f8098c33 (diff)
Add --height option
-rw-r--r--.travis.yml2
-rw-r--r--CHANGELOG.md6
-rw-r--r--README.md36
-rwxr-xr-xbin/fzf-tmux3
-rw-r--r--man/man1/fzf.19
-rw-r--r--plugin/fzf.vim26
-rw-r--r--shell/completion.bash57
-rw-r--r--shell/completion.zsh14
-rw-r--r--shell/key-bindings.bash33
-rw-r--r--shell/key-bindings.fish13
-rw-r--r--shell/key-bindings.zsh10
-rw-r--r--src/options.go18
-rw-r--r--src/result.go2
-rw-r--r--src/result_test.go15
-rw-r--r--src/terminal.go225
-rw-r--r--src/tui/light.go764
-rw-r--r--src/tui/ncurses.go250
-rw-r--r--src/tui/tcell.go273
-rw-r--r--src/tui/tui.go170
-rw-r--r--src/tui/tui_test.go14
-rw-r--r--src/util/util.go16
-rw-r--r--src/util/util_unix.go6
-rw-r--r--src/util/util_windows.go6
-rw-r--r--test/test_go.rb272
24 files changed, 1628 insertions, 612 deletions
diff --git a/.travis.yml b/.travis.yml
index a1a6497d..3f2a67f7 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -2,7 +2,7 @@ language: ruby
matrix:
include:
- env: TAGS=
- rvm: 2.2.0
+ rvm: 2.3.3
# - env: TAGS=tcell
# rvm: 2.2.0
diff --git a/CHANGELOG.md b/CHANGELOG.md
index a37b74c5..f78270c5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,12 @@
CHANGELOG
=========
+0.16.0
+------
+- Added `--height HEIGHT[%]` option
+- Preview window will truncate long lines by default. Line wrap can be enabled
+ by `:wrap` flag in `--preview-window`.
+
0.15.9
------
- Fixed rendering glitches introduced in 0.15.8
diff --git a/README.md b/README.md
index 63812794..550ea777 100644
--- a/README.md
+++ b/README.md
@@ -123,6 +123,29 @@ vim $(fzf)
- Mouse: scroll, click, double-click; shift-click and shift-scroll on
multi-select mode
+#### Layout
+
+fzf by default starts in fullscreen mode, but you can make it start below the
+cursor with `--height` option.
+
+```sh
+vim $(fzf --height 40%)
+```
+
+Also check out `--reverse` option if you prefer "top-down" layout instead of
+the default "bottom-up" layout.
+
+```sh
+vim $(fzf --height 40% --reverse)
+```
+
+You can add these options to `$FZF_DEFAULT_OPTS` so that they're applied by
+default.
+
+```sh
+export FZF_DEFAULT_OPTS='--height 40% --reverse'
+```
+
#### Search syntax
Unless otherwise specified, fzf starts in "extended-search mode" where you can
@@ -189,6 +212,13 @@ cat /usr/share/dict/words | fzf-tmux -l 20% --multi --reverse
It will still work even when you're not on tmux, silently ignoring `-[udlr]`
options, so you can invariably use `fzf-tmux` in your scripts.
+Alternatively, you can use `--height HEIGHT[%]` option not to start fzf in
+fullscreen mode.
+
+```sh
+fzf --height 40%
+```
+
Key bindings for command line
-----------------------------
@@ -206,9 +236,9 @@ fish.
- Set `FZF_ALT_C_COMMAND` to override the default command
- Set `FZF_ALT_C_OPTS` to pass additional options
-If you're on a tmux session, fzf will start in a split pane. You may disable
-this tmux integration by setting `FZF_TMUX` to 0, or change the height of the
-pane with `FZF_TMUX_HEIGHT` (e.g. `20`, `50%`).
+If you're on a tmux session, you can start fzf in a split pane by setting
+`FZF_TMUX` to 1, and change the height of the pane with `FZF_TMUX_HEIGHT`
+(e.g. `20`, `50%`).
If you use vi mode on bash, you need to add `set -o vi` *before* `source
~/.fzf.bash` in your .bashrc, so that it correctly sets up key bindings for vi
diff --git a/bin/fzf-tmux b/bin/fzf-tmux
index dd335d2b..f2011998 100755
--- a/bin/fzf-tmux
+++ b/bin/fzf-tmux
@@ -114,6 +114,9 @@ if [[ -z "$TMUX" || "$opt" =~ ^-h && "$columns" -le 40 || ! "$opt" =~ ^-h && "$l
exit $?
fi
+# --height option is not allowed
+args+=("--no-height")
+
# Handle zoomed tmux pane by moving it to a temp window
if tmux list-panes -F '#F' | grep -q Z; then
zoomed=1
diff --git a/man/man1/fzf.1 b/man/man1/fzf.1
index 4e19ce92..fac2aab5 100644
--- a/man/man1/fzf.1
+++ b/man/man1/fzf.1
@@ -130,6 +130,10 @@ on the center of the screen.
Label characters for \fBjump\fR and \fBjump-accept\fR
.SS Layout
.TP
+.BI "--height=" "HEIGHT[%]"
+Display fzf window below the cursor with the given height instead of using
+fullscreen.
+.TP
.B "--reverse"
Reverse orientation
.TP
@@ -248,10 +252,11 @@ e.g. \fBfzf --preview="head -$LINES {}"\fR
Note that you can escape a placeholder pattern by prepending a backslash.
.RE
.TP
-.BI "--preview-window=" "[POSITION][:SIZE[%]][:hidden]"
+.BI "--preview-window=" "[POSITION][:SIZE[%]][:wrap][:hidden]"
Determine the layout of the preview window. If the argument ends with
\fB:hidden\fR, the preview window will be hidden by default until
-\fBtoggle-preview\fR action is triggered.
+\fBtoggle-preview\fR action is triggered. Long lines are truncated by default.
+Line wrap can be enabled with \fB:wrap\fR flag.
.RS
.B POSITION: (default: right)
diff --git a/plugin/fzf.vim b/plugin/fzf.vim
index 06af648e..c8c6c91f 100644
--- a/plugin/fzf.vim
+++ b/plugin/fzf.vim
@@ -296,14 +296,24 @@ try
else
let prefix = ''
endif
- let tmux = (!has('nvim') || get(g:, 'fzf_prefer_tmux', 0)) && s:tmux_enabled() && s:splittable(dict)
+
+ let use_height = has_key(dict, 'down') &&
+ \ !(has('nvim') || has('win32') || has('win64') || s:present(dict, 'up', 'left', 'right'))
+ let tmux = !use_height && (!has('nvim') || get(g:, 'fzf_prefer_tmux', 0)) && s:tmux_enabled() && s:splittable(dict)
+ let term = has('nvim') && !tmux
+ if use_height
+ let optstr .= ' --height='.s:calc_size(&lines, dict.down, dict)
+ elseif term
+ let optstr .= ' --no-height'
+ endif
let command = prefix.(tmux ? s:fzf_tmux(dict) : fzf_exec).' '.optstr.' > '.temps.result
- if has('nvim') && !tmux
+ if term
return s:execute_term(dict, command, temps)
endif
- let lines = tmux ? s:execute_tmux(dict, command, temps) : s:execute(dict, command, temps)
+ let lines = tmux ? s:execute_tmux(dict, command, temps)
+ \ : s:execute(dict, command, use_height, temps)
call s:callback(dict, lines)
return lines
finally
@@ -400,9 +410,9 @@ function! s:exit_handler(code, command, ...)
return 1
endfunction
-function! s:execute(dict, command, temps) abort
+function! s:execute(dict, command, use_height, temps) abort
call s:pushd(a:dict)
- if has('unix')
+ if has('unix') && !a:use_height
silent! !clear 2> /dev/null
endif
let escaped = escape(substitute(a:command, '\n', '\\n', 'g'), '%#')
@@ -416,7 +426,11 @@ function! s:execute(dict, command, temps) abort
else
let command = escaped
endif
- execute 'silent !'.command
+ if a:use_height
+ call system(printf('tput cup %d > /dev/tty; tput cnorm > /dev/tty; %s 2> /dev/tty', &lines, command))
+ else
+ execute 'silent !'.command
+ endif
let exit_status = v:shell_error
redraw!
return s:exit_handler(exit_status, command) ? s:collect(a:temps) : []
diff --git a/shell/completion.bash b/shell/completion.bash
index 392aee2d..d6d7238d 100644
--- a/shell/completion.bash
+++ b/shell/completion.bash
@@ -5,7 +5,7 @@
# / __/ / /_/ __/
# /_/ /___/_/-completion.bash
#
-# - $FZF_TMUX (default: 1)
+# - $FZF_TMUX (default: 0)
# - $FZF_TMUX_HEIGHT (default: '40%')
# - $FZF_COMPLETION_TRIGGER (default: '**')
# - $FZF_COMPLETION_OPTS (default: empty)
@@ -30,6 +30,15 @@ fi
###########################################################
+# To redraw line after fzf closes (printf '\e[5n')
+bind '"\e[0n": redraw-current-line'
+
+__fzfcmd_complete() {
+ [ -n "$TMUX_PANE" ] && [ "${FZF_TMUX:-0}" != 0 ] && [ ${LINES:-40} -gt 15 ] &&
+ echo "fzf-tmux -d${FZF_TMUX_HEIGHT:-40%}" ||
+ echo "fzf --height ${FZF_TMUX_HEIGHT:-40%} --reverse"
+}
+
_fzf_orig_completion_filter() {
sed 's/^\(.*-F\) *\([^ ]*\).* \([^ ]*\)$/export _fzf_orig_completion_\3="\1 %s \3 #\2";/' |
awk -F= '{gsub(/[^A-Za-z0-9_= ;]/, "_", $1); print $1"="$2}'
@@ -43,35 +52,42 @@ _fzf_opts_completion() {
opts="
-x --extended
-e --exact
+ --algo
-i +i
-n --nth
+ --with-nth
-d --delimiter
+s --no-sort
--tac
--tiebreak
- --bind
-m --multi
--no-mouse
- --color
- --black
- --reverse
+ --bind
+ --cycle
--no-hscroll
+ --jump-labels
+ --height
+ --reverse
+ --margin
--inline-info
--prompt
+ --header
+ --header-lines
+ --ansi
+ --tabstop
+ --color
+ --no-bold
+ --history
+ --history-size
+ --preview
+ --preview-window
-q --query
-1 --select-1
-0 --exit-0
-f --filter
--print-query
--expect
- --toggle-sort
- --sync
- --cycle
- --history
- --history-size
- --header
- --header-lines
- --margin"
+ --sync"
case "${prev}" in
--tiebreak)
@@ -116,7 +132,7 @@ _fzf_handle_dynamic_completion() {
__fzf_generic_path_completion() {
local cur base dir leftover matches trigger cmd fzf
- [ "${FZF_TMUX:-1}" != 0 ] && fzf="fzf-tmux -d ${FZF_TMUX_HEIGHT:-40%}" || fzf="fzf"
+ fzf="$(__fzfcmd_complete)"
cmd="${COMP_WORDS[0]//[^A-Za-z0-9_=]/_}"
COMPREPLY=()
trigger=${FZF_COMPLETION_TRIGGER-'**'}
@@ -132,7 +148,6 @@ __fzf_generic_path_completion() {
leftover=${leftover/#\/}
[ -z "$dir" ] && dir='.'
[ "$dir" != "/" ] && dir="${dir/%\//}"
- tput sc
matches=$(eval "$1 $(printf %q "$dir")" | $fzf $FZF_COMPLETION_OPTS $2 -q "$leftover" | while read -r item; do
printf "%q$3 " "$item"
done)
@@ -142,7 +157,7 @@ __fzf_generic_path_completion() {
else
COMPREPLY=( "$cur" )
fi
- tput rc
+ printf '\e[5n'
return 0
fi
dir=$(dirname "$dir")
@@ -160,7 +175,7 @@ _fzf_complete() {
local cur selected trigger cmd fzf post
post="$(caller 0 | awk '{print $2}')_post"
type -t "$post" > /dev/null 2>&1 || post=cat
- [ "${FZF_TMUX:-1}" != 0 ] && fzf="fzf-tmux -d ${FZF_TMUX_HEIGHT:-40%}" || fzf="fzf"
+ fzf="$(__fzfcmd_complete)"
cmd="${COMP_WORDS[0]//[^A-Za-z0-9_=]/_}"
trigger=${FZF_COMPLETION_TRIGGER-'**'}
@@ -168,10 +183,9 @@ _fzf_complete() {
if [[ "$cur" == *"$trigger" ]]; then
cur=${cur:0:${#cur}-${#trigger}}
- tput sc
selected=$(cat | $fzf $FZF_COMPLETION_OPTS $1 -q "$cur" | $post | tr '\n' ' ')
selected=${selected% } # Strip trailing space not to repeat "-o nospace"
- tput rc
+ printf '\e[5n'
if [ -n "$selected" ]; then
COMPREPLY=("$selected")
@@ -200,10 +214,9 @@ _fzf_complete_kill() {
[ -n "${COMP_WORDS[COMP_CWORD]}" ] && return 1
local selected fzf
- [ "${FZF_TMUX:-1}" != 0 ] && fzf="fzf-tmux -d ${FZF_TMUX_HEIGHT:-40%}" || fzf="fzf"
- tput sc
+ fzf="$(__fzfcmd_complete)"
selected=$(ps -ef | sed 1d | $fzf -m $FZF_COMPLETION_OPTS | awk '{print $2}' | tr '\n' ' ')
- tput rc
+ printf '\e[5n'
if [ -n "$selected" ]; then
COMPREPLY=( "$selected" )
diff --git a/shell/completion.zsh b/shell/completion.zsh
index d3faef80..fb2c16a1 100644
--- a/shell/completion.zsh
+++ b/shell/completion.zsh
@@ -5,7 +5,7 @@
# / __/ / /_/ __/
# /_/ /___/_/-completion.zsh
#
-# - $FZF_TMUX (default: 1)
+# - $FZF_TMUX (default: 0)
# - $FZF_TMUX_HEIGHT (default: '40%')
# - $FZF_COMPLETION_TRIGGER (default: '**')
# - $FZF_COMPLETION_OPTS (default: empty)
@@ -30,6 +30,12 @@ fi
###########################################################
+__fzfcmd_complete() {
+ [ -n "$TMUX_PANE" ] && [ "${FZF_TMUX:-0}" != 0 ] && [ ${LINES:-40} -gt 15 ] &&
+ echo "fzf-tmux -d${FZF_TMUX_HEIGHT:-40%}" ||
+ echo "fzf --height ${FZF_TMUX_HEIGHT:-40%} --reverse"
+}
+
__fzf_generic_path_completion() {
local base lbuf compgen fzf_opts suffix tail fzf dir leftover matches
# (Q) flag removes a quoting level: "foo\ bar" => "foo bar"
@@ -39,7 +45,7 @@ __fzf_generic_path_completion() {
fzf_opts=$4
suffix=$5
tail=$6
- [ ${FZF_TMUX:-1} -eq 1 ] && fzf="fzf-tmux -d ${FZF_TMUX_HEIGHT:-40%}" || fzf="fzf"
+ fzf="$(__fzfcmd_complete)"
setopt localoptions nonomatch
dir="$base"
@@ -90,7 +96,7 @@ _fzf_complete() {
post="${funcstack[2]}_post"
type $post > /dev/null 2>&1 || post=cat
- [ ${FZF_TMUX:-1} -eq 1 ] && fzf="fzf-tmux -d ${FZF_TMUX_HEIGHT:-40%}" || fzf="fzf"
+ fzf="$(__fzfcmd_complete)"
_fzf_feed_fifo "$fifo"
matches=$(cat "$fifo" | ${=fzf} ${=FZF_COMPLETION_OPTS} ${=fzf_opts} -q "${(Q)prefix}" | $post | tr '\n' ' ')
@@ -157,7 +163,7 @@ fzf-completion() {
tail=${LBUFFER:$(( ${#LBUFFER} - ${#trigger} ))}
# Kill completion (do not require trigger sequence)
if [ $cmd = kill -a ${LBUFFER[-1]} = ' ' ]; then
- [ ${FZF_TMUX:-1} -eq 1 ] && fzf="fzf-tmux -d ${FZF_TMUX_HEIGHT:-40%}" || fzf="fzf"
+ fzf="$(__fzfcmd_complete)"
matches=$(ps -ef | sed 1d | ${=fzf} ${=FZF_COMPLETION_OPTS} -m | awk '{print $2}' | tr '\n' ' ')
if [ -n "$matches" ]; then
LBUFFER="$LBUFFER$matches"
diff --git a/shell/key-bindings.bash b/shell/key-bindings.bash
index 33d8bc8d..382302d4 100644
--- a/shell/key-bindings.bash
+++ b/shell/key-bindings.bash
@@ -5,7 +5,7 @@ __fzf_select__() {
-o -type f -print \
-o -type d -print \
-o -type l -print 2> /dev/null | cut -b3-"}"
- eval "$cmd | fzf -m $FZF_CTRL_T_OPTS" | while read -r item; do
+ eval "$cmd | fzf --height ${FZF_TMUX_HEIGHT:-40%} --reverse -m $@ $FZF_CTRL_T_OPTS" | while read -r item; do
printf '%q ' "$item"
done
echo
@@ -13,8 +13,14 @@ __fzf_select__() {
if [[ $- =~ i ]]; then
+__fzf_use_tmux__() {
+ [ -n "$TMUX_PANE" ] && [ "${FZF_TMUX:-0}" != 0 ] && [ ${LINES:-40} -gt 15 ]
+}
+
__fzfcmd() {
- [ "${FZF_TMUX:-1}" != 0 ] && echo "fzf-tmux -d${FZF_TMUX_HEIGHT:-40%}" || echo "fzf"
+ __fzf_use_tmux__ &&
+ echo "fzf-tmux -d${FZF_TMUX_HEIGHT:-40%}" ||
+ echo "fzf --height ${FZF_TMUX_HEIGHT:-40%} --reverse"
}
__fzf_select_tmux__() {
@@ -26,14 +32,14 @@ __fzf_select_tmux__() {
height="-l $height"
fi
- tmux split-window $height "cd $(printf %q "$PWD"); FZF_DEFAULT_OPTS=$(printf %q "$FZF_DEFAULT_OPTS") PATH=$(printf %q "$PATH") FZF_CTRL_T_COMMAND=$(printf %q "$FZF_CTRL_T_COMMAND") FZF_CTRL_T_OPTS=$(printf %q "$FZF_CTRL_T_OPTS") bash -c 'source \"${BASH_SOURCE[0]}\"; RESULT=\"\$(__fzf_select__)\"; tmux setb -b fzf \"\$RESULT\" \\; pasteb -b fzf -t $TMUX_PANE \\; deleteb -b fzf || tmux send-keys -t $TMUX_PANE \"\$RESULT\"'"
+ tmux split-window $height "cd $(printf %q "$PWD"); FZF_DEFAULT_OPTS=$(printf %q "$FZF_DEFAULT_OPTS") PATH=$(printf %q "$PATH") FZF_CTRL_T_COMMAND=$(printf %q "$FZF_CTRL_T_COMMAND") FZF_CTRL_T_OPTS=$(printf %q "$FZF_CTRL_T_OPTS") bash -c 'source \"${BASH_SOURCE[0]}\"; RESULT=\"\$(__fzf_select__ --no-height)\"; tmux setb -b fzf \"\$RESULT\" \\; pasteb -b fzf -t $TMUX_PANE \\; deleteb -b fzf || tmux send-keys -t $TMUX_PANE \"\$RESULT\"'"
}
fzf-file-widget() {
if __fzf_use_tmux__; then
__fzf_select_tmux__
else
- local selected="$(__fzf_select__)"
+ local selected="$(__fzf_select__ --height ${FZF_TMUX_HEIGHT:-40%} --reverse)"
READLINE_LINE="${READLINE_LINE:0:$READLINE_POINT}$selected${READLINE_LINE:$READLINE_POINT}"
READLINE_POINT=$(( READLINE_POINT + ${#selected} ))
fi
@@ -51,7 +57,7 @@ __fzf_history__() (
shopt -u nocaseglob nocasematch
line=$(
HISTTIMEFORMAT= history |
- eval "$(__fzfcmd) +s --tac +m -n2..,.. --tiebreak=index --toggle-sort=ctrl-r $FZF_CTRL_R_OPTS" |
+ eval "$(__fzfcmd) +s --tac --no-reverse +m -n2..,.. --tiebreak=index --toggle-sort=ctrl-r $FZF_CTRL_R_OPTS" |
command grep '^ *[0-9]') &&
if [[ $- =~ H ]]; then
sed 's/^ *\([0-9]*\)\** .*/!\1/' <<< "$line"
@@ -60,22 +66,15 @@ __fzf_history__() (
fi
)
-__fzf_use_tmux__() {
- [ -n "$TMUX_PANE" ] && [ "${FZF_TMUX:-1}" != 0 ] && [ ${LINES:-40} -gt 15 ]
-}
-
-[ $BASH_VERSINFO -gt 3 ] && __use_bind_x=1 || __use_bind_x=0
-__fzf_use_tmux__ && __use_tmux=1 || __use_tmux=0
-
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 [ $__use_bind_x -eq 1 ]; then
+ if [ $BASH_VERSINFO -gt 3 ]; then
bind -x '"\C-t": "fzf-file-widget"'
- elif [ $__use_tmux -eq 1 ]; then
+ 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
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"'
@@ -102,9 +101,9 @@ else
# CTRL-T - Paste the selected file path into the command line
# - FIXME: Selected items are attached to the end regardless of cursor position
- if [ $__use_bind_x -eq 1 ]; then
+ if [ $BASH_VERSINFO -gt 3 ]; then
bind -x '"\C-t": "fzf-file-widget"'
- elif [ $__use_tmux -eq 1 ]; then
+ 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 "'
@@ -120,6 +119,4 @@ else
bind -m vi-command '"\ec": "ddi`__fzf_cd__`\C-x\C-e\C-x\C-r\C-m"'
fi
-unset -v __use_tmux __use_bind_x
-
fi
diff --git a/shell/key-bindings.fish b/shell/key-bindings.fish
index dd75fecf..fc618448 100644
--- a/shell/key-bindings.fish
+++ b/shell/key-bindings.fish
@@ -39,7 +39,7 @@ function fzf_key_bindings
end
function fzf-history-widget -d "Show command history"
- history | eval (__fzfcmd) +s +m --tiebreak=index $FZF_CTRL_R_OPTS -q '(commandline)' | read -l result
+ history | eval (__fzfcmd) +s +m --no-reverse --tiebreak=index $FZF_CTRL_R_OPTS -q '(commandline)' | read -l result
and commandline -- $result
commandline -f repaint
end
@@ -54,15 +54,12 @@ function fzf_key_bindings
end
function __fzfcmd
- set -q FZF_TMUX; or set FZF_TMUX 1
+ set -q FZF_TMUX; or set FZF_TMUX 0
+ set -q FZF_TMUX_HEIGHT; or set FZF_TMUX_HEIGHT 40%
if [ $FZF_TMUX -eq 1 ]
- if set -q FZF_TMUX_HEIGHT
- echo "fzf-tmux -d$FZF_TMUX_HEIGHT"
- else
- echo "fzf-tmux -d40%"
- end
+ echo "fzf-tmux -d$FZF_TMUX_HEIGHT"
else
- echo "fzf"
+ echo "fzf --height $FZF_TMUX_HEIGHT --reverse"
end
end
diff --git a/shell/key-bindings.zsh b/shell/key-bindings.zsh
index fed01532..7e24d92a 100644
--- a/shell/key-bindings.zsh
+++ b/shell/key-bindings.zsh
@@ -17,8 +17,14 @@ __fsel() {
return $ret
}
+__fzf_use_tmux__() {
+ [ -n "$TMUX_PANE" ] && [ "${FZF_TMUX:-0}" != 0 ] && [ ${LINES:-40} -gt 15 ]
+}
+
__fzfcmd() {
- [ ${FZF_TMUX:-1} -eq 1 ] && echo "fzf-tmux -d${FZF_TMUX_HEIGHT:-40%}" || echo "fzf"
+ __fzf_use_tmux__ &&
+ echo "fzf-tmux -d${FZF_TMUX_HEIGHT:-40%}" ||
+ echo "fzf --height ${FZF_TMUX_HEIGHT:-40%} --reverse"
}
fzf-file-widget() {
@@ -49,7 +55,7 @@ bindkey '\ec' fzf-cd-widget
fzf-history-widget() {
local selected num
setopt localoptions noglobsubst pipefail 2> /dev/null
- selected=( $(fc -l 1 | eval "$(__fzfcmd) +s --tac +m -n2..,.. --tiebreak=index --toggle-sort=ctrl-r $FZF_CTRL_R_OPTS -q ${(q)LBUFFER}") )
+ selected=( $(fc -l 1 | eval "$(__fzfcmd) +s --tac --no-reverse +m -n2..,.. --tiebreak=index --toggle-sort=ctrl-r $FZF_CTRL_R_OPTS -q ${(q)LBUFFER}") )
local ret=$?
if [ -n "$selected" ]; then
num=$selected[1]
diff --git a/src/options.go b/src/options.go
index 6fd3f6c6..0c6661f3 100644
--- a/src/options.go
+++ b/src/options.go
@@ -10,6 +10,7 @@ import (
"github.com/junegunn/fzf/src/algo"
"github.com/junegunn/fzf/src/tui"
+ "github.com/junegunn/fzf/src/util"
"github.com/junegunn/go-shellwords"
)
@@ -46,6 +47,8 @@ const usage = `usage: fzf [options]
--jump-labels=CHARS Label characters for jump and jump-accept
Layout
+ --height=HEIGHT[%] Display fzf window below the cursor with the given
+ height instead of using fullscreen
--reverse Reverse orientation
--margin=MARGIN Screen margin (TRBL / TB,RL / T,RL,B / T,R,B,L)
--inline-info Display finder info inline with the query
@@ -147,6 +150,7 @@ type Options struct {
Theme *tui.ColorTheme
Black bool
Bold bool
+ Height sizeSpec
Reverse bool
Cycle bool
Hscroll bool
@@ -760,6 +764,14 @@ func parseSize(str string, maxPercent float64, label string) sizeSpec {
return sizeSpec{val, percent}
}
+func parseHeight(str string) sizeSpec {
+ if util.IsWindows() {
+ errorExit("--height options is currently not supported on Windows")
+ }
+ size := parseSize(str, 100, "height")
+ return size
+}
+
func parsePreviewWindow(opts *previewOpts, input string) {
// Default
opts.position = posRight
@@ -1003,6 +1015,10 @@ func parseOptions(opts *Options, allArgs []string) {
case "--preview-window":
parsePreviewWindow(&opts.Preview,
nextString(allArgs, &i, "preview window layout required: [up|down|left|right][:SIZE[%]][:wrap][:hidden]"))
+ case "--height":
+ opts.Height = parseHeight(nextString(allArgs, &i, "height required: [HEIGHT[%]]"))
+ case "--no-height":
+ opts.Height = sizeSpec{}
case "--no-margin":
opts.Margin = defaultMargin()
case "--margin":
@@ -1029,6 +1045,8 @@ func parseOptions(opts *Options, allArgs []string) {
opts.WithNth = splitNth(value)
} else if match, _ := optString(arg, "-s", "--sort="); match {
opts.Sort = 1 // Don't care
+ } else if match, value := optString(arg, "--height="); match {
+ opts.Height = parseHeight(value)
} else if match, value := optString(arg, "--toggle-sort="); match {
parseToggleSort(opts.Keymap, value)
} else if match, value := optString(arg, "--expect="); match {
diff --git a/src/result.go b/src/result.go
index e2d7c755..3d79176f 100644
--- a/src/result.go
+++ b/src/result.go
@@ -166,7 +166,7 @@ func (result *Result) colorOffsets(matchOffsets []Offset, theme *tui.ColorTheme,
}
colors = append(colors, colorOffset{
offset: [2]int32{int32(start), int32(idx)},
- color: tui.PairFor(fg, bg),
+ color: tui.NewColorPair(fg, bg),
attr: ansi.color.attr.Merge(attr)})
}
}
diff --git a/src/result_test.go b/src/result_test.go
index 15b1bdbb..0e91fc87 100644
--- a/src/result_test.go
+++ b/src/result_test.go
@@ -105,7 +105,8 @@ func TestColorOffset(t *testing.T) {
ansiOffset{[2]int32{33, 40}, ansiState{4, 8, tui.Bold}}}}}
// [{[0 5] 9 false} {[5 15] 99 false} {[15 20] 9 false} {[22 25] 10 true} {[25 35] 99 false} {[35 40] 11 true}]
- colors := item.colorOffsets(offsets, tui.Dark256, 99, 0, true)
+ pair := tui.NewColorPair(99, 199)
+ colors := item.colorOffsets(offsets, tui.Dark256, pair, tui.AttrRegular, true)
assert := func(idx int, b int32, e int32, c tui.ColorPair, bold bool) {
var attr tui.Attr
if bold {
@@ -116,10 +117,10 @@ func TestColorOffset(t *testing.T) {
t.Error(o)
}
}
- assert(0, 0, 5, tui.ColUser, false)
- assert(1, 5, 15, 99, false)
- assert(2, 15, 20, tui.ColUser, false)
- assert(3, 22, 25, tui.ColUser+1, true)
- assert(4, 25, 35, 99, false)
- assert(5, 35, 40, tui.ColUser+2, true)
+ assert(0, 0, 5, tui.NewColorPair(1, 5), false)
+ assert(1, 5, 15, pair, false)
+ assert(2, 15, 20, tui.NewColorPair(1, 5), false)
+ assert(3, 22, 25, tui.NewColorPair(2, 6), true)
+ assert(4, 25, 35, pair, false)
+ assert(5, 35, 40, tui.NewColorPair(4, 8), true)
}
diff --git a/src/terminal.go b/src/terminal.go
index 5b482b06..80029236 100644
--- a/src/terminal.go
+++ b/src/terminal.go
@@ -15,8 +15,6 @@ import (
"github.com/junegunn/fzf/src/tui"
"github.com/junegunn/fzf/src/util"
-
- "github.com/junegunn/go-runewidth"
)
// import "github.com/pkg/profile"
@@ -42,6 +40,14 @@ type previewer struct {
enabled bool
}
+type itemLine struct {
+ current bool
+ label string
+ result Result
+}
+
+var emptyLine = itemLine{}
+
// Terminal represents terminal input/output
type Terminal struct {
initDelay time.Duration
@@ -69,11 +75,12 @@ type Terminal struct {
header []string
header0 []string
ansi bool
+ tabstop int
margin [4]sizeSpec
strong tui.Attr
- window *tui.Window
- bwindow *tui.Window
- pwindow *tui.Window
+ window tui.Window
+ bwindow tui.Window
+ pwindow tui.Window
count int
progress int
reading bool
@@ -89,10 +96,12 @@ type Terminal struct {
eventBox *util.EventBox
mutex sync.Mutex
initFunc func()
+ prevLines []itemLine
suppress bool
startChan chan bool
slab *util.Slab
theme *tui.ColorTheme
+ tui tui.Renderer
}
type selectedItem struct {
@@ -115,7 +124,6 @@ func (a byTimeOrder) Less(i, j int) bool {
}
var _spinner = []string{`-`, `\`, `|`, `/`, `-`, `\`, `|`, `/`}
-var _runeWidths = make(map[rune]int)
var _tabStop int
const (
@@ -247,7 +255,6 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
} else {
header = reverseStringArray(opts.Header)
}
- _tabStop = opts.Tabstop
var delay time.Duration
if opts.Tac {
delay = initialDelayTac
@@ -262,6 +269,24 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
if !opts.Bold {
strongAttr = tui.AttrRegular
}
+ var renderer tui.Renderer
+ if opts.Height.size > 0 {
+ maxHeightFunc := func(termHeight int) int {
+ var maxHeight int
+ if opts.Height.percent {
+ maxHeight = int(opts.Height.size * float64(termHeight) / 100.0)
+ } else {
+ maxHeight = util.Min(int(opts.Height.size), termHeight)
+ }
+ if opts.InlineInfo {
+ return util.Max(maxHeight, 3)
+ }
+ return util.Max(maxHeight, 4)
+ }
+ renderer = tui.NewLightRenderer(opts.Theme, opts.Black, opts.Mouse, opts.Tabstop, maxHeightFunc)
+ } else {
+ renderer = tui.NewFullscreenRenderer(opts.Theme, opts.Black, opts.Mouse)
+ }
return &Terminal{
initDelay: delay,
inlineInfo: opts.InlineInfo,
@@ -290,6 +315,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
header: header,
header0: header,
ansi: opts.Ansi,
+ tabstop: opts.Tabstop,
reading: true,
jumping: jumpDisabled,
jumpLabels: opts.JumpLabels,
@@ -306,9 +332,8 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
slab: util.MakeSlab(slab16Size, slab32Size),
theme: opts.Theme,
startChan: make(chan bool, 1),
- initFunc: func() {
- tui.Init(opts.Theme, opts.Black, opts.Mouse)
- }}
+ tui: renderer,
+ initFunc: func() { renderer.Init() }}
}
// Input returns current query string
@@ -401,22 +426,10 @@ func (t *Terminal) sortSelected() []selectedItem {
return sels
}
-func runeWidth(r rune, prefixWidth int) int {
- if r == '\t' {
- return _tabStop - prefixWidth%_tabStop
- } else if w, found := _runeWidths[r]; found {
- return w
- } else {
- w := runewidth.RuneWidth(r)
- _runeWidths[r] = w
- return w
- }
-}
-
-func displayWidth(runes []rune) int {
+func (t *Terminal) displayWidth(runes []rune) int {
l := 0
for _, r := range runes {
- l += runeWidth(r, l)
+ l += util.RuneWidth(r, l, t.tabstop)
}
return l
}
@@ -437,9 +450,10 @@ func calculateSize(base int, size sizeSpec, margin int, minSize int) int {
}
func (t *Terminal) resizeWindows() {
- screenWidth := tui.MaxX()
- screenHeight := tui.MaxY()
+ screenWidth := t.tui.MaxX()
+ screenHeight := t.tui.MaxY()
marginInt := [4]int{}
+ t.prevLines = make([]itemLine, screenHeight)
for idx, sizeSpec := range t.margin {
if sizeSpec.percent {
var max float64
@@ -487,40 +501,40 @@ func (t *Terminal) resizeWindows() {
height := screenHeight - marginInt[0] - marginInt[2]
if t.isPreviewEnabled() {
createPreviewWindow := func(y int, x int, w int, h int) {
- t.bwindow = tui.NewWindow(y, x, w, h, true)
+ t.bwindow = t.tui.NewWindow(y, x, w, h, true)
pwidth := w - 4
// ncurses auto-wraps the line when the cursor reaches the right-end of
// the window. To prevent unintended line-wraps, we use the width one
// column larger than the desired value.
- if !t.preview.wrap && tui.DoesAutoWrap() {
+ if !t.preview.wrap && t.tui.DoesAutoWrap() {
pwidth += 1
}
- t.pwindow = tui.NewWindow(y+1, x+2, pwidth, h-2, false)
+ t.pwindow = t.tui.NewWindow(y+1, x+2, pwidth, h-2, false)
}
switch t.preview.position {
case posUp:
pheight := calculateSize(height, t.preview.size, minHeight, 3)
- t.window = tui.NewWindow(
+ t.window = t.tui.NewWindow(
marginInt[0]+pheight, marginInt[3], width, height-pheight, false)
createPreviewWindow(marginInt[0], marginInt[3], width, pheight)
case posDown:
pheight := calculateSize(height, t.preview.size, minHeight, 3)
- t.window = tui.NewWindow(
+ t.window = t.tui.NewWindow(
marginInt[0], marginInt[3], width, height-pheight, false)
createPreviewWindow(marginInt[0]+height-pheight, marginInt[3], width, pheight)
case posLeft:
pwidth := calculateSize(width, t.preview.size, minWidth, 5)
- t.window = tui.NewWindow(
+ t.window = t.tui.NewWindow(
marginInt[0], marginInt[3]+pwidth, width-pwidth, height, false)
createPreviewWindow(marginInt[0], marginInt[3], pwidth, height)
case posRight:
pwidth := calculateSize(width, t.preview.size, minWidth, 5)
- t.window = tui.NewWindow(
+ t.window = t.tui.NewWindow(
marginInt[0], marginInt[3], width-pwidth, height, false)
createPreviewWindow(marginInt[0], marginInt[3]+width-pwidth, pwidth, height)
}
} else {
- t.window = tui.NewWindow(
+ t.window = t.tui.NewWindow(
marginInt[0],
marginInt[3],