m/fzf
1
0
mirror of https://github.com/junegunn/fzf.git synced 2025-11-18 08:13:40 -05:00

Add support for text styling using --color

Close #1663
This commit is contained in:
Junegunn Choi
2020-10-25 19:29:37 +09:00
parent 03c4f04246
commit 11841f688b
12 changed files with 502 additions and 347 deletions

View File

@@ -15,14 +15,17 @@ func (a Attr) Merge(b Attr) Attr {
}
const (
AttrRegular Attr = Attr(0)
Bold = Attr(1)
Dim = Attr(1 << 1)
Italic = Attr(1 << 2)
Underline = Attr(1 << 3)
Blink = Attr(1 << 4)
Blink2 = Attr(1 << 5)
Reverse = Attr(1 << 6)
AttrUndefined = Attr(0)
AttrRegular = Attr(1 << 7)
AttrClear = Attr(1 << 8)
Bold = Attr(1)
Dim = Attr(1 << 1)
Italic = Attr(1 << 2)
Underline = Attr(1 << 3)
Blink = Attr(1 << 4)
Blink2 = Attr(1 << 5)
Reverse = Attr(1 << 6)
)
func (r *FullscreenRenderer) Init() {}

View File

@@ -627,7 +627,7 @@ func (r *LightRenderer) MaxY() int {
func (r *LightRenderer) NewWindow(top int, left int, width int, height int, preview bool, borderStyle BorderStyle) Window {
w := &LightWindow{
renderer: r,
colored: r.theme != nil,
colored: r.theme.Colored,
preview: preview,
border: borderStyle,
top: top,
@@ -637,14 +637,12 @@ func (r *LightRenderer) NewWindow(top int, left int, width int, height int, prev
tabstop: r.tabstop,
fg: colDefault,
bg: colDefault}
if r.theme != nil {
if preview {
w.fg = r.theme.PreviewFg
w.bg = r.theme.PreviewBg
} else {
w.fg = r.theme.Fg
w.bg = r.theme.Bg
}
if preview {
w.fg = r.theme.PreviewFg.Color
w.bg = r.theme.PreviewBg.Color
} else {
w.fg = r.theme.Fg.Color
w.bg = r.theme.Bg.Color
}
w.drawBorder()
return w
@@ -661,9 +659,9 @@ func (w *LightWindow) drawBorder() {
func (w *LightWindow) drawBorderHorizontal() {
w.Move(0, 0)
w.CPrint(ColBorder, AttrRegular, repeat(w.border.horizontal, w.width))
w.CPrint(ColBorder, repeat(w.border.horizontal, w.width))
w.Move(w.height-1, 0)
w.CPrint(ColBorder, AttrRegular, repeat(w.border.horizontal, w.width))
w.CPrint(ColBorder, repeat(w.border.horizontal, w.width))
}
func (w *LightWindow) drawBorderAround() {
@@ -672,17 +670,15 @@ func (w *LightWindow) drawBorderAround() {
if w.preview {
color = ColPreviewBorder
}
w.CPrint(color, AttrRegular,
string(w.border.topLeft)+repeat(w.border.horizontal, w.width-2)+string(w.border.topRight))
w.CPrint(color, 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(color, AttrRegular, string(w.border.vertical))
w.CPrint(color, AttrRegular, repeat(' ', w.width-2))
w.CPrint(color, AttrRegular, string(w.border.vertical))
w.CPrint(color, string(w.border.vertical))
w.CPrint(color, repeat(' ', w.width-2))
w.CPrint(color, string(w.border.vertical))
}
w.Move(w.height-1, 0)
w.CPrint(color, AttrRegular,
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-2)+string(w.border.bottomRight))
}
func (w *LightWindow) csi(code string) {
@@ -745,6 +741,9 @@ func (w *LightWindow) MoveAndClear(y int, x int) {
func attrCodes(attr Attr) []string {
codes := []string{}
if (attr & AttrClear) > 0 {
return codes
}
if (attr & Bold) > 0 {
codes = append(codes, "1")
}
@@ -804,12 +803,8 @@ func cleanse(str string) string {
return strings.Replace(str, "\x1b", "", -1)
}
func (w *LightWindow) CPrint(pair ColorPair, attr Attr, text string) {
if !w.colored {
w.csiColor(colDefault, colDefault, attrFor(pair, attr))
} else {
w.csiColor(pair.Fg(), pair.Bg(), attr)
}
func (w *LightWindow) CPrint(pair ColorPair, text string) {
w.csiColor(pair.Fg(), pair.Bg(), pair.Attr())
w.stderrInternal(cleanse(text), false)
w.csi("m")
}

View File

@@ -77,11 +77,13 @@ const (
Blink = Attr(tcell.AttrBlink)
Reverse = Attr(tcell.AttrReverse)
Underline = Attr(tcell.AttrUnderline)
Italic = Attr(tcell.AttrNone) // Not supported
Italic = Attr(tcell.AttrItalic)
)
const (
AttrRegular Attr = 0
AttrUndefined = Attr(0)
AttrRegular = Attr(1 << 7)
AttrClear = Attr(1 << 8)
)
func (r *FullscreenRenderer) defaultTheme() *ColorTheme {
@@ -414,7 +416,7 @@ func (r *FullscreenRenderer) NewWindow(top int, left int, width int, height int,
normal = ColPreview
}
return &TcellWindow{
color: r.theme != nil,
color: r.theme.Colored,
preview: preview,
top: top,
left: left,
@@ -460,27 +462,23 @@ func (w *TcellWindow) MoveAndClear(y int, x int) {
}
func (w *TcellWindow) Print(text string) {
w.printString(text, w.normal, 0)
w.printString(text, w.normal)
}
func (w *TcellWindow) printString(text string, pair ColorPair, a Attr) {
func (w *TcellWindow) printString(text string, pair ColorPair) {
t := text
lx := 0
a := pair.Attr()
var style tcell.Style
if w.color {
style = pair.style().
style := pair.style()
if a&AttrClear == 0 {
style = style.
Reverse(a&Attr(tcell.AttrReverse) != 0).
Underline(a&Attr(tcell.AttrUnderline) != 0)
} else {
style = w.normal.style().
Reverse(a&Attr(tcell.AttrReverse) != 0 || pair == ColCurrent || pair == ColCurrentMatch).
Underline(a&Attr(tcell.AttrUnderline) != 0 || pair == ColMatch || pair == ColCurrentMatch)
Underline(a&Attr(tcell.AttrUnderline) != 0).
Italic(a&Attr(tcell.AttrItalic) != 0).
Blink(a&Attr(tcell.AttrBlink) != 0).
Dim(a&Attr(tcell.AttrDim) != 0)
}
style = style.
Blink(a&Attr(tcell.AttrBlink) != 0).
Bold(a&Attr(tcell.AttrBold) != 0).
Dim(a&Attr(tcell.AttrDim) != 0)
for {
if len(t) == 0 {
@@ -513,8 +511,8 @@ func (w *TcellWindow) printString(text string, pair ColorPair, a Attr) {
w.lastX += lx
}
func (w *TcellWindow) CPrint(pair ColorPair, attr Attr, text string) {
w.printString(text, pair, attr)
func (w *TcellWindow) CPrint(pair ColorPair, text string) {
w.printString(text, pair)
}
func (w *TcellWindow) fillString(text string, pair ColorPair, a Attr) FillReturn {
@@ -531,7 +529,8 @@ func (w *TcellWindow) fillString(text string, pair ColorPair, a Attr) FillReturn
Bold(a&Attr(tcell.AttrBold) != 0).
Dim(a&Attr(tcell.AttrDim) != 0).
Reverse(a&Attr(tcell.AttrReverse) != 0).
Underline(a&Attr(tcell.AttrUnderline) != 0)
Underline(a&Attr(tcell.AttrUnderline) != 0).
Italic(a&Attr(tcell.AttrItalic) != 0)
for _, r := range text {
if r == '\n' {
@@ -574,7 +573,7 @@ func (w *TcellWindow) CFill(fg Color, bg Color, a Attr, str string) FillReturn {
if bg == colDefault {
bg = w.normal.Bg()
}
return w.fillString(str, NewColorPair(fg, bg), a)
return w.fillString(str, NewColorPair(fg, bg, a), a)
}
func (w *TcellWindow) drawBorder() {

View File

@@ -123,6 +123,15 @@ func (c Color) is24() bool {
return c > 0 && (c&(1<<24)) > 0
}
type ColorAttr struct {
Color Color
Attr Attr
}
func NewColorAttr() ColorAttr {
return ColorAttr{Color: colUndefined, Attr: AttrUndefined}
}
const (
colUndefined Color = -2
colDefault Color = -1
@@ -148,9 +157,9 @@ const (
)
type ColorPair struct {
fg Color
bg Color
id int
fg Color
bg Color
attr Attr
}
func HexToColor(rrggbb string) Color {
@@ -160,8 +169,8 @@ func HexToColor(rrggbb string) Color {
return Color((1 << 24) + (r << 16) + (g << 8) + b)
}
func NewColorPair(fg Color, bg Color) ColorPair {
return ColorPair{fg, bg, -1}
func NewColorPair(fg Color, bg Color, attr Attr) ColorPair {
return ColorPair{fg, bg, attr}
}
func (p ColorPair) Fg() Color {
@@ -172,23 +181,59 @@ func (p ColorPair) Bg() Color {
return p.bg
}
func (p ColorPair) Attr() Attr {
return p.attr
}
func (p ColorPair) merge(other ColorPair, except Color) ColorPair {
dup := p
dup.attr = dup.attr.Merge(other.attr)
if other.fg != except {
dup.fg = other.fg
}
if other.bg != except {
dup.bg = other.bg
}
return dup
}
func (p ColorPair) WithAttr(attr Attr) ColorPair {
dup := p
dup.attr = dup.attr.Merge(attr)
return dup
}
func (p ColorPair) MergeAttr(other ColorPair) ColorPair {
return p.WithAttr(other.attr)
}
func (p ColorPair) Merge(other ColorPair) ColorPair {
return p.merge(other, colUndefined)
}
func (p ColorPair) MergeNonDefault(other ColorPair) ColorPair {
return p.merge(other, colDefault)
}
type ColorTheme struct {
Fg Color
Bg Color
PreviewFg Color
PreviewBg Color
DarkBg Color
Gutter Color
Prompt Color
Match Color
Current Color
CurrentMatch Color
Spinner Color
Info Color
Cursor Color
Selected Color
Header Color
Border Color
Colored bool
Input ColorAttr
Fg ColorAttr
Bg ColorAttr
PreviewFg ColorAttr
PreviewBg ColorAttr
DarkBg ColorAttr
Gutter ColorAttr
Prompt ColorAttr
Match ColorAttr
Current ColorAttr
CurrentMatch ColorAttr
Spinner ColorAttr
Info ColorAttr
Cursor ColorAttr
Selected ColorAttr
Header ColorAttr
Border ColorAttr
}
type Event struct {
@@ -307,7 +352,7 @@ type Window interface {
Move(y int, x int)
MoveAndClear(y int, x int)
Print(text string)
CPrint(color ColorPair, attr Attr, text string)
CPrint(color ColorPair, text string)
Fill(text string) FillReturn
CFill(fg Color, bg Color, attr Attr, text string) FillReturn
Erase()
@@ -336,41 +381,69 @@ var (
Dark256 *ColorTheme
Light256 *ColorTheme
ColPrompt ColorPair
ColNormal ColorPair
ColMatch ColorPair
ColCursor ColorPair
ColSelected ColorPair
ColCurrent ColorPair
ColCurrentMatch ColorPair
ColCurrentCursor ColorPair
ColCurrentSelected ColorPair
ColSpinner ColorPair
ColInfo ColorPair
ColHeader ColorPair
ColBorder ColorPair
ColPreview ColorPair
ColPreviewBorder ColorPair
ColPrompt ColorPair
ColNormal ColorPair
ColInput ColorPair
ColMatch ColorPair
ColCursor ColorPair
ColCursorEmpty ColorPair
ColSelected ColorPair
ColCurrent ColorPair
ColCurrentMatch ColorPair
ColCurrentCursor ColorPair
ColCurrentCursorEmpty ColorPair
ColCurrentSelected ColorPair
ColCurrentSelectedEmpty ColorPair
ColSpinner ColorPair
ColInfo ColorPair
ColHeader ColorPair
ColBorder ColorPair
ColPreview ColorPair
ColPreviewBorder ColorPair
)
func EmptyTheme() *ColorTheme {
return &ColorTheme{
Fg: colUndefined,
Bg: colUndefined,
PreviewFg: colUndefined,
PreviewBg: colUndefined,
DarkBg: colUndefined,
Gutter: colUndefined,
Prompt: colUndefined,
Match: colUndefined,
Current: colUndefined,
CurrentMatch: colUndefined,
Spinner: colUndefined,
Info: colUndefined,
Cursor: colUndefined,
Selected: colUndefined,
Header: colUndefined,
Border: colUndefined}
Colored: true,
Input: ColorAttr{colUndefined, AttrUndefined},
Fg: ColorAttr{colUndefined, AttrUndefined},
Bg: ColorAttr{colUndefined, AttrUndefined},
PreviewFg: ColorAttr{colUndefined, AttrUndefined},
PreviewBg: ColorAttr{colUndefined, AttrUndefined},
DarkBg: ColorAttr{colUndefined, AttrUndefined},
Gutter: ColorAttr{colUndefined, AttrUndefined},
Prompt: ColorAttr{colUndefined, AttrUndefined},
Match: ColorAttr{colUndefined, AttrUndefined},
Current: ColorAttr{colUndefined, AttrUndefined},
CurrentMatch: ColorAttr{colUndefined, AttrUndefined},
Spinner: ColorAttr{colUndefined, AttrUndefined},
Info: ColorAttr{colUndefined, AttrUndefined},
Cursor: ColorAttr{colUndefined, AttrUndefined},
Selected: ColorAttr{colUndefined, AttrUndefined},
Header: ColorAttr{colUndefined, AttrUndefined},
Border: ColorAttr{colUndefined, AttrUndefined}}
}
func NoColorTheme() *ColorTheme {
return &ColorTheme{
Colored: false,
Input: ColorAttr{colDefault, AttrRegular},
Fg: ColorAttr{colDefault, AttrRegular},
Bg: ColorAttr{colDefault, AttrRegular},
PreviewFg: ColorAttr{colDefault, AttrRegular},
PreviewBg: ColorAttr{colDefault, AttrRegular},
DarkBg: ColorAttr{colDefault, AttrRegular},
Gutter: ColorAttr{colDefault, AttrRegular},
Prompt: ColorAttr{colDefault, AttrRegular},
Match: ColorAttr{colDefault, Underline},
Current: ColorAttr{colDefault, Reverse},
CurrentMatch: ColorAttr{colDefault, Reverse | Underline},
Spinner: ColorAttr{colDefault, AttrRegular},
Info: ColorAttr{colDefault, AttrRegular},
Cursor: ColorAttr{colDefault, AttrRegular},
Selected: ColorAttr{colDefault, AttrRegular},
Header: ColorAttr{colDefault, AttrRegular},
Border: ColorAttr{colDefault, AttrRegular}}
}
func errorExit(message string) {
@@ -380,74 +453,80 @@ func errorExit(message string) {
func init() {
Default16 = &ColorTheme{
Fg: colDefault,
Bg: colDefault,
PreviewFg: colUndefined,
PreviewBg: colUndefined,
DarkBg: colBlack,
Gutter: colUndefined,
Prompt: colBlue,
Match: colGreen,
Current: colYellow,
CurrentMatch: colGreen,
Spinner: colGreen,
Info: colWhite,
Cursor: colRed,
Selected: colMagenta,
Header: colCyan,
Border: colBlack}
Colored: true,
Input: ColorAttr{colDefault, AttrUndefined},
Fg: ColorAttr{colDefault, AttrUndefined},
Bg: ColorAttr{colDefault, AttrUndefined},
PreviewFg: ColorAttr{colUndefined, AttrUndefined},
PreviewBg: ColorAttr{colUndefined, AttrUndefined},
DarkBg: ColorAttr{colBlack, AttrUndefined},
Gutter: ColorAttr{colUndefined, AttrUndefined},
Prompt: ColorAttr{colBlue, AttrUndefined},
Match: ColorAttr{colGreen, AttrUndefined},
Current: ColorAttr{colYellow, AttrUndefined},
CurrentMatch: ColorAttr{colGreen, AttrUndefined},
Spinner: ColorAttr{colGreen, AttrUndefined},
Info: ColorAttr{colWhite, AttrUndefined},
Cursor: ColorAttr{colRed, AttrUndefined},
Selected: ColorAttr{colMagenta, AttrUndefined},
Header: ColorAttr{colCyan, AttrUndefined},
Border: ColorAttr{colBlack, AttrUndefined}}
Dark256 = &ColorTheme{
Fg: colDefault,
Bg: colDefault,
PreviewFg: colUndefined,
PreviewBg: colUndefined,
DarkBg: 236,
Gutter: colUndefined,
Prompt: 110,
Match: 108,
Current: 254,
CurrentMatch: 151,
Spinner: 148,
Info: 144,
Cursor: 161,
Selected: 168,
Header: 109,
Border: 59}
Colored: true,
Input: ColorAttr{colDefault, AttrUndefined},
Fg: ColorAttr{colDefault, AttrUndefined},
Bg: ColorAttr{colDefault, AttrUndefined},
PreviewFg: ColorAttr{colUndefined, AttrUndefined},
PreviewBg: ColorAttr{colUndefined, AttrUndefined},
DarkBg: ColorAttr{236, AttrUndefined},
Gutter: ColorAttr{colUndefined, AttrUndefined},
Prompt: ColorAttr{110, AttrUndefined},
Match: ColorAttr{108, AttrUndefined},
Current: ColorAttr{254, AttrUndefined},
CurrentMatch: ColorAttr{151, AttrUndefined},
Spinner: ColorAttr{148, AttrUndefined},
Info: ColorAttr{144, AttrUndefined},
Cursor: ColorAttr{161, AttrUndefined},
Selected: ColorAttr{168, AttrUndefined},
Header: ColorAttr{109, AttrUndefined},
Border: ColorAttr{59, AttrUndefined}}
Light256 = &ColorTheme{
Fg: colDefault,
Bg: colDefault,
PreviewFg: colUndefined,
PreviewBg: colUndefined,
DarkBg: 251,
Gutter: colUndefined,
Prompt: 25,
Match: 66,
Current: 237,
CurrentMatch: 23,
Spinner: 65,
Info: 101,
Cursor: 161,
Selected: 168,
Header: 31,
Border: 145}
Colored: true,
Input: ColorAttr{colDefault, AttrUndefined},
Fg: ColorAttr{colDefault, AttrUndefined},
Bg: ColorAttr{colDefault, AttrUndefined},
PreviewFg: ColorAttr{colUndefined, AttrUndefined},
PreviewBg: ColorAttr{colUndefined, AttrUndefined},
DarkBg: ColorAttr{251, AttrUndefined},
Gutter: ColorAttr{colUndefined, AttrUndefined},
Prompt: ColorAttr{25, AttrUndefined},
Match: ColorAttr{66, AttrUndefined},
Current: ColorAttr{237, AttrUndefined},
CurrentMatch: ColorAttr{23, AttrUndefined},
Spinner: ColorAttr{65, AttrUndefined},
Info: ColorAttr{101, AttrUndefined},
Cursor: ColorAttr{161, AttrUndefined},
Selected: ColorAttr{168, AttrUndefined},
Header: ColorAttr{31, AttrUndefined},
Border: ColorAttr{145, AttrUndefined}}
}
func initTheme(theme *ColorTheme, baseTheme *ColorTheme, forceBlack bool) {
if theme == nil {
initPalette(theme)
return
}
if forceBlack {
theme.Bg = colBlack
theme.Bg = ColorAttr{colBlack, AttrUndefined}
}
o := func(a Color, b Color) Color {
if b == colUndefined {
return a
o := func(a ColorAttr, b ColorAttr) ColorAttr {
c := a
if b.Color != colUndefined {
c.Color = b.Color
}
return b
if b.Attr != AttrUndefined {
c.Attr = b.Attr
}
return c
}
theme.Input = o(baseTheme.Input, theme.Input)
theme.Fg = o(baseTheme.Fg, theme.Fg)
theme.Bg = o(baseTheme.Bg, theme.Bg)
theme.PreviewFg = o(theme.Fg, o(baseTheme.PreviewFg, theme.PreviewFg))
@@ -469,54 +548,29 @@ func initTheme(theme *ColorTheme, baseTheme *ColorTheme, forceBlack bool) {
}
func initPalette(theme *ColorTheme) {
idx := 0
pair := func(fg, bg Color) ColorPair {
idx++
return ColorPair{fg, bg, idx}
pair := func(fg, bg ColorAttr) ColorPair {
return ColorPair{fg.Color, bg.Color, fg.Attr}
}
if theme != nil {
ColPrompt = pair(theme.Prompt, theme.Bg)
ColNormal = pair(theme.Fg, theme.Bg)
ColMatch = pair(theme.Match, theme.Bg)
ColCursor = pair(theme.Cursor, theme.Gutter)
ColSelected = pair(theme.Selected, theme.Gutter)
ColCurrent = pair(theme.Current, theme.DarkBg)
ColCurrentMatch = pair(theme.CurrentMatch, theme.DarkBg)
ColCurrentCursor = pair(theme.Cursor, theme.DarkBg)
ColCurrentSelected = pair(theme.Selected, theme.DarkBg)
ColSpinner = pair(theme.Spinner, theme.Bg)
ColInfo = pair(theme.Info, theme.Bg)
ColHeader = pair(theme.Header, theme.Bg)
ColBorder = pair(theme.Border, theme.Bg)
ColPreview = pair(theme.PreviewFg, theme.PreviewBg)
ColPreviewBorder = pair(theme.Border, theme.PreviewBg)
} else {
ColPrompt = pair(colDefault, colDefault)
ColNormal = pair(colDefault, colDefault)
ColMatch = pair(colDefault, colDefault)
ColCursor = pair(colDefault, colDefault)
ColSelected = pair(colDefault, colDefault)
ColCurrent = pair(colDefault, colDefault)
ColCurrentMatch = pair(colDefault, colDefault)
ColCurrentCursor = pair(colDefault, colDefault)
ColCurrentSelected = pair(colDefault, colDefault)
ColSpinner = pair(colDefault, colDefault)
ColInfo = pair(colDefault, colDefault)
ColHeader = pair(colDefault, colDefault)
ColBorder = pair(colDefault, colDefault)
ColPreview = pair(colDefault, colDefault)
ColPreviewBorder = pair(colDefault, colDefault)
}
}
blank := theme.Fg
blank.Attr = AttrRegular
func attrFor(color ColorPair, attr Attr) Attr {
switch color {
case ColCurrent:
return attr | Reverse
case ColMatch:
return attr | Underline
case ColCurrentMatch:
return attr | Underline | Reverse
}
return attr
ColPrompt = pair(theme.Prompt, theme.Bg)
ColNormal = pair(theme.Fg, theme.Bg)
ColInput = pair(theme.Input, theme.Bg)
ColMatch = pair(theme.Match, theme.Bg)
ColCursor = pair(theme.Cursor, theme.Gutter)
ColCursorEmpty = pair(blank, theme.Gutter)
ColSelected = pair(theme.Selected, theme.Gutter)
ColCurrent = pair(theme.Current, theme.DarkBg)
ColCurrentMatch = pair(theme.CurrentMatch, theme.DarkBg)
ColCurrentCursor = pair(theme.Cursor, theme.DarkBg)
ColCurrentCursorEmpty = pair(blank, theme.DarkBg)
ColCurrentSelected = pair(theme.Selected, theme.DarkBg)
ColCurrentSelectedEmpty = pair(blank, theme.DarkBg)
ColSpinner = pair(theme.Spinner, theme.Bg)
ColInfo = pair(theme.Info, theme.Bg)
ColHeader = pair(theme.Header, theme.Bg)
ColBorder = pair(theme.Border, theme.Bg)
ColPreview = pair(theme.PreviewFg, theme.PreviewBg)
ColPreviewBorder = pair(theme.Border, theme.PreviewBg)
}