summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--README.md8
-rw-r--r--man/man1/fzf.121
-rw-r--r--src/options.go40
-rw-r--r--src/terminal.go50
-rwxr-xr-x[-rw-r--r--]test/test_go.rb47
5 files changed, 136 insertions, 30 deletions
diff --git a/README.md b/README.md
index e6a6dcaf..30578bac 100644
--- a/README.md
+++ b/README.md
@@ -223,8 +223,8 @@ cursor with `--height` option.
vim $(fzf --height 40%)
```
-Also check out `--reverse` option if you prefer "top-down" layout instead of
-the default "bottom-up" layout.
+Also check out `--reverse` and `--layout` options if you prefer
+"top-down" layout instead of the default "bottom-up" layout.
```sh
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,
```sh
-export FZF_DEFAULT_OPTS='--height 40% --reverse --border'
+export FZF_DEFAULT_OPTS='--height 40% --layout=reverse --border'
```
#### Search syntax
@@ -272,7 +272,7 @@ or `py`.
- e.g. `export FZF_DEFAULT_COMMAND='fd --type f'`
- `FZF_DEFAULT_OPTS`
- Default options
- - e.g. `export FZF_DEFAULT_OPTS="--reverse --inline-info"`
+ - e.g. `export FZF_DEFAULT_OPTS="--layout=reverse --inline-info"`
#### Options
diff --git a/man/man1/fzf.1 b/man/man1/fzf.1
index 5dd00e49..b113be41 100644
--- a/man/man1/fzf.1
+++ b/man/man1/fzf.1
@@ -156,8 +156,21 @@ the full screen.
Minimum height when \fB--height\fR is given in percent (default: 10).
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
.B "--reverse"
-Reverse orientation
+A synonym for \fB--layout=reverse\fB
+
.TP
.B "--border"
Draw border above and below the finder
@@ -195,7 +208,7 @@ Input prompt (default: '> ')
.TP
.BI "--header=" "STR"
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
\fB--ansi\fR is not set.
.TP
@@ -543,8 +556,8 @@ triggered whenever the query string is changed.
\fBtoggle\fR (\fIright-click\fR)
\fBtoggle-all\fR
\fBtoggle+down\fR \fIctrl-i (tab)\fR
- \fBtoggle-in\fR (\fB--reverse\fR ? \fBtoggle+up\fR : \fBtoggle+down\fR)
- \fBtoggle-out\fR (\fB--reverse\fR ? \fBtoggle+down\fR : \fBtoggle+up\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-sort\fR
diff --git a/src/options.go b/src/options.go
index 45eca5ef..fc32344c 100644
--- a/src/options.go
+++ b/src/options.go
@@ -53,7 +53,7 @@ const usage = `usage: fzf [options]
height instead of using fullscreen
--min-height=HEIGHT Minimum height when --height is given in percent
(default: 10)
- --reverse Reverse orientation
+ --layout=LAYOUT Choose layout: [default|reverse|reverse-list]
--border Draw border above and below the finder
--margin=MARGIN Screen margin (TRBL / TB,RL / T,RL,B / T,R,B,L)
--inline-info Display finder info inline with the query
@@ -90,7 +90,8 @@ const usage = `usage: fzf [options]
Environment variables
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
)
+type layoutType int
+
+const (
+ layoutDefault layoutType = iota
+ layoutReverse
+ layoutReverseList
+)
+
type previewOpts struct {
command string
position windowPosition
@@ -161,7 +170,7 @@ type Options struct {
Bold bool
Height sizeSpec
MinHeight int
- Reverse bool
+ Layout layoutType
Cycle bool
Hscroll bool
HscrollOff int
@@ -211,7 +220,7 @@ func defaultOptions() *Options {
Black: false,
Bold: true,
MinHeight: 10,
- Reverse: false,
+ Layout: layoutDefault,
Cycle: false,
Hscroll: true,
HscrollOff: 10,
@@ -857,6 +866,20 @@ func parseHeight(str string) sizeSpec {
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) {
// Default
opts.position = posRight
@@ -1037,10 +1060,13 @@ func parseOptions(opts *Options, allArgs []string) {
opts.Bold = true
case "--no-bold":
opts.Bold = false
+ case "--layout":
+ opts.Layout = parseLayout(
+ nextString(allArgs, &i, "layout required (default / reverse / reverse-list)"))
case "--reverse":
- opts.Reverse = true
+ opts.Layout = layoutReverse
case "--no-reverse":
- opts.Reverse = false
+ opts.Layout = layoutDefault
case "--cycle":
opts.Cycle = true
case "--no-cycle":
@@ -1156,6 +1182,8 @@ func parseOptions(opts *Options, allArgs []string) {
opts.Height = parseHeight(value)
} else if match, value := optString(arg, "--min-height="); match {
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 {
parseToggleSort(opts.Keymap, value)
} else if match, value := optString(arg, "--expect="); match {
diff --git a/src/terminal.go b/src/terminal.go
index 664b7d47..d4685042 100644
--- a/src/terminal.go
+++ b/src/terminal.go
@@ -59,7 +59,7 @@ type Terminal struct {
inlineInfo bool
prompt string
promptLen int
- reverse bool
+ layout layoutType
fullscreen bool
hscroll bool
hscrollOff int
@@ -302,10 +302,11 @@ func trimQuery(query string) []rune {
func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
input := trimQuery(opts.Query)
var header []string
- if opts.Reverse {
- header = opts.Header
- } else {
+ switch opts.Layout {
+ case layoutDefault, layoutReverseList:
header = reverseStringArray(opts.Header)
+ default:
+ header = opts.Header
}
var delay time.Duration
if opts.Tac {
@@ -363,7 +364,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
t := Terminal{
initDelay: delay,
inlineInfo: opts.InlineInfo,
- reverse: opts.Reverse,
+ layout: opts.Layout,
fullscreen: fullscreen,
hscroll: opts.Hscroll,
hscrollOff: opts.HscrollOff,
@@ -643,8 +644,21 @@ func (t *Terminal) resizeWindows() {
}
func (t *Terminal) move(y int, x int, clear bool) {
- if !t.reverse {
- y = t.window.Height() - y - 1
+ h := t.window.Height()
+
+ 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 {
@@ -748,7 +762,7 @@ func (t *Terminal) printList() {
count := t.merger.Length() - t.offset
for j := 0; j < maxy; j++ {
i := j
- if !t.reverse {
+ if t.layout == layoutDefault {
i = maxy - 1 - j
}
line := i + 2 + len(t.header)
@@ -1680,12 +1694,12 @@ func (t *Terminal) Loop() {
req(reqList, reqInfo)
}
case actToggleIn:
- if t.reverse {
+ if t.layout != layoutDefault {
return doAction(action{t: actToggleUp}, mapkey)
}
return doAction(action{t: actToggleDown}, mapkey)
case actToggleOut:
- if t.reverse {
+ if t.layout != layoutDefault {
return doAction(action{t: actToggleDown}, mapkey)
}
return doAction(action{t: actToggleUp}, mapkey)
@@ -1813,13 +1827,21 @@ func (t *Terminal) Loop() {
mx -= t.window.Left()
my -= t.window.Top()
mx = util.Constrain(mx-t.promptLen, 0, len(t.input))
- if !t.reverse {
- my = t.window.Height() - my - 1
- }
min := 2 + len(t.header)
if t.inlineInfo {
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 {
// Double-click
if my >= min {
@@ -1912,7 +1934,7 @@ func (t *Terminal) constrain() {
}
func (t *Terminal) vmove(o int, allowCycle bool) {
- if t.reverse {
+ if t.layout != layoutDefault {
o *= -1
}
dest := t.cy + o
diff --git a/test/test_go.rb b/test/test_go.rb
index 4f4f94e1..c7758068 100644..100755
--- a/test/test_go.rb
+++ b/test/test_go.rb
@@ -1060,6 +1060,21 @@ class TestGoFZF < TestBase
assert_equal '50', readonce.chomp
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
tmux.send_keys "seq 100 | #{fzf '--header-lines=200'}", :Enter
tmux.until do |lines|
@@ -1087,7 +1102,8 @@ class TestGoFZF < TestBase
header = File.readlines(FILE).take(5).map(&:strip)
tmux.until do |lines|
lines[-2].include?('100/100') &&
- lines[-7..-3].map(&:strip) == header
+ lines[-7..-3].map(&:strip) == header &&
+ lines[-8] == '> 1'
end
end
@@ -1096,7 +1112,18 @@ class TestGoFZF < TestBase
header = File.readlines(FILE).take(5).map(&:strip)
tmux.until do |lines|
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
@@ -1120,6 +1147,16 @@ class TestGoFZF < TestBase
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
tmux.send_keys "seq 10 | #{fzf '--bind 2:cancel'}", :Enter
tmux.until { |lines| lines[-2].include?('10/10') }
@@ -1145,6 +1182,12 @@ class TestGoFZF < TestBase
tmux.send_keys :Enter
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
writelines tempname, ["f\too\tba\tr\tbaz\tbarfooq\tux"]
{