diff --git a/comp.go b/comp.go index 66abaf2..95348dd 100644 --- a/comp.go +++ b/comp.go @@ -67,6 +67,9 @@ var ( "ignorecase", "noignorecase", "ignorecase!", + "lscolors", + "nolscolors", + "lscolors!", "preview", "nopreview", "preview!", diff --git a/doc.go b/doc.go index 04c6e63..96631ed 100644 --- a/doc.go +++ b/doc.go @@ -79,6 +79,7 @@ The following options can be used to customize the behavior of lf: globsearch boolean (default off) hidden boolean (default off) ignorecase boolean (default on) + lscolors boolean (default on if LSCOLORS or LS_COLORS environment variable is set) preview boolean (default on) reverse boolean (default off) smartcase boolean (default on) diff --git a/docstring.go b/docstring.go index 7c12645..87094d7 100644 --- a/docstring.go +++ b/docstring.go @@ -83,6 +83,7 @@ The following options can be used to customize the behavior of lf: globsearch boolean (default off) hidden boolean (default off) ignorecase boolean (default on) + lscolors boolean (default on if LSCOLORS or LS_COLORS environment variable is set) preview boolean (default on) reverse boolean (default off) smartcase boolean (default on) diff --git a/eval.go b/eval.go index 3536ad2..9cc1050 100644 --- a/eval.go +++ b/eval.go @@ -47,6 +47,12 @@ func (e *setExpr) eval(app *app, args []string) { gOpts.ignorecase = false case "ignorecase!": gOpts.ignorecase = !gOpts.ignorecase + case "lscolors": + gOpts.lscolors = true + case "nolscolors": + gOpts.lscolors = false + case "lscolors!": + gOpts.lscolors = !gOpts.lscolors case "preview": gOpts.preview = true case "nopreview": diff --git a/lscolors.go b/lscolors.go new file mode 100644 index 0000000..5889579 --- /dev/null +++ b/lscolors.go @@ -0,0 +1,148 @@ +package main + +import ( + "log" + "os" + "strconv" + "strings" + + "github.com/nsf/termbox-go" +) + +func init() { + if lscolors := os.Getenv("LSCOLORS"); lscolors != "" { + gColors.parseBSD(lscolors) + gOpts.lscolors = true + } + if ls_colors := os.Getenv("LS_COLORS"); ls_colors != "" { + gColors.parseLinux(ls_colors) + gOpts.lscolors = true + } +} + +type lsColorsEntry struct { + fg termbox.Attribute + bg termbox.Attribute +} + +type lsColorsT map[string]lsColorsEntry + +var gColors = make(lsColorsT) + +// This function parses LS_COLORS environment variable +func (lsc lsColorsT) parseLinux(env string) { + e := strings.Split(env, ":") + for _, e := range e { + i := strings.IndexRune(e, '=') + if i >= 0 { + key := e[:i] + values := strings.Split(e[i+1:], ";") + var fg, bg termbox.Attribute + for _, a := range values { + i, _ := strconv.Atoi(a) // strconv.Atoi() will returns 0 on error which is termbox.ColorDefault. No need to check the error. + + switch { + case i == 1: + fg = fg | termbox.AttrBold + case i == 4: + fg = fg | termbox.AttrUnderline + case i == 5: + // Flashing text + case i == 7: + fg = fg | termbox.AttrReverse + case i == 8: + // Concealed + case 30 <= i && i <= 37: + fg = fg | termbox.Attribute(i-29) + case 40 <= i && i <= 47: + bg = bg | termbox.Attribute(i-39) + } + } + lsc[key] = lsColorsEntry{fg: fg, bg: bg} + } + } +} + +// This function parses LSCOLORS variable. See http://www.manpages.info/freebsd/ls.1.html +func (lsc lsColorsT) parseBSD(env string) { + if len(env) != 22 { + log.Print("LSCOLORS variable invalid") + return + } + + unixLsColors := []string{"di", "so", "ln", "pi", "ex", "bd", "cd", "su", "sg", "tw", "ow"} + unixColors := map[byte]termbox.Attribute{ + 'a': termbox.ColorBlack, + 'b': termbox.ColorRed, + 'c': termbox.ColorGreen, + 'd': termbox.ColorYellow, // should be brown + 'e': termbox.ColorBlue, + 'f': termbox.ColorMagenta, + 'g': termbox.ColorCyan, + 'h': termbox.ColorWhite, // should be light grey + 'A': termbox.AttrBold | termbox.ColorBlack, + 'B': termbox.AttrBold | termbox.ColorRed, + 'C': termbox.AttrBold | termbox.ColorGreen, + 'D': termbox.AttrBold | termbox.ColorWhite, // brown + 'E': termbox.AttrBold | termbox.ColorBlue, + 'F': termbox.AttrBold | termbox.ColorMagenta, + 'G': termbox.AttrBold | termbox.ColorCyan, + 'H': termbox.AttrBold | termbox.ColorWhite, // light grey + } + + getColor := func(r byte) termbox.Attribute { + if color, ok := unixColors[r]; ok { + return color + } else { + return termbox.ColorDefault + } + } + + for i, key := range unixLsColors { + lsc[key] = lsColorsEntry{fg: getColor(env[i*2]), bg: getColor(env[i*2+1])} + } +} + +// This function returns foreground and background colors for given file +func (lsc lsColorsT) getColors(f *file) (termbox.Attribute, termbox.Attribute) { + var key = "" + + switch { + case f.Mode()&os.ModeSticky != 0: + key = "st" + case f.Mode()&os.ModeSetuid != 0: + key = "su" + case f.Mode()&os.ModeSetgid != 0: + key = "sg" + case f.IsDir(): + key = "di" + case f.linkState == working: + key = "ln" + case f.Mode()&os.ModeNamedPipe != 0: + key = "pi" + case f.Mode()&os.ModeSocket != 0: + key = "so" + case f.Mode()&os.ModeCharDevice != 0: + key = "cd" + case f.Mode()&os.ModeDevice != 0: + key = "bd" + case f.linkState == broken: + key = "or" + case f.Mode().IsRegular() && f.Mode()&0111 != 0: + key = "ex" + default: + if extI := strings.LastIndexByte(f.Name(), '.'); extI > 0 { + key = "*" + f.Name()[extI:] + } + } + + if val, ok := lsc[key]; ok { + return val.fg, val.bg + } else { + if val, ok = lsc["no"]; ok { + return val.fg, val.bg + } else { + return termbox.ColorDefault, termbox.ColorDefault + } + } +} diff --git a/opts.go b/opts.go index b2892e9..aff1548 100644 --- a/opts.go +++ b/opts.go @@ -8,6 +8,7 @@ var gOpts struct { globsearch bool hidden bool ignorecase bool + lscolors bool preview bool reverse bool smartcase bool @@ -34,6 +35,7 @@ func init() { gOpts.globsearch = false gOpts.hidden = false gOpts.ignorecase = true + // gOpts.lscolors = true this option is initialized in lscolors.go init() depending on whether the corresponding environment variable is set gOpts.preview = true gOpts.reverse = false gOpts.smartcase = true diff --git a/ui.go b/ui.go index bb18778..bf6e871 100644 --- a/ui.go +++ b/ui.go @@ -296,28 +296,32 @@ func (win *win) printDir(dir *dir, marks map[string]int, saves map[string]bool) end := min(beg+win.h, maxind+1) for i, f := range dir.fi[beg:end] { - switch { - case f.linkState == working: - fg = termbox.ColorCyan - if f.Mode().IsDir() { - fg |= termbox.AttrBold + if gOpts.lscolors { + fg, bg = gColors.getColors(f) + } else { + switch { + case f.linkState == working: + fg = termbox.ColorCyan + if f.Mode().IsDir() { + fg |= termbox.AttrBold + } + case f.linkState == broken: + fg = termbox.ColorMagenta + case f.Mode().IsRegular(): + if f.Mode()&0111 != 0 { + fg = termbox.AttrBold | termbox.ColorGreen + } else { + fg = termbox.ColorDefault + } + case f.Mode().IsDir(): + fg = termbox.AttrBold | termbox.ColorBlue + case f.Mode()&os.ModeNamedPipe != 0: + fg = termbox.ColorRed + case f.Mode()&os.ModeSocket != 0: + fg = termbox.ColorYellow + case f.Mode()&os.ModeDevice != 0: + fg = termbox.ColorWhite } - case f.linkState == broken: - fg = termbox.ColorMagenta - case f.Mode().IsRegular(): - if f.Mode()&0111 != 0 { - fg = termbox.AttrBold | termbox.ColorGreen - } else { - fg = termbox.ColorDefault - } - case f.Mode().IsDir(): - fg = termbox.AttrBold | termbox.ColorBlue - case f.Mode()&os.ModeNamedPipe != 0: - fg = termbox.ColorRed - case f.Mode()&os.ModeSocket != 0: - fg = termbox.ColorYellow - case f.Mode()&os.ModeDevice != 0: - fg = termbox.ColorWhite } path := filepath.Join(dir.path, f.Name())