handle 256 colors in $LS_COLORS

Related #37
This commit is contained in:
Gokcehan 2018-04-14 21:18:39 +03:00
parent f92e0f53a5
commit 5a9a829252
7 changed files with 227 additions and 235 deletions

215
colors.go Normal file
View File

@ -0,0 +1,215 @@
package main
import (
"log"
"os"
"path/filepath"
"strconv"
"strings"
"github.com/nsf/termbox-go"
)
type colorEntry struct {
fg termbox.Attribute
bg termbox.Attribute
}
type colorMap map[string]colorEntry
func parseColors() colorMap {
if env := os.Getenv("LS_COLORS"); env != "" {
return parseColorsGNU(env)
}
if env := os.Getenv("LSCOLORS"); env != "" {
return parseColorsBSD(env)
}
// default values from dircolors
defaultColors := []string{
// "rs=0",
"di=01;34",
"ln=01;36",
// "mh=00",
"pi=40;33",
"so=01;35",
"do=01;35",
"bd=40;33;01",
"cd=40;33;01",
"or=40;31;01",
// "mi=00",
"su=37;41",
"sg=30;43",
"ca=30;41",
"tw=30;42",
"ow=34;42",
"st=37;44",
"ex=01;32",
}
return parseColorsGNU(strings.Join(defaultColors, ":"))
}
func applyAnsiCodes(s string, fg, bg termbox.Attribute) (termbox.Attribute, termbox.Attribute) {
toks := strings.Split(s, ";")
var nums []int
for _, t := range toks {
if t == "" {
fg = termbox.ColorDefault
bg = termbox.ColorDefault
break
}
n, err := strconv.Atoi(t)
if err != nil {
log.Printf("converting escape code: %s", err)
continue
}
nums = append(nums, n)
}
// parse 256 color terminal ansi codes
if len(nums) == 3 {
if nums[0] == 48 && nums[1] == 5 {
bg = termbox.Attribute(nums[2] + 1)
}
if nums[0] == 38 && nums[1] == 5 {
fg = termbox.Attribute(nums[2] + 1)
}
}
for _, n := range nums {
attr, ok := gAnsiCodes[n]
if !ok {
log.Printf("unknown ansi code: %d", n)
continue
}
switch {
case n == 0:
fg, bg = attr, attr
case n == 1 || n == 4 || n == 7:
fg |= attr
case 30 <= n && n <= 37:
fg &= gAnsiColorResetMask
fg |= attr
case 40 <= n && n <= 47:
bg = attr
}
}
return fg, bg
}
// This function parses $LS_COLORS environment variable.
func parseColorsGNU(env string) colorMap {
colors := make(colorMap)
entries := strings.Split(env, ":")
for _, entry := range entries {
if entry == "" {
continue
}
pair := strings.Split(entry, "=")
if len(pair) != 2 {
log.Printf("invalid $LS_COLORS entry: %s", entry)
return colors
}
key, val := pair[0], pair[1]
fg, bg := applyAnsiCodes(val, termbox.ColorDefault, termbox.ColorDefault)
colors[key] = colorEntry{fg: fg, bg: bg}
}
return colors
}
// This function parses $LSCOLORS environment variable.
func parseColorsBSD(env string) colorMap {
colors := make(colorMap)
if len(env) != 22 {
log.Printf("invalid $LSCOLORS variable: %s", env)
return colors
}
colorNames := []string{"di", "ln", "so", "pi", "ex", "bd", "cd", "su", "sg", "tw", "ow"}
colorCodes := map[byte]termbox.Attribute{
'a': termbox.ColorBlack,
'b': termbox.ColorRed,
'c': termbox.ColorGreen,
'd': termbox.ColorYellow, // brown
'e': termbox.ColorBlue,
'f': termbox.ColorMagenta,
'g': termbox.ColorCyan,
'h': termbox.ColorWhite, // light grey
'A': termbox.AttrBold | termbox.ColorBlack,
'B': termbox.AttrBold | termbox.ColorRed,
'C': termbox.AttrBold | termbox.ColorGreen,
'D': termbox.AttrBold | termbox.ColorYellow, // brown
'E': termbox.AttrBold | termbox.ColorBlue,
'F': termbox.AttrBold | termbox.ColorMagenta,
'G': termbox.AttrBold | termbox.ColorCyan,
'H': termbox.AttrBold | termbox.ColorWhite, // light grey
'x': termbox.ColorDefault,
}
getColor := func(r byte) termbox.Attribute {
if color, ok := colorCodes[r]; ok {
return color
}
log.Printf("invalid $LSCOLORS entry: %c", r)
return termbox.ColorDefault
}
for i, key := range colorNames {
colors[key] = colorEntry{fg: getColor(env[i*2]), bg: getColor(env[i*2+1])}
}
return colors
}
func (cm colorMap) get(f *file) (termbox.Attribute, termbox.Attribute) {
var key string
switch {
case f.IsDir() && f.Mode()&os.ModeSticky != 0 && f.Mode()&0002 != 0:
key = "tw"
case f.IsDir() && f.Mode()&os.ModeSticky != 0:
key = "st"
case f.IsDir() && f.Mode()&0002 != 0:
key = "ow"
case f.IsDir():
key = "di"
case f.linkState == working:
key = "ln"
case f.linkState == broken:
key = "or"
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.Mode()&os.ModeSetuid != 0:
key = "su"
case f.Mode()&os.ModeSetgid != 0:
key = "sg"
case f.Mode().IsRegular() && f.Mode()&0111 != 0:
key = "ex"
default:
key = "*" + filepath.Ext(f.Name())
}
if val, ok := cm[key]; ok {
return val.fg, val.bg
}
if val, ok := cm["fi"]; ok {
return val.fg, val.bg
}
return termbox.ColorDefault, termbox.ColorDefault
}

