diff --git a/CHANGELOG.md b/CHANGELOG.md index 37ede60..0ea9ee2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +0.16.4 +------ +- Added `--border` option to draw border above and below the finder +- Bug fixes and improvements + 0.16.3 ------ - Fixed a bug where fzf incorrectly display the lines when straddling tab diff --git a/README.md b/README.md index 303ca4c..c016de4 100644 --- a/README.md +++ b/README.md @@ -141,10 +141,10 @@ vim $(fzf --height 40% --reverse) ``` You can add these options to `$FZF_DEFAULT_OPTS` so that they're applied by -default. +default. For example, ```sh -export FZF_DEFAULT_OPTS='--height 40% --reverse' +export FZF_DEFAULT_OPTS='--height 40% --reverse --border' ``` #### Search syntax diff --git a/man/man1/fzf.1 b/man/man1/fzf.1 index f7d3a44..74ac2a4 100644 --- a/man/man1/fzf.1 +++ b/man/man1/fzf.1 @@ -156,6 +156,9 @@ Ignored when \fB--height\fR is not specified. .B "--reverse" Reverse orientation .TP +.B "--border" +Draw border above and below the finder +.TP .BI "--margin=" MARGIN Comma-separated expression for margins around the finder. .br diff --git a/src/options.go b/src/options.go index 3fadb4e..30cc751 100644 --- a/src/options.go +++ b/src/options.go @@ -54,6 +54,7 @@ const usage = `usage: fzf [options] --min-height=HEIGHT Minimum height when --height is given in percent (default: 10) --reverse Reverse orientation + --border Draw border above and below the finder --margin=MARGIN Screen margin (TRBL / TB,RL / T,RL,B / T,R,B,L) --inline-info Display finder info inline with the query --prompt=STR Input prompt (default: '> ') @@ -183,6 +184,7 @@ type Options struct { Header []string HeaderLines int Margin [4]sizeSpec + Bordered bool Tabstop int Version bool } @@ -1086,6 +1088,10 @@ func parseOptions(opts *Options, allArgs []string) { opts.Height = sizeSpec{} case "--no-margin": opts.Margin = defaultMargin() + case "--no-border": + opts.Bordered = false + case "--border": + opts.Bordered = true case "--margin": opts.Margin = parseMargin( nextString(allArgs, &i, "margin required (TRBL / TB,RL / T,RL,B / T,R,B,L)")) diff --git a/src/terminal.go b/src/terminal.go index 134462e..5853022 100644 --- a/src/terminal.go +++ b/src/terminal.go @@ -83,8 +83,10 @@ type Terminal struct { tabstop int margin [4]sizeSpec strong tui.Attr + bordered bool + border tui.Window window tui.Window - bwindow tui.Window + pborder tui.Window pwindow tui.Window count int progress int @@ -295,15 +297,22 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal { maxHeightFunc := func(termHeight int) int { var maxHeight int if opts.Height.percent { - maxHeight = util.Min(termHeight, - util.Max(int(opts.Height.size*float64(termHeight)/100.0), opts.MinHeight)) + maxHeight = util.Max(int(opts.Height.size*float64(termHeight)/100.0), opts.MinHeight) } else { - maxHeight = util.Min(termHeight, int(opts.Height.size)) + maxHeight = int(opts.Height.size) + } + + effectiveMinHeight := minHeight + if previewBox != nil && (opts.Preview.position == posUp || opts.Preview.position == posDown) { + effectiveMinHeight *= 2 } if opts.InlineInfo { - return util.Max(maxHeight, minHeight-1) + effectiveMinHeight -= 1 } - return util.Max(maxHeight, minHeight) + if opts.Bordered { + effectiveMinHeight += 2 + } + return util.Min(termHeight, util.Max(maxHeight, effectiveMinHeight)) } renderer = tui.NewLightRenderer(opts.Theme, opts.Black, opts.Mouse, opts.Tabstop, maxHeightFunc) } else if tui.HasFullscreenRenderer() { @@ -343,6 +352,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal { printQuery: opts.PrintQuery, history: opts.History, margin: opts.Margin, + bordered: opts.Bordered, strong: strongAttr, cycle: opts.Cycle, header: header, @@ -499,6 +509,9 @@ func (t *Terminal) resizeWindows() { } else { marginInt[idx] = int(sizeSpec.size) } + if t.bordered && idx%2 == 0 { + marginInt[idx] += 1 + } } adjust := func(idx1 int, idx2 int, max int, min int) { if max >= min { @@ -524,19 +537,29 @@ func (t *Terminal) resizeWindows() { } adjust(1, 3, screenWidth, minAreaWidth) adjust(0, 2, screenHeight, minAreaHeight) + if t.border != nil { + t.border.Close() + } if t.window != nil { t.window.Close() } - if t.bwindow != nil { - t.bwindow.Close() + if t.pborder != nil { + t.pborder.Close() t.pwindow.Close() } width := screenWidth - marginInt[1] - marginInt[3] height := screenHeight - marginInt[0] - marginInt[2] + if t.bordered { + t.border = t.tui.NewWindow( + marginInt[0]-1, + marginInt[3], + width, + height+2, tui.BorderHorizontal) + } if previewVisible { createPreviewWindow := func(y int, x int, w int, h int) { - t.bwindow = t.tui.NewWindow(y, x, w, h, true) + t.pborder = t.tui.NewWindow(y, x, w, h, tui.BorderAround) pwidth := w - 4 // ncurses auto-wraps the line when the cursor reaches the right-end of // the window. To prevent unintended line-wraps, we use the width one @@ -544,28 +567,28 @@ func (t *Terminal) resizeWindows() { if !t.preview.wrap && t.tui.DoesAutoWrap() { pwidth += 1 } - t.pwindow = t.tui.NewWindow(y+1, x+2, pwidth, h-2, false) + t.pwindow = t.tui.NewWindow(y+1, x+2, pwidth, h-2, tui.BorderNone) } switch t.preview.position { case posUp: pheight := calculateSize(height, t.preview.size, minHeight, 3) t.window = t.tui.NewWindow( - marginInt[0]+pheight, marginInt[3], width, height-pheight, false) + marginInt[0]+pheight, marginInt[3], width, height-pheight, tui.BorderNone) createPreviewWindow(marginInt[0], marginInt[3], width, pheight) case posDown: pheight := calculateSize(height, t.preview.size, minHeight, 3) t.window = t.tui.NewWindow( - marginInt[0], marginInt[3], width, height-pheight, false) + marginInt[0], marginInt[3], width, height-pheight, tui.BorderNone) createPreviewWindow(marginInt[0]+height-pheight, marginInt[3], width, pheight) case posLeft: pwidth := calculateSize(width, t.preview.size, minWidth, 5) t.window = t.tui.NewWindow( - marginInt[0], marginInt[3]+pwidth, width-pwidth, height, false) + marginInt[0], marginInt[3]+pwidth, width-pwidth, height, tui.BorderNone) createPreviewWindow(marginInt[0], marginInt[3], pwidth, height) case posRight: pwidth := calculateSize(width, t.preview.size, minWidth, 5) t.window = t.tui.NewWindow( - marginInt[0], marginInt[3], width-pwidth, height, false) + marginInt[0], marginInt[3], width-pwidth, height, tui.BorderNone) createPreviewWindow(marginInt[0], marginInt[3]+width-pwidth, pwidth, height) } } else { @@ -573,7 +596,7 @@ func (t *Terminal) resizeWindows() { marginInt[0], marginInt[3], width, - height, false) + height, tui.BorderNone) } if !t.tui.IsOptimized() && t.theme != nil && t.theme.HasBg() { for i := 0; i < t.window.Height(); i++ { @@ -978,11 +1001,15 @@ func (t *Terminal) printAll() { func (t *Terminal) refresh() { if !t.suppress { - if t.hasPreviewWindow() { - t.tui.RefreshWindows([]tui.Window{t.bwindow, t.pwindow, t.window}) - } else { - t.tui.RefreshWindows([]tui.Window{t.window}) + windows := make([]tui.Window, 0, 4) + if t.bordered { + windows = append(windows, t.border) } + if t.hasPreviewWindow() { + windows = append(windows, t.pborder, t.pwindow) + } + windows = append(windows, t.window) + t.tui.RefreshWindows(windows) } } diff --git a/src/tui/dummy.go b/src/tui/dummy.go index 01179c8..60a23fb 100644 --- a/src/tui/dummy.go +++ b/src/tui/dummy.go @@ -40,6 +40,6 @@ func (r *FullscreenRenderer) MaxY() int { return 0 } func (r *FullscreenRenderer) RefreshWindows(windows []Window) {} -func (r *FullscreenRenderer) NewWindow(top int, left int, width int, height int, border bool) Window { +func (r *FullscreenRenderer) NewWindow(top int, left int, width int, height int, borderStyle BorderStyle) Window { return nil } diff --git a/src/tui/light.go b/src/tui/light.go index 2ce0d5f..9465c49 100644 --- a/src/tui/light.go +++ b/src/tui/light.go @@ -95,7 +95,7 @@ type LightRenderer struct { type LightWindow struct { renderer *LightRenderer colored bool - border bool + border BorderStyle top int left int width int @@ -600,11 +600,11 @@ func (r *LightRenderer) IsOptimized() bool { return false } -func (r *LightRenderer) NewWindow(top int, left int, width int, height int, border bool) Window { +func (r *LightRenderer) NewWindow(top int, left int, width int, height int, borderStyle BorderStyle) Window { w := &LightWindow{ renderer: r, colored: r.theme != nil, - border: border, + border: borderStyle, top: top, left: left, width: width, @@ -614,13 +614,27 @@ func (r *LightRenderer) NewWindow(top int, left int, width int, height int, bord if r.theme != nil { w.bg = r.theme.Bg } - if w.border { - w.drawBorder() - } + w.drawBorder() return w } func (w *LightWindow) drawBorder() { + switch w.border { + case BorderAround: + w.drawBorderAround() + case BorderHorizontal: + w.drawBorderHorizontal() + } +} + +func (w *LightWindow) drawBorderHorizontal() { + w.Move(0, 0) + w.CPrint(ColBorder, AttrRegular, repeat("─", w.width)) + w.Move(w.height-1, 0) + w.CPrint(ColBorder, AttrRegular, repeat("─", w.width)) +} + +func (w *LightWindow) drawBorderAround() { w.Move(0, 0) w.CPrint(ColBorder, AttrRegular, "┌"+repeat("─", w.width-2)+"┐") for y := 1; y < w.height-1; y++ { @@ -854,9 +868,7 @@ func (w *LightWindow) FinishFill() { } func (w *LightWindow) Erase() { - if w.border { - w.drawBorder() - } + w.drawBorder() // We don't erase the window here to avoid flickering during scroll w.Move(0, 0) } diff --git a/src/tui/ncurses.go b/src/tui/ncurses.go index 978b2e7..4b88b44 100644 --- a/src/tui/ncurses.go +++ b/src/tui/ncurses.go @@ -189,12 +189,13 @@ func (r *FullscreenRenderer) Close() { C.delscreen(_screen) } -func (r *FullscreenRenderer) NewWindow(top int, left int, width int, height int, border bool) Window { +func (r *FullscreenRenderer) NewWindow(top int, left int, width int, height int, borderStyle BorderStyle) Window { win := C.newwin(C.int(height), C.int(width), C.int(top), C.int(left)) if r.theme != nil { C.wbkgd(win, C.chtype(C.COLOR_PAIR(C.int(ColNormal.index())))) } - if border { + // FIXME Does not implement BorderHorizontal + if borderStyle != BorderNone { pair, attr := _colorFn(ColBorder, 0) C.wcolor_set(win, pair, nil) C.wattron(win, attr) diff --git a/src/tui/tcell.go b/src/tui/tcell.go index aa67ae1..964c19e 100644 --- a/src/tui/tcell.go +++ b/src/tui/tcell.go @@ -27,15 +27,15 @@ func (p ColorPair) style() tcell.Style { type Attr tcell.Style type TcellWindow struct { - color bool - top int - left int - width int - height int - lastX int - lastY int - moveCursor bool - border bool + color bool + top int + left int + width int + height int + lastX int + lastY int + moveCursor bool + borderStyle BorderStyle } func (w *TcellWindow) Top() int { @@ -61,8 +61,11 @@ func (w *TcellWindow) Refresh() { } w.lastX = 0 w.lastY = 0 - if w.border { - w.drawBorder() + switch w.borderStyle { + case BorderAround: + w.drawBorder(true) + case BorderHorizontal: + w.drawBorder(false) } } @@ -377,15 +380,15 @@ func (r *FullscreenRenderer) RefreshWindows(windows []Window) { _screen.Show() } -func (r *FullscreenRenderer) NewWindow(top int, left int, width int, height int, border bool) Window { +func (r *FullscreenRenderer) NewWindow(top int, left int, width int, height int, borderStyle BorderStyle) Window { // TODO return &TcellWindow{ - color: r.theme != nil, - top: top, - left: left, - width: width, - height: height, - border: border} + color: r.theme != nil, + top: top, + left: left, + width: width, + height: height, + borderStyle: borderStyle} } func (w *TcellWindow) Close() { @@ -536,7 +539,7 @@ func (w *TcellWindow) CFill(fg Color, bg Color, a Attr, str string) FillReturn { return w.fillString(str, ColorPair{fg, bg, -1}, a) } -func (w *TcellWindow) drawBorder() { +func (w *TcellWindow) drawBorder(around bool) { left := w.left right := left + w.width top := w.top @@ -554,13 +557,15 @@ func (w *TcellWindow) drawBorder() { _screen.SetContent(x, bot-1, tcell.RuneHLine, nil, style) } - for y := top; y < bot; y++ { - _screen.SetContent(left, y, tcell.RuneVLine, nil, style) - _screen.SetContent(right-1, y, tcell.RuneVLine, nil, style) - } + if around { + for y := top; y < bot; y++ { + _screen.SetContent(left, y, tcell.RuneVLine, nil, style) + _screen.SetContent(right-1, y, tcell.RuneVLine, nil, style) + } - _screen.SetContent(left, top, tcell.RuneULCorner, nil, style) - _screen.SetContent(right-1, top, tcell.RuneURCorner, nil, style) - _screen.SetContent(left, bot-1, tcell.RuneLLCorner, nil, style) - _screen.SetContent(right-1, bot-1, tcell.RuneLRCorner, nil, style) + _screen.SetContent(left, top, tcell.RuneULCorner, nil, style) + _screen.SetContent(right-1, top, tcell.RuneURCorner, nil, style) + _screen.SetContent(left, bot-1, tcell.RuneLLCorner, nil, style) + _screen.SetContent(right-1, bot-1, tcell.RuneLRCorner, nil, style) + } } diff --git a/src/tui/tui.go b/src/tui/tui.go index 2508aa6..f8d905a 100644 --- a/src/tui/tui.go +++ b/src/tui/tui.go @@ -195,6 +195,14 @@ type MouseEvent struct { Mod bool } +type BorderStyle int + +const ( + BorderNone BorderStyle = iota + BorderAround + BorderHorizontal +) + type Renderer interface { Init() Pause() @@ -211,7 +219,7 @@ type Renderer interface { DoesAutoWrap() bool IsOptimized() bool - NewWindow(top int, left int, width int, height int, border bool) Window + NewWindow(top int, left int, width int, height int, borderStyle BorderStyle) Window } type Window interface {