From b5bdce175c92e8dc5f7eef9c9b6292961f0c8991 Mon Sep 17 00:00:00 2001 From: Pavel Balaev Date: Tue, 2 May 2023 15:31:49 +0300 Subject: [PATCH] ls: add icons this patch adds icons to ls output. usage: --icon. optimised for kitty termimal and pragmatapro font. --- src/icons.c | 240 ++++++++++++++++++++++++++++++++++++++++++++++++ src/icons.gperf | 74 +++++++++++++++ src/icons.h | 11 +++ src/local.mk | 7 +- src/ls.c | 93 ++++++++++++++++++- 5 files changed, 420 insertions(+), 5 deletions(-) create mode 100644 src/icons.c create mode 100644 src/icons.gperf create mode 100644 src/icons.h diff --git a/src/icons.c b/src/icons.c new file mode 100644 index 0000000..6baa0fb --- /dev/null +++ b/src/icons.c @@ -0,0 +1,240 @@ +/* ANSI-C code produced by gperf version 3.1 */ +/* Command-line: gperf -m 10 -D -t -L ANSI-C -E -I -C -K ext -N get_icon icons.gperf */ +/* Computed positions: -k'1,$' */ + +#if !((' ' == 32) && ('!' == 33) && ('"' == 34) && ('#' == 35) \ + && ('%' == 37) && ('&' == 38) && ('\'' == 39) && ('(' == 40) \ + && (')' == 41) && ('*' == 42) && ('+' == 43) && (',' == 44) \ + && ('-' == 45) && ('.' == 46) && ('/' == 47) && ('0' == 48) \ + && ('1' == 49) && ('2' == 50) && ('3' == 51) && ('4' == 52) \ + && ('5' == 53) && ('6' == 54) && ('7' == 55) && ('8' == 56) \ + && ('9' == 57) && (':' == 58) && (';' == 59) && ('<' == 60) \ + && ('=' == 61) && ('>' == 62) && ('?' == 63) && ('A' == 65) \ + && ('B' == 66) && ('C' == 67) && ('D' == 68) && ('E' == 69) \ + && ('F' == 70) && ('G' == 71) && ('H' == 72) && ('I' == 73) \ + && ('J' == 74) && ('K' == 75) && ('L' == 76) && ('M' == 77) \ + && ('N' == 78) && ('O' == 79) && ('P' == 80) && ('Q' == 81) \ + && ('R' == 82) && ('S' == 83) && ('T' == 84) && ('U' == 85) \ + && ('V' == 86) && ('W' == 87) && ('X' == 88) && ('Y' == 89) \ + && ('Z' == 90) && ('[' == 91) && ('\\' == 92) && (']' == 93) \ + && ('^' == 94) && ('_' == 95) && ('a' == 97) && ('b' == 98) \ + && ('c' == 99) && ('d' == 100) && ('e' == 101) && ('f' == 102) \ + && ('g' == 103) && ('h' == 104) && ('i' == 105) && ('j' == 106) \ + && ('k' == 107) && ('l' == 108) && ('m' == 109) && ('n' == 110) \ + && ('o' == 111) && ('p' == 112) && ('q' == 113) && ('r' == 114) \ + && ('s' == 115) && ('t' == 116) && ('u' == 117) && ('v' == 118) \ + && ('w' == 119) && ('x' == 120) && ('y' == 121) && ('z' == 122) \ + && ('{' == 123) && ('|' == 124) && ('}' == 125) && ('~' == 126)) +/* The character set is not based on ISO-646. */ +#error "gperf generated tables don't work with this execution character set. Please report a bug to ." +#endif + +#line 1 "icons.gperf" + +#include +#include + +#if defined(__clang__) && defined(__has_warning) +# if __has_warning("-Wimplicit-fallthrough") +# pragma clang diagnostic ignored "-Wimplicit-fallthrough" +# endif +# if __has_warning("-Wsuggest-attribute=pure") +# pragma clang diagnostic ignored "-Wsuggest-attribute=pure" +# endif +#elif defined(__GNUC__) +# if (__GNUC__ >= 7) +# pragma GCC diagnostic ignored "-Wimplicit-fallthrough" +# endif +# pragma GCC diagnostic ignored "-Wsuggest-attribute=pure" +#endif + +const struct icon *get_icon(const char *, size_t); +/* Command-line: + gperf -m 10 -D -t -L ANSI-C -E -I -C -K ext -N get_icon icons.gperf */ +#line 23 "icons.gperf" +struct icon { + const char *ext; + const char *symbol; +}; +#include +/* maximum key range = 47, duplicates = 0 */ + +#ifdef __GNUC__ +__inline +#else +#ifdef __cplusplus +inline +#endif +#endif +static unsigned int +hash (register const char *str, register size_t len) +{ + static const unsigned char asso_values[] = + { + 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, + 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, + 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, + 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, + 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, + 15, 14, 12, 49, 49, 49, 49, 49, 49, 49, + 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, + 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, + 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, + 49, 49, 49, 49, 49, 49, 49, 26, 1, 30, + 11, 0, 11, 4, 0, 21, 20, 4, 15, 17, + 30, 39, 6, 12, 33, 0, 35, 1, 18, 49, + 3, 11, 25, 26, 49, 49, 49, 49, 49, 49, + 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, + 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, + 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, + 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, + 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, + 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, + 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, + 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, + 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, + 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, + 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, + 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, + 49, 49, 49, 49, 49, 49, 49 + }; + return len + asso_values[(unsigned char)str[len - 1]] + asso_values[(unsigned char)str[0]+1]; +} + +const struct icon * +get_icon (register const char *str, register size_t len) +{ + enum + { + TOTAL_KEYWORDS = 47, + MIN_WORD_LENGTH = 1, + MAX_WORD_LENGTH = 10, + MIN_HASH_VALUE = 2, + MAX_HASH_VALUE = 48 + }; + + static const struct icon wordlist[] = + { +#line 74 "icons.gperf" + {"rs", "\ue6a8"}, +#line 37 "icons.gperf" + {"db", "\uf1c0"}, +#line 52 "icons.gperf" + {"deb", "\uf306"}, +#line 54 "icons.gperf" + {"aab", "\uf17b"}, +#line 61 "icons.gperf" + {"js", "\uf81d"}, +#line 56 "icons.gperf" + {"docx", "\uf1c2"}, +#line 53 "icons.gperf" + {"apk", "\uf17b"}, +#line 66 "icons.gperf" + {"gitignore", "\uf408"}, +#line 67 "icons.gperf" + {"gitmodules", "\uf408"}, +#line 42 "icons.gperf" + {"jpg", "\uf1c5"}, +#line 41 "icons.gperf" + {"jpeg", "\uf1c5"}, +#line 65 "icons.gperf" + {"gitconfig", "\uf408"}, +#line 36 "icons.gperf" + {"exe", "\uf17a"}, +#line 38 "icons.gperf" + {"diff", "\uf440"}, +#line 58 "icons.gperf" + {"epub", "\ue28a"}, +#line 39 "icons.gperf" + {"patch", "\uf440"}, +#line 68 "icons.gperf" + {"cfg", "\uf423"}, +#line 43 "icons.gperf" + {"png", "\uf1c5"}, +#line 29 "icons.gperf" + {"cpp", "\ue51d"}, +#line 34 "icons.gperf" + {"php", "\uf81e"}, +#line 30 "icons.gperf" + {"h", "\ue51e"}, +#line 71 "icons.gperf" + {"hs", "\ue677"}, +#line 62 "icons.gperf" + {"log", "\uf18d"}, +#line 35 "icons.gperf" + {"py", "\uf81f"}, +#line 40 "icons.gperf" + {"pdf", "\uf1c1"}, +#line 48 "icons.gperf" + {"gz", "\uf410"}, +#line 51 "icons.gperf" + {"ebuild", "\uf30d"}, +#line 70 "icons.gperf" + {"key", "\ue50a"}, +#line 31 "icons.gperf" + {"hpp", "\ue51d"}, +#line 73 "icons.gperf" + {"puml", "\uf0e8"}, +#line 63 "icons.gperf" + {"pem", "\uf2bc"}, +#line 55 "icons.gperf" + {"doc", "\uf1c2"}, +#line 59 "icons.gperf" + {"java", "\ue638"}, +#line 50 "icons.gperf" + {"zip", "\uf410"}, +#line 72 "icons.gperf" + {"uml", "\uf0e8"}, +#line 46 "icons.gperf" + {"sh", "\ue695"}, +#line 47 "icons.gperf" + {"xz", "\uf410"}, +#line 57 "icons.gperf" + {"txt", "\uf15c"}, +#line 60 "icons.gperf" + {"jar", "\uee638"}, +#line 33 "icons.gperf" + {"go", "\uf40d"}, +#line 28 "icons.gperf" + {"c", "\ue51e"}, +#line 64 "icons.gperf" + {"md", "\uf48a"}, +#line 69 "icons.gperf" + {"ini", "\uf423"}, +#line 44 "icons.gperf" + {"mp4", "\uf03d"}, +#line 32 "icons.gperf" + {"lua", "\ue520"}, +#line 45 "icons.gperf" + {"mp3", "\uf001"}, +#line 49 "icons.gperf" + {"bz2", "\uf410"} + }; + + static const signed char lookup[] = + { + -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, + 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, + 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, + 40, 41, 42, 43, 44, 45, 46 + }; + + if (len <= MAX_WORD_LENGTH && len >= MIN_WORD_LENGTH) + { + register unsigned int key = hash (str, len); + + if (key <= MAX_HASH_VALUE) + { + register int index = lookup[key]; + + if (index >= 0) + { + register const char *s = wordlist[index].ext; + + if (*str == *s && !strcmp (str + 1, s + 1)) + return &wordlist[index]; + } + } + } + return 0; +} diff --git a/src/icons.gperf b/src/icons.gperf new file mode 100644 index 0000000..1abd94a --- /dev/null +++ b/src/icons.gperf @@ -0,0 +1,74 @@ +%{ +#include +#include + +#if defined(__clang__) && defined(__has_warning) +# if __has_warning("-Wimplicit-fallthrough") +# pragma clang diagnostic ignored "-Wimplicit-fallthrough" +# endif +# if __has_warning("-Wsuggest-attribute=pure") +# pragma clang diagnostic ignored "-Wsuggest-attribute=pure" +# endif +#elif defined(__GNUC__) +# if (__GNUC__ >= 7) +# pragma GCC diagnostic ignored "-Wimplicit-fallthrough" +# endif +# pragma GCC diagnostic ignored "-Wsuggest-attribute=pure" +#endif + +const struct icon *get_icon(const char *, size_t); +/* Command-line: + gperf -m 10 -D -t -L ANSI-C -E -I -C -K ext -N get_icon icons.gperf */ +%} +struct icon { + const char *ext; + const char *symbol; +}; +%% +"c", "\ue51e" +"cpp", "\ue51d" +"h", "\ue51e" +"hpp", "\ue51d" +"lua", "\ue520" +"go", "\uf40d" +"php", "\uf81e" +"py", "\uf81f" +"exe", "\uf17a" +"db", "\uf1c0" +"diff", "\uf440" +"patch", "\uf440" +"pdf", "\uf1c1" +"jpeg", "\uf1c5" +"jpg", "\uf1c5" +"png", "\uf1c5" +"mp4", "\uf03d" +"mp3", "\uf001" +"sh", "\ue695" +"xz", "\uf410" +"gz", "\uf410" +"bz2", "\uf410" +"zip", "\uf410" +"ebuild", "\uf30d" +"deb", "\uf306" +"apk", "\uf17b" +"aab", "\uf17b" +"doc", "\uf1c2" +"docx", "\uf1c2" +"txt", "\uf15c" +"epub", "\ue28a" +"java", "\ue638" +"jar", "\uee638" +"js", "\uf81d" +"log", "\uf18d" +"pem", "\uf2bc" +"md", "\uf48a" +"gitconfig", "\uf408" +"gitignore", "\uf408" +"gitmodules", "\uf408" +"cfg", "\uf423" +"ini", "\uf423" +"key", "\ue50a" +"hs", "\ue677" +"uml", "\uf0e8" +"puml", "\uf0e8" +"rs", "\ue6a8" diff --git a/src/icons.h b/src/icons.h new file mode 100644 index 0000000..8299971 --- /dev/null +++ b/src/icons.h @@ -0,0 +1,11 @@ +#ifndef ICONS_H_ +#define ICONS_H_ + +struct icon { + const char *ext; + const char *symbol; +}; + +const struct icon *get_icon(const char *, size_t); + +#endif diff --git a/src/local.mk b/src/local.mk index 13eeea8..ac2d47f 100644 --- a/src/local.mk +++ b/src/local.mk @@ -54,6 +54,7 @@ noinst_HEADERS = \ src/iopoll.h \ src/longlong.h \ src/ls.h \ + src/icons.h \ src/operand2sig.h \ src/prog-fprintf.h \ src/remove.h \ @@ -363,12 +364,12 @@ nodist_src_coreutils_SOURCES = src/coreutils.h src_coreutils_SOURCES = src/coreutils.c src_cp_SOURCES = src/cp.c $(copy_sources) $(selinux_sources) -src_dir_SOURCES = src/ls.c src/ls-dir.c +src_dir_SOURCES = src/ls.c src/ls-dir.c src/icons.c src_env_SOURCES = src/env.c src/operand2sig.c -src_vdir_SOURCES = src/ls.c src/ls-vdir.c +src_vdir_SOURCES = src/ls.c src/ls-vdir.c src/icons.c src_id_SOURCES = src/id.c src/group-list.c src_groups_SOURCES = src/groups.c src/group-list.c -src_ls_SOURCES = src/ls.c src/ls-ls.c +src_ls_SOURCES = src/ls.c src/ls-ls.c src/icons.c src/icons.h src_ln_SOURCES = src/ln.c \ src/force-link.c src/force-link.h \ src/relpath.c src/relpath.h diff --git a/src/ls.c b/src/ls.c index 71d94fd..a42799c 100644 --- a/src/ls.c +++ b/src/ls.c @@ -96,6 +96,7 @@ #include "human.h" #include "filemode.h" #include "filevercmp.h" +#include "icons.h" #include "idcache.h" #include "ls.h" #include "mbswidth.h" @@ -290,6 +291,7 @@ static void print_horizontal (void); static int format_user_width (uid_t u); static int format_group_width (gid_t g); static void print_long_format (const struct fileinfo *f); +static void print_icon (const struct fileinfo *f); static void print_many_per_line (void); static size_t print_name_with_quoting (const struct fileinfo *f, bool symlink_target, @@ -506,6 +508,10 @@ static bool print_owner = true; static bool print_author; +/* True means to display icons. */ + +static bool print_icons; + /* True means to display group information. -G and -o turn this off. */ static bool print_group = true; @@ -846,6 +852,7 @@ enum TIME_OPTION, TIME_STYLE_OPTION, ZERO_OPTION, + ICON_OPTION, }; static struct option const long_options[] = @@ -894,6 +901,7 @@ static struct option const long_options[] = {"block-size", required_argument, NULL, BLOCK_SIZE_OPTION}, {"context", no_argument, 0, 'Z'}, {"author", no_argument, NULL, AUTHOR_OPTION}, + {"icon", no_argument, NULL, ICON_OPTION}, {GETOPT_HELP_OPTION_DECL}, {GETOPT_VERSION_OPTION_DECL}, {NULL, 0, NULL, 0} @@ -2126,6 +2134,10 @@ decode_switches (int argc, char **argv) print_author = true; break; + case ICON_OPTION: + print_icons = true; + break; + case HIDE_OPTION: { struct ignore_pattern *hide = xmalloc (sizeof *hide); @@ -3404,6 +3416,7 @@ gobble_file (char const *name, enum filetype type, ino_t inode, if (command_line_arg || print_hyperlink || format_needs_stat + || print_icons /* When coloring a directory (we may know the type from direct.d_type), we have to stat it in order to indicate sticky and/or other-writable attributes. */ @@ -3570,7 +3583,7 @@ gobble_file (char const *name, enum filetype type, ino_t inode, } if (S_ISLNK (f->stat.st_mode) - && (format == long_format || check_symlink_mode)) + && (format == long_format || check_symlink_mode || print_icons)) { struct stat linkstats; @@ -3585,7 +3598,7 @@ gobble_file (char const *name, enum filetype type, ino_t inode, /* Avoid following symbolic links when possible, ie, when they won't be traced and when no indicator is needed. */ if (linkname - && (file_type <= indicator_style || check_symlink_mode) + && (file_type <= indicator_style || check_symlink_mode || print_icons) && stat_for_mode (linkname, &linkstats) == 0) { f->linkok = true; @@ -4171,6 +4184,7 @@ print_current_files (void) case one_per_line: for (i = 0; i < cwd_n_used; i++) { + print_icon(sorted_file[i]); print_file_name_and_frills (sorted_file[i], 0); putchar (eolbyte); } @@ -4527,6 +4541,7 @@ print_long_format (const struct fileinfo *f) } dired_outbuf (buf, p - buf); + print_icon(f); size_t w = print_name_with_quoting (f, false, &dired_obstack, p - buf); if (f->filetype == symbolic_link) @@ -5167,6 +5182,9 @@ length_of_file_name_and_frills (const struct fileinfo *f) len += (c != 0); } + if (print_icons) + len += 3; + return len; } @@ -5193,6 +5211,7 @@ print_many_per_line (void) struct fileinfo const *f = sorted_file[filesno]; size_t name_length = length_of_file_name_and_frills (f); size_t max_name_length = line_fmt->col_arr[col++]; + print_icon(f); print_file_name_and_frills (f, pos); filesno += rows; @@ -5206,6 +5225,76 @@ print_many_per_line (void) } } +static void +print_icon_ext (const struct fileinfo *f) +{ + const struct icon *icon; + char *ext; + + if ((ext = strrchr(f->name, '.')) == NULL) { + printf("%s ", "\uf016"); + return; + } + + ext++; + + icon = get_icon(ext, strlen(ext)); + + if (icon == NULL) { + printf("%s ", "\uf016"); + return; + } + + printf("%s ", icon->symbol); +} + +static void +print_icon (const struct fileinfo *f) +{ + if (!print_icons) + return; + + if (f->stat_ok) { + if (S_ISREG (f->stat.st_mode)) + print_icon_ext(f); + else if (S_ISDIR (f->stat.st_mode)) + printf("%s ", "\uf115"); + else if (S_ISLNK (f->stat.st_mode)) + if (S_ISDIR (f->linkmode)) + printf("%s ", "\uf482"); + else + printf("%s ", "\uf481"); + else if (S_ISFIFO (f->stat.st_mode)) + printf("%s ", "\uf731"); + else if (S_ISSOCK (f->stat.st_mode)) + printf("%s ", "\uf6a7"); + else + printf("%s ", "\uf016"); + } else { + switch (f->filetype) + { + case directory: + printf("%s ", "\uf115"); + break; + case symbolic_link: + printf("%s ", "\uf481"); + break; + case sock: + printf("%s ", "\uf6a7"); + break; + case fifo: + printf("%s ", "\uf731"); + break; + case normal: + print_icon_ext(f); + break; + default: + printf("%s ", "\uf016"); + break; + } + } +} + static void print_horizontal (void) { -- 2.39.2