diff --git a/man/man1/fzf.1 b/man/man1/fzf.1 index 0486b033..d9186517 100644 --- a/man/man1/fzf.1 +++ b/man/man1/fzf.1 @@ -21,7 +21,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. .. -.TH fzf 1 "Feb 2020" "fzf 0.21.0" "fzf - a command-line fuzzy finder" +.TH fzf 1 "Mar 2020" "fzf 0.21.0" "fzf - a command-line fuzzy finder" .SH NAME fzf - a command-line fuzzy finder @@ -178,8 +178,16 @@ Choose the layout (default: default) A synonym for \fB--layout=reverse\fB .TP -.B "--border" -Draw border above and below the finder +.BI "--border" [=STYLE] +Draw border around the finder + +.br +.BR rounded " Border with rounded corners (default)" +.br +.BR sharp " Border with sharp corners" +.br +.BR horizontal " Horizontal lines above and below the finder" +.br .TP .B "--no-unicode" diff --git a/src/options.go b/src/options.go index 69ff7d4c..b87e85c2 100644 --- a/src/options.go +++ b/src/options.go @@ -57,7 +57,8 @@ const usage = `usage: fzf [options] --min-height=HEIGHT Minimum height when --height is given in percent (default: 10) --layout=LAYOUT Choose layout: [default|reverse|reverse-list] - --border Draw border above and below the finder + --border[=STYLE] Draw border around the finder + [rounded|sharp|horizontal] (default: rounded) --margin=MARGIN Screen margin (TRBL / TB,RL / T,RL,B / T,R,B,L) --info=STYLE Finder info style [default|inline|hidden] --prompt=STR Input prompt (default: '> ') @@ -212,7 +213,7 @@ type Options struct { Header []string HeaderLines int Margin [4]sizeSpec - Bordered bool + BorderShape tui.BorderShape Unicode bool Tabstop int ClearOnExit bool @@ -301,12 +302,12 @@ func nextString(args []string, i *int, message string) string { return args[*i] } -func optionalNextString(args []string, i *int) string { - if len(args) > *i+1 && !strings.HasPrefix(args[*i+1], "-") { +func optionalNextString(args []string, i *int) (bool, string) { + if len(args) > *i+1 && !strings.HasPrefix(args[*i+1], "-") && !strings.HasPrefix(args[*i+1], "+") { *i++ - return args[*i] + return true, args[*i] } - return "" + return false, "" } func atoi(str string) int { @@ -400,6 +401,23 @@ func parseAlgo(str string) algo.Algo { return algo.FuzzyMatchV2 } +func parseBorder(str string, optional bool) tui.BorderShape { + switch str { + case "rounded": + return tui.BorderRounded + case "sharp": + return tui.BorderSharp + case "horizontal": + return tui.BorderHorizontal + default: + if optional && str == "" { + return tui.BorderRounded + } + errorExit("invalid border style (expected: rounded|sharp|horizontal)") + } + return tui.BorderNone +} + func parseKeyChords(str string, message string) map[int]string { if len(str) == 0 { errorExit(message) @@ -1098,7 +1116,7 @@ func parseOptions(opts *Options, allArgs []string) { case "--bind": parseKeymap(opts.Keymap, nextString(allArgs, &i, "bind expression required")) case "--color": - spec := optionalNextString(allArgs, &i) + _, spec := optionalNextString(allArgs, &i) if len(spec) == 0 { opts.Theme = tui.EmptyTheme() } else { @@ -1246,9 +1264,10 @@ func parseOptions(opts *Options, allArgs []string) { case "--no-margin": opts.Margin = defaultMargin() case "--no-border": - opts.Bordered = false + opts.BorderShape = tui.BorderNone case "--border": - opts.Bordered = true + hasArg, arg := optionalNextString(allArgs, &i) + opts.BorderShape = parseBorder(arg, !hasArg) case "--no-unicode": opts.Unicode = false case "--unicode": @@ -1273,6 +1292,8 @@ func parseOptions(opts *Options, allArgs []string) { opts.Filter = &value } else if match, value := optString(arg, "-d", "--delimiter="); match { opts.Delimiter = delimiterRegexp(value) + } else if match, value := optString(arg, "--border="); match { + opts.BorderShape = parseBorder(value, false) } else if match, value := optString(arg, "--prompt="); match { opts.Prompt = value } else if match, value := optString(arg, "--pointer="); match { diff --git a/src/terminal.go b/src/terminal.go index bd41bdb8..a6a5c5ba 100644 --- a/src/terminal.go +++ b/src/terminal.go @@ -100,7 +100,7 @@ type Terminal struct { margin [4]sizeSpec strong tui.Attr unicode bool - bordered bool + borderShape tui.BorderShape cleanExit bool border tui.Window window tui.Window @@ -370,9 +370,9 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal { effectiveMinHeight *= 2 } if opts.InfoStyle != infoDefault { - effectiveMinHeight -= 1 + effectiveMinHeight-- } - if opts.Bordered { + if opts.BorderShape != tui.BorderNone { effectiveMinHeight += 2 } return util.Min(termHeight, util.Max(maxHeight, effectiveMinHeight)) @@ -391,62 +391,62 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal { spinner = []string{`-`, `\`, `|`, `/`, `-`, `\`, `|`, `/`} } t := Terminal{ - initDelay: delay, - infoStyle: opts.InfoStyle, - spinner: spinner, - queryLen: [2]int{0, 0}, - layout: opts.Layout, - fullscreen: fullscreen, - hscroll: opts.Hscroll, - hscrollOff: opts.HscrollOff, - wordRubout: wordRubout, - wordNext: wordNext, - cx: len(input), - cy: 0, - offset: 0, - xoffset: 0, - yanked: []rune{}, - input: input, - multi: opts.Multi, - sort: opts.Sort > 0, - toggleSort: opts.ToggleSort, - delimiter: opts.Delimiter, - expect: opts.Expect, - keymap: opts.Keymap, - pressed: "", - printQuery: opts.PrintQuery, - history: opts.History, - margin: opts.Margin, - unicode: opts.Unicode, - bordered: opts.Bordered, - cleanExit: opts.ClearOnExit, - strong: strongAttr, - cycle: opts.Cycle, - header: header, - header0: header, - ansi: opts.Ansi, - tabstop: opts.Tabstop, - reading: true, - failed: nil, - jumping: jumpDisabled, - jumpLabels: opts.JumpLabels, - printer: opts.Printer, - printsep: opts.PrintSep, - merger: EmptyMerger, - selected: make(map[int32]selectedItem), - reqBox: util.NewEventBox(), - preview: opts.Preview, - previewer: previewer{"", 0, 0, previewBox != nil && !opts.Preview.hidden, false}, - previewBox: previewBox, - eventBox: eventBox, - mutex: sync.Mutex{}, - suppress: true, - slab: util.MakeSlab(slab16Size, slab32Size), - theme: opts.Theme, - startChan: make(chan bool, 1), - killChan: make(chan int), - tui: renderer, - initFunc: func() { renderer.Init() }} + initDelay: delay, + infoStyle: opts.InfoStyle, + spinner: spinner, + queryLen: [2]int{0, 0}, + layout: opts.Layout, + fullscreen: fullscreen, + hscroll: opts.Hscroll, + hscrollOff: opts.HscrollOff, + wordRubout: wordRubout, + wordNext: wordNext, + cx: len(input), + cy: 0, + offset: 0, + xoffset: 0, + yanked: []rune{}, + input: input, + multi: opts.Multi, + sort: opts.Sort > 0, + toggleSort: opts.ToggleSort, + delimiter: opts.Delimiter, + expect: opts.Expect, + keymap: opts.Keymap, + pressed: "", + printQuery: opts.PrintQuery, + history: opts.History, + margin: opts.Margin, + unicode: opts.Unicode, + borderShape: opts.BorderShape, + cleanExit: opts.ClearOnExit, + strong: strongAttr, + cycle: opts.Cycle, + header: header, + header0: header, + ansi: opts.Ansi, + tabstop: opts.Tabstop, + reading: true, + failed: nil, + jumping: jumpDisabled, + jumpLabels: opts.JumpLabels, + printer: opts.Printer, + printsep: opts.PrintSep, + merger: EmptyMerger, + selected: make(map[int32]selectedItem), + reqBox: util.NewEventBox(), + preview: opts.Preview, + previewer: previewer{"", 0, 0, previewBox != nil && !opts.Preview.hidden, false}, + previewBox: previewBox, + eventBox: eventBox, + mutex: sync.Mutex{}, + suppress: true, + slab: util.MakeSlab(slab16Size, slab32Size), + theme: opts.Theme, + startChan: make(chan bool, 1), + killChan: make(chan int), + tui: renderer, + initFunc: func() { renderer.Init() }} t.prompt, t.promptLen = t.processTabs([]rune(opts.Prompt), 0) t.pointer, t.pointerLen = t.processTabs([]rune(opts.Pointer), 0) t.marker, t.markerLen = t.processTabs([]rune(opts.Marker), 0) @@ -595,8 +595,11 @@ func (t *Terminal) resizeWindows() { } else { marginInt[idx] = int(sizeSpec.size) } - if t.bordered && idx%2 == 0 { - marginInt[idx] += 1 + switch t.borderShape { + case tui.BorderHorizontal: + marginInt[idx] += 1 - idx%2 + case tui.BorderRounded, tui.BorderSharp: + marginInt[idx] += 1 + idx%2 } } adjust := func(idx1 int, idx2 int, max int, min int) { @@ -636,18 +639,26 @@ func (t *Terminal) resizeWindows() { width := screenWidth - marginInt[1] - marginInt[3] height := screenHeight - marginInt[0] - marginInt[2] - if t.bordered { + switch t.borderShape { + case tui.BorderHorizontal: t.border = t.tui.NewWindow( marginInt[0]-1, marginInt[3], width, height+2, false, tui.MakeBorderStyle(tui.BorderHorizontal, t.unicode)) + case tui.BorderRounded, tui.BorderSharp: + t.border = t.tui.NewWindow( + marginInt[0]-1, + marginInt[3]-2, + width+4, + height+2, + false, tui.MakeBorderStyle(t.borderShape, t.unicode)) } noBorder := tui.MakeBorderStyle(tui.BorderNone, t.unicode) if previewVisible { createPreviewWindow := func(y int, x int, w int, h int) { - previewBorder := tui.MakeBorderStyle(tui.BorderAround, t.unicode) + previewBorder := tui.MakeBorderStyle(tui.BorderRounded, t.unicode) if !t.preview.border { previewBorder = tui.MakeTransparentBorder() } @@ -1146,7 +1157,7 @@ func (t *Terminal) refresh() { t.placeCursor() if !t.suppress { windows := make([]tui.Window, 0, 4) - if t.bordered { + if t.borderShape != tui.BorderNone { windows = append(windows, t.border) } if t.hasPreviewWindow() { diff --git a/src/tui/light.go b/src/tui/light.go index 05f87e6f..fbc76f59 100644 --- a/src/tui/light.go +++ b/src/tui/light.go @@ -105,6 +105,7 @@ type LightRenderer struct { type LightWindow struct { renderer *LightRenderer colored bool + preview bool border BorderStyle top int left int @@ -681,6 +682,7 @@ func (r *LightRenderer) NewWindow(top int, left int, width int, height int, prev w := &LightWindow{ renderer: r, colored: r.theme != nil, + preview: preview, border: borderStyle, top: top, left: left, @@ -704,7 +706,7 @@ func (r *LightRenderer) NewWindow(top int, left int, width int, height int, prev func (w *LightWindow) drawBorder() { switch w.border.shape { - case BorderAround: + case BorderRounded, BorderSharp: w.drawBorderAround() case BorderHorizontal: w.drawBorderHorizontal() @@ -720,16 +722,20 @@ func (w *LightWindow) drawBorderHorizontal() { func (w *LightWindow) drawBorderAround() { w.Move(0, 0) - w.CPrint(ColPreviewBorder, AttrRegular, + color := ColBorder + if w.preview { + color = ColPreviewBorder + } + w.CPrint(color, AttrRegular, string(w.border.topLeft)+repeat(w.border.horizontal, w.width-2)+string(w.border.topRight)) for y := 1; y < w.height-1; y++ { w.Move(y, 0) - w.CPrint(ColPreviewBorder, AttrRegular, string(w.border.vertical)) - w.CPrint(ColPreviewBorder, AttrRegular, repeat(' ', w.width-2)) - w.CPrint(ColPreviewBorder, AttrRegular, string(w.border.vertical)) + w.CPrint(color, AttrRegular, string(w.border.vertical)) + w.CPrint(color, AttrRegular, repeat(' ', w.width-2)) + w.CPrint(color, AttrRegular, string(w.border.vertical)) } w.Move(w.height-1, 0) - w.CPrint(ColPreviewBorder, AttrRegular, + w.CPrint(color, AttrRegular, string(w.border.bottomLeft)+repeat(w.border.horizontal, w.width-2)+string(w.border.bottomRight)) } diff --git a/src/tui/tcell.go b/src/tui/tcell.go index 79b4944e..db891655 100644 --- a/src/tui/tcell.go +++ b/src/tui/tcell.go @@ -28,6 +28,7 @@ type Attr tcell.Style type TcellWindow struct { color bool + preview bool top int left int width int @@ -418,6 +419,7 @@ func (r *FullscreenRenderer) NewWindow(top int, left int, width int, height int, } return &TcellWindow{ color: r.theme != nil, + preview: preview, top: top, left: left, width: width, @@ -591,7 +593,7 @@ func (w *TcellWindow) drawBorder() { var style tcell.Style if w.color { - if w.borderStyle.shape == BorderAround { + if w.preview { style = ColPreviewBorder.style() } else { style = ColBorder.style() @@ -605,7 +607,7 @@ func (w *TcellWindow) drawBorder() { _screen.SetContent(x, bot-1, w.borderStyle.horizontal, nil, style) } - if w.borderStyle.shape == BorderAround { + if w.borderStyle.shape != BorderHorizontal { for y := top; y < bot; y++ { _screen.SetContent(left, y, w.borderStyle.vertical, nil, style) _screen.SetContent(right-1, y, w.borderStyle.vertical, nil, style) diff --git a/src/tui/tui.go b/src/tui/tui.go index 179adfd5..4968b366 100644 --- a/src/tui/tui.go +++ b/src/tui/tui.go @@ -210,7 +210,8 @@ type BorderShape int const ( BorderNone BorderShape = iota - BorderAround + BorderRounded + BorderSharp BorderHorizontal ) @@ -228,14 +229,25 @@ type BorderCharacter int func MakeBorderStyle(shape BorderShape, unicode bool) BorderStyle { if unicode { + if shape == BorderRounded { + return BorderStyle{ + shape: shape, + horizontal: '─', + vertical: '│', + topLeft: '╭', + topRight: '╮', + bottomLeft: '╰', + bottomRight: '╯', + } + } return BorderStyle{ shape: shape, horizontal: '─', vertical: '│', - topLeft: '╭', - topRight: '╮', - bottomLeft: '╰', - bottomRight: '╯', + topLeft: '┌', + topRight: '┐', + bottomLeft: '└', + bottomRight: '┘', } } return BorderStyle{ @@ -251,7 +263,7 @@ func MakeBorderStyle(shape BorderShape, unicode bool) BorderStyle { func MakeTransparentBorder() BorderStyle { return BorderStyle{ - shape: BorderAround, + shape: BorderRounded, horizontal: ' ', vertical: ' ', topLeft: ' ',