mirror of
https://github.com/Llewellynvdm/fzf.git
synced 2024-12-23 11:29:01 +00:00
Color customization (#245)
This commit is contained in:
parent
446e822723
commit
fdbfe36c0b
@ -91,21 +91,38 @@ Enable processing of ANSI color codes
|
|||||||
.B "--no-mouse"
|
.B "--no-mouse"
|
||||||
Disable mouse
|
Disable mouse
|
||||||
.TP
|
.TP
|
||||||
.B "--color=COL"
|
.BI "--color=" "[BASE_SCHEME][,COLOR:ANSI]"
|
||||||
Color scheme: [dark|light|16|bw]
|
Color configuration. The name of the base color scheme is followed by custom
|
||||||
.br
|
color mappings. Ansi color code of -1 denotes terminal default
|
||||||
(default: dark on 256-color terminal, otherwise 16)
|
foreground/background color.
|
||||||
.br
|
|
||||||
.R ""
|
.RS
|
||||||
.br
|
e.g. \fBfzf --color=bg+:24\fR
|
||||||
.BR dark " Color scheme for dark 256-color terminal"
|
\fBfzf --color=light,fg:232,bg:255,bg+:116,info:27\fR
|
||||||
.br
|
.RE
|
||||||
.BR light " Color scheme for light 256-color terminal"
|
|
||||||
.br
|
.RS
|
||||||
.BR 16 " Color scheme for 16-color terminal"
|
.B BASE SCHEME:
|
||||||
.br
|
(default: dark on 256-color terminal, otherwise 16)
|
||||||
.BR bw " No colors"
|
|
||||||
.br
|
\fBdark \fRColor scheme for dark 256-color terminal
|
||||||
|
\fBlight \fRColor scheme for light 256-color terminal
|
||||||
|
\fB16 \fRColor scheme for 16-color terminal
|
||||||
|
\fBbw \fRNo colors
|
||||||
|
|
||||||
|
.B COLOR:
|
||||||
|
\fBfg \fRText
|
||||||
|
\fBbg \fRBackground
|
||||||
|
\fBhl \fRHighlighted substrings
|
||||||
|
\fBfg+ \fRText (current line)
|
||||||
|
\fBbg+ \fRBackground (current line)
|
||||||
|
\fBhl+ \fRHighlighted substrings (current line)
|
||||||
|
\fBinfo \fRInfo
|
||||||
|
\fBprompt \fRPrompt
|
||||||
|
\fBpointer \fRPointer to the current line
|
||||||
|
\fBmarker \fRMulti-select marker
|
||||||
|
\fBspinner \fRStreaming input indicator
|
||||||
|
.RE
|
||||||
.TP
|
.TP
|
||||||
.B "--black"
|
.B "--black"
|
||||||
Use black background
|
Use black background
|
||||||
|
@ -106,15 +106,18 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type ColorTheme struct {
|
type ColorTheme struct {
|
||||||
darkBg C.short
|
UseDefault bool
|
||||||
prompt C.short
|
Fg int16
|
||||||
match C.short
|
Bg int16
|
||||||
current C.short
|
DarkBg int16
|
||||||
currentMatch C.short
|
Prompt int16
|
||||||
spinner C.short
|
Match int16
|
||||||
info C.short
|
Current int16
|
||||||
cursor C.short
|
CurrentMatch int16
|
||||||
selected C.short
|
Spinner int16
|
||||||
|
Info int16
|
||||||
|
Cursor int16
|
||||||
|
Selected int16
|
||||||
}
|
}
|
||||||
|
|
||||||
type Event struct {
|
type Event struct {
|
||||||
@ -142,7 +145,10 @@ var (
|
|||||||
Default16 *ColorTheme
|
Default16 *ColorTheme
|
||||||
Dark256 *ColorTheme
|
Dark256 *ColorTheme
|
||||||
Light256 *ColorTheme
|
Light256 *ColorTheme
|
||||||
DarkBG C.short
|
FG int
|
||||||
|
CurrentFG int
|
||||||
|
BG int
|
||||||
|
DarkBG int
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@ -150,35 +156,44 @@ func init() {
|
|||||||
_clickY = []int{}
|
_clickY = []int{}
|
||||||
_colorMap = make(map[int]int)
|
_colorMap = make(map[int]int)
|
||||||
Default16 = &ColorTheme{
|
Default16 = &ColorTheme{
|
||||||
darkBg: C.COLOR_BLACK,
|
UseDefault: true,
|
||||||
prompt: C.COLOR_BLUE,
|
Fg: 15,
|
||||||
match: C.COLOR_GREEN,
|
Bg: 0,
|
||||||
current: C.COLOR_YELLOW,
|
DarkBg: C.COLOR_BLACK,
|
||||||
currentMatch: C.COLOR_GREEN,
|
Prompt: C.COLOR_BLUE,
|
||||||
spinner: C.COLOR_GREEN,
|
Match: C.COLOR_GREEN,
|
||||||
info: C.COLOR_WHITE,
|
Current: C.COLOR_YELLOW,
|
||||||
cursor: C.COLOR_RED,
|
CurrentMatch: C.COLOR_GREEN,
|
||||||
selected: C.COLOR_MAGENTA}
|
Spinner: C.COLOR_GREEN,
|
||||||
|
Info: C.COLOR_WHITE,
|
||||||
|
Cursor: C.COLOR_RED,
|
||||||
|
Selected: C.COLOR_MAGENTA}
|
||||||
Dark256 = &ColorTheme{
|
Dark256 = &ColorTheme{
|
||||||
darkBg: 236,
|
UseDefault: true,
|
||||||
prompt: 110,
|
Fg: 15,
|
||||||
match: 108,
|
Bg: 0,
|
||||||
current: 254,
|
DarkBg: 236,
|
||||||
currentMatch: 151,
|
Prompt: 110,
|
||||||
spinner: 148,
|
Match: 108,
|
||||||
info: 144,
|
Current: 254,
|
||||||
cursor: 161,
|
CurrentMatch: 151,
|
||||||
selected: 168}
|
Spinner: 148,
|
||||||
|
Info: 144,
|
||||||
|
Cursor: 161,
|
||||||
|
Selected: 168}
|
||||||
Light256 = &ColorTheme{
|
Light256 = &ColorTheme{
|
||||||
darkBg: 251,
|
UseDefault: true,
|
||||||
prompt: 25,
|
Fg: 15,
|
||||||
match: 66,
|
Bg: 0,
|
||||||
current: 237,
|
DarkBg: 251,
|
||||||
currentMatch: 23,
|
Prompt: 25,
|
||||||
spinner: 65,
|
Match: 66,
|
||||||
info: 101,
|
Current: 237,
|
||||||
cursor: 161,
|
CurrentMatch: 23,
|
||||||
selected: 168}
|
Spinner: 65,
|
||||||
|
Info: 101,
|
||||||
|
Cursor: 161,
|
||||||
|
Selected: 168}
|
||||||
}
|
}
|
||||||
|
|
||||||
func attrColored(pair int, bold bool) C.int {
|
func attrColored(pair int, bold bool) C.int {
|
||||||
@ -268,23 +283,35 @@ func Init(theme *ColorTheme, black bool, mouse bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func initPairs(theme *ColorTheme, black bool) {
|
func initPairs(theme *ColorTheme, black bool) {
|
||||||
var bg C.short
|
fg := C.short(theme.Fg)
|
||||||
|
bg := C.short(theme.Bg)
|
||||||
if black {
|
if black {
|
||||||
bg = C.COLOR_BLACK
|
bg = C.COLOR_BLACK
|
||||||
} else {
|
} else if theme.UseDefault {
|
||||||
C.use_default_colors()
|
fg = -1
|
||||||
bg = -1
|
bg = -1
|
||||||
|
C.use_default_colors()
|
||||||
|
}
|
||||||
|
if theme.UseDefault {
|
||||||
|
FG = -1
|
||||||
|
BG = -1
|
||||||
|
} else {
|
||||||
|
FG = int(fg)
|
||||||
|
BG = int(bg)
|
||||||
|
C.assume_default_colors(C.int(theme.Fg), C.int(bg))
|
||||||
}
|
}
|
||||||
|
|
||||||
DarkBG = theme.darkBg
|
CurrentFG = int(theme.Current)
|
||||||
C.init_pair(ColPrompt, theme.prompt, bg)
|
DarkBG = int(theme.DarkBg)
|
||||||
C.init_pair(ColMatch, theme.match, bg)
|
darkBG := C.short(DarkBG)
|
||||||
C.init_pair(ColCurrent, theme.current, DarkBG)
|
C.init_pair(ColPrompt, C.short(theme.Prompt), bg)
|
||||||
C.init_pair(ColCurrentMatch, theme.currentMatch, DarkBG)
|
C.init_pair(ColMatch, C.short(theme.Match), bg)
|
||||||
C.init_pair(ColSpinner, theme.spinner, bg)
|
C.init_pair(ColCurrent, C.short(theme.Current), darkBG)
|
||||||
C.init_pair(ColInfo, theme.info, bg)
|
C.init_pair(ColCurrentMatch, C.short(theme.CurrentMatch), darkBG)
|
||||||
C.init_pair(ColCursor, theme.cursor, DarkBG)
|
C.init_pair(ColSpinner, C.short(theme.Spinner), bg)
|
||||||
C.init_pair(ColSelected, theme.selected, DarkBG)
|
C.init_pair(ColInfo, C.short(theme.Info), bg)
|
||||||
|
C.init_pair(ColCursor, C.short(theme.Cursor), darkBG)
|
||||||
|
C.init_pair(ColSelected, C.short(theme.Selected), darkBG)
|
||||||
}
|
}
|
||||||
|
|
||||||
func Close() {
|
func Close() {
|
||||||
|
18
src/item.go
18
src/item.go
@ -143,13 +143,25 @@ func (item *Item) colorOffsets(color int, bold bool, current bool) []colorOffset
|
|||||||
offset: Offset{int32(start), int32(idx)}, color: color, bold: bold})
|
offset: Offset{int32(start), int32(idx)}, color: color, bold: bold})
|
||||||
} else {
|
} else {
|
||||||
ansi := item.colors[curr-1]
|
ansi := item.colors[curr-1]
|
||||||
|
fg := ansi.color.fg
|
||||||
|
if fg == -1 {
|
||||||
|
if current {
|
||||||
|
fg = curses.CurrentFG
|
||||||
|
} else {
|
||||||
|
fg = curses.FG
|
||||||
|
}
|
||||||
|
}
|
||||||
bg := ansi.color.bg
|
bg := ansi.color.bg
|
||||||
if current && bg == -1 {
|
if bg == -1 {
|
||||||
bg = int(curses.DarkBG)
|
if current {
|
||||||
|
bg = curses.DarkBG
|
||||||
|
} else {
|
||||||
|
bg = curses.BG
|
||||||
|
}
|
||||||
}
|
}
|
||||||
offsets = append(offsets, colorOffset{
|
offsets = append(offsets, colorOffset{
|
||||||
offset: Offset{int32(start), int32(idx)},
|
offset: Offset{int32(start), int32(idx)},
|
||||||
color: curses.PairFor(ansi.color.fg, bg),
|
color: curses.PairFor(fg, bg),
|
||||||
bold: ansi.color.bold || bold})
|
bold: ansi.color.bold || bold})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
111
src/options.go
111
src/options.go
@ -4,6 +4,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
|
|
||||||
@ -35,8 +36,7 @@ const usage = `usage: fzf [options]
|
|||||||
-m, --multi Enable multi-select with tab/shift-tab
|
-m, --multi Enable multi-select with tab/shift-tab
|
||||||
--ansi Enable processing of ANSI color codes
|
--ansi Enable processing of ANSI color codes
|
||||||
--no-mouse Disable mouse
|
--no-mouse Disable mouse
|
||||||
--color=COL Color scheme; [dark|light|16|bw]
|
--color=COLSPEC Base scheme (dark|light|16|bw) and/or custom colors
|
||||||
(default: dark on 256-color terminal, otherwise 16)
|
|
||||||
--black Use black background
|
--black Use black background
|
||||||
--reverse Reverse orientation
|
--reverse Reverse orientation
|
||||||
--no-hscroll Disable horizontal scroll
|
--no-hscroll Disable horizontal scroll
|
||||||
@ -121,14 +121,14 @@ type Options struct {
|
|||||||
Version bool
|
Version bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func defaultOptions() *Options {
|
func defaultTheme() *curses.ColorTheme {
|
||||||
var defaultTheme *curses.ColorTheme
|
|
||||||
if strings.Contains(os.Getenv("TERM"), "256") {
|
if strings.Contains(os.Getenv("TERM"), "256") {
|
||||||
defaultTheme = curses.Dark256
|
return curses.Dark256
|
||||||
} else {
|
|
||||||
defaultTheme = curses.Default16
|
|
||||||
}
|
}
|
||||||
|
return curses.Default16
|
||||||
|
}
|
||||||
|
|
||||||
|
func defaultOptions() *Options {
|
||||||
return &Options{
|
return &Options{
|
||||||
Mode: ModeFuzzy,
|
Mode: ModeFuzzy,
|
||||||
Case: CaseSmart,
|
Case: CaseSmart,
|
||||||
@ -141,7 +141,7 @@ func defaultOptions() *Options {
|
|||||||
Multi: false,
|
Multi: false,
|
||||||
Ansi: false,
|
Ansi: false,
|
||||||
Mouse: true,
|
Mouse: true,
|
||||||
Theme: defaultTheme,
|
Theme: defaultTheme(),
|
||||||
Black: false,
|
Black: false,
|
||||||
Reverse: false,
|
Reverse: false,
|
||||||
Hscroll: true,
|
Hscroll: true,
|
||||||
@ -187,6 +187,14 @@ func nextString(args []string, i *int, message string) string {
|
|||||||
return args[*i]
|
return args[*i]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func optionalNextString(args []string, i *int) string {
|
||||||
|
if len(args) > *i+1 {
|
||||||
|
*i++
|
||||||
|
return args[*i]
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
func optionalNumeric(args []string, i *int) int {
|
func optionalNumeric(args []string, i *int) int {
|
||||||
if len(args) > *i+1 {
|
if len(args) > *i+1 {
|
||||||
if strings.IndexAny(args[*i+1], "0123456789") == 0 {
|
if strings.IndexAny(args[*i+1], "0123456789") == 0 {
|
||||||
@ -277,20 +285,72 @@ func parseTiebreak(str string) tiebreak {
|
|||||||
return byLength
|
return byLength
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseTheme(str string) *curses.ColorTheme {
|
func dupeTheme(theme *curses.ColorTheme) *curses.ColorTheme {
|
||||||
switch strings.ToLower(str) {
|
dupe := *theme
|
||||||
case "dark":
|
return &dupe
|
||||||
return curses.Dark256
|
}
|
||||||
case "light":
|
|
||||||
return curses.Light256
|
func parseTheme(defaultTheme *curses.ColorTheme, str string) *curses.ColorTheme {
|
||||||
case "16":
|
theme := dupeTheme(defaultTheme)
|
||||||
return curses.Default16
|
for _, str := range strings.Split(strings.ToLower(str), ",") {
|
||||||
case "bw", "no":
|
switch str {
|
||||||
return nil
|
case "dark":
|
||||||
default:
|
theme = dupeTheme(curses.Dark256)
|
||||||
errorExit("invalid color scheme: " + str)
|
case "light":
|
||||||
|
theme = dupeTheme(curses.Light256)
|
||||||
|
case "16":
|
||||||
|
theme = dupeTheme(curses.Default16)
|
||||||
|
case "bw", "no":
|
||||||
|
theme = nil
|
||||||
|
default:
|
||||||
|
fail := func() {
|
||||||
|
errorExit("invalid color specification: " + str)
|
||||||
|
}
|
||||||
|
// Color is disabled
|
||||||
|
if theme == nil {
|
||||||
|
errorExit("colors disabled; cannot customize colors")
|
||||||
|
}
|
||||||
|
|
||||||
|
pair := strings.Split(str, ":")
|
||||||
|
if len(pair) != 2 {
|
||||||
|
fail()
|
||||||
|
}
|
||||||
|
ansi32, err := strconv.Atoi(pair[1])
|
||||||
|
if err != nil || ansi32 < -1 || ansi32 > 255 {
|
||||||
|
fail()
|
||||||
|
}
|
||||||
|
ansi := int16(ansi32)
|
||||||
|
switch pair[0] {
|
||||||
|
case "fg":
|
||||||
|
theme.Fg = ansi
|
||||||
|
theme.UseDefault = theme.UseDefault && ansi < 0
|
||||||
|
case "bg":
|
||||||
|
theme.Bg = ansi
|
||||||
|
theme.UseDefault = theme.UseDefault && ansi < 0
|
||||||
|
case "fg+":
|
||||||
|
theme.Current = ansi
|
||||||
|
case "bg+":
|
||||||
|
theme.DarkBg = ansi
|
||||||
|
case "hl":
|
||||||
|
theme.Match = ansi
|
||||||
|
case "hl+":
|
||||||
|
theme.CurrentMatch = ansi
|
||||||
|
case "prompt":
|
||||||
|
theme.Prompt = ansi
|
||||||
|
case "spinner":
|
||||||
|
theme.Spinner = ansi
|
||||||
|
case "info":
|
||||||
|
theme.Info = ansi
|
||||||
|
case "pointer":
|
||||||
|
theme.Cursor = ansi
|
||||||
|
case "marker":
|
||||||
|
theme.Selected = ansi
|
||||||
|
default:
|
||||||
|
fail()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return theme
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseKeymap(keymap map[int]actionType, toggleSort bool, str string) (map[int]actionType, bool) {
|
func parseKeymap(keymap map[int]actionType, toggleSort bool, str string) (map[int]actionType, bool) {
|
||||||
@ -400,7 +460,12 @@ func parseOptions(opts *Options, allArgs []string) {
|
|||||||
case "--bind":
|
case "--bind":
|
||||||
opts.Keymap, opts.ToggleSort = parseKeymap(opts.Keymap, opts.ToggleSort, nextString(allArgs, &i, "bind expression required"))
|
opts.Keymap, opts.ToggleSort = parseKeymap(opts.Keymap, opts.ToggleSort, nextString(allArgs, &i, "bind expression required"))
|
||||||
case "--color":
|
case "--color":
|
||||||
opts.Theme = parseTheme(nextString(allArgs, &i, "color scheme name required"))
|
spec := optionalNextString(allArgs, &i)
|
||||||
|
if len(spec) == 0 {
|
||||||
|
opts.Theme = defaultTheme()
|
||||||
|
} else {
|
||||||
|
opts.Theme = parseTheme(opts.Theme, spec)
|
||||||
|
}
|
||||||
case "--toggle-sort":
|
case "--toggle-sort":
|
||||||
opts.Keymap = checkToggleSort(opts.Keymap, nextString(allArgs, &i, "key name required"))
|
opts.Keymap = checkToggleSort(opts.Keymap, nextString(allArgs, &i, "key name required"))
|
||||||
opts.ToggleSort = true
|
opts.ToggleSort = true
|
||||||
@ -497,7 +562,7 @@ func parseOptions(opts *Options, allArgs []string) {
|
|||||||
} else if match, value := optString(arg, "--tiebreak="); match {
|
} else if match, value := optString(arg, "--tiebreak="); match {
|
||||||
opts.Tiebreak = parseTiebreak(value)
|
opts.Tiebreak = parseTiebreak(value)
|
||||||
} else if match, value := optString(arg, "--color="); match {
|
} else if match, value := optString(arg, "--color="); match {
|
||||||
opts.Theme = parseTheme(value)
|
opts.Theme = parseTheme(opts.Theme, value)
|
||||||
} else if match, value := optString(arg, "--bind="); match {
|
} else if match, value := optString(arg, "--bind="); match {
|
||||||
opts.Keymap, opts.ToggleSort = parseKeymap(opts.Keymap, opts.ToggleSort, value)
|
opts.Keymap, opts.ToggleSort = parseKeymap(opts.Keymap, opts.ToggleSort, value)
|
||||||
} else {
|
} else {
|
||||||
|
@ -155,3 +155,49 @@ func TestBind(t *testing.T) {
|
|||||||
}
|
}
|
||||||
check(actAbort, keymap[curses.F1])
|
check(actAbort, keymap[curses.F1])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestColorSpec(t *testing.T) {
|
||||||
|
theme := curses.Dark256
|
||||||
|
dark := parseTheme(theme, "dark")
|
||||||
|
if *dark != *theme {
|
||||||
|
t.Errorf("colors should be equivalent")
|
||||||
|
}
|
||||||
|
if dark == theme {
|
||||||
|
t.Errorf("point should not be equivalent")
|
||||||
|
}
|
||||||
|
|
||||||
|
light := parseTheme(theme, "dark,light")
|
||||||
|
if *light == *theme {
|
||||||
|
t.Errorf("should not be equivalent")
|
||||||
|
}
|
||||||
|
if *light != *curses.Light256 {
|
||||||
|
t.Errorf("colors should be equivalent")
|
||||||
|
}
|
||||||
|
if light == theme {
|
||||||
|
t.Errorf("point should not be equivalent")
|
||||||
|
}
|
||||||
|
|
||||||
|
customized := parseTheme(theme, "fg:231,bg:232")
|
||||||
|
if customized.Fg != 231 || customized.Bg != 232 {
|
||||||
|
t.Errorf("color not customized")
|
||||||
|
}
|
||||||
|
if *curses.Dark256 == *customized {
|
||||||
|
t.Errorf("colors should not be equivalent")
|
||||||
|
}
|
||||||
|
customized.Fg = curses.Dark256.Fg
|
||||||
|
customized.Bg = curses.Dark256.Bg
|
||||||
|
if *curses.Dark256 == *customized {
|
||||||
|
t.Errorf("colors should now be equivalent")
|
||||||
|
}
|
||||||
|
|
||||||
|
customized = parseTheme(theme, "fg:231,dark,bg:232")
|
||||||
|
if customized.Fg != curses.Dark256.Fg || customized.Bg == curses.Dark256.Bg {
|
||||||
|
t.Errorf("color not customized")
|
||||||
|
}
|
||||||
|
if customized.UseDefault {
|
||||||
|
t.Errorf("not using default colors")
|
||||||
|
}
|
||||||
|
if !curses.Dark256.UseDefault {
|
||||||
|
t.Errorf("using default colors")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user