/* * vim: foldmethod=marker * * * oooooooooo. oooooooooooo ooo ooooo * `888' `Y8b `888' `8 `88. .888' * 888 888 888 888b d'888 * 888 888 888oooo8 8 Y88. .P 888 * 888 888 888 " 8 `888' 888 * 888 d88' 888 8 Y 888 * o888bood8P' o888o o8o o888o * * Dylan's File Manager * * * Copyright (c) 2026 Dylan Araps * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "lib/arg.h" #include "lib/bitset.h" #include "lib/date.h" #include "lib/readline.h" #include "lib/str.h" #include "lib/term.h" #include "lib/term_key.h" #include "lib/utf8.h" #include "lib/util.h" #include "lib/vt.h" #if defined(__linux__) #include "platform/linux.h" #elif defined(__APPLE__) || defined(__FreeBSD__) || defined(__NetBSD__) ||\ defined(__OpenBSD__) || defined(__DragonFly__) #include "platform/bsd.h" #else #include "platform/posix.h" #endif static const char DFM_HELP[] = "usage: " CFG_NAME " [options] [path]\n\n" "options:\n" "-H | +H toggle hidden files (-H off, +H on)\n" "-p picker mode (print selected path to stdout and exit)\n" "-o program to use when opening files (default: xdg-open)\n" "-c position cursor over 'name' instead of first entry\n" "-s change default sort\n" " n name\n" " N name reverse\n" " e extension\n" " s size\n" " S size reverse\n" " d date\n" " D date reverse\n" "-v change default view\n" " n name only\n" " s size\n" " p permissions\n" " t time\n" " a all\n\n" "--help show this help\n" "--version show version\n\n" "path:\n" "directory to open (default: \".\")\n\n" "environment:\n" "DFM_OPENER program used to open files (overridden by -o)\n" "DFM_BOOKMARK_[0-9] bookmark directories\n" "DFM_COPYER program used to copy PWD and file contents.\n" "DFM_TRASH program used to trash files.\n" "DFM_TRASH_DIR path to trash directory.\n" "DFM_IMG_MODE image made to use ('chafa', 'kitty')\n" "DFM_SU privilege escalation tool to use ('sudo', 'doas', ...)\n" ; enum fm_opt { FM_ERROR = 1 << 0, FM_ROOT = 1 << 1, FM_REDRAW_DIR = 1 << 2, FM_REDRAW_NAV = 1 << 3, FM_REDRAW_CMD = 1 << 4, FM_REDRAW_FLUSH = 1 << 5, FM_REDRAW = FM_REDRAW_DIR|FM_REDRAW_NAV|FM_REDRAW_CMD|FM_REDRAW_FLUSH, FM_DIRTY = 1 << 6, FM_DIRTY_WITHIN = 1 << 7, FM_HIDDEN = 1 << 8, FM_TRUNC = 1 << 9, FM_MARK_PWD = 1 << 10, FM_MSG = 1 << 11, FM_MSG_ERR = 1 << 12, FM_PICKER = 1 << 13, FM_PRINT_PWD = 1 << 14, FM_SEARCH = 1 << 15, FM_PWD_UTF8 = 1 << 16, FM_PWD_CTRL = 1 << 17, }; struct fm; typedef void (*fm_key_press)(struct fm *, int k, cut, cut); typedef int (*fm_key_enter)(struct fm *, str *); typedef int (*fm_filter)(struct fm *, usize, cut, cut); struct fm { struct term t; struct term_key k; struct platform p; struct readline r; int dfd; str pwd; str ppwd; str mpwd; str io; usize ml; usize mp; char de[DFM_ENT_MAX]; usize del; usize dec; union { align_max _a; unsigned char d[DFM_DIR_MAX * sizeof(u32)]; } d; usize dl; u8 dv; u8 ds; u32 du; u64 v[BITSET_W(DFM_DIR_MAX)]; u16 vp[BITSET_W(DFM_DIR_MAX)]; usize vl; char vq[DFM_NAME_MAX]; usize vql; usize vqw; u64 vm[BITSET_W(DFM_DIR_MAX)]; usize vml; u32 ht[DFM_DIR_HT_CAP]; usize y; usize o; usize c; u32 st; u16 row; u16 col; u32 f; u32 cf; cut opener; fm_key_press kp; fm_key_enter kd; fm_filter sf; s64 tz; u8 nl; u8 im; const char *a0; cut ast; }; // Entry Virtual {{{ #define ENT_V_OFF 0, 20 #define ENT_V_CHAR 20, 8 #define ENT_V_TOMB 28, 1 #define ENT_V_MARK 29, 1 #define ENT_V_VIS 30, 1 #define ENT_V_DOT 31, 1 #define ent_v_get(e, o) bitfield_get32((e), ENT_V_##o) #define ent_v_set(e, o, v) bitfield_set32((e), (v), ENT_V_##o) #define ent_v_geto(p, i, o) ent_v_get(ent_v_load((p), (i)), o) static inline unsigned char * ent_v_ptr(struct fm *p, usize i) { return p->d.d + (i * sizeof(u32)); } static inline const unsigned char * ent_v_ptr_const(const struct fm *p, usize i) { return p->d.d + (i * sizeof(u32)); } static inline u32 ent_v_load(const struct fm *p, usize i) { u32 v; memcpy(&v, ent_v_ptr_const(p, i), sizeof(v)); return v; } static inline void ent_v_store(struct fm *p, usize i, u32 v) { memcpy(ent_v_ptr(p, i), &v, sizeof(v)); } // }}} // Entry Physical {{{ enum { ENT_DIR = 0, ENT_LNK_DIR = 1, ENT_LNK = 3, ENT_LNK_BRK = 5, ENT_UNKNOWN = 4, ENT_FIFO = 6, ENT_SOCK = 8, ENT_SPEC = 10, ENT_REG = 12, ENT_REG_EXEC = 14, ENT_TYPE_MAX = 16, }; #define ENT_IS_LNK(t) ((t) & 1) #define ENT_IS_DIR(t) ((t) <= ENT_LNK_DIR) #define ENT_UTF8 0, 1 #define ENT_CTRL 1, 1 #define ENT_LOC 2, 16 #define ENT_LEN 18, 8 #define ENT_SIZE 26, 12 #define ENT_TYPE 38, 4 #define ENT_PERM 42, 12 #define ENT_TIME 54, 5 #define ENT_HASH 59, 5 #define ent_get(e, o) bitfield_get64((u64)(e), ENT_##o) #define ent_set(e, o, v) bitfield_set64((e), (v), ENT_##o) #define lnk_set(l, o, v) bitfield_set8((l), (v), ENT_##o) static inline u64 ent_load(const struct fm *p, usize i) { u64 m; memcpy(&m, p->de + ent_v_geto(p, i, OFF) - sizeof(m), sizeof(m)); return m; } static inline void ent_store(struct fm *p, usize i, u64 m) { memcpy(p->de + ent_v_geto(p, i, OFF) - sizeof(m), &m, 8); } static inline u64 ent_load_off(const struct fm *p, u32 o) { u64 m; memcpy(&m, p->de + o - sizeof(m), sizeof(m)); return m; } static inline void ent_perm_decode(str *s, mode_t m, u8 t) { char b[11]; int d = t ? t == ENT_DIR : S_ISDIR(m); b[0] = d ? 'd' : '-'; b[1] = (m & S_IRUSR) ? 'r' : '-'; b[2] = (m & S_IWUSR) ? 'w' : '-'; b[3] = (m & S_ISUID) ? (m & S_IXUSR) ? 's' : 'S' : (m & S_IXUSR) ? 'x' : '-'; b[4] = (m & S_IRGRP) ? 'r' : '-'; b[5] = (m & S_IWGRP) ? 'w' : '-'; b[6] = (m & S_ISGID) ? (m & S_IXGRP) ? 's' : 'S' : (m & S_IXGRP) ? 'x' : '-'; b[7] = (m & S_IROTH) ? 'r' : '-'; b[8] = (m & S_IWOTH) ? 'w' : '-'; b[9] = (m & S_ISVTX) ? (m & S_IXOTH) ? 't' : 'T' : (m & S_IXOTH) ? 'x' : '-'; b[10] = ' '; str_push(s, b, sizeof(b)); } static inline u32 ent_size_encode(off_t s) { if (s <= 0) return 0; u64 v = (u64)s; u32 e = 63 - u64_clz(v); u64 b = 1ULL << e; u32 f = 0; if (e) { u64 d = v - b; f = (u32)((d << 6) >> e); if (f > 63) f = 63; } return (e << 6) | f; } static inline u64 ent_size_bytes(u32 v, u8 t) { if (ENT_IS_LNK(t)) return v; if (!v) return 0; u32 e = v >> 6; u32 f = v & 63; u64 b = 1ULL << e; return b + ((b * f) >> 6); } static inline u32 ent_size_add(u32 e, u64 a) { if (!e) return ent_size_encode(a); if (!a) return e; u64 c = ent_size_bytes(e, ENT_TYPE_MAX); return ent_size_encode(c + a); } static inline u32 ent_size_sub(u32 e, u64 s) { if (!e) return 0; u64 c = ent_size_bytes(e, ENT_TYPE_MAX); if (s >= c) return 0; return ent_size_encode(c - s); } static inline usize ent_size_decode(str *s, u32 v, usize p, u8 t) { usize w = 0; if (ENT_IS_LNK(t) || !v) { w += str_push_u32_p(s, v, ' ', p ? p - 1 : 0); str_push_c(s, 'B'); w++; if (p) { str_push_c(s, ' '); w++; } return w; } u32 e = v >> 6; u32 f = v & 63; u32 u = e / 10; if (u > 6) u = 6; u64 b = 1ULL << (e - u * 10); u64 ip = b + ((b * f) >> 6); u32 d = ((f * 10) + 32) >> 6; if (d == 10) { ip++; d = 0; } int sd = (u && ip < 10); usize su = 1 + (sd ? 2 : 0); usize pa = p > su ? p - su : 0; w += str_push_u32_p(s, (u32)ip, ' ', pa); if (sd) { str_push_c(s, '.'); str_push_u32(s, d); w += 2; } str_push_c(s, "BKMGTPE"[u]); w++; if (p) { str_push_c(s, ' '); w++; } return w; } static inline u32 ent_time_encode(time_t t) { time_t d = time(NULL) - t; return d <= 0 ? 0 : (u32)(63 - u64_clz(d)); } static inline void ent_time_decode(str *s, u32 v) { static const char *u[] = { "s ", "s ", "s ", "s ", "s ", "s ", "m ", "m ", "m ", "m ", "m ", "m ", "h ", "h ", "h ", "h ", "h ", "d ", "d ", "d ", "d ", "d ", "w ", "w ", "w ", "w ", "mo", "mo", "mo", "mo", "mo" }; if (v > 31) v = 31; str_push(s, v == 31 ? ">= " : " ", 3); str_push_u32_p(s, v == 31 ? 1u << 5 : 1u << (v % 6), ' ', 2); str_push(s, u[v], 2); str_push_c(s, ' '); } static inline void ent_map_stat(u64 *e, const struct stat *s, u8 t) { if (t != ENT_TYPE_MAX) goto e; if (S_ISDIR(s->st_mode)) t = ENT_DIR; else if (S_ISLNK(s->st_mode)) t = ENT_LNK; else if (S_ISREG(s->st_mode) && (s->st_mode & (S_IXUSR|S_IXGRP|S_IXOTH))) t = ENT_REG_EXEC; else if (S_ISREG(s->st_mode)) t = ENT_REG; else if (S_ISFIFO(s->st_mode)) t = ENT_FIFO; else if (S_ISSOCK(s->st_mode)) t = ENT_SOCK; else if (S_ISCHR(s->st_mode) || S_ISBLK(s->st_mode)) t = ENT_SPEC; else t = ENT_UNKNOWN; e: ent_set(e, TYPE, t); ent_set(e, PERM, s->st_mode & 07777); ent_set(e, TIME, ent_time_encode(s->ST_MTIM)); } static inline void ent_map_stat_size(u64 *e, const struct stat *s) { ent_set(e, SIZE, S_ISLNK(s->st_mode) ? s->st_size : ent_size_encode(s->st_size)); } static inline usize ent_name_len(const char *s, u8 *utf8, u8 *ctrl) { const unsigned char *m = (const unsigned char *)s; const unsigned char *p = m; *utf8 = 0; *ctrl = 0; #ifdef __GNUC__ typedef size_t __attribute__((__may_alias__)) W; #define ONES ((size_t)-1 / UCHAR_MAX) #define HIGHS (ONES * (UCHAR_MAX / 2 + 1)) #define HAS_NUL(v) (((v) - ONES) & ~(v) & HIGHS) #define HAS_HIGH(v) ((v) & HIGHS) #define HAS_BYTE_LT(v,n) (((v) - ONES*(n)) & ~(v) & HIGHS) #define HAS_BYTE_EQ(v,c) HAS_BYTE_LT((v) ^ (ONES*(c)), 1) #define HAS_CTRL(v) (HAS_BYTE_LT(v, 0x20) | HAS_BYTE_EQ(v, 0x7f)) for (; (uintptr_t)p % sizeof(W); p++) { unsigned char b = *p; if (!b) return (size)(p - m); if (b & 0x80) { *utf8 = 1; goto done; } if (b < 0x20 || b == 0x7f) { *ctrl = 1; if (*utf8) goto done; } } for (const W *w = (const void *)p;; w++) { W v = *w; if (HAS_HIGH(v) | HAS_NUL(v)) { p = (const unsigned char *)w; for (;; p++) { unsigned char b = *p; if (!b) return (size)(p - m); if (b & 0x80) { *utf8 = 1; goto done; } if (b < 0x20 || b == 0x7f) { *ctrl = 1; if (*utf8) goto done; } } } if (!*ctrl && HAS_CTRL(v)) *ctrl = 1; } #endif for (;;) { unsigned char b = *p; if (!b) return (size)(p - m); if (b & 0x80) { *utf8 = 1; goto done; } if (b < 0x20 || b == 0x7f) { *ctrl = 1; if (*utf8) goto done; } p++; } done: if (*ctrl) goto len; for (p++;;) { #ifdef __GNUC__ for (; (uintptr_t)p % sizeof(W); p++) { if (!*p) return (size)(p - m); if (*p < 0x20 || *p == 0x7f) { *ctrl = 1; goto len; } } for (const W *w = (const void *)p;; w++) { W v = *w; if (HAS_NUL(v)) { p = (const unsigned char *)w; for (; *p; p++) if (*p < 0x20 || *p == 0x7f) { *ctrl = 1; goto len; } return (size)(p - m); } if (HAS_CTRL(v)) { *ctrl = 1; goto len; } } #endif if (!*p) return (size)(p - m); if (*p < 0x20 || *p == 0x7f) { *ctrl = 1; goto len; } p++; } len: for (p++;;) { #ifdef __GNUC__ for (; (uintptr_t)p % sizeof(W); p++) if (!*p) return (size)(p - m); for (const W *w = (const void *)p;; w++) { if (HAS_NUL(*w)) { p = (const unsigned char *)w; for (; *p; p++); return (size)(p - m); } } #endif if (!*p) return (size)(p - m); p++; } } static inline usize ent_next(struct fm *p, usize i) { return bitset_next_set(p->v, i, p->dl); } static inline usize ent_prev(struct fm *p, usize i) { return bitset_prev_set(p->v, i, p->dl); } // }}} // Sorting {{{ typedef int (*ent_sort_cb)(struct fm *, u32, u32); static inline int fm_ent_cmp_name(struct fm *p, u32 a, u32 b) { static const unsigned char t[256] = { ['0']=1,['1']=1,['2']=1,['3']=1,['4']=1, ['5']=1,['6']=1,['7']=1,['8']=1,['9']=1 }; u32 oa = ent_v_get(a, OFF); u32 ob = ent_v_get(b, OFF); u64 ma = ent_load_off(p, oa); u64 mb = ent_load_off(p, ob); int r = ENT_IS_DIR(ent_get(mb, TYPE)) - ENT_IS_DIR(ent_get(ma, TYPE)); if (unlikely(r)) return r; u8 fa = ent_v_get(a, CHAR); u8 fb = ent_v_get(b, CHAR); int da = (unsigned)(fa - '0') < 10; int db = (unsigned)(fb - '0') < 10; if (da ^ db) return da ? -1 : 1; if (fa != fb && !(t[fa] & t[fb])) return fa < fb ? -1 : 1; const char *pa = p->de + oa; const char *pb = p->de + ob; usize la = ent_get(ma, LEN); usize lb = ent_get(mb, LEN); usize i = 0; usize j = 0; while (i < la && j < lb) { unsigned char ca = (unsigned char)pa[i]; unsigned char cb = (unsigned char)pb[j]; if (unlikely(t[ca] & t[cb])) { usize ia = i; usize ja = j; for (; ia < la && pa[ia] == '0'; ia++); for (; ja < lb && pb[ja] == '0'; ja++); usize ea = ia; usize eb = ja; for (; ea < la && t[(unsigned char)pa[ea]]; ea++); for (; eb < lb && t[(unsigned char)pb[eb]]; eb++); usize na = ea - ia; usize nb = eb - ja; if (na != nb) return na < nb ? -1 : 1; int cmp = memcmp(pa + ia, pb + ja, na); if (cmp) return cmp; usize za = ia - i; usize zb = ja - j; if (za != zb) return za < zb ? -1 : 1; i = ea; j = eb; continue; } if (ca != cb) return ca < cb ? -1 : 1; i++; j++; } return (i < la) - (j < lb); } static inline int fm_ent_cmp_name_rev(struct fm *p, u32 a, u32 b) { return fm_ent_cmp_name(p, a, b) * -1; } static inline int fm_ent_cmp_size(struct fm *p, u32 a, u32 b) { u64 ma = ent_load_off(p, ent_v_get(a, OFF)); u64 mb = ent_load_off(p, ent_v_get(b, OFF)); u32 sa = ent_size_bytes(ent_get(ma, SIZE), ent_get(ma, TYPE)); u32 sb = ent_size_bytes(ent_get(mb, SIZE), ent_get(mb, TYPE)); return (size)sa - (size)sb; } static inline int fm_ent_cmp_date(struct fm *p, u32 a, u32 b) { u64 ma = ent_load_off(p, ent_v_get(a, OFF)); u64 mb = ent_load_off(p, ent_v_get(b, OFF)); return (size)ent_get(ma, TIME) - (size)ent_get(mb, TIME); } static inline int fm_ent_cmp_size_rev(struct fm *p, u32 a, u32 b) { return fm_ent_cmp_size(p, b, a); } static inline int fm_ent_cmp_date_rev(struct fm *p, u32 a, u32 b) { return fm_ent_cmp_date(p, b, a); } static inline int fm_ent_cmp_fext(struct fm *p, u32 a, u32 b) { u32 oa = ent_v_get(a, OFF); u32 ob = ent_v_get(b, OFF); u64 ma = ent_load_off(p, oa); u64 mb = ent_load_off(p, ob); cut ca = { p->de + oa, ent_get(ma, LEN) }; cut cb = { p->de + ob, ent_get(mb, LEN) }; const char *pa = ca.d + ca.l; const char *pb = cb.d + cb.l; for (; pa > ca.d && pa[-1] != '.'; pa--); for (; pb > cb.d && pb[-1] != '.'; pb--); if (pa == ca.d && pb != cb.d) return 1; if (pb == cb.d && pa != ca.d) return -1; if (pa == ca.d && pb == cb.d) return 0; usize la = (usize)(ca.d + ca.l - pa); usize lb = (usize)(cb.d + cb.l - pb); int r = memcmp(pa, pb, la < lb ? la : lb); return r ? r : (int)(la < lb) - (int)(la > lb); } static inline void fm_ent_isort(struct fm *p, ent_sort_cb f, usize lo, usize hi) { for (usize i = lo + 1; i < hi; i++) { u32 x = ent_v_load(p, i); usize j = i; for (; j > lo && f(p, ent_v_load(p, j - 1), x) > 0; j--) ent_v_store(p, j, ent_v_load(p, j - 1)); ent_v_store(p, j, x); } } static inline void fm_ent_qsort(struct fm *p, ent_sort_cb f, usize lo, usize hi, int d) { while (hi - lo > 16) { if (!d--) break; usize mid = lo + ((hi - lo) >> 1); u32 a = ent_v_load(p, lo); u32 b = ent_v_load(p, mid); u32 c = ent_v_load(p, hi - 1); u32 pivot = (f(p, a, b) < 0 ? (f(p, b, c) < 0 ? b : (f(p, a, c) < 0 ? c : a)) : (f(p, a, c) < 0 ? a : (f(p, b, c) < 0 ? c : b))); usize i = lo; usize j = hi - 1; for (;; i++, j--) { for (; f(p, ent_v_load(p, i), pivot) < 0; i++); for (; f(p, pivot, ent_v_load(p, j)) < 0; j--); if (i >= j) break; u32 t = ent_v_load(p, i); ent_v_store(p, i, ent_v_load(p, j)); ent_v_store(p, j, t); } if (j - lo < hi - (j + 1)) { fm_ent_qsort(p, f, lo, j + 1, d); lo = j + 1; } else { fm_ent_qsort(p, f, j + 1, hi, d); hi = j + 1; } } fm_ent_isort(p, f, lo, hi); } static inline ent_sort_cb fm_sort_fn(u8 s) { switch (s) { case 'n': return fm_ent_cmp_name; case 'N': return fm_ent_cmp_name_rev; case 'e': return fm_ent_cmp_fext; case 's': return fm_ent_cmp_size; case 'S': return fm_ent_cmp_size_rev; case 'd': return fm_ent_cmp_date; case 'D': return fm_ent_cmp_date_rev; default: return 0; } } // }}} // Util {{{ static inline cut fm_ent(const struct fm *p, usize i) { u32 o = ent_v_geto(p, i, OFF); u64 m = ent_load_off(p, o); return (cut) {p->de + o, ent_get(m, LEN) }; } static inline cut fm_file_type(mode_t m) { if (S_ISREG(m) && m & (S_IXUSR|S_IXGRP|S_IXOTH)) return (cut){ S("executable file") }; if (S_ISREG(m)) return (cut){ S("regular file") }; if (S_ISDIR(m)) return (cut){ S("directory") }; if (S_ISLNK(m)) return (cut){ S("symlink") }; if (S_ISCHR(m)) return (cut){ S("char device") }; if (S_ISBLK(m)) return (cut){ S("block device") }; if (S_ISFIFO(m)) return (cut){ S("fifo") }; if (S_ISSOCK(m)) return (cut){ S("socket") }; return (cut){ S("unknown") }; } static inline void str_push_time(str *s, s64 tz, time_t ts) { s32 y; u32 m; u32 d; u32 H; u32 M; u32 S; ut_to_date_time(tz, ts, &y, &m, &d, &H, &M, &S); str_push_u32_p(s, y, '0', 2); str_push_c(s, '-'); str_push_u32_p(s, m, '0', 2); str_push_c(s, '-'); str_push_u32_p(s, d, '0', 2); str_push_c(s, ' '); str_push_u32_p(s, H, '0', 2); str_push_c(s, ':'); str_push_u32_p(s, M, '0', 2); str_push_c(s, ':'); str_push_u32_p(s, S, '0', 2); } static inline void str_push_q(str *s, const char *d, usize l, bool ctrl) { if (ctrl) str_push_sanitize(s, d, l); else str_push(s, d, l); } static inline int next_tok(const char *s, usize l, usize *c, cut *o) { usize p = *c; for (; p < l && (s[p] == ' ' || !s[p]); p++); if (p >= l) { *c = p; *o = (cut){ s, 0 }; return 0; } usize t = p; for (; p < l && s[p] != ' ' && s[p]; p++); *c = p; *o = (cut){ &s[t], p - t }; return 1; } // }}} // Visibility {{{ static inline void fm_v_clr(struct fm *p, usize i) { if (!ent_v_geto(p, i, VIS)) return; u32 e = ent_v_load(p, i); ent_v_set(&e, VIS, 0); ent_v_store(p, i, e); } static inline void fm_v_assign(struct fm *p, usize i, u8 v) { if (ent_v_geto(p, i, VIS) == v) return; u32 e = ent_v_load(p, i); ent_v_set(&e, VIS, v); ent_v_store(p, i, e); } static inline void fm_v_rebuild(struct fm *p) { p->vl = 0; u16 s = 0; for (usize b = 0, c = BITSET_W(p->dl); b < c; b++) { u64 w = 0; for (usize j = 0; j < 64; j++) { usize i = (b << 6) + j; if (i >= p->dl) break; if (ent_v_geto(p, i, VIS)) w |= 1ULL << j; } p->v[b] = w; p->vp[b] = s; s += u64_popcount(w); } p->vl = s; } // }}} // Filtering {{{ static inline usize fm_filter_pct_rank(struct fm *p, usize idx) { usize b = idx >> 6; usize o = idx & 63; u64 m = o ? ((1ULL << o) - 1) : 0ULL; return (usize)p->vp[b] + u64_popcount(p->v[b] & m); } static inline void fm_filter_apply(struct fm *p, fm_filter f, cut cl, cut cr) { for (usize i = 0; i < p->dl; i++) if (ent_v_geto(p, i, TOMB)) fm_v_clr(p, i); else fm_v_assign(p, i, f(p, i, cl, cr)); fm_v_rebuild(p); p->f |= FM_REDRAW_DIR|FM_REDRAW_NAV; } static inline void fm_filter_apply_inc(struct fm *p, fm_filter f, cut cl, cut cr) { for (usize i = ent_next(p, 0); i != SIZE_MAX; i = ent_next(p, i + 1)) if (ent_v_geto(p, i, TOMB) || !f(p, i, cl, cr)) fm_v_clr(p, i); fm_v_rebuild(p); p->f |= FM_REDRAW_DIR|FM_REDRAW_NAV; } static int fm_filter_hidden(struct fm *p, usize i, cut cl, cut cr) { (void)cl; (void)cr; if (ent_v_geto(p, i, TOMB)) return 0; if (p->f & FM_HIDDEN) return 1; return !ent_v_geto(p, i, DOT); } static inline int fm_filter_startswith(struct fm *p, usize i, cut cl, cut cr) { usize al = cl.l; usize bl = cr.l; const char *am = cl.d; const char *bm = cr.d; u64 m = ent_load(p, i); u32 o = ent_v_geto(p, i, OFF); cut n = { p->de + o, ent_get(m, LEN) }; usize w = al + bl; if (w > n.l) return 0; if (al && (*n.d != *am || (al > 1 && memcmp(n.d + 1, am + 1, al - 1)))) return 0; return !(bl && memcmp(n.d + al, bm, bl)); } static int fm_filter_substr(struct fm *p, usize i, cut cl, cut cr) { usize al = cl.l; usize bl = cr.l; const char *am = cl.d; const char *bm = cr.d; usize w = al + bl; if (!w) return 1; u64 m = ent_load(p, i); u32 o = ent_v_geto(p, i, OFF); cut n = { p->de + o, ent_get(m, LEN) }; if (w > n.l) return 0; for (usize j = 0, x = n.l - w; j <= x; j++) { if (al && memcmp(n.d + j, am, al)) continue; if (bl && memcmp(n.d + j + al, bm, bl)) continue; return 1; } return 0; } // // TODO: Make this incremental. // static inline void fm_filter_save(struct fm *p, cut cl, cut cr) { usize c = sizeof(p->vq); usize i = 0; if (cl.l) { usize n = MIN(cl.l, c - 1); memcpy(p->vq, cl.d, n); i += n; } if (cr.l && i < c - 1) { usize n = MIN(cr.l, c - 1 - i); memcpy(p->vq + i, cr.d, n); i += n; } p->vq[i] = 0; p->vql = i; // TODO: unhardcode this. p->vqw = p->r.clw + p->r.crw; } static inline void fm_filter_clear(struct fm *p) { fm_filter_apply(p, fm_filter_hidden, CUT_NULL, CUT_NULL); p->vql = 0; p->vqw = 0; p->f &= ~FM_SEARCH; } static inline usize fm_visible_select(struct fm *p, usize k) { if (k >= p->vl) return SIZE_MAX; usize lo = 0; usize hi = BITSET_W(p->dl); if (!hi) return SIZE_MAX; while (lo + 1 < hi) { usize mi = lo + ((hi - lo) >> 1); if (p->vp[mi] <= k) lo = mi; else hi = mi; } u64 w = p->v[lo]; usize rank = k - p->vp[lo]; for (; rank--; w &= w - 1); return (lo << 6) + u64_ctz(w); } // }}} // UTF8 Truncation {{{ #define DFM_HT_OCC 0x800u #define DFM_HT_CACHE 0x40000000u #define CACHE_HASH(x) ((u32)((x) & 0x0003F7FFu)) #define CACHE_LEN(x) ((u16)(((x) >> 18) & 0x0FFFu)) #define CACHE_IS(x) (((x) & (DFM_HT_CACHE | DFM_HT_OCC)) == DFM_HT_CACHE) #define CACHE_PACK(h,l) (DFM_HT_CACHE | \ ((h) & 0x0003F7FFu) | (((u32)(l) & 0x0FFFu) << 18)) static inline u16 fm_cache_hash(const struct fm *p, const char *n, usize l, usize c) { u32 h = hash_fnv1a32(n, l); u32 m = h; m ^= (u32)c * 0x9E3779B1u; m ^= (u32)p->dv * 0x85EBCA6Bu; m ^= m >> 16; return (u16)m; } static inline usize fm_cache_slot(u16 h) { return (h & 0xF7FFu) & (DFM_DIR_HT_CAP - 1); } static inline void fm_clear_cache(struct fm *p) { for (usize i = 0; i < DFM_DIR_HT_CAP; i++) if (CACHE_IS(p->ht[i])) p->ht[i] = 0; } static inline usize fm_cache_trunc_utf8(struct fm *p, const char *n, usize l, usize c, usize *oc) { u16 h = fm_cache_hash(p, n, l, c); usize i = fm_cache_slot(h); for (usize j = 0; j < 4; j++) { usize s = (i + j) & (DFM_DIR_HT_CAP - 1); u32 v = p->ht[s]; if (CACHE_IS(v) && CACHE_HASH(v) == (h & 0xF7FFu)) { *oc = c; return CACHE_LEN(v) < l ? CACHE_LEN(v) : l; } } usize tl = utf8_trunc(n, l, c, oc); for (usize j = 0; j < 4; j++) { usize s = (i + j) & (DFM_DIR_HT_CAP - 1); u32 v = p->ht[s]; if (CACHE_IS(v) || !(v & DFM_HT_OCC)) { p->ht[s] = CACHE_PACK(h, (u16)tl); break; } } return tl; } static inline usize fm_cache_trunc(struct fm *p, u64 m, const char *n, usize l, usize c) { if (!c) return 0; int u = ent_get(m, UTF8); if (l < c) return l; if (!u) return MIN(l, c); usize oc; return fm_cache_trunc_utf8(p, n, l, c, &oc); } // }}} // Directory Lookup {{{ #define DFM_HT_TOMB 0x7FFu #define DFM_HT_IS_FREE(x) (!((x) & DFM_HT_OCC)) static inline void fm_dir_ht_hash_split(u32 h, u16 *a, u8 *b) { u32 m = h ^ (h >> 16); u16 x = m & 0x07FF; *a = x ? x : 1; *b = (m >> 11) & 0x1F; } static inline usize fm_dir_ht_find(struct fm *p, cut c, u16 *o) { u32 h = hash_fnv1a32(c.d, c.l); u16 a; u8 b; fm_dir_ht_hash_split(h, &a, &b); usize i = h & (DFM_DIR_HT_CAP - 1); for (;;) { u32 s = p->ht[i]; if (!s) { *o = 0xFFFF; return i; } if ((s & DFM_HT_OCC) && !CACHE_IS(s) && (s & 0x07FF) == a) { u64 m = ent_load_off(p, s >> 12); if (ent_get(m, HASH) == b) { u16 j = (u16)ent_get(m, LOC); if (!ent_v_geto(p, j, TOMB) && cut_cmp(fm_ent(p, j), c)) { *o = j; return i; } } } i = (i + 1) & (DFM_DIR_HT_CAP - 1); } } static inline int fm_dir_exists(struct fm *p, cut c) { u16 i; fm_dir_ht_find(p, c, &i); return i != 0xFFFF; } static inline usize fm_dir_ht_find_insert(struct fm *p, u32 h) { usize i = h & (DFM_DIR_HT_CAP - 1); usize ft = SIZE_MAX; for (;;) { u32 s = p->ht[i]; if (s == DFM_HT_TOMB) { if (ft == SIZE_MAX) ft = i; } else if (DFM_HT_IS_FREE(s) || CACHE_IS(s)) return ft != SIZE_MAX ? ft : i; i = (i + 1) & (DFM_DIR_HT_CAP - 1); } } static inline void fm_dir_ht_insert(struct fm *p, cut c, u16 o, u64 *m) { u32 h = hash_fnv1a32(c.d, c.l); u16 a; u8 b; fm_dir_ht_hash_split(h, &a, &b); ent_set(m, HASH, b); usize i = fm_dir_ht_find_insert(p, h); p->ht[i] = (ent_v_geto(p, o, OFF) << 12) | DFM_HT_OCC | a; } static inline void fm_dir_ht_remove(struct fm *p, usize i) { p->ht[i] = DFM_HT_TOMB; } static inline void fm_dir_ht_clear(struct fm *p) { memset(p->ht, 0, sizeof(p->ht)); } // }}} // Draw {{{ static inline void fm_draw_flush(struct fm *p) { STR_PUSH(&p->io, VT_ESU); p->io.f(&p->io, p, 0); STR_PUSH(&p->io, VT_BSU); } static inline void fm_draw_name_tail(str *s, const char *n, usize l, usize dr, int utf8, int ctrl) { usize mtc = dr > DFM_TRUNC_WIDTH ? dr - DFM_TRUNC_WIDTH : 0; usize c = MIN(mtc, DFM_TRUNC_LEN); usize tc = 0; usize t = l; if (utf8) { while (t > 0 && tc < c) { usize pr = t - 1; while (pr > 0 && (n[pr] & 0xC0) == 0x80) pr--; u32 cp; int e; utf8_decode_untrusted((void *)(n + pr), &cp, &e); usize w = e ? 1 : utf8_width(cp); if (tc + w > c) break; tc += w; t = e ? t - 1 : pr; } } else { tc = MIN(l, c); t = l - tc; } if (dr <= tc + tc + DFM_TRUNC_WIDTH) return; vt_cub(s, tc + DFM_TRUNC_WIDTH + 1); STR_PUSH(s, DFM_TRUNC_STR); str_push_q(s, n + t, l - t, ctrl); } static inline usize fm_draw_name_ellipsis(struct fm *p, u64 m, const char *n, usize l, usize c) { if (c < 2) return 0; c--; int ct = ent_get(m, CTRL); if (!ent_get(m, UTF8)) { usize d = MIN(l, c); str_push_q(&p->io, n, d, ct); if (l > d) fm_draw_name_tail(&p->io, n, l, d + 1, 0, ct); return d; } usize dr; usize tl = fm_cache_trunc_utf8(p, n, l, c, &dr); str_push_q(&p->io, n, tl, ct); if (tl < l) fm_draw_name_tail(&p->io, n, l, dr, 1, ct); return dr; } static inline void fm_draw_ent(struct fm *p, usize n) { u64 e = ent_load(p, n); u32 o = ent_v_geto(p, n, OFF); u32 t = ent_get(e, TYPE); s32 vw = p->col; switch (p->dv) { case 's': vw -= 7; ent_size_decode(&p->io, ent_get(e, SIZE), 6, t); break; case 'p': vw -= 11; ent_perm_decode(&p->io, ent_get(e, PERM), t); break; case 't': vw -= 8; ent_time_decode(&p->io, ent_get(e, TIME)); break; case 'a': vw -= 26; ent_perm_decode(&p->io, ent_get(e, PERM), t); ent_time_decode(&p->io, ent_get(e, TIME)); ent_size_decode(&p->io, ent_get(e, SIZE), 6, t); break; } switch (t) { case ENT_DIR: STR_PUSH(&p->io, DFM_COL_DIR); vw--; break; case ENT_FIFO: STR_PUSH(&p->io, DFM_COL_FIFO); break; case ENT_LNK: STR_PUSH(&p->io, DFM_COL_LNK); break; case ENT_LNK_BRK: STR_PUSH(&p->io, DFM_COL_LNK_BRK); break; case ENT_LNK_DIR: STR_PUSH(&p->io, DFM_COL_LNK_DIR); break; case ENT_REG_EXEC: STR_PUSH(&p->io, DFM_COL_REG_EXEC); vw--; break; case ENT_SOCK: STR_PUSH(&p->io, DFM_COL_SOCK); break; case ENT_SPEC: STR_PUSH(&p->io, DFM_COL_SPEC); break; case ENT_UNKNOWN: STR_PUSH(&p->io, DFM_COL_UNKNOWN); break; } int m = p->f & FM_MARK_PWD && p->vml && ent_v_geto(p, n, MARK); if (m) { STR_PUSH(&p->io, DFM_COL_MARK " "); vw -= 2; } if (p->c == n) STR_PUSH(&p->io, DFM_COL_CURSOR); usize l = ent_get(e, LEN); const char *dn = &p->de[o]; vw -= fm_draw_name_ellipsis(p, e, dn, l, vw); switch (t) { case ENT_LNK_DIR: case ENT_DIR: str_push_c(&p->io, '/'); break; case ENT_REG_EXEC: str_push_c(&p->io, '*'); break; } if (m) str_push_c(&p->io, '*'); if (ENT_IS_LNK(t)) { u8 sl = ent_get(e, SIZE); vw -= 4; if (vw <= 16) goto e; STR_PUSH(&p->io, VT_SGR0 " -> "); if (sl) { dn = &p->de[o + l + 2]; vw = fm_cache_trunc(p, dn[-1], dn, sl, vw); int ct = ent_get(dn[-1], CTRL); str_push_q(&p->io, dn, vw, ct); } else str_push_c(&p->io, '?'); } e: STR_PUSH(&p->io, VT_SGR0 VT_EL0 VT_CR); } static inline usize fm_draw_pwd(struct fm *p, s32 vw) { int ctrl = p->f & FM_PWD_CTRL; int utf8 = p->f & FM_PWD_UTF8; if (vw <= 0) return 0; if (!utf8) { if (p->pwd.l < (usize)vw) { str_push_q(&p->io, p->pwd.m, p->pwd.l, ctrl); return p->pwd.l; } } else { usize fc; usize fb = fm_cache_trunc_utf8(p, p->pwd.m, p->pwd.l, (usize)vw, &fc); if (fb == p->pwd.l) { str_push_q(&p->io, p->pwd.m, p->pwd.l, ctrl); return fc; } } usize i = p->pwd.l; for (; i > 0 && p->pwd.m[i - 1] != '/'; i--); if (i < p->pwd.l && p->pwd.m[i] == '/') i++; usize tl = p->pwd.l - i; const char *tail = p->pwd.m + i; usize tc = 0; usize tb = 0; if (utf8) tb = fm_cache_trunc_utf8(p, tail, tl, (usize)vw, &tc); else { tb = MIN(tl, (usize)vw); tc = tb; } if (tc + DFM_TRUNC_WIDTH + 1 < (usize)vw) { usize ac = (usize)vw - tc - DFM_TRUNC_WIDTH - 1; usize hc = 0; usize hb = 0; if (utf8) hb = fm_cache_trunc_utf8(p, p->pwd.m, i, ac, &hc); else { hb = MIN(i, ac); hc = hb; } str_push_q(&p->io, p->pwd.m, hb, ctrl); STR_PUSH(&p->io, DFM_TRUNC_STR "/"); str_push_q(&p->io, tail, tl, ctrl); return hc + DFM_TRUNC_WIDTH + tc; } str_push_q(&p->io, tail, tb, ctrl); return tc; } static inline void fm_draw_search_query(struct fm *p, usize qw) { str_push_c(&p->io, '/'); STR_PUSH(&p->io, VT_SGR(1)); if (p->sf == fm_filter_substr) str_push_c(&p->io, '*'); usize qu = MIN(qw, p->vqw); usize of = p->vql; for (usize acc = 0; of > 0; ) { usize pr = of - 1; while (pr > 0 && (p->vq[pr] & 0xC0) == 0x80) pr--; u32 cp; utf8_decode((void *)(p->vq + pr), &cp); usize w = utf8_width(cp); if (acc + w > qu) break; acc += w; of = pr; } if (of < p->vql) { str_push(&p->io, p->vq + of, p->vql - of); STR_PUSH(&p->io, "*" VT_SGR0); } } static inline void fm_draw_dir(struct fm *p) { usize s = p->y >= p->o ? p->y - p->o : 0; usize m = p->vl - s; usize d = MIN(m, p->row); usize c = fm_visible_select(p, s); STR_PUSH(&p->io, VT_CUP1); for (usize i = 0; i < d && c != SIZE_MAX; i++) { fm_draw_ent(p, c); STR_PUSH(&p->io, VT_CUD1); c = ent_next(p, c + 1); } for (usize i = d; i < p->row; i++) STR_PUSH(&p->io, VT_EL2 VT_CUD1); } static inline void fm_draw_nav_begin(struct fm *p, cut c) { vt_cup(&p->io, 0, p->row + (DFM_MARGIN - 1)); str_push(&p->io, c.d, c.l); str_memset(&p->io, ' ', p->col); STR_PUSH(&p->io, VT_CR); } static inline void fm_draw_nav_end(struct fm *p) { STR_PUSH(&p->io, VT_SGR0); } static inline void fm_draw_inf(struct fm *p) { cut c = p->f & (FM_TRUNC|FM_ERROR) ? CUT(DFM_COL_NAV_ERR) : p->f & FM_ROOT ? CUT(DFM_COL_NAV_ROOT) : CUT(DFM_COL_NAV); s32 vw = p->col; fm_draw_nav_begin(p, c); if (unlikely(p->nl)) { STR_PUSH(&p->io, " " VT_SGR(1)); str_push_c(&p->io, p->nl); STR_PUSH(&p->io, VT_SGR0); str_push(&p->io, c.d, c.l); vw -= 2; } str_push_c(&p->io, ' '); vw -= str_push_u32(&p->io, p->y + !!p->vl); str_push_c(&p->io, '/'); vw -= str_push_u32(&p->io, p->vl); str_push_c(&p->io, ' '); vw -= 3; str_push_c(&p->io, '['); vw -= 3; if (unlikely(p->f & FM_ROOT)) { str_push_c(&p->io, 'R'); vw--; } if (likely(!(p->f & FM_TRUNC))) str_push_c(&p->io, p->ds); else str_push_c(&p->io, 'T'); if (unlikely(p->f & FM_ERROR)) { str_push_c(&p->io, 'E'); vw--; } if (unlikely(p->f & FM_HIDDEN)) { str_push_c(&p->io, 'H'); vw--; } STR_PUSH(&p->io, "] "); if (vw > 10 && p->vml) { STR_PUSH(&p->io, DFM_COL_NAV_MARK " "); vw -= str_push_u32(&p->io, p->vml); STR_PUSH(&p->io, "+ " VT_SGR0); str_push(&p->io, c.d, c.l); str_push_c(&p->io, ' '); vw -= 4; } if (vw > 20 && likely(!(p->f & FM_TRUNC))) { STR_PUSH(&p->io, "~"); vw -= ent_size_decode(&p->io, p->du, 0, ENT_TYPE_MAX); STR_PUSH(&p->io, " "); vw -= 2; } if (vw > 5) { if (p->f & FM_SEARCH) { s32 qw = vw - 3 + (p->sf == fm_filter_substr); if (qw < 0) qw = 0; usize qu = MIN((usize)qw, p->vqw); usize pa = (usize)qw - qu; if (p->vqw <= (usize)qw) fm_draw_pwd(p, pa); fm_draw_search_query(p, (usize)qw); } else vw -= fm_draw_pwd(p, vw - 1); } fm_draw_nav_end(p); } static inline void fm_draw_msg(struct fm *p, const char *s, usize l) { p->f |= FM_MSG|FM_REDRAW_NAV; rl_clear(&p->r); str_push(&p->r.cl, s, l); } static inline void fm_draw_err(struct fm *p, const char *s, usize l, int e) { p->f |= FM_MSG_ERR|FM_REDRAW_NAV; rl_clear(&p->r); STR_PUSH(&p->r.cl, " error: "); str_push(&p->r.cl, s, l); if (!e) return; STR_PUSH(&p->r.cl, ": "); str_push_s(&p->r.cl, strerror(e)); } static inline void fm_draw_cmd(struct fm *p) { vt_cup(&p->io, 0, p->row + DFM_MARGIN); rl_write_visible(&p->r, &p->io); STR_PUSH(&p->io, VT_EL0); } static inline void fm_draw_buf(struct fm *p, cut c) { fm_draw_nav_begin(p, c); str_push(&p->io, p->r.cl.m, p->r.cl.l); fm_draw_nav_end(p); } static inline void fm_draw_nav(struct fm *p) { if (p->f & (FM_MSG|FM_MSG_ERR)) { fm_draw_buf(p, p->f & FM_MSG ? CUT(DFM_COL_NAV_MSG) : CUT(DFM_COL_NAV_ERR)); rl_clear(&p->r); p->f &= ~(FM_MSG|FM_MSG_ERR); } else fm_draw_inf(p); } // }}} // Cursor {{{ static inline void fm_cursor_set(struct fm *p, usize y, usize o) { if (!p->vl || !p->row) { p->y = 0; p->o = 0; p->c = ent_next(p, 0); return; } if (y >= p->vl) y = p->vl - 1; if (o >= p->row) o = p->row - 1; if (o > y) o = y; p->y = y; p->o = o; p->c = fm_visible_select(p, y); } static inline void fm_scroll_to(struct fm *p, cut d) { if (!p->vl) goto e; u16 i; fm_dir_ht_find(p, d, &i); if (i == 0xFFFF || !ent_v_geto(p, i, VIS)) goto e; usize r = fm_filter_pct_rank(p, i); usize ms = p->vl > p->row ? p->vl - p->row : 0; usize h = p->row >> 1; usize s = r <= p->row - 2 ? 0 : r >= ms ? ms : r > h ? r - h : 0; if (s > ms) s = ms; fm_cursor_set(p, r, r - s); return; e: fm_cursor_set(p, 0, 0); } static inline size fm_scroll_to_rank(struct fm *p, usize r) { size dy = (size)r - (size)p->y; if (!dy || !p->vl) return 0; if (dy > (size)p->row || dy < -(size)p->row) { size h = (size)p->row >> 1; size j = (size)r - (dy > 0 ? h : -h); if (j < 0) j = 0; if (j >= (size)p->vl) j = (size)p->vl - 1; fm_cursor_set(p, (usize)j, 0); p->f |= FM_REDRAW_DIR|FM_REDRAW_NAV; dy = (size)r - (size)p->y; } return dy; } static inline void fm_cursor_sync(struct fm *p) { if (!p->vl || !p->row) { p->y = 0; p->o = 0; p->c = SIZE_MAX; return; } if (p->y >= p->vl) p->y = p->vl - 1; if (p->o >= p->row) p->o = p->row - 1; if (p->o > p->y) p->o = p->y; p->c = fm_visible_select(p, p->y); } // }}} // Terminal {{{ static inline int fm_term_resize(struct fm *p) { if (term_size_update(&p->t, &p->row, &p->col) < 0) return -1; p->row = p->row > DFM_MARGIN ? p->row - DFM_MARGIN : 1; rl_vw_set(&p->r, p->col); vt_decstbm(&p->io, 1, p->row); fm_cursor_set(p, p->y, p->o); p->f |= FM_REDRAW; return 0; } static inline int fm_term_raw(struct fm *p) { STR_PUSH(&p->io, VT_ALT_SCREEN_Y VT_DECTCEM_N VT_DECAWM_N VT_BPASTE_ON VT_ED2 VT_CUP1); return term_raw(&p->t) < 0 ? -1 : fm_term_resize(p); } static inline int fm_term_cooked(struct fm *p) { vt_decstbm(&p->io, 1, p->row + DFM_MARGIN); STR_PUSH(&p->io, VT_SGR0 VT_BPASTE_OFF VT_DECAWM_Y VT_DECTCEM_Y VT_ALT_SCREEN_N); fm_draw_flush(p); return term_cooked(&p->t); } static inline int fm_term_init(struct fm *p) { int r = term_init(&p->t); return fm_term_raw(p) < 0 ? -1 : r < 0 ? -1 : fm_term_resize(p); } static inline int fm_term_free(struct fm *p) { int r = fm_term_cooked(p); term_destroy(&p->t); return r; } // }}} // Entry Marking {{{ static inline void * fm_mark_slot(struct fm *p, usize i) { return p->d.d + i * sizeof(char *); } static inline char * fm_mark_load(struct fm *p, usize i) { char *v; memcpy(&v, fm_mark_slot(p, p->mp + i), sizeof(v)); return v; } static inline void fm_mark_store(struct fm *p, usize i, char *v) { memcpy(fm_mark_slot(p, p->mp + i), &v, sizeof(v)); } static inline int fm_mark_has_room(const struct fm *p) { return p->mp * sizeof(char *) > (p->dl + DFM_MARK_CMD_PRE) * sizeof(u32) + sizeof(char *); } static inline u8 fm_mark_len(const char *p) { return (u8)p[-1]; } static inline cut fm_mark_at(struct fm *p, usize i) { char *m = fm_mark_load(p, i); return (cut) { m, fm_mark_len(m) }; } static inline void fm_mark_terminate(struct fm *p) { fm_mark_store(p, p->ml, NULL); } static inline void fm_mark_write_at(struct fm *p, usize i, char *v) { fm_mark_store(p, i, v); } static inline void fm_mark_write_newest(struct fm *p, char *v) { p->mp--; p->ml++; fm_mark_store(p, 0, v); fm_mark_terminate(p); } static inline void fm_mark_clear_ptr(struct fm *p) { #define DIR_PTR_CAP (DFM_DIR_MAX / (sizeof(char *) / sizeof(u32))) p->mp = DIR_PTR_CAP - DFM_MARK_CMD_PRE - DFM_MARK_CMD_POST; } static inline void fm_mark_clear_range(struct fm *p, usize lo, usize hi) { if (hi <= lo) return; usize b0 = lo >> 6; usize b1 = (hi - 1) >> 6; for (usize b = b0; b <= b1; b++) { u64 m = ~0ULL; if (b == b0) { u64 m0 = (lo & 63) ? ((1ULL << (lo & 63)) - 1ULL) : 0ULL; m &= ~m0; } if (b == b1) { u64 end = ((hi - 1) & 63); u64 m1 = (end == 63) ? ~0ULL : ((1ULL << (end + 1)) - 1ULL); m &= m1; } u64 tc = p->vm[b] & p->v[b] & m; if (!tc) continue; u64 w = tc; while (w) { usize i = (b << 6) + u64_ctz(w); w &= w - 1; if (i >= p->dl) break; u32 x = ent_v_load(p, i); ent_v_set(&x, MARK, 0); ent_v_store(p, i, x); } p->vm[b] &= ~m; p->vml -= u64_popcount(tc); } } static inline void fm_mark_clear_all(struct fm *p) { p->ml = 0; p->vml = 0; memset(p->vm, 0, sizeof(p->vm)); fm_mark_clear_ptr(p); fm_mark_terminate(p); for (usize i = 0; i < p->dl; i++) { u32 e = ent_v_load(p, i); ent_v_set(&e, MARK, 0); ent_v_store(p, i, e); } p->dec = sizeof(p->de); } static inline int fm_mark_push(struct fm *p, cut c) { usize n = c.l + 4; if (unlikely(!fm_mark_has_room(p) || p->dec < p->del + n)) return 0; p->dec -= n; char *b = p->de + p->dec; u16 h = (u16) hash_fnv1a32(c.d, c.l); b[0] = (unsigned char)(h & 0xff); b[1] = (unsigned char)(h >> 8); b[2] = (unsigned char)c.l; memcpy(b + 3, c.d, c.l); b[c.l + 3] = 0; fm_mark_write_newest(p, b + 3); p->f |= FM_MARK_PWD; return 1; } static inline void fm_mark_drop_idx(struct fm *p, usize i) { if (!p->ml) return; if (i != p->ml - 1) fm_mark_write_at(p, i, fm_mark_load(p, p->ml - 1)); p->ml--; fm_mark_terminate(p); } static inline usize fm_mark_find(struct fm *p, usize c, int d) { usize n = SIZE_MAX; usize nw = BITSET_W(p->dl); for (usize b = 0; b < nw; b++) { for (u64 w = p->vm[b] & p->v[b]; w; ) { usize j = (b << 6) + u64_ctz(w); w &= w - 1; if (d > 0) { if (j > c && (n == SIZE_MAX || j < n)) n = j; } else { if (j < c && (n == SIZE_MAX || j > n)) n = j; } } } return n; } static inline void fm_mark_apply_bitset(struct fm *p, const u64 *s) { usize nw = BITSET_W(p->dl); for (usize b = 0; b < nw; b++) { u64 w = s[b]; while (w) { usize i = (b << 6) + u64_ctz(w); w &= w - 1; if (i >= p->dl) break; u32 x = ent_v_load(p, i); ent_v_set(&x, MARK, 1); ent_v_store(p, i, x); } } } static inline void fm_mark_invalidate(struct fm *p) { p->ml = 0; fm_mark_clear_ptr(p); fm_mark_terminate(p); p->dec = sizeof(p->de); } static inline usize fm_mark_materialize_range(struct fm *p, usize *x) { if (!p->vml) return 0; if (!p->mpwd.l) return 0; usize n = 0; usize i = *x; usize nw = BITSET_W(p->dl); for (usize b = i >> 6; b < nw; b++) { u64 w = p->vm[b] & p->v[b]; if (b == (i >> 6)) w &= ~((1ULL << (i & 63)) - 1ULL); while (w) { usize bit = (b << 6) + u64_ctz(w); w &= w - 1; if (bit >= p->dl) continue; cut c = fm_ent(p, bit); usize cl = c.l + 4; if (!fm_mark_has_room(p) || p->dec < p->del + cl) { *x = n ? bit : i; return n; } if (!fm_mark_push(p, c)) { *x = n ? bit : i; return n; } n++; *x = bit + 1; } } *x = p->dl; return n; } static inline int fm_mark_materialize(struct fm *p) { if (!p->vml) return 0; if (p->ml) return 0; if (!p->mpwd.l) return 0; if (!str_cmp(&p->mpwd, &p->pwd)) return 0; fm_mark_invalidate(p); usize oml = p->ml; usize omp = p->mp; usize odec = p->dec; usize i = 0; usize n = fm_mark_materialize_range(p, &i); if (n != p->vml) { p->ml = oml; p->mp = omp; p->dec = odec; return -1; } return 0; } static inline void fm_mark_clear_idx(struct fm *p, usize i) { if (!ent_v_geto(p, i, MARK)) return; u32 x = ent_v_load(p, i); ent_v_set(&x, MARK, 0); ent_v_store(p, i, x); usize b = i >> 6; u64 bit = 1ULL << (i & 63); p->vm[b] &= ~bit; p->vml--; } static inline void fm_mark_pop_first(struct fm *p) { if (!p->ml) return; cut m = fm_mark_at(p, 0); u16 j; fm_dir_ht_find(p, m, &j); if (j != 0xFFFF) fm_mark_clear_idx(p, j); fm_mark_drop_idx(p, 0); } static inline void fm_mark_clear(struct fm *p) { fm_mark_clear_all(p); p->mpwd.l = 0; p->f &= ~FM_MARK_PWD; } static inline void fm_mark_init(struct fm *p) { p->mpwd.l = 0; str_push(&p->mpwd, p->pwd.m, p->pwd.l); str_terminate(&p->mpwd); p->f |= FM_MARK_PWD; } static inline u32 fm_mark_toggle_idx(struct fm *p, usize i) { u8 s = ent_v_geto(p, i, MARK); u32 x = ent_v_load(p, i); ent_v_set(&x, MARK, !s); ent_v_store(p, i, x); usize b = i >> 6; u64 bit = 1ULL << (i & 63); if (s) { p->vm[b] &= ~bit; p->vml--; } else { p->vm[b] |= bit; p->vml++; } if (p->ml) fm_mark_invalidate(p); return 1; } // }}} // Filesystem {{{ static inline int fm_dir_has_room(const struct fm *p, usize e) { return (p->dl + e) * sizeof(u32) <= p->mp * sizeof(char *) - DFM_MARK_CMD_PRE * sizeof(char *); } static inline void fm_dir_rebuild_loc(struct fm *p) { for (usize i = 0; i < p->dl; i++) { u64 m = ent_load(p, i); ent_set(&m, LOC, (u16)i); ent_store(p, i, m); } } static inline void fm_dir_sort(struct fm *p) { if (likely(!(p->f & FM_TRUNC))) { fm_ent_qsort(p, fm_sort_fn(p->ds), 0, p->dl, 32); fm_dir_rebuild_loc(p); } fm_filter f = rl_empty(&p->r) ? fm_filter_hidden : p->sf; fm_filter_apply(p, f, rl_cl_get(&p->r), rl_cr_get(&p->r)); fm_cursor_set(p, p->y, p->o); } static inline void fm_dir_mark_rebuild(struct fm *p) { if (!p->ml || !(p->f & FM_MARK_PWD)) return; memset(p->vm, 0, sizeof(p->vm)); p->vml = 0; for (usize i = 0; i < p->dl; i++) { u32 x = ent_v_load(p, i); ent_v_set(&x, MARK, 0); ent_v_store(p, i, x); } for (usize i = 0; i < p->ml; i++) { cut m = fm_mark_at(p, i); u16 j; fm_dir_ht_find(p, m, &j); if (j != 0xFFFF) { u32 x = ent_v_load(p, j); ent_v_set(&x, MARK, 1); ent_v_store(p, j, x); p->vm[j >> 6] |= 1ULL << (j & 63); p->vml++; u64 md = ent_load(p, j); ent_store(p, j, md); } } } static inline void fm_dir_clear(struct fm *p) { p->y = 0; p->o = 0; p->c = 0; p->f &= ~FM_TRUNC; rl_clear(&p->r); p->del = 0; p->dl = 0; p->du = 0; p->st = 0; fm_dir_ht_clear(p); } static inline int fm_dir_load_ent(struct fm *p, const char *s) { if (s[0] == '.' && (s[1] == '\0' || (s[1] == '.' && s[2] == '\0'))) return 0; if (unlikely(!fm_dir_has_room(p, 1))) return -1; u8 utf8; u8 ctrl; u8 l = (u8)ent_name_len(s, &utf8, &ctrl); if (unlikely(p->del + sizeof(u64) + l + 1 >= p->dec)) return -1; u64 m = 0; u32 o = p->del; u32 x = 0; ent_v_set(&x, OFF, o + sizeof(m)); ent_v_set(&x, CHAR, s[0]); ent_v_set(&x, DOT, s[0] == '.'); ent_v_store(p, p->dl, x); ent_set(&m, LEN, l); ent_set(&m, LOC, (u16)p->dl); ent_set(&m, UTF8, utf8); ent_set(&m, CTRL, ctrl); memcpy(p->de + p->del + sizeof(m), s, l + 1); p->del += sizeof(m) + l + 1; p->dl++; struct stat st; if (unlikely(fstatat(p->dfd, s, &st, AT_SYMLINK_NOFOLLOW) == -1)) { ent_set(&m, TYPE, ENT_UNKNOWN); ent_set(&m, SIZE, 0); ent_set(&m, TIME, 0); ent_set(&m, PERM, 0); goto t; } if (S_ISLNK(st.st_mode)) { struct stat ts; if (fstatat(p->dfd, s, &ts, 0) == -1) ent_map_stat(&m, &st, ENT_LNK_BRK); else ent_map_stat(&m, &ts, S_ISDIR(ts.st_mode) ? ENT_LNK_DIR : ENT_LNK); usize ll = (usize)st.st_size; if (p->del + ll + 2 < p->dec) { char *lm = p->de + p->del + 1; ssize_t r = readlinkat(p->dfd, s, lm, st.st_size); if (likely(r != -1)) { u8 lu; u8 ct; ent_name_len(lm, &lu, &ct); u8 f = 0; lnk_set(&f, UTF8, lu); lnk_set(&f, CTRL, ct); lm[-1] = f; lm[ll] = 0; p->del += ll + 2; } else { ent_set(&m, SIZE, 0); goto t; } } goto w; } ent_map_stat(&m, &st, ENT_TYPE_MAX); w: ent_map_stat_size(&m, &st); u64 sz = ent_size_bytes(ent_get(m, SIZE), ent_get(m, TYPE)); p->du = ent_size_add(p->du, sz); t: fm_dir_ht_insert(p, (cut){ s, l }, (u16)(p->dl - 1), &m); memcpy(p->de + o, &m, sizeof(m)); return 0; } static inline int fm_dir_load(struct fm *p) { int d = openat(p->dfd, ".", O_RDONLY|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW); if (d < 0) return 0; DIR *n = fdopendir(d); if (unlikely(!n)) { close(d); return 0; } fm_dir_clear(p); for (struct dirent *e; (e = readdir(n)); ) if (fm_dir_load_ent(p, e->d_name) == -1) { p->f |= FM_TRUNC; break; } closedir(n); fm_dir_sort(p); fm_dir_mark_rebuild(p); fs_watch(&p->p, "."); return 1; } static inline int fm_dir_add(struct fm *p, cut c) { if (fm_dir_exists(p, c)) return 0; if (fm_dir_load_ent(p, c.d) == -1) return -1; int h = !(p->f & FM_HIDDEN) && *c.d == '.'; fm_v_assign(p, p->dl - 1, !h); p->f |= FM_DIRTY; p->st = ent_v_geto(p, p->dl - 1, OFF); return 0; } static inline int fm_dir_del(struct fm *p, cut c) { u16 f; usize s = fm_dir_ht_find(p, c, &f); if (f == 0xFFFF) return -1; u64 m = ent_load(p, f); u64 sz = ent_size_bytes(ent_get(m, SIZE), ent_get(m, TYPE)); p->du = ent_size_sub(p->du, sz); u32 x = ent_v_load(p, f); ent_v_set(&x, TOMB, 1); ent_v_set(&x, MARK, 0); ent_v_store(p, f, x); fm_dir_ht_remove(p, s); p->f |= FM_DIRTY; return 0; } static inline void fm_dir_refresh(struct fm *p) { cut o = p->c == SIZE_MAX ? CUT_NULL : fm_ent(p, p->c); fm_dir_load(p); fm_scroll_to(p, o); fm_cursor_sync(p); p->f |= FM_DIRTY; } // }}} // Core {{{ static inline void fm_path_classify(struct fm *p) { u8 ut; u8 ct; ent_name_len(p->pwd.m, &ut, &ct); p->f ^= (-ut ^ p->f) & FM_PWD_UTF8; p->f ^= (-ct ^ p->f) & FM_PWD_CTRL; } static inline int fm_path_change(struct fm *p) { fm_filter_clear(p); if (fm_mark_materialize(p) < 0) { fm_draw_err(p, S("not enough memory to materialize marks, unmark to cd"), 0); return 0; } return 1; } static inline int fm_path_open(struct fm *p) { int fd = open(p->pwd.m, O_DIRECTORY|O_CLOEXEC); if (fd == -1) return 0; if (p->dfd != AT_FDCWD) close(p->dfd); p->dfd = fd; p->f ^= (-(p->mpwd.l && str_cmp(&p->mpwd, &p->pwd)) ^ p->f) & FM_MARK_PWD; return fchdir(fd) != -1; } static inline void fm_path_save(struct fm *p) { p->ppwd.l = 0; str_push(&p->ppwd, p->pwd.m, p->pwd.l); } static inline void fm_path_load(struct fm *p) { p->pwd.l = 0; str_push(&p->pwd, p->ppwd.m, p->ppwd.l); } static inline int fm_path_cd(struct fm *p, const char *d, usize l) { if (!fm_path_change(p)) return 0; fm_path_save(p); p->pwd.l = 0; str_push(&p->pwd, d, l); str_terminate(&p->pwd); usize nl = path_resolve(p->pwd.m, p->pwd.l); p->pwd.l = nl; int r = fm_path_open(p); if (!r) { fm_path_load(p); fm_draw_err(p, S("cd"), errno); } else fm_path_classify(p); return r && fm_dir_load(p); } static inline int fm_path_chdir(struct fm *p, const char *d) { if (!fm_path_change(p)) return 0; fm_path_save(p); p->pwd.l = 0; str_push(&p->pwd, d, strlen(d)); str_terminate(&p->pwd); int r = fm_path_open(p); if (!r || !fm_dir_load(p)) { fm_path_load(p); fm_draw_err(p, S("cd"), errno); return 0; } fm_path_save(p); if (!getcwd(p->pwd.m, p->pwd.c)) { fm_path_load(p); fm_draw_err(p, S("cd"), errno); return 0; } p->pwd.l = strlen(p->pwd.m); str_terminate(&p->pwd); fm_path_classify(p); return 1; } static inline int fm_path_cd_relative(struct fm *p, const char *d, u8 l) { if (!fm_path_change(p)) return 0; fm_path_save(p); if (p->pwd.l > 1) str_push_c(&p->pwd, '/'); str_push(&p->pwd, d, l); str_terminate(&p->pwd); usize nl = path_resolve(p->pwd.m, p->pwd.l); p->pwd.l = nl; int r = fm_path_open(p); if (!r) { fm_path_load(p); fm_draw_err(p, S("cd"), errno); } else fm_path_classify(p); return r && fm_dir_load(p); } static inline cut fm_path_cd_up(struct fm *p) { if (!fm_path_change(p)) return CUT_NULL; fm_path_save(p); usize l = p->pwd.l; usize i = l; for (; i > 1 && p->pwd.m[i - 1] != '/'; i--); usize n = (i > 1) ? i - 1 : 1; char s = p->pwd.m[n]; p->pwd.m[n] = 0; p->pwd.l = n; int r = fm_path_open(p); if (!r) { p->pwd.m[n] = s; p->pwd.l = l; fm_draw_err(p, S("cd"), errno); } else fm_path_classify(p); return r && fm_dir_load(p) ? (cut){ p->ppwd.m + i, l - i } : CUT_NULL; } static inline int fm_exec(struct fm *p, int in, const char *d, const char *const a[], bool bg, bool tf) { if (tf) fm_term_cooked(p); int r = run_cmd(bg ? p->t.null : p->t.fd, in, d, a, bg); if (tf) fm_term_raw(p); if (r == -1) { if (errno == ENOENT) fm_draw_err(p, S("exec: command not found"), 0); else fm_draw_err(p, S("exec"), errno); return -1; } if (WIFEXITED(r)) { int ec = WEXITSTATUS(r); if (ec == 127) { fm_draw_err(p, S("exec: command not found"), 0); return -1; } else if (ec) { fm_draw_err(p, S("exec: exited non-zero"), 0); return -1; } } if (WIFSIGNALED(r)) { fm_draw_err(p, S("exec: killed by signal"), 0); return -1; } return 0; } static inline void fm_open(struct fm *p) { if (p->c == SIZE_MAX) return; cut c = fm_ent(p, p->c); if (!c.l) return; u64 m = ent_load(p, p->c); if (ENT_IS_DIR(ent_get(m, TYPE))) fm_path_cd_relative(p, c.d, c.l); else if (unlikely(p->f & FM_PICKER)) { str_push_c(&p->pwd, '/'); str_push(&p->pwd, c.d, c.l); term_set_dead(&p->t, 1); } else { const char *const a[] = { p->opener.d, c.d, NULL }; fm_exec(p, -1, NULL, a, 0, 1); } } // }}} // Command {{{ struct fm_cmd { cut prompt; cut left; cut right; fm_key_press press; fm_key_enter enter; u32 config; }; #define FM_CMD(C, ...) \ static inline void \ C(struct fm *p) \ { \ fm_cmd(p, &(struct fm_cmd){__VA_ARGS__}); \ } enum { CMD_BG = 1 << 0, CMD_CONFLICT = 1 << 1, CMD_MUT = 1 << 2, CMD_EXEC = 1 << 3, CMD_MARK_DIR = 1 << 4, CMD_NOT_MARK_DIR = 1 << 5, CMD_STDIN = 1 << 6, CMD_FILE_CURSOR = 1 << 7, CMD_EXEC_MARK = 1 << 8, CMD_EXEC_ROOT = 1 << 9, CMD_MODE_EACH = 0, CMD_MODE_VIRTUAL, CMD_MODE_CHUNK, CMD_MODE_BULK, CMD_MODE_SINGLE, }; static inline void fm_cmd_exec(struct fm *p) { if (p->kd && p->kd(p, &p->r.cl) >= 0) rl_clear(&p->r); p->r.vx = 0; p->r.pr.l = 0; p->kp = 0; p->kd = 0; } static inline void fm_cmd(struct fm *p, struct fm_cmd *c) { if (!c->press && !c->enter) { fm_draw_err(p, S("no callbacks defined"), 0); return; } rl_clear(&p->r); rl_pr_set(&p->r, c->prompt); if (c->left.l) { str_push(&p->r.cl, c->left.d, c->left.l); str_terminate(&p->r.cl); } if (c->right.l) rl_cr_set(&p->r, c->right); if (c->config & CMD_FILE_CURSOR) { if (p->c == SIZE_MAX) return; cut e = fm_ent(p, p->c); str_push(&p->r.cl, e.d, e.l); } rl_cl_sync(&p->r); p->cf = c->config; p->kp = c->press; p->kd = c->enter; p->f |= FM_REDRAW_CMD; if (p->f & FM_ROOT && !(p->cf & CMD_EXEC_ROOT)) return; if ((p->cf & CMD_EXEC_MARK && p->vml) || p->cf & CMD_EXEC) { rl_join(&p->r); fm_cmd_exec(p); } } static inline void fm_cmd_search_press(struct fm *p, int k, cut cl, cut cr) { if (cl.l > 1 && k != KEY_BACKSPACE && p->vl != p->dl && !cr.l) { fm_filter_apply_inc(p, p->sf, cl, cr); } else fm_filter_apply(p, p->sf, cl, cr); fm_filter_save(p, cl, cr); fm_cursor_set(p, 0, 0); } static inline int fm_cmd_search(struct fm *p, str *s) { if (p->vl == 1) fm_open(p); else { if (s->l) { cut q = (cut){ s->m, s->l }; fm_filter_apply(p, p->sf, q, CUT_NULL); fm_filter_save(p, q, CUT_NULL); } else fm_filter_apply(p, fm_filter_hidden, (cut){ s->m, s->l }, CUT_NULL); fm_cursor_set(p, 0, 0); } return -1; } static inline int fm_cmd_cd(struct fm *p, str *s) { int r = 0; if (s->m[0] == '/') r = fm_path_cd(p, s->m, s->l); else r = fm_path_cd_relative(p, s->m, s->l); return r ? 0 : -1; } static u8 fm_prompt_conflict(struct fm *p, cut d) { fm_draw_nav_begin(p, CUT(DFM_COL_NAV_ERR)); STR_PUSH(&p->io, "conflict: '"); str_push(&p->io, d.d, d.l); STR_PUSH(&p->io, "': try overwrite?"); STR_PUSH(&p->io, " [a]bort [y]es [Y]es all [n]o [N]o all"); fm_draw_nav_end(p); fm_draw_flush(p); for (;;) { if (!term_key_read(p->t.fd, &p->k)) return 'a'; switch (p->k.b[0]) { case 'a': case 'y': case 'Y': case 'n': case 'N': return p->k.b[0]; } } } static inline int fm_prepare_marks_conflict(struct fm *p) { usize i = 0; cut m = CUT_NULL; int om = 0; if (!p->ml) { m = fm_ent(p, p->c); goto c; } for (; i < p->ml; ) { m = fm_mark_at(p, i); c: if (!fm_dir_exists(p, m)) goto s; if (om != 'Y' && om != 'N') om = fm_prompt_conflict(p, m); switch (om) { case 'a': return -1; case 'y': case 'Y': goto s; case 'n': fm_mark_drop_idx(p, i); om = -2; continue; case 'N': p->ml = 0; return -1; } s: i++; } return om; } static inline int fm_cmd_build_bulk_exec(struct fm *p, cut s, usize ti, usize tc, u32 f) { usize omi = ti; usize prn = ti; usize pon = (tc - omi - 1); usize tt = prn + p->ml + pon; usize pri = p->mp - prn; usize poi = p->mp + p->ml; char **m = (char **)(void *)p->d.d; cut c; for (usize i = 0, n = 0; next_tok(s.d, s.l, &n, &c); i++) { if (i == omi) { if (p->ml) continue; cut e = fm_ent(p, p->c); c = e; } else if (c.l == 2 && c.d[0] == '%' && c.d[1] == 'd') c = (cut) { p->pwd.m, p->pwd.l }; else if (c.l > 1 && c.d[0] == '$') { c.d = getenv(c.d + 1); if (!c.d) return -2; } m[i < ti ? pri++ : poi++] = (char *)c.d; } char **a = &m[p->mp - prn]; a[tt] = NULL; const char *wd = p->mpwd.m; return fm_exec(p, -1, wd, (const char *const *)a, f & CMD_BG, !(f & CMD_BG)); } static inline int fm_cmd_build_bulk_chunk(struct fm *p, cut s, usize ti, usize tc, u32 f) { int r = 0; usize b = 0; while (b < p->dl && p->vml) { fm_mark_invalidate(p); usize pb = b; usize n = fm_mark_materialize_range(p, &b); if (!n) break; r = fm_cmd_build_bulk_exec(p, s, ti, tc, f); if (r < 0) return r; fm_mark_clear_range(p, pb, b); } if (!p->vml) fm_mark_clear_all(p); return r; } static inline int fm_cmd_build_bulk(struct fm *p, cut s, usize ti, usize tc, u32 f) { if (fm_mark_materialize(p) < 0) { fm_draw_err(p, S("not enough memory to materialize marks"), 0); return -1; } if (fm_cmd_build_bulk_exec(p, s, ti, tc, f) < 0) return -1; fm_mark_clear_all(p); return 0; } static inline int fm_cmd_build(struct fm *p, cut s, usize ti, usize tc, u32 f, usize mp, cut mk, bool t) { char **m = (char **)(void *)p->d.d; usize pri = mp; cut c; for (usize j = 0, n = 0; next_tok(s.d, s.l, &n, &c); j++) if (j == ti) m[pri++] = (char *)mk.d; else if (c.l == 2 && c.d[0] == '%' && c.d[1] == 'd') m[pri++] = p->pwd.m; else if (c.l > 1 && c.d[0] == '$') { char *e = getenv(c.d + 1); if (!e) return -2; m[pri++] = e; } else m[pri++] = (char *)c.d; u32 lf = f; m[mp + tc] = NULL; int fd = -1; if (lf & CMD_STDIN) { fd = open(mk.d, O_RDONLY|O_CLOEXEC); if (fd < 0) return -1; } int r = fm_exec(p, fd, p->pwd.m, (const char *const *)&m[mp], lf & CMD_BG, t); if (fd >= 0) close(fd); return r; } static inline int fm_cmd_build_each_virtual(struct fm *p, cut s, usize ti, usize tc, u32 f) { cut mk = CUT_NULL; usize mp = p->mp - (tc + 1); if (!p->vml) { if (p->c == SIZE_MAX) return 0; mk = fm_ent(p, p->c); return fm_cmd_build(p, s, ti, tc, f, mp, mk, 1); } if (!(f & CMD_BG)) fm_term_cooked(p); int r = 0; usize nw = BITSET_W(p->dl); for (usize b = 0; b < nw; b++) { for (u64 w = p->vm[b] & p->v[b]; w; ) { usize i = (b << 6) + u64_ctz(w); w &= w - 1; if (i >= p->dl) break; mk = fm_ent(p, i); if (fm_cmd_build(p, s, ti, tc, f, mp, mk, 0) < 0) { r = -1; goto e; } fm_mark_clear_idx(p, i); if (!p->vml) goto e; } } e: if (!(f & CMD_BG)) fm_term_raw(p); return r; } static inline int fm_cmd_build_each(struct fm *p, cut s, usize ti, usize tc, u32 f) { cut mk = CUT_NULL; usize mp = p->mp - (tc + 1); if (!p->vml) { if (p->c == SIZE_MAX) return 0; mk = fm_ent(p, p->c); return fm_cmd_build(p, s, ti, tc, f, mp, mk, 1); } if (!(f & CMD_BG)) fm_term_cooked(p); for (; p->ml; ) { mk = fm_mark_at(p, 0); str fp = p->mpwd; str_push_c(&fp, '/'); str_push(&fp, mk.d, mk.l); str_terminate(&fp); mk = (cut) { fp.m, fp.l }; if (fm_cmd_build(p, s, ti, tc, f, mp, mk, 0) < 0) { if (!(f & CMD_BG)) fm_term_raw(p); return -1; } fm_mark_pop_first(p); if (p->vml) p->vml--; } if (!(f & CMD_BG)) fm_term_raw(p); return 0; } static inline int fm_cmd_sh(struct fm *p, cut c) { if (!c.l) return 0; cut f = !p->ml && p->c != SIZE_MAX ? fm_ent(p, p->c) : CUT_NULL; cut sh = get_env("SHELL", "/bin/sh"); const char *cmd[] = { sh.d, DFM_SHELL_OPTS, c.d, CFG_NAME, f.d, NULL }; STATIC_ASSERT(ARR_SIZE(cmd) < DFM_MARK_CMD_PRE, ""); usize l = ARR_SIZE(cmd) - (!f.l * 2); char **m = (char **)(void *)p->d.d; char **a = &m[p->mp - l]; memcpy(a, cmd, sizeof(*cmd) * l); if (!f.l) a[l + p->ml] = NULL; int r = fm_exec(p, -1, f.l ? p->pwd.m : p->mpwd.m, (const char *const *)a, 0, 1); if (!f.l) fm_mark_clear_all(p); if (r < 0) p->f |= FM_ERROR; return r; } static inline int fm_cmd_run_sh(struct fm *p, str *s) { if (fm_mark_materialize(p) < 0) { fm_draw_err(p, S("not enough memory to materialize marks"), 0); return -1; } usize e = s->m[0] == '!'; return fm_cmd_sh(p, (cut){ s->m + e, s->l - e }); } static inline cut fm_cmd_parse(struct fm *p, str *s, usize *oti, usize *ott, usize *otc) { cut c; usize tc = 0; usize ti = SIZE_MAX; usize tt = 0; usize li = 0; cut lt = CUT_NULL; for (usize i = 0, n = 0; next_tok(s->m, s->l, &n, &c); i++, tc++) { ((char *)c.d)[c.l] = 0; lt = c; li = i; if (ti == SIZE_MAX && c.l == 2 && c.d[0] == '%') { if (c.d[1] == 'm' || c.d[1] == 'f' ) { ti = i; tt = c.d[1]; } } } if (tc && lt.l == 1 && lt.d[0] == '&') { p->cf |= CMD_BG; tc--; if (ti != SIZE_MAX && li < ti) ti--; } *oti = ti; *ott = tt; *otc = tc; c = (cut){ s->m, s->l }; switch (s->m[0]) { case '<': c.d++; c.l--; p->cf |= CMD_STDIN; break; } return c; } static inline int fm_cmd_run(struct fm *p, str *s) { int m = CMD_MODE_SINGLE; int r = 0; if (!s->l) return 0; if (p->cf & CMD_MARK_DIR && !(p->f & FM_MARK_PWD) && p->vml) { fm_draw_err(p, S("not in mark directory"), 0); return -1; } if (p->cf & CMD_NOT_MARK_DIR && p->f & FM_MARK_PWD) { fm_draw_err(p, S("in mark directory"), 0); return -1; } if (s->m[0] == '!') return fm_cmd_run_sh(p, s); usize ti; usize tt; usize tc; cut a = fm_cmd_parse(p, s, &ti, &tt, &tc); if ((tt && (!p->vml && !p->vl))) { fm_draw_err(p, S("nothing to operate on"), 0); return -1; } if (p->cf & (CMD_STDIN|CMD_FILE_CURSOR)) m = CMD_MODE_SINGLE; else if (tt == 'm') { m = p->f & FM_MARK_PWD ? CMD_MODE_CHUNK : CMD_MODE_BULK; m = p->vml ? m : CMD_MODE_EACH; } else if (tt == 'f') m = p->f & FM_MARK_PWD ? CMD_MODE_VIRTUAL : CMD_MODE_EACH; switch (m) { case CMD_MODE_SINGLE: case CMD_MODE_EACH: case CMD_MODE_VIRTUAL: if (tc > DFM_MARK_CMD_PRE) { fm_draw_err(p, S("argv too large"), 0); return -1; } break; case CMD_MODE_BULK: case CMD_MODE_CHUNK: if (ti > DFM_MARK_CMD_PRE || tc - ti - 1 > DFM_MARK_CMD_POST) { fm_draw_err(p, S("argv too large"), 0); return -1; } } switch (m) { case CMD_MODE_EACH: case CMD_MODE_BULK: if (p->cf & CMD_CONFLICT) { r = fm_prepare_marks_conflict(p); if (r < 0) p->f |= FM_REDRAW_NAV; if (r == -1) return 0; if (r == -2 && !p->ml) return 0; } break; } cut mk = CUT_NULL; switch (m) { case CMD_MODE_SINGLE: if (p->c != SIZE_MAX) mk = fm_ent(p, p->c); r = fm_cmd_build(p, a, ti, tc, p->cf, p->mp - (tc + 1), mk, 1); break; case CMD_MODE_EACH: r = fm_cmd_build_each(p, a, ti, tc, p->cf); break; case CMD_MODE_VIRTUAL: r = fm_cmd_build_each_virtual(p, a, ti, tc, p->cf); break; case CMD_MODE_BULK: r = fm_cmd_build_bulk(p, a, ti, tc, p->cf); break; case CMD_MODE_CHUNK: r = fm_cmd_build_bulk_chunk(p, a, ti, tc, p->cf); break; } #ifdef FS_WATCH if (r != -1 && p->cf & CMD_MUT) p->f |= FM_DIRTY_WITHIN; #else if (r != -1 && (p->cf & (CMD_MUT))) fm_dir_refresh(p); #endif if (r == -2) fm_draw_err(p, S("environment variable unset"), 0); if (r < 0) p->f |= FM_ERROR; return r; } // }}} // Action {{{ static inline void act_quit(struct fm *p) { term_set_dead(&p->t, 1); } // static inline void // act_crash(struct fm *p) // { // (void) p; // *(volatile int *)0 = 1; // } static inline void act_quit_print_pwd(struct fm *p) { p->f |= FM_PRINT_PWD; act_quit(p); } static inline void act_toggle_root(struct fm *p) { cut pe = get_env("DFM_SU", DFM_SU); if (p->f & FM_ROOT) term_set_dead(&p->t, 1); else if (!pe.l) fm_draw_err(p, S("DFM_SU not set"), 0); else { cut c = p->c == SIZE_MAX ? CUT_NULL : fm_ent(p, p->c); usize o = p->ppwd.l; STR_PUSH(&p->ppwd, "env DFM_LEVEL="); str_push_c(&p->ppwd, p->nl ? p->nl : '0'); str_push_c(&p->ppwd, ' '); str_push_s(&p->ppwd, p->a0); STR_PUSH(&p->ppwd, " -s "); str_push_c(&p->ppwd, p->ds); STR_PUSH(&p->ppwd, " -v "); str_push_c(&p->ppwd, p->dv); if (c.l) { STR_PUSH(&p->ppwd, " -c "); str_push(&p->ppwd, c.d, c.l); } str_terminate(&p->ppwd); p->ppwd.l = o; if (!strcmp(basename_l(pe.d, pe.l), "su")) { const char *const a[] = { pe.d, "-c", p->ppwd.m + o, 0 }; fm_exec(p, -1, NULL, a, 0, 1); } else { p->ppwd.m[o + 15] = 0; char ds[2] = {p->ds, 0 }; char dv[2] = {p->dv, 0 }; const char *const a[] = { pe.d, "env", p->ppwd.m + 5, p->a0, "-s", ds, "-v", dv, c.l ? "-c" : 0, c.d, 0 }; fm_exec(p, -1, NULL, a, 0, 1); } } } static inline void act_cd_home(struct fm *p) { cut h = get_env("HOME", ""); if (!h.l) return; fm_path_cd(p, h.d, h.l); } static inline void act_cd_mark_directory(struct fm *p) { if (!p->vml) return; fm_path_cd(p, p->mpwd.m, p->mpwd.l); } static inline void act_cd_trash(struct fm *p) { cut e = get_env("DFM_TRASH_DIR", DFM_TRASH_DIR); if (e.l) fm_path_cd(p, e.d, e.l); else fm_draw_err(p, S("DFM_TRASH_DIR not set"), 0); } static inline void act_cd_last(struct fm *p) { fm_path_cd(p, p->ppwd.m, p->ppwd.l); } static inline void act_copy_pwd(struct fm *p) { int fd = fd_from_buf(p->pwd.m, p->pwd.l); if (fd < 0) fm_draw_err(p, S("PWD too large"), errno); else { const char *const a[] = { get_env("DFM_COPYER", DFM_COPYER).d, NULL }; fm_exec(p, fd, NULL, a, 1, 0); close(fd); fm_draw_msg(p, S("copied PWD to clipboard")); } } #define ACT_CD_BOOKMARK(N) \ static inline void \ act_cd_bookmark_##N(struct fm *p) \ { \ cut e = get_env("DFM_BOOKMARK_" #N, DFM_BOOKMARK_##N); \ if (e.l) fm_path_cd(p, e.d, e.l); \ else fm_draw_err(p, S("DFM_BOOKMARK_" #N " not set"), 0); \ } ACT_CD_BOOKMARK(0) ACT_CD_BOOKMARK(1) ACT_CD_BOOKMARK(2) ACT_CD_BOOKMARK(3) ACT_CD_BOOKMARK(4) ACT_CD_BOOKMARK(5) ACT_CD_BOOKMARK(6) ACT_CD_BOOKMARK(7) ACT_CD_BOOKMARK(8) ACT_CD_BOOKMARK(9) static inline void act_cd_up(struct fm *p) { if (p->f & FM_SEARCH) { rl_clear(&p->r); fm_filter_clear(p); if (p->c == SIZE_MAX) { fm_cursor_set(p, 0, 0); return; } usize o = ent_next(p, 0); if (o == SIZE_MAX) return; cut c = fm_ent(p, o); fm_scroll_to(p, c); p->c = o; return; } cut b = fm_path_cd_up(p); if (!b.l) return; fm_scroll_to(p, b); fm_cursor_sync(p); } static inline void act_stat(struct fm *p) { if (p->c == SIZE_MAX) return; cut e = fm_ent(p, p->c); struct stat st; if (fstatat(p->dfd, e.d, &st, AT_SYMLINK_NOFOLLOW) == -1) { fm_draw_err(p, S("stat"), errno); return; } STR_PUSH(&p->io, VT_ED2 VT_CUP1); STR_PUSH(&p->io, "Name: "); str_push(&p->io, e.d, e.l); STR_PUSH(&p->io, VT_CR VT_LF); STR_PUSH(&p->io, "Type: "); cut t = fm_file_type(st.st_mode); str_push(&p->io, t.d, t.l); STR_PUSH(&p->io, VT_CR VT_LF); if (S_ISLNK(st.st_mode)) { char b[PATH_MAX]; ssize_t r = readlinkat(p->dfd, e.d, b, sizeof(b) - 1); if (r >= 0) { b[r] = 0; STR_PUSH(&p->io, "Target: "); str_push_s(&p->io, b); STR_PUSH(&p->io, VT_CR VT_LF); } } STR_PUSH(&p->io, "Size: "); str_push_u64(&p->io, (u64)st.st_size); STR_PUSH(&p->io, VT_CR VT_LF); STR_PUSH(&p->io, "Mode: 0"); str_push_u32_b(&p->io, st.st_mode & 07777, 8, 0, 0); STR_PUSH(&p->io, ", "); ent_perm_decode(&p->io, st.st_mode, 0); STR_PUSH(&p->io, VT_CR VT_LF); STR_PUSH(&p->io, "UID: "); str_push_u32(&p->io, (u32)st.st_uid); STR_PUSH(&p->io, VT_CR VT_LF); STR_PUSH(&p->io, "GID: "); str_push_u32(&p->io, (u32)st.st_gid); STR_PUSH(&p->io, VT_CR VT_LF); STR_PUSH(&p->io, "Links: "); str_push_u64(&p->io, (u64)st.st_nlink); STR_PUSH(&p->io, VT_CR VT_LF); STR_PUSH(&p->io, "Blocks: "); str_push_u64(&p->io, (u64)st.st_blocks); STR_PUSH(&p->io, VT_CR VT_LF); STR_PUSH(&p->io, "Inode: "); str_push_u64(&p->io, (u64)st.st_ino); STR_PUSH(&p->io, VT_CR VT_LF); STR_PUSH(&p->io, "Device: "); str_push_u64(&p->io, (u64)st.st_dev); STR_PUSH(&p->io, VT_CR VT_LF); STR_PUSH(&p->io, "Access: "); str_push_time(&p->io, p->tz, st.ST_ATIM); STR_PUSH(&p->io, VT_CR VT_LF); STR_PUSH(&p->io, "Modify: "); str_push_time(&p->io, p->tz, st.ST_MTIM); STR_PUSH(&p->io, VT_CR VT_LF); STR_PUSH(&p->io, "Change: "); str_push_time(&p->io, p->tz, st.ST_CTIM); STR_PUSH(&p->io, VT_CR VT_LF); STR_PUSH(&p->io, VT_CR VT_LF "Press any key..."); fm_draw_flush(p); term_key_read(p->t.fd, &p->k); p->f |= FM_REDRAW; } static inline void act_view_image(struct fm *p) { if (p->c == SIZE_MAX) return; cut e = fm_ent(p, p->c); u64 m = ent_load(p, p->c); if (ENT_IS_DIR(ent_get(m, TYPE))) return; p->f |= FM_REDRAW; STR_PUSH(&p->io, VT_ED2 VT_CUP1); fm_draw_flush(p); p->io.l = 0; str_push_u32(&p->io, p->col); str_push_c(&p->io, 'x'); str_push_u32(&p->io, p->row - 1); int r = 0; if (p->im == 'k') { STR_PUSH(&p->io, "@0x0\0"); const char *const a[] = { DFM_IMG_KITTY_CMD, p->io.m, e.d, NULL }; r = fm_exec(p, -1, NULL, a, 0, 0); } else { str_terminate(&p->io); const char *const a[] = { DFM_IMG_CHAFA_CMD, p->io.m, e.d, NULL }; r = fm_exec(p, -1, NULL, a, 0, 0); } p->io.l = 0; if (r < 0) return; rl_clear(&p->r); STR_PUSH(&p->r.cl, VT_DECTCEM_N " viewing: "); str_push(&p->r.cl, e.d, e.l); fm_draw_buf(p, CUT(DFM_COL_NAV)); fm_draw_flush(p); rl_clear(&p->r); term_key_read(p->t.fd, &p->k); } static inline void act_open(struct fm *p) { fm_open(p); } static inline void act_view_next(struct fm *p) { switch (p->dv) { default: p->dv = 's'; break; case 's': p->dv = 'p'; break; case 'p': p->dv = 't'; break; case 't': p->dv = 'a'; break; case 'a': p->dv = 'n'; break; } p->f |= FM_REDRAW_DIR|FM_REDRAW_NAV; } static inline void act_sort_next(struct fm *p) { switch (p->ds) { default: p->ds = 'N'; break; case 'N': p->ds = 's'; break; case 's': p->ds = 'S'; break; case 'S': p->ds = 'd'; break; case 'd': p->ds = 'D'; break; case 'D': p->ds = 'e'; break; case 'e': p->ds = 'n'; break; } fm_dir_sort(p); } static inline void act_redraw(struct fm *p) { p->f |= FM_REDRAW; } static inline void act_refresh(struct fm *p) { fm_dir_refresh(p); } static inline void act_scroll_top(struct fm *p) { fm_cursor_set(p, 0, 0); p->f |= FM_REDRAW_DIR|FM_REDRAW_NAV; } static inline void act_scroll_bottom(struct fm *p) { fm_cursor_set(p, p->vl - !!p->vl, p->row -1); p->f |= FM_REDRAW_DIR|FM_REDRAW_NAV; } static inline void act_page_down(struct fm *p) { if (!p->vl) return; usize ny = p->y + p->row; if (ny >= p->vl) ny = p->vl - 1; fm_cursor_set(p, ny, p->row - 1); p->f |= FM_REDRAW_DIR|FM_REDRAW_NAV; } static inline void act_page_up(struct fm *p) { if (!p->vl) return; usize ny = (p->y > p->row) ? (p->y - p->row) : 0; fm_cursor_set(p, ny, 0); p->f |= FM_REDRAW_DIR|FM_REDRAW_NAV; } static inline void act_scroll_down(struct fm *p) { if (unlikely(p->y + 1 >= p->vl)) return; usize l = p->c; p->y++; p->o += p->o < p->row - 1; usize n = ent_next(p, p->c + 1); if (n == SIZE_MAX) return; p->c = n; fm_draw_ent(p, l); STR_PUSH(&p->io, VT_LF); fm_draw_ent(p, p->c); p->f |= FM_REDRAW_NAV; } static inline void act_scroll_up(struct fm *p) { if (unlikely(!p->y)) return; usize l = p->c; p->y--; usize n = ent_prev(p, p->c - 1); if (n == SIZE_MAX) return; p->c = n; fm_draw_ent(p, l); if (!p->o) { STR_PUSH(&p->io, VT_IL0); } else { p->o -= !!p->o; STR_PUSH(&p->io, VT_CUU1); } fm_draw_ent(p, p->c); p->f |= FM_REDRAW_NAV; } static inline void act_toggle_hidden(struct fm *p) { if (p->c == SIZE_MAX) return; cut c = fm_ent(p, p->c); if (!c.l) return; p->f ^= FM_HIDDEN; fm_filter_clear(p); fm_scroll_to(p, c); fm_cursor_sync(p); } static inline void act_search_startswith(struct fm *p) { p->sf = fm_filter_startswith; fm_filter_clear(p); p->f |= FM_SEARCH; fm_cursor_set(p, 0, 0); fm_cmd(p, &(struct fm_cmd){ .prompt = CUT("/"), .press = fm_cmd_search_press, .enter = fm_cmd_search, }); } static inline void act_search_substring(struct fm *p) { p->sf = fm_filter_substr; fm_filter_clear(p); p->f |= FM_SEARCH; fm_cursor_set(p, 0, 0); fm_cmd(p, &(struct fm_cmd){ .prompt = CUT("/*"), .press = fm_cmd_search_press, .enter = fm_cmd_search, }); } static inline void act_shell(struct fm *p) { cut sh = get_env("SHELL", "/bin/sh"); const char *const a[] = { sh.d, NULL }; fm_exec(p, -1, NULL, a, 0, 1); } static inline void act_alt_buffer(struct fm *p) { STR_PUSH(&p->io, VT_ALT_SCREEN_N); fm_draw_flush(p); term_key_read(p->t.fd, &p->k); STR_PUSH(&p->io, VT_ALT_SCREEN_Y); fm_draw_flush(p); p->f &= ~FM_ERROR; p->f |= FM_REDRAW; } static inline void act_mark_toggle(struct fm *p) { if (p->c == SIZE_MAX) return; if (!(p->f & FM_MARK_PWD)) fm_mark_clear(p); fm_mark_init(p); if (!fm_mark_toggle_idx(p, p->c)) { fm_draw_err(p, S("not enough memory to mark"), 0); return; } fm_mark_invalidate(p); fm_draw_ent(p, p->c); p->f |= FM_REDRAW_NAV; } static inline void act_mark_toggle_all(struct fm *p) { usize i = ent_next(p, 0); if (i == SIZE_MAX) return; int pr = ent_v_geto(p, i, MARK); fm_mark_clear(p); if (pr) goto e; fm_mark_init(p); p->vml = 0; usize nw = BITSET_W(p->dl); for (usize b = 0; b < nw; b++) { p->vm[b] = p->v[b]; p->vml += u64_popcount(p->vm[b]); } fm_mark_apply_bitset(p, p->vm); p->ml = 0; e: p->f |= FM_REDRAW_DIR|FM_REDRAW_NAV; } static inline void act_mark_clear(struct fm *p) { fm_mark_clear(p); p->f |= FM_REDRAW_DIR|FM_REDRAW_NAV; } static inline void act_mark_next(struct fm *p) { if (!p->vml || p->c == SIZE_MAX) return; usize b = fm_mark_find(p, p->c, 1); if (b == SIZE_MAX) return; usize r = fm_filter_pct_rank(p, b); size y = fm_scroll_to_rank(p, r); while (y-- > 0) act_scroll_down(p); } static inline void act_mark_prev(struct fm *p) { if (!p->vml || p->c == SIZE_MAX) return; usize b = fm_mark_find(p, p->c, -1); if (b == SIZE_MAX) return; usize r = fm_filter_pct_rank(p, b); size y = fm_scroll_to_rank(p, r); while (y++ < 0) act_scroll_up(p); } static inline void act_mark_invert(struct fm *p) { if (!p->vl) return; if (!(p->f & FM_MARK_PWD)) { fm_mark_clear(p); fm_mark_init(p); } p->vml = 0; usize nw = BITSET_W(p->dl); for (usize b = 0; b < nw; b++) { p->vm[b] = p->v[b] & ~p->vm[b]; p->vml += u64_popcount(p->vm[b]); } fm_mark_apply_bitset(p, p->vm); for (usize b = 0; b < nw; b++) { u64 cl = p->v[b] & ~p->vm[b]; while (cl) { usize i = (b << 6) + u64_ctz(cl); cl &= cl - 1; if (i >= p->dl) break; u32 x = ent_v_load(p, i); ent_v_set(&x, MARK, 0); ent_v_store(p, i, x); } } fm_mark_invalidate(p); p->f |= FM_REDRAW_DIR|FM_REDRAW_NAV; } static inline void act_mark_range(struct fm *p) { if (p->c == SIZE_MAX) return; if (!(p->f & FM_MARK_PWD)) fm_mark_init(p); usize ml = SIZE_MAX; usize nw = BITSET_W(p->dl); for (usize b = 0; b < nw; b++) { u64 w = p->vm[b] & p->v[b]; while (w) { usize i = (b << 6) + u64_ctz(w); w &= w - 1; if (i >= p->dl) break; ml = i; } } if (ml == SIZE_MAX) { fm_mark_toggle_idx(p, p->c); p->f |= FM_REDRAW_DIR | FM_REDRAW_NAV; return; } usize lo = MIN(ml, p->c); usize hi = MAX(ml, p->c); for (usize i = lo; i <= hi; i++) { if (!ent_v_geto(p, i, VIS)) continue; if (!ent_v_geto(p, i, MARK)) fm_mark_toggle_idx(p, i); } fm_mark_invalidate(p); p->f |= FM_REDRAW_DIR|FM_REDRAW_NAV; } // }}} // Input {{{ static inline void input_disabled(struct fm *p) { (void) p; } static inline void input_move_beginning(struct fm *p) { switch (rl_home(&p->r)) { case RL_FULL: p->f |= FM_REDRAW_CMD; break; case RL_PARTIAL: STR_PUSH(&p->io, VT_CR); p->f |= FM_REDRAW_FLUSH; break; } } static inline void input_move_end(struct fm *p) { switch (rl_end(&p->r)) { case RL_FULL: p->f |= FM_REDRAW_CMD; break; case RL_PARTIAL: STR_PUSH(&p->io, VT_CR); vt_cuf(&p->io, p->r.vx); p->f |= FM_REDRAW_FLUSH; break; } } static inline void input_move_left(struct fm *p) { usize n; switch (rl_left(&p->r, &n)) { case RL_FULL: p->f |= FM_REDRAW_CMD; break; case RL_PARTIAL: vt_cub(&p->io, n); p->f |= FM_REDRAW_FLUSH; break; } } static inline void input_move_word_left(struct fm *p) { if (rl_word_left(&p->r) != -1) p->f |= FM_REDRAW_CMD; } static inline void input_move_word_right(struct fm *p) { if (rl_word_right(&p->r) != -1) p->f |= FM_REDRAW_CMD; } static inline void input_move_right(struct fm *p) { usize n; switch (rl_right(&p->r, &n)) { case RL_FULL: p->f |= FM_REDRAW_CMD; break; case RL_PARTIAL: vt_cuf(&p->io, n); p->f |= FM_REDRAW_FLUSH; break; } } static inline void input_delete_to_end(struct fm *p) { int r = rl_delete_right(&p->r); if (r == RL_NONE) return; STR_PUSH(&p->io, VT_EL0); p->f |= FM_REDRAW_FLUSH; if (p->kp) p->kp(p, 0, rl_cl_get(&p->r), rl_cr_get(&p->r)); } static inline void input_delete_to_beginning(struct fm *p) { int r = rl_delete_left(&p->r); if (r == RL_NONE) return; p->f |= FM_REDRAW_CMD; if (p->kp) p->kp(p, 0, rl_cl_get(&p->r), rl_cr_get(&p->r)); } static inline void input_delete(struct fm *p) { usize n; switch (rl_delete(&p->r, &n)) { case RL_FULL: p->f |= FM_REDRAW_CMD; break; case RL_PARTIAL: vt_dch(&p->io, n); p->f |= FM_REDRAW_FLUSH; break; case RL_NONE: return; } if (p->kp) p->kp(p, 0, rl_cl_get(&p->r), rl_cr_get(&p->r)); } static inline void input_delete_word_left(struct fm *p) { int r = rl_delete_word_prev(&p->r); if (r == RL_NONE) return; p->f |= FM_REDRAW_CMD; if (p->kp) p->kp(p, 0, rl_cl_get(&p->r), rl_cr_get(&p->r)); } static inline void input_delete_word_right(struct fm *p) { int r = rl_delete_word_right(&p->r); if (r == RL_NONE) return; p->f |= FM_REDRAW_CMD; if (p->kp) p->kp(p, 0, rl_cl_get(&p->r), rl_cr_get(&p->r)); } static inline void input_backspace(struct fm *p) { usize n; switch (rl_backspace(&p->r, &n)) { case RL_FULL: p->f |= FM_REDRAW_CMD; break; case RL_PARTIAL: vt_cub(&p->io, n); vt_dch(&p->io, n); p->f |= FM_REDRAW_FLUSH; break; case RL_NONE: return; } if (p->kp) p->kp(p, 0, rl_cl_get(&p->r), rl_cr_get(&p->r)); } static inline void input_cancel(struct fm *p) { rl_clear(&p->r); p->kp = 0; p->kd = 0; STR_PUSH(&p->io, VT_EL2); p->f |= FM_REDRAW_NAV; } static inline void input_submit(struct fm *p) { rl_join(&p->r); fm_cmd_exec(p); p->r.vx = 0; STR_PUSH(&p->io, VT_EL2); p->f |= FM_REDRAW_NAV; } static inline void input_insert(struct fm *p) { assert(!(p->k.c & KEY_TAG)); usize n; switch (rl_insert(&p->r, p->k.c, p->k.b, p->k.l, &n)) { case RL_FULL: p->f |= FM_REDRAW_CMD; break; case RL_PARTIAL: vt_ich(&p->io, n); str_push(&p->io, (char *) p->k.b, p->k.l); p->f |= FM_REDRAW_FLUSH; break; case RL_NONE: return; } if (p->kp) p->kp(p, 0, rl_cl_get(&p->r), rl_cr_get(&p->r)); } static inline void input_insert_paste(struct fm *p) { for (bool s = false;;) { if (!term_key_read(p->t.fd, &p->k)) return; if (p->k.c == KEY_PASTE_END) return; if (p->k.b[0] == '\r' || p->k.b[0] == '\n') { if (!s) p->k.c = p->k.b[0] = ' '; s = true; } else s = false; if (KEY_GET_MOD(p->k.c) || KEY_IS_SYM(p->k.c) || p->k.c < 32) continue; input_insert(p); } } // }}} #include "config_cmd.h" #include "config_key.h" // Init {{{ static inline usize fm_io_flush(str *s, void *ctx, usize n) { (void) n; struct fm *p = ctx; write_all(p->t.fd, s->m, s->l); s->l = 0; return 0; } static inline int fm_nest(struct fm *p) { char nl[2] = "0"; cut l = get_env("DFM_LEVEL", ""); if (l.l) p->nl = *nl = *l.d + 1; return setenv("DFM_LEVEL", nl, 1); } static inline int fm_init(struct fm *p) { if (fs_watch_init(&p->p) == -1) return -1; if (fm_nest(p) == -1) return -1; p->opener = get_env("DFM_OPENER", DFM_OPENER); p->im = get_env("DFM_IMG_MODE", DFM_IMG_MODE).d[0]; p->dfd = AT_FDCWD; p->ds = DFM_DEFAULT_SORT; p->dv = DFM_DEFAULT_VIEW; p->sf = fm_filter_startswith; p->dec = sizeof(p->de); p->tz = tz_offset(); #if DFM_SHOW_HIDDEN p->f |= FM_HIDDEN; #endif if (!geteuid()) p->f |= FM_ROOT; fm_mark_clear_all(p); STR_INIT(&p->pwd, DFM_PATH_MAX, 0, 0); STR_INIT(&p->ppwd, DFM_PATH_MAX, 0, 0); STR_INIT(&p->mpwd, DFM_PATH_MAX, 0, 0); STR_INIT(&p->io, DFM_IO_MAX, fm_io_flush, p); return 0; } static inline void fm_free(struct fm *p) { fs_watch_free(&p->p); close(p->dfd); int fd = term_dead(&p->t) ? STDOUT_FILENO : STDERR_FILENO; if (!p->pwd.l) return; write_all(fd, p->pwd.m, p->pwd.l); write_all(fd, S("\n")); } // }}} // Main {{{ static inline void fm_watch_handle(struct fm *p) { for (cut n = {0};;) { char r = fs_watch_pump(&p->p, &n.d, &n.l); if (!r) return; if (r == '!' || !n.l) { fm_dir_refresh(p); return; } switch (r) { case '+': fm_dir_add(p, n); break; case '-': fm_dir_del(p, n); break; case '~': fm_dir_del(p, n); fm_dir_add(p, n); break; } } } static inline void fm_update(struct fm *p) { term_reap(); fm_watch_handle(p); if (!(p->f & FM_DIRTY)) return; p->f &= ~FM_DIRTY; p->f |= FM_REDRAW_DIR|FM_REDRAW_NAV; fm_dir_sort(p); fm_cursor_sync(p); if (p->f & FM_DIRTY_WITHIN && p->st) { u64 m = ent_load_off(p, p->st); cut st = { p->de + p->st, ent_get(m, LEN) }; fm_scroll_to(p, st); p->st = 0; p->f &= ~FM_DIRTY_WITHIN; } } static inline void fm_draw(struct fm *p) { if ((p->f & FM_REDRAW) == FM_REDRAW) { STR_PUSH(&p->io, VT_ED2); fm_clear_cache(p); } if (p->f & FM_REDRAW_DIR) fm_draw_dir(p); if (p->f & FM_REDRAW_NAV) fm_draw_nav(p); if (p->f & FM_REDRAW_CMD) fm_draw_cmd(p); if (p->f & FM_REDRAW) { if (p->kp || p->kd) { vt_cup(&p->io, p->r.vx, p->row + DFM_MARGIN); STR_PUSH(&p->io, VT_DECTCEM_Y); } else { vt_cup(&p->io, 0, p->o + 1); STR_PUSH(&p->io, VT_DECTCEM_N); } fm_draw_flush(p); } p->f &= ~FM_REDRAW; } static inline void fm_input(struct fm *p) { if (!term_key_read(p->t.fd, &p->k)) return; if (p->r.pr.l) fm_key_input(p->k.c)(p); else fm_key(p->k.c)(p); } static inline int fm_run(struct fm *p) { if (fm_term_init(p) < 0) return -1; rl_init(&p->r, p->col, CUT_NULL); if (p->ast.l) { fm_scroll_to(p, p->ast); fm_cursor_sync(p); } for (; likely(!term_dead(&p->t)); ) { fm_update(p); fm_draw(p); int e = term_wait(&p->t); if (e & TERM_WAIT_WCH) if (fm_term_resize(p) < 0) fm_draw_err(p, S("resize failed"), errno); if (e & TERM_WAIT_KEY) fm_input(p); } fm_term_free(p); if (!(p->f & FM_PRINT_PWD)) p->pwd.l = 0; return 0; } int main(int argc, char *argv[]) { static struct fm p; p.a0 = argv[0]; str *s = &p.pwd; if (fm_init(&p) < 0) { STR_PUSH(s, "error?: "); str_push_s(s, strerror(errno)); goto e; } const char *pwd = "."; struct argv A = arg_init(argc, argv); for (struct arg a; (a = arg_next(&A)).sign != -1;) { const char *n; switch (a.name) { case 'H': p.f ^= (-(a.sign == '+') ^ p.f) & FM_HIDDEN; continue; case 'p': p.f |= FM_PICKER; continue; case 'o': n = arg_next_positional(&A); if (!n) goto arg_no_val; p.opener.d = n; continue; case 's': n = arg_next_positional(&A); if (!n) goto arg_no_val; p.ds = fm_sort_fn(*n) ? *n : 'n'; continue; case 'v': n = arg_next_positional(&A); if (!n) goto arg_no_val; p.dv = *n; continue; case 'c': n = arg_next_positional(&A); if (!n) goto arg_no_val; p.ast = (cut){ n, strlen(n) }; continue; case '-': if (!strcmp(a.pos, "--help")) { STR_PUSH(s, DFM_HELP); term_set_dead(&p.t, 1); goto e; } else if (!strcmp(a.pos, "--version")) { STR_PUSH(s, CFG_NAME " " CFG_VERSION " " CC_COMMIT " (" CC_BRANCH ") " CC_DATE); goto e; } STR_PUSH(s, "unknown arg "); str_push_s(s, a.pos); goto e; default: if (unlikely(a.name)) { STR_PUSH(s, "unknown arg "); str_push_c(s, a.sign); str_push_c(s, a.name); goto e; } pwd = a.pos; continue; } arg_no_val: STR_PUSH(s, "arg "); str_push_c(s, a.sign); str_push_c(s, a.name); STR_PUSH(s, " missing value"); goto e; } if (!fm_path_chdir(&p, pwd)) { STR_PUSH(s, "cd: '"); str_push_s(s, pwd); STR_PUSH(s, "': "); str_push_s(s, strerror(errno)); goto e; } if (fm_run(&p) < 0) { STR_PUSH(s, "term: '"); str_push_s(s, strerror(errno)); goto e; } fm_free(&p); return EXIT_SUCCESS; e: fm_free(&p); return EXIT_FAILURE; } // }}}