Implement --scroll-off=LINES

Close #2533
pull/2659/head
Junegunn Choi 3 years ago
parent e0dd2be3fb
commit 02cee2234d
No known key found for this signature in database
GPG Key ID: 254BC280FEF9C627

@ -21,7 +21,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE. THE SOFTWARE.
.. ..
.TH fzf 1 "Oct 2021" "fzf 0.27.3" "fzf - a command-line fuzzy finder" .TH fzf 1 "Nov 2021" "fzf 0.27.4" "fzf - a command-line fuzzy finder"
.SH NAME .SH NAME
fzf - a command-line fuzzy finder fzf - a command-line fuzzy finder
@ -135,10 +135,14 @@ Enable cyclic scroll
Keep the right end of the line visible when it's too long. Effective only when Keep the right end of the line visible when it's too long. Effective only when
the query string is empty. the query string is empty.
.TP .TP
.BI "--scroll-off=" "LINES"
Number of screen lines to keep above or below when scrolling to the top or to
the bottom (default: 0).
.TP
.B "--no-hscroll" .B "--no-hscroll"
Disable horizontal scroll Disable horizontal scroll
.TP .TP
.BI "--hscroll-off=" "COL" .BI "--hscroll-off=" "COLS"
Number of screen columns to keep to the right of the highlighted substring Number of screen columns to keep to the right of the highlighted substring
(default: 10). Setting it to a large value will cause the text to be positioned (default: 10). Setting it to a large value will cause the text to be positioned
on the center of the screen. on the center of the screen.

@ -44,8 +44,10 @@ const usage = `usage: fzf [options]
--bind=KEYBINDS Custom key bindings. Refer to the man page. --bind=KEYBINDS Custom key bindings. Refer to the man page.
--cycle Enable cyclic scroll --cycle Enable cyclic scroll
--keep-right Keep the right end of the line visible on overflow --keep-right Keep the right end of the line visible on overflow
--scroll-off=LINES Number of screen lines to keep above or below when
scrolling to the top or to the bottom (default: 0)
--no-hscroll Disable horizontal scroll --no-hscroll Disable horizontal scroll
--hscroll-off=COL Number of screen columns to keep to the right of the --hscroll-off=COLS Number of screen columns to keep to the right of the
highlighted substring (default: 10) highlighted substring (default: 10)
--filepath-word Make word-wise movements respect path separators --filepath-word Make word-wise movements respect path separators
--jump-labels=CHARS Label characters for jump and jump-accept --jump-labels=CHARS Label characters for jump and jump-accept
@ -200,6 +202,7 @@ type Options struct {
KeepRight bool KeepRight bool
Hscroll bool Hscroll bool
HscrollOff int HscrollOff int
ScrollOff int
FileWord bool FileWord bool
InfoStyle infoStyle InfoStyle infoStyle
JumpLabels string JumpLabels string
@ -261,6 +264,7 @@ func defaultOptions() *Options {
KeepRight: false, KeepRight: false,
Hscroll: true, Hscroll: true,
HscrollOff: 10, HscrollOff: 10,
ScrollOff: 0,
FileWord: false, FileWord: false,
InfoStyle: infoDefault, InfoStyle: infoDefault,
JumpLabels: defaultJumpLabels, JumpLabels: defaultJumpLabels,
@ -1354,6 +1358,8 @@ func parseOptions(opts *Options, allArgs []string) {
opts.Hscroll = false opts.Hscroll = false
case "--hscroll-off": case "--hscroll-off":
opts.HscrollOff = nextInt(allArgs, &i, "hscroll offset required") opts.HscrollOff = nextInt(allArgs, &i, "hscroll offset required")
case "--scroll-off":
opts.ScrollOff = nextInt(allArgs, &i, "scroll offset required")
case "--filepath-word": case "--filepath-word":
opts.FileWord = true opts.FileWord = true
case "--no-filepath-word": case "--no-filepath-word":
@ -1530,6 +1536,8 @@ func parseOptions(opts *Options, allArgs []string) {
opts.Tabstop = atoi(value) opts.Tabstop = atoi(value)
} else if match, value := optString(arg, "--hscroll-off="); match { } else if match, value := optString(arg, "--hscroll-off="); match {
opts.HscrollOff = atoi(value) opts.HscrollOff = atoi(value)
} else if match, value := optString(arg, "--scroll-off="); match {
opts.ScrollOff = atoi(value)
} else if match, value := optString(arg, "--jump-labels="); match { } else if match, value := optString(arg, "--jump-labels="); match {
opts.JumpLabels = value opts.JumpLabels = value
validateJumpLabels = true validateJumpLabels = true
@ -1547,6 +1555,10 @@ func parseOptions(opts *Options, allArgs []string) {
errorExit("hscroll offset must be a non-negative integer") errorExit("hscroll offset must be a non-negative integer")
} }
if opts.ScrollOff < 0 {
errorExit("scroll offset must be a non-negative integer")
}
if opts.Tabstop < 1 { if opts.Tabstop < 1 {
errorExit("tab stop must be a positive integer") errorExit("tab stop must be a positive integer")
} }

@ -121,6 +121,7 @@ type Terminal struct {
keepRight bool keepRight bool
hscroll bool hscroll bool
hscrollOff int hscrollOff int
scrollOff int
wordRubout string wordRubout string
wordNext string wordNext string
cx int cx int
@ -502,6 +503,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
keepRight: opts.KeepRight, keepRight: opts.KeepRight,
hscroll: opts.Hscroll, hscroll: opts.Hscroll,
hscrollOff: opts.HscrollOff, hscrollOff: opts.HscrollOff,
scrollOff: opts.ScrollOff,
wordRubout: wordRubout, wordRubout: wordRubout,
wordNext: wordNext, wordNext: wordNext,
cx: len(input), cx: len(input),
@ -2749,9 +2751,26 @@ func (t *Terminal) constrain() {
t.cy = util.Constrain(t.cy, 0, count-1) t.cy = util.Constrain(t.cy, 0, count-1)
minOffset := t.cy - height + 1 minOffset := util.Max(t.cy-height+1, 0)
maxOffset := util.Max(util.Min(count-height, t.cy), 0) maxOffset := util.Max(util.Min(count-height, t.cy), 0)
t.offset = util.Constrain(t.offset, minOffset, maxOffset) t.offset = util.Constrain(t.offset, minOffset, maxOffset)
if t.scrollOff == 0 {
return
}
scrollOff := util.Min(height/2, t.scrollOff)
for {
prevOffset := t.offset
if t.cy-t.offset < scrollOff {
t.offset = util.Max(minOffset, t.offset-1)
}
if t.cy-t.offset >= height-scrollOff {
t.offset = util.Min(maxOffset, t.offset+1)
}
if t.offset == prevOffset {
break
}
}
} }
func (t *Terminal) vmove(o int, allowCycle bool) { func (t *Terminal) vmove(o int, allowCycle bool) {

@ -2076,6 +2076,39 @@ class TestGoFZF < TestBase
tmux.send_keys 'C-t' tmux.send_keys 'C-t'
tmux.until { |lines| assert_includes lines[1], '4' } tmux.until { |lines| assert_includes lines[1], '4' }
end end
def test_scroll_off
tmux.send_keys "seq 1000 | #{FZF} --scroll-off=3 --bind l:last", :Enter
tmux.until { |lines| assert_equal 1000, lines.item_count }
height = tmux.until { |lines| lines }.first.to_i
tmux.send_keys :PgUp
tmux.until do |lines|
assert_equal height + 3, lines.first.to_i
assert_equal "> #{height}", lines[3].strip
end
tmux.send_keys :Up
tmux.until { |lines| assert_equal "> #{height + 1}", lines[3].strip }
tmux.send_keys 'l'
tmux.until { |lines| assert_equal '> 1000', lines.first.strip }
tmux.send_keys :PgDn
tmux.until { |lines| assert_equal "> #{1000 - height + 1}", lines.reverse[5].strip }
tmux.send_keys :Down
tmux.until { |lines| assert_equal "> #{1000 - height}", lines.reverse[5].strip }
end
def test_scroll_off_large
tmux.send_keys "seq 1000 | #{FZF} --scroll-off=9999", :Enter
tmux.until { |lines| assert_equal 1000, lines.item_count }
height = tmux.until { |lines| lines }.first.to_i
tmux.send_keys :PgUp
tmux.until { |lines| assert_equal "> #{height}", lines[height / 2].strip }
tmux.send_keys :Up
tmux.until { |lines| assert_equal "> #{height + 1}", lines[height / 2].strip }
tmux.send_keys :Up
tmux.until { |lines| assert_equal "> #{height + 2}", lines[height / 2].strip }
tmux.send_keys :Down
tmux.until { |lines| assert_equal "> #{height + 1}", lines[height / 2].strip }
end
end end
module TestShell module TestShell

Loading…
Cancel
Save