Add --border option to draw horizontal lines above and below the finder

Goes well with --height
This commit is contained in:
Junegunn Choi 2017-02-04 21:51:22 +09:00
parent fe83589ade
commit 4b700192c1
No known key found for this signature in database
GPG Key ID: 254BC280FEF9C627
10 changed files with 128 additions and 61 deletions

View File

@ -1,6 +1,11 @@
CHANGELOG CHANGELOG
========= =========
0.16.4
------
- Added `--border` option to draw border above and below the finder
- Bug fixes and improvements
0.16.3 0.16.3
------ ------
- Fixed a bug where fzf incorrectly display the lines when straddling tab - Fixed a bug where fzf incorrectly display the lines when straddling tab

View File

@ -141,10 +141,10 @@ vim $(fzf --height 40% --reverse)
``` ```
You can add these options to `$FZF_DEFAULT_OPTS` so that they're applied by You can add these options to `$FZF_DEFAULT_OPTS` so that they're applied by
default. default. For example,
```sh ```sh
export FZF_DEFAULT_OPTS='--height 40% --reverse' export FZF_DEFAULT_OPTS='--height 40% --reverse --border'
``` ```
#### Search syntax #### Search syntax

View File

@ -156,6 +156,9 @@ Ignored when \fB--height\fR is not specified.
.B "--reverse" .B "--reverse"
Reverse orientation Reverse orientation
.TP .TP
.B "--border"
Draw border above and below the finder
.TP
.BI "--margin=" MARGIN .BI "--margin=" MARGIN
Comma-separated expression for margins around the finder. Comma-separated expression for margins around the finder.
.br .br

View File

