summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJunegunn Choi <junegunn.c@gmail.com>2020-10-25 19:29:37 +0900
committerJunegunn Choi <junegunn.c@gmail.com>2020-10-25 19:30:41 +0900
commit11841f688bd12e56c11dc2ae71911a1e25213951 (patch)
tree25732a5add40e7d70ac01450ab18e1e133a8e15f
parent03c4f042463b2cf96dbb05fdbd06cfbb4903e6c8 (diff)
Add support for text styling using --color
Close #1663
-rw-r--r--CHANGELOG.md7
-rw-r--r--man/man1/fzf.124
-rw-r--r--src/core.go4
-rw-r--r--src/options.go104
-rw-r--r--src/options_test.go14
-rw-r--r--src/result.go64
-rw-r--r--src/result_test.go67
-rw-r--r--src/terminal.go84
-rw-r--r--src/tui/dummy.go19
-rw-r--r--src/tui/light.go43
-rw-r--r--src/tui/tcell.go41
-rw-r--r--src/tui/tui.go374
12 files changed, 500 insertions, 345 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index c11585ef..075c85b4 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -15,6 +15,13 @@ CHANGELOG
sleep 0.01
done'
```
+- Extended color specification: supports text styles
+ - `regular` / `bold` / `dim` / `underline` / `italic` / `reverse` / `blink`
+ ```sh
+ rg --line-number --no-heading --color=always "" |
+ fzf --ansi --prompt "Rg: " \
+ --color fg+:italic,hl:underline:-1,hl+:reverse:-1,prompt:reverse
+ ```
- To indicate if `--multi` mode is enabled, fzf will print the number of
selected items even when no item is selected
```sh
diff --git a/man/man1/fzf.1 b/man/man1/fzf.1
index f48c8424..b8898f3e 100644
--- a/man/man1/fzf.1
+++ b/man/man1/fzf.1
@@ -267,11 +267,9 @@ Enable processing of ANSI color codes
.BI "--tabstop=" SPACES
Number of spaces for a tab character (default: 8)
.TP
-.BI "--color=" "[BASE_SCHEME][,COLOR:ANSI]"
+.BI "--color=" "[BASE_SCHEME][,COLOR_NAME[:ANSI_COLOR][:ANSI_ATTRIBUTES]]..."
Color configuration. The name of the base color scheme is followed by custom
-color mappings. Ansi color code of -1 denotes terminal default
-foreground/background color. You can also specify 24-bit color in \fB#rrggbb\fR
-format.
+color mappings.
.RS
.B BASE SCHEME:
@@ -282,7 +280,7 @@ format.
\fB16 \fRColor scheme for 16-color terminal
\fBbw \fRNo colors (equivalent to \fB--no-color\fR)
-.B COLOR:
+.B COLOR NAMES:
\fBfg \fRText
\fBbg \fRBackground
\fBpreview-fg \fRPreview window text
@@ -300,10 +298,24 @@ format.
\fBspinner \fRStreaming input indicator
\fBheader \fRHeader
+.B ANSI COLORS:
+ \fB-1 \fRDefault terminal foreground/background color
+ \fB \fR(or the original color of the text)
+ \fB0 ~ 15 \fR16 base colors
+ \fB16 ~ 255 \fRANSI 256 colors
+ \fB#rrggbb \fR24-bit colors
+
+.B ANSI ATTRIBUTES: (Only applies to foreground colors)
+ \fBregular \fRClears previously set attributes; should precede the other ones
+ \fBbold\fR
+ \fBunderline\fR
+ \fBitalic\fR
+ \fBreverse\fR
+
.B EXAMPLES:
\fB# Seoul256 theme with 8-bit colors
- # (https://github.com/junegunn/seoul256.vim)
+ # (https://github.com/junegunn/seoul256.vim)
fzf --color='bg:237,bg+:236,info:143,border:240,spinner:108' \\
--color='hl:65,fg:252,header:65,fg+:252' \\
--color='pointer:161,marker:168,prompt:110,hl+:108'
diff --git a/src/core.go b/src/core.go
index 99dde536..bd45df69 100644
--- a/src/core.go
+++ b/src/core.go
@@ -66,7 +66,7 @@ func Run(opts *Options, revision string) {
var lineAnsiState, prevLineAnsiState *ansiState
if opts.Ansi {
- if opts.Theme != nil {
+ if opts.Theme.Colored {
ansiProcessor = func(data []byte) (util.Chars, *[]ansiOffset) {
prevLineAnsiState = lineAnsiState
trimmed, offsets, newState := extractColor(string(data), lineAnsiState, nil)
@@ -102,7 +102,7 @@ func Run(opts *Options, revision string) {
} else {
chunkList = NewChunkList(func(item *Item, data []byte) bool {
tokens := Tokenize(string(data), opts.Delimiter)
- if opts.Ansi && opts.Theme != nil && len(tokens) > 1 {
+ if opts.Ansi && opts.Theme.Colored && len(tokens) > 1 {
var ansiState *ansiState
if prevLineAnsiState != nil {
ansiStateDup := *prevLineAnsiState
diff --git a/src/options.go b/src/options.go
index ea6e420e..ce000a03 100644
--- a/src/options.go
+++ b/src/options.go
@@ -590,11 +590,8 @@ func parseTiebreak(str string) []criterion {
}
func dupeTheme(theme *tui.ColorTheme) *tui.ColorTheme {
- if theme != nil {
- dupe := *theme
- return &dupe
- }
- return nil
+ dupe := *theme
+ return &dupe
}
func parseTheme(defaultTheme *tui.ColorTheme, str string) *tui.ColorTheme {
@@ -619,54 +616,76 @@ func parseTheme(defaultTheme *tui.ColorTheme, str string) *tui.ColorTheme {
continue
}
- pair := strings.Split(str, ":")
- if len(pair) != 2 {
+ components := strings.Split(str, ":")
+ if len(components) < 2 {
fail()
}
- var ansi tui.Color
- if rrggbb.MatchString(pair[1]) {
- ansi = tui.HexToColor(pair[1])
- } else {
- ansi32, err := strconv.Atoi(pair[1])
- if err != nil || ansi32 < -1 || ansi32 > 255 {
- fail()
+ cattr := tui.NewColorAttr()
+ for _, component := range components[1:] {
+ switch component {
+ case "regular":
+ cattr.Attr = tui.AttrRegular
+ case "bold", "strong":
+ cattr.Attr |= tui.Bold
+ case "dim":
+ cattr.Attr |= tui.Dim
+ case "italic":
+ cattr.Attr |= tui.Italic
+ case "underline":
+ cattr.Attr |= tui.Underline
+ case "blink":
+ cattr.Attr |= tui.Blink
+ case "reverse":
+ cattr.Attr |= tui.Reverse
+ case "":
+ default:
+ if rrggbb.MatchString(component) {
+ cattr.Color = tui.HexToColor(component)
+ } else {
+ ansi32, err := strconv.Atoi(component)
+ if err != nil || ansi32 < -1 || ansi32 > 255 {
+ fail()
+ }
+ cattr.Color = tui.Color(ansi32)
+ }
}
- ansi = tui.Color(ansi32)
}
- switch pair[0] {
+ switch components[0] {
+ case "input":
+ theme.Input = cattr
case "fg":
- theme.Fg = ansi
+ theme.Fg = cattr
case "bg":
- theme.Bg = ansi
+ theme.Bg = cattr
case "preview-fg":
- theme.PreviewFg = ansi
+ theme.PreviewFg = cattr
case "preview-bg":
- theme.PreviewBg = ansi
+ theme.PreviewBg = cattr
case "fg+":
- theme.Current = ansi
+ theme.Current = cattr
case "bg+":
- theme.DarkBg = ansi
+ theme.DarkBg = cattr
case "gutter":
- theme.Gutter = ansi
+ theme.Gutter = cattr
case "hl":
- theme.Match = ansi
+ theme.Match = cattr
case "hl+":
- theme.CurrentMatch = ansi
+ theme.CurrentMatch = cattr
case "border":
- theme.Border = ansi
+ theme.Border = cattr
case "prompt":
- theme.Prompt = ansi
+ theme.Prompt = cattr
case "spinner":
- theme.Spinner = ansi
+ theme.Spinner = cattr
case "info":
- theme.Info = ansi
+ theme.Info = cattr
case "pointer":
- theme.Cursor = ansi
+ theme.Cursor = cattr
case "marker":
- theme.Selected = ansi
+ theme.Selected = cattr
case "header":
- theme.Header = ansi
+ theme.Header = cattr
default:
fail()
}
@@ -1180,7 +1199,7 @@ func parseOptions(opts *Options, allArgs []string) {
case "--no-mouse":
opts.Mouse = false
case "+c", "--no-color":
- opts.Theme = nil
+ opts.Theme = tui.NoColorTheme()
case "+2", "--no-256":
opts.Theme = tui.Default16
case "--black":
@@ -1478,6 +1497,25 @@ func postProcessOptions(opts *Options) {
}
}
}
+
+ if opts.Bold {
+ theme := opts.Theme
+ boldify := func(c tui.ColorAttr) tui.ColorAttr {
+ dup := c
+ if !theme.Colored {
+ dup.Attr |= tui.Bold
+ } else if (c.Attr & tui.AttrRegular) == 0 {
+ dup.Attr |= tui.Bold
+ }
+ return dup
+ }
+ theme.Current = boldify(theme.Current)
+ theme.CurrentMatch = boldify(theme.CurrentMatch)
+ theme.Prompt = boldify(theme.Prompt)
+ theme.Input = boldify(theme.Input)
+ theme.Cursor = boldify(theme.Cursor)
+ theme.Spinner = boldify(theme.Spinner)
+ }
}
// ParseOptions parses command-line options
diff --git a/src/options_test.go b/src/options_test.go
index 78207275..a8fc75bd 100644
--- a/src/options_test.go
+++ b/src/options_test.go
@@ -295,7 +295,7 @@ func TestColorSpec(t *testing.T) {
}
customized := parseTheme(theme, "fg:231,bg:232")
- if customized.Fg != 231 || customized.Bg != 232 {
+ if customized.Fg.Color != 231 || customized.Bg.Color != 232 {
t.Errorf("color not customized")
}
if *tui.Dark256 == *customized {
@@ -313,18 +313,6 @@ func TestColorSpec(t *testing.T) {
}
}
-func TestParseNilTheme(t *testing.T) {
- var theme *tui.ColorTheme
- newTheme := parseTheme(theme, "prompt:12")
- if newTheme != nil {
- t.Errorf("color is disabled. keep it that way.")
- }
- newTheme = parseTheme(theme, "prompt:12,dark,prompt:13")
- if newTheme.Prompt != 13 {
- t.Errorf("color should now be enabled and customized")
- }
-}
-
func TestDefaultCtrlNP(t *testing.T) {
check := func(words []string, key int, expected actionType) {
opts := defaultOptions()
diff --git a/src/result.go b/src/result.go
index be325cbb..b3971f44 100644
--- a/src/result.go
+++ b/src/result.go
@@ -15,7 +15,6 @@ type Offset [2]int32
type colorOffset struct {
offset [2]int32
color tui.ColorPair
- attr tui.Attr
}
type Result struct {
@@ -87,14 +86,14 @@ func minRank() Result {
return Result{item: &minItem, points: [4]uint16{math.MaxUint16, 0, 0, 0}}
}
-func (result *Result) colorOffsets(matchOffsets []Offset, theme *tui.ColorTheme, color tui.ColorPair, attr tui.Attr, current bool) []colorOffset {
+func (result *Result) colorOffsets(matchOffsets []Offset, theme *tui.ColorTheme, colBase tui.ColorPair, colMatch tui.ColorPair, current bool) []colorOffset {
itemColors := result.item.Colors()
- // No ANSI code, or --color=no
+ // No ANSI codes
if len(itemColors) == 0 {
var offsets []colorOffset
for _, off := range matchOffsets {
- offsets = append(offsets, colorOffset{offset: [2]int32{off[0], off[1]}, color: color, attr: attr})
+ offsets = append(offsets, colorOffset{offset: [2]int32{off[0], off[1]}, color: colMatch})
}
return offsets
}
@@ -111,17 +110,19 @@ func (result *Result) colorOffsets(matchOffsets []Offset, theme *tui.ColorTheme,
maxCol = ansi.offset[1]
}
}
- cols := make([]int, maxCol)
+ cols := make([]int, maxCol)
for colorIndex, ansi := range itemColors {
for i := ansi.offset[0]; i < ansi.offset[1]; i++ {
- cols[i] = colorIndex + 1 // XXX
+ cols[i] = colorIndex + 1 // 1-based index of itemColors
}
}
for _, off := range matchOffsets {
for i := off[0]; i < off[1]; i++ {
- cols[i] = -1
+ // Negative of 1-based index of itemColors
+ // - The extra -1 means highlighted
+ cols[i] = cols[i]*-1 - 1
}
}
@@ -133,36 +134,41 @@ func (result *Result) colorOffsets(matchOffsets []Offset, theme *tui.ColorTheme,
// --++++++++-- --++++++++++---
curr := 0
start := 0
+ ansiToColorPair := func(ansi ansiOffset, base tui.ColorPair) tui.ColorPair {
+ fg := ansi.color.fg
+ bg := ansi.color.bg
+ if fg == -1 {
+ if current {
+ fg = theme.Current.Color
+ } else {
+ fg = theme.Fg.Color
+ }
+ }
+ if bg == -1 {
+ if current {
+ bg = theme.DarkBg.Color
+ } else {
+ bg = theme.Bg.Color
+ }
+ }
+ return tui.NewColorPair(fg, bg, ansi.color.attr).MergeAttr(base)
+ }
var colors []colorOffset
add := func(idx int) {
if curr != 0 && idx > start {
- if curr == -1 {
+ if curr < 0 {
+ color := colMatch
+ if curr < -1 && theme.Colored {
+ origColor := ansiToColorPair(itemColors[-curr-2], colMatch)
+ color = origColor.MergeNonDefault(color)
+ }
colors = append(colors, colorOffset{
- offset: [2]int32{int32(start), int32(idx)}, color: color, attr: attr})
+ offset: [2]int32{int32(start), int32(idx)}, color: color})
} else {
ansi := itemColors[curr-1]
- fg := ansi.color.fg
- bg := ansi.color.bg
- if theme != nil {
- if fg == -1 {
- if current {
- fg = theme.Current
- } else {
- fg = theme.Fg
- }
- }
- if bg == -1 {
- if current {
- bg = theme.DarkBg
- } else {
- bg = theme.Bg
- }
- }
- }
colors = append(colors, colorOffset{
offset: [2]int32{int32(start), int32(idx)},
- color: tui.NewColorPair(fg, bg),
- attr: ansi.color.attr.Merge(attr)})
+ color: ansiToColorPair(ansi, colBase)})
}
}
}
diff --git a/src/result_test.go b/src/result_test.go
index afd17307..7b21ba54 100644
--- a/src/result_test.go
+++ b/src/result_test.go
@@ -105,32 +105,55 @@ func TestColorOffset(t *testing.T) {
// ++++++++ ++++++++++
// --++++++++-- --++++++++++---
- offsets := []Offset{Offset{5, 15}, Offset{25, 35}}
+ offsets := []Offset{{5, 15}, {25, 35}}
item := Result{
item: &Item{
colors: &[]ansiOffset{
- ansiOffset{[2]int32{0, 20}, ansiState{1, 5, 0}},
- ansiOffset{[2]int32{22, 27}, ansiState{2, 6, tui.Bold}},
- ansiOffset{[2]int32{30, 32}, ansiState{3, 7, 0}},
- ansiOffset{[2]int32{33, 40}, ansiState{4, 8, tui.Bold}}}}}
- // [{[0 5] 9 false} {[5 15] 99 false} {[15 20] 9 false} {[22 25] 10 true} {[25 35] 99 false} {[35 40] 11 true}]
-
- pair := tui.NewColorPair(99, 199)
- colors := item.colorOffsets(offsets, tui.Dark256, pair, tui.AttrRegular, true)
- assert := func(idx int, b int32, e int32, c tui.ColorPair, bold bool) {
- var attr tui.Attr
- if bold {
- attr = tui.Bold
- }
+ {[2]int32{0, 20}, ansiState{1, 5, 0}},
+ {[2]int32{22, 27}, ansiState{2, 6, tui.Bold}},
+ {[2]int32{30, 32}, ansiState{3, 7, 0}},
+ {[2]int32{33, 40}, ansiState{4, 8, tui.Bold}}}}}
+
+ colBase := tui.NewColorPair(89, 189, tui.AttrUndefined)
+ colMatch := tui.NewColorPair(99, 199, tui.AttrUndefined)
+ colors := item.colorOffsets(offsets, tui.Dark256, colBase, colMatch, true)
+ assert := func(idx int, b int32, e int32, c tui.ColorPair) {
o := colors[idx]
- if o.offset[0] != b || o.offset[1] != e || o.color != c || o.attr != attr {
- t.Error(o)
+ if o.offset[0] != b || o.offset[1] != e || o.color != c {
+ t.Error(o, b, e, c)
}
}
- assert(0, 0, 5, tui.NewColorPair(1, 5), false)
- assert(1, 5, 15, pair, false)
- assert(2, 15, 20, tui.NewColorPair(1, 5), false)
- assert(3, 22, 25, tui.NewColorPair(2, 6), true)
- assert(4, 25, 35, pair, false)
- assert(5, 35, 40, tui.NewColorPair(4, 8), true)
+ // [{[0 5] {1 5 0}} {[5 15] {99 199 0}} {[15 20] {1 5 0}}
+ // {[22 25] {2 6 1}} {[25 27] {99 199 1}} {[27 30] {99 199 0}}
+ // {[30 32] {99 199 0}} {[32 33] {99 199 0}} {[33 35] {99 199 1}}
+ // {[35 40] {4 8 1}}]
+ assert(0, 0, 5, tui.NewColorPair(1, 5, tui.AttrUndefined))
+ assert(1, 5, 15, colMatch)
+ assert(2, 15, 20, tui.NewColorPair(1, 5, tui.AttrUndefined))
+ assert(3, 22, 25, tui.NewColorPair(2, 6, tui.Bold))
+ assert(4, 25, 27, colMatch.WithAttr(tui.Bold))
+ assert(5, 27, 30, colMatch)
+ assert(6, 30, 32, colMatch)
+ assert(7, 32, 33, colMatch) // TODO: Should we merge consecutive blocks?
+ assert(8, 33, 35, colMatch.WithAttr(tui.Bold))
+ assert(9, 35, 40, tui.NewColorPair(4, 8, tui.Bold))
+
+ colRegular := tui.NewColorPair(-1, -1, tui.AttrUndefined)
+ colUnderline := tui.NewColorPair(-1, -1, tui.Underline)
+ colors = item.colorOffsets(offsets, tui.Dark256, colRegular, colUnderline, true)
+
+ // [{[0 5] {1 5 0}} {[5 15] {1 5 8}} {[15 20] {1 5 0}}
+ // {[22 25] {2 6 1}} {[25 27] {2 6 9}} {[27 30] {-1 -1 8}}
+ // {[30 32] {3 7 8}} {[32 33] {-1 -1 8}} {[33 35] {4 8 9}}
+ // {[35 40] {4 8 1}}]
+ assert(0, 0, 5, tui.NewColorPair(1, 5, tui.AttrUndefined))
+ assert(1, 5, 15, tui.NewColorPair(1, 5, tui.Underline))
+ assert(2, 15, 20, tui.NewColorPair(1, 5, tui.AttrUndefined))
+ assert(3, 22, 25, tui.NewColorPair(2, 6, tui.Bold))
+ assert(4, 25, 27, tui.NewColorPair(2, 6, tui.Bold|tui.Underline))
+ assert(5, 27, 30, colUnderline)
+ assert(6, 30, 32, tui.NewColorPair(3, 7, tui.Underline))
+ assert(7, 32, 33, colUnderline)
+ assert(8, 33, 35, tui.NewColorPair(4, 8, tui.Bold|tui.Underline))
+ assert(9, 35, 40, tui.NewColorPair(4, 8, tui.Bold))
}
diff --git a/src/terminal.go b/src/terminal.go
index 9801872f..d896d1de 100644
--- a/src/terminal.go
+++ b/src/terminal.go
@@ -23,6 +23,7 @@ import (
var placeholder *regexp.Regexp
var numericPrefix *regexp.Regexp
+var whiteSuffix *regexp.Regexp
var activeTempFiles []string
const ellipsis string = ".."
@@ -31,6 +32,7 @@ const clearCode string = "\x1b[2J"
func init() {
placeholder = regexp.MustCompile(`\\?(?:{[+sf]*[0-9,-.]*}|{q}|{\+?f?nf?})`)
numericPrefix = regexp.MustCompile(`^[[:punct:]]*([0-9]+)`)
+ whiteSuffix = regexp.MustCompile(`\s*$`)
activeTempFiles = []string{}
}
@@ -515,9 +517,29 @@ func (t *Terminal) parsePrompt(prompt string) (func(), int) {
var state *ansiState
trimmed, colors, _ := extractColor(prompt, state, nil)
item := &Item{text: util.ToChars([]byte(trimmed)), colors: colors}
+
+ // "Prompt> "
+ // ------- // Do not apply ANSI attributes to the trailing whitespaces
+ // // unless the part has a non-default ANSI state
+ loc := whiteSuffix.FindStringIndex(trimmed)
+ if loc != nil {
+ blankState := ansiOffset{[2]int32{int32(loc[0]), int32(loc[1])}, ansiState{-1, -1, tui.AttrClear}}
+ if item.colors != nil {
+ lastColor := (*item.colors)[len(*item.colors)-1]
+ fmt.Println(lastColor.offset[1], int32(loc[1]))
+ if lastColor.offset[1] < int32(loc[1]) {
+ blankState.offset[0] = lastColor.offset[1]
+ colors := append(*item.colors, blankState)
+ item.colors = &colors
+ }
+ } else {
+ colors := []ansiOffset{blankState}
+ item.colors = &colors
+ }
+ }
output := func() {
t.printHighlighted(
- Result{item: item}, t.strong, tui.ColPrompt, tui.ColPrompt, false, false)
+ Result{item: item}, tui.ColPrompt, tui.ColPrompt, false, false)
}
_, promptLen := t.processTabs([]rune(trimmed), 0)
@@ -839,8 +861,8 @@ func (t *Terminal) printPrompt() {
t.prompt()
before, after := t.updatePromptOffset()
- t.window.CPrint(tui.ColNormal, t.strong, string(before))
- t.window.CPrint(tui.ColNormal, t.strong, string(after))
+ t.window.CPrint(tui.ColInput, string(before))
+ t.window.CPrint(tui.ColInput, string(after))
}
func (t *Terminal) trimMessage(message string, maxWidth int) string {
@@ -859,7 +881,7 @@ func (t *Terminal) printInfo() {
if t.reading {
duration := int64(spinnerDuration)
idx := (time.Now().UnixNano() % (duration * int64(len(t.spinner)))) / duration
- t.window.CPrint(tui.ColSpinner, t.strong, t.spinner[idx])
+ t.window.CPrint(tui.ColSpinner, t.spinner[idx])
}
t.move(1, 2, false)
pos = 2
@@ -870,9 +892,9 @@ func (t *Terminal) printInfo() {
}
t.move(0, pos, true)
if t.reading {
- t.window.CPrint(tui.ColSpinner, t.strong, " < ")
+ t.window.CPrint(tui.ColSpinner, " < ")
} else {
- t.window.CPrint(tui.ColPrompt, t.strong, " < ")
+ t.window.CPrint(tui.ColPrompt, " < ")
}
pos += len(" < ")
case infoHidden:
@@ -903,7 +925,7 @@ func (t *Terminal) printInfo() {
output = fmt.Sprintf("[Command failed: %s]", *t.failed)
}
output = t.trimMessage(output, t.window.Width()-pos)
- t.window.CPrint(tui.ColInfo, 0, output)
+ t.window.CPrint(tui.ColInfo, output)
}
func (t *Terminal) printHeader() {
@@ -928,7 +950,7 @@ func (t *Terminal) printHeader() {
t.move(line, 2, true)
t.printHighlighted(Result{item: item},
- tui.AttrRegular, tui.ColHeader, tui.ColHeader, false, false)
+ tui.ColHeader, tui.ColHeader, false, false)
}
}
@@ -958,7 +980,7 @@ func (t *Terminal) printList() {
func (t *Terminal) printItem(result Result, line int, i int, current bool) {
item := result.item
_, selected := t.selected[item.Index()]
- label := t.pointerEmpty
+ label := ""
if t.jumping != jumpDisabled {
if i < len(t.jumpLabels) {
// Striped
@@ -983,21 +1005,29 @@ func (t *Terminal) printItem(result Result, line int, i int, current bool) {
t.move(line, 0, false)
if current {
- t.window.CPrint(tui.ColCurrentCursor, t.strong, label)
+ if len(label) == 0 {
+ t.window.CPrint(tui.ColCurrentCursorEmpty, t.pointerEmpty)
+ } else {
+ t.window.CPrint(tui.ColCurrentCursor, label)
+ }
if selected {
- t.window.CPrint(tui.ColCurrentSelected, t.strong, t.marker)
+ t.window.CPrint(tui.ColCurrentSelected, t.marker)
} else {
- t.window.CPrint(tui.ColCurrentSelected, t.strong, t.markerEmpty)
+ t.window.CPrint(tui.ColCurrentSelectedEmpty, t.markerEmpty)
}
- newLine.width = t.printHighlighted(result, t.strong, tui.ColCurrent, tui.ColCurrentMatch, true, true)
+ newLine.width = t.printHighlighted(result, tui.ColCurrent, tui.ColCurrentMatch, true, true)
} else {
- t.window.CPrint(tui.ColCursor, t.strong, label)
+ if len(label) == 0 {
+ t.window.CPrint(tui.ColCursorEmpty, t.pointerEmpty)
+ } else {
+ t.window.CPrint(tui.ColCursor, label)
+ }
if selected {
- t.window.CPrint(tui.ColSelected, t.strong, t.marker)
+ t.window.CPrint(tui.ColSelected, t.marker)
} else {
t.window.Print(t.markerEmpty)
}
- newLine.width = t.printHighlighted(result, 0, tui.ColNormal, tui.ColMatch, false, true)
+ newLine.width = t.printHighlighted(result, tui.ColNormal, tui.ColMatch, false, true)
}
fillSpaces := prevLine.width - newLine.width
if fillSpaces > 0 {
@@ -1051,7 +1081,7 @@ func (t *Terminal) overflow(runes []rune, max int) bool {
return t.displayWidthWithLimit(runes, 0, max) > max
}
-func (t *Terminal) printHighlighted(result Result, attr tui.Attr, col1 tui.ColorPair, col2 tui.ColorPair, current bool, match bool) int {
+func (t *Terminal) printHighlighted(result Result, colBase tui.ColorPair, colMatch tui.ColorPair, current bool, match bool) int {
item := result.item
// Overflow
@@ -1076,7 +1106,7 @@ func (t *Terminal) printHighlighted(result Result, attr tui.Attr, col1 tui.Color
maxe = util.Max(maxe, int(offset[1]))
}
- offsets := result.colorOffsets(charOffsets, t.theme, col2, attr, current)
+ offsets := result.colorOffsets(charOffsets, t.theme, colBase, colMatch, current)
maxWidth := t.window.Width() - (t.pointerLen + t.markerLen + 1)
maxe = util.Constrain(maxe+util.Min(maxWidth/2-2, t.hscrollOff), 0, len(text))
displayWidth := t.displayWidthWithLimit(text, 0, maxWidth)
@@ -1134,11 +1164,11 @@ func (t *Terminal) printHighlighted(result Result, attr tui.Attr, col1 tui.Color
e := util.Constrain32(offset.offset[1], index, maxOffset)
substr, prefixWidth = t.processTabs(text[index:b], prefixWidth)
- t.window.CPrint(col1, attr, substr)
+ t.window.CPrint(colBase, substr)
if b < e {
substr, prefixWidth = t.processTabs(text[b:e], prefixWidth)
- t.window.CPrint(offset.color, offset.attr, substr)
+ t.window.CPrint(offset.color, substr)
}
index = e
@@ -1148,7 +1178,7 @@ func (t *Terminal) printHighlighted(result Result, attr tui.Attr, col1 tui.Color
}
if index < maxOffset {
substr, _ = t.processTabs(text[index:], prefixWidth)
- t.window.CPrint(col1, attr, substr)
+ t.window.CPrint(colBase, substr)
}
return displayWidth
}
@@ -1161,7 +1191,7 @@ func (t *Terminal) renderPreviewSpinner() {
if !t.previewer.scrollable {
if maxWidth > 0 {
t.pwindow.Move(0, maxWidth-1)
- t.pwindow.CPrint(tui.ColSpinner, t.strong, spin)
+ t.pwindow.CPrint(tui.ColSpinner, spin)
}
} else {
offsetString := fmt.Sprintf("%d/%d", t.previewer.offset+1, numLines)
@@ -1173,8 +1203,8 @@ func (t *Terminal) renderPreviewSpinner() {
pos := maxWidth - t.displayWidth(offsetRunes)
t.pwindow.Move(0, pos)
if maxWidth > 0 {
- t.pwindow.CPrint(tui.ColSpinner, t.strong, spin)
- t.pwindow.CPrint(tui.ColInfo, tui.Reverse, string(offsetRunes))
+ t.pwindow.CPrint(tui.ColSpinner, spin)
+ t.pwindow.CPrint(tui.ColInfo.WithAttr(tui.Reverse), string(offsetRunes))
}
}
}
@@ -1185,7 +1215,7 @@ func (t *Terminal) renderPreviewText(unchanged bool) {
lineNo := -t.previewer.offset
height := t.pwindow.Height()
if unchanged {
- t.pwindow.Move(0, 0)
+ t.pwindow.MoveAndClear(0, 0)
} else {
t.previewed.filled = false
t.pwindow.Erase()
@@ -1205,7 +1235,7 @@ func (t *Terminal) renderPreviewText(unchanged bool) {
}
str, width := t.processTabs(trimmed, prefixWidth)
prefixWidth += width
- if t.theme != nil && ansi != nil && ansi.colored() {
+ if t.theme.Colored && ansi != nil && ansi.colored() {
fillRet = t.pwindow.CFill(ansi.fg, ansi.bg, ansi.attr, str)
} else {
fillRet = t.pwindow.CFill(tui.ColPreview.Fg(), tui.ColPreview.Bg(), tui.AttrRegular, str)
@@ -1258,7 +1288,7 @@ func (t *Terminal) printPreviewDelayed() {
message := t.trimMessage("Loading ..", t.pwindow.Width())
pos := t.pwindow.Width() - len(message)
t.pwindow.Move(0, pos)
- t.pwindow.CPrint(tui.ColInfo, tui.Reverse, message)
+ t.pwindow.CPrint(tui.ColInfo.WithAttr(tui.Reverse), message)
}
func (t *Terminal) processTabs(runes []rune, prefixWidth int) (string, int) {
diff --git a/src/tui/dummy.go b/src/tui/dummy.go
index a6df8550..ca50e655 100644
--- a/src/tui/dummy.go
+++ b/src/tui/dummy.go
@@ -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() {}
diff --git a/src/tui/light.go b/src/tui/light.go
index d051e67a..6bd08216 100644
--- a/src/tui/light.go
+++ b/src/tui/light.go
@@ -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")
}
diff --git a/src/tui/tcell.go b/src/tui/tcell.go
index a4c599bb..e37b5ef8 100644
--- a/src/tui/tcell.go
+++ b/src/tui/tcell.go
@@ -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() {
diff --git a/src/tui/tui.go b/src/tui/tui.go
index 146aafac..40c75112 100644
--- a/src/tui/tui.go
+++ b/src/tui/tui.go
@@ -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