2
doc.go
View File

@ -79,7 +79,7 @@ The following options can be used to customize the behavior of lf:
globsearch boolean (default off) globsearch boolean (default off)
hidden boolean (default off) hidden boolean (default off)
ignorecase boolean (default on) ignorecase boolean (default on)
lscolors boolean (default on if LSCOLORS or LS_COLORS environment variable is set) lscolors boolean (default on)
preview boolean (default on) preview boolean (default on)
reverse boolean (default off) reverse boolean (default off)
smartcase boolean (default on) smartcase boolean (default on)

View File

@ -83,7 +83,7 @@ The following options can be used to customize the behavior of lf:
globsearch boolean (default off) globsearch boolean (default off)
hidden boolean (default off) hidden boolean (default off)
ignorecase boolean (default on) ignorecase boolean (default on)
lscolors boolean (default on if LSCOLORS or LS_COLORS environment variable is set) lscolors boolean (default on)
preview boolean (default on) preview boolean (default on)
reverse boolean (default off) reverse boolean (default off)
smartcase boolean (default on) smartcase boolean (default on)

View File

@ -49,10 +49,13 @@ func (e *setExpr) eval(app *app, args []string) {
gOpts.ignorecase = !gOpts.ignorecase gOpts.ignorecase = !gOpts.ignorecase
case "lscolors": case "lscolors":
gOpts.lscolors = true gOpts.lscolors = true
app.ui.colors = parseColors()
case "nolscolors": case "nolscolors":
gOpts.lscolors = false gOpts.lscolors = false
app.ui.colors = parseColors()
case "lscolors!": case "lscolors!":
gOpts.lscolors = !gOpts.lscolors gOpts.lscolors = !gOpts.lscolors
app.ui.colors = parseColors()
case "preview": case "preview":
gOpts.preview = true gOpts.preview = true
case "nopreview": case "nopreview":

View File

@ -1,148 +0,0 @@
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
}
}
}

View File

