Better support for Windows terminals

* Default border style on Windows is changed to `sharp` because some
  Windows terminals are not capable of displaying `rounded` border
  characters correctly.
* If your terminal emulator renders each box-drawing character with
  2 columns, set `RUNEWIDTH_EASTASIAN` environment variable to `1`.
This commit is contained in:
Junegunn Choi 2023-01-16 01:22:02 +09:00
parent 1c83b39691
commit 0c5956c43c
No known key found for this signature in database
GPG Key ID: 254BC280FEF9C627
8 changed files with 66 additions and 30 deletions

View File

@ -88,6 +88,10 @@ CHANGELOG
# a will put 'alpha' on the prompt, ctrl-b will put 'bravo' # a will put 'alpha' on the prompt, ctrl-b will put 'bravo'
fzf --bind 'a:put+put(lpha),ctrl-b:put(bravo)' fzf --bind 'a:put+put(lpha),ctrl-b:put(bravo)'
``` ```
- Added color name `preview-label` for `--preview-label` (defaults to `label`
for `--border-label`)
- Better support for (Windows) terminals where each box-drawing character
takes 2 columns. Set `RUNEWIDTH_EASTASIAN` environment variable to `1`.
- Behavior changes - Behavior changes
- fzf will always execute the preview command if the command template - fzf will always execute the preview command if the command template
contains `{q}` even when it's empty. If you prefer the old behavior, contains `{q}` even when it's empty. If you prefer the old behavior,
@ -114,8 +118,9 @@ CHANGELOG
when the user manually scrolls the window, the following stops. With when the user manually scrolls the window, the following stops. With
this version, fzf will resume following if the user scrolls the window this version, fzf will resume following if the user scrolls the window
to the bottom. to the bottom.
- Added color name `preview-label` for `--preview-label` (defaults to `label` - Default border style on Windows is changed to `sharp` because some
for `--border-label`) Windows terminals are not capable of displaying `rounded` border
characters correctly.
- Minor bug fixes and improvements - Minor bug fixes and improvements
0.35.1 0.35.1

View File

@ -230,6 +230,10 @@ Draw border around the finder
.BR none .BR none
.br .br
If you use a terminal emulator where box-drawing characters take 2 columns,
try setting \fBRUNEWIDTH_EASTASIAN\fR to \fB1\fR. If the border is still
not properly rendered, set \fB--no-unicode\fR.
.TP .TP
.BI "--border-label" [=LABEL] .BI "--border-label" [=LABEL]
Label to print on the horizontal border line. Should be used with one of the Label to print on the horizontal border line. Should be used with one of the
@ -568,9 +572,9 @@ Label to print on the horizontal border line of the preview window.
Should be used with one of the following \fB--preview-window\fR options. Should be used with one of the following \fB--preview-window\fR options.
.br .br
.B * border-rounded (default) .B * border-rounded (default on non-Windows platforms)
.br .br
.B * border-sharp .B * border-sharp (default on Windows)
.br .br
.B * border-bold .B * border-bold
.br .br

View File

