summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJunegunn Choi <junegunn.c@gmail.com>2025-09-16 21:22:56 +0900
committerGitHub <noreply@github.com>2025-09-16 21:22:56 +0900
commita67aa85820c2e278e1c32fb8fdfe137523537ccb (patch)
tree05f9f818606d18fd34c56037f0033a4540d41082
parentc5cabe1691a0c486ae0ac733106aa1b843db2f59 (diff)
downloadfzf-a67aa85820c2e278e1c32fb8fdfe137523537ccb.tar.gz
Style change: thinner gutter column (#4521)
-rw-r--r--CHANGELOG.md26
-rw-r--r--src/options.go25
-rw-r--r--src/options_test.go2
-rw-r--r--src/terminal.go38
-rw-r--r--src/tui/tui.go4
-rw-r--r--test/lib/common.rb2
-rw-r--r--test/test_exec.rb2
-rw-r--r--test/test_layout.rb68
8 files changed, 138 insertions, 29 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5f2b30d9..228624a5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,32 @@
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
------
- Bug fixes and improvements
diff --git a/src/options.go b/src/options.go
index 76081ca6..dc6ac050 100644
--- a/src/options.go
+++ b/src/options.go
@@ -590,6 +590,7 @@ type Options struct {
Separator *string
JumpLabels string
Prompt string
+ Gutter *string
Pointer *string
Marker *string
MarkerMulti *[3]string
@@ -710,6 +711,7 @@ func defaultOptions() *Options {
Separator: nil,
JumpLabels: defaultJumpLabels,
Prompt: "> ",
+ Gutter: nil,
Pointer: nil,
Marker: nil,
MarkerMulti: nil,
@@ -2857,6 +2859,13 @@ func parseOptions(index *int, opts *Options, allArgs []string) error {
if err != nil {
return err
}
+ case "--gutter":
+ str, err := nextString("gutter character required")
+ if err != nil {
+ return err
+ }
+ str = firstLine(str)
+ opts.Gutter = &str
case "--pointer":
str, err := nextString("pointer sign required")
if err != nil {
@@ -3355,22 +3364,28 @@ func applyPreset(opts *Options, preset string) error {
return nil
}
-func validateSign(sign string, signOptName string) error {
- if uniseg.StringWidth(sign) > 2 {
- return fmt.Errorf("%v display width should be up to 2", signOptName)
+func validateSign(sign string, signOptName string, maxWidth int) error {
+ if uniseg.StringWidth(sign) > maxWidth {
+ return fmt.Errorf("%v display width should be up to %d", signOptName, maxWidth)
}
return nil
}
func validateOptions(opts *Options) error {
if opts.Pointer != nil {
- if err := validateSign(*opts.Pointer, "pointer"); err != nil {
+ if err := validateSign(*opts.Pointer, "pointer", 2); err != nil {
return err
}
}
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
}
}
diff --git a/src/options_test.go b/src/options_test.go
index f35c7ee3..a14dece7 100644
--- a/src/options_test.go
+++ b/src/options_test.go
@@ -462,7 +462,7 @@ func TestValidateSign(t *testing.T) {
}
for _, testCase := range testCases {
- err := validateSign(testCase.inputSign, "")
+ err := validateSign(testCase.inputSign, "", 2)
if testCase.isValid && err != nil {
t.Errorf("Input sign `%s` caused error", testCase.inputSign)
}
diff --git a/src/terminal.go b/src/terminal.go
index 11882d8f..ddaad44e 100644
--- a/src/terminal.go
+++ b/src/terminal.go
@@ -272,6 +272,7 @@ type Terminal struct {
footerLabel labelPrinter
footerLabelLen int
footerLabelOpts labelOpts
+ gutterReverse bool
pointer string
pointerLen int
pointerEmpty string
@@ -1094,10 +1095,27 @@ func NewTerminal(opts *Options, eventBox *util.EventBox, executor *util.Executor
// This should be called before accessing tui.Color*
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)
// 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)
+
+ // Labels
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.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)
}
+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) {
t.move(line, 0, false)
- t.window.CPrint(tui.ColCursorEmpty, t.pointerEmpty)
+ t.gutter(false)
t.window.Print(t.markerEmpty)
x := t.pointerLen + t.markerLen
@@ -3262,7 +3292,7 @@ func (t *Terminal) printItem(result Result, line int, maxLine int, index int, cu
return indentSize
}
if len(label) == 0 {
- t.window.CPrint(tui.ColCurrentCursorEmpty, t.pointerEmpty)
+ t.gutter(true)
} else {
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
}
if len(label) == 0 {
- t.window.CPrint(tui.ColCursorEmpty, t.pointerEmpty)
+ t.gutter(false)
} else {
t.window.CPrint(tui.ColCursor, label)
}
diff --git a/src/tui/tui.go b/src/tui/tui.go
index 92154533..965be337 100644
--- a/src/tui/tui.go
+++ b/src/tui/tui.go
@@ -776,6 +776,7 @@ var (
ColMatch ColorPair
ColCursor ColorPair
ColCursorEmpty ColorPair
+ ColCursorEmptyChar ColorPair
ColMarker ColorPair
ColSelected ColorPair
ColSelectedMatch ColorPair
@@ -1168,10 +1169,11 @@ func initPalette(theme *ColorTheme) {
ColSelectedMatch = pair(theme.SelectedMatch, theme.SelectedBg)
ColCursor = pair(theme.Cursor, theme.Gutter)
ColCursorEmpty = pair(blank, theme.Gutter)
+ ColCursorEmptyChar = pair(theme.Gutter, theme.ListBg)
if theme.SelectedBg.Color != theme.ListBg.Color {
ColMarker = pair(theme.Marker, theme.SelectedBg)
} else {
- ColMarker = pair(theme.Marker, theme.Gutter)
+ ColMarker = pair(theme.Marker, theme.ListBg)
}
ColCurrent = pair(theme.Current, theme.DarkBg)
ColCurrentMatch = pair(theme.CurrentMatch, theme.DarkBg)
diff --git a/test/lib/common.rb b/test/lib/common.rb
index ec8b05e9..d93c7685 100644
--- a/test/lib/common.rb
+++ b/test/lib/common.rb
@@ -24,7 +24,7 @@ DEFAULT_TIMEOUT = 10
FILE = File.expand_path(__FILE__)
BASE = File.expand_path('../..', __dir__)
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)
since = Time.now
diff --git a/test/test_exec.rb b/test/test_exec.rb
index c02e8c94..339a2bde 100644
--- a/test/test_exec.rb
+++ b/test/test_exec.rb
@@ -403,7 +403,7 @@ class TestExec < TestInteractive
end
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.send_keys 999
tmux.until { |lines| assert_equal 0, lines.match_count }
diff --git a/test/test_layout.rb b/test/test_layout.rb
index fd92cd63..db04c044 100644
--- a/test/test_layout.rb
+++ b/test/test_layout.rb
@@ -178,8 +178,8 @@ class TestLayout < TestInteractive
tmux.send_keys 'seq 3 | fzf --height ~100% --info=inline --border rounded', :Enter
expected = <<~OUTPUT
╭──────────
- │ 3
- │ 2
+ │ ▌ 3
+ │ ▌ 2
│ > 1
│ > < 3/3
╰──────────
@@ -197,8 +197,8 @@ class TestLayout < TestInteractive
│ │
│ │
│ ╰────────
- │ 3
- │ 2
+ │ ▌ 3
+ │ ▌ 2
│ > 1
│ > < 3/3
╰──────────
@@ -247,7 +247,7 @@ class TestLayout < TestInteractive
tmux.send_keys 'seq 100 | fzf --height ~5 --info=inline --border rounded', :Enter
expected = <<~OUTPUT
╭──────────────
- │ 2
+ │ ▌ 2
│ > 1
│ > < 100/100
╰──────────────
@@ -275,12 +275,12 @@ class TestLayout < TestInteractive
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
block = <<~BLOCK
- │ ┃998
- │ ┃999
- │ ┃1000
- │ ╹
- │ ╻1
- │ ╹2
+ │ ▌┃998
+ │ ▌┃999
+ │ ▌┃1000
+ │ ▌╹
+ │ ▌╻1
+ │ ▌╹2
│ >>0
│ 3/3 (3)
│ >
@@ -312,11 +312,11 @@ class TestLayout < TestInteractive
│ >
│ 3/3 (3)
│ >>0
- │ ╻1
- │ ╹2
- │ ╻1
- │ ┃2
- │ ┃3
+ │ ▌╻1
+ │ ▌╹2
+ │ ▌╻1
+ │ ▌┃2
+ │ ▌┃3
BLOCK
tmux.until { assert_block(block, it) }
end
@@ -1156,6 +1156,42 @@ class TestLayout < TestInteractive
tmux.until { assert_block(block, it) }
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
skip unless ENV['LONGTEST']