@ -54,6 +54,7 @@ const usage = `usage: fzf [options]
--min-height=HEIGHT Minimum height when --height is given in percent --min-height=HEIGHT Minimum height when --height is given in percent
(default: 10) (default: 10)
--reverse Reverse orientation --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) --margin=MARGIN Screen margin (TRBL / TB,RL / T,RL,B / T,R,B,L)
--inline-info Display finder info inline with the query --inline-info Display finder info inline with the query
--prompt=STR Input prompt (default: '> ') --prompt=STR Input prompt (default: '> ')
@ -183,6 +184,7 @@ type Options struct {
Header []string Header []string
HeaderLines int HeaderLines int
Margin [4]sizeSpec Margin [4]sizeSpec
Bordered bool
Tabstop int Tabstop int
Version bool Version bool
} }
@ -1086,6 +1088,10 @@ func parseOptions(opts *Options, allArgs []string) {
opts.Height = sizeSpec{} opts.Height = sizeSpec{}
case "--no-margin": case "--no-margin":
opts.Margin = defaultMargin() opts.Margin = defaultMargin()
case "--no-border":
opts.Bordered = false
case "--border":
opts.Bordered = true
case "--margin": case "--margin":
opts.Margin = parseMargin( opts.Margin = parseMargin(
nextString(allArgs, &i, "margin required (TRBL / TB,RL / T,RL,B / T,R,B,L)")) nextString(allArgs, &i, "margin required (TRBL / TB,RL / T,RL,B / T,R,B,L)"))

View File

@ -83,8 +83,10 @@ type Terminal struct {
tabstop int tabstop int
margin [4]sizeSpec margin [4]sizeSpec
strong tui.Attr strong tui.Attr
bordered bool
border tui.Window
window tui.Window window tui.Window
bwindow tui.Window pborder tui.Window
pwindow tui.Window pwindow tui.Window
count int count int
progress int progress int
@ -295,15 +297,22 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
maxHeightFunc := func(termHeight int) int { maxHeightFunc := func(termHeight int) int {
var maxHeight int var maxHeight int
if opts.Height.percent { if opts.Height.percent {
maxHeight = util.Min(termHeight, maxHeight = util.Max(int(opts.Height.size*float64(termHeight)/100.0), opts.MinHeight)
util.Max(int(opts.Height.size*float64(termHeight)/100.0), opts.MinHeight))
} else { } 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 { 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) renderer = tui.NewLightRenderer(opts.Theme, opts.Black, opts.Mouse, opts.Tabstop, maxHeightFunc)
} else if tui.HasFullscreenRenderer() { } else if tui.HasFullscreenRenderer() {
@ -343,6 +352,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
printQuery: opts.PrintQuery, printQuery: opts.PrintQuery,
history: opts.History, history: opts.History,
margin: opts.Margin, margin: opts.Margin,
bordered: opts.Bordered,
strong: strongAttr, strong: strongAttr,
cycle: opts.Cycle, cycle: opts.Cycle,
header: header, header: header,
@ -499,6 +509,9 @@ func (t *Terminal) resizeWindows() {
} else { } else {
marginInt[idx] = int(sizeSpec.size) marginInt[idx] = int(sizeSpec.size)
} }
if t.bordered && idx%2 == 0 {
marginInt[idx] += 1
}
} }
adjust := func(idx1 int, idx2 int, max int, min int) { adjust := func(idx1 int, idx2 int, max int, min int) {
if max >= min { if max >= min {
@ -524,19 +537,29 @@ func (t *Terminal) resizeWindows() {
} }
adjust(1, 3, screenWidth, minAreaWidth) adjust(1, 3, screenWidth, minAreaWidth)
adjust(0, 2, screenHeight, minAreaHeight) adjust(0, 2, screenHeight, minAreaHeight)
if t.border != nil {
t.border.Close()
}
if t.window != nil { if t.window != nil {
t.window.Close() t.window.Close()
} }
if t.bwindow != nil { if t.pborder != nil {
t.bwindow.Close() t.pborder.Close()
t.pwindow.Close() t.pwindow.Close()
} }
width := screenWidth - marginInt[1] - marginInt[3] width := screenWidth - marginInt[1] - marginInt[3]
height := screenHeight - marginInt[0] - marginInt[2] 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 { if previewVisible {
createPreviewWindow := func(y int, x int, w int, h int) { 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 pwidth := w - 4
// ncurses auto-wraps the line when the cursor reaches the right-end of // 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 // 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() { if !t.preview.wrap && t.tui.DoesAutoWrap() {
pwidth += 1 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 { switch t.preview.position {
case posUp: case posUp:
pheight := calculateSize(height, t.preview.size, minHeight, 3) pheight := calculateSize(height, t.preview.size, minHeight, 3)
t.window = t.tui.NewWindow( 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) createPreviewWindow(marginInt[0], marginInt[3], width, pheight)
case posDown: case posDown:
pheight := calculateSize(height, t.preview.size, minHeight, 3) pheight := calculateSize(height, t.preview.size, minHeight, 3)
t.window = t.tui.NewWindow( 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) createPreviewWindow(marginInt[0]+height-pheight, marginInt[3], width, pheight)
case posLeft: case posLeft:
pwidth := calculateSize(width, t.preview.size, minWidth, 5) pwidth := calculateSize(width, t.preview.size, minWidth, 5)
t.window = t.tui.NewWindow( 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) createPreviewWindow(marginInt[0], marginInt[3], pwidth, height)
case posRight: case posRight:
pwidth := calculateSize(width, t.preview.size, minWidth, 5) pwidth := calculateSize(width, t.preview.size, minWidth, 5)
t.window = t.tui.NewWindow( 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) createPreviewWindow(marginInt[0], marginInt[3]+width-pwidth, pwidth, height)
} }
} else { } else {
@ -573,7 +596,7 @@ func (t *Terminal) resizeWindows() {
marginInt[0], marginInt[0],
marginInt[3], marginInt[3],
width, width,
height, false) height, tui.BorderNone)
} }
if !t.tui.IsOptimized() && t.theme != nil && t.theme.HasBg() { if !t.tui.IsOptimized() && t.theme != nil && t.theme.HasBg() {
for i := 0; i < t.window.Height(); i++ { for i := 0; i < t.window.Height(); i++ {
@ -978,11 +1001,15 @@ func (t *Terminal) printAll() {
func (t *Terminal) refresh() { func (t *Terminal) refresh() {
if !t.suppress { if !t.suppress {
if t.hasPreviewWindow() { windows := make([]tui.Window, 0, 4)
t.tui.RefreshWindows([]tui.Window{t.bwindow, t.pwindow, t.window}) if t.bordered {
} else { windows = append(windows, t.border)
t.tui.RefreshWindows([]tui.Window{t.window})
} }
if t.hasPreviewWindow() {
windows = append(windows, t.pborder, t.pwindow)
}
windows = append(windows, t.window)
t.tui.RefreshWindows(windows)
} }
} }

View File

@ -40,6 +40,6 @@ func (r *FullscreenRenderer) MaxY() int { return 0 }
func (r *FullscreenRenderer) RefreshWindows(windows []Window) {} 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 return nil
} }

View File

@ -95,7 +95,7 @@ type LightRenderer struct {
type LightWindow struct { type LightWindow struct {
renderer *LightRenderer renderer *LightRenderer
colored bool colored bool
border bool border BorderStyle
top int top int
left int left int
width int width int
@ -600,11 +600,11 @@ func (r *LightRenderer) IsOptimized() bool {
return false 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{ w := &LightWindow{
renderer: r, renderer: r,
colored: r.theme != nil, colored: r.theme != nil,
border: border, border: borderStyle,
top: top, top: top,
left: left, left: left,
width: width, width: width,
@ -614,13 +614,27 @@ func (r *LightRenderer) NewWindow(top int, left int, width int, height int, bord
if r.theme != nil { if r.theme != nil {
w.bg = r.theme.Bg w.bg = r.theme.Bg
} }
if w.border { w.drawBorder()
w.drawBorder()
}
return w return w
} }
func (w *LightWindow) drawBorder() { 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.Move(0, 0)
w.CPrint(ColBorder, AttrRegular, "┌"+repeat("─", w.width-2)+"┐") w.CPrint(ColBorder, AttrRegular, "┌"+repeat("─", w.width-2)+"┐")
for y := 1; y < w.height-1; y++ { for y := 1; y < w.height-1; y++ {
@ -854,9 +868,7 @@ func (w *LightWindow) FinishFill() {
} }
func (w *LightWindow) Erase() { func (w *LightWindow) Erase() {
if w.border { w.drawBorder()
w.drawBorder()
}
// We don't erase the window here to avoid flickering during scroll // We don't erase the window here to avoid flickering during scroll
w.Move(0, 0) w.Move(0, 0)
} }

View File

@ -189,12 +189,13 @@ func (r *FullscreenRenderer) Close() {
C.delscreen(_screen) 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)) win := C.newwin(C.int(height), C.int(width), C.int(top), C.int(left))
if r.theme != nil { if r.theme != nil {
C.wbkgd(win, C.chtype(C.COLOR_PAIR(C.int(ColNormal.index())))) 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) pair, attr := _colorFn(ColBorder, 0)
C.wcolor_set(win, pair, nil) C.wcolor_set(win, pair, nil)
C.wattron(win, attr) C.wattron(win, attr)

View File

@ -27,15 +27,15 @@ func (p ColorPair) style() tcell.Style {
type Attr tcell.Style type Attr tcell.Style
type TcellWindow struct { type TcellWindow struct {
color bool color bool
top int top int
left int left int
width int width int
height int height int
lastX int lastX int
lastY int lastY int
moveCursor bool moveCursor bool
border bool borderStyle BorderStyle
} }
func (w *TcellWindow) Top() int { func (w *TcellWindow) Top() int {
@ -61,8 +61,11 @@ func (w *TcellWindow) Refresh() {
} }
w.lastX = 0 w.lastX = 0
w.lastY = 0 w.lastY = 0
if w.border { switch w.borderStyle {
w.drawBorder() case BorderAround:
w.drawBorder(true)
case BorderHorizontal:
w.drawBorder(false)
} }
} }
@ -377,15 +380,15 @@ func (r *FullscreenRenderer) RefreshWindows(windows []Window) {
_screen.Show() _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 // TODO
return &TcellWindow{ return &TcellWindow{
color: r.theme != nil, color: r.theme != nil,
top: top, top: top,
left: left, left: left,
width: width, width: width,
height: height, height: height,
border: border} borderStyle: borderStyle}
} }
func (w *TcellWindow) Close() { 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) return w.fillString(str, ColorPair{fg, bg, -1}, a)
} }
func (w *TcellWindow) drawBorder() { func (w *TcellWindow) drawBorder(around bool) {
left := w.left left := w.left
right := left + w.width right := left + w.width
top := w.top top := w.top
@ -554,13 +557,15 @@ func (w *TcellWindow) drawBorder() {
_screen.SetContent(x, bot-1, tcell.RuneHLine, nil, style) _screen.SetContent(x, bot-1, tcell.RuneHLine, nil, style)
} }
for y := top; y < bot; y++ { if around {
_screen.SetContent(left, y, tcell.RuneVLine, nil, style) for y := top; y < bot; y++ {
_screen.SetContent(right-1, y, tcell.RuneVLine, nil, style) _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(left, top, tcell.RuneULCorner, nil, style)
_screen.SetContent(right-1, top, tcell.RuneURCorner, nil, style) _screen.SetContent(right-1, top, tcell.RuneURCorner, nil, style)
_screen.SetContent(left, bot-1, tcell.RuneLLCorner, nil, style) _screen.SetContent(left, bot-1, tcell.RuneLLCorner, nil, style)
_screen.SetContent(right-1, bot-1, tcell.RuneLRCorner, nil, style) _screen.SetContent(right-1, bot-1, tcell.RuneLRCorner, nil, style)
}
} }

View File

@ -195,6 +195,14 @@ type MouseEvent struct {
Mod bool Mod bool
} }
type BorderStyle int
const (
BorderNone BorderStyle = iota
BorderAround
BorderHorizontal
)
type Renderer interface { type Renderer interface {
Init() Init()
Pause() Pause()
@ -211,7 +219,7 @@ type Renderer interface {
DoesAutoWrap() bool DoesAutoWrap() bool
IsOptimized() 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 { type Window interface {