@ -314,7 +314,7 @@ type Options struct {
} }
func defaultPreviewOpts(command string) previewOpts { func defaultPreviewOpts(command string) previewOpts {
return previewOpts{command, posRight, sizeSpec{50, true}, "", false, false, false, false, tui.BorderRounded, 0, 0, nil} return previewOpts{command, posRight, sizeSpec{50, true}, "", false, false, false, false, tui.DefaultBorderShape, 0, 0, nil}
} }
func defaultOptions() *Options { func defaultOptions() *Options {
@ -543,7 +543,7 @@ func parseBorder(str string, optional bool) tui.BorderShape {
return tui.BorderNone return tui.BorderNone
default: default:
if optional && str == "" { if optional && str == "" {
return tui.BorderRounded return tui.DefaultBorderShape
} }
errorExit("invalid border style (expected: rounded|sharp|bold|double|horizontal|vertical|top|bottom|left|right|none)") errorExit("invalid border style (expected: rounded|sharp|bold|double|horizontal|vertical|top|bottom|left|right|none)")
} }

View File

@ -210,6 +210,7 @@ type Terminal struct {
window tui.Window window tui.Window
pborder tui.Window pborder tui.Window
pwindow tui.Window pwindow tui.Window
borderWidth int
count int count int
progress int progress int
hasLoadActions bool hasLoadActions bool
@ -604,6 +605,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
unicode: opts.Unicode, unicode: opts.Unicode,
listenPort: opts.ListenPort, listenPort: opts.ListenPort,
borderShape: opts.BorderShape, borderShape: opts.BorderShape,
borderWidth: 1,
borderLabel: nil, borderLabel: nil,
borderLabelOpts: opts.BorderLabel, borderLabelOpts: opts.BorderLabel,
previewLabel: nil, previewLabel: nil,
@ -666,8 +668,11 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
} }
t.separator, t.separatorLen = t.ansiLabelPrinter(bar, &tui.ColSeparator, true) t.separator, t.separatorLen = t.ansiLabelPrinter(bar, &tui.ColSeparator, true)
} }
if t.unicode {
t.borderWidth = runewidth.RuneWidth('│')
}
if opts.Scrollbar == nil { if opts.Scrollbar == nil {
if t.unicode { if t.unicode && t.borderWidth == 1 {
t.scrollbar = "│" t.scrollbar = "│"
} else { } else {
t.scrollbar = "|" t.scrollbar = "|"
@ -968,20 +973,21 @@ func (t *Terminal) adjustMarginAndPadding() (int, int, [4]int, [4]int) {
paddingInt[idx] = sizeSpecToInt(idx, sizeSpec) paddingInt[idx] = sizeSpecToInt(idx, sizeSpec)
} }
bw := t.borderWidth
extraMargin := [4]int{} // TRBL extraMargin := [4]int{} // TRBL
for idx, sizeSpec := range t.margin { for idx, sizeSpec := range t.margin {
switch t.borderShape { switch t.borderShape {
case tui.BorderHorizontal: case tui.BorderHorizontal:
extraMargin[idx] += 1 - idx%2 extraMargin[idx] += 1 - idx%2
case tui.BorderVertical: case tui.BorderVertical:
extraMargin[idx] += 2 * (idx % 2) extraMargin[idx] += (1 + bw) * (idx % 2)
case tui.BorderTop: case tui.BorderTop:
if idx == 0 { if idx == 0 {
extraMargin[idx]++ extraMargin[idx]++
} }
case tui.BorderRight: case tui.BorderRight:
if idx == 1 { if idx == 1 {
extraMargin[idx] += 2 extraMargin[idx] += 1 + bw
} }
case tui.BorderBottom: case tui.BorderBottom:
if idx == 2 { if idx == 2 {
@ -989,10 +995,10 @@ func (t *Terminal) adjustMarginAndPadding() (int, int, [4]int, [4]int) {
} }
case tui.BorderLeft: case tui.BorderLeft:
if idx == 3 { if idx == 3 {
extraMargin[idx] += 2 extraMargin[idx] += 1 + bw
} }
case tui.BorderRounded, tui.BorderSharp, tui.BorderBold, tui.BorderDouble: case tui.BorderRounded, tui.BorderSharp, tui.BorderBold, tui.BorderDouble:
extraMargin[idx] += 1 + idx%2 extraMargin[idx] += 1 + bw*(idx%2)
} }
marginInt[idx] = sizeSpecToInt(idx, sizeSpec) + extraMargin[idx] marginInt[idx] = sizeSpecToInt(idx, sizeSpec) + extraMargin[idx]
} }
@ -1106,6 +1112,7 @@ func (t *Terminal) resizeWindows(forcePreview bool) {
return return
} }
hasThreshold := previewOpts.threshold > 0 && previewOpts.alternative != nil hasThreshold := previewOpts.threshold > 0 && previewOpts.alternative != nil
bw := t.borderWidth
createPreviewWindow := func(y int, x int, w int, h int) { createPreviewWindow := func(y int, x int, w int, h int) {
pwidth := w pwidth := w
pheight := h pheight := h
@ -1118,15 +1125,15 @@ func (t *Terminal) resizeWindows(forcePreview bool) {
t.pborder = t.tui.NewWindow(y, x, w, h, true, previewBorder) t.pborder = t.tui.NewWindow(y, x, w, h, true, previewBorder)
switch previewOpts.border { switch previewOpts.border {
case tui.BorderSharp, tui.BorderRounded, tui.BorderBold, tui.BorderDouble: case tui.BorderSharp, tui.BorderRounded, tui.BorderBold, tui.BorderDouble:
pwidth -= 4 pwidth -= (1 + bw) * 2
pheight -= 2 pheight -= 2
x += 2 x += 1 + bw
y += 1 y += 1
case tui.BorderLeft: case tui.BorderLeft:
pwidth -= 2 pwidth -= 1 + bw
x += 2 x += 1 + bw
case tui.BorderRight: case tui.BorderRight:
pwidth -= 2 pwidth -= 1 + bw
case tui.BorderTop: case tui.BorderTop:
pheight -= 1 pheight -= 1
y += 1 y += 1
@ -1136,8 +1143,8 @@ func (t *Terminal) resizeWindows(forcePreview bool) {
pheight -= 2 pheight -= 2
y += 1 y += 1
case tui.BorderVertical: case tui.BorderVertical:
pwidth -= 4 pwidth -= (1 + bw) * 2
x += 2 x += 1 + bw
} }
if len(t.scrollbar) > 0 && !previewOpts.border.HasRight() { if len(t.scrollbar) > 0 && !previewOpts.border.HasRight() {
// Need a column to show scrollbar // Need a column to show scrollbar
@ -1832,7 +1839,7 @@ func (t *Terminal) renderPreviewScrollbar(yoff int, barLength int, barStart int)
if len(t.previewer.bar) != height { if len(t.previewer.bar) != height {
t.previewer.bar = make([]bool, height) t.previewer.bar = make([]bool, height)
} }
xshift := -2 xshift := -1 - t.borderWidth
if !t.previewOpts.border.HasRight() { if !t.previewOpts.border.HasRight() {
xshift = -1 xshift = -1
} }
@ -1846,7 +1853,7 @@ func (t *Terminal) renderPreviewScrollbar(yoff int, barLength int, barStart int)
// Avoid unnecessary redraws // Avoid unnecessary redraws
bar := i >= yoff+barStart && i < yoff+barStart+barLength bar := i >= yoff+barStart && i < yoff+barStart+barLength
if bar == t.previewer.bar[i] { if bar == t.previewer.bar[i] && !t.tui.NeedScrollbarRedraw() {
continue continue
} }

View File

@ -8,6 +8,8 @@ func HasFullscreenRenderer() bool {
return false return false
} }
var DefaultBorderShape BorderShape = BorderRounded
func (a Attr) Merge(b Attr) Attr { func (a Attr) Merge(b Attr) Attr {
return a | b return a | b
} }
@ -32,6 +34,7 @@ func (r *FullscreenRenderer) Resize(maxHeightFunc func(int) int) {}
func (r *FullscreenRenderer) Pause(bool) {} func (r *FullscreenRenderer) Pause(bool) {}
func (r *FullscreenRenderer) Resume(bool, bool) {} func (r *FullscreenRenderer) Resume(bool, bool) {}
func (r *FullscreenRenderer) Clear() {} func (r *FullscreenRenderer) Clear() {}
func (r *FullscreenRenderer) NeedScrollbarRedraw() bool { return false }
func (r *FullscreenRenderer) Refresh() {} func (r *FullscreenRenderer) Refresh() {}
func (r *FullscreenRenderer) Close() {} func (r *FullscreenRenderer) Close() {}

View File

@ -651,6 +651,10 @@ func (r *LightRenderer) Clear() {
r.flush() r.flush()
} }
func (r *LightRenderer) NeedScrollbarRedraw() bool {
return false
}
func (r *LightRenderer) RefreshWindows(windows []Window) { func (r *LightRenderer) RefreshWindows(windows []Window) {
r.flush() r.flush()
} }
@ -743,13 +747,14 @@ func (w *LightWindow) drawBorderHorizontal(top, bottom bool) {
if w.preview { if w.preview {
color = ColPreviewBorder color = ColPreviewBorder
} }
hw := runewidth.RuneWidth(w.border.horizontal)
if top { if top {
w.Move(0, 0) w.Move(0, 0)
w.CPrint(color, repeat(w.border.horizontal, w.width)) w.CPrint(color, repeat(w.border.horizontal, w.width/hw))
} }
if bottom { if bottom {
w.Move(w.height-1, 0) w.Move(w.height-1, 0)
w.CPrint(color, repeat(w.border.horizontal, w.width)) w.CPrint(color, repeat(w.border.horizontal, w.width/hw))
} }
} }
@ -780,15 +785,19 @@ func (w *LightWindow) drawBorderAround() {
if w.preview { if w.preview {
color = ColPreviewBorder color = ColPreviewBorder
} }
w.CPrint(color, string(w.border.topLeft)+repeat(w.border.horizontal, w.width-2)+string(w.border.topRight)) hw := runewidth.RuneWidth(w.border.horizontal)
vw := runewidth.RuneWidth(w.border.vertical)
tcw := runewidth.RuneWidth(w.border.topLeft) + runewidth.RuneWidth(w.border.topRight)
bcw := runewidth.RuneWidth(w.border.bottomLeft) + runewidth.RuneWidth(w.border.bottomRight)
w.CPrint(color, string(w.border.topLeft)+repeat(w.border.horizontal, (w.width-tcw)/hw)+string(w.border.topRight))
for y := 1; y < w.height-1; y++ { for y := 1; y < w.height-1; y++ {
w.Move(y, 0) w.Move(y, 0)
w.CPrint(color, string(w.border.vertical)) w.CPrint(color, string(w.border.vertical))
w.CPrint(color, repeat(' ', w.width-2)) w.CPrint(color, repeat(' ', w.width-vw*2))
w.CPrint(color, string(w.border.vertical)) w.CPrint(color, string(w.border.vertical))
} }
w.Move(w.height-1, 0) w.Move(w.height-1, 0)
w.CPrint(color, string(w.border.bottomLeft)+repeat(w.border.horizontal, w.width-2)+string(w.border.bottomRight)) w.CPrint(color, string(w.border.bottomLeft)+repeat(w.border.horizontal, (w.width-bcw)/hw)+string(w.border.bottomRight))
} }
func (w *LightWindow) csi(code string) { func (w *LightWindow) csi(code string) {

View File

@ -17,6 +17,8 @@ func HasFullscreenRenderer() bool {
return true return true
} }
var DefaultBorderShape BorderShape = BorderSharp
func asTcellColor(color Color) tcell.Color { func asTcellColor(color Color) tcell.Color {
if color == colDefault { if color == colDefault {
return tcell.ColorDefault return tcell.ColorDefault
@ -187,6 +189,10 @@ func (r *FullscreenRenderer) Clear() {
_screen.Clear() _screen.Clear()
} }
func (r *FullscreenRenderer) NeedScrollbarRedraw() bool {
return true
}
func (r *FullscreenRenderer) Refresh() { func (r *FullscreenRenderer) Refresh() {
// noop // noop
} }
@ -686,15 +692,16 @@ func (w *TcellWindow) drawBorder() {
style = w.normal.style() style = w.normal.style()
} }
hw := runewidth.RuneWidth(w.borderStyle.horizontal)
switch shape { switch shape {
case BorderRounded, BorderSharp, BorderBold, BorderDouble, BorderHorizontal, BorderTop: case BorderRounded, BorderSharp, BorderBold, BorderDouble, BorderHorizontal, BorderTop:
for x := left; x < right; x++ { for x := left; x <= right-hw; x += hw {
_screen.SetContent(x, top, w.borderStyle.horizontal, nil, style) _screen.SetContent(x, top, w.borderStyle.horizontal, nil, style)
} }
} }
switch shape { switch shape {
case BorderRounded, BorderSharp, BorderBold, BorderDouble, BorderHorizontal, BorderBottom: case BorderRounded, BorderSharp, BorderBold, BorderDouble, BorderHorizontal, BorderBottom:
for x := left; x < right; x++ { for x := left; x <= right-hw; x += hw {
_screen.SetContent(x, bot-1, w.borderStyle.horizontal, nil, style) _screen.SetContent(x, bot-1, w.borderStyle.horizontal, nil, style)
} }
} }
@ -707,14 +714,14 @@ func (w *TcellWindow) drawBorder() {
switch shape { switch shape {
case BorderRounded, BorderSharp, BorderBold, BorderDouble, BorderVertical, BorderRight: case BorderRounded, BorderSharp, BorderBold, BorderDouble, BorderVertical, BorderRight:
for y := top; y < bot; y++ { for y := top; y < bot; y++ {
_screen.SetContent(right-1, y, w.borderStyle.vertical, nil, style) _screen.SetContent(right-hw, y, w.borderStyle.vertical, nil, style)
} }
} }
switch shape { switch shape {
case BorderRounded, BorderSharp, BorderBold, BorderDouble: case BorderRounded, BorderSharp, BorderBold, BorderDouble:
_screen.SetContent(left, top, w.borderStyle.topLeft, nil, style) _screen.SetContent(left, top, w.borderStyle.topLeft, nil, style)
_screen.SetContent(right-1, top, w.borderStyle.topRight, nil, style) _screen.SetContent(right-runewidth.RuneWidth(w.borderStyle.topRight), top, w.borderStyle.topRight, nil, style)
_screen.SetContent(left, bot-1, w.borderStyle.bottomLeft, nil, style) _screen.SetContent(left, bot-1, w.borderStyle.bottomLeft, nil, style)
_screen.SetContent(right-1, bot-1, w.borderStyle.bottomRight, nil, style) _screen.SetContent(right-runewidth.RuneWidth(w.borderStyle.bottomRight), bot-1, w.borderStyle.bottomRight, nil, style)
} }
} }

View File

@ -410,6 +410,7 @@ type Renderer interface {
RefreshWindows(windows []Window) RefreshWindows(windows []Window)
Refresh() Refresh()
Close() Close()
NeedScrollbarRedraw() bool
GetChar() Event GetChar() Event