/*
 * 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 <assert.h>
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>

#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>

#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 <opener>    program to use when opening files (default: xdg-open)\n"
  "-c <name>      position cursor over 'name' instead of first entry\n"
  "-q <query>     start in search results (\"*query\" for substring)\n"
  "-s <mode>      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 <mode>      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;
  const char *aq;
  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);
    str_push_c(&p->ppwd, ' ');
    str_push_c(&p->ppwd, p->f & FM_HIDDEN ? '+' : '-');
    STR_PUSH(&p->ppwd, "H ");
    str_push_c(&p->ppwd, 0);
    bool q = p->f & FM_SEARCH && p->vql;
    usize qo = p->ppwd.l + 4;
    if (q) {
      STR_PUSH(&p->ppwd, " -q ");
      if (p->sf == fm_filter_substr) str_push_c(&p->ppwd, '*');
      str_push(&p->ppwd, p->vq, p->vql);
    }
    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,
        p->f & FM_HIDDEN ? "+H" : "-H",
        "-s", ds, "-v", dv, c.l ? "-c" : 0, c.d,
        q ? "-q" : 0, p->ppwd.m + qo, 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->aq) {
    if (p->aq[0] == '*') {
      p->sf = fm_filter_substr;
      p->aq++;
    } else
      p->sf = fm_filter_startswith;
    str_push_s(&p->r.cl, p->aq);
    str_terminate(&p->r.cl);
    rl_cl_sync(&p->r);
    p->f |= FM_SEARCH;
    fm_cmd_search(p, &p->r.cl);
    rl_clear(&p->r);
  }

  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|FM_PICKER))) 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 'q':
      p.aq = arg_next_positional(&A);
      if (!p.aq) goto arg_no_val;
      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;
}

// }}}

