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 - 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} - } - 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) - } -} - -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 + pair := func(fg, bg ColorAttr) ColorPair { + return ColorPair{fg.Color, bg.Color, fg.Attr} } - return attr + blank := theme.Fg + blank.Attr = AttrRegular + + 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) }