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
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

@@ -35,7 +35,7 @@ type TcellWindow struct {
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,7 +380,7 @@ 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,
@@ -385,7 +388,7 @@ func (r *FullscreenRenderer) NewWindow(top int, left int, width int, height int,
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,6 +557,7 @@ func (w *TcellWindow) drawBorder() {
_screen.SetContent(x, bot-1, tcell.RuneHLine, nil, style) _screen.SetContent(x, bot-1, tcell.RuneHLine, nil, style)
} }
if around {
for y := top; y < bot; y++ { for y := top; y < bot; y++ {
_screen.SetContent(left, y, tcell.RuneVLine, nil, style) _screen.SetContent(left, y, tcell.RuneVLine, nil, style)
_screen.SetContent(right-1, y, tcell.RuneVLine, nil, style) _screen.SetContent(right-1, y, tcell.RuneVLine, nil, style)
@@ -564,3 +568,4 @@ func (w *TcellWindow) drawBorder() {
_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 {