@ -35,7 +35,7 @@ func init() {
gOpts.globsearch = false gOpts.globsearch = false
gOpts.hidden = false gOpts.hidden = false
gOpts.ignorecase = true gOpts.ignorecase = true
// gOpts.lscolors = true this option is initialized in lscolors.go init() depending on whether the corresponding environment variable is set gOpts.lscolors = true
gOpts.preview = true gOpts.preview = true
gOpts.reverse = false gOpts.reverse = false
gOpts.smartcase = true gOpts.smartcase = true

90
ui.go
View File

@ -126,60 +126,6 @@ func (win *win) renew(w, h, x, y int) {
win.w, win.h, win.x, win.y = w, h, x, y win.w, win.h, win.x, win.y = w, h, x, y
} }
func applyAnsiCodes(s string, fg, bg termbox.Attribute) (termbox.Attribute, termbox.Attribute) {
toks := strings.Split(s, ";")
var nums []int
for _, t := range toks {
if t == "" {
fg = termbox.ColorDefault
bg = termbox.ColorDefault
break
}
n, err := strconv.Atoi(t)
if err != nil {
log.Printf("converting escape code: %s", err)
continue
}
nums = append(nums, n)
}
// Parse 256 color terminal ansi codes
// termbox-go has a color offset of one, because of attributes
if len(nums) == 3 {
if nums[0] == 48 && nums[1] == 5 {
bg = termbox.Attribute(nums[2])
bg++
}
if nums[0] == 38 && nums[1] == 5 {
fg = termbox.Attribute(nums[2])
fg++
}
return fg, bg
}
for _, n := range nums {
attr, ok := gAnsiCodes[n]
if !ok {
log.Printf("unknown ansi code: %d", n)
continue
}
switch {
case n == 0:
fg, bg = attr, attr
case n == 1 || n == 4 || n == 7:
fg |= attr
case 30 <= n && n <= 37:
fg &= gAnsiColorResetMask
fg |= attr
case 40 <= n && n <= 47:
bg = attr
}
}
return fg, bg
}
func printLength(s string) int { func printLength(s string) int {
ind := 0 ind := 0
off := 0 off := 0
@ -267,7 +213,7 @@ func (win *win) printReg(reg *reg) {
return return
} }
func (win *win) printDir(dir *dir, marks map[string]int, saves map[string]bool) { func (win *win) printDir(dir *dir, marks map[string]int, saves map[string]bool, colors colorMap) {
if win.w < 3 { if win.w < 3 {
return return
} }
@ -296,33 +242,7 @@ func (win *win) printDir(dir *dir, marks map[string]int, saves map[string]bool)
end := min(beg+win.h, maxind+1) end := min(beg+win.h, maxind+1)
for i, f := range dir.fi[beg:end] { for i, f := range dir.fi[beg:end] {
if gOpts.lscolors { fg, bg = colors.get(f)
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
}
}
path := filepath.Join(dir.path, f.Name()) path := filepath.Join(dir.path, f.Name())
@ -429,6 +349,7 @@ type ui struct {
cmdBuf []rune cmdBuf []rune
keyAcc []rune keyAcc []rune
keyCount []rune keyCount []rune
colors colorMap
} }
func getWidths(wtot int) []int { func getWidths(wtot int) []int {
@ -485,6 +406,7 @@ func newUI() *ui {
menuWin: newWin(wtot, 1, 0, htot-2), menuWin: newWin(wtot, 1, 0, htot-2),
keyChan: make(chan string, 1000), keyChan: make(chan string, 1000),
evChan: evChan, evChan: evChan,
colors: parseColors(),
} }
} }
@ -614,7 +536,7 @@ func (ui *ui) draw(nav *nav) {
doff := len(nav.dirs) - length doff := len(nav.dirs) - length
for i := 0; i < length; i++ { for i := 0; i < length; i++ {
ui.wins[woff+i].printDir(nav.dirs[doff+i], nav.marks, nav.saves) ui.wins[woff+i].printDir(nav.dirs[doff+i], nav.marks, nav.saves, ui.colors)
} }
switch ui.cmdPrefix { switch ui.cmdPrefix {
@ -640,7 +562,7 @@ func (ui *ui) draw(nav *nav) {
preview := ui.wins[len(ui.wins)-1] preview := ui.wins[len(ui.wins)-1]
if f.IsDir() { if f.IsDir() {
preview.printDir(ui.dirPrev, nav.marks, nav.saves) preview.printDir(ui.dirPrev, nav.marks, nav.saves, ui.colors)
} else if f.Mode().IsRegular() { } else if f.Mode().IsRegular() {
preview.printReg(ui.regPrev) preview.printReg(ui.regPrev)
} }