Skip to content

Commit

Permalink
Add support for preview window header
Browse files Browse the repository at this point in the history
Fix junegunn#2373

  # Display top 3 lines as the fixed header
  fzf --preview 'bat --style=header,grid --color=always {}' --preview-window '~3'
  • Loading branch information
junegunn committed Mar 12, 2021
1 parent 7310370 commit 4c4c6e6
Show file tree
Hide file tree
Showing 5 changed files with 87 additions and 29 deletions.
9 changes: 8 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
CHANGELOG
=========

0.25.2
0.26.0
------
- Added support for fixed header in preview window
```sh
# Display top 3 lines as the fixed header
fzf --preview 'bat --style=header,grid --color=always {}' --preview-window '~3'
```
- Added `select` and `deselect` action for unconditionally selecting or
deselecting a single item in `--multi` mode. Complements `toggle` action.
- Sigificant performance improvement in ANSI code processing
- Bug fixes and improvements
- Built with Go 1.16

0.25.1
Expand Down
7 changes: 6 additions & 1 deletion man/man1/fzf.1
Original file line number Diff line number Diff line change
Expand Up @@ -442,7 +442,7 @@ e.g.
done'\fR
.RE
.TP
.BI "--preview-window=" "[POSITION][:SIZE[%]][:rounded|sharp|noborder][:[no]wrap][:[no]follow][:[no]cycle][:[no]hidden][:+SCROLL[-OFFSET]][:default]"
.BI "--preview-window=" "[POSITION][:SIZE[%]][:rounded|sharp|noborder][:[no]wrap][:[no]follow][:[no]cycle][:[no]hidden][:+SCROLL[-OFFSET]][:~HEADER_LINES][:default]"

.RS
.B POSITION: (default: right)
Expand Down Expand Up @@ -487,6 +487,9 @@ for adjusting the base offset so that you can see the text above it. It should
be given as a numeric integer (\fB-INTEGER\fR), or as a denominator form
(\fB-/INTEGER\fR) for specifying a fraction of the preview window height.

* \fB~HEADER_LINES\fR keeps the top N lines as the fixed header so that they
are always visible.

* \fBdefault\fR resets all options previously set to the default.

.RS
Expand All @@ -506,6 +509,8 @@ e.g.
--preview 'bat --style=numbers --color=always --highlight-line {2} {1}' \\
--preview-window +{2}-/2\fR

# Display top 3 lines as the fixed header
fzf --preview 'bat --style=header,grid --color=always {}' --preview-window '~3'
.RE

