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
No known key found for this signature in database
GPG Key ID: 254BC280FEF9C627
12 changed files with 502 additions and 347 deletions

View File

@ -15,6 +15,13 @@ CHANGELOG
sleep 0.01 sleep 0.01
done' 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 - To indicate if `--multi` mode is enabled, fzf will print the number of
selected items even when no item is selected selected items even when no item is selected
```sh ```sh

View File

@ -267,11 +267,9 @@ Enable processing of ANSI color codes
.BI "--tabstop=" SPACES .BI "--tabstop=" SPACES
Number of spaces for a tab character (default: 8) Number of spaces for a tab character (default: 8)
.TP .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 configuration. The name of the base color scheme is followed by custom
color mappings. Ansi color code of -1 denotes terminal default color mappings.
foreground/background color. You can also specify 24-bit color in \fB#rrggbb\fR
format.
.RS .RS
.B BASE SCHEME: .B BASE SCHEME:
@ -282,7 +280,7 @@ format.
\fB16 \fRColor scheme for 16-color terminal \fB16 \fRColor scheme for 16-color terminal
\fBbw \fRNo colors (equivalent to \fB--no-color\fR) \fBbw \fRNo colors (equivalent to \fB--no-color\fR)
.B COLOR: .B COLOR NAMES:
\fBfg \fRText \fBfg \fRText
\fBbg \fRBackground \fBbg \fRBackground
\fBpreview-fg \fRPreview window text \fBpreview-fg \fRPreview window text
@ -300,6 +298,20 @@ format.
\fBspinner \fRStreaming input indicator \fBspinner \fRStreaming input indicator
\fBheader \fRHeader \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: .B EXAMPLES:
\fB# Seoul256 theme with 8-bit colors \fB# Seoul256 theme with 8-bit colors

View File

