mirror of
https://github.com/Llewellynvdm/fzf.git
synced 2024-11-21 12:25:09 +00:00
Add --gap option to put empty lines between items
This commit is contained in:
parent
4161403a1d
commit
1a32220ca9
15
CHANGELOG.md
15
CHANGELOG.md
@ -1,6 +1,21 @@
|
||||
CHANGELOG
|
||||
=========
|
||||
|
||||
0.56.0
|
||||
------
|
||||
- Added `--gap[=N]` option to display empty lines between items.
|
||||
- This can be useful to visually separate adjacent multi-line items.
|
||||
```sh
|
||||
# All bash functions, highlighted
|
||||
declare -f | perl -0777 -pe 's/^}\n/}\0/gm' |
|
||||
bat --plain --language bash --color always |
|
||||
fzf --read0 --ansi --reverse --multi --highlight-line --gap
|
||||
```
|
||||
- Or just to make the list easier to read. For single-line items, you probably want to set `--color gutter:-1` as well to hide the gutter.
|
||||
```sh
|
||||
fzf --gap --color gutter:-1
|
||||
```
|
||||
|
||||
0.55.0
|
||||
------
|
||||
_Release highlights: https://junegunn.github.io/fzf/releases/0.55.0/_
|
||||
|
@ -208,6 +208,9 @@ Indicator for wrapped lines. The default is '↳ ' or '> ' depending on
|
||||
.B "\-\-no\-multi\-line"
|
||||
Disable multi-line display of items when using \fB\-\-read0\fR
|
||||
.TP
|
||||
.BI "\-\-gap" "[=N]"
|
||||
Render empty lines between each item
|
||||
.TP
|
||||
.B "\-\-keep\-right"
|
||||
Keep the right end of the line visible when it's too long. Effective only when
|
||||
the query string is empty.
|
||||
|
@ -56,6 +56,7 @@ Usage: fzf [options]
|
||||
--wrap Enable line wrap
|
||||
--wrap-sign=STR Indicator for wrapped lines
|
||||
--no-multi-line Disable multi-line display of items when using --read0
|
||||
--gap[=N] Render empty lines between each item
|
||||
--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)
|
||||
@ -473,6 +474,7 @@ type Options struct {
|
||||
Header []string
|
||||
HeaderLines int
|
||||
HeaderFirst bool
|
||||
Gap int
|
||||
Ellipsis *string
|
||||
Scrollbar *string
|
||||
Margin [4]sizeSpec
|
||||
@ -579,6 +581,7 @@ func defaultOptions() *Options {
|
||||
Header: make([]string, 0),
|
||||
HeaderLines: 0,
|
||||
HeaderFirst: false,
|
||||
Gap: 0,
|
||||
Ellipsis: nil,
|
||||
Scrollbar: nil,
|
||||
Margin: defaultMargin(),
|
||||
@ -2343,6 +2346,12 @@ func parseOptions(index *int, opts *Options, allArgs []string) error {
|
||||
opts.HeaderFirst = true
|
||||
case "--no-header-first":
|
||||
opts.HeaderFirst = false
|
||||
case "--gap":
|
||||
if opts.Gap, err = optionalNumeric(allArgs, &i, 1); err != nil {
|
||||
return err
|
||||
}
|
||||
case "--no-gap":
|
||||
opts.Gap = 0
|
||||
case "--ellipsis":
|
||||
str, err := nextString(allArgs, &i, "ellipsis string required")
|
||||
if err != nil {
|
||||
@ -2630,6 +2639,10 @@ func parseOptions(index *int, opts *Options, allArgs []string) error {
|
||||
if opts.HeaderLines, err = atoi(value); err != nil {
|
||||
return err
|
||||
}
|
||||
} else if match, value := optString(arg, "--gap="); match {
|
||||
if opts.Gap, err = atoi(value); err != nil {
|
||||
return err
|
||||
}
|
||||
} else if match, value := optString(arg, "--ellipsis="); match {
|
||||
str := firstLine(value)
|
||||
opts.Ellipsis = &str
|
||||
|
@ -245,6 +245,7 @@ type Terminal struct {
|
||||
hscroll bool
|
||||
hscrollOff int
|
||||
scrollOff int
|
||||
gap int
|
||||
wordRubout string
|
||||
wordNext string
|
||||
cx int
|
||||
@ -825,6 +826,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox, executor *util.Executor
|
||||
headerVisible: true,
|
||||
headerFirst: opts.HeaderFirst,
|
||||
headerLines: opts.HeaderLines,
|
||||
gap: opts.Gap,
|
||||
header: []string{},
|
||||
header0: opts.Header,
|
||||
ansi: opts.Ansi,
|
||||
@ -1136,15 +1138,23 @@ func (t *Terminal) wrapCols() int {
|
||||
return util.Max(t.window.Width()-(t.pointerLen+t.markerLen+1), 1)
|
||||
}
|
||||
|
||||
// Number of lines the item takes including the gap
|
||||
func (t *Terminal) numItemLines(item *Item, atMost int) (int, bool) {
|
||||
var numLines int
|
||||
if !t.wrap && !t.multiLine {
|
||||
return 1, false
|
||||
numLines = 1 + t.gap
|
||||
return numLines, numLines > atMost
|
||||
}
|
||||
var overflow bool
|
||||
if !t.wrap && t.multiLine {
|
||||
return item.text.NumLines(atMost)
|
||||
numLines, overflow = item.text.NumLines(atMost)
|
||||
} else {
|
||||
var lines [][]rune
|
||||
lines, overflow = item.text.Lines(t.multiLine, atMost, t.wrapCols(), t.wrapSignWidth, t.tabstop)
|
||||
numLines = len(lines)
|
||||
}
|
||||
lines, overflow := item.text.Lines(t.multiLine, atMost, t.wrapCols(), t.wrapSignWidth, t.tabstop)
|
||||
return len(lines), overflow
|
||||
numLines += t.gap
|
||||
return numLines, overflow || numLines > atMost
|
||||
}
|
||||
|
||||
func (t *Terminal) itemLines(item *Item, atMost int) ([][]rune, bool) {
|
||||
@ -2050,6 +2060,21 @@ func (t *Terminal) printHeader() {
|
||||
t.wrap = wrap
|
||||
}
|
||||
|
||||
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)
|
||||
// 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.
|
||||
if t.canSpanMultiLines() {
|
||||
t.prevLines[line].hasBar = t.printBar(line, true, barRange)
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Terminal) printList() {
|
||||
t.constrain()
|
||||
barLength, barStart := t.getScrollbar()
|
||||
@ -2070,14 +2095,7 @@ func (t *Terminal) printList() {
|
||||
item := t.merger.Get(itemCount + t.offset)
|
||||
line = t.printItem(item, line, maxy, itemCount, itemCount == t.cy-t.offset, barRange)
|
||||
} else if !t.prevLines[line].empty {
|
||||
t.move(line, 0, true)
|
||||
t.markEmptyLine(line)
|
||||
// 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.
|
||||
if t.multiLine || t.wrap {
|
||||
t.prevLines[line].hasBar = t.printBar(line, true, barRange)
|
||||
}
|
||||
t.renderEmptyLine(line, barRange)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2125,9 +2143,6 @@ func (t *Terminal) printItem(result Result, line int, maxLine int, index int, cu
|
||||
prevLine.queryLen == newLine.queryLen &&
|
||||
prevLine.result == newLine.result {
|
||||
t.prevLines[line].hasBar = printBar(line, false)
|
||||
if !t.multiLine && !t.wrap {
|
||||
return line
|
||||
}
|
||||
return line + numLines - 1
|
||||
}
|
||||
|
||||
@ -2214,6 +2229,10 @@ func (t *Terminal) printItem(result Result, line int, maxLine int, index int, cu
|
||||
}
|
||||
finalLineNum = t.printHighlighted(result, base, match, false, true, line, maxLine, forceRedraw, preTask, postTask)
|
||||
}
|
||||
for i := 0; i < t.gap && finalLineNum < maxLine; i++ {
|
||||
finalLineNum++
|
||||
t.renderEmptyLine(finalLineNum, barRange)
|
||||
}
|
||||
return finalLineNum
|
||||
}
|
||||
|
||||
@ -2275,7 +2294,7 @@ func (t *Terminal) printHighlighted(result Result, colBase tui.ColorPair, colMat
|
||||
allOffsets := result.colorOffsets(charOffsets, t.theme, colBase, colMatch, current)
|
||||
|
||||
maxLines := 1
|
||||
if t.multiLine || t.wrap {
|
||||
if t.canSpanMultiLines() {
|
||||
maxLines = maxLineNum - lineNum + 1
|
||||
}
|
||||
lines, overflow := t.itemLines(item, maxLines)
|
||||
@ -2285,7 +2304,7 @@ func (t *Terminal) printHighlighted(result Result, colBase tui.ColorPair, colMat
|
||||
topCutoff := false
|
||||
skipLines := 0
|
||||
wrapped := false
|
||||
if t.multiLine || t.wrap {
|
||||
if t.canSpanMultiLines() {
|
||||
// Cut off the upper lines in the 'default' layout
|
||||
if t.layout == layoutDefault && !current && maxLines == numItemLines && overflow {
|
||||
lines, _ = t.itemLines(item, math.MaxInt)
|
||||
@ -4875,7 +4894,7 @@ func (t *Terminal) constrain() {
|
||||
for tries := 0; tries < maxLines; tries++ {
|
||||
numItems := maxLines
|
||||
// How many items can be fit on screen including the current item?
|
||||
if (t.multiLine || t.wrap) && t.merger.Length() > 0 {
|
||||
if t.canSpanMultiLines() && t.merger.Length() > 0 {
|
||||
numItemsFound := 0
|
||||
linesSum := 0
|
||||
|
||||
@ -4930,12 +4949,12 @@ func (t *Terminal) constrain() {
|
||||
for {
|
||||
prevOffset := newOffset
|
||||
numItems := t.merger.Length()
|
||||
itemLines := 1
|
||||
if (t.multiLine || t.wrap) && t.cy < numItems {
|
||||
itemLines := 1 + t.gap
|
||||
if t.canSpanMultiLines() && t.cy < numItems {
|
||||
itemLines, _ = t.numItemLines(t.merger.Get(t.cy).item, maxLines)
|
||||
}
|
||||
linesBefore := t.cy - newOffset
|
||||
if t.multiLine || t.wrap {
|
||||
if t.canSpanMultiLines() {
|
||||
linesBefore = 0
|
||||
for i := newOffset; i < t.cy && i < numItems; i++ {
|
||||
lines, _ := t.numItemLines(t.merger.Get(i).item, maxLines-linesBefore-itemLines)
|
||||
|
@ -3392,6 +3392,40 @@ class TestGoFZF < TestBase
|
||||
assert lines[1]&.end_with?('1000││')
|
||||
end
|
||||
end
|
||||
|
||||
def test_gap
|
||||
tmux.send_keys %[seq 100 | #{FZF} --gap --border --reverse], :Enter
|
||||
block = <<~BLOCK
|
||||
╭─────────────────
|
||||
│ >
|
||||
│ 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
|
||||
block = <<~BLOCK
|
||||
╭─────────────────
|
||||
│ >
|
||||
│ 100/100 ──────
|
||||
│ > 1
|
||||
│
|
||||
│
|
||||
│ 2
|
||||
│
|
||||
│
|
||||
│ 3
|
||||
BLOCK
|
||||
tmux.until { assert_block(block, _1) }
|
||||
end
|
||||
end
|
||||
|
||||
module TestShell
|
||||
|
Loading…
Reference in New Issue
Block a user