Provide an option to reverse items only (#1267)

pull/1316/head
Akinori MUSHA 6 years ago committed by Junegunn Choi
parent 2c26f02f5c
commit daa1958f86

@ -223,8 +223,8 @@ cursor with `--height` option.
vim $(fzf --height 40%) vim $(fzf --height 40%)
``` ```
Also check out `--reverse` option if you prefer "top-down" layout instead of Also check out `--reverse` and `--layout` options if you prefer
the default "bottom-up" layout. "top-down" layout instead of the default "bottom-up" layout.
```sh ```sh
vim $(fzf --height 40% --reverse) vim $(fzf --height 40% --reverse)
@ -234,7 +234,7 @@ You can add these options to `$FZF_DEFAULT_OPTS` so that they're applied by
default. For example, default. For example,
```sh ```sh
export FZF_DEFAULT_OPTS='--height 40% --reverse --border' export FZF_DEFAULT_OPTS='--height 40% --layout=reverse --border'
``` ```
#### Search syntax #### Search syntax
@ -272,7 +272,7 @@ or `py`.
- e.g. `export FZF_DEFAULT_COMMAND='fd --type f'` - e.g. `export FZF_DEFAULT_COMMAND='fd --type f'`
- `FZF_DEFAULT_OPTS` - `FZF_DEFAULT_OPTS`
- Default options - Default options
- e.g. `export FZF_DEFAULT_OPTS="--reverse --inline-info"` - e.g. `export FZF_DEFAULT_OPTS="--layout=reverse --inline-info"`
#### Options #### Options

@ -155,9 +155,22 @@ the full screen.
.BI "--min-height=" "HEIGHT" .BI "--min-height=" "HEIGHT"
Minimum height when \fB--height\fR is given in percent (default: 10). Minimum height when \fB--height\fR is given in percent (default: 10).
Ignored when \fB--height\fR is not specified. Ignored when \fB--height\fR is not specified.
.TP
.BI "--layout=" "LAYOUT"
Choose the layout (default: default)
.br
.BR default " Display from the bottom of the screen"
.br
.BR reverse " Display from the top of the screen"
.br
.BR reverse-list " Display from the top of the screen, prompt at the bottom"
.br
.TP .TP
.B "--reverse" .B "--reverse"
Reverse orientation A synonym for \fB--layout=reverse\fB
.TP .TP
.B "--border" .B "--border"
Draw border above and below the finder Draw border above and below the finder
@ -195,7 +208,7 @@ Input prompt (default: '> ')
.TP .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--reverse\fR option, and in the given order from top to bottom regardless of \fB--layout\fR option, and
are not affected by \fB--with-nth\fR. ANSI color codes are processed even when are not affected by \fB--with-nth\fR. ANSI color codes are processed even when
\fB--ansi\fR is not set. \fB--ansi\fR is not set.
.TP .TP
@ -543,8 +556,8 @@ triggered whenever the query string is changed.
\fBtoggle\fR (\fIright-click\fR) \fBtoggle\fR (\fIright-click\fR)
\fBtoggle-all\fR \fBtoggle-all\fR
\fBtoggle+down\fR \fIctrl-i (tab)\fR \fBtoggle+down\fR \fIctrl-i (tab)\fR
\fBtoggle-in\fR (\fB--reverse\fR ? \fBtoggle+up\fR : \fBtoggle+down\fR) \fBtoggle-in\fR (\fB--layout=reverse*\fR ? \fBtoggle+up\fR : \fBtoggle+down\fR)
\fBtoggle-out\fR (\fB--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

@ -53,7 +53,7 @@ const usage = `usage: fzf [options]
height instead of using fullscreen height instead of using fullscreen
--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)
--reverse Reverse orientation --layout=LAYOUT Choose layout: [default|reverse|reverse-list]
--border Draw border above and below the finder --border Draw border above and below the finder
--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)
--inline-info Display finder info inline with the query --inline-info Display finder info inline with the query
@ -90,7 +90,8 @@ const usage = `usage: fzf [options]
Environment variables Environment variables
FZF_DEFAULT_COMMAND Default command to use when input is tty FZF_DEFAULT_COMMAND Default command to use when input is tty
FZF_DEFAULT_OPTS Default options (e.g. '--reverse --inline-info') FZF_DEFAULT_OPTS Default options
(e.g. '--layout=reverse --inline-info')
` `
@ -132,6 +133,14 @@ const (
posRight posRight
) )
type layoutType int
const (
layoutDefault layoutType = iota
layoutReverse
layoutReverseList
)
type previewOpts struct { type previewOpts struct {
command string command string
position windowPosition position windowPosition
@ -161,7 +170,7 @@ type Options struct {
Bold bool Bold bool
Height sizeSpec Height sizeSpec
MinHeight int MinHeight int
Reverse bool Layout layoutType
Cycle bool Cycle bool
Hscroll bool Hscroll bool
HscrollOff int HscrollOff int
@ -211,7 +220,7 @@ func defaultOptions() *Options {
Black: false, Black: false,
Bold: true, Bold: true,
MinHeight: 10, MinHeight: 10,
Reverse: false, Layout: layoutDefault,
Cycle: false, Cycle: false,
Hscroll: true, Hscroll: true,
HscrollOff: 10, HscrollOff: 10,
@ -857,6 +866,20 @@ func parseHeight(str string) sizeSpec {
return size return size
} }
func parseLayout(str string) layoutType {
switch str {
case "default":
return layoutDefault
case "reverse":
return layoutReverse
case "reverse-list":
return layoutReverseList
default:
errorExit("invalid layout (expected: default / reverse / reverse-list)")
}
return layoutDefault
}
func parsePreviewWindow(opts *previewOpts, input string) { func parsePreviewWindow(opts *previewOpts, input string) {
// Default // Default
opts.position = posRight opts.position = posRight
@ -1037,10 +1060,13 @@ func parseOptions(opts *Options, allArgs []string) {
opts.Bold = true opts.Bold = true
case "--no-bold": case "--no-bold":
opts.Bold = false opts.Bold = false
case "--layout":
opts.Layout = parseLayout(
nextString(allArgs, &i, "layout required (default / reverse / reverse-list)"))
case "--reverse": case "--reverse":
opts.Reverse = true opts.Layout = layoutReverse
case "--no-reverse": case "--no-reverse":
opts.Reverse = false opts.Layout = layoutDefault
case "--cycle": case "--cycle":
opts.Cycle = true opts.Cycle = true
case "--no-cycle": case "--no-cycle":
@ -1156,6 +1182,8 @@ func parseOptions(opts *Options, allArgs []string) {
opts.Height = parseHeight(value) opts.Height = parseHeight(value)
} else if match, value := optString(arg, "--min-height="); match { } else if match, value := optString(arg, "--min-height="); match {
opts.MinHeight = atoi(value) opts.MinHeight = atoi(value)
} else if match, value := optString(arg, "--layout="); match {
opts.Layout = parseLayout(value)
} else if match, value := optString(arg, "--toggle-sort="); match { } else if match, value := optString(arg, "--toggle-sort="); match {
parseToggleSort(opts.Keymap, value) parseToggleSort(opts.Keymap, value)
} else if match, value := optString(arg, "--expect="); match { } else if match, value := optString(arg, "--expect="); match {

@ -59,7 +59,7 @@ type Terminal struct {
inlineInfo bool inlineInfo bool
prompt string prompt string
promptLen int promptLen int
reverse bool layout layoutType
fullscreen bool fullscreen bool
hscroll bool hscroll bool
hscrollOff int hscrollOff int
@ -302,10 +302,11 @@ func trimQuery(query string) []rune {
func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal { func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
input := trimQuery(opts.Query) input := trimQuery(opts.Query)
var header []string var header []string
if opts.Reverse { switch opts.Layout {
header = opts.Header case layoutDefault, layoutReverseList:
} else {
header = reverseStringArray(opts.Header) header = reverseStringArray(opts.Header)
default:
header = opts.Header
} }
var delay time.Duration var delay time.Duration
if opts.Tac { if opts.Tac {
@ -363,7 +364,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
t := Terminal{ t := Terminal{
initDelay: delay, initDelay: delay,
inlineInfo: opts.InlineInfo, inlineInfo: opts.InlineInfo,
reverse: opts.Reverse, layout: opts.Layout,
fullscreen: fullscreen, fullscreen: fullscreen,
hscroll: opts.Hscroll, hscroll: opts.Hscroll,
hscrollOff: opts.HscrollOff, hscrollOff: opts.HscrollOff,
@ -643,8 +644,21 @@ func (t *Terminal) resizeWindows() {
} }
func (t *Terminal) move(y int, x int, clear bool) { func (t *Terminal) move(y int, x int, clear bool) {
if !t.reverse { h := t.window.Height()
y = t.window.Height() - y - 1
switch t.layout {
case layoutDefault:
y = h - y - 1
case layoutReverseList:
n := 2 + len(t.header)
if t.inlineInfo {
n--
}
if y < n {
y = h - y - 1
} else {
y -= n
}
} }
if clear { if clear {
@ -748,7 +762,7 @@ func (t *Terminal) printList() {
count := t.merger.Length() - t.offset count := t.merger.Length() - t.offset
for j := 0; j < maxy; j++ { for j := 0; j < maxy; j++ {
i := j i := j
if !t.reverse { if t.layout == layoutDefault {
i = maxy - 1 - j i = maxy - 1 - j
} }
line := i + 2 + len(t.header) line := i + 2 + len(t.header)
@ -1680,12 +1694,12 @@ func (t *Terminal) Loop() {
req(reqList, reqInfo) req(reqList, reqInfo)
} }
case actToggleIn: case actToggleIn:
if t.reverse { if t.layout != layoutDefault {
return doAction(action{t: actToggleUp}, mapkey) return doAction(action{t: actToggleUp}, mapkey)
} }
return doAction(action{t: actToggleDown}, mapkey) return doAction(action{t: actToggleDown}, mapkey)
case actToggleOut: case actToggleOut:
if t.reverse { if t.layout != layoutDefault {
return doAction(action{t: actToggleDown}, mapkey) return doAction(action{t: actToggleDown}, mapkey)
} }
return doAction(action{t: actToggleUp}, mapkey) return doAction(action{t: actToggleUp}, mapkey)
@ -1813,13 +1827,21 @@ func (t *Terminal) Loop() {
mx -= t.window.Left() mx -= t.window.Left()
my -= t.window.Top() my -= t.window.Top()
mx = util.Constrain(mx-t.promptLen, 0, len(t.input)) mx = util.Constrain(mx-t.promptLen, 0, len(t.input))
if !t.reverse {
my = t.window.Height() - my - 1
}
min := 2 + len(t.header) min := 2 + len(t.header)
if t.inlineInfo { if t.inlineInfo {
min-- min--
} }
h := t.window.Height()
switch t.layout {
case layoutDefault:
my = h - my - 1
case layoutReverseList:
if my < h-min {
my += min
} else {
my = h - my - 1
}
}
if me.Double { if me.Double {
// Double-click // Double-click
if my >= min { if my >= min {
@ -1912,7 +1934,7 @@ func (t *Terminal) constrain() {
} }
func (t *Terminal) vmove(o int, allowCycle bool) { func (t *Terminal) vmove(o int, allowCycle bool) {
if t.reverse { if t.layout != layoutDefault {
o *= -1 o *= -1
} }
dest := t.cy + o dest := t.cy + o

@ -1060,6 +1060,21 @@ class TestGoFZF < TestBase
assert_equal '50', readonce.chomp assert_equal '50', readonce.chomp
end end
def test_header_lines_reverse_list
tmux.send_keys "seq 100 | #{fzf '--header-lines=10 -q 5 --layout=reverse-list'}", :Enter
2.times do
tmux.until do |lines|
lines[0] == '> 50' &&
lines[-4] == ' 2' &&
lines[-3] == ' 1' &&
lines[-2].include?('/90')
end
tmux.send_keys :Up
end
tmux.send_keys :Enter
assert_equal '50', readonce.chomp
end
def test_header_lines_overflow def test_header_lines_overflow
tmux.send_keys "seq 100 | #{fzf '--header-lines=200'}", :Enter tmux.send_keys "seq 100 | #{fzf '--header-lines=200'}", :Enter
tmux.until do |lines| tmux.until do |lines|
@ -1087,7 +1102,8 @@ class TestGoFZF < TestBase
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') &&
lines[-7..-3].map(&:strip) == header lines[-7..-3].map(&:strip) == header &&
lines[-8] == '> 1'
end end
end end
@ -1096,7 +1112,18 @@ class TestGoFZF < TestBase
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') &&
lines[2..6].map(&:strip) == header lines[2..6].map(&:strip) == header &&
lines[7] == '> 1'
end
end
def test_header_reverse_list
tmux.send_keys "seq 100 | #{fzf "--header=\\\"\\$(head -5 #{FILE})\\\" --layout=reverse-list"}", :Enter
header = File.readlines(FILE).take(5).map(&:strip)
tmux.until do |lines|
lines[-2].include?('100/100') &&
lines[-7..-3].map(&:strip) == header &&
lines[0] == '> 1'
end end
end end
@ -1120,6 +1147,16 @@ class TestGoFZF < TestBase
end end
end end
def test_header_and_header_lines_reverse_list
tmux.send_keys "seq 100 | #{fzf "--layout=reverse-list --header-lines 10 --header \\\"\\$(head -5 #{FILE})\\\""}", :Enter
header = File.readlines(FILE).take(5).map(&:strip)
tmux.until do |lines|
lines[-2].include?('90/90') &&
lines[-7...-2].map(&:strip) == header &&
lines[-17...-7].map(&:strip) == (1..10).map(&:to_s).reverse
end
end
def test_cancel def test_cancel
tmux.send_keys "seq 10 | #{fzf '--bind 2:cancel'}", :Enter tmux.send_keys "seq 10 | #{fzf '--bind 2:cancel'}", :Enter
tmux.until { |lines| lines[-2].include?('10/10') } tmux.until { |lines| lines[-2].include?('10/10') }
@ -1145,6 +1182,12 @@ class TestGoFZF < TestBase
tmux.send_keys :Enter tmux.send_keys :Enter
end end
def test_margin_reverse_list
tmux.send_keys "yes | head -1000 | #{fzf '--margin 5,3 --layout=reverse-list'}", :Enter
tmux.until { |lines| lines[4] == '' && lines[5] == ' > y' }
tmux.send_keys :Enter
end
def test_tabstop def test_tabstop
writelines tempname, ["f\too\tba\tr\tbaz\tbarfooq\tux"] writelines tempname, ["f\too\tba\tr\tbaz\tbarfooq\tux"]
{ {

Loading…
Cancel
Save