.SS Scripting
Expand Down
30 changes: 17 additions & 13 deletions src/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ const usage = `usage: fzf [options]
[up|down|left|right][:SIZE[%]]
[:[no]wrap][:[no]cycle][:[no]follow][:[no]hidden]
[:rounded|sharp|noborder]
[:+SCROLL[-OFFSET]]
[:+SCROLL[-OFFSET]][:~HEADER_LINES]
[:default]
Scripting
Expand Down Expand Up @@ -161,15 +161,16 @@ const (
)

type previewOpts struct {
command string
position windowPosition
size sizeSpec
scroll string
hidden bool
wrap bool
cycle bool
follow bool
border tui.BorderShape
command string
position windowPosition
size sizeSpec
scroll string
hidden bool
wrap bool
cycle bool
follow bool
border tui.BorderShape
headerLines int
}

// Options stores the values of command-line options
Expand Down Expand Up @@ -231,7 +232,7 @@ type Options struct {
}

func defaultPreviewOpts(command string) previewOpts {
return previewOpts{command, posRight, sizeSpec{50, true}, "", false, false, false, false, tui.BorderRounded}
return previewOpts{command, posRight, sizeSpec{50, true}, "", false, false, false, false, tui.BorderRounded, 0}
}

func defaultOptions() *Options {
Expand Down Expand Up @@ -1078,6 +1079,7 @@ func parsePreviewWindow(opts *previewOpts, input string) {
tokens := strings.Split(input, ":")
sizeRegex := regexp.MustCompile("^[0-9]+%?$")
offsetRegex := regexp.MustCompile("^\\+([0-9]+|{-?[0-9]+})(-[0-9]+|-/[1-9][0-9]*)?$")
headerRegex := regexp.MustCompile("^~(0|[1-9][0-9]*)$")
for _, token := range tokens {
switch token {
case "":
Expand Down Expand Up @@ -1114,7 +1116,9 @@ func parsePreviewWindow(opts *previewOpts, input string) {
case "nofollow":
opts.follow = false
default:
if sizeRegex.MatchString(token) {
if headerRegex.MatchString(token) {
opts.headerLines = atoi(token[1:])
} else if sizeRegex.MatchString(token) {
opts.size = parseSize(token, 99, "window size")
} else if offsetRegex.MatchString(token) {
opts.scroll = token[1:]
Expand Down Expand Up @@ -1364,7 +1368,7 @@ func parseOptions(opts *Options, allArgs []string) {
opts.Preview.command = ""
case "--preview-window":
parsePreviewWindow(&opts.Preview,
nextString(allArgs, &i, "preview window layout required: [up|down|left|right][:SIZE[%]][:rounded|sharp|noborder][:wrap][:cycle][:hidden][:+SCROLL[-OFFSET]][:default]"))
nextString(allArgs, &i, "preview window layout required: [up|down|left|right][:SIZE[%]][:rounded|sharp|noborder][:wrap][:cycle][:hidden][:+SCROLL[-OFFSET]][:~HEADER_LINES][:default]"))
case "--height":
opts.Height = parseHeight(nextString(allArgs, &i, "height required: HEIGHT[%]"))
case "--min-height":
Expand Down
44 changes: 30 additions & 14 deletions src/terminal.go
Original file line number Diff line number Diff line change
Expand Up @@ -1295,18 +1295,37 @@ func (t *Terminal) renderPreviewSpinner() {
}
}

func (t *Terminal) renderPreviewText(unchanged bool) {
maxWidth := t.pwindow.Width()
lineNo := -t.previewer.offset
height := t.pwindow.Height()
func (t *Terminal) renderPreviewArea(unchanged bool) {
if unchanged {
t.pwindow.MoveAndClear(0, 0)
t.pwindow.MoveAndClear(0, 0) // Clear scroll offset display
} else {
t.previewed.filled = false
t.pwindow.Erase()
}

height := t.pwindow.Height()
header := []string{}
body := t.previewer.lines
headerLines := t.previewOpts.headerLines
// Do not enable preview header lines if it's value is too large
if headerLines > 0 && headerLines < util.Min(len(body), height) {
header = t.previewer.lines[0:headerLines]
body = t.previewer.lines[headerLines:]
// Always redraw header
t.renderPreviewText(height, header, 0, false)
t.pwindow.MoveAndClear(t.pwindow.Y(), 0)
}
t.renderPreviewText(height, body, -t.previewer.offset+headerLines, unchanged)

if !unchanged {
t.pwindow.FinishFill()
}
}

func (t *Terminal) renderPreviewText(height int, lines []string, lineNo int, unchanged bool) {
maxWidth := t.pwindow.Width()
var ansi *ansiState
for _, line := range t.previewer.lines {
for _, line := range lines {
var lbg tui.Color = -1
if ansi != nil {
ansi.lbg = -1
Expand Down Expand Up @@ -1354,9 +1373,6 @@ func (t *Terminal) renderPreviewText(unchanged bool) {
}
lineNo++
}
if !unchanged {
t.pwindow.FinishFill()
}
}

func (t *Terminal) printPreview() {
Expand All @@ -1369,7 +1385,7 @@ func (t *Terminal) printPreview() {
t.previewer.version == t.previewed.version &&
t.previewer.offset == t.previewed.offset
t.previewer.scrollable = t.previewer.offset > 0 || numLines > height
t.renderPreviewText(unchanged)
t.renderPreviewArea(unchanged)
t.renderPreviewSpinner()
t.previewed.numLines = numLines
t.previewed.version = t.previewer.version
Expand All @@ -1382,7 +1398,7 @@ func (t *Terminal) printPreviewDelayed() {
}

t.previewer.scrollable = false
t.renderPreviewText(true)
t.renderPreviewArea(true)

message := t.trimMessage("Loading ..", t.pwindow.Width())
pos := t.pwindow.Width() - len(message)
Expand Down Expand Up @@ -1929,7 +1945,7 @@ func (t *Terminal) Loop() {
cmd := util.ExecCommand(command, true)
if pwindow != nil {
height := pwindow.Height()
initialOffset = util.Max(0, t.evaluateScrollOffset(items, height))
initialOffset = util.Max(0, t.evaluateScrollOffset(items, util.Max(0, height-t.previewOpts.headerLines)))
env := os.Environ()
lines := fmt.Sprintf("LINES=%d", height)
columns := fmt.Sprintf("COLUMNS=%d", pwindow.Width())
Expand Down Expand Up @@ -2132,7 +2148,7 @@ func (t *Terminal) Loop() {
if t.previewer.following {
t.previewer.offset = len(t.previewer.lines) - t.pwindow.Height()
} else if result.offset >= 0 {
t.previewer.offset = util.Constrain(result.offset, 0, len(t.previewer.lines)-1)
t.previewer.offset = util.Constrain(result.offset, t.previewOpts.headerLines, len(t.previewer.lines)-1)
}
t.printPreview()
case reqPreviewRefresh:
Expand Down Expand Up @@ -2205,7 +2221,7 @@ func (t *Terminal) Loop() {
if t.previewOpts.cycle {
newOffset = (newOffset + numLines) % numLines
}
newOffset = util.Constrain(newOffset, 0, numLines-1)
newOffset = util.Constrain(newOffset, t.previewOpts.headerLines, numLines-1)
if t.previewer.offset != newOffset {
t.previewer.offset = newOffset
req(reqPreviewRefresh)
Expand Down
26 changes: 26 additions & 0 deletions test/test_go.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2016,6 +2016,32 @@ def test_kill_reload_command_on_accept
nil
end
end

def test_preview_header
tmux.send_keys "seq 100 | #{FZF} --bind ctrl-k:preview-up+preview-up,ctrl-j:preview-down+preview-down+preview-down --preview 'seq 1000' --preview-window 'top:+{1}:~3'", :Enter
tmux.until { |lines| assert_equal 100, lines.item_count }
top5 = ->(lines) { lines.drop(1).take(5).map { |s| s[/[0-9]+/] } }
tmux.until do |lines|
assert_includes lines[1], '4/1000'
assert_equal(%w[1 2 3 4 5], top5[lines])
end
tmux.send_keys '55'
tmux.until do |lines|
assert_equal 1, lines.match_count
assert_equal(%w[1 2 3 55 56], top5[lines])
end
tmux.send_keys 'C-J'
tmux.until do |lines|
assert_equal(%w[1 2 3 58 59], top5[lines])
end
tmux.send_keys :BSpace
tmux.until do |lines|
assert_equal 19, lines.match_count
assert_equal(%w[1 2 3 5 6], top5[lines])
end
tmux.send_keys 'C-K'
tmux.until { |lines| assert_equal(%w[1 2 3 4 5], top5[lines]) }
end
end

module TestShell
Expand Down

0 comments on commit 4c4c6e6

Please sign in to comment.