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)
hidden boolean (default off)
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)
reverse boolean (default off)
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)
hidden boolean (default off)
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)
reverse boolean (default off)
smartcase boolean (default on)

View File

@ -49,10 +49,13 @@ func (e *setExpr) eval(app *app, args []string) {
gOpts.ignorecase = !gOpts.ignorecase
case "lscolors":
gOpts.lscolors = true
app.ui.colors = parseColors()
case "nolscolors":
gOpts.lscolors = false
app.ui.colors = parseColors()
case "lscolors!":
gOpts.lscolors = !gOpts.lscolors
app.ui.colors = parseColors()
case "preview":
gOpts.preview = true
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.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.lscolors = true
gOpts.preview = true
gOpts.reverse = false
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
}
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 {
ind := 0
off := 0
@ -267,7 +213,7 @@ func (win *win) printReg(reg *reg) {
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 {
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)
for i, f := range dir.fi[beg:end] {
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
}
}
fg, bg = colors.get(f)
path := filepath.Join(dir.path, f.Name())
@ -429,6 +349,7 @@ type ui struct {
cmdBuf []rune
keyAcc []rune
keyCount []rune
colors colorMap
}
func getWidths(wtot int) []int {
@ -485,6 +406,7 @@ func newUI() *ui {
menuWin: newWin(wtot, 1, 0, htot-2),
keyChan: make(chan string, 1000),
evChan: evChan,
colors: parseColors(),
}
}
@ -614,7 +536,7 @@ func (ui *ui) draw(nav *nav) {
doff := len(nav.dirs) - length
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 {
@ -640,7 +562,7 @@ func (ui *ui) draw(nav *nav) {
preview := ui.wins[len(ui.wins)-1]
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() {
preview.printReg(ui.regPrev)
}