@ -66,7 +66,7 @@ func Run(opts *Options, revision string) {
var lineAnsiState, prevLineAnsiState *ansiState var lineAnsiState, prevLineAnsiState *ansiState
if opts.Ansi { if opts.Ansi {
if opts.Theme != nil { if opts.Theme.Colored {
ansiProcessor = func(data []byte) (util.Chars, *[]ansiOffset) { ansiProcessor = func(data []byte) (util.Chars, *[]ansiOffset) {
prevLineAnsiState = lineAnsiState prevLineAnsiState = lineAnsiState
trimmed, offsets, newState := extractColor(string(data), lineAnsiState, nil) trimmed, offsets, newState := extractColor(string(data), lineAnsiState, nil)
@ -102,7 +102,7 @@ func Run(opts *Options, revision string) {
} else { } else {
chunkList = NewChunkList(func(item *Item, data []byte) bool { chunkList = NewChunkList(func(item *Item, data []byte) bool {
tokens := Tokenize(string(data), opts.Delimiter) 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 var ansiState *ansiState
if prevLineAnsiState != nil { if prevLineAnsiState != nil {
ansiStateDup := *prevLineAnsiState ansiStateDup := *prevLineAnsiState

View File

@ -590,11 +590,8 @@ func parseTiebreak(str string) []criterion {
} }
func dupeTheme(theme *tui.ColorTheme) *tui.ColorTheme { func dupeTheme(theme *tui.ColorTheme) *tui.ColorTheme {
if theme != nil { dupe := *theme
dupe := *theme return &dupe
return &dupe
}
return nil
} }
func parseTheme(defaultTheme *tui.ColorTheme, str string) *tui.ColorTheme { func parseTheme(defaultTheme *tui.ColorTheme, str string) *tui.ColorTheme {
@ -619,54 +616,76 @@ func parseTheme(defaultTheme *tui.ColorTheme, str string) *tui.ColorTheme {
continue continue
} }
pair := strings.Split(str, ":") components := strings.Split(str, ":")
if len(pair) != 2 { if len(components) < 2 {
fail() fail()
} }
var ansi tui.Color cattr := tui.NewColorAttr()
if rrggbb.MatchString(pair[1]) { for _, component := range components[1:] {
ansi = tui.HexToColor(pair[1]) switch component {
} else { case "regular":
ansi32, err := strconv.Atoi(pair[1]) cattr.Attr = tui.AttrRegular
if err != nil || ansi32 < -1 || ansi32 > 255 { case "bold", "strong":
fail() 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": case "fg":
theme.Fg = ansi theme.Fg = cattr
case "bg": case "bg":
theme.Bg = ansi theme.Bg = cattr
case "preview-fg": case "preview-fg":
theme.PreviewFg = ansi theme.PreviewFg = cattr
case "preview-bg": case "preview-bg":
theme.PreviewBg = ansi theme.PreviewBg = cattr
case "fg+": case "fg+":
theme.Current = ansi theme.Current = cattr
case "bg+": case "bg+":
theme.DarkBg = ansi theme.DarkBg = cattr
case "gutter": case "gutter":
theme.Gutter = ansi theme.Gutter = cattr
case "hl": case "hl":
theme.Match = ansi theme.Match = cattr
case "hl+": case "hl+":
theme.CurrentMatch = ansi theme.CurrentMatch = cattr
case "border": case "border":
theme.Border = ansi theme.Border = cattr
case "prompt": case "prompt":
theme.Prompt = ansi theme.Prompt = cattr
case "spinner": case "spinner":
theme.Spinner = ansi theme.Spinner = cattr
case "info": case "info":
theme.Info = ansi theme.Info = cattr
case "pointer": case "pointer":
theme.Cursor = ansi theme.Cursor = cattr
case "marker": case "marker":
theme.Selected = ansi theme.Selected = cattr
case "header": case "header":
theme.Header = ansi theme.Header = cattr
default: default:
fail() fail()
} }
@ -1180,7 +1199,7 @@ func parseOptions(opts *Options, allArgs []string) {
case "--no-mouse": case "--no-mouse":
opts.Mouse = false opts.Mouse = false
case "+c", "--no-color": case "+c", "--no-color":
opts.Theme = nil opts.Theme = tui.NoColorTheme()
case "+2", "--no-256": case "+2", "--no-256":
opts.Theme = tui.Default16 opts.Theme = tui.Default16
case "--black": 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 // ParseOptions parses command-line options

View File

@ -295,7 +295,7 @@ func TestColorSpec(t *testing.T) {
} }
customized := parseTheme(theme, "fg:231,bg:232") 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") t.Errorf("color not customized")
} }
if *tui.Dark256 == *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) { func TestDefaultCtrlNP(t *testing.T) {
check := func(words []string, key int, expected actionType) { check := func(words []string, key int, expected actionType) {
opts := defaultOptions() opts := defaultOptions()

View File

@ -15,7 +15,6 @@ type Offset [2]int32
type colorOffset struct { type colorOffset struct {
offset [2]int32 offset [2]int32
color tui.ColorPair color tui.ColorPair
attr tui.Attr
} }
type Result struct { type Result struct {
@ -87,14 +86,14 @@ func minRank() Result {
return Result{item: &minItem, points: [4]uint16{math.MaxUint16, 0, 0, 0}} 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() itemColors := result.item.Colors()
// No ANSI code, or --color=no // No ANSI codes
if len(itemColors) == 0 { if len(itemColors) == 0 {
var offsets []colorOffset var offsets []colorOffset
for _, off := range matchOffsets { 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 return offsets
} }
@ -111,17 +110,19 @@ func (result *Result) colorOffsets(matchOffsets []Offset, theme *tui.ColorTheme,
maxCol = ansi.offset[1] maxCol = ansi.offset[1]
} }
} }
cols := make([]int, maxCol)
cols := make([]int, maxCol)
for colorIndex, ansi := range itemColors { for colorIndex, ansi := range itemColors {
for i := ansi.offset[0]; i < ansi.offset[1]; i++ { 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 _, off := range matchOffsets {
for i := off[0]; i < off[1]; i++ { 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 curr := 0
start := 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 var colors []colorOffset
add := func(idx int) { add := func(idx int) {
if curr != 0 && idx > start { if curr != 0 && idx > start {
if curr == -1 { if curr < 0 {
colors = append(colors, colorOffset{ color := colMatch
offset: [2]int32{int32(start), int32(idx)}, color: color, attr: attr}) if curr < -1 && theme.Colored {
} else { origColor := ansiToColorPair(itemColors[-curr-2], colMatch)
ansi := itemColors[curr-1] color = origColor.MergeNonDefault(color)
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: color})
} else {
ansi := itemColors[curr-1]
colors = append(colors, colorOffset{ colors = append(colors, colorOffset{
offset: [2]int32{int32(start), int32(idx)}, offset: [2]int32{int32(start), int32(idx)},
color: tui.NewColorPair(fg, bg), color: ansiToColorPair(ansi, colBase)})
attr: ansi.color.attr.Merge(attr)})
} }
} }
} }

View File

@ -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 := Result{
item: &Item{ item: &Item{
colors: &[]ansiOffset{ colors: &[]ansiOffset{
ansiOffset{[2]int32{0, 20}, ansiState{1, 5, 0}}, {[2]int32{0, 20}, ansiState{1, 5, 0}},
ansiOffset{[2]int32{22, 27}, ansiState{2, 6, tui.Bold}}, {[2]int32{22, 27}, ansiState{2, 6, tui.Bold}},
ansiOffset{[2]int32{30, 32}, ansiState{3, 7, 0}}, {[2]int32{30, 32}, ansiState{3, 7, 0}},
ansiOffset{[2]int32{33, 40}, ansiState{4, 8, tui.Bold}}}}} {[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) colBase := tui.NewColorPair(89, 189, tui.AttrUndefined)
colors := item.colorOffsets(offsets, tui.Dark256, pair, tui.AttrRegular, true) colMatch := tui.NewColorPair(99, 199, tui.AttrUndefined)
assert := func(idx int, b int32, e int32, c tui.ColorPair, bold bool) { colors := item.colorOffsets(offsets, tui.Dark256, colBase, colMatch, true)
var attr tui.Attr assert := func(idx int, b int32, e int32, c tui.ColorPair) {
if bold {
attr = tui.Bold
}
o := colors[idx] o := colors[idx]
if o.offset[0] != b || o.offset[1] != e || o.color != c || o.attr != attr { if o.offset[0] != b || o.offset[1] != e || o.color != c {
t.Error(o) t.Error(o, b, e, c)
} }
} }
assert(0, 0, 5, tui.NewColorPair(1, 5), false) // [{[0 5] {1 5 0}} {[5 15] {99 199 0}} {[15 20] {1 5 0}}
assert(1, 5, 15, pair, false) // {[22 25] {2 6 1}} {[25 27] {99 199 1}} {[27 30] {99 199 0}}
assert(2, 15, 20, tui.NewColorPair(1, 5), false) // {[30 32] {99 199 0}} {[32 33] {99 199 0}} {[33 35] {99 199 1}}
assert(3, 22, 25, tui.NewColorPair(2, 6), true) // {[35 40] {4 8 1}}]
assert(4, 25, 35, pair, false) assert(0, 0, 5, tui.NewColorPair(1, 5, tui.AttrUndefined))
assert(5, 35, 40, tui.NewColorPair(4, 8), true) 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))
} }

View File

@ -23,6 +23,7 @@ import (
var placeholder *regexp.Regexp var placeholder *regexp.Regexp
var numericPrefix *regexp.Regexp var numericPrefix *regexp.Regexp
var whiteSuffix *regexp.Regexp
var activeTempFiles []string var activeTempFiles []string
const ellipsis string = ".." const ellipsis string = ".."
@ -31,6 +32,7 @@ const clearCode string = "\x1b[2J"
func init() { func init() {
placeholder = regexp.MustCompile(`\\?(?:{[+sf]*[0-9,-.]*}|{q}|{\+?f?nf?})`) placeholder = regexp.MustCompile(`\\?(?:{[+sf]*[0-9,-.]*}|{q}|{\+?f?nf?})`)
numericPrefix = regexp.MustCompile(`^[[:punct:]]*([0-9]+)`) numericPrefix = regexp.MustCompile(`^[[:punct:]]*([0-9]+)`)
whiteSuffix = regexp.MustCompile(`\s*$`)
activeTempFiles = []string{} activeTempFiles = []string{}
} }
@ -515,9 +517,29 @@ func (t *Terminal) parsePrompt(prompt string) (func(), int) {
var state *ansiState var state *ansiState
trimmed, colors, _ := extractColor(prompt, state, nil) trimmed, colors, _ := extractColor(prompt, state, nil)
item := &Item{text: util.ToChars([]byte(trimmed)), colors: colors} 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() { output := func() {
t.printHighlighted( 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) _, promptLen := t.processTabs([]rune(trimmed), 0)
@ -839,8 +861,8 @@ func (t *Terminal) printPrompt() {
t.prompt() t.prompt()
before, after := t.updatePromptOffset() before, after := t.updatePromptOffset()
t.window.CPrint(tui.ColNormal, t.strong, string(before)) t.window.CPrint(tui.ColInput, string(before))
t.window.CPrint(tui.ColNormal, t.strong, string(after)) t.window.CPrint(tui.ColInput, string(after))
} }
func (t *Terminal) trimMessage(message string, maxWidth int) string { func (t *Terminal) trimMessage(message string, maxWidth int) string {
@ -859,7 +881,7 @@ func (t *Terminal) printInfo() {
if t.reading { if t.reading {
duration := int64(spinnerDuration) duration := int64(spinnerDuration)
idx := (time.Now().UnixNano() % (duration * int64(len(t.spinner)))) / duration 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) t.move(1, 2, false)
pos = 2 pos = 2
@ -870,9 +892,9 @@ func (t *Terminal) printInfo() {
} }
t.move(0, pos, true) t.move(0, pos, true)
if t.reading { if t.reading {
t.window.CPrint(tui.ColSpinner, t.strong, " < ") t.window.CPrint(tui.ColSpinner, " < ")
} else { } else {
t.window.CPrint(tui.ColPrompt, t.strong, " < ") t.window.CPrint(tui.ColPrompt, " < ")
} }
pos += len(" < ") pos += len(" < ")
case infoHidden: case infoHidden:
@ -903,7 +925,7 @@ func (t *Terminal) printInfo() {
output = fmt.Sprintf("[Command failed: %s]", *t.failed) output = fmt.Sprintf("[Command failed: %s]", *t.failed)
} }
output = t.trimMessage(output, t.window.Width()-pos) 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() { func (t *Terminal) printHeader() {
@ -928,7 +950,7 @@ func (t *Terminal) printHeader() {
t.move(line, 2, true) t.move(line, 2, true)
t.printHighlighted(Result{item: item}, 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) { func (t *Terminal) printItem(result Result, line int, i int, current bool) {
item := result.item item := result.item
_, selected := t.selected[item.Index()] _, selected := t.selected[item.Index()]
label := t.pointerEmpty label := ""
if t.jumping != jumpDisabled { if t.jumping != jumpDisabled {
if i < len(t.jumpLabels) { if i < len(t.jumpLabels) {
// Striped // Striped
@ -983,21 +1005,29 @@ func (t *Terminal) printItem(result Result, line int, i int, current bool) {
t.move(line, 0, false) t.move(line, 0, false)
if current { if current {
t.window.CPrint(tui.ColCurrentCursor, t.strong, label) if len(label) == 0 {
if selected { t.window.CPrint(tui.ColCurrentCursorEmpty, t.pointerEmpty)
t.window.CPrint(tui.ColCurrentSelected, t.strong, t.marker)
} else { } else {
t.window.CPrint(tui.ColCurrentSelected, t.strong, t.markerEmpty) t.window.CPrint(tui.ColCurrentCursor, label)
} }
newLine.width = t.printHighlighted(result, t.strong, tui.ColCurrent, tui.ColCurrentMatch, true, true)
} else {
t.window.CPrint(tui.ColCursor, t.strong, label)
if selected { if selected {
t.window.CPrint(tui.ColSelected, t.strong, t.marker) t.window.CPrint(tui.ColCurrentSelected, t.marker)
} else {
t.window.CPrint(tui.ColCurrentSelectedEmpty, t.markerEmpty)
}
newLine.width = t.printHighlighted(result, tui.ColCurrent, tui.ColCurrentMatch, true, true)
} else {
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.marker)
} else { } else {
t.window.Print(t.markerEmpty) 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 fillSpaces := prevLine.width - newLine.width
if fillSpaces > 0 { if fillSpaces > 0 {
@ -1051,7 +1081,7 @@ func (t *Terminal) overflow(runes []rune, max int) bool {
return t.displayWidthWithLimit(runes, 0, max) > max 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 item := result.item
// Overflow // Overflow
@ -1076,7 +1106,7 @@ func (t *Terminal) printHighlighted(result Result, attr tui.Attr, col1 tui.Color
maxe = util.Max(maxe, int(offset[1])) 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) maxWidth := t.window.Width() - (t.pointerLen + t.markerLen + 1)
maxe = util.Constrain(maxe+util.Min(maxWidth/2-2, t.hscrollOff), 0, len(text)) maxe = util.Constrain(maxe+util.Min(maxWidth/2-2, t.hscrollOff), 0, len(text))
displayWidth := t.displayWidthWithLimit(text, 0, maxWidth) 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) e := util.Constrain32(offset.offset[1], index, maxOffset)
substr, prefixWidth = t.processTabs(text[index:b], prefixWidth) substr, prefixWidth = t.processTabs(text[index:b], prefixWidth)
t.window.CPrint(col1, attr, substr) t.window.CPrint(colBase, substr)
if b < e { if b < e {
substr, prefixWidth = t.processTabs(text[b:e], prefixWidth) substr, prefixWidth = t.processTabs(text[b:e], prefixWidth)
t.window.CPrint(offset.color, offset.attr, substr) t.window.CPrint(offset.color, substr)
} }
index = e index = e
@ -1148,7 +1178,7 @@ func (t *Terminal) printHighlighted(result Result, attr tui.Attr, col1 tui.Color
} }
if index < maxOffset { if index < maxOffset {
substr, _ = t.processTabs(text[index:], prefixWidth) substr, _ = t.processTabs(text[index:], prefixWidth)
t.window.CPrint(col1, attr, substr) t.window.CPrint(colBase, substr)
} }
return displayWidth return displayWidth
} }
@ -1161,7 +1191,7 @@ func (t *Terminal) renderPreviewSpinner() {
if !t.previewer.scrollable { if !t.previewer.scrollable {
if maxWidth > 0 { if maxWidth > 0 {
t.pwindow.Move(0, maxWidth-1) t.pwindow.Move(0, maxWidth-1)
t.pwindow.CPrint(tui.ColSpinner, t.strong, spin) t.pwindow.CPrint(tui.ColSpinner, spin)
} }
} else { } else {
offsetString := fmt.Sprintf("%d/%d", t.previewer.offset+1, numLines) offsetString := fmt.Sprintf("%d/%d", t.previewer.offset+1, numLines)
@ -1173,8 +1203,8 @@ func (t *Terminal) renderPreviewSpinner() {
pos := maxWidth - t.displayWidth(offsetRunes) pos := maxWidth - t.displayWidth(offsetRunes)
t.pwindow.Move(0, pos) t.pwindow.Move(0, pos)
if maxWidth > 0 { if maxWidth > 0 {
t.pwindow.CPrint(tui.ColSpinner, t.strong, spin) t.pwindow.CPrint(tui.ColSpinner, spin)
t.pwindow.CPrint(tui.ColInfo, tui.Reverse, string(offsetRunes)) t.pwindow.CPrint(tui.ColInfo.WithAttr(tui.Reverse), string(offsetRunes))
} }
} }
} }
@ -1185,7 +1215,7 @@ func (t *Terminal) renderPreviewText(unchanged bool) {
lineNo := -t.previewer.offset lineNo := -t.previewer.offset
height := t.pwindow.Height() height := t.pwindow.Height()
if unchanged { if unchanged {
t.pwindow.Move(0, 0) t.pwindow.MoveAndClear(0, 0)
} else { } else {
t.previewed.filled = false t.previewed.filled = false
t.pwindow.Erase() t.pwindow.Erase()
@ -1205,7 +1235,7 @@ func (t *Terminal) renderPreviewText(unchanged bool) {
} }
str, width := t.processTabs(trimmed, prefixWidth) str, width := t.processTabs(trimmed, prefixWidth)
prefixWidth += width 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) fillRet = t.pwindow.CFill(ansi.fg, ansi.bg, ansi.attr, str)
} else { } else {
fillRet = t.pwindow.CFill(tui.ColPreview.Fg(), tui.ColPreview.Bg(), tui.AttrRegular, str) 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()) message := t.trimMessage("Loading ..", t.pwindow.Width())
pos := t.pwindow.Width() - len(message) pos := t.pwindow.Width() - len(message)
t.pwindow.Move(0, pos) 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) { func (t *Terminal) processTabs(runes []rune, prefixWidth int) (string, int) {

View File

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

View File

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

View File

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