Add --preview-wrap-sign

This commit is contained in:
Junegunn Choi
2026-02-19 21:30:23 +09:00
parent 0ecbf3f475
commit 4a195e6323
6 changed files with 269 additions and 202 deletions
+2
View File
@@ -32,6 +32,8 @@ CHANGELOG
# In the preview window # In the preview window
fzf --preview "printf '\e[4:3;58;2;255;0;0mRed curly underline\e[0m\n'" fzf --preview "printf '\e[4:3;58;2;255;0;0mRed curly underline\e[0m\n'"
``` ```
- Added `--preview-wrap-sign` to set a different wrap indicator for the preview
window
- Added `alt-gutter` color option (#4602) (@hedgieinsocks) - Added `alt-gutter` color option (#4602) (@hedgieinsocks)
- Added fish completion support (#4605) (@lalvarezt) - Added fish completion support (#4605) (@lalvarezt)
- zsh: Handle multi-line history selection (#4595) (@LangLangBart) - zsh: Handle multi-line history selection (#4595) (@LangLangBart)
+5
View File
@@ -919,6 +919,11 @@ Should be used with one of the following \fB\-\-preview\-window\fR options.
.B * border\-bottom .B * border\-bottom
.br .br
.TP
.BI "\-\-preview\-wrap\-sign" =INDICATOR
Indicator for wrapped lines in the preview window. If not set, the value of
\fB\-\-wrap\-sign\fR is used.
.TP .TP
.BI "\-\-preview\-label\-pos" [=N[:top|bottom]] .BI "\-\-preview\-label\-pos" [=N[:top|bottom]]
Position of the border label on the border line of the preview window. Specify Position of the border label on the border line of the preview window. Specify
+1
View File
@@ -219,6 +219,7 @@ _fzf_opts_completion() {
--with-shell --with-shell
--wrap --wrap
--wrap-sign --wrap-sign
--preview-wrap-sign
--zsh --zsh
-0 --exit-0 -0 --exit-0
-1 --select-1 -1 --select-1
+8
View File
@@ -167,6 +167,7 @@ Usage: fzf [options]
--preview-label=LABEL --preview-label=LABEL
--preview-label-pos=N Same as --border-label and --border-label-pos, --preview-label-pos=N Same as --border-label and --border-label-pos,
but for preview window but for preview window
--preview-wrap-sign=STR Indicator for wrapped lines in the preview window
HEADER HEADER
--header=STR String to print as header --header=STR String to print as header
@@ -608,6 +609,7 @@ type Options struct {
Wrap bool Wrap bool
WrapWord bool WrapWord bool
WrapSign *string WrapSign *string
PreviewWrapSign *string
MultiLine bool MultiLine bool
CursorLine bool CursorLine bool
KeepRight bool KeepRight bool
@@ -3113,6 +3115,12 @@ func parseOptions(index *int, opts *Options, allArgs []string) error {
if opts.Preview.border, err = parseBorder(arg, !hasArg); err != nil { if opts.Preview.border, err = parseBorder(arg, !hasArg); err != nil {
return err return err
} }
case "--preview-wrap-sign":
str, err := nextString("preview wrap sign required")
if err != nil {
return err
}
opts.PreviewWrapSign = &str
case "--height": case "--height":
str, err := nextString("height required: [~]HEIGHT[%]") str, err := nextString("height required: [~]HEIGHT[%]")
if err != nil { if err != nil {
+44
View File
@@ -464,6 +464,50 @@ func TestPreviewOpts(t *testing.T) {
} }
} }
func TestPreviewWrapSign(t *testing.T) {
// Default: no preview wrap sign override
opts := optsFor()
if opts.PreviewWrapSign != nil {
t.Errorf("expected nil PreviewWrapSign, got %v", *opts.PreviewWrapSign)
}
// --preview-wrap-sign sets PreviewWrapSign
opts = optsFor("--preview-wrap-sign", ">> ")
if opts.PreviewWrapSign == nil || *opts.PreviewWrapSign != ">> " {
t.Errorf("expected '>> ', got %v", opts.PreviewWrapSign)
}
// --preview-wrap-sign is independent of --wrap-sign
opts = optsFor("--wrap-sign", "| ", "--preview-wrap-sign", ">> ")
if opts.WrapSign == nil || *opts.WrapSign != "| " {
t.Errorf("expected WrapSign '| ', got %v", opts.WrapSign)
}
if opts.PreviewWrapSign == nil || *opts.PreviewWrapSign != ">> " {
t.Errorf("expected PreviewWrapSign '>> ', got %v", opts.PreviewWrapSign)
}
// --preview-wrap-sign without --wrap-sign
opts = optsFor("--preview-wrap-sign", "→ ")
if opts.WrapSign != nil {
t.Errorf("expected nil WrapSign, got %v", *opts.WrapSign)
}
if opts.PreviewWrapSign == nil || *opts.PreviewWrapSign != "→ " {
t.Errorf("expected PreviewWrapSign '→ ', got %v", opts.PreviewWrapSign)
}
// Last --preview-wrap-sign wins
opts = optsFor("--preview-wrap-sign", "A ", "--preview-wrap-sign", "B ")
if opts.PreviewWrapSign == nil || *opts.PreviewWrapSign != "B " {
t.Errorf("expected PreviewWrapSign 'B ', got %v", opts.PreviewWrapSign)
}
// Empty string is allowed
opts = optsFor("--preview-wrap-sign", "")
if opts.PreviewWrapSign == nil || *opts.PreviewWrapSign != "" {
t.Errorf("expected empty PreviewWrapSign, got %v", opts.PreviewWrapSign)
}
}
func TestAdditiveExpect(t *testing.T) { func TestAdditiveExpect(t *testing.T) {
opts := optsFor("--expect=a", "--expect", "b", "--expect=c") opts := optsFor("--expect=a", "--expect", "b", "--expect=c")
if len(opts.Expect) != 3 { if len(opts.Expect) != 3 {
+14 -7
View File
@@ -253,6 +253,8 @@ type Terminal struct {
wrapWord bool wrapWord bool
wrapSign string wrapSign string
wrapSignWidth int wrapSignWidth int
previewWrapSign string
previewWrapSignWidth int
ghost string ghost string
separator labelPrinter separator labelPrinter
separatorLen int separatorLen int
@@ -1251,6 +1253,11 @@ func NewTerminal(opts *Options, eventBox *util.EventBox, executor *util.Executor
t.wrapSign = *opts.WrapSign t.wrapSign = *opts.WrapSign
} }
t.wrapSign, t.wrapSignWidth = t.processTabsStr(t.wrapSign, 0) t.wrapSign, t.wrapSignWidth = t.processTabsStr(t.wrapSign, 0)
t.previewWrapSign = t.wrapSign
t.previewWrapSignWidth = t.wrapSignWidth
if opts.PreviewWrapSign != nil {
t.previewWrapSign, t.previewWrapSignWidth = t.processTabsStr(*opts.PreviewWrapSign, 0)
}
if opts.Scrollbar == nil { if opts.Scrollbar == nil {
if t.unicode && t.borderWidth == 1 { if t.unicode && t.borderWidth == 1 {
t.scrollbar = "│" t.scrollbar = "│"
@@ -2312,7 +2319,7 @@ func (t *Terminal) resizeWindows(forcePreview bool, redrawBorder bool) {
pwidth -= 1 pwidth -= 1
} }
t.pwindow = t.tui.NewWindow(y, x, pwidth, pheight, tui.WindowPreview, noBorder, true) t.pwindow = t.tui.NewWindow(y, x, pwidth, pheight, tui.WindowPreview, noBorder, true)
t.pwindow.SetWrapSign(t.wrapSign, t.wrapSignWidth) t.pwindow.SetWrapSign(t.previewWrapSign, t.previewWrapSignWidth)
if !hadPreviewWindow { if !hadPreviewWindow {
t.pwindow.Erase() t.pwindow.Erase()
} }
@@ -4130,14 +4137,14 @@ func (t *Terminal) previewLineHeight(line string, maxWidth int) int {
// For word-wrap mode, count the sub-lines produced by word wrapping. // For word-wrap mode, count the sub-lines produced by word wrapping.
// Each sub-line may still char-wrap if it contains a word longer than the width. // Each sub-line may still char-wrap if it contains a word longer than the width.
if t.activePreviewOpts.wrapWord { if t.activePreviewOpts.wrapWord {
subLines := t.wordWrapAnsiLine(line, maxWidth, t.wrapSignWidth) subLines := t.wordWrapAnsiLine(line, maxWidth, t.previewWrapSignWidth)
total := 0 total := 0
for i, sub := range subLines { for i, sub := range subLines {
prefixWidth := 0 prefixWidth := 0
cols := maxWidth cols := maxWidth
if i > 0 { if i > 0 {
prefixWidth = t.wrapSignWidth prefixWidth = t.previewWrapSignWidth
cols -= t.wrapSignWidth cols -= t.previewWrapSignWidth
} }
w := t.ansiLineWidth(sub, prefixWidth) w := t.ansiLineWidth(sub, prefixWidth)
if cols <= 0 { if cols <= 0 {
@@ -4154,7 +4161,7 @@ func (t *Terminal) previewLineHeight(line string, maxWidth int) int {
return 1 return 1
} }
remaining := w - maxWidth remaining := w - maxWidth
contWidth := max(1, maxWidth-t.wrapSignWidth) contWidth := max(1, maxWidth-t.previewWrapSignWidth)
return 1 + (remaining+contWidth-1)/contWidth return 1 + (remaining+contWidth-1)/contWidth
} }
@@ -4333,7 +4340,7 @@ Loop:
// Pre-split line into sub-lines for word wrapping // Pre-split line into sub-lines for word wrapping
var subLines []string var subLines []string
if t.activePreviewOpts.wrapWord { if t.activePreviewOpts.wrapWord {
subLines = t.wordWrapAnsiLine(line, maxWidth, t.wrapSignWidth) subLines = t.wordWrapAnsiLine(line, maxWidth, t.previewWrapSignWidth)
} else { } else {
subLines = []string{line} subLines = []string{line}
} }
@@ -4341,7 +4348,7 @@ Loop:
var fillRet tui.FillReturn var fillRet tui.FillReturn
wrap := t.activePreviewOpts.wrap wrap := t.activePreviewOpts.wrap
printWrapSign := func() { printWrapSign := func() {
if t.pwindow.CFill(tui.ColPreview.Fg(), tui.ColPreview.Bg(), -1, tui.Dim, t.wrapSign) == tui.FillNextLine { if t.pwindow.CFill(tui.ColPreview.Fg(), tui.ColPreview.Bg(), -1, tui.Dim, t.previewWrapSign) == tui.FillNextLine {
t.pwindow.Move(t.pwindow.Y()-1, t.pwindow.Width()) t.pwindow.Move(t.pwindow.Y()-1, t.pwindow.Width())
} }
fillRet = tui.FillContinue fillRet = tui.FillContinue