diff --git a/CHANGELOG.md b/CHANGELOG.md index 3e30749b..75e873b2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -61,6 +61,16 @@ Also, fzf now offers "style presets" for quick customization, which can be activ - `transform-header-label` - Added `--preview-border[=STYLE]` as short for `--preview-window=border[-STYLE]` - Added new preview border style `line` which draws a single separator line between the preview window and the rest of the interface +- fzf will now render a dashed line (`┈┈`) in each `--gap` for better visual separation. + ```sh + # All bash/zsh functions, highlighted + declare -f | + perl -0 -pe 's/^}\n/}\0/gm' | + bat --plain --language bash --color always | + fzf --read0 --ansi --layout reverse --multi --highlight-line \ + --gap + ``` + * You can customize the line using `--gap-line[=STR]`. - You can specify `border-native` to `--tmux` so that native tmux border is used instead of `--border`. This can be useful if you start a different program from inside the popup. ```sh fzf --tmux border-native --bind 'enter:execute:less {}' diff --git a/src/options.go b/src/options.go index 3932cb56..470d3081 100644 --- a/src/options.go +++ b/src/options.go @@ -582,6 +582,7 @@ type Options struct { HeaderLines int HeaderFirst bool Gap int + GapLine *string Ellipsis *string Scrollbar *string Margin [4]sizeSpec @@ -2567,6 +2568,15 @@ func parseOptions(index *int, opts *Options, allArgs []string) error { } case "--no-gap": opts.Gap = 0 + case "--gap-line": + if given, bar := optionalNextString(); given { + opts.GapLine = &bar + } else { + opts.GapLine = nil + } + case "--no-gap-line": + empty := "" + opts.GapLine = &empty case "--ellipsis": str, err := nextString("ellipsis string required") if err != nil { @@ -2987,6 +2997,14 @@ func postProcessOptions(opts *Options) error { opts.Pointer = &defaultPointer } + if opts.GapLine == nil { + defaultGapLine := "┈" + if !opts.Unicode { + defaultGapLine = "-" + } + opts.GapLine = &defaultGapLine + } + markerLen := 1 if opts.Marker == nil { if opts.MarkerMulti != nil && opts.MarkerMulti[0] == "" { diff --git a/src/terminal.go b/src/terminal.go index d7ca89b6..2b86e010 100644 --- a/src/terminal.go +++ b/src/terminal.go @@ -267,6 +267,8 @@ type Terminal struct { hscrollOff int scrollOff int gap int + gapLine labelPrinter + gapLineLen int wordRubout string wordNext string cx int @@ -949,6 +951,11 @@ func NewTerminal(opts *Options, eventBox *util.EventBox, executor *util.Executor t.separator, t.separatorLen = t.ansiLabelPrinter(bar, &tui.ColSeparator, true) } + // Gap line + if t.gap > 0 && len(*opts.GapLine) > 0 { + t.gapLine, t.gapLineLen = t.ansiLabelPrinter(*opts.GapLine, &tui.ColListBorder, true) + } + if opts.Ellipsis != nil { t.ellipsis = *opts.Ellipsis } else if t.unicode { @@ -2446,9 +2453,7 @@ func (t *Terminal) canSpanMultiLines() bool { return t.multiLine || t.wrap || t.gap > 0 } -func (t *Terminal) renderEmptyLine(line int, barRange [2]int) { - t.move(line, 0, true) - t.markEmptyLine(line) +func (t *Terminal) renderBar(line int, barRange [2]int) { // If the screen is not filled with the list in non-multi-line mode, // scrollbar is not visible at all. But in multi-line mode, we may need // to redraw the scrollbar character at the end. @@ -2457,6 +2462,29 @@ func (t *Terminal) renderEmptyLine(line int, barRange [2]int) { } } +func (t *Terminal) renderEmptyLine(line int, barRange [2]int) { + t.move(line, 0, true) + t.markEmptyLine(line) + t.renderBar(line, barRange) +} + +func (t *Terminal) renderGapLine(line int, barRange [2]int, drawLine bool) { + t.move(line, 0, false) + t.window.CPrint(tui.ColCursorEmpty, t.pointerEmpty) + t.window.Print(t.markerEmpty) + x := t.pointerLen + t.markerLen + + width := t.window.Width() - x - 1 + if drawLine && t.gapLine != nil { + t.gapLine(t.window, width) + } else { + t.move(line, x, true) + } + t.markOtherLine(line) + t.renderBar(line, barRange) + t.prevLines[line].width = width +} + func (t *Terminal) printList() { t.constrain() barLength, barStart := t.getScrollbar() @@ -2629,7 +2657,7 @@ func (t *Terminal) printItem(result Result, line int, maxLine int, index int, cu } for i := 0; i < t.gap && finalLineNum < maxLine; i++ { finalLineNum++ - t.renderEmptyLine(finalLineNum, barRange) + t.renderGapLine(finalLineNum, barRange, i == t.gap-1) } return finalLineNum } diff --git a/test/test_go.rb b/test/test_go.rb index 6a7f2ce4..2b5e0a24 100755 --- a/test/test_go.rb +++ b/test/test_go.rb @@ -3423,28 +3423,28 @@ class TestGoFZF < TestBase │ > │ 100/100 ────── │ > 1 - │ + │ ┈┈┈┈┈┈┈┈┈┈┈┈┈┈ │ 2 - │ + │ ┈┈┈┈┈┈┈┈┈┈┈┈┈┈ │ 3 - │ + │ ┈┈┈┈┈┈┈┈┈┈┈┈┈┈ │ 4 BLOCK tmux.until { assert_block(block, _1) } end def test_gap_2 - tmux.send_keys %(seq 100 | #{FZF} --gap=2 --border --reverse), :Enter + tmux.send_keys %(seq 100 | #{FZF} --gap=2 --gap-line xyz --border --reverse), :Enter block = <<~BLOCK ╭───────────────── │ > │ 100/100 ────── │ > 1 │ - │ + │ xyzxyzxyzxyzxy │ 2 │ - │ + │ xyzxyzxyzxyzxy │ 3 BLOCK tmux.until { assert_block(block, _1) }