mirror of
https://github.com/junegunn/fzf.git
synced 2025-11-15 23:03:47 -05:00
Style change: thinner gutter column (#4521)
This commit is contained in:
26
CHANGELOG.md
26
CHANGELOG.md
@@ -1,6 +1,32 @@
|
|||||||
CHANGELOG
|
CHANGELOG
|
||||||
=========
|
=========
|
||||||
|
|
||||||
|
0.66.0
|
||||||
|
------
|
||||||
|
- Style changes
|
||||||
|
- Narrowed the gutter column by using the left-half block character (`▌`).
|
||||||
|
- Removed background colors from markers.
|
||||||
|
- Added `--gutter CHAR` option for customizing the gutter column. Some examples using [box-drawing characters](https://en.wikipedia.org/wiki/Box-drawing_characters):
|
||||||
|
```sh
|
||||||
|
# Right-aligned gutter
|
||||||
|
fzf --gutter '▐'
|
||||||
|
|
||||||
|
# Even thinner gutter
|
||||||
|
fzf --gutter '▎'
|
||||||
|
|
||||||
|
# Checker
|
||||||
|
fzf --gutter '▚'
|
||||||
|
|
||||||
|
# Dotted
|
||||||
|
fzf --gutter '▖'
|
||||||
|
|
||||||
|
# Full-width
|
||||||
|
fzf --gutter '█'
|
||||||
|
|
||||||
|
# No gutter
|
||||||
|
fzf --gutter ' '
|
||||||
|
```
|
||||||
|
|
||||||
0.65.2
|
0.65.2
|
||||||
------
|
------
|
||||||
- Bug fixes and improvements
|
- Bug fixes and improvements
|
||||||
|
|||||||
@@ -590,6 +590,7 @@ type Options struct {
|
|||||||
Separator *string
|
Separator *string
|
||||||
JumpLabels string
|
JumpLabels string
|
||||||
Prompt string
|
Prompt string
|
||||||
|
Gutter *string
|
||||||
Pointer *string
|
Pointer *string
|
||||||
Marker *string
|
Marker *string
|
||||||
MarkerMulti *[3]string
|
MarkerMulti *[3]string
|
||||||
@@ -710,6 +711,7 @@ func defaultOptions() *Options {
|
|||||||
Separator: nil,
|
Separator: nil,
|
||||||
JumpLabels: defaultJumpLabels,
|
JumpLabels: defaultJumpLabels,
|
||||||
Prompt: "> ",
|
Prompt: "> ",
|
||||||
|
Gutter: nil,
|
||||||
Pointer: nil,
|
Pointer: nil,
|
||||||
Marker: nil,
|
Marker: nil,
|
||||||
MarkerMulti: nil,
|
MarkerMulti: nil,
|
||||||
@@ -2857,6 +2859,13 @@ func parseOptions(index *int, opts *Options, allArgs []string) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
case "--gutter":
|
||||||
|
str, err := nextString("gutter character required")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
str = firstLine(str)
|
||||||
|
opts.Gutter = &str
|
||||||
case "--pointer":
|
case "--pointer":
|
||||||
str, err := nextString("pointer sign required")
|
str, err := nextString("pointer sign required")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -3355,22 +3364,28 @@ func applyPreset(opts *Options, preset string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateSign(sign string, signOptName string) error {
|
func validateSign(sign string, signOptName string, maxWidth int) error {
|
||||||
if uniseg.StringWidth(sign) > 2 {
|
if uniseg.StringWidth(sign) > maxWidth {
|
||||||
return fmt.Errorf("%v display width should be up to 2", signOptName)
|
return fmt.Errorf("%v display width should be up to %d", signOptName, maxWidth)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateOptions(opts *Options) error {
|
func validateOptions(opts *Options) error {
|
||||||
if opts.Pointer != nil {
|
if opts.Pointer != nil {
|
||||||
if err := validateSign(*opts.Pointer, "pointer"); err != nil {
|
if err := validateSign(*opts.Pointer, "pointer", 2); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if opts.Marker != nil {
|
if opts.Marker != nil {
|
||||||
if err := validateSign(*opts.Marker, "marker"); err != nil {
|
if err := validateSign(*opts.Marker, "marker", 2); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts.Gutter != nil {
|
||||||
|
if err := validateSign(*opts.Gutter, "gutter", 1); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -462,7 +462,7 @@ func TestValidateSign(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, testCase := range testCases {
|
for _, testCase := range testCases {
|
||||||
err := validateSign(testCase.inputSign, "")
|
err := validateSign(testCase.inputSign, "", 2)
|
||||||
if testCase.isValid && err != nil {
|
if testCase.isValid && err != nil {
|
||||||
t.Errorf("Input sign `%s` caused error", testCase.inputSign)
|
t.Errorf("Input sign `%s` caused error", testCase.inputSign)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -272,6 +272,7 @@ type Terminal struct {
|
|||||||
footerLabel labelPrinter
|
footerLabel labelPrinter
|
||||||
footerLabelLen int
|
footerLabelLen int
|
||||||
footerLabelOpts labelOpts
|
footerLabelOpts labelOpts
|
||||||
|
gutterReverse bool
|
||||||
pointer string
|
pointer string
|
||||||
pointerLen int
|
pointerLen int
|
||||||
pointerEmpty string
|
pointerEmpty string
|
||||||
@@ -1094,10 +1095,27 @@ func NewTerminal(opts *Options, eventBox *util.EventBox, executor *util.Executor
|
|||||||
// This should be called before accessing tui.Color*
|
// This should be called before accessing tui.Color*
|
||||||
tui.InitTheme(opts.Theme, renderer.DefaultTheme(), opts.Black, opts.InputBorderShape.Visible(), opts.HeaderBorderShape.Visible())
|
tui.InitTheme(opts.Theme, renderer.DefaultTheme(), opts.Black, opts.InputBorderShape.Visible(), opts.HeaderBorderShape.Visible())
|
||||||
|
|
||||||
|
// Gutter character
|
||||||
|
var gutterChar string
|
||||||
|
if opts.Gutter != nil {
|
||||||
|
gutterChar = *opts.Gutter
|
||||||
|
} else if t.unicode && !t.theme.Gutter.Color.IsDefault() {
|
||||||
|
gutterChar = "▌"
|
||||||
|
} else {
|
||||||
|
gutterChar = " "
|
||||||
|
t.gutterReverse = true
|
||||||
|
}
|
||||||
|
|
||||||
t.prompt, t.promptLen = t.parsePrompt(opts.Prompt)
|
t.prompt, t.promptLen = t.parsePrompt(opts.Prompt)
|
||||||
// Pre-calculated empty pointer and marker signs
|
// Pre-calculated empty pointer and marker signs
|
||||||
t.pointerEmpty = strings.Repeat(" ", t.pointerLen)
|
if t.pointerLen == 0 {
|
||||||
|
t.pointerEmpty = ""
|
||||||
|
} else {
|
||||||
|
t.pointerEmpty = gutterChar + strings.Repeat(" ", util.Max(0, t.pointerLen-1))
|
||||||
|
}
|
||||||
t.markerEmpty = strings.Repeat(" ", t.markerLen)
|
t.markerEmpty = strings.Repeat(" ", t.markerLen)
|
||||||
|
|
||||||
|
// Labels
|
||||||
t.listLabel, t.listLabelLen = t.ansiLabelPrinter(opts.ListLabel.label, &tui.ColListLabel, false)
|
t.listLabel, t.listLabelLen = t.ansiLabelPrinter(opts.ListLabel.label, &tui.ColListLabel, false)
|
||||||
t.borderLabel, t.borderLabelLen = t.ansiLabelPrinter(opts.BorderLabel.label, &tui.ColBorderLabel, false)
|
t.borderLabel, t.borderLabelLen = t.ansiLabelPrinter(opts.BorderLabel.label, &tui.ColBorderLabel, false)
|
||||||
t.previewLabel, t.previewLabelLen = t.ansiLabelPrinter(opts.PreviewLabel.label, &tui.ColPreviewLabel, false)
|
t.previewLabel, t.previewLabelLen = t.ansiLabelPrinter(opts.PreviewLabel.label, &tui.ColPreviewLabel, false)
|
||||||
@@ -3096,9 +3114,21 @@ func (t *Terminal) renderEmptyLine(line int, barRange [2]int) {
|
|||||||
t.renderBar(line, barRange)
|
t.renderBar(line, barRange)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *Terminal) gutter(current bool) {
|
||||||
|
var color tui.ColorPair
|
||||||
|
if current {
|
||||||
|
color = tui.ColCurrentCursorEmpty
|
||||||
|
} else if t.gutterReverse {
|
||||||
|
color = tui.ColCursorEmpty
|
||||||
|
} else {
|
||||||
|
color = tui.ColCursorEmptyChar
|
||||||
|
}
|
||||||
|
t.window.CPrint(color, t.pointerEmpty)
|
||||||
|
}
|
||||||
|
|
||||||
func (t *Terminal) renderGapLine(line int, barRange [2]int, drawLine bool) {
|
func (t *Terminal) renderGapLine(line int, barRange [2]int, drawLine bool) {
|
||||||
t.move(line, 0, false)
|
t.move(line, 0, false)
|
||||||
t.window.CPrint(tui.ColCursorEmpty, t.pointerEmpty)
|
t.gutter(false)
|
||||||
t.window.Print(t.markerEmpty)
|
t.window.Print(t.markerEmpty)
|
||||||
x := t.pointerLen + t.markerLen
|
x := t.pointerLen + t.markerLen
|
||||||
|
|
||||||
@@ -3262,7 +3292,7 @@ func (t *Terminal) printItem(result Result, line int, maxLine int, index int, cu
|
|||||||
return indentSize
|
return indentSize
|
||||||
}
|
}
|
||||||
if len(label) == 0 {
|
if len(label) == 0 {
|
||||||
t.window.CPrint(tui.ColCurrentCursorEmpty, t.pointerEmpty)
|
t.gutter(true)
|
||||||
} else {
|
} else {
|
||||||
t.window.CPrint(tui.ColCurrentCursor, label)
|
t.window.CPrint(tui.ColCurrentCursor, label)
|
||||||
}
|
}
|
||||||
@@ -3284,7 +3314,7 @@ func (t *Terminal) printItem(result Result, line int, maxLine int, index int, cu
|
|||||||
return indentSize
|
return indentSize
|
||||||
}
|
}
|
||||||
if len(label) == 0 {
|
if len(label) == 0 {
|
||||||
t.window.CPrint(tui.ColCursorEmpty, t.pointerEmpty)
|
t.gutter(false)
|
||||||
} else {
|
} else {
|
||||||
t.window.CPrint(tui.ColCursor, label)
|
t.window.CPrint(tui.ColCursor, label)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -776,6 +776,7 @@ var (
|
|||||||
ColMatch ColorPair
|
ColMatch ColorPair
|
||||||
ColCursor ColorPair
|
ColCursor ColorPair
|
||||||
ColCursorEmpty ColorPair
|
ColCursorEmpty ColorPair
|
||||||
|
ColCursorEmptyChar ColorPair
|
||||||
ColMarker ColorPair
|
ColMarker ColorPair
|
||||||
ColSelected ColorPair
|
ColSelected ColorPair
|
||||||
ColSelectedMatch ColorPair
|
ColSelectedMatch ColorPair
|
||||||
@@ -1168,10 +1169,11 @@ func initPalette(theme *ColorTheme) {
|
|||||||
ColSelectedMatch = pair(theme.SelectedMatch, theme.SelectedBg)
|
ColSelectedMatch = pair(theme.SelectedMatch, theme.SelectedBg)
|
||||||
ColCursor = pair(theme.Cursor, theme.Gutter)
|
ColCursor = pair(theme.Cursor, theme.Gutter)
|
||||||
ColCursorEmpty = pair(blank, theme.Gutter)
|
ColCursorEmpty = pair(blank, theme.Gutter)
|
||||||
|
ColCursorEmptyChar = pair(theme.Gutter, theme.ListBg)
|
||||||
if theme.SelectedBg.Color != theme.ListBg.Color {
|
if theme.SelectedBg.Color != theme.ListBg.Color {
|
||||||
ColMarker = pair(theme.Marker, theme.SelectedBg)
|
ColMarker = pair(theme.Marker, theme.SelectedBg)
|
||||||
} else {
|
} else {
|
||||||
ColMarker = pair(theme.Marker, theme.Gutter)
|
ColMarker = pair(theme.Marker, theme.ListBg)
|
||||||
}
|
}
|
||||||
ColCurrent = pair(theme.Current, theme.DarkBg)
|
ColCurrent = pair(theme.Current, theme.DarkBg)
|
||||||
ColCurrentMatch = pair(theme.CurrentMatch, theme.DarkBg)
|
ColCurrentMatch = pair(theme.CurrentMatch, theme.DarkBg)
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ DEFAULT_TIMEOUT = 10
|
|||||||
FILE = File.expand_path(__FILE__)
|
FILE = File.expand_path(__FILE__)
|
||||||
BASE = File.expand_path('../..', __dir__)
|
BASE = File.expand_path('../..', __dir__)
|
||||||
Dir.chdir(BASE)
|
Dir.chdir(BASE)
|
||||||
FZF = "FZF_DEFAULT_OPTS=\"--no-scrollbar --pointer \\> --marker \\>\" FZF_DEFAULT_COMMAND= #{BASE}/bin/fzf".freeze
|
FZF = %[FZF_DEFAULT_OPTS="--no-scrollbar --gutter ' ' --pointer '>' --marker '>'" FZF_DEFAULT_COMMAND= #{BASE}/bin/fzf].freeze
|
||||||
|
|
||||||
def wait(timeout = DEFAULT_TIMEOUT)
|
def wait(timeout = DEFAULT_TIMEOUT)
|
||||||
since = Time.now
|
since = Time.now
|
||||||
|
|||||||
@@ -403,7 +403,7 @@ class TestExec < TestInteractive
|
|||||||
end
|
end
|
||||||
|
|
||||||
def test_become
|
def test_become
|
||||||
tmux.send_keys "seq 100 | #{FZF} --bind 'enter:become:seq {} | #{FZF}'", :Enter
|
tmux.send_keys "seq 100 | fzf --bind 'enter:become:seq {} | fzf'", :Enter
|
||||||
tmux.until { |lines| assert_equal 100, lines.match_count }
|
tmux.until { |lines| assert_equal 100, lines.match_count }
|
||||||
tmux.send_keys 999
|
tmux.send_keys 999
|
||||||
tmux.until { |lines| assert_equal 0, lines.match_count }
|
tmux.until { |lines| assert_equal 0, lines.match_count }
|
||||||
|
|||||||
@@ -178,8 +178,8 @@ class TestLayout < TestInteractive
|
|||||||
tmux.send_keys 'seq 3 | fzf --height ~100% --info=inline --border rounded', :Enter
|
tmux.send_keys 'seq 3 | fzf --height ~100% --info=inline --border rounded', :Enter
|
||||||
expected = <<~OUTPUT
|
expected = <<~OUTPUT
|
||||||
╭──────────
|
╭──────────
|
||||||
│ 3
|
│ ▌ 3
|
||||||
│ 2
|
│ ▌ 2
|
||||||
│ > 1
|
│ > 1
|
||||||
│ > < 3/3
|
│ > < 3/3
|
||||||
╰──────────
|
╰──────────
|
||||||
@@ -197,8 +197,8 @@ class TestLayout < TestInteractive
|
|||||||
│ │
|
│ │
|
||||||
│ │
|
│ │
|
||||||
│ ╰────────
|
│ ╰────────
|
||||||
│ 3
|
│ ▌ 3
|
||||||
│ 2
|
│ ▌ 2
|
||||||
│ > 1
|
│ > 1
|
||||||
│ > < 3/3
|
│ > < 3/3
|
||||||
╰──────────
|
╰──────────
|
||||||
@@ -247,7 +247,7 @@ class TestLayout < TestInteractive
|
|||||||
tmux.send_keys 'seq 100 | fzf --height ~5 --info=inline --border rounded', :Enter
|
tmux.send_keys 'seq 100 | fzf --height ~5 --info=inline --border rounded', :Enter
|
||||||
expected = <<~OUTPUT
|
expected = <<~OUTPUT
|
||||||
╭──────────────
|
╭──────────────
|
||||||
│ 2
|
│ ▌ 2
|
||||||
│ > 1
|
│ > 1
|
||||||
│ > < 100/100
|
│ > < 100/100
|
||||||
╰──────────────
|
╰──────────────
|
||||||
@@ -275,12 +275,12 @@ class TestLayout < TestInteractive
|
|||||||
def test_fzf_multi_line
|
def test_fzf_multi_line
|
||||||
tmux.send_keys %[(echo -en '0\\0'; echo -en '1\\n2\\0'; seq 1000) | fzf --read0 --multi --bind load:select-all --border rounded], :Enter
|
tmux.send_keys %[(echo -en '0\\0'; echo -en '1\\n2\\0'; seq 1000) | fzf --read0 --multi --bind load:select-all --border rounded], :Enter
|
||||||
block = <<~BLOCK
|
block = <<~BLOCK
|
||||||
│ ┃998
|
│ ▌┃998
|
||||||
│ ┃999
|
│ ▌┃999
|
||||||
│ ┃1000
|
│ ▌┃1000
|
||||||
│ ╹
|
│ ▌╹
|
||||||
│ ╻1
|
│ ▌╻1
|
||||||
│ ╹2
|
│ ▌╹2
|
||||||
│ >>0
|
│ >>0
|
||||||
│ 3/3 (3)
|
│ 3/3 (3)
|
||||||
│ >
|
│ >
|
||||||
@@ -312,11 +312,11 @@ class TestLayout < TestInteractive
|
|||||||
│ >
|
│ >
|
||||||
│ 3/3 (3)
|
│ 3/3 (3)
|
||||||
│ >>0
|
│ >>0
|
||||||
│ ╻1
|
│ ▌╻1
|
||||||
│ ╹2
|
│ ▌╹2
|
||||||
│ ╻1
|
│ ▌╻1
|
||||||
│ ┃2
|
│ ▌┃2
|
||||||
│ ┃3
|
│ ▌┃3
|
||||||
BLOCK
|
BLOCK
|
||||||
tmux.until { assert_block(block, it) }
|
tmux.until { assert_block(block, it) }
|
||||||
end
|
end
|
||||||
@@ -1156,6 +1156,42 @@ class TestLayout < TestInteractive
|
|||||||
tmux.until { assert_block(block, it) }
|
tmux.until { assert_block(block, it) }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_gutter_default
|
||||||
|
tmux.send_keys %(seq 10 | fzf), :Enter
|
||||||
|
block = <<~BLOCK
|
||||||
|
▌ 3
|
||||||
|
▌ 2
|
||||||
|
> 1
|
||||||
|
10/10
|
||||||
|
>
|
||||||
|
BLOCK
|
||||||
|
tmux.until { assert_block(block, it) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_gutter_default_no_unicode
|
||||||
|
tmux.send_keys %(seq 10 | fzf --no-unicode), :Enter
|
||||||
|
block = <<~BLOCK
|
||||||
|
3
|
||||||
|
2
|
||||||
|
> 1
|
||||||
|
10/10
|
||||||
|
>
|
||||||
|
BLOCK
|
||||||
|
tmux.until { assert_block(block, it) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_gutter_custom
|
||||||
|
tmux.send_keys %(seq 10 | fzf --gutter x), :Enter
|
||||||
|
block = <<~BLOCK
|
||||||
|
x 3
|
||||||
|
x 2
|
||||||
|
> 1
|
||||||
|
10/10
|
||||||
|
>
|
||||||
|
BLOCK
|
||||||
|
tmux.until { assert_block(block, it) }
|
||||||
|
end
|
||||||
|
|
||||||
def test_combinations
|
def test_combinations
|
||||||
skip unless ENV['LONGTEST']
|
skip unless ENV['LONGTEST']
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user