1package main 2 3import ( 4 "log" 5 "os" 6 "path/filepath" 7 "strconv" 8 "strings" 9 10 "github.com/gdamore/tcell/v2" 11) 12 13type styleMap map[string]tcell.Style 14 15func parseStyles() styleMap { 16 sm := make(styleMap) 17 18 // Default values from dircolors 19 // 20 // no* NORMAL 00 21 // fi FILE 00 22 // rs* RESET 0 23 // di DIR 01;34 24 // ln LINK 01;36 25 // mh* MULTIHARDLINK 00 26 // pi FIFO 40;33 27 // so SOCK 01;35 28 // do* DOOR 01;35 29 // bd BLK 40;33;01 30 // cd CHR 40;33;01 31 // or ORPHAN 40;31;01 32 // mi* MISSING 00 33 // su SETUID 37;41 34 // sg SETGID 30;43 35 // ca* CAPABILITY 30;41 36 // tw STICKY_OTHER_WRITABLE 30;42 37 // ow OTHER_WRITABLE 34;42 38 // st STICKY 37;44 39 // ex EXEC 01;32 40 // 41 // (Entries marked with * are not implemented in lf) 42 43 // default values from dircolors with background colors removed 44 defaultColors := []string{ 45 "fi=00", 46 "di=01;34", 47 "ln=01;36", 48 "pi=33", 49 "so=01;35", 50 "bd=33;01", 51 "cd=33;01", 52 "or=31;01", 53 "su=01;32", 54 "sg=01;32", 55 "tw=01;34", 56 "ow=01;34", 57 "st=01;34", 58 "ex=01;32", 59 } 60 61 sm.parseGNU(strings.Join(defaultColors, ":")) 62 63 if env := os.Getenv("LSCOLORS"); env != "" { 64 sm.parseBSD(env) 65 } 66 67 if env := os.Getenv("LS_COLORS"); env != "" { 68 sm.parseGNU(env) 69 } 70 71 if env := os.Getenv("LF_COLORS"); env != "" { 72 sm.parseGNU(env) 73 } 74 75 return sm 76} 77 78func applyAnsiCodes(s string, st tcell.Style) tcell.Style { 79 toks := strings.Split(s, ";") 80 81 var nums []int 82 for _, tok := range toks { 83 if tok == "" { 84 nums = append(nums, 0) 85 continue 86 } 87 n, err := strconv.Atoi(tok) 88 if err != nil { 89 log.Printf("converting escape code: %s", err) 90 continue 91 } 92 nums = append(nums, n) 93 } 94 95 // ECMA-48 details the standard 96 // TODO: should we support turning off attributes? 97 // Probably because this is used for previewers too 98 for i := 0; i < len(nums); i++ { 99 n := nums[i] 100 switch { 101 case n == 0: 102 st = tcell.StyleDefault 103 case n == 1: 104 st = st.Bold(true) 105 case n == 2: 106 st = st.Dim(true) 107 case n == 4: 108 st = st.Underline(true) 109 case n == 5 || n == 6: 110 st = st.Blink(true) 111 case n == 7: 112 st = st.Reverse(true) 113 case n == 8: 114 // TODO: tcell PR for proper conceal 115 _, bg, _ := st.Decompose() 116 st = st.Foreground(bg) 117 case n == 9: 118 st = st.StrikeThrough(true) 119 case n >= 30 && n <= 37: 120 st = st.Foreground(tcell.PaletteColor(n - 30)) 121 case n >= 90 && n <= 97: 122 st = st.Foreground(tcell.PaletteColor(n - 82)) 123 case n == 38: 124 if i+3 <= len(nums) && nums[i+1] == 5 { 125 st = st.Foreground(tcell.PaletteColor(nums[i+2])) 126 i += 2 127 } else if i+5 <= len(nums) && nums[i+1] == 2 { 128 st = st.Foreground(tcell.NewRGBColor( 129 int32(nums[i+2]), 130 int32(nums[i+3]), 131 int32(nums[i+4]))) 132 i += 4 133 } else { 134 log.Printf("unknown ansi code or incorrect form: %d", n) 135 } 136 case n >= 40 && n <= 47: 137 st = st.Background(tcell.PaletteColor(n - 40)) 138 case n >= 100 && n <= 107: 139 st = st.Background(tcell.PaletteColor(n - 92)) 140 case n == 48: 141 if i+3 <= len(nums) && nums[i+1] == 5 { 142 st = st.Background(tcell.PaletteColor(nums[i+2])) 143 i += 2 144 } else if i+5 <= len(nums) && nums[i+1] == 2 { 145 st = st.Background(tcell.NewRGBColor( 146 int32(nums[i+2]), 147 int32(nums[i+3]), 148 int32(nums[i+4]))) 149 i += 4 150 } else { 151 log.Printf("unknown ansi code or incorrect form: %d", n) 152 } 153 default: 154 log.Printf("unknown ansi code: %d", n) 155 } 156 } 157 158 return st 159} 160 161// This function parses $LS_COLORS environment variable. 162func (sm styleMap) parseGNU(env string) { 163 for _, entry := range strings.Split(env, ":") { 164 if entry == "" { 165 continue 166 } 167 168 pair := strings.Split(entry, "=") 169 170 if len(pair) != 2 { 171 log.Printf("invalid $LS_COLORS entry: %s", entry) 172 return 173 } 174 175 key, val := pair[0], pair[1] 176 177 key = replaceTilde(key) 178 179 if filepath.IsAbs(key) { 180 key = filepath.Clean(key) 181 } 182 183 sm[key] = applyAnsiCodes(val, tcell.StyleDefault) 184 } 185} 186 187// This function parses $LSCOLORS environment variable. 188func (sm styleMap) parseBSD(env string) { 189 if len(env) != 22 { 190 log.Printf("invalid $LSCOLORS variable: %s", env) 191 return 192 } 193 194 colorNames := []string{"di", "ln", "so", "pi", "ex", "bd", "cd", "su", "sg", "tw", "ow"} 195 196 getStyle := func(r1, r2 byte) tcell.Style { 197 st := tcell.StyleDefault 198 199 switch { 200 case r1 == 'x': 201 st = st.Foreground(tcell.ColorDefault) 202 case 'A' <= r1 && r1 <= 'H': 203 st = st.Foreground(tcell.PaletteColor(int(r1 - 'A'))).Bold(true) 204 case 'a' <= r1 && r1 <= 'h': 205 st = st.Foreground(tcell.PaletteColor(int(r1 - 'a'))) 206 default: 207 log.Printf("invalid $LSCOLORS entry: %c", r1) 208 return tcell.StyleDefault 209 } 210 211 switch { 212 case r2 == 'x': 213 st = st.Background(tcell.ColorDefault) 214 case 'a' <= r2 && r2 <= 'h': 215 st = st.Background(tcell.PaletteColor(int(r2 - 'a'))) 216 default: 217 log.Printf("invalid $LSCOLORS entry: %c", r2) 218 return tcell.StyleDefault 219 } 220 221 return st 222 } 223 224 for i, key := range colorNames { 225 sm[key] = getStyle(env[i*2], env[i*2+1]) 226 } 227} 228 229func (sm styleMap) get(f *file) tcell.Style { 230 if val, ok := sm[f.path]; ok { 231 return val 232 } 233 234 if f.IsDir() { 235 if val, ok := sm[f.Name()+"/"]; ok { 236 return val 237 } 238 } 239 240 var key string 241 242 switch { 243 case f.linkState == working: 244 key = "ln" 245 case f.linkState == broken: 246 key = "or" 247 case f.IsDir() && f.Mode()&os.ModeSticky != 0 && f.Mode()&0002 != 0: 248 key = "tw" 249 case f.IsDir() && f.Mode()&0002 != 0: 250 key = "ow" 251 case f.IsDir() && f.Mode()&os.ModeSticky != 0: 252 key = "st" 253 case f.IsDir(): 254 key = "di" 255 case f.Mode()&os.ModeNamedPipe != 0: 256 key = "pi" 257 case f.Mode()&os.ModeSocket != 0: 258 key = "so" 259 case f.Mode()&os.ModeDevice != 0: 260 key = "bd" 261 case f.Mode()&os.ModeCharDevice != 0: 262 key = "cd" 263 case f.Mode()&os.ModeSetuid != 0: 264 key = "su" 265 case f.Mode()&os.ModeSetgid != 0: 266 key = "sg" 267 case f.Mode()&0111 != 0: 268 key = "ex" 269 } 270 271 if val, ok := sm[key]; ok { 272 return val 273 } 274 275 if val, ok := sm[f.Name()+"*"]; ok { 276 return val 277 } 278 279 if val, ok := sm[filepath.Base(f.Name())+".*"]; ok { 280 return val 281 } 282 283 if val, ok := sm["*"+f.ext]; ok { 284 return val 285 } 286 287 if val, ok := sm["fi"]; ok { 288 return val 289 } 290 291 return tcell.StyleDefault 292} 293