From 02cee2234dc85af7198f707072fd60a156fc4035 Mon Sep 17 00:00:00 2001 From: Junegunn Choi Date: Tue, 2 Nov 2021 21:18:29 +0900 Subject: [PATCH] Implement --scroll-off=LINES Close #2533 --- man/man1/fzf.1 | 8 ++++++-- src/options.go | 14 +++++++++++++- src/terminal.go | 21 ++++++++++++++++++++- test/test_go.rb | 33 +++++++++++++++++++++++++++++++++ 4 files changed, 72 insertions(+), 4 deletions(-) diff --git a/man/man1/fzf.1 b/man/man1/fzf.1 index 1cee9d9..975018b 100644 --- a/man/man1/fzf.1 +++ b/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 "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 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 the query string is empty. .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" Disable horizontal scroll .TP -.BI "--hscroll-off=" "COL" +.BI "--hscroll-off=" "COLS" 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 on the center of the screen. diff --git a/src/options.go b/src/options.go index 385eabc..daa4fe0 100644 --- a/src/options.go +++ b/src/options.go @@ -44,8 +44,10 @@ const usage = `usage: fzf [options] --bind=KEYBINDS Custom key bindings. Refer to the man page. --cycle Enable cyclic scroll --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 - --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) --filepath-word Make word-wise movements respect path separators --jump-labels=CHARS Label characters for jump and jump-accept @@ -200,6 +202,7 @@ type Options struct { KeepRight bool Hscroll bool HscrollOff int + ScrollOff int FileWord bool InfoStyle infoStyle JumpLabels string @@ -261,6 +264,7 @@ func defaultOptions() *Options { KeepRight: false, Hscroll: true, HscrollOff: 10, + ScrollOff: 0, FileWord: false, InfoStyle: infoDefault, JumpLabels: defaultJumpLabels, @@ -1354,6 +1358,8 @@ func parseOptions(opts *Options, allArgs []string) { opts.Hscroll = false case "--hscroll-off": opts.HscrollOff = nextInt(allArgs, &i, "hscroll offset required") + case "--scroll-off": + opts.ScrollOff = nextInt(allArgs, &i, "scroll offset required") case "--filepath-word": opts.FileWord = true case "--no-filepath-word": @@ -1530,6 +1536,8 @@ func parseOptions(opts *Options, allArgs []string) { opts.Tabstop = atoi(value) } else if match, value := optString(arg, "--hscroll-off="); match { 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 { opts.JumpLabels = value validateJumpLabels = true @@ -1547,6 +1555,10 @@ func parseOptions(opts *Options, allArgs []string) { 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 { errorExit("tab stop must be a positive integer") } diff --git a/src/terminal.go b/src/terminal.go index 07fa986..1af399a 100644 --- a/src/terminal.go +++ b/src/terminal.go @@ -121,6 +121,7 @@ type Terminal struct { keepRight bool hscroll bool hscrollOff int + scrollOff int wordRubout string wordNext string cx int @@ -502,6 +503,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal { keepRight: opts.KeepRight, hscroll: opts.Hscroll, hscrollOff: opts.HscrollOff, + scrollOff: opts.ScrollOff, wordRubout: wordRubout, wordNext: wordNext, cx: len(input), @@ -2749,9 +2751,26 @@ func (t *Terminal) constrain() { 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) 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) { diff --git a/test/test_go.rb b/test/test_go.rb index 2e33185..e82a85c 100755 --- a/test/test_go.rb +++ b/test/test_go.rb @@ -2076,6 +2076,39 @@ class TestGoFZF < TestBase tmux.send_keys 'C-t' tmux.until { |lines| assert_includes lines[1], '4' } 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